diff --git a/src/03-setup/README.md b/src/03-setup/README.md index 9f8840107..4771e9539 100644 --- a/src/03-setup/README.md +++ b/src/03-setup/README.md @@ -29,7 +29,7 @@ should work but we have listed the version we have tested. [`cargo-binutils`]: https://github.com/rust-embedded/cargo-binutils -- [`cargo-embed`]. Version 0.9.0 or newer. +- [`cargo-embed`]. Version 0.9.1 or newer. [`cargo-embed`]: https://github.com/probe-rs/cargo-embed diff --git a/src/04-meet-your-hardware/README.md b/src/04-meet-your-hardware/README.md index e96846da3..ad2089b9e 100644 --- a/src/04-meet-your-hardware/README.md +++ b/src/04-meet-your-hardware/README.md @@ -2,22 +2,19 @@ Let's get familiar with the hardware we'll be working with. -## STM32F3DISCOVERY (the "F3") +## micro:bit

- +

-We'll refer to this board as "F3" throughout this book. Here are some of the -many components on the board: +Here are some of the many components on the board: - A [microcontroller]. -- A number of LEDs, including the eight aligned in a "compass" formation. -- Two buttons. -- Two USB ports. -- An [accelerometer]. -- A [magnetometer]. -- A [gyroscope]. +- A number of LEDs, most notably the LED matrix on the back +- Two user buttons as well as a reset button (the one next to the USB port). +- One USB port. +- A sensor that is both a [magnetometer] and an [accelerometer] [microcontroller]: https://en.wikipedia.org/wiki/Microcontroller [accelerometer]: https://en.wikipedia.org/wiki/Accelerometer @@ -25,17 +22,20 @@ many components on the board: [gyroscope]: https://en.wikipedia.org/wiki/Gyroscope Of these components, the most important is the microcontroller (sometimes -shortened to "MCU" for "microcontroller unit"), which is the large black square -sitting in the center of your board. The MCU is what runs your code. You might -sometimes read about "programming a board", when in reality what we are doing -is programming the MCU that is installed on the board. +shortened to "MCU" for "microcontroller unit"), which is the bigger of the two +black squares sitting on the side of the board with the USB port. The MCU is +what runs your code. You might sometimes read about "programming a board", when +in reality what we are doing is programming the MCU that is installed on the board. -## STM32F303VCT6 (the "STM32F3") +If you happen to be interested in a more in detail description of the board you +can checkout the [micro:bit website](https://tech.microbit.org/hardware/). + +## Nordic nRF51822 (the "nRF51") Since the MCU is so important, let's take a closer look at the one sitting on our board. -Our MCU is surrounded by 100 tiny metal **pins**. These pins are connected to -**traces**, the little "roads" that act as the wires connecting components +Our MCU has 48 tiny metal **pins** sitting right underneath it (it's a so called [QFN48] chip). +These pins are connected to **traces**, the little "roads" that act as the wires connecting components together on the board. The MCU can dynamically alter the electrical properties of the pins. This works similar to a light switch altering how electrical current flows through a circuit. By enabling or disabling electrical current to @@ -44,68 +44,57 @@ be turned on and off. Each manufacturer uses a different part numbering scheme, but many will allow you to determine information about a component simply by looking at the part -number. Looking at our MCU's part number (`STM32F303VCT6`), the `ST` at the -front hints to us that this is a part manufactured by [ST Microelectronics]. -Searching through [ST's marketing materials] we can also learn the following: - -[ST Microelectronics]: https://st.com/ -[ST's marketing materials]: https://www.st.com/en/microcontrollers-microprocessors/stm32-mainstream-mcus.html - -- The `M32` represents that this is an Arm®-based 32-bit microcontroller. -- The `F3` represents that the MCU is from ST's "STM32F3" series. This is a - series of MCUs based on the Cortex®-M4 processor design. -- The remainder of the part number goes into more details about things like - extra features and RAM size, which at this point we're less concerned about. - -> ### Arm? Cortex-M4? -> -> If our chip is manufactured by ST, then who is Arm? And if our chip is the -> STM32F3, what is the Cortex-M4? -> -> You might be surprised to hear that while "Arm-based" chips are quite -> popular, the company behind the "Arm" trademark ([Arm Holdings][]) doesn't -> actually manufacture chips for purchase. Instead, their primary business -> model is to just *design* parts of chips. They will then license those designs to -> manufacturers, who will in turn implement the designs (perhaps with some of -> their own tweaks) in the form of physical hardware that can then be sold. -> Arm's strategy here is different from companies like Intel, which both -> designs *and* manufactures their chips. -> -> Arm licenses a bunch of different designs. Their "Cortex-M" family of designs -> are mainly used as the core in microcontrollers. For example, the Cortex-M0 -> is designed for low cost and low power usage. The Cortex-M7 is higher cost, -> but with more features and performance. The core of our STM32F3 is based on -> the Cortex-M4, which is in the middle: more features and performance than the -> Cortex-M0, but less expensive than the Cortex-M7. -> -> Luckily, you don't need to know too much about different types of processors -> or Cortex designs for the sake of this book. However, you are hopefully now a -> bit more knowledgeable about the terminology of your device. While you are -> working specifically with an STM32F3, you might find yourself reading -> documentation and using tools for Cortex-M-based chips, as the STM32F3 is -> based on a Cortex-M design. +number. Looking at our MCU's part number (`nRF51822-QFAA-R`, you probably cannot +see it with your bare eye, but it is on the chip), the `n` at the +front hints to us that this is a part manufactured by [Nordic Semiconductor]. +Looking up the part number on their website we quickly find the [product page]. +There we learn that our chip's main marketing point is that it is a +"Bluetooth Low Energy and 2.4 GHz SoC" (SoC being short for "System on a Chip"), +which explains the RF in the product name since RF is short for radio frequency. +If we search through the documentation of the chip linked on the [product page] +for a bit we find the [product specification] which contains chapter 10 "Ordering Information" +dedicated to explaining the weird chip naming. Here we learn that: + +[QFN48]: https://en.wikipedia.org/wiki/Flat_no-leads_package +[Nordic Semiconductor]: https://www.nordicsemi.com/ +[product page]: https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF51822 +[product specification]: https://infocenter.nordicsemi.com/pdf/nRF51822_PS_v3.3.pdf + +- The `nRF51` is the MCU's series, indicating that there are other `nRF51` MCUs +- The `822` is the part code +- The `QF` is short for `QFN48` +- The `AA` is the variant code, indicating how much RAM and flash memory the MCU has, + in our case 256 kilobyte flash and 16 kilobyte RAM +- The `R` is the packaging code which is relevant for factories manufacturing boards + with this chip on them in larger scales + +The product specification does of course contain a lot more useful information about +the chip, for example that it is based on an ARM® Cortex™-M0 32 bit processor. + +### Arm? Cortex-M0? + +If our chip is manufactured by Nordic, then who is Arm? And if our chip is the +nRF51822, what is the Cortex-M0? + +You might be surprised to hear that while "Arm-based" chips are quite +popular, the company behind the "Arm" trademark ([Arm Holdings][]) doesn't +actually manufacture chips for purchase. Instead, their primary business +model is to just *design* parts of chips. They will then license those designs to +manufacturers, who will in turn implement the designs (perhaps with some of +their own tweaks) in the form of physical hardware that can then be sold. +Arm's strategy here is different from companies like Intel, which both +designs *and* manufactures their chips. + +Arm licenses a bunch of different designs. Their "Cortex-M" family of designs +are mainly used as the core in microcontrollers. For example, the Cortex-M0 +(the core our chip is based on) is designed for low cost and low power usage. +The Cortex-M7 is higher cost, but with more features and performance. + +Luckily, you don't need to know too much about different types of processors +or Cortex designs for the sake of this book. However, you are hopefully now a +bit more knowledgeable about the terminology of your device. While you are +working specifically with an nRF51822, you might find yourself reading +documentation and using tools for Cortex-M-based chips, as the nRF51822 is +based on a Cortex-M design. [Arm Holdings]: https://www.arm.com/ - -## The Serial module - -

- -

- -If you have an older revision of the discovery board, you can use this module to -exchange data between the microcontroller in the F3 and your computer. This module -will be connected to your computer using an USB cable. I won't say more at this -point. - -If you have a newer release of the board then you don't need this module. The -ST-LINK will double as a USB<->serial converter connected to the microcontroller USART1 at pins PC4 and PC5. - -## The Bluetooth module - -

- -

- -This module has the exact same purpose as the serial module but it sends the data over Bluetooth -instead of over USB. diff --git a/src/05-led-roulette/.cargo/config b/src/05-led-roulette/.cargo/config index 01d25c8b3..a0ec1777f 100644 --- a/src/05-led-roulette/.cargo/config +++ b/src/05-led-roulette/.cargo/config @@ -1,5 +1,7 @@ -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q" +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] 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 43ac7579f..776e8a465 100644 --- a/src/05-led-roulette/Cargo.toml +++ b/src/05-led-roulette/Cargo.toml @@ -1,8 +1,13 @@ [package] -authors = ["Jorge Aparicio "] -edition = "2018" name = "led-roulette" version = "0.1.0" +authors = ["Henrik Böving "] +edition = "2018" [dependencies] -aux5 = { path = "auxiliary" } +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"] } diff --git a/src/05-led-roulette/Embed.toml b/src/05-led-roulette/Embed.toml new file mode 100644 index 000000000..91584ec92 --- /dev/null +++ b/src/05-led-roulette/Embed.toml @@ -0,0 +1,11 @@ +[default.general] +chip = "nrf51822_xxAA" + +[default.reset] +halt_afterwards = true + +[default.rtt] +enabled = false + +[default.gdb] +enabled = true diff --git a/src/05-led-roulette/README.md b/src/05-led-roulette/README.md index 1f5ccdd56..b3e928d6a 100644 --- a/src/05-led-roulette/README.md +++ b/src/05-led-roulette/README.md @@ -3,22 +3,17 @@ 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. -Throughout this text we'll be using the starter code that's in the [discovery] repository. Make sure -you always have the latest version of the master branch because this website tracks that branch. - The starter code is in the `src` directory of that repository. Inside that directory there are more directories named after each chapter of this book. Most of those directories are starter Cargo projects. -[discovery]: https://github.com/rust-embedded/discovery - Now, jump into the `src/05-led-roulette` directory. Check the `src/main.rs` file: ``` rust @@ -46,4 +41,18 @@ as well. This directory contains a Cargo configuration file (`.cargo/config`) th 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. +Furthermore there is also an `Embed.toml` file + +```toml +{{#include Embed.toml}} +``` + +This file tells `cargo-embed` that: + +* we are working with a nrf51822, +* 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 + Alright, let's start by building this program. diff --git a/src/05-led-roulette/auxiliary/Cargo.toml b/src/05-led-roulette/auxiliary/Cargo.toml deleted file mode 100644 index 1b7b80f55..000000000 --- a/src/05-led-roulette/auxiliary/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -authors = ["Jorge Aparicio "] -edition = "2018" -name = "aux5" -version = "0.1.0" - -[dependencies] -cortex-m = "0.6.3" -panic-halt = "0.2.0" -cortex-m-rt = "0.6.3" - -[dependencies.f3] -features = ["rt"] -version = "0.6.1" diff --git a/src/05-led-roulette/auxiliary/src/lib.rs b/src/05-led-roulette/auxiliary/src/lib.rs deleted file mode 100644 index 2218b18e5..000000000 --- a/src/05-led-roulette/auxiliary/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Initialization code - -#![no_std] - -#[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964 -extern crate panic_halt; // panic handler - -pub use cortex_m_rt::entry; -pub use f3::{ - hal::{delay::Delay, prelude}, - led::Leds, -}; - -use f3::hal::{prelude::*, stm32f30x}; - -pub fn init() -> (Delay, Leds) { - let cp = cortex_m::Peripherals::take().unwrap(); - let dp = stm32f30x::Peripherals::take().unwrap(); - - let mut flash = dp.FLASH.constrain(); - let mut rcc = dp.RCC.constrain(); - - let clocks = rcc.cfgr.freeze(&mut flash.acr); - - let delay = Delay::new(cp.SYST, clocks); - - let leds = Leds::new(dp.GPIOE.split(&mut rcc.ahb)); - - (delay, leds) -} diff --git a/src/05-led-roulette/build-it.md b/src/05-led-roulette/build-it.md index 420cf524b..3766f0a96 100644 --- a/src/05-led-roulette/build-it.md +++ b/src/05-led-roulette/build-it.md @@ -5,21 +5,23 @@ 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 F3 has a Cortex-M4F processor in it. `rustc` knows how to cross compile -to the Cortex-M architecture and provides 4 different targets that cover the different processor +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 families within that architecture: - `thumbv6m-none-eabi`, for the Cortex-M0 and Cortex-M1 processors - `thumbv7m-none-eabi`, for the Cortex-M3 processor - `thumbv7em-none-eabi`, for the Cortex-M4 and Cortex-M7 processors - `thumbv7em-none-eabihf`, for the Cortex-M4**F** and Cortex-M7**F** processors +- `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 F3, we'll use the `thumbv7em-none-eabihf` target. Before cross compiling you have to +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`: ``` console -$ rustup target add thumbv7em-none-eabihf +$ rustup target add thumbv6m-none-eabi ``` You only need to do the above step once; `rustup` will re-install a new standard library @@ -30,48 +32,54 @@ With the `rust-std` component in place you can now cross compile the program usi ``` console $ # make sure you are in the `src/05-led-roulette` directory -$ cargo build --target thumbv7em-none-eabihf +$ cargo build --target thumbv6m-none-eabi Compiling semver-parser v0.7.0 - Compiling aligned v0.1.1 - Compiling libc v0.2.35 - Compiling bare-metal v0.1.1 - Compiling cast v0.2.2 - Compiling cortex-m v0.4.3 - (..) - Compiling stm32f30x v0.6.0 - Compiling stm32f30x-hal v0.1.2 - Compiling aux5 v0.1.0 (file://$PWD/aux) - Compiling led-roulette v0.1.0 (file://$PWD) - Finished dev [unoptimized + debuginfo] target(s) in 35.84 secs + 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 ``` -> **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. -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: +> **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/thumbv7em-none-eabihf/debug/led-roulette` -$ cargo readobj --target thumbv7em-none-eabihf --bin led-roulette -- -file-headers +$ # equivalent to `readelf -h target/thumbv6m-none-eabi/debug/led-roulette` + cargo readobj --target thumbv6m-none-eabi --bin led-roulette -- -file-headers 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: 0x0 + ABI Version: 0 Type: EXEC (Executable file) Machine: ARM Version: 0x1 - Entry point address: 0x8000197 + Entry point address: 0xC1 Start of program headers: 52 (bytes into file) - Start of section headers: 740788 (bytes into file) - Flags: 0x5000400 + Start of section headers: 599484 (bytes into file) + Flags: 0x5000200 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 2 Size of section headers: 40 (bytes) - Number of section headers: 20 - Section header string table index: 18 + Number of section headers: 22 + Section header string table index: 20 ``` Next, we'll flash the program into our microcontroller. diff --git a/src/05-led-roulette/build.rs b/src/05-led-roulette/build.rs new file mode 100644 index 000000000..c8d8c9141 --- /dev/null +++ b/src/05-led-roulette/build.rs @@ -0,0 +1,30 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory (wherever `Cargo.toml` is). However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! a rebuild of the application with new memory settings is ensured after updating `memory.x`. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/src/05-led-roulette/debug-it.md b/src/05-led-roulette/debug-it.md index 4131dfca9..fedee282b 100644 --- a/src/05-led-roulette/debug-it.md +++ b/src/05-led-roulette/debug-it.md @@ -1,33 +1,64 @@ # Debug it +## 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? -We are already inside a debugging session so let's debug our program. +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 +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. -After the `load` command, our program is stopped at its *entry point*. This is indicated by the -"Start address 0x8000XXX" part of GDB's output. The entry point is the part of a program that a -processor / CPU will execute first. +## Let's debug! -The starter project I've provided to you has some extra code that runs *before* the `main` function. -At this time, we are not interested in that "pre-main" part so let's skip right to the beginning of -the `main` function. We'll do that using a breakpoint: +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: +```shell +$ 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) ``` -(gdb) break main -Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 10. +> **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**: 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 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. + +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: + +``` +(gdb) break main +Breakpoint 1 at 0xac: file src/05-led-roulette/src/main.rs, line 9. (gdb) continue Continuing. Note: automatically using hardware breakpoints for read-only addresses. -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10 -10 let x = 42; +Breakpoint 1, main () 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 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 six of these -breakpoints so it's a good idea to pay attention to these messages. +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: @@ -41,43 +72,40 @@ mode, on the GDB shell enter the following command: ![GDB session](../assets/gdb-layout-src.png "GDB TUI") -At any point you can leave the TUI mode using the following command: +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) tui disable -``` +(gdb) break 13 +Breakpoint 2 at 0xb8: file src/05-led-roulette/src/main.rs, line 13. +(gdb) continue +Continuing. -OK. We are now at the beginning of `main`. We can advance the program statement by statement using -the `step` command. So let's use that twice to reach the `_y = x` statement. Once you've typed `step` -once you can just hit enter to run it -again. +Breakpoint 2, led_roulette::__cortex_m_rt_main () at src/05-led-roulette/src/main.rs:13 +(gdb) +``` +At any point you can leave the TUI mode using the following command: ``` -(gdb) step -14 _y = x; +(gdb) tui disable ``` -If you are not using the TUI mode, on each `step` call GDB will print back the current statement -along with its line number. - We are now "on" the `_y = x` statement; that statement hasn't been executed yet. This means that `x` is initialized but `_y` is not. Let's inspect those stack/local variables using the `print` command: ``` (gdb) print x $1 = 42 - (gdb) print &x -$2 = (i32 *) 0x10001ff4 - +$2 = (*mut i32) 0x20003fe8 (gdb) print _y -$3 = -536810104 - +$3 = 536870912 (gdb) print &_y -$4 = (i32 *) 0x10001ff0 +$4 = (*mut i32) 0x20003fec +(gdb) ``` -As expected, `x` contains the value `42`. `_y`, however, contains the value `-536810104` (?). Because +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 @@ -90,14 +118,16 @@ Instead of printing the local variables one by one, you can also use the `info l ``` (gdb) info locals x = 42 -_y = -536810104 +_y = 536870912 +(gdb) ``` -OK. With another `step`, we'll be on top of the `loop {}` statement: +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: ``` -(gdb) step -17 loop {} +(gdb) next +16 loop {} ``` And `_y` should now be initialized. @@ -107,12 +137,12 @@ And `_y` should now be initialized. $5 = 42 ``` -If we use `step` again on top of the `loop {}` statement, we'll get stuck because the program will +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 source code view later by issuing the `layout src` command again. -> **NOTE** If you used the `step` 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 @@ -125,62 +155,54 @@ program around the line you are currently at. ``` (gdb) disassemble /m -Dump of assembler code for function main: -7 #[entry] - 0x08000188 <+0>: sub sp, #8 - 0x0800018a <+2>: movs r0, #42 ; 0x2a - -8 fn main() -> ! { -9 let _y; -10 let x = 42; - 0x0800018c <+4>: str r0, [sp, #4] - -11 _y = x; - 0x0800018e <+6>: ldr r0, [sp, #4] - 0x08000190 <+8>: str r0, [sp, #0] - -12 -13 // infinite loop; just so we don't leave this stack frame -14 loop {} -=> 0x08000192 <+10>: b.n 0x8000194 - 0x08000194 <+12>: b.n 0x8000194 +Dump of assembler code for function led_roulette::__cortex_m_rt_main: +10 fn main() -> ! { + 0x000000b2 <+0>: sub sp, #8 + 0x000000b4 <+2>: movs r0, #42 ; 0x2a + +11 let _y; +12 let x = 42; + 0x000000b6 <+4>: str r0, [sp, #0] + +13 _y = x; + 0x000000b8 <+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 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 and the line number +of the instruction the processor will execute next. ``` (gdb) stepi -0x08000194 14 loop {} - +16 loop {} (gdb) stepi -0x08000194 14 loop {} +16 loop {} ``` One last trick before we move to something more interesting. Enter the following commands into GDB: ``` -(gdb) monitor reset halt -Unable to match requested speed 1000 kHz, using 950 kHz -Unable to match requested speed 1000 kHz, using 950 kHz -adapter speed: 950 kHz -target halted due to debug-request, current mode: Thread -xPSR: 0x01000000 pc: 0x08000196 msp: 0x10002000 - -(gdb) continue +(gdb) monitor reset +(gdb) c Continuing. -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10 -10 let x = 42; +Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9 +9 #[entry] +(gdb) ``` 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. +`monitor reset` 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. @@ -202,8 +224,9 @@ A debugging session is active. Inferior 1 [Remote target] will be detached. Quit anyway? (y or n) y -Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target +Detaching from program: $PWD/target/thumbv6m-none-eabi/debug/led-roulette, Remote target 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 @@ -212,8 +235,6 @@ Ending remote debugging. [gdb-dashboard]: https://github.com/cyrus-and/gdb-dashboard#gdb-dashboard -Don't close OpenOCD though! We'll use it again and again later on. It's better -just to leave it running. 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/src/05-led-roulette/flash-it.md b/src/05-led-roulette/flash-it.md index 54c50b98e..2ea7da03b 100644 --- a/src/05-led-roulette/flash-it.md +++ b/src/05-led-roulette/flash-it.md @@ -7,150 +7,32 @@ 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. -Onto the actual flashing. First thing we need is to do is launch OpenOCD. We did that in the -previous section but this time we'll run the command inside a temporary directory (`/tmp` on \*nix; -`%TEMP%` on Windows). +Flashing the binary itself is quite simple thanks to `cargo-embed`, you only have to type `cargo embed`. -Make sure the F3 is connected to your computer and run the following commands on a new terminal. +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 +we already talked about but what purpose does the other one serve? The other chip has 3 main purposes: -``` console -$ # *nix -$ cd /tmp +1. Provide power from the USB connector to our MCU +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) -$ # Windows -$ cd %TEMP% +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. -$ # Windows: remember that you need an extra `-s %PATH_TO_OPENOCD%\share\scripts` -$ openocd \ - -f interface/stlink-v2-1.cfg \ - -f target/stm32f3x.cfg -``` - -> **NOTE** Older revisions of the board need to pass slightly different arguments to -> `openocd`. Review [this section] for the details. - -[this section]: ../03-setup/verify.md#first-openocd-connection - -The program will block; leave that terminal open. - -Now it's a good time to explain what this command is actually doing. - -I mentioned that the F3 actually has two microcontrollers. One of them is used as a -programmer/debugger. The part of the board that's used as a programmer is called ST-LINK (that's what -STMicroelectronics decided to call it). This ST-LINK is connected to the target microcontroller -using a Serial Wire Debug (SWD) interface (this interface is an ARM standard so you'll run into it -when dealing with other Cortex-M based microcontrollers). This SWD interface can be used to flash -and debug a microcontroller. The ST-LINK is connected to the "USB ST-LINK" port and will appear as -a USB device when you connect the F3 to your computer. - -

- -

- - -As for OpenOCD, it's software that provides some services like a *GDB server* on top of USB -devices that expose a debugging protocol like SWD or JTAG. +So lets flash it! -Onto the actual command: those `.cfg` files we are using instruct OpenOCD to look for a ST-LINK USB -device (`interface/stlink-v2-1.cfg`) and to expect a STM32F3XX microcontroller -(`target/stm32f3x.cfg`) to be connected to the ST-LINK. - -The OpenOCD output looks like this: - -``` console -Open On-Chip Debugger 0.9.0 (2016-04-27-23:18) -Licensed under GNU GPL v2 -For bug reports, read - http://openocd.org/doc/doxygen/bugs.html -Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '. -adapter speed: 1000 kHz -adapter_nsrst_delay: 100 -Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD -none separate -Info : Unable to match requested speed 1000 kHz, using 950 kHz -Info : Unable to match requested speed 1000 kHz, using 950 kHz -Info : clock speed 950 kHz -Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B -Info : using stlink api v2 -Info : Target voltage: 2.919073 -Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints -``` - -The "6 breakpoints, 4 watchpoints" part indicates the debugging features the processor has -available. - -Leave that `openocd` process running, and open a new terminal. Make sure that you are inside the project's `src/05-led-roulette/` directory. - -I mentioned that OpenOCD provides a GDB server so let's connect to that right now: - -``` console -$ -q target/thumbv7em-none-eabihf/debug/led-roulette -Reading symbols from target/thumbv7em-none-eabihf/debug/led-roulette...done. -(gdb) +```console +$ cargo embed + (...) + 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 ``` -**NOTE**: `` represents a GDB program capable of debugging ARM binaries. -This could be `arm-none-eabi-gdb`, `gdb-multiarch` or `gdb` depending on your -system -- you may have to try all three. - -This only opens a GDB shell. To actually connect to the OpenOCD GDB server, use the following -command within the GDB shell: - -``` -(gdb) target remote :3333 -Remote debugging using :3333 -0x00000000 in ?? () -``` - -**NOTE**: If you are getting errors like `undefined debug reason 7 - target needs reset`, you can try running `monitor reset halt` as described [here](https://stackoverflow.com/questions/38994596/reason-7-target-needs-reset-unreliable-debugging-setup). - -**NOTE**: If the debugger is still not connecting to the OpenOCD server, then you may need to try using `arm-none-eabi-gdb` instead of the `gdb` command, as described above. - -By default OpenOCD's GDB server listens on TCP port 3333 (localhost). This command is connecting to -that port. - -After entering this command, you'll see new output in the OpenOCD terminal: - -``` diff - Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints -+Info : accepting 'gdb' connection on tcp/3333 -+Info : device id = 0x10036422 -+Info : flash size = 256kbytes -``` - -Almost there. To flash the device, we'll use the `load` command inside the GDB shell: - -``` -(gdb) load -Loading section .vector_table, size 0x188 lma 0x8000000 -Loading section .text, size 0x38a lma 0x8000188 -Loading section .rodata, size 0x8 lma 0x8000514 -Start address 0x8000188, load size 1306 -Transfer rate: 6 KB/sec, 435 bytes/write. -``` - -And that's it. You'll also see new output in the OpenOCD terminal. - -``` diff - Info : flash size = 256kbytes -+Info : Unable to match requested speed 1000 kHz, using 950 kHz -+Info : Unable to match requested speed 1000 kHz, using 950 kHz -+adapter speed: 950 kHz -+target state: halted -+target halted due to debug-request, current mode: Thread -+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000 -+Info : Unable to match requested speed 8000 kHz, using 4000 kHz -+Info : Unable to match requested speed 8000 kHz, using 4000 kHz -+adapter speed: 4000 kHz -+target state: halted -+target halted due to breakpoint, current mode: Thread -+xPSR: 0x61000000 pc: 0x2000003a msp: 0x2000a000 -+Info : Unable to match requested speed 1000 kHz, using 950 kHz -+Info : Unable to match requested speed 1000 kHz, using 950 kHz -+adapter speed: 950 kHz -+target state: halted -+target halted due to debug-request, current mode: Thread -+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000 -``` -Our program is loaded, let's debug it! +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! diff --git a/src/05-led-roulette/it-blinks.md b/src/05-led-roulette/it-blinks.md new file mode 100644 index 000000000..17b572abd --- /dev/null +++ b/src/05-led-roulette/it-blinks.md @@ -0,0 +1,102 @@ +# It blinks + +## 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. + +[DelayUs]: https://docs.rs/embedded-hal/0.2.4/embedded_hal/blocking/delay/trait.DelayUs.html +[DelayMs]: https://docs.rs/embedded-hal/0.2.4/embedded_hal/blocking/delay/trait.DelayMs.html + +Inside of 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: + +```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 nrf51_hal as hal; +use hal::prelude::*; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let p = hal::pac::Peripherals::take().unwrap(); + + let mut delay = hal::Timer::new(p.TIMER0); + loop { + delay.delay_ms(1000u32); + rprintln!("1000 ms passed"); + } +} +``` + +In order to actually see the prints we have to change `Embed.toml` like this: +``` +[default.general] +chip = "nrf51822_xxAA" + +[default.reset] +halt_afterwards = false + +[default.rtt] +enabled = true + +[default.gdb] +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. + +## 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: + +```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 nrf51_hal as hal; +use hal::prelude::*; + +#[entry] +fn main() -> ! { + rtt_init_print!(); + let p = hal::pac::Peripherals::take().unwrap(); + + let mut delay = hal::Timer::new(p.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); + + loop { + row1.set_high().unwrap(); + rprintln!("Light!"); + delay.delay_ms(500u32); + + row1.set_low().unwrap(); + rprintln!("Dark!"); + delay.delay_ms(500u32); + } +} +``` + +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. diff --git a/src/05-led-roulette/light-it-up.md b/src/05-led-roulette/light-it-up.md new file mode 100644 index 000000000..c17f4181f --- /dev/null +++ b/src/05-led-roulette/light-it-up.md @@ -0,0 +1,112 @@ +# Light it up +## 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. + +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 + +## 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. + +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**: 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). + +[schematic page]: https://tech.microbit.org/hardware/schematic/ +[schematic]: https://github.com/bbcmicrobit/hardware/blob/master/V1.5/SCH_BBC-Microbit_V1.5.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: + +```rust +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use cortex_m_rt::entry; +use panic_halt as _; +use nrf51_hal as hal; +use 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); + + row1.set_high().unwrap(); + + loop {} +} +``` + +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. + +> **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 +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: + +``` +$ gdb target/thumbv6m-none-eabi/debug/led-roulette +(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 +489 pub unsafe extern "C" fn Reset() -> ! { +(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. diff --git a/src/05-led-roulette/memory.x b/src/05-led-roulette/memory.x new file mode 100644 index 000000000..9e2ab65f6 --- /dev/null +++ b/src/05-led-roulette/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + /* NOTE K = KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/src/05-led-roulette/my-solution.md b/src/05-led-roulette/my-solution.md index 0bf05d0e9..a9c2614ed 100644 --- a/src/05-led-roulette/my-solution.md +++ b/src/05-led-roulette/my-solution.md @@ -9,99 +9,134 @@ Here's mine: #![no_main] #![no_std] -use aux5::{entry, prelude::*, Delay, Leds}; +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) +]; #[entry] fn main() -> ! { - let (mut delay, mut leds): (Delay, Leds) = aux5::init(); + rtt_init_print!(); + let p = hal::pac::Peripherals::take().unwrap(); + + let mut delay = hal::Timer::new(p.TIMER0); + + 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 ms = 50_u8; loop { - for curr in 0..8 { - let next = (curr + 1) % 8; + let mut previous_pair = (1, 1); + for current_pair in COMBINATIONS.iter() { + delay.delay_ms(30u32); - leds[next].on(); - delay.delay_ms(ms); - leds[curr].off(); - delay.delay_ms(ms); + 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; } } } - ``` One more thing! Check that your solution also works when compiled in "release" mode: ``` console -$ cargo build --target thumbv7em-none-eabihf --release +$ cargo embed --release ``` -You can test it with this `gdb` command: +If you want to debug your "release" mode binary you'll have to use a different GDB command: ``` console -$ # or, you could simply call `cargo run --target thumbv7em-none-eabihf --release` -$ arm-none-eabi-gdb target/thumbv7em-none-eabihf/release/led-roulette -$ # ~~~~~~~ +$ gdb target/thumbv6m-none-eabi/release/led-roulette ``` 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 -$ # equivalent to size target/thumbv7em-none-eabihf/debug/led-roulette -$ cargo size --target thumbv7em-none-eabihf --bin led-roulette -- -A +$ cargo size --bin led-roulette -- -A + Finished dev [unoptimized + debuginfo] target(s) in 0.03s led-roulette : section size addr -.vector_table 392 0x8000000 -.text 16404 0x8000188 -.rodata 2924 0x80041a0 +.vector_table 168 0x0 +.text 20996 0xa8 +.rodata 2956 0x52ac .data 0 0x20000000 -.bss 4 0x20000000 -.debug_str 602185 0x0 -.debug_abbrev 24134 0x0 -.debug_info 553143 0x0 -.debug_ranges 112744 0x0 -.debug_macinfo 86 0x0 -.debug_pubnames 56467 0x0 -.debug_pubtypes 94866 0x0 -.ARM.attributes 58 0x0 -.debug_frame 174812 0x0 -.debug_line 354866 0x0 -.debug_loc 534 0x0 -.comment 75 0x0 -Total 1993694 - -$ cargo size --target thumbv7em-none-eabihf --bin led-roulette --release -- -A +.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 +.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 + + +$ cargo size --bin led-roulette --release -- -A + Finished release [optimized + debuginfo] target(s) in 0.02s led-roulette : section size addr -.vector_table 392 0x8000000 -.text 1826 0x8000188 -.rodata 84 0x80008ac +.vector_table 168 0x0 +.text 4044 0xa8 +.rodata 692 0x1074 .data 0 0x20000000 -.bss 4 0x20000000 -.debug_str 23334 0x0 -.debug_loc 6964 0x0 -.debug_abbrev 1337 0x0 -.debug_info 40582 0x0 -.debug_ranges 2936 0x0 -.debug_macinfo 1 0x0 -.debug_pubnames 5470 0x0 -.debug_pubtypes 10016 0x0 -.ARM.attributes 58 0x0 -.debug_frame 164 0x0 -.debug_line 9081 0x0 -.comment 18 0x0 -Total 102267 +.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 +.ARM.attributes 50 0x0 +.debug_frame 2152 0x0 +.debug_line 17050 0x0 +.comment 147 0x0 +Total 175996 ``` > **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 2KB +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). A `static` variable is being used in `aux5::init`; that's why it shows 4 -bytes of `bss`. - -One final thing! We have been running our programs from within GDB but our programs don't depend on -GDB at all. You can confirm this be closing both GDB and OpenOCD and then resetting the board by -pressing the black button on the board. The LED roulette application will run without intervention -of GDB. +in RAM (`static` variables). diff --git a/src/05-led-roulette/src/main.rs b/src/05-led-roulette/src/main.rs index 26e449962..9380a52ba 100644 --- a/src/05-led-roulette/src/main.rs +++ b/src/05-led-roulette/src/main.rs @@ -2,7 +2,9 @@ #![no_main] #![no_std] -use aux5::entry; +use cortex_m_rt::entry; +use panic_halt as _; +use nrf51_hal as _; #[entry] fn main() -> ! { diff --git a/src/05-led-roulette/the-challenge.md b/src/05-led-roulette/the-challenge.md index b023849fc..2067ff76c 100644 --- a/src/05-led-roulette/the-challenge.md +++ b/src/05-led-roulette/the-challenge.md @@ -3,73 +3,24 @@ You are now well armed to face a challenge! Your task will be to implement the application I showed you at the beginning of this chapter. -Here's the GIF again: -

- +

-Also, this may help: +If you can't exactly see what's happening here it is in a much slower version:

- +

-This is a timing diagram. It indicates which LED is on at any given instant of time and for how long -each LED should be on. On the X axis we have the time in milliseconds. The timing diagram shows a -single period. This pattern will repeat itself every 800 ms. The Y axis labels each LED with a -cardinal point: North, East, etc. As part of the challenge you'll have to figure out how each -element in the `Leds` array maps to these cardinal points (hint: `cargo doc --open` `;-)`). - -Before you attempt this challenge, let me give you one last tip. Our GDB sessions always involve -entering the same commands at the beginning. We can use a `.gdb` file to execute some commands -right after GDB is started. This way you can save yourself the effort of having to enter them -manually on each GDB session. - -Place this `openocd.gdb` file in the root of the Cargo project, right next to the `Cargo.toml`: - -``` console -$ cat openocd.gdb -``` - -``` text -target remote :3333 -load -break main -continue -``` - -Then modify the second line of the `.cargo/config` file: - -``` console -$ cat .cargo/config -``` - -``` toml -[target.thumbv7em-none-eabihf] -runner = "arm-none-eabi-gdb -q -x openocd.gdb" # <- -rustflags = [ - "-C", "link-arg=-Tlink.x", -] -``` - -With that in place, you should now be able to start a `gdb` session that will automatically flash -the program and jump to the beginning of `main`: +2 hints before you start: -``` console -$ cargo run --target thumbv7em-none-eabihf - Running `arm-none-eabi-gdb -q -x openocd.gdb target/thumbv7em-none-eabihf/debug/led-roulette` -Reading symbols from target/thumbv7em-none-eabihf/debug/led-roulette...done. -(..) -Loading section .vector_table, size 0x188 lma 0x8000000 -Loading section .text, size 0x3b20 lma 0x8000188 -Loading section .rodata, size 0xb0c lma 0x8003cc0 -Start address 0x8003b1c, load size 18356 -Transfer rate: 20 KB/sec, 6118 bytes/write. -Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 9. -Note: automatically using hardware breakpoints for read-only addresses. +1. As we learned before the LED matrix of the micro:bit is actually a 3x9 while being exposed as a 5x5. Furthermore, + it seems like the 9 columns and 3 rows are more or less randomly mapped to the visual 5x5 matrix. If you don't want + to go through the effort of figuring out the pins you have to set high/low in order to blink the border of the + matrix, here is the list: `(R1, C1) (R2, C4) (R1, C2), (R2, C5) (R1, C3) (R3, C8) (R2, C1) (R1, C4) (R3, C2) (R2, + C6) (R3, C1) (R2, C7) (R3, C3) (R1, C8) (R2, C2) (R3, C4)` -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9 -9 let (mut delay, mut leds): (Delay, Leds) = aux5::init(); -(gdb) -``` +2. If you are thinking about storing columns and rows in arrays you will quickly notice they are of different types since + all GPIO pins are represented as their own type. However, you can call `.degrade()` on the individual GPIO objects in + order to "degrade" them all into the same type and then store them in an array. diff --git a/src/05-led-roulette/the-led-and-delay-abstractions.md b/src/05-led-roulette/the-led-and-delay-abstractions.md deleted file mode 100644 index a91a0eb10..000000000 --- a/src/05-led-roulette/the-led-and-delay-abstractions.md +++ /dev/null @@ -1,222 +0,0 @@ -# The `Led` and `Delay` abstractions - -Now, I'm going to introduce two high level abstractions that we'll use to implement the LED roulette -application. - -The auxiliary crate, `aux5`, exposes an initialization function called `init`. When called this -function returns two values packed in a tuple: a `Delay` value and a `Leds` value. - -`Delay` can be used to block your program for a specified amount of milliseconds. - -`Leds` is actually an array of eight `Led`s. Each `Led` represents one of the LEDs on the F3 board, -and exposes two methods: `on` and `off` which can be used to turn the LED on or off, respectively. - -Let's try out these two abstractions by modifying the starter code to look like this: - -``` rust -#![deny(unsafe_code)] -#![no_main] -#![no_std] - -use aux5::{entry, prelude::*, Delay, Leds}; - -#[entry] -fn main() -> ! { - let (mut delay, mut leds): (Delay, Leds) = aux5::init(); - - let half_period = 500_u16; - - loop { - leds[0].on(); - delay.delay_ms(half_period); - - leds[0].off(); - delay.delay_ms(half_period); - } -} -``` - -Now build it: - -``` console -$ cargo build --target thumbv7em-none-eabihf -``` - -> **NOTE** It's possible to forget to rebuild the program *before* starting a GDB session; this -> omission can lead to very confusing debug sessions. To avoid this problem you can call `cargo run` -> instead of `cargo build`; `cargo run` will build *and* start a debug session ensuring you never -> forget to recompile your program. - -Now, we'll repeat the flashing procedure that we did in the previous section: - -``` console -$ # this starts a GDB session of the program; no need to specify the path to the binary -$ arm-none-eabi-gdb -q target/thumbv7em-none-eabihf/debug/led-roulette -Reading symbols from target/thumbv7em-none-eabihf/debug/led-roulette...done. -(gdb) target remote :3333 -Remote debugging using :3333 -(..) - -(gdb) load -Loading section .vector_table, size 0x188 lma 0x8000000 -Loading section .text, size 0x3fc6 lma 0x8000188 -Loading section .rodata, size 0xa0c lma 0x8004150 -Start address 0x8000188, load size 19290 -Transfer rate: 19 KB/sec, 4822 bytes/write. - -(gdb) break main -Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 9. - -(gdb) continue -Continuing. -Note: automatically using hardware breakpoints for read-only addresses. - -Breakpoint 1, main () at src/05-led-roulette/src/main.rs:9 -9 let (mut delay, mut leds): (Delay, Leds) = aux5::init(); -``` - -OK. Let's step through the code. This time, we'll use the `next` command instead of `step`. The -difference is that the `next` command will step *over* function calls instead of going inside them. - -``` -(gdb) next -11 let half_period = 500_u16; - -(gdb) next -13 loop { - -(gdb) next -14 leds[0].on(); - -(gdb) next -15 delay.delay_ms(half_period); -``` - -After executing the `leds[0].on()` statement, you should see a red LED, the one pointing North, -turn on. - -Let's continue stepping over the program: - -``` -(gdb) next -17 leds[0].off(); - -(gdb) next -18 delay.delay_ms(half_period); -``` - -The `delay_ms` call will block the program for half a second but you may not notice because the -`next` command also takes some time to execute. However, after stepping over the `leds[0].off()` -statement you should see the red LED turn off. - -You can already guess what this program does. Let it run uninterrupted using the `continue` command. - -``` -(gdb) continue -Continuing. -``` - -Now, let's do something more interesting. We are going to modify the behavior of our program using -GDB. - -First, let's stop the infinite loop by hitting `Ctrl+C`. You'll probably end up somewhere inside -`Led::on`, `Led::off` or `delay_ms`: - -``` -Program received signal SIGINT, Interrupt. -0x080033f6 in core::ptr::read_volatile (src=0xe000e010) at /checkout/src/libcore/ptr.rs:472 -472 /checkout/src/libcore/ptr.rs: No such file or directory. -``` - -In my case, the program stopped its execution inside a `read_volatile` function. GDB output shows -some interesting information about that: `core::ptr::read_volatile (src=0xe000e010)`. This means -that the function comes from the `core` crate and that it was called with argument `src = -0xe000e010`. - -Just so you know, a more explicit way to show the arguments of a function is to use the `info args` -command: - -``` -(gdb) info args -src = 0xe000e010 -``` - -Regardless of where your program may have stopped you can always look at the output of the -`backtrace` command (`bt` for short) to learn how it got there: - -``` -(gdb) backtrace -#0 0x080033f6 in core::ptr::read_volatile (src=0xe000e010) - at /checkout/src/libcore/ptr.rs:472 -#1 0x08003248 in >::get (self=0xe000e010) - at $REGISTRY/vcell-0.1.0/src/lib.rs:43 -#2 >::read (self=0xe000e010) - at $REGISTRY/volatile-register-0.2.0/src/lib.rs:75 -#3 cortex_m::peripheral::syst::::has_wrapped (self=0x10001fbc) - at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124 -#4 0x08002d9c in >::delay_us (self=0x10001fbc, us=500000) - at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:58 -#5 0x08002cce in >::delay_ms (self=0x10001fbc, ms=500) - at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:32 -#6 0x08002d0e in >::delay_ms (self=0x10001fbc, ms=500) - at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:38 -#7 0x080001ee in main () at src/05-led-roulette/src/main.rs:18 -``` - -`backtrace` will print a trace of function calls from the current function down to main. - -Back to our topic. To do what we are after, first, we have to return to the `main` function. We can -do that using the `finish` command. This command resumes the program execution and stops it again -right after the program returns from the current function. We'll have to call it several times. - -``` -(gdb) finish -cortex_m::peripheral::syst::::has_wrapped (self=0x10001fbc) - at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124 -124 self.csr.read() & SYST_CSR_COUNTFLAG != 0 -Value returned is $1 = 5 - -(gdb) finish -Run till exit from #0 cortex_m::peripheral::syst::::has_wrapped ( - self=0x10001fbc) - at $REGISTRY/cortex-m-0.5.7/src/peripheral/syst.rs:124 -0x08002d9c in >::delay_us ( - self=0x10001fbc, us=500000) - at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:58 -58 while !self.syst.has_wrapped() {} -Value returned is $2 = false - -(..) - -(gdb) finish -Run till exit from #0 0x08002d0e in >::delay_ms (self=0x10001fbc, ms=500) - at $REGISTRY/stm32f30x-hal-0.2.0/src/delay.rs:38 -0x080001ee in main () at src/05-led-roulette/src/main.rs:18 -18 delay.delay_ms(half_period); -``` - -We are back in `main`. We have a local variable in here: `half_period` - -``` -(gdb) info locals -half_period = 500 -delay = (..) -leds = (..) -``` - -Now, we are going to modify this variable using the `set` command: - -``` -(gdb) set half_period = 100 - -(gdb) print half_period -$1 = 100 -``` - -If you let program run free again using the `continue` command, you should see that the LED will -blink at a much faster rate now! - -Question! What happens if you keep lowering the value of `half_period`? At what value of -`half_period` you can no longer see the LED blink? - -Now, it's your turn to write a program. diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1f1b5788a..8737c5379 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -11,7 +11,8 @@ - [Build it](05-led-roulette/build-it.md) - [Flash it](05-led-roulette/flash-it.md) - [Debug it](05-led-roulette/debug-it.md) - - [The `led` and `delay` abstractions](05-led-roulette/the-led-and-delay-abstractions.md) + - [Light it up](05-led-roulette/light-it-up.md) + - [It blinks](05-led-roulette/it-blinks.md) - [The challenge](05-led-roulette/the-challenge.md) - [My solution](05-led-roulette/my-solution.md) - [Hello, world!](06-hello-world/README.md) diff --git a/src/appendix/2-how-to-use-gdb/README.md b/src/appendix/2-how-to-use-gdb/README.md index 4153c6dcc..cdc8d6a65 100644 --- a/src/appendix/2-how-to-use-gdb/README.md +++ b/src/appendix/2-how-to-use-gdb/README.md @@ -83,7 +83,4 @@ Below are some useful GDB commands that can help us debug our programs. This ass ### Controlling OpenOCD Remotely -* `monitor reset run`: Reset the CPU, starting execution over again - * `monitor reset`: Same as above -* `monitor reset init`: Reset the CPU, halting execution at the start -* `monitor targets`: Display information and state of current target +* `monitor reset`: Reset the CPU, starting execution over again diff --git a/src/assets/gdb-layout-asm.png b/src/assets/gdb-layout-asm.png index 70fa51c75..0f1f1ef0d 100644 Binary files a/src/assets/gdb-layout-asm.png and b/src/assets/gdb-layout-asm.png differ diff --git a/src/assets/gdb-layout-src.png b/src/assets/gdb-layout-src.png index 1cf5e3a86..8c374199c 100644 Binary files a/src/assets/gdb-layout-src.png and b/src/assets/gdb-layout-src.png differ diff --git a/src/assets/roulette_fast.mp4 b/src/assets/roulette_fast.mp4 new file mode 100644 index 000000000..fec3f6c05 Binary files /dev/null and b/src/assets/roulette_fast.mp4 differ diff --git a/src/assets/roulette_slow.mp4 b/src/assets/roulette_slow.mp4 new file mode 100644 index 000000000..a7aae2a19 Binary files /dev/null and b/src/assets/roulette_slow.mp4 differ