diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..532c2e6 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,9 @@ +[target.thumbv6m-none-eabi] +runner = "arm-none-eabi-gdb" +rustflags = [ + "-C", "link-arg=-Wl,-Tlink.x", + "-C", "link-arg=-nostartfiles", +] + +[build] +target = "thumbv6m-none-eabi" diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..26a03e4 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,6 @@ +target remote :3333 +monitor arm semihosting enable +set print asm-demangle on +load +#break main +continue diff --git a/Cargo.toml b/Cargo.toml index 55774c9..6217771 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "src/getting-started", + "src/hello-world", ] [profile.dev] diff --git a/src/getting-started/memory.x b/memory.x similarity index 100% rename from src/getting-started/memory.x rename to memory.x diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f82f1ee..1a1ea77 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -13,14 +13,17 @@ - [Verify the installation](setup/VERIFY.md) - [Getting started](getting-started/00.00.README.md) - - [Building](getting-started/01.00.PROJECT.md) + - [Building](getting-started/01.00.BUILD.md) - [Flashing](getting-started/02.00.FLASH.md) - - [LED](getting-started/03.00.LED.md) - - [Debugging](getting-started/04.00.DEBUG.md) + - [Debugging](getting-started/03.00.DEBUG.md) -- [WIP - Hello world](hello-world/00.00.README.md) - - [Semi-hosting](hello-world/01.00.SEMIHOSTING.md) - - [UART](hello-world/02.00.UART.md) +- [Hello world](hello-world/00.00.README.md) + - [Semihosting](hello-world/01.00.SEMIHOSTING.md) + - [Serial communication](hello-world/02.00.UART.md) + - [*nix](hello-world/02.01.NIX.md) + - [Windows](hello-world/02.02.WINDOWS.md) + - [LED](hello-world/03.00.LED.md) + - [Solution](hello-world/03.01.SOLUTION.md) - [Choose your own adventure](choice/00.00.README.md) @@ -46,6 +49,8 @@ [Explore](appendix/explore.md) +[GDB cheatsheet](appendix/gdb.md) + [General troubleshooting](appendix/troubleshooting.md) - -Connect the mirco:bit to your computer and run the following commands on a new terminal. - - +Connect the micro:bit to your computer and run the following commands on a new terminal. We need to give OCD the name of the interfaces we are using: @@ -156,45 +149,16 @@ This will automate the last few steps so we don't need to repeatedly do the same `.gdbinit` ``` +# Connects GDB to OpenOCD server port target remote :3333 +# (optional) Unmangle function names when debugging +set print asm-demangle on +# Load your program, breaks at entry load +# (optional) Add breakpoint at function +break rustled::main +# Continue with execution continue ``` -## LED - -Let us now turn on an LED! But how? - -Well, first we should look at the documentation of our crate, -and you should be able to figure out how to get access to the gpio, -and set individual pins high and low: - -``` rust -if let Some(p) = microbit::Peripherals::take() { - let mut gpio = p.GPIO.split(); - let mut pin1 = gpio.pin1.into_push_pull_output(); - pin1.set_high(); -} -``` - -Next we need to see how these pins are hooked up, -for that we need [the micro:bit schematics][schematics] linked to at the bottom of [the hardware overview][hw]. -On the first sheet you should find a diagram with a grid of numbered LEDs. - -> If you do not know much about electronics: -> Each row and column (labelled ROW and COL) represent a GPIO output pin. -> The components labelled LED are light emitting diodes; -> LEDs only let current flow one way, and only emit light when current is flowing. -> If a row is set high, high voltage, and a column is set low, low voltage, -> the LED at the point that they cross will have a potential difference across it, -> so current will flow and it will light up. - -The 5x5 array of LEDs are actually wired up as a 3x9 array (3 rows by 9 columns), with 2 missing. -This is usually done to make the circuit design easier. - -The fifth sheet shows how each row and column correspond to each GPIO pin. - -[hw]: http://tech.microbit.org/hardware/ -[schematics]: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf - -You should now have enough information to try and turn on an LED. +Now we can learn how to debug code on the micro:bit. \ No newline at end of file diff --git a/src/getting-started/04.00.DEBUG.md b/src/getting-started/03.00.DEBUG.md similarity index 76% rename from src/getting-started/04.00.DEBUG.md rename to src/getting-started/03.00.DEBUG.md index b84db3c..ec103b5 100644 --- a/src/getting-started/04.00.DEBUG.md +++ b/src/getting-started/03.00.DEBUG.md @@ -1,5 +1,22 @@ # Debugging +## Setup + +Before we start, let's add some code to debug: + +``` rust +// -- snip -- +entry!(main); +fn main() -> ! { + let _y; + let x = 42; + _y = x; + loop {} +} +``` + +## GDB session + We are already inside a debugging session so let's debug our program. After the `load` command, our program is stopped at its *entry point*. This is indicated by the @@ -76,13 +93,15 @@ $3 = 134219052 $4 = (i32 *) 0x10001fd8 ``` -As expected, `x` contains the value `42`. `y`, however, contains the value `134219052` (?). Because -`_y` has not been initialized yet, it contains some garbage value. +As expected, `x` contains the value `42`. +`_y` however, contains the value `134219052` (?). +Because `_y` has not been initialized yet, it contains some garbage value. -The command `print &x` prints the address of the variable `x`. The interesting bit here is that GDB -output shows the type of the reference: `i32*`, a pointer to an `i32` value. Another interesting -thing is that the addresses of `x` and `_y` are very close to each other: their addresses are just -`4` bytes apart. +The command `print &x` prints the address of the variable `x`. +The interesting bit here is that GDB output shows the type of the reference: +`i32*`, a pointer to an `i32` value. +Another interesting thing is that the addresses of `x` and `_y` are very close to each other: +their addresses are just `4` bytes apart. Instead of printing the local variables one by one, you can also use the `info locals` command: @@ -116,8 +135,8 @@ command and advance one instruction at a time using `stepi`. (gdb) layout asm ``` -If you are not using the TUI mode, you can use the `disassemble /m` command to disassemble the -program around the line you are currently at. +If you are not using the TUI mode, +you can use the `disassemble /m` command to disassemble the program around the line you are currently at. ``` (gdb) disassemble /m @@ -145,8 +164,8 @@ End of assembler dump. See the fat arrow `=>` on the left side? It shows the instruction the processor will execute next. -If not inside the TUI mode on each `stepi` command GDB will print the statement, the line number -*and* the address of the instruction the processor will execute next. +If not inside the TUI mode on each `stepi` command GDB will print the statement, +the line number *and* the address of the instruction the processor will execute next. ``` (gdb) stepi @@ -156,7 +175,8 @@ If not inside the TUI mode on each `stepi` command GDB will print the statement, 0x08000194 17 loop {} ``` -One last trick before we move to something more interesting. Enter the following commands into GDB: +One last trick before we move to something more interesting. +Enter the following commands into GDB: ``` (gdb) monitor reset halt @@ -176,17 +196,16 @@ Breakpoint 1, led_roulette::main () at src/main.rs:8 We are now back at the beginning of `main`! `monitor reset halt` will reset the microcontroller and stop it right at the program entry point. -The following `continue` command will let the program run freely until it reaches the `main` -function that has a breakpoint on it. +The following `continue` command will let the program run freely until it reaches the `main` function that has a breakpoint on it. -This combo is handy when you, by mistake, skipped over a part of the program that you were -interested in inspecting. You can easily roll back the state of your program back to its very -beginning. +This combo is handy when you, by mistake, +skipped over a part of the program that you were interested in inspecting. +You can easily roll back the state of your program back to its very beginning. -> **The fine print**: This `reset` command doesn't clear or touch RAM. That memory will retain its -> values from the previous run. That shouldn't be a problem though, unless your program behavior -> depends of the value of *uninitialized* variables but that's the definition of Undefined Behavior -> (UB). +> **The fine print**: This `reset` command doesn't clear or touch RAM. +> That memory will retain its values from the previous run. +> That shouldn't be a problem though, unless your program behavior depends of the value of *uninitialized* variables, +> but that's the definition of *undefined behavior* (UB). We are done with this debug session. You can end it with the `quit` command. @@ -209,3 +228,9 @@ Ending remote debugging. Don't close OpenOCD though! We'll use it again and again later on. It's better just to leave it running. + +## What next? + +In the next chapter we will learn +how to send messages from the micro:bit to your computer, +as well as howt to control the HAL GPIO. diff --git a/src/getting-started/Cargo.toml b/src/getting-started/Cargo.toml index c54dec3..98c8a98 100644 --- a/src/getting-started/Cargo.toml +++ b/src/getting-started/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustled" +name = "start" version = "0.1.0" [dependencies] diff --git a/src/getting-started/src/main.rs b/src/getting-started/src/main.rs index 87c9520..99577d6 100644 --- a/src/getting-started/src/main.rs +++ b/src/getting-started/src/main.rs @@ -1,29 +1,30 @@ -#![deny(unsafe_code)] #![no_std] +#![no_main] -extern crate aux5; +extern crate panic_abort; +extern crate cortex_m_rt as rt; -use aux5::prelude::*; -use aux5::{Delay, Leds}; +#[macro_use(entry, exception)] +extern crate microbit; -fn main() { - let (mut delay, mut leds): (Delay, Leds) = aux5::init(); +use rt::ExceptionFrame; - let period = 50_u16; - let mut step: usize = 0; - loop { - match step % 2 { - 0 => leds[step/2].on(), - 1 => { - let wrap_step = ((step + 16 - 3) / 2) % 8; - leds[wrap_step].off(); - }, - _ => unreachable!(), - } - delay.delay_ms(period); - match step { - 15 => step = 0, - _ => step += 1, - } - } +exception!(HardFault, hard_fault); + +fn hard_fault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} + +exception!(*, default_handler); + +fn default_handler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} + +entry!(main); +fn main() -> ! { + let _y; + let x = 42; + _y = x; + loop {} } diff --git a/src/hello-world/.gdbinit b/src/hello-world/.gdbinit new file mode 100644 index 0000000..2107960 --- /dev/null +++ b/src/hello-world/.gdbinit @@ -0,0 +1,5 @@ +target remote :3333 +monitor arm semihosting enable +load +break hello::main +continue diff --git a/src/hello-world/00.00.README.md b/src/hello-world/00.00.README.md new file mode 100644 index 0000000..716ed14 --- /dev/null +++ b/src/hello-world/00.00.README.md @@ -0,0 +1 @@ +# Hello world diff --git a/src/hello-world/01.00.SEMIHOSTING.md b/src/hello-world/01.00.SEMIHOSTING.md new file mode 100644 index 0000000..36810ee --- /dev/null +++ b/src/hello-world/01.00.SEMIHOSTING.md @@ -0,0 +1,77 @@ +# Semihosting + +Semihosting is a feature which allows targets without I/O support to use the I/O of the host. +When the special `BKPT` instruction is reached, the host reads the characters directly from the micro:bit's memory. + +## Semihosting is slow + +The most important thing to remember about semihosting is that it is slow. +The processor halts entirely for each operation, making each operation take 107 milliseconds. +This means that if you are doing any time sensitive work, you should not use it for logging. +[Check out this blog post for more information.](http://blog.japaric.io/itm/) + +## GDB + +The first thing to do is to enable semihosting in GDB. +As before, we will add this to `.gdbinit` to avoid typing it every time. + +`.gdbinit` + +``` gdb +target remote :3333 +monitor arm semihosting enable +load +``` + +## OpenOCD + +You may have incorrectly assumed at this point that the outpust would appear in GDB. +Remember that GDB simply connects to OpenOCD to interface with the micro:bit. +OpenOCD is very loud currently, +so it will be quite hard to see the output of our micro:bit in the noise. +Fix this by stopping and restarting it with logging dumped to a file. + +``` console +openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -l /tmp/openocd.log +``` + +## Panic + +The easiest way to use semihosting is to use it for the `panic!` macro. + +`Cargo.toml` + +``` toml +panic-semihosting = "" +``` + +You can then see what happens if you add a `panic!` to your code: + +``` rust +fn main() -> ! { + panic!("test-panic"); +} +``` + +``` +Open On-Chip Debugger 0.10.0 +Licensed under GNU GPL v2 +For bug reports, read + http://openocd.org/doc/doxygen/bugs.html +panicked at 'test-panic', src/hello-world/src/main.rs:27:5 +``` + +## stdout + +Finally, this is how to write to stdout, although writing to stderr is just as easy. + +``` rust +extern crate cortex_m_semihosting as sh; +use core::fmt::Write; +use sh::hio; +// -- snip -- + let mut stdout = hio::hstdout().unwrap(); + stdout.write_str("semitest\n\r").unwrap(); +``` + +And that's it! diff --git a/src/hello-world/02.00.UART.md b/src/hello-world/02.00.UART.md new file mode 100644 index 0000000..07e1058 --- /dev/null +++ b/src/hello-world/02.00.UART.md @@ -0,0 +1,38 @@ +# Serial communication + +The micro:bit has a perihperal called UART, +a Universal Asynchronous Receiver/Transmitter. +This is a form of serial communication, data is transferred serially, +i.e. one bit at a a time. +It is asynchronous communication, and there is no clock signal to dictate the bitrate, +intead this is agreed upon beforehand. +The protocol has frames consisting of a start bit, data bits, parity bits, and stop bits. +We will be using 8 bits per frame: 1 start, 6 data and 1 stop. +The data rate is called the _baud rate_, and we will use 115200bps. + +## USB + +The micro:bit allows us to transmit and receive this serial communication over USB +with no additional hardware, +along with our flashing and debugging activities. + +## Tooling + +To read and write to the serial bus from your computer, you will need to configure your tooling: + +- [*nix](hello-world/02.01.NIX.html) +- [Windows](hello-world/02.02.WINDOWS.html) + +## Code + +``` rust +// -- snip -- +if let Some(p) = microbit::Peripherals::take() { + let mut gpio = p.GPIO.split(); + // Configure RX and TX pins accordingly + let tx = gpio.pin24.into_push_pull_output().downgrade(); + let rx = gpio.pin25.into_floating_input().downgrade(); + let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split(); + let _ = write!(tx, "\n\rStarting!\n\r"); +} +``` diff --git a/src/hello-world/02.01.NIX.md b/src/hello-world/02.01.NIX.md new file mode 100644 index 0000000..934f3f4 --- /dev/null +++ b/src/hello-world/02.01.NIX.md @@ -0,0 +1,83 @@ +# *nix tooling + +Connect the serial module to your laptop and let's find out what name the OS assigned to it. + +``` console +$ dmesg | grep -i tty +(..) +[ +0.000155] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0 +``` + +> **NOTE** On macs, the USB device will named like this: `cu.usbserial-*`. Adjust the following +> commands accordingly! + +But what's this `ttyUSB0` thing? It's a file of course! Everything is a file in *nix: + +``` console +$ ls -l /dev/ttyUSB0 +crw-rw---- 1 root uucp 188, 0 Oct 27 00:00 /dev/ttyUSB0 +``` + +You can send out data by simply writing to this file: + +``` console +$ echo 'Hello, world!' > /dev/ttyUSB0 +``` + +You should see the TX (red) LED on the serial module blink, just once and very fast! + +## minicom + +Dealing with serial devices using `echo` is far from ergonomic. So, we'll use the program `minicom` +to interact with the serial device using the keyboard. + +We must configure `minicom` before we use it. There are quite a few ways to do that but we'll use a +`.minirc.dfl` file in the home directory. Create a file in `~/.minirc.dfl` with the following +contents: + +``` console +$ cat ~/.minirc.dfl +pu baudrate 115200 +pu bits 8 +pu parity N +pu stopbits 1 +pu rtscts No +pu xonxoff No +``` + +> **NOTE** Make sure this file ends in a newline! Otherwise, `minicom` will fail to read it. + +That file should be straightforward to read (except for the last two lines), but nonetheless let's +go over it line by line: + +- `pu baudrate 115200`. Sets baud rate to 115200 bps. +- `pu bits 8`. 8 bits per frame. +- `pu parity N`. No parity check. +- `pu stopbits 1`. 1 stop bit. +- `pu rtscts No`. No hardware control flow. +- `pu xonxoff No`. No software control flow. + +Once that's in place. We can launch `minicom` + +``` console +$ minicom -D /dev/ttyUSB0 -b 115200 +``` + +This tells `minicom` to open the serial device at `/dev/ttyUSB0` and set its baud rate to 115200. +A text-based user interface (TUI) will pop out. + +You can now send data using the keyboard! Go ahead and type something. +Note that the TUI *won't* echo back what you type (nothing will happen when you type) +but you'll see TX (red) LED on the serial module blink with each keystroke. + +## `minicom` commands + +`minicom` exposes commands via keyboard shortcuts. On Linux, the shortcuts start with `Ctrl+A`. On +mac, the shortcuts start with the `Meta` key. Some useful commands below: + +- `Ctrl+A` + `Z`. Minicom Command Summary +- `Ctrl+A` + `C`. Clear the screen +- `Ctrl+A` + `X`. Exit and reset +- `Ctrl+A` + `Q`. Quit with no reset + +> **NOTE** mac users: In the above commands, replace `Ctrl+A` with `Meta`. diff --git a/src/hello-world/02.02.WINDOWS.md b/src/hello-world/02.02.WINDOWS.md new file mode 100644 index 0000000..02f9a21 --- /dev/null +++ b/src/hello-world/02.02.WINDOWS.md @@ -0,0 +1,43 @@ +# Windows tooling + +Before plugging the Serial module, run the following command on the terminal: + +``` console +$ mode +``` + +It will print a list of devices that are connected to your laptop. The ones that start with `COM` in +their names are serial devices. This is the kind of device we'll be working with. Take note of all +the `COM` *ports* `mode` outputs *before* plugging the serial module. + +Now, plug the Serial module and run the `mode` command again. You should see a new `COM` port appear +on the list. That's the COM port assigned to the serial module. + +Now launch `putty`. A GUI will pop out. + +

+ +

+ +On the starter screen, which should have the "Session" category open, pick "Serial" as the +"Connection type". On the "Serial line" field enter the `COM` device you got on the previous step, +for example `COM3`. + +Next, pick the "Connection/Serial" category from the menu on the left. On this new view, make sure +that the serial port is configured as follows: + +- "Speed (baud)": 115200 +- "Data bits": 8 +- "Stop bits": 1 +- "Parity": None +- "Flow control": None + +Finally, click the Open button. A console will show up now: + +

+ +

+ +If you type on this console, the TX (red) LED on the Serial module should blink. Each key stroke +should make the LED blink once. Note that the console won't echo back what you type so the screen +will remain blank. diff --git a/src/hello-world/03.00.LED.md b/src/hello-world/03.00.LED.md new file mode 100644 index 0000000..96db5a7 --- /dev/null +++ b/src/hello-world/03.00.LED.md @@ -0,0 +1,37 @@ +# LED + +Let us now turn on an LED! But how? + +Well, first we should look at the documentation of our crate, +and you should be able to figure out how to get access to the gpio, +and set individual pins high and low: + +``` rust +if let Some(p) = microbit::Peripherals::take() { + let mut gpio = p.GPIO.split(); + let mut pin1 = gpio.pin1.into_push_pull_output(); + pin1.set_high(); +} +``` + +Next we need to see how these pins are hooked up, +for that we need [the micro:bit schematics][schematics] linked to at the bottom of [the hardware overview][hw]. +On the first sheet you should find a diagram with a grid of numbered LEDs. + +> If you do not know much about electronics: +> Each row and column (labelled ROW and COL) represent a GPIO output pin. +> The components labelled LED are light emitting diodes; +> LEDs only let current flow one way, and only emit light when current is flowing. +> If a row is set high, high voltage, and a column is set low, low voltage, +> the LED at the point that they cross will have a potential difference across it, +> so current will flow and it will light up. + +The 5x5 array of LEDs are actually wired up as a 3x9 array (3 rows by 9 columns), with 2 missing. +This is usually done to make the circuit design easier. + +The fifth sheet shows how each row and column correspond to each GPIO pin. + +[hw]: http://tech.microbit.org/hardware/ +[schematics]: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf + +You should now have enough information to try and turn on an LED. diff --git a/src/getting-started/03.00.LED.md b/src/hello-world/03.01.SOLUTION.md similarity index 78% rename from src/getting-started/03.00.LED.md rename to src/hello-world/03.01.SOLUTION.md index 419d543..48a5025 100644 --- a/src/getting-started/03.00.LED.md +++ b/src/hello-world/03.01.SOLUTION.md @@ -1,4 +1,4 @@ -# LED +# Solution This is my solution: @@ -41,5 +41,7 @@ fn default_handler(irqn: i16) { It is worth noting that pin4 starts low, so does not need to be explicitly set low. -You now know enough to start playing around with the LED display, -but before you do, you should know to debug your Rust code on the micro:bit. +You now know enough to start playing around with the LED display and the GPIO in general. +Before you do, +you should know that the microbit crate already includes an abstraction for the LED display, +and how this is implemented is demonstrated in the [LED display chapter](display/00.00.README.html) diff --git a/src/hello-world/Cargo.toml b/src/hello-world/Cargo.toml new file mode 100644 index 0000000..c8b2012 --- /dev/null +++ b/src/hello-world/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hello" +version = "0.1.0" + +[dependencies] +cortex-m-rt="~0.5" +cortex-m-semihosting="" +panic-semihosting = "~0.3" +microbit="~0.5" diff --git a/src/hello-world/src/main.rs b/src/hello-world/src/main.rs new file mode 100644 index 0000000..2a987c1 --- /dev/null +++ b/src/hello-world/src/main.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +extern crate panic_semihosting; +extern crate cortex_m_rt as rt; +extern crate cortex_m_semihosting as sh; + +#[macro_use(entry, exception)] +extern crate microbit; + +use core::fmt::Write; +use rt::ExceptionFrame; +use sh::hio; + +exception!(HardFault, hard_fault); + +fn hard_fault(ef: &ExceptionFrame) -> ! { + panic!("{:#?}", ef); +} + +exception!(*, default_handler); + +fn default_handler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} + +entry!(main); +fn main() -> ! { + let mut stdout = hio::hstdout().unwrap(); + stdout.write_str("semihosting test\n\r").unwrap(); + + if let Some(p) = microbit::Peripherals::take() { + let mut gpio = p.GPIO.split(); + // Configure RX and TX pins accordingly + let tx = gpio.pin24.into_push_pull_output().downgrade(); + let rx = gpio.pin25.into_floating_input().downgrade(); + let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split(); + let _ = write!(tx, "\n\rserial test\n\r"); + } + + panic!("test-panic"); +}