diff --git a/mdbook/src/05-led-roulette/Cargo.toml b/mdbook/src/05-led-roulette/Cargo.toml
index 92832e6..4ad17a6 100644
--- a/mdbook/src/05-led-roulette/Cargo.toml
+++ b/mdbook/src/05-led-roulette/Cargo.toml
@@ -10,6 +10,7 @@ panic-halt = "0.2.0"
panic-rtt-target = "0.1.3"
rtt-target = "0.5.0"
microbit-v2 = "0.15.0"
+embedded-hal = "1.0.0"
[dependencies.cortex-m]
version = "0.7.7"
diff --git a/mdbook/src/05-led-roulette/README.md b/mdbook/src/05-led-roulette/README.md
index 0aba690..ab9a3d2 100644
--- a/mdbook/src/05-led-roulette/README.md
+++ b/mdbook/src/05-led-roulette/README.md
@@ -3,21 +3,22 @@
Alright, let's start by building the following application:
-
-I'm going to give you a high level API to implement this app but don't worry we'll do low level
-stuff later on. The main goal of this chapter is to get familiar with the *flashing* and debugging
-process.
+I'm going to give you a high level API to implement this app. Don't worry — we'll do low level stuff
+later on. The main goal of this chapter is to get familiar with the *flashing* and debugging
+process. "Flashing" is the process of writing a compiled program's code into "flash" memory on a
+device: it has nothing to do with the LEDs.
-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
+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.
-Now, jump into the `src/05-led-roulette` directory. Check the `src/main.rs` file:
+Now, jump into the `src/05-led-roulette` directory. Check the `examples/init.rs` file:
``` rust
-{{#include src/main.rs}}
+{{#include examples/init.rs}}
```
Microcontroller programs are different from standard programs in two aspects: `#![no_std]` and
@@ -25,23 +26,30 @@ Microcontroller programs are different from standard programs in two aspects: `#
The `no_std` attribute says that this program won't use the `std` crate, which assumes an underlying
OS; the program will instead use the `core` crate, a subset of `std` that can run on bare metal
-systems (i.e., systems without OS abstractions like files and sockets).
+systems (that is, systems without OS abstractions like files and sockets).
The `no_main` attribute says that this program won't use the standard `main` interface, which is
tailored for command line applications that receive arguments. Instead of the standard `main` we'll
use the `entry` attribute from the [`cortex-m-rt`] crate to define a custom entry point. In this
-program we have named the entry point "main", but any other name could have been used. The entry
-point function must have signature `fn() -> !`; this type indicates that the function can't return
--- this means that the program never terminates.
+program we have named the entry point `main`, but any other name could have been used. The entry
+point function must have signature `fn() -> !`; this type indicates that the function can't return.
+This means that the program never terminates by returning from `main`: if the compiler detects that
+this would be possible it will refuse to compile your program.
[`cortex-m-rt`]: https://crates.io/crates/cortex-m-rt
-If you are a careful observer, you'll also notice there is a `.cargo` directory in the Cargo project
-as well. This directory contains a Cargo configuration file (`.cargo/config`) that tweaks the
-linking process to tailor the memory layout of the program to the requirements of the target device.
-This modified linking process is a requirement of the `cortex-m-rt` crate.
+If you are a careful observer, you'll also notice there is a possibly-hidden `.cargo` directory in
+the Cargo project as well. This directory contains a Cargo configuration file `.cargo/config.toml`.
-Furthermore, there is also an `Embed.toml` file
+```toml
+{{#include .cargo/config.toml}}
+```
+
+This file tweaks the linking process to tailor the memory layout of the program to the requirements
+of the target device. This modified linking process is a requirement of the `cortex-m-rt`
+crate. The `.cargo/config.toml` file also tells Cargo how to build and run code on our MB2.
+
+There is also an `Embed.toml` file here:
```toml
{{#include Embed.toml}}
@@ -49,10 +57,10 @@ Furthermore, there is also an `Embed.toml` file
This file tells `cargo-embed` that:
-* we are working with an NRF52833.
-* 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.
-* we want to enable GDB, this will be required for the debugging procedure
+- We are working with an NRF52833.
+- We want to halt the chip after flashing it, so our program stops before `main`.
+- We want to disable RTT. RTT is a protocol that allows the chip to send text to a debugger.
+ You have already seen RTT in action: it was the protocol that sent "Hello World" in chapter 3.
+- We want to enable GDB. This will be required for the debugging procedure.
-Alright, let's start by building this program.
+Now that we've seen what's going on, let's start by building this program.
diff --git a/mdbook/src/05-led-roulette/build-it.md b/mdbook/src/05-led-roulette/build-it.md
index 5192d21..252ac41 100644
--- a/mdbook/src/05-led-roulette/build-it.md
+++ b/mdbook/src/05-led-roulette/build-it.md
@@ -1,13 +1,13 @@
# Build it
The first step is to build our "binary" crate. Because the microcontroller has a different
-architecture than your computer we'll have to cross compile. Cross compiling in Rust land is as simple
-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.
+architecture than your computer we'll have to cross compile. Cross compiling in Rust land is as
+simple 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.
-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:
+As we already know the microcontroller on the micro:bit v2 has a Cortex-M4F processor in it.
+`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
- `thumbv7m-none-eabi`, for the Cortex-M3 processor
@@ -16,50 +16,55 @@ 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
-(The `hf`/`F` parts have hardware floating point acceleration. This will make
-numeric computations involving fractional ("floating decimal point") computations
-much faster.)
+"Thumb" here refers to a version of the Arm instruction set that has smaller instructions for
+reduced code size (it's a pun, see). The `hf`/`F` parts have hardware floating point
+acceleration. This will make numeric computations involving fractional ("floating decimal point")
+computations much faster.
For the micro:bit v2, we'll want the `thumbv7em-none-eabihf` target.
-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`:
+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
$ rustup target add thumbv7em-none-eabihf
```
-You only need to do the above step once; `rustup` will re-install a new standard library
-(`rust-std` component) whenever you update your toolchain. Therefore you can skip this step, if you have already added the necessary target
+You only need to do the above step once; `rustup` will then update this target (re-installing a new
+standard library `rust-std` component that contains the `core` library we use) whenever you update
+your toolchain. Therefore you can skip this step if you have already added the necessary target
while [verifying your setup].
[verifying your setup]: ../03-setup/verify.html#verifying-cargo-embed
-With the `rust-std` component in place you can now cross compile the program using Cargo:
+With the `rust-std` component in place you can now cross compile the program using Cargo.
+Make sure you are in the `src/05-led-roulette` directory, then build. This initial code is an
+example, so we compile it as such.
``` console
-# make sure you are in the `src/05-led-roulette` directory
-
-$ cargo build --target thumbv7em-none-eabihf
+$ cargo build --example init
Compiling semver-parser v0.7.0
- Compiling typenum v1.12.0
- Compiling cortex-m v0.6.3
- (...)
- Compiling microbit-v2 v0.10.1
+ Compiling proc-macro2 v1.0.86
+ ...
+
Finished dev [unoptimized + debuginfo] target(s) in 33.67s
```
-> **NOTE** Be sure to compile this crate *without* optimizations. The provided Cargo.toml
-> file and build command above will ensure optimizations are off.
+> **NOTE** Be sure to compile this crate *without* optimizations. The provided `Cargo.toml` file and
+> build command above will ensure optimizations are off as long as you *don't* pass `cargo` the
+> `--release` flag.
+
+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. (The command below is equivalent to
+
+ readelf -h ../../../target/thumbv7em-none-eabihf/debug/examples/init
-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:
+on systems that have `readelf`.)
``` console
-# equivalent to `readelf -h target/thumbv7em-none-eabihf/debug/led-roulette`
-$ cargo readobj --target thumbv7em-none-eabihf --bin led-roulette -- --file-headers
+$ cargo readobj --example init -- --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
diff --git a/mdbook/src/05-led-roulette/debug-it.md b/mdbook/src/05-led-roulette/debug-it.md
index 9213e2e..a070417 100644
--- a/mdbook/src/05-led-roulette/debug-it.md
+++ b/mdbook/src/05-led-roulette/debug-it.md
@@ -1,46 +1,45 @@
# Debug it
+
+Let's figure out how to debug our little program. It doesn't really have any interesting bugs yet,
+but that's the best kind of program to learn debugging on.
+
## How does this even work?
-Before we debug our little program let's take a moment to quickly understand what is actually
-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?
-The little option `default.gdb.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.
+Before we debug our program let's take a moment to quickly understand what is actually 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?
+
+The little option `default.gdb.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". 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 via USB to the "debugging probe" on
+the second chip. This chip does the job of talking to the 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
+`cargo-embed` is running in our current shell. We can open a new shell and go back into our project
directory. Once we are there we first have to open the binary in gdb like this:
```shell
-$ gdb target/thumbv7em-none-eabihf/debug/led-roulette
+$ gdb ../../../target/thumbv7em-none-eabihf/debug/examples/init
```
-> **NOTE**: Depending on which GDB you installed you will have to use a different command to launch it,
-> check out [chapter 3] if you forgot which one it was.
+> **NOTE**: Depending on which GDB you installed you will have to use a different command to launch
+> it. Check out [chapter 3] if you forgot which one it was.
[chapter 3]: ../03-setup/index.md#tools
-> **NOTE**: If you are getting `target/thumbv7em-none-eabihf/debug/led-roulette: No such file or directory`
-> error, try adding `../../` to the file path, for example:
->
-> ```shell
-> $ gdb ../../target/thumbv7em-none-eabihf/debug/led-roulette
-> ```
->
-> This is caused by each example project being in a `workspace` that contains the entire book, and workspaces have
-> a single `target` directory. Check out [Workspaces chapter in Rust Book] for more.
+The `../../..` in this command is needed, since each example project is in a "workspace" that
+contains the entire book. Workspaces have a single shared `target` directory. Check out [Workspaces
+chapter in Rust Book] for more.
-[Workspaces chapter in Rust Book]: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#creating-a-workspace
+> **NOTE** If `cargo-embed` prints a lot of warnings here don't worry about it. As of now it does
+> not fully implement the GDB protocol, and thus might not recognize all the commands your GDB is
+> sending to it. As long as GDB does not crash, you are fine.
-> **NOTE**: If `cargo-embed` prints a lot of warnings here don't worry about it. As of now it does not fully
-> implement the GDB protocol and thus might not recognize all the commands your GDB is sending to it,
-> as long as it does not crash, you are fine.
+[Workspaces chapter in Rust Book]: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#creating-a-workspace
-Next we will have to connect to the GDB stub. It runs on `localhost:1337` per default so in order to
+Next we will have to connect to the GDB stub. It runs on `localhost:1337` by default so in order to
connect to it run the following:
```shell
@@ -50,18 +49,17 @@ Remote debugging using :1337
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:
+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 then continuing program execution until we hit the breakpoint:
```
(gdb) break main
-Breakpoint 1 at 0x104: file src/05-led-roulette/src/main.rs, line 9.
+Breakpoint 1 at 0x104: file src/05-led-roulette/examples/init.rs, line 9.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) continue
Continuing.
-Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9
+Breakpoint 1, init::__cortex_m_rt_main_trampoline () at src/05-led-roulette/examples/init.rs:9
9 #[entry]
```
@@ -69,9 +67,10 @@ Breakpoints can be used to stop the normal flow of a program. The `continue` com
program run freely *until* it reaches a breakpoint. In this case, until it reaches the `main`
function because there's a breakpoint there.
-Note that GDB output says "Breakpoint 1". Remember that our processor can only use a limited amount of these
-breakpoints, so it's a good idea to pay attention to these messages. If you happen to run out of breakpoints,
-you can list all the current ones with `info break` and delete desired ones with `delete `.
+Note that GDB output says "Breakpoint 1". Remember that our processor can only use a limited amount
+of these breakpoints, so it's a good idea to pay attention to these messages. If you happen to run
+out of breakpoints, you can list all the current ones with `info break` and delete desired ones with
+`delete `.
For a nicer debugging experience, we'll be using GDB's Text User Interface (TUI). To enter into that
mode, on the GDB shell enter the following command:
@@ -85,16 +84,16 @@ mode, on the GDB shell enter the following command:
![GDB session](../assets/gdb-layout-src.png "GDB TUI")
-GDB's break command does not only work for function names, it can also break at certain line numbers.
-If we wanted to break in line 13 we can simply do:
+GDB's break command does works for more than just function names: it can also break at certain line
+numbers. If we want to break in line 13 we can simply do:
```
(gdb) break 13
-Breakpoint 2 at 0x110: file src/05-led-roulette/src/main.rs, line 13.
+Breakpoint 2 at 0x110: file src/05-led-roulette/examples/init.rs, line 13.
(gdb) continue
Continuing.
-Breakpoint 2, led_roulette::__cortex_m_rt_main () at src/05-led-roulette/src/main.rs:13
+Breakpoint 2, init::__cortex_m_rt_main () at src/05-led-roulette/examples/init.rs:13
(gdb)
```
@@ -115,11 +114,12 @@ $2 = (*mut i32) 0x20003fe8
(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.
+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: `*mut i32`, a
+pointer to a mutable `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:
+If we want to continue the program execution line by line, we can do that using the `next` command.
+Let's proceed to the `loop {}` statement:
```
(gdb) next
@@ -133,7 +133,7 @@ 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:
+Instead of printing the local variables one by one you can also use the `info locals` command:
```
(gdb) info locals
@@ -147,7 +147,8 @@ never pass that statement. Instead, we'll switch to the disassemble view with th
command and advance one instruction at a time using `stepi`. You can always switch back into Rust
source code view later by issuing the `layout src` command again.
-> **NOTE**: If you used the `next` or `continue` command by mistake and GDB got stuck, you can get unstuck by hitting `Ctrl+C`.
+> **NOTE**: If you used the `next` or `continue` command by mistake and GDB got stuck, you can get
+> unstuck by hitting `Ctrl+C`.
```
(gdb) layout asm
@@ -160,7 +161,7 @@ program around the line you are currently at.
```
(gdb) disassemble /m
-Dump of assembler code for function _ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E:
+Dump of assembler code for function _ZN12init18__cortex_m_rt_main17h3e25e3afbec4e196E:
10 fn main() -> ! {
0x0000010a <+0>: sub sp, #8
0x0000010c <+2>: movs r0, #42 ; 0x2a
@@ -175,8 +176,8 @@ Dump of assembler code for function _ZN12led_roulette18__cortex_m_rt_main17h3e25
14
15 // infinite loop; just so we don't leave this stack frame
16 loop {}
-=> 0x00000112 <+8>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10>
- 0x00000114 <+10>: b.n 0x114 <_ZN12led_roulette18__cortex_m_rt_main17h3e25e3afbec4e196E+10>
+=> 0x00000112 <+8>: b.n 0x114 <_ZN12init18__cortex_m_rt_main17h3e25e3afbec4e196E+10>
+ 0x00000114 <+10>: b.n 0x114 <_ZN12init18__cortex_m_rt_main17h3e25e3afbec4e196E+10>
End of assembler dump.
```
@@ -200,7 +201,7 @@ One last trick before we move to something more interesting. Enter the following
(gdb) c
Continuing.
-Breakpoint 1, led_roulette::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9
+Breakpoint 1, init::__cortex_m_rt_main_trampoline () at src/05-led-roulette/src/main.rs:9
9 #[entry]
(gdb)
```
@@ -217,8 +218,8 @@ 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 on the value of *uninitialized* variables but that's the definition of Undefined Behavior
-> (UB).
+> depends on 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.
@@ -234,12 +235,13 @@ Ending remote debugging.
[Inferior 1 (Remote target) detached]
```
-> **NOTE**: If the default GDB CLI is not to your liking check out [gdb-dashboard]. It uses Python to
-> turn the default GDB CLI into a dashboard that shows registers, the source view, the assembly view
-> and other things.
+> **NOTE**: If the default GDB CLI is not to your liking check out [gdb-dashboard]. It uses Python
+> to turn the default GDB CLI into a dashboard that shows registers, the source view, the assembly
+> view and other things.
[gdb-dashboard]: https://github.com/cyrus-and/gdb-dashboard#gdb-dashboard
-If you want to learn more about what GDB can do, check out the section [How to use GDB](../appendix/2-how-to-use-gdb/).
+If you want to learn more about what GDB can do, check out the section [How to use
+GDB](../appendix/2-how-to-use-gdb/).
What's next? The high level API I promised.
diff --git a/mdbook/src/05-led-roulette/examples/blinky.rs b/mdbook/src/05-led-roulette/examples/blinky.rs
new file mode 100644
index 0000000..7333aa5
--- /dev/null
+++ b/mdbook/src/05-led-roulette/examples/blinky.rs
@@ -0,0 +1,30 @@
+#![deny(unsafe_code)]
+#![no_main]
+#![no_std]
+
+use cortex_m_rt::entry;
+use rtt_target::{rtt_init_print, rprintln};
+use panic_rtt_target as _;
+use microbit::board::Board;
+use microbit::hal::timer::Timer;
+use embedded_hal::{delay::DelayNs, digital::OutputPin};
+
+#[entry]
+fn main() -> ! {
+ rtt_init_print!();
+ let mut board = Board::take().unwrap();
+
+ let mut timer = Timer::new(board.TIMER0);
+
+ board.display_pins.col1.set_low().unwrap();
+ let mut row1 = board.display_pins.row1;
+
+ loop {
+ row1.set_low().unwrap();
+ rprintln!("Dark!");
+ timer.delay_ms(1_000_u32);
+ row1.set_high().unwrap();
+ rprintln!("Light!");
+ timer.delay_ms(1_000_u32);
+ }
+}
diff --git a/mdbook/src/05-led-roulette/examples/delay-print.rs b/mdbook/src/05-led-roulette/examples/delay-print.rs
new file mode 100644
index 0000000..6d36441
--- /dev/null
+++ b/mdbook/src/05-led-roulette/examples/delay-print.rs
@@ -0,0 +1,23 @@
+#![deny(unsafe_code)]
+#![no_main]
+#![no_std]
+
+use cortex_m_rt::entry;
+use rtt_target::{rtt_init_print, rprintln};
+use panic_rtt_target as _;
+use microbit::board::Board;
+use microbit::hal::timer::Timer;
+use embedded_hal::delay::DelayNs;
+
+#[entry]
+fn main() -> ! {
+ rtt_init_print!();
+ let board = Board::take().unwrap();
+
+ let mut timer = Timer::new(board.TIMER0);
+
+ loop {
+ timer.delay_ms(1000u32);
+ rprintln!("1000 ms passed");
+ }
+}
diff --git a/mdbook/src/05-led-roulette/examples/light-it-all.rs b/mdbook/src/05-led-roulette/examples/light-it-all.rs
new file mode 100644
index 0000000..9fffa80
--- /dev/null
+++ b/mdbook/src/05-led-roulette/examples/light-it-all.rs
@@ -0,0 +1,36 @@
+#![no_main]
+#![no_std]
+
+use cortex_m_rt::entry;
+use rtt_target::rtt_init_print;
+use panic_rtt_target as _;
+use microbit::{
+ board::Board,
+ display::blocking::Display,
+ hal::Timer,
+};
+use embedded_hal::delay::DelayNs;
+
+#[entry]
+fn main() -> ! {
+ rtt_init_print!();
+
+ let board = Board::take().unwrap();
+ let mut timer = Timer::new(board.TIMER0);
+ let mut display = Display::new(board.display_pins);
+ let light_it_all = [
+ [1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1],
+ ];
+
+ loop {
+ // Show light_it_all for 1000ms
+ display.show(&mut timer, light_it_all, 1000);
+ // clear the display again
+ display.clear();
+ timer.delay_ms(1000_u32);
+ }
+}
diff --git a/mdbook/src/05-led-roulette/examples/light-it-up.rs b/mdbook/src/05-led-roulette/examples/light-it-up.rs
new file mode 100644
index 0000000..24d9dbf
--- /dev/null
+++ b/mdbook/src/05-led-roulette/examples/light-it-up.rs
@@ -0,0 +1,18 @@
+#![deny(unsafe_code)]
+#![no_main]
+#![no_std]
+
+use cortex_m_rt::entry;
+use panic_halt as _;
+use microbit::board::Board;
+use embedded_hal::digital::OutputPin;
+
+#[entry]
+fn main() -> ! {
+ let mut board = Board::take().unwrap();
+
+ board.display_pins.col1.set_low().unwrap();
+ board.display_pins.row1.set_high().unwrap();
+
+ loop {}
+}
diff --git a/mdbook/src/05-led-roulette/flash-it.md b/mdbook/src/05-led-roulette/flash-it.md
index 73b4ec0..a98a250 100644
--- a/mdbook/src/05-led-roulette/flash-it.md
+++ b/mdbook/src/05-led-roulette/flash-it.md
@@ -1,43 +1,43 @@
# Flash it
-Flashing is the process of moving our program into the microcontroller's (persistent) memory. Once
+Flashing is the process of moving our program into the microcontroller's persistent memory. Once
flashed, the microcontroller will execute the flashed program every time it is powered on.
In this case, our `led-roulette` program will be the *only* program in the microcontroller memory.
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`.
+Flashing the binary itself is quite simple, thanks to `cargo embed`.
-Before executing that command though, let's 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 three black squares on there. The biggest
-one is a speaker. Another is our MCU
-we already talked about… but what purpose does the remaining
-one serve? This chip is another MCU, an NRF52820 almost as powerful as
-the NRF52833 we will be programming! This chip has three main purposes:
+Before executing that command though, let's 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
+three black squares on there. The biggest one is a speaker. Another is our MCU we already talked
+about… but what purpose does the remaining one serve? This chip is *another* MCU, an NRF52820 almost
+as powerful as the NRF52833 we will be programming! This chip has three main purposes:
-1. Enable power and reset control of our NRF52833 MCU from the USB connector
-2. Provide a serial to USB bridge for our MCU (we will look into that in a later chapter)
-3. Being a programmer/debugger (this is the relevant purpose for now)
+1. Enable power and reset control of our NRF52833 MCU from the USB connector.
+2. Provide a serial to USB bridge for our MCU (we will look into that in a later chapter).
+3. Provide an interface for programming and debugging our NRF52833 (this is the relevant purpose for
+ now).
-Basically this chip acts as a bridge between our computer (to which it is connected via USB) and the MCU (to which it is
-connected via traces and communicates with using the SWD protocol). This bridge enables us to flash new binaries on to
-the MCU, inspect its state via a debugger and other things.
+This chip acts as sort of bridge between our computer (to which it is connected via USB) and the MCU
+(to which it is connected via traces and communicates with using the SWD protocol). This bridge
+enables us to flash new binaries on to the MCU, inspect a program's state via a debugger and do
+other useful things.
So lets flash it!
```console
-$ cargo embed --target thumbv7em-none-eabihf
+$ cargo embed --example init
(...)
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
```
-
-You will notice that `cargo-embed` blocks after outputting the last line, this is intended and you should not close 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.
+You will notice that `cargo-embed` does not exit after outputting the last line. This is intended:
+you should not close `cargo-embed`, since we need it in this state for the next step — debugging it!
+Furthermore, you will have noticed that `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. Thus, you can leave out the `cargo build` step in the future if you want to
+flash your code right away.
diff --git a/mdbook/src/05-led-roulette/it-blinks.md b/mdbook/src/05-led-roulette/it-blinks.md
index 467cb99..4d824d7 100644
--- a/mdbook/src/05-led-roulette/it-blinks.md
+++ b/mdbook/src/05-led-roulette/it-blinks.md
@@ -1,54 +1,34 @@
# It blinks
+The equivalent of "hello world" for embedded programming is known as "blinky": make an LED on the
+board blink on and off. As with "hello world" in high-level programs, this verifies that you
+understand the basic operations needed for a very simple task, and that a bunch of things are
+working as expected.
+
## Delaying
-Now we're going to take a brief look into delay abstractions provided by `embedded-hal`
-before combining this with the GPIO abstractions from the previous chapter in order to
-finally make an LED blink.
-`embedded-hal` provides us with two abstractions to delay the execution of our program:
-[`DelayUs`] and [`DelayMs`]. Both of them essentially work the exact same way except
-that they accept different units for their delay function.
+Let's first take a brief look into delay abstractions provided by `embedded-hal`. We will then
+combine this with the GPIO abstractions from the previous chapter in order to finally make an LED
+blink.
+
+`embedded-hal` provides us with an abstraction to delay the
+execution of our program: the [`DelayNs`] trait.
-[`DelayUs`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/delay/trait.DelayUs.html
-[`DelayMs`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/delay/trait.DelayMs.html
+[`DelayNs`]: https://docs.rs/embedded-hal/1.0.0/embedded_hal/delay/trait.DelayMs.html
-Inside our MCU, several so-called "timers" exist. They can do various things regarding time for us,
-including simply pausing the execution of our program for a fixed amount of time. A very
-simple delay-based program that prints something every second might for example look like this:
+Our MCU contains many "timer" peripherals, and several other timer-like devices. As the name
+implies, timers can do various things regarding time for us, including simply pausing the execution
+of our program for a fixed amount of time. A very simple delay-based program that prints something
+every second might for example look like this code, in `examples/delay-print.rs`:
```rs
-#![deny(unsafe_code)]
-#![no_main]
-#![no_std]
-
-use cortex_m_rt::entry;
-use rtt_target::{rtt_init_print, rprintln};
-use panic_rtt_target as _;
-use microbit::board::Board;
-use microbit::hal::timer::Timer;
-use microbit::hal::prelude::*;
-
-#[entry]
-fn main() -> ! {
- rtt_init_print!();
- let mut board = Board::take().unwrap();
-
- let mut timer = Timer::new(board.TIMER0);
-
- loop {
- timer.delay_ms(1000u16);
- rprintln!("1000 ms passed");
- }
-}
+{{#include examples/delay-print.rs}}
```
-Note that we changed our panic implementation from `panic_halt` to
-`panic_rtt_target` here. This will require you to uncomment the two
-RTT lines from `Cargo.toml` and comment the `panic-halt` one out,
-since Rust only allows one panic implementation at a time.
+Note that we changed our panic implementation from `panic_halt` to `panic_rtt_target` here.
-In order to actually see the prints we have to change
-`Embed.toml` to turn off `gdb` and `halt_afterwards` and turn on `rtt`, like this:
+In order to actually see the prints we have to change `Embed.toml` to turn off `gdb` and
+`halt_afterwards` and turn on `rtt`, like this:
```
[default.general]
chip = "nrf52833_xxAA"
@@ -63,47 +43,23 @@ enabled = true
enabled = false
```
-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.
+And now with a quick `cargo embed --example delay-print` 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 section and looks like this:
+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 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 section and
+looks like this:
```rs
-#![deny(unsafe_code)]
-#![no_main]
-#![no_std]
-
-use cortex_m_rt::entry;
-use rtt_target::{rtt_init_print, rprintln};
-use panic_rtt_target as _;
-use microbit::board::Board;
-use microbit::hal::timer::Timer;
-use microbit::hal::prelude::*;
-
-#[entry]
-fn main() -> ! {
- rtt_init_print!();
- let mut board = Board::take().unwrap();
-
- let mut timer = Timer::new(board.TIMER0);
-
- board.display_pins.col1.set_low().unwrap();
- let mut row1 = board.display_pins.row1;
-
- loop {
- 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` (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.
+After`cargo embed --example blinky` (with the proper `Cargo.toml`) 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.
+
+Note that a more convenient way to run this code, since we don't need any of the fancy features of
+`cargo embed`, is just to say `cargo run --example blinky`. This will flash our blinky and start it
+running. You can take a look at `.cargo/config.toml` for how `cargo run` is invoking `probe-rs run`
+to flash and run our program.
diff --git a/mdbook/src/05-led-roulette/light-it-up.md b/mdbook/src/05-led-roulette/light-it-up.md
index ae124de..b01af8f 100644
--- a/mdbook/src/05-led-roulette/light-it-up.md
+++ b/mdbook/src/05-led-roulette/light-it-up.md
@@ -2,77 +2,65 @@
## embedded-hal
In this chapter we are going to make one of the many LEDs on 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 one of the traits
-provided by `embedded-hal`, specifically 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.
[`OutputPin`]: https://docs.rs/embedded-hal/0.2.6/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 separate 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.
+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 separate 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.
-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].
-Luckily for us though we can use the aforementioned micro:bit BSP
-which abstracts all of this nicely away from us.
+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]. 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/
[micro:bit v2 schematic]: https://github.com/microbit-foundation/microbit-v2-hardware/blob/main/V2.00/MicroBit_V2.0.0_S_schematic.PDF
## Actually lighting it up!
-The code required to light up an LED in the matrix is actually quite simple but it requires a bit of setup. First take
-a look at it and then we can go through it step by step:
+The code required to light up an LED in the matrix is actually quite simple but it requires a bit of
+setup. First take a look at `examples/light-it-up.rs`; then we can go through it step by step.
```rust
-#![deny(unsafe_code)]
-#![no_main]
-#![no_std]
-
-use cortex_m_rt::entry;
-use panic_halt as _;
-use microbit::board::Board;
-use microbit::hal::prelude::*;
-
-#[entry]
-fn main() -> ! {
- let mut board = Board::take().unwrap();
-
- board.display_pins.col1.set_low().unwrap();
- board.display_pins.row1.set_high().unwrap();
-
- loop {}
-}
+{{#include examples/light-it-up.rs}}
```
-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 few lines until the `main` function just do some basic imports and setup we mostly 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.
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.
+all the peripherals of a chip. When we say
-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.
+ let mut board = Board::take().unwrap();
+
+We take all of these peripherals from the PAC and bind 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.
+
+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/main.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:
+Testing our little program is quite simple. First put it into `src/main.rs`. Afterwards we simply
+have to run the `cargo embed` command from the last section again, and let it flash just like
+before. Then open our GDB and connect to the GDB stub:
```
$ # Your GDB debug command from the last section
@@ -83,5 +71,5 @@ cortex_m_rt::Reset () at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec8
(gdb)
```
-If we now let the program run via the GDB `continue` command, one of the LEDs on the back of the micro:bit should light
-up.
+We now let the program run via the GDB `continue` command: one of the LEDs on the front of the
+micro:bit should light up.
diff --git a/mdbook/src/05-led-roulette/my-solution.md b/mdbook/src/05-led-roulette/my-solution.md
index dd74eb7..8e99b6e 100644
--- a/mdbook/src/05-led-roulette/my-solution.md
+++ b/mdbook/src/05-led-roulette/my-solution.md
@@ -2,8 +2,8 @@
What solution did you come up with?
-Here's mine, it's probably one of the simplest (but of course not most
-beautiful) way to generate the required matrix:
+Here's mine. It's probably one of the simplest (but of course not most beautiful) ways to generate
+the required matrix:
``` rust
{{#include src/main.rs}}
@@ -12,46 +12,24 @@ beautiful) way to generate the required matrix:
One more thing! Check that your solution also works when compiled in "release" mode:
``` console
-$ cargo embed --target thumbv7em-none-eabihf --release
- (...)
+$ cargo embed --release
```
If you want to debug your "release" mode binary you'll have to use a different GDB command:
``` console
-$ gdb target/thumbv7em-none-eabihf/release/led-roulette
+$ gdb ../../../target/thumbv7em-none-eabihf/release/led-roulette
```
+The Rust compiler modifies the machine instructions generated in a release build (sometimes by a
+lot) in order to try to make the code faster or smaller. Unfortunately, GDB has a hard time figuring
+out what is going on after this. As a result, debugging release builds with GDB can be difficult.
+
Binary size is something we should always keep an eye on! How big is your solution? You can check
that using the `size` command on the release binary:
``` console
-$ cargo size --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 --target thumbv7em-none-eabihf --release -- -A
+$ cargo size --release -- -A
Finished release [optimized + debuginfo] target(s) in 0.02s
led-roulette :
section size addr
@@ -76,15 +54,13 @@ section size addr
Total 283715
```
-Your numbers may differ somewhat depending on how your code
-is built: this is OK.
-
-> **NOTE** The Cargo project is already configured to build the release binary using LTO.
+Your numbers may differ somewhat depending on how your code is built: this is OK.
-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.
+Know how to read this output? The `text` section contains the program instructions. The `rodata`
+section contains read-only data stored with the program instructions. The `data` and `bss` sections
+contain variables statically allocated in RAM (`static` variables). If you remember the
+specification of the microcontroller on your micro:bit, you should notice that its flash memory is
+less than double the size of this extremely simple binary: can this be right? As we can see from
+the size statistics most of the binary is actually made up of debugging related sections. However,
+those are not flashed to the microcontroller at any time — after all they aren't relevant for the
+execution.
diff --git a/mdbook/src/05-led-roulette/src/main.rs b/mdbook/src/05-led-roulette/src/main.rs
index ad0f34a..5611133 100644
--- a/mdbook/src/05-led-roulette/src/main.rs
+++ b/mdbook/src/05-led-roulette/src/main.rs
@@ -7,23 +7,11 @@ use microbit::{board::Board, display::blocking::Display, hal::Timer};
use panic_rtt_target as _;
use rtt_target::rtt_init_print;
+#[rustfmt::ignore]
+
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),
+ (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]
@@ -33,6 +21,7 @@ fn main() -> ! {
let board = Board::take().unwrap();
let mut timer = Timer::new(board.TIMER0);
let mut display = Display::new(board.display_pins);
+ #[rustfmt::ignore]
let mut leds = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
diff --git a/mdbook/src/05-led-roulette/the-challenge.md b/mdbook/src/05-led-roulette/the-challenge.md
index 613c56a..4e23bb5 100644
--- a/mdbook/src/05-led-roulette/the-challenge.md
+++ b/mdbook/src/05-led-roulette/the-challenge.md
@@ -4,56 +4,22 @@ You are now well armed to face a challenge! Your task will be to implement the a
you at the beginning of this chapter.
-
+
If you can't exactly see what's happening here it is in a much slower version:
-
+
Since working with the LED pins separately is quite annoying
-(especially if you have to use basically all of them like here)
-you can use the display API provided by the BSP. It works like this:
+(especially if you have to use basically all of them like
+here) you can use the display API provided by the BSP. It
+works like this (`examples/light-it-all.rs`):
```rust
-#![deny(unsafe_code)]
-#![no_main]
-#![no_std]
-
-use cortex_m_rt::entry;
-use rtt_target::rtt_init_print;
-use panic_rtt_target as _;
-use microbit::{
- board::Board,
- display::blocking::Display,
- hal::{prelude::*, Timer},
-};
-
-#[entry]
-fn main() -> ! {
- rtt_init_print!();
-
- let board = Board::take().unwrap();
- let mut timer = Timer::new(board.TIMER0);
- let mut display = Display::new(board.display_pins);
- let light_it_all = [
- [1, 1, 1, 1, 1],
- [1, 1, 1, 1, 1],
- [1, 1, 1, 1, 1],
- [1, 1, 1, 1, 1],
- [1, 1, 1, 1, 1],
- ];
-
- loop {
- // Show light_it_all for 1000ms
- display.show(&mut timer, light_it_all, 1000);
- // clear the display again
- display.clear();
- timer.delay_ms(1000_u32);
- }
-}
+{{#include examples/light-it-all.rs}}
```
Equipped with this API your task basically boils down to just having