diff --git a/.github/bors.toml b/.github/bors.toml index 9fed1a7f4..fb56de254 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -3,9 +3,9 @@ delete_merged_branches = true required_approvals = 1 status = [ "build-book", - "build-chapter (05-led-roulette)", - "build-chapter (07-uart)", - "build-chapter (08-i2c)", - "build-chapter (15-led-compass)", - "build-chapter (16-punch-o-meter)", + "build-chapter-microbit (05-led-roulette)", + "build-chapter-old (07-uart)", + "build-chapter-old (08-i2c)", + "build-chapter-old (15-led-compass)", + "build-chapter-old (16-punch-o-meter)", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97083f6b..05bff50be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,12 +7,36 @@ on: jobs: # Check a build succeeds for each chapter that contains example code. - build-chapter: + build-chapter-microbit: runs-on: ubuntu-20.04 strategy: matrix: chapter: - 05-led-roulette + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv6m-none-eabi + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + - name: Build chapter micro:bit v1 + working-directory: src/${{ matrix.chapter }} + run: cargo build --features v1 --target thumbv6m-none-eabi + - name: Build chapter micro:bit v2 + working-directory: src/${{ matrix.chapter }} + run: cargo build --features v2 --target thumbv7em-none-eabihf + # Until everything is microbit + build-chapter-old: + runs-on: ubuntu-20.04 + strategy: + matrix: + chapter: - 07-uart - 08-i2c - 15-led-compass @@ -24,7 +48,7 @@ jobs: profile: minimal toolchain: stable target: thumbv6m-none-eabi - - uses: actions-rs/toolchain@v1 # Until everything has been rewritten + - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable diff --git a/src/05-led-roulette/.cargo/config b/src/05-led-roulette/.cargo/config index a0ec1777f..6260c5718 100644 --- a/src/05-led-roulette/.cargo/config +++ b/src/05-led-roulette/.cargo/config @@ -2,6 +2,3 @@ rustflags = [ "-C", "link-arg=-Tlink.x", ] - -[build] -target = "thumbv6m-none-eabi" diff --git a/src/05-led-roulette/Cargo.toml b/src/05-led-roulette/Cargo.toml index 776e8a465..0dedefeae 100644 --- a/src/05-led-roulette/Cargo.toml +++ b/src/05-led-roulette/Cargo.toml @@ -4,10 +4,24 @@ version = "0.1.0" authors = ["Henrik Böving "] edition = "2018" +[dependencies.microbit-v2] +version = "0.10.1" +git = "https://github.com/nrf-rs/microbit/" +optional = true + + +[dependencies.microbit] +version = "0.10.1" +git = "https://github.com/nrf-rs/microbit/" +optional = true + [dependencies] cortex-m = "0.6.0" cortex-m-rt = "0.6.10" panic-halt = "0.2.0" -nrf51-hal = "0.11.0" rtt-target = { version = "0.2.2", features = ["cortex-m"] } panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } + +[features] +v2 = ["microbit-v2"] +v1 = ["microbit"] diff --git a/src/05-led-roulette/Embed.toml b/src/05-led-roulette/Embed.toml index 91584ec92..f89445be1 100644 --- a/src/05-led-roulette/Embed.toml +++ b/src/05-led-roulette/Embed.toml @@ -1,5 +1,8 @@ [default.general] -chip = "nrf51822_xxAA" +# v2 +# chip = "nrf52833" +# v1 +# chip = "nrf51822" [default.reset] halt_afterwards = true diff --git a/src/05-led-roulette/README.md b/src/05-led-roulette/README.md index b3e928d6a..f645accd6 100644 --- a/src/05-led-roulette/README.md +++ b/src/05-led-roulette/README.md @@ -10,7 +10,7 @@ I'm going to give you a high level API to implement this app but don't worry we' stuff later on. The main goal of this chapter is to get familiar with the *flashing* and debugging process. -The starter code is in the `src` directory of that repository. Inside that directory there are more +The starter code is in the `src` directory of the book repository. Inside that directory there are more directories named after each chapter of this book. Most of those directories are starter Cargo projects. @@ -49,7 +49,8 @@ Furthermore there is also an `Embed.toml` file This file tells `cargo-embed` that: -* we are working with a nrf51822, +* we are working with either an nrf52833 or nrf51822, you will again have to remove the comments from the + chip you are using, just like you did in chapter 3. * we want to halt the chip after we flashed it so our program does not instantly jump to the loop * we want to disable RTT, RTT being a protocol that allows the chip to send text to a debugger. You have in fact already seen RTT in action, it was the protocol that sent "Hello World" in chapter 3. diff --git a/src/05-led-roulette/build-it.md b/src/05-led-roulette/build-it.md index 3766f0a96..6215b4843 100644 --- a/src/05-led-roulette/build-it.md +++ b/src/05-led-roulette/build-it.md @@ -5,8 +5,8 @@ architecture than your computer we'll have to cross compile. Cross compiling in as passing an extra `--target` flag to `rustc`or Cargo. The complicated part is figuring out the argument of that flag: the *name* of the target. -The microcontroller in the micro:bit has a Cortex-M0 processor in it. `rustc` knows how to cross compile -to the Cortex-M architecture and provides several different targets that cover the different processor +As we already know the microcontroller on the micro:bit v2 has a Cortex-M4F processor in it, the one on v1 a Cortex-M0. +`rustc` knows how to cross-compile to the Cortex-M architecture and provides several different targets that cover the different processors families within that architecture: - `thumbv6m-none-eabi`, for the Cortex-M0 and Cortex-M1 processors @@ -16,11 +16,14 @@ families within that architecture: - `thumbv8m.main-none-eabi`, for the Cortex-M33 and Cortex-M35P processors - `thumbv8m.main-none-eabihf`, for the Cortex-M33**F** and Cortex-M35P**F** processors -For the micro:bit, we'll use the `thumbv6m-none-eabi` target. Before cross compiling you have to -download pre-compiled version of the standard library (a reduced version of it actually) for your -target. That's done using `rustup`: +For the micro:bit v2, we'll use the `thumbv7em-none-eabihf` target, for v1 the `thumbv6m-none-eabi` one. +Before cross-compiling you have to download a pre-compiled version of the standard library +(a reduced version of it, actually) for your target. That's done using `rustup`: ``` console +# For micro:bit v2 +$ rustup target add thumbv7em-none-eabihf +# For micro:bit v1 $ rustup target add thumbv6m-none-eabi ``` @@ -30,36 +33,64 @@ You only need to do the above step once; `rustup` will re-install a new standard With the `rust-std` component in place you can now cross compile the program using Cargo: ``` console -$ # make sure you are in the `src/05-led-roulette` directory +# make sure you are in the `src/05-led-roulette` directory -$ cargo build --target thumbv6m-none-eabi +# For micro:bit v2 +$ cargo build --features v2 --target thumbv7em-none-eabihf Compiling semver-parser v0.7.0 Compiling typenum v1.12.0 - Compiling proc-macro2 v1.0.19 - Compiling unicode-xid v0.2.1 Compiling cortex-m v0.6.3 (...) - Compiling as-slice v0.1.3 - Compiling aligned v0.3.4 - Compiling cortex-m-rt-macros v0.1.8 - Compiling nrf-hal-common v0.11.1 - Finished dev [unoptimized + debuginfo] target(s) in 18.69s + Compiling microbit-v2 v0.10.1 + Finished dev [unoptimized + debuginfo] target(s) in 33.67s + +# For micro:bit v1 +$ cargo build --features v1 --target thumbv6m-none-eabi + Compiling fixed v1.2.0 + Compiling syn v1.0.39 + Compiling cortex-m v0.6.3 + (...) + Compiling microbit v0.10.1 + Finished dev [unoptimized + debuginfo] target(s) in 22.73s ``` > **NOTE** Be sure to compile this crate *without* optimizations. The provided Cargo.toml > file and build command above will ensure optimizations are off. -> **NOTE** If you have looked into `.cargo/config` you will have noticed that the target - is actually always set to "thumbv6m-none-eabi" so the --target flag to `cargo` can in - fact be omitted here. - OK, now we have produced an executable. This executable won't blink any leds, it's just a simplified version that we will build upon later in the chapter. As a sanity check, let's verify that the produced executable is actually an ARM binary: ``` console -$ # equivalent to `readelf -h target/thumbv6m-none-eabi/debug/led-roulette` - cargo readobj --target thumbv6m-none-eabi --bin led-roulette -- -file-headers +# For micro:bit v2 +# equivalent to `readelf -h target/thumbv7em-none-eabihf/debug/led-roulette` +$ cargo readobj --features v2 --target thumbv7em-none-eabihf --bin led-roulette -- -file-headers + Finished dev [unoptimized + debuginfo] target(s) in 0.01s +ELF Header: + Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF32 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: EXEC (Executable file) + Machine: ARM + Version: 0x1 + Entry point address: 0x117 + Start of program headers: 52 (bytes into file) + Start of section headers: 793112 (bytes into file) + Flags: 0x5000400 + Size of this header: 52 (bytes) + Size of program headers: 32 (bytes) + Number of program headers: 4 + Size of section headers: 40 (bytes) + Number of section headers: 21 + Section header string table index: 19 + +# For micro:bit v1 +# equivalent to `readelf -h target/thumbv6m-none-eabi/debug/led-roulette` +$ cargo readobj --features v1 --target thumbv6m-none-eabi --bin led-roulette -- -file-headers + Finished dev [unoptimized + debuginfo] target(s) in 0.01s ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 @@ -72,11 +103,11 @@ ELF Header: Version: 0x1 Entry point address: 0xC1 Start of program headers: 52 (bytes into file) - Start of section headers: 599484 (bytes into file) + Start of section headers: 693196 (bytes into file) Flags: 0x5000200 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) - Number of program headers: 2 + Number of program headers: 4 Size of section headers: 40 (bytes) Number of section headers: 22 Section header string table index: 20 diff --git a/src/05-led-roulette/debug-it.md b/src/05-led-roulette/debug-it.md index 6f6bd4726..bf4335f2d 100644 --- a/src/05-led-roulette/debug-it.md +++ b/src/05-led-roulette/debug-it.md @@ -4,8 +4,8 @@ Before we debug our little program let's take a moment to quickly understand wha happening here. In the previous chapter we already discussed the purpose of the second chip on the board as well as how it talks to our computer, but how can we actually use it? -As you can see from the output of `cargo-embed` it opened a "GDB stub", this is a server that our GDB -can connect to and send commands like "set a breakpoint at address X" to, the server can then decide +The little option `default.gb.enabled = true` in `Embed.toml` made `cargo-embed` open a so-called "GDB stub" after flashing, +this is a server that our GDB can connect to and send commands like "set a breakpoint at address X" to. The server can then decide on its own how to handle this command. In the case of the `cargo-embed` GDB stub it will forward the command to the debugging probe on the board via USB which then does the job of actually talking to the MCU for us. @@ -13,18 +13,14 @@ MCU for us. ## Let's debug! Since `cargo-embed` is blocking our current shell we can simply open a new one and cd back into our project -directory. Once we are there we can connect to the GDB server like this: +directory. Once we are there we first have to open the binary in gdb like this: ```shell +# For micro:bit v2 +$ gdb target/thumbv7em-none-eabihf/debug/led-roulette + +# For micro:bit v1 $ gdb target/thumbv6m-none-eabi/debug/led-roulette -(gdb) target remote :1337 -Remote debugging using :1337 -::fmt ( - self=, - f=) - at ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:489 -489 pub unsafe extern "C" fn Reset() -> ! { -(gdb) ``` > **NOTE** Depending on which GDB you installed you will have to use a different command to launch it, @@ -34,22 +30,29 @@ Remote debugging using :1337 > implement the GDB protocol and thus might not recognize all of the commands your GDB is sending to it, > as long as it does not crash, you are fine. -Right now we are inside the `Reset()` function. This is (surprisingly) the function that is run after a reset -of the chip. Since we did tell cargo-embed to halt the chip after we flashed it, this is where we start. +Next we will have to connect to the GDB stub. It runs on `localhost:1337` per default so in order to +connect to it run the following: -This `Reset()` function is part of a small piece of setup code that initializes some things for our Rust program -before moving on to the `main()` function. Let's set a breakpoint there and jump to it: +```shell +(gdb) target remote :1337 +Remote debugging using :1337 +0x00000116 in nrf52833_pac::{{impl}}::fmt (self=0xd472e165, f=0x3c195ff7) at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec823/nrf52833-pac-0.9.0/src/lib.rs:157 +157 #[derive(Copy, Clone, Debug)] +``` + +Next what we want to do is get to the main function of our program. +We will do this by first setting a breakpoint there and the continuing +program execution until we hit the breakpoint: ``` (gdb) break main -Breakpoint 1 at 0xac: file src/05-led-roulette/src/main.rs, line 9. +Breakpoint 1 at 0x104: file src/05-led-roulette/src/main.rs, line 9. +Note: automatically using hardware breakpoints for read-only addresses. (gdb) continue Continuing. -Note: automatically using hardware breakpoints for read-only addresses. -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9 +Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9 9 #[entry] -(gdb) ``` Breakpoints can be used to stop the normal flow of a program. The `continue` command will let the @@ -77,7 +80,7 @@ If we wanted to break in line 13 we can simply do: ``` (gdb) break 13 -Breakpoint 2 at 0xb8: file src/05-led-roulette/src/main.rs, line 13. +Breakpoint 2 at 0x110: file src/05-led-roulette/src/main.rs, line 13. (gdb) continue Continuing. @@ -98,29 +101,11 @@ is initialized but `_y` is not. Let's inspect those stack/local variables using $1 = 42 (gdb) print &x $2 = (*mut i32) 0x20003fe8 -(gdb) print _y -$3 = 536870912 -(gdb) print &_y -$4 = (*mut i32) 0x20003fec (gdb) ``` -As expected, `x` contains the value `42`. `_y`, however, contains the value `536870912` (?). 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. - -Instead of printing the local variables one by one, you can also use the `info locals` command: - -``` -(gdb) info locals -x = 42 -_y = 536870912 -(gdb) -``` +As expected, `x` contains the value `42`. 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. If we want to continue the program execution line by line we can do that using the `next` command so let's proceed to the `loop {}` statement: @@ -137,6 +122,15 @@ And `_y` should now be initialized. $5 = 42 ``` +Instead of printing the local variables one by one, you can also use the `info locals` command: + +``` +(gdb) info locals +x = 42 +_y = 42 +(gdb) +``` + If we use `next` again on top of the `loop {}` statement, we'll get stuck because the program will never pass that statement. Instead, we'll switch to the disassemble view with the `layout asm` command and advance one instruction at a time using `stepi`. You can always switch back into Rust @@ -155,23 +149,23 @@ program around the line you are currently at. ``` (gdb) disassemble /m -Dump of assembler code for function led_roulette::__cortex_m_rt_main: +Dump of assembler code for function _ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E: 10 fn main() -> ! { - 0x000000b2 <+0>: sub sp, #8 - 0x000000b4 <+2>: movs r0, #42 ; 0x2a + 0x0000010a <+0>: sub sp, #8 + 0x0000010c <+2>: movs r0, #42 ; 0x2a 11 let _y; 12 let x = 42; - 0x000000b6 <+4>: str r0, [sp, #0] + 0x0000010e <+4>: str r0, [sp, #0] 13 _y = x; - 0x000000b8 <+6>: str r0, [sp, #4] + 0x00000110 <+6>: str r0, [sp, #4] 14 15 // infinite loop; just so we don't leave this stack frame 16 loop {} -=> 0x000000ba <+8>: b.n 0xbc - 0x000000bc <+10>: b.n 0xbc +=> 0x00000112 <+8>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10> + 0x00000114 <+10>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10> End of assembler dump. ``` @@ -195,7 +189,7 @@ One last trick before we move to something more interesting. Enter the following (gdb) c Continuing. -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9 +Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9 9 #[entry] (gdb) ``` @@ -224,7 +218,7 @@ A debugging session is active. Inferior 1 [Remote target] will be detached. Quit anyway? (y or n) y -Detaching from program: $PWD/target/thumbv6m-none-eabi/debug/led-roulette, Remote target +Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target Ending remote debugging. [Inferior 1 (Remote target) detached] ``` diff --git a/src/05-led-roulette/flash-it.md b/src/05-led-roulette/flash-it.md index 2ea7da03b..8b456d9d2 100644 --- a/src/05-led-roulette/flash-it.md +++ b/src/05-led-roulette/flash-it.md @@ -7,10 +7,11 @@ In this case, our `led-roulette` program will be the *only* program in the micro By this I mean that there's nothing else running on the microcontroller: no OS, no "daemon", nothing. `led-roulette` has full control over the device. -Flashing the binary itself is quite simple thanks to `cargo-embed`, you only have to type `cargo embed`. +Flashing the binary itself is quite simple thanks to `cargo embed`. Before executing that command though, lets look into what it actually does. If you look at the side of your micro:bit -with the USB connector facing upwards you will notice, that there are actually 2 black squares on there, one is our MCU +with the USB connector facing upwards you will notice, that there are actually 2 black squares on there +(on the micro:bit v2 there is a third and biggest one, which is a speaker), one is our MCU we already talked about but what purpose does the other one serve? The other chip has 3 main purposes: 1. Provide power from the USB connector to our MCU @@ -24,15 +25,24 @@ the MCU, inspect its state via a debugger and other things. So lets flash it! ```console -$ cargo embed +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf (...) - Erasing sectors ✔ [00:00:00] [##################################################################################################################################################################] 2.00KB/ 2.00KB @ 4.57KB/s (eta 0s ) - Programming pages ✔ [00:00:00] [##################################################################################################################################################################] 2.00KB/ 2.00KB @ 1.93KB/s (eta 0s ) - Finished flashing in 0.764s -Firing up GDB stub at localhost:1337. -GDB stub listening on localhost:1337 + Erasing sectors ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 4.21KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 2.71KiB/s (eta 0s ) + Finished flashing in 0.608s + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi + (...) + Erasing sectors ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 4.14KiB/s (eta 0s ) + Programming pages ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 2.69KiB/s (eta 0s ) + Finished flashing in 0.614s ``` You will notice that `cargo-embed` blocks after outputting the last line, this is inteded and you should not close it -since we need it in this state for the next step, debugging it! +since we need it in this state for the next step: debugging it! Furthermore, you will have noticed that the `cargo build` +and `cargo embed` are actually passed the same flags, this is because `cargo embed` actually executes the build and then +flashes the resulting binary on to the chip, hence you can leave out the `cargo build` step in the future if you +want to flash your code right away. diff --git a/src/05-led-roulette/it-blinks.md b/src/05-led-roulette/it-blinks.md index 17b572abd..fcdbdb252 100644 --- a/src/05-led-roulette/it-blinks.md +++ b/src/05-led-roulette/it-blinks.md @@ -24,17 +24,19 @@ simple delay-based program that prints something every second might for example use cortex_m_rt::entry; use rtt_target::{rtt_init_print, rprintln}; use panic_rtt_target as _; -use nrf51_hal as hal; -use hal::prelude::*; +use microbit::board::Board; +use microbit::hal::timer::Timer; +use microbit::hal::prelude::*; #[entry] fn main() -> ! { rtt_init_print!(); - let p = hal::pac::Peripherals::take().unwrap(); + let mut board = Board::take().unwrap(); + + let mut timer = Timer::new(board.TIMER0); - let mut delay = hal::Timer::new(p.TIMER0); loop { - delay.delay_ms(1000u32); + timer.delay_ms(1000u32); rprintln!("1000 ms passed"); } } @@ -43,7 +45,10 @@ fn main() -> ! { In order to actually see the prints we have to change `Embed.toml` like this: ``` [default.general] -chip = "nrf51822_xxAA" +# v2 +# chip = "nrf52833" +# v1 +# chip = "nrf51822" [default.reset] halt_afterwards = false @@ -55,14 +60,14 @@ enabled = true enabled = false ``` -And now after putting the code into `src/main.rs` and another quick `cargo embed` you should see -"`1000 ms passed`" being sent to your console every second from your MCU. +And now after putting the code into `src/main.rs` and another quick `cargo embed` (again with the same flags you used before) +you should see "`1000 ms passed`" being sent to your console every second from your MCU. ## Blinking Now we've arrived at the point where we can combine our new knowledge about GPIO and delay abstractions in order to actually make an LED on the back of the micro:bit blink. The resulting program is really just -a mash-up of the one above and the one that turned an LED on in the last chapter and looks like this: +a mash-up of the one above and the one that turned an LED on in the last section and looks like this: ```rs #![deny(unsafe_code)] @@ -72,31 +77,30 @@ a mash-up of the one above and the one that turned an LED on in the last chapter use cortex_m_rt::entry; use rtt_target::{rtt_init_print, rprintln}; use panic_rtt_target as _; -use nrf51_hal as hal; -use hal::prelude::*; +use microbit::board::Board; +use microbit::hal::timer::Timer; +use microbit::hal::prelude::*; #[entry] fn main() -> ! { rtt_init_print!(); - let p = hal::pac::Peripherals::take().unwrap(); + let mut board = Board::take().unwrap(); - let mut delay = hal::Timer::new(p.TIMER0); + let mut timer = Timer::new(board.TIMER0); - let p0 = hal::gpio::p0::Parts::new(p.GPIO); - let mut row1 = p0.p0_13.into_push_pull_output(hal::gpio::Level::Low); - let _col1 = p0.p0_04.into_push_pull_output(hal::gpio::Level::Low); + board.display_pins.col1.set_low().unwrap(); + let mut row1 = board.display_pins.row1; loop { - row1.set_high().unwrap(); - rprintln!("Light!"); - delay.delay_ms(500u32); - - row1.set_low().unwrap(); - rprintln!("Dark!"); - delay.delay_ms(500u32); + row1.set_low().unwrap(); + rprintln!("Dark!"); + timer.delay_ms(1_000_u16); + row1.set_high().unwrap(); + rprintln!("Light!"); + timer.delay_ms(1_000_u16); } } ``` -And after putting the code into `src/main.rs` and a final `cargo embed` you should see the LED we light up before -blinking as well as a print, every time the LED changes from off to on and vice versa. +And after putting the code into `src/main.rs` and a final `cargo embed` (with the proper flags) +you should see the LED we light up before blinking as well as a print, every time the LED changes from off to on and vice versa. diff --git a/src/05-led-roulette/light-it-up.md b/src/05-led-roulette/light-it-up.md index c17f4181f..7a0ab16db 100644 --- a/src/05-led-roulette/light-it-up.md +++ b/src/05-led-roulette/light-it-up.md @@ -2,42 +2,29 @@ ## embedded-hal In this chapter we are going to make one of the many LEDs on the back of the micro:bit light up since this is -basically the "Hello World" of embedded programming. In order to get this task done we will use a set of -abstractions provided by the crate `embedded-hal`. `embedded-hal` is a crate which provides a set of traits -that describe behaviour of hardware, for example the [OutputPin trait] which allows us to turn a pin on or off. +basically the "Hello World" of embedded programming. In order to get this task done we will use one of the traits +provided by `embedded-hal`, specifically the [OutputPin trait] which allows us to turn a pin on or off. -In order to use these traits we have to implement them for the chip we are using. Luckily this has already been done -in our case in the [nrf51-hal]. Crates like this are commonly referred to as HALs (Hardware Abstraction Layer) -and allow us to use the same API to blink an LED and of course many more complex things accross all chips that implement -the `embedded-hal` traits. - -For example, a person working on an embedded project might need to read temperature data from a sensor. In -order to achieve this they can write a driver library that doesn't do anything MCU specific but instead just relies on -`embedded-hal`. This will allow anyone with an MCU that implements the `embedded-hal` traits to easily plug and play -their driver crate, despite having an MCU made by a completely different manufacturer or even with a different architecture, etc. - -[OutputPin trait]: https://docs.rs/embedded-hal/0.2.4/embedded_hal/digital/v2/trait.OutputPin.html -[nrf51-hal]: https://crates.io/crates/nrf51-hal +[OutputPin trait]: https://docs.rs/embedded-hal/0.2.5/embedded_hal/digital/v2/trait.OutputPin.html ## The micro:bit LEDs On the back of the micro:bit you can see a 5x5 square of LEDs, usually called an LED matrix. This matrix alignment is used so that instead of having to use 25 seperate pins to drive every single one of the LEDs, we can just use 10 (5+5) pins in -order to control which column and which row of our matrix lights up. However, the micro:bit team implemented this a -little differently. Their [schematic page] says that it is actually implemented as a 3x9 matrix but a few columns simply -remain unused. +order to control which column and which row of our matrix lights up. -In order to determine which pins we need to control to light up an LED we can check out -micro:bit's open source [schematic], linked on the same page. The very first sheet contains the LED matrix circuit which -is apparently connected to the pins named ROW1-3 and COL1-9. Further down on sheet 5 you can see that these pins -directly map to our MCU. For example, ROW1 is connected to P0.13. +> **NOTE** that the micro:bit v1 team implemented this a little differently. Their [schematic page] says +> that it is actually implemented as a 3x9 matrix but a few columns simply remain unused. -> **NOTE**: The naming scheme of the NRF51 for its pins (P0.13) simply refers to port 0 (P0) pin 13. This is done -> because on MCUs with dozens or hundreds of pins you usually end up with multiple pins grouped up as ports for the sake of -> clarity. The NRF51, however, is so small that it only has one GPIO port (P0). +Usually in order to determine which specific pins we have to control in +order to light a specific LED up we would now have to read the +[micro:bit v2 schematic] or the [micro:bit v1 schematic] respectively. +Luckily for us though we can use the aforementioned micro:bit BSP +which abstracts all of this nicely away from us. [schematic page]: https://tech.microbit.org/hardware/schematic/ -[schematic]: https://github.com/bbcmicrobit/hardware/blob/master/V1.5/SCH_BBC-Microbit_V1.5.PDF +[micro:bit v2 schematic]: https://github.com/microbit-foundation/microbit-v2-hardware/blob/main/V2/MicroBit_V2.0.0_S_schematic.PDF +[micro:bit v1 schematic]: https://github.com/bbcmicrobit/hardware/blob/master/V1.5/SCH_BBC-Microbit_V1.5.PDF ## Actually lighting it up! @@ -51,18 +38,15 @@ a look at it and then we can go through it step by step: use cortex_m_rt::entry; use panic_halt as _; -use nrf51_hal as hal; -use hal::prelude::*; +use microbit::board::Board; +use microbit::hal::prelude::*; #[entry] fn main() -> ! { - let p = hal::pac::Peripherals::take().unwrap(); - - let p0 = hal::gpio::p0::Parts::new(p.GPIO); - let mut row1 = p0.p0_13.into_push_pull_output(hal::gpio::Level::Low); - let _col1 = p0.p0_04.into_push_pull_output(hal::gpio::Level::Low); + let mut board = Board::take().unwrap(); - row1.set_high().unwrap(); + board.display_pins.col1.set_low().unwrap(); + board.display_pins.row1.set_high().unwrap(); loop {} } @@ -71,39 +55,34 @@ fn main() -> ! { The first few lines until the main function just do some basic imports and setup we already looked at before. However, the main function looks pretty different to what we have seen up to now. -The first line is related to how most HALs written in Rust work internally. Usually these crates rely on so-called -PACs (Peripheral Access Crates). A PAC is usually an autogenerated crate that provides some minimal abstractions -for all the peripherals our MCU has to offer. `let p = hal::pac::Peripherals::take().unwrap();` basically takes all -these peripherals from the PAC and binds them to a variable. +The first line is related to how most HALs written in Rust work internally. +As discussed before they are built on top of PAC crates which own (in the Rust sense) +all the peripherals of a chip. `let mut board = Board::take().unwrap();` basically takes all +these peripherals from the PAC and binds them to a variable. In this specific case we are +not only working with a HAL but with an entire BSP, so this also takes ownership +of the Rust representation of the other chips on the board. > **NOTE**: If you are wondering why we have to call `unwrap()` here, in theory it is possible for `take()` to be called > more than once. This would lead to the peripherals being represented by two separate variables and thus lots of > possible confusing behaviour because two variables modify the same resource. In order to avoid this, PACs are > implemented in a way that it would panic if you tried to take the peripherals twice. -Once we got the peripherals, we assemble the GPIO port 0 from them with `let p0 = hal::gpio::p0::Parts::new(p.GPIO);` and -proceed to construct the `ROW1` and `COL1` pin using the two lines below, initialized as a switched-off -(`hal::gpio::Level::Low`) push-pull output pin (`into_push_pull_output`). - -> **NOTE** If you don't know what push-pull means, don't worry about it, it's mostly irrelevant for us here, if you do -> want to figure it out, have a look [here](https://en.wikipedia.org/wiki/Push%E2%80%93pull_output). - -Now we can finally light the LED connected to `ROW1`, `COL1` up by setting the `ROW1` pin to high (i.e. switching it on). -The reason we can leave `COL1` set to low is because of how the LED matrix circuit works. Furthermore, `embedded-hal` is +Now we can light the LED connected to `row1`, `col1` up by setting the `row1` pin to high (i.e. switching it on). +The reason we can leave `col1` set to low is because of how the LED matrix circuit works. Furthermore, `embedded-hal` is designed in a way that every operation on hardware can possibly return an error, even just toggling a pin on or off. Since that is highly unlikely in our case, we can just `unwrap()` the result. - ## Testing it -Testing our little program is quite simple. First put it into `src/mains.rs`. Afterwards we simply have to run `cargo-embed` -again, let it flash and just like before, open our GDB and connect to the GDB stub: +Testing our little program is quite simple. First put it into `src/mains.rs`. Afterwards we simply have to run the +`cargo embed` command from the last section again, let it flash and just like before. Then open our GDB and connect +to the GDB stub: ``` -$ gdb target/thumbv6m-none-eabi/debug/led-roulette +$ # Your GDB debug command from the last section (gdb) target remote :1337 Remote debugging using :1337 -cortex_m_rt::Reset () at ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:489 +cortex_m_rt::Reset () at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:489 489 pub unsafe extern "C" fn Reset() -> ! { (gdb) ``` diff --git a/src/05-led-roulette/my-solution.md b/src/05-led-roulette/my-solution.md index a9c2614ed..a6369503d 100644 --- a/src/05-led-roulette/my-solution.md +++ b/src/05-led-roulette/my-solution.md @@ -2,7 +2,8 @@ What solution did you come up with? -Here's mine: +Here's mine, it's probably one of the simplest (but of course not most +beautiful) way to generate the required matrix: ``` rust #![deny(unsafe_code)] @@ -12,58 +13,40 @@ Here's mine: use cortex_m_rt::entry; use rtt_target::rtt_init_print; use panic_rtt_target as _; -use nrf51_hal as hal; -use hal::prelude::*; - -// All border LEDs in order with the exception of the very first LED which is set -// at the last spot -const COMBINATIONS: [(usize, usize); 16] = [ - (2, 4), (1, 2), (2, 5), (1, 3), (3, 8), (2, 1), (1, 4), (3, 2), (2,6), - (3, 1), (2, 7), (3, 3), (1, 8), (2, 2), (3, 4), (1, 1) +use microbit::{ + board::Board, + display::blocking::Display, + hal::Timer, +}; + +const PIXELS: [(usize, usize); 16] = [ + (0,0), (0,1), (0,2), (0,3), (0,4), (1,4), (2,4), (3,4), (4,4), + (4,3), (4,2), (4,1), (4,0), (3,0), (2,0), (1,0) ]; #[entry] fn main() -> ! { rtt_init_print!(); - let p = hal::pac::Peripherals::take().unwrap(); - let mut delay = hal::Timer::new(p.TIMER0); + let board = Board::take().unwrap(); + let mut timer = Timer::new(board.TIMER0); + let mut display = Display::new(board.display_pins); + let mut leds = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; - let p0 = hal::gpio::p0::Parts::new(p.GPIO); - - // Initialize all rows and cols to off - let mut row1 = p0.p0_13.into_push_pull_output(hal::gpio::Level::Low).degrade(); - let row2 = p0.p0_14.into_push_pull_output(hal::gpio::Level::Low).degrade(); - let row3 = p0.p0_15.into_push_pull_output(hal::gpio::Level::Low).degrade(); - let mut col1 = p0.p0_04.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col2 = p0.p0_05.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col3 = p0.p0_06.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col4 = p0.p0_07.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col5 = p0.p0_08.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col6 = p0.p0_09.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col7 = p0.p0_10.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col8 = p0.p0_11.into_push_pull_output(hal::gpio::Level::High).degrade(); - let col9 = p0.p0_12.into_push_pull_output(hal::gpio::Level::High).degrade(); - - // bring up the very first LED - row1.set_high().unwrap(); - col1.set_low().unwrap(); - - let mut cols = [col1, col2, col3, col4, col5, col6, col7, col8, col9]; - let mut rows = [row1, row2, row3]; + let mut last_led = (0,0); loop { - let mut previous_pair = (1, 1); - for current_pair in COMBINATIONS.iter() { - delay.delay_ms(30u32); - - rows[current_pair.0 - 1].set_high().unwrap(); - cols[current_pair.1 - 1].set_low().unwrap(); - - rows[previous_pair.0 - 1].set_low().unwrap(); - cols[previous_pair.1 - 1].set_high().unwrap(); - - previous_pair = *current_pair; + for current_led in PIXELS.iter() { + leds[last_led.0][last_led.1] = 0; + leds[current_led.0][current_led.1] = 1; + display.show(&mut timer, leds, 30); + last_led = *current_led; } } } @@ -72,12 +55,22 @@ fn main() -> ! { One more thing! Check that your solution also works when compiled in "release" mode: ``` console -$ cargo embed --release +# For micro:bit v2 +$ cargo embed --features v2 --target thumbv7em-none-eabihf --release + (...) + +# For micro:bit v1 +$ cargo embed --features v1 --target thumbv6m-none-eabi --release + (...) ``` If you want to debug your "release" mode binary you'll have to use a different GDB command: ``` console +# For micro:bit v2 +$ gdb target/thumbv7em-none-eabihf/release/led-roulette + +# For micro:bit v1 $ gdb target/thumbv6m-none-eabi/release/led-roulette ``` @@ -85,58 +78,113 @@ Binary size is something we should always keep an eye on! How big is your soluti that using the `size` command on the release binary: ``` console -$ cargo size --bin led-roulette -- -A - Finished dev [unoptimized + debuginfo] target(s) in 0.03s +# For micro:bit v2 +$ cargo size --features v2 --target thumbv7em-none-eabihf -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 256 0x0 +.text 26984 0x100 +.rodata 2732 0x6a68 +.data 0 0x20000000 +.bss 1092 0x20000000 +.uninit 0 0x20000444 +.debug_abbrev 33941 0x0 +.debug_info 494113 0x0 +.debug_aranges 23528 0x0 +.debug_ranges 130824 0x0 +.debug_str 498781 0x0 +.debug_pubnames 143351 0x0 +.debug_pubtypes 124464 0x0 +.ARM.attributes 58 0x0 +.debug_frame 69128 0x0 +.debug_line 290580 0x0 +.debug_loc 1449 0x0 +.comment 109 0x0 +Total 1841390 + + +$ cargo size --features v2 --target thumbv7em-none-eabihf --release -- -A + Finished release [optimized + debuginfo] target(s) in 0.02s +led-roulette : +section size addr +.vector_table 256 0x0 +.text 6332 0x100 +.rodata 648 0x19bc +.data 0 0x20000000 +.bss 1076 0x20000000 +.uninit 0 0x20000434 +.debug_loc 9036 0x0 +.debug_abbrev 2754 0x0 +.debug_info 96460 0x0 +.debug_aranges 1120 0x0 +.debug_ranges 11520 0x0 +.debug_str 71325 0x0 +.debug_pubnames 32316 0x0 +.debug_pubtypes 29294 0x0 +.ARM.attributes 58 0x0 +.debug_frame 2108 0x0 +.debug_line 19303 0x0 +.comment 109 0x0 +Total 283715 + +# micro:bit v1 +$ cargo size --features v1 --target thumbv6m-none-eabi -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.02s led-roulette : section size addr .vector_table 168 0x0 -.text 20996 0xa8 -.rodata 2956 0x52ac +.text 28584 0xa8 +.rodata 2948 0x7050 .data 0 0x20000000 -.bss 1088 0x20000000 -.uninit 0 0x20000440 -.debug_abbrev 21988 0x0 -.debug_info 283389 0x0 -.debug_aranges 15832 0x0 -.debug_str 307609 0x0 -.debug_pubnames 68859 0x0 -.debug_pubtypes 55406 0x0 +.bss 1092 0x20000000 +.uninit 0 0x20000444 +.debug_abbrev 30020 0x0 +.debug_info 373392 0x0 +.debug_aranges 18344 0x0 +.debug_ranges 89656 0x0 +.debug_str 375887 0x0 +.debug_pubnames 115633 0x0 +.debug_pubtypes 86658 0x0 .ARM.attributes 50 0x0 -.debug_frame 47732 0x0 -.debug_line 199401 0x0 -.debug_ranges 68936 0x0 -.debug_loc 976 0x0 -.comment 147 0x0 -Total 1095533 +.debug_frame 54144 0x0 +.debug_line 237714 0x0 +.debug_loc 1499 0x0 +.comment 109 0x0 +Total 1415898 - -$ cargo size --bin led-roulette --release -- -A +$ cargo size --features v1 --target thumbv6m-none-eabi --release -- -A Finished release [optimized + debuginfo] target(s) in 0.02s led-roulette : section size addr .vector_table 168 0x0 -.text 4044 0xa8 -.rodata 692 0x1074 +.text 4848 0xa8 +.rodata 648 0x1398 .data 0 0x20000000 .bss 1076 0x20000000 .uninit 0 0x20000434 -.debug_loc 7520 0x0 -.debug_abbrev 3444 0x0 -.debug_info 55229 0x0 -.debug_aranges 1144 0x0 -.debug_ranges 3608 0x0 -.debug_str 48267 0x0 -.debug_pubnames 15435 0x0 -.debug_pubtypes 15970 0x0 +.debug_loc 9705 0x0 +.debug_abbrev 3235 0x0 +.debug_info 61908 0x0 +.debug_aranges 1208 0x0 +.debug_ranges 5784 0x0 +.debug_str 57358 0x0 +.debug_pubnames 22959 0x0 +.debug_pubtypes 18891 0x0 .ARM.attributes 50 0x0 -.debug_frame 2152 0x0 -.debug_line 17050 0x0 -.comment 147 0x0 -Total 175996 +.debug_frame 2316 0x0 +.debug_line 18444 0x0 +.comment 19 0x0 +Total 208617 + ``` > **NOTE** The Cargo project is already configured to build the release binary using LTO. -Know how to read this output? The `text` section contains the program instructions. It's around 4KB -in my case. On the other hand, the `data` and `bss` sections contain variables statically allocated -in RAM (`static` variables). +Know how to read this output? The `text` section contains the program instructions. On the other hand, +the `data` and `bss` sections contain variables statically allocated in RAM (`static` variables). +If you remember back in the specification of the microcontroller on your micro:bit, you should +notice that its flash memory is actually far too small to contain this binary, so how is this possible? +As we can see from the size statistics most of the binary is actually made up of debugging related +sections , those are however not flashed to the microcontroller at any time, after all they aren't +relevant for the execution. diff --git a/src/05-led-roulette/src/main.rs b/src/05-led-roulette/src/main.rs index 9380a52ba..bb8c08ca6 100644 --- a/src/05-led-roulette/src/main.rs +++ b/src/05-led-roulette/src/main.rs @@ -4,7 +4,7 @@ use cortex_m_rt::entry; use panic_halt as _; -use nrf51_hal as _; +use microbit as _; #[entry] fn main() -> ! { diff --git a/src/05-led-roulette/the-challenge.md b/src/05-led-roulette/the-challenge.md index 2067ff76c..613c56a31 100644 --- a/src/05-led-roulette/the-challenge.md +++ b/src/05-led-roulette/the-challenge.md @@ -13,14 +13,48 @@ If you can't exactly see what's happening here it is in a much slower version: