├── mdbook ├── .gitignore ├── src │ ├── assets │ │ ├── mb2-axes.jpg │ │ ├── minicom.png │ │ ├── quadrants.jpg │ │ ├── usb-cable.jpg │ │ ├── microbit-v2.jpg │ │ ├── putty-console.png │ │ ├── roulette_fast.mp4 │ │ ├── roulette_slow.mp4 │ │ ├── gdb-layout-asm.png │ │ ├── gdb-layout-src.png │ │ └── putty-settings.png │ ├── 12-i2c │ │ ├── my-solution.md │ │ ├── Embed.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── the-challenge.md │ │ ├── examples │ │ │ ├── show-accel.rs │ │ │ └── chip-id.rs │ │ ├── lsm303agr.md │ │ ├── the-general-protocol.md │ │ ├── using-a-driver.md │ │ ├── src │ │ │ └── main.rs │ │ ├── README.md │ │ └── read-a-single-register.md │ ├── 11-uart │ │ ├── my-solution.md │ │ ├── Embed.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── send-a-string.md │ │ ├── echo-server.md │ │ ├── README.md │ │ ├── Cargo.toml │ │ ├── examples │ │ │ ├── receive-byte.rs │ │ │ ├── send-byte.rs │ │ │ ├── send-string.rs │ │ │ └── naive-send-string.rs │ │ ├── receive-a-single-byte.md │ │ ├── naive-approach-write.md │ │ ├── src │ │ │ └── main.rs │ │ ├── reverse-a-string.md │ │ └── send-a-single-byte.md │ ├── 13-led-compass │ │ ├── my-solution.md │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── Cargo.toml │ │ ├── examples │ │ │ ├── show-mag.rs │ │ │ └── magnitude.rs │ │ ├── the-challenge.md │ │ ├── magnitude.md │ │ ├── templates │ │ │ └── compass.rs │ │ └── src │ │ │ └── main.rs │ ├── DOWNLOAD.md │ ├── 03-setup │ │ ├── Embed.toml │ │ ├── memory.x │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── src │ │ │ └── main.rs │ │ ├── Cargo.toml │ │ ├── macos.md │ │ ├── windows.md │ │ ├── IDE.md │ │ ├── build.rs │ │ └── verify.md │ ├── 09-registers │ │ ├── embed.gdb │ │ ├── Embed.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── examples │ │ │ ├── bad.rs │ │ │ ├── type-safe.rs │ │ │ ├── volatile.rs │ │ │ └── spooky.rs │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── main.rs │ │ │ └── lib.rs │ │ ├── spooky-action-at-a-distance.md │ │ └── README.md │ ├── serial-setup │ │ ├── Cargo.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ └── src │ │ │ └── lib.rs │ ├── 06-hello-world │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── Cargo.toml │ │ ├── examples │ │ │ ├── light-up.rs │ │ │ ├── fast-blink.rs │ │ │ ├── spin-wait.rs │ │ │ └── timer-blinky.rs │ │ ├── src │ │ │ └── main.rs │ │ ├── nanoblinky.rs │ │ ├── toggle-it.md │ │ ├── board-support-crate.md │ │ ├── timers.md │ │ ├── portability.md │ │ ├── nop.md │ │ ├── spin-wait.md │ │ └── README.md │ ├── 07-led-roulette │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── Cargo.toml │ │ ├── the-challenge.md │ │ ├── README.md │ │ ├── examples │ │ │ └── light-it-all.rs │ │ ├── templates │ │ │ └── solution.rs │ │ ├── src │ │ │ └── main.rs │ │ └── my-solution.md │ ├── 14-punch-o-meter │ │ ├── Embed.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── my-solution.md │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples │ │ │ └── show-accel.rs │ │ ├── the-challenge.md │ │ ├── gravity-is-up.md │ │ └── src │ │ │ └── main.rs │ ├── 15-interrupts │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── Cargo.toml │ │ ├── my-solution.md │ │ ├── the-challenge.md │ │ ├── examples │ │ │ ├── square-wave.rs │ │ │ ├── poke.rs │ │ │ ├── count-once.rs │ │ │ ├── count.rs │ │ │ ├── count-bounce.rs │ │ │ └── count-debounce.rs │ │ ├── addendum-pwm.md │ │ ├── waiting-to-be-interrupted.md │ │ ├── the-mb2-speaker.md │ │ └── debouncing.md │ ├── 16-snake-game │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── src │ │ │ ├── display │ │ │ │ ├── interrupt.rs │ │ │ │ └── show.rs │ │ │ ├── display.rs │ │ │ ├── controls.rs │ │ │ ├── game │ │ │ │ ├── rng.rs │ │ │ │ ├── movement.rs │ │ │ │ ├── coords.rs │ │ │ │ └── snake.rs │ │ │ ├── controls │ │ │ │ ├── interrupt.rs │ │ │ │ └── init.rs │ │ │ └── main.rs │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── final-assembly.md │ ├── 05-meet-your-software │ │ ├── Embed.toml │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── examples │ │ │ ├── light-it-up.rs │ │ │ ├── init.rs │ │ │ └── delay-print.rs │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── embedded-setup.md │ │ ├── flash-it.md │ │ └── light-it-up.md │ ├── 08-inputs-and-outputs │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Embed.toml │ │ ├── my-solution.md │ │ ├── Cargo.toml │ │ ├── the-challenge.md │ │ ├── examples │ │ │ ├── button-a-bsp.rs │ │ │ ├── polling-led-toggle.rs │ │ │ └── blink-held.rs │ │ ├── README.md │ │ ├── src │ │ │ └── main.rs │ │ └── polling.md │ ├── appendix │ │ ├── 3-mag-calibration │ │ │ ├── Embed.toml │ │ │ ├── .cargo │ │ │ │ └── config.toml │ │ │ ├── Cargo.toml │ │ │ ├── src │ │ │ │ ├── led.rs │ │ │ │ └── main.rs │ │ │ └── README.md │ │ ├── 4-licenses-and-attribution │ │ │ ├── README.md │ │ │ └── LICENSE-MIT │ │ └── 1-general-troubleshooting │ │ │ └── README.md │ ├── 04-meet-your-hardware │ │ ├── README.md │ │ └── terminology.md │ ├── 10-serial-communication │ │ ├── windows-tooling.md │ │ └── nix-tooling.md │ ├── README.md │ └── 02-requirements │ │ └── README.md ├── epub-link.js └── book.toml ├── triagebot.toml ├── .github ├── CODEOWNERS └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE-CC-BY ├── LICENSE-MIT ├── LICENSE-APACHE ├── .vscode └── settings.json ├── .cargo └── config.toml ├── Cargo.toml ├── reworkspace.py └── rechapter.py /mdbook/.gitignore: -------------------------------------------------------------------------------- 1 | /book 2 | -------------------------------------------------------------------------------- /triagebot.toml: -------------------------------------------------------------------------------- 1 | [assign] 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @rust-embedded/resources -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gdb_history 2 | Cargo.lock 3 | target/ 4 | -------------------------------------------------------------------------------- /LICENSE-CC-BY: -------------------------------------------------------------------------------- 1 | mdbook/src/appendix/4-licenses-and-attribution/LICENSE-CC-BY -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | mdbook/src/appendix/4-licenses-and-attribution/LICENSE-MIT -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | mdbook/src/appendix/4-licenses-and-attribution/LICENSE-APACHE -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.features": "all" 3 | } 4 | -------------------------------------------------------------------------------- /mdbook/src/assets/mb2-axes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/mb2-axes.jpg -------------------------------------------------------------------------------- /mdbook/src/assets/minicom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/minicom.png -------------------------------------------------------------------------------- /mdbook/src/assets/quadrants.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/quadrants.jpg -------------------------------------------------------------------------------- /mdbook/src/assets/usb-cable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/usb-cable.jpg -------------------------------------------------------------------------------- /mdbook/src/assets/microbit-v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/microbit-v2.jpg -------------------------------------------------------------------------------- /mdbook/src/assets/putty-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/putty-console.png -------------------------------------------------------------------------------- /mdbook/src/assets/roulette_fast.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/roulette_fast.mp4 -------------------------------------------------------------------------------- /mdbook/src/assets/roulette_slow.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/roulette_slow.mp4 -------------------------------------------------------------------------------- /mdbook/src/assets/gdb-layout-asm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/gdb-layout-asm.png -------------------------------------------------------------------------------- /mdbook/src/assets/gdb-layout-src.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/gdb-layout-src.png -------------------------------------------------------------------------------- /mdbook/src/assets/putty-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rust-embedded/discovery-mb2/HEAD/mdbook/src/assets/putty-settings.png -------------------------------------------------------------------------------- /mdbook/src/12-i2c/my-solution.md: -------------------------------------------------------------------------------- 1 | # My solution 2 | 3 | My solution is in `src/main.rs`. 4 | 5 | ```rust 6 | {{#include src/main.rs}} 7 | ``` 8 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/my-solution.md: -------------------------------------------------------------------------------- 1 | # My solution 2 | 3 | You will find my solution in `src/main.rs`: 4 | 5 | ```rust 6 | {{#include src/main.rs}} 7 | ``` 8 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/my-solution.md: -------------------------------------------------------------------------------- 1 | # My Solution 2 | 3 | Here's my solution (in `src/main.rs`): 4 | 5 | ``` rust 6 | {{#include src/main.rs}} 7 | ``` 8 | -------------------------------------------------------------------------------- /mdbook/src/DOWNLOAD.md: -------------------------------------------------------------------------------- 1 | # 📥 download ePUB version of the book 2 | 3 | 4 | You can download the EPUB version of this book here: [Download](/discovery-mb2/Rust%20Embedded%20MB2%20Discovery%20Book.epub) 5 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.probe] 2 | protocol = "Swd" 3 | 4 | [default.general] 5 | chip = "nrf52833_xxAA" 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/embed.gdb: -------------------------------------------------------------------------------- 1 | target remote :1337 2 | set print asm-demangle on 3 | set print pretty on 4 | set style sources off 5 | break main 6 | break DefaultHandler 7 | break HardFault 8 | monitor reset halt 9 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/memory.x: -------------------------------------------------------------------------------- 1 | /* Linker script for the nRF52833 */ 2 | MEMORY 3 | { 4 | /* NOTE K = KiBi = 1024 bytes */ 5 | FLASH : ORIGIN = 0x00000000, LENGTH = 512K 6 | RAM : ORIGIN = 0x20000000, LENGTH = 128K 7 | } 8 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = true 6 | 7 | [default.rtt] 8 | enabled = false 9 | 10 | [default.gdb] 11 | enabled = true 12 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/serial-setup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serial-setup" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | embedded-io = "0.6.1" 8 | microbit-v2 = "0.15.1" 9 | cortex-m = "0.7.7" 10 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = true 6 | 7 | [default.rtt] 8 | enabled = false 9 | 10 | [default.gdb] 11 | enabled = true 12 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = true 6 | 7 | [default.rtt] 8 | enabled = false 9 | 10 | [default.gdb] 11 | enabled = true 12 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/serial-setup/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = true 6 | 7 | [default.rtt] 8 | enabled = false 9 | 10 | [default.gdb] 11 | enabled = true 12 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" # micro:bit V2 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" # micro:bit V2 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | ] 9 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | runner = "probe-rs run --chip nRF52833_xxAA" 6 | rustflags = [ 7 | "-C", "linker=rust-lld", 8 | "-C", "link-arg=-Tlink.x", 9 | ] 10 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" # micro:bit V2 3 | 4 | [default.reset] 5 | halt_afterwards = false 6 | 7 | [default.rtt] 8 | enabled = true 9 | 10 | [default.gdb] 11 | enabled = false 12 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/my-solution.md: -------------------------------------------------------------------------------- 1 | # My solution 2 | 3 | Here's my solution (in `src/main.rs`). Hopefully that was pretty easy. You'll soon see that simple polling like this is not very practical. 4 | 5 | ```rust 6 | {{#include src/main.rs}} 7 | ``` 8 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/my-solution.md: -------------------------------------------------------------------------------- 1 | # My solution 2 | 3 | Here's my solution (`src/main.rs`). Note that you can get quite high G values by rapping the edge of 4 | your MB2 on a table. Note also that this can break the accelerometer, so probably don't? 5 | 6 | ``` rust 7 | {{#include src/main.rs}} 8 | ``` 9 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/send-a-string.md: -------------------------------------------------------------------------------- 1 | # Send a string 2 | 3 | The next task will be to send a whole string from the microcontroller to your computer. 4 | 5 | I want you to send the string `"The quick brown fox jumps over the lazy dog."` from the microcontroller to 6 | your computer. 7 | 8 | It's your turn to write the program. 9 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/Embed.toml: -------------------------------------------------------------------------------- 1 | [default.general] 2 | chip = "nrf52833_xxAA" # uncomment this line for micro:bit V2 3 | # chip = "nrf51822_xxAA" # uncomment this line for micro:bit V1 4 | 5 | [default.reset] 6 | halt_afterwards = false 7 | 8 | [default.rtt] 9 | enabled = false 10 | 11 | [default.gdb] 12 | enabled = false 13 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/examples/bad.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::ptr; 5 | 6 | #[allow(unused_imports)] 7 | use registers::entry; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | registers::init(); 12 | 13 | unsafe { 14 | ptr::read_volatile(0x5000_A784 as *const u32); 15 | } 16 | 17 | loop {} 18 | } 19 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm::wfi; 5 | use panic_rtt_target as _; 6 | use nrf52833_pac as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use cortex_m_rt::entry; 10 | 11 | #[entry] 12 | fn main() -> ! { 13 | rtt_init_print!(); 14 | rprintln!("Hello World"); 15 | loop { 16 | wfi(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Bart Massey"] 3 | edition = "2021" 4 | name = "hello-world" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.7.5" 9 | embedded-hal = "1.0.0" 10 | microbit-v2 = "0.15.1" 11 | nrf52833-hal = "0.18.0" 12 | panic-halt = "1.0.0" 13 | 14 | [dependencies.cortex-m] 15 | version = "0.7.7" 16 | features = ["inline-asm"] 17 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "inputs-and-polling" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | microbit-v2 = "0.15.0" 8 | cortex-m-rt = "0.7.3" 9 | rtt-target = "0.5.0" 10 | panic-rtt-target = "0.1.3" 11 | embedded-hal = "1.0.0" 12 | 13 | [dependencies.cortex-m] 14 | version = "0.7" 15 | features = ["critical-section-single-core"] 16 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/echo-server.md: -------------------------------------------------------------------------------- 1 | # Echo server 2 | 3 | Let's merge transmission and reception into a single program and write an echo server. An echo 4 | server sends back to the client the same text it receives. For this application, the microcontroller 5 | will be the server and you and your computer will be the client. 6 | 7 | This should be straightforward to implement. (hint: do it byte by byte) 8 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/display/interrupt.rs: -------------------------------------------------------------------------------- 1 | use super::DISPLAY; 2 | 3 | use cortex_m::interrupt::free as interrupt_free; 4 | use microbit::pac::{self, interrupt}; 5 | 6 | #[pac::interrupt] 7 | fn TIMER1() { 8 | interrupt_free(|cs| { 9 | if let Some(display) = DISPLAY.borrow(cs).borrow_mut().as_mut() { 10 | display.handle_display_event(); 11 | } 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "interrupts" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cortex-m-rt = "0.7" 8 | critical-section = "1" 9 | critical-section-lock-mut = "0.1" 10 | embedded-hal = "1.0.0" 11 | microbit-v2 = "0.15" 12 | panic-rtt-target = "0.1" 13 | rtt-target = "0.5" 14 | 15 | 16 | [dependencies.cortex-m] 17 | version = "0.7" 18 | features = ["critical-section-single-core"] 19 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "punch-o-meter" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | microbit-v2 = "0.15.1" 9 | cortex-m-rt = "0.7.5" 10 | rtt-target = "0.6.1" 11 | panic-rtt-target = "0.2.0" 12 | lsm303agr = "1.1.0" 13 | 14 | [dependencies.cortex-m] 15 | version = "0.7.7" 16 | features = ["inline-asm", "critical-section-single-core"] 17 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/examples/light-it-up.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use embedded_hal::digital::OutputPin; 7 | use microbit::board::Board; 8 | use panic_halt as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | let mut board = Board::take().unwrap(); 13 | 14 | board.display_pins.col1.set_low().unwrap(); 15 | board.display_pins.row1.set_high().unwrap(); 16 | 17 | loop {} 18 | } 19 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "led-roulette" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.7.5" 9 | panic-halt = "1.0.0" 10 | panic-rtt-target = "0.2.0" 11 | rtt-target = "0.6.1" 12 | microbit-v2 = "0.15.1" 13 | embedded-hal = "1.0.0" 14 | 15 | [dependencies.cortex-m] 16 | version = "0.7.7" 17 | features = ["inline-asm", "critical-section-single-core"] 18 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtt-check" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.7.5" 9 | panic-rtt-target = "0.2.0" 10 | rtt-target = "0.6.1" 11 | 12 | [dependencies.nrf52833-pac] 13 | version = "0.12.2" 14 | features = ["cortex-m-rt"] 15 | 16 | [dependencies.cortex-m] 17 | version = "0.7.7" 18 | features = ["inline-asm", "critical-section-single-core"] 19 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/examples/init.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m::asm; 6 | use cortex_m_rt::entry; 7 | use microbit as _; 8 | use panic_halt as _; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | #[allow(clippy::needless_late_init)] 13 | let _y; 14 | let x = 42; 15 | _y = x; 16 | 17 | // infinite loop; just so we don't leave this stack frame 18 | loop { 19 | asm::nop(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "led-compass" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | microbit-v2 = "0.15.1" 9 | cortex-m-rt = "0.7.5" 10 | rtt-target = "0.6.1" 11 | panic-rtt-target = "0.2.0" 12 | lsm303agr = "1.1.0" 13 | libm = "0.2.15" 14 | embedded-hal = "1.0.0" 15 | 16 | [dependencies.cortex-m] 17 | version = "0.7.7" 18 | features = ["critical-section-single-core"] 19 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snake-game" 3 | version = "0.1.0" 4 | authors = ["Alan Bunbury "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | microbit-v2 = "0.15.1" 9 | cortex-m-rt = "0.7.5" 10 | rtt-target = "0.6.1" 11 | panic-rtt-target = "0.2.0" 12 | heapless = "0.8.0" 13 | tiny-led-matrix = "1.0.2" 14 | embedded-hal = "1.0.0" 15 | 16 | [dependencies.cortex-m] 17 | version = "0.7.7" 18 | features = ["critical-section-single-core"] 19 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "meet-your-software" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.7.5" 9 | panic-halt = "1.0.0" 10 | panic-rtt-target = "0.2.0" 11 | rtt-target = "0.6.1" 12 | microbit-v2 = "0.15.1" 13 | embedded-hal = "1.0.0" 14 | 15 | [dependencies.cortex-m] 16 | version = "0.7.7" 17 | features = ["inline-asm", "critical-section-single-core"] 18 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/macos.md: -------------------------------------------------------------------------------- 1 | # macOS 2 | 3 | All the tools can be installed using [Homebrew]: 4 | 5 | [Homebrew]: http://brew.sh/ 6 | 7 | ``` console 8 | $ # GDB debugger - The version in brew is built for all architectures including all of the ARM embedded cores 9 | $ brew install gdb 10 | 11 | $ # Minicom 12 | $ brew install minicom 13 | 14 | $ # lsusb lists USB ports 15 | $ brew install lsusb 16 | ``` 17 | 18 | That's all! Go to the [next section]. 19 | 20 | [next section]: verify.md 21 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/README.md: -------------------------------------------------------------------------------- 1 | # UART 2 | 3 | Our microcontroller (like most) has a peripheral called a UART (for "Universal Asynchronous 4 | Receiver/Transmitter). This peripheral can be configured to work with several serial communication 5 | protocols. The peripheral we will be working with is named UARTE (for "UART with Easy DMA", a topic 6 | outside the scope of this chapter). 7 | 8 | Throughout this chapter, we'll use serial communication to exchange information between the 9 | microcontroller and your computer. 10 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/examples/light-up.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use nrf52833_hal::{gpio, pac}; 6 | use panic_halt as _; 7 | 8 | #[entry] 9 | fn main() -> ! { 10 | let peripherals = pac::Peripherals::take().unwrap(); 11 | let p0 = gpio::p0::Parts::new(peripherals.P0); 12 | let _row1 = p0.p0_21.into_push_pull_output(gpio::Level::High); 13 | let _col1 = p0.p0_28.into_push_pull_output(gpio::Level::Low); 14 | 15 | #[allow(clippy::empty_loop)] 16 | loop {} 17 | } 18 | -------------------------------------------------------------------------------- /mdbook/epub-link.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', () => { 2 | const rightButtons = document.querySelector(".right-buttons"); 3 | if (!rightButtons) return; 4 | 5 | const epubLink = document.createElement("a"); 6 | epubLink.href = "/discovery-mb2/Rust Embedded MB2 Discovery Book.epub"; 7 | epubLink.textContent = "📘ePUB"; 8 | epubLink.download = "Rust Embedded MB2 Discovery Book.epub"; // force download 9 | epubLink.classList.add("epub-download"); 10 | 11 | rightButtons.appendChild(epubLink); 12 | }); 13 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/my-solution.md: -------------------------------------------------------------------------------- 1 | # My Solution 2 | 3 | I found it a bit tricky to figure out how the interrupt 4 | handler should calculate the next interrupt time to keep 5 | the siren going. I ended up with a couple of state variables 6 | to keep track of whether the speaker pin was on or off 7 | (could have checked the hardware) and to keep track of what 8 | time the siren was at in its up-down cycle. 9 | 10 | My code contains all the details (`src/main.rs`). 11 | 12 | 13 | ```rust 14 | {{#include src/main.rs}} 15 | ``` 16 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uart" 3 | version = "0.1.0" 4 | authors = ["Henrik Böving "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | cortex-m-rt = "0.7.5" 9 | embedded-io = "0.6.1" 10 | heapless = "0.8.0" 11 | microbit-v2 = "0.15.1" 12 | panic-rtt-target = "0.2.0" 13 | rtt-target = "0.6.1" 14 | 15 | [dependencies.cortex-m] 16 | version = "0.7.7" 17 | features = ["inline-asm", "critical-section-single-core"] 18 | 19 | [dependencies.serial-setup] 20 | version = "0.1.0" 21 | path = "../serial-setup" 22 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mag-cal" 3 | version = "0.1.0" 4 | authors = [ 5 | "Henrik Böving ", 6 | "Bart Massey ", 7 | ] 8 | edition = "2021" 9 | 10 | [dependencies] 11 | microbit-v2 = "0.15.1" 12 | cortex-m-rt = "0.7.5" 13 | rtt-target = "0.6.1" 14 | panic-rtt-target = "0.2.0" 15 | lsm303agr = "1.1.0" 16 | libm = "0.2.15" 17 | embedded-hal = "1.0.0" 18 | 19 | [dependencies.cortex-m] 20 | version = "0.7.7" 21 | features = ["critical-section-single-core"] 22 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/examples/delay-print.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use microbit::board::Board; 7 | use microbit::hal::timer::Timer; 8 | use panic_rtt_target as _; 9 | use rtt_target::{rprintln, rtt_init_print}; 10 | 11 | #[entry] 12 | fn main() -> ! { 13 | rtt_init_print!(); 14 | let board = Board::take().unwrap(); 15 | 16 | let mut timer = Timer::new(board.TIMER0); 17 | 18 | loop { 19 | timer.delay_ms(1000u32); 20 | rprintln!("1000 ms passed"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Henrik Böving "] 3 | edition = "2021" 4 | name = "i2c" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | microbit-v2 = "0.15.1" 9 | cortex-m-rt = "0.7.5" 10 | rtt-target = "0.6.1" 11 | panic-rtt-target = "0.2.0" 12 | nb = "1.1.0" 13 | heapless = "0.8.0" 14 | lsm303agr = "1.1.0" 15 | embedded-hal = "1.0.0" 16 | 17 | [dependencies.cortex-m] 18 | version = "0.7.7" 19 | features = ["inline-asm", "critical-section-single-core"] 20 | 21 | [dependencies.serial-setup] 22 | version = "0.1.0" 23 | path = "../serial-setup" 24 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Jorge Aparicio ", 4 | "Wink Saville ", 5 | "Henk Oordt ", 6 | ] 7 | edition = "2021" 8 | name = "registers" 9 | version = "0.2.0" 10 | 11 | [dependencies] 12 | cortex-m-rt = "0.7.5" 13 | microbit-v2 = "0.15.1" 14 | panic-rtt-target = "0.2.0" 15 | rtt-target = "0.6.1" 16 | 17 | [dependencies.cortex-m] 18 | version = "0.7.7" 19 | features = ["inline-asm", "critical-section-single-core"] 20 | 21 | [dependencies.nrf52833-hal] 22 | version = "0.18.0" 23 | features = ["rt"] 24 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/examples/type-safe.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | #[allow(unused_imports)] 5 | use registers::entry; 6 | 7 | #[entry] 8 | fn main() -> ! { 9 | let (p0, _p1) = registers::init(); 10 | 11 | // Turn on the top row 12 | p0.out.modify(|_, w| w.pin21().set_bit()); 13 | 14 | // Turn on the bottom row 15 | p0.out.modify(|_, w| w.pin19().set_bit()); 16 | 17 | // Turn off the top row 18 | p0.out.modify(|_, w| w.pin21().clear_bit()); 19 | 20 | // Turn off the bottom row 21 | p0.out.modify(|_, w| w.pin19().clear_bit()); 22 | 23 | loop {} 24 | } 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "mdbook/src/03-setup", 5 | "mdbook/src/05-meet-your-software", 6 | "mdbook/src/06-hello-world", 7 | "mdbook/src/07-led-roulette", 8 | "mdbook/src/08-inputs-and-outputs", 9 | "mdbook/src/09-registers", 10 | "mdbook/src/11-uart", 11 | "mdbook/src/12-i2c", 12 | "mdbook/src/13-led-compass", 13 | "mdbook/src/14-punch-o-meter", 14 | "mdbook/src/15-interrupts", 15 | "mdbook/src/16-snake-game", 16 | "mdbook/src/appendix/3-mag-calibration", 17 | "mdbook/src/serial-setup", 18 | ] 19 | 20 | [profile.release] 21 | codegen-units = 1 22 | debug = true 23 | lto = true 24 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/display/show.rs: -------------------------------------------------------------------------------- 1 | use super::DISPLAY; 2 | 3 | use cortex_m::interrupt::free as interrupt_free; 4 | 5 | use tiny_led_matrix::Render; 6 | 7 | /// Display an image. 8 | pub fn display_image(image: &impl Render) { 9 | interrupt_free(|cs| { 10 | if let Some(display) = DISPLAY.borrow(cs).borrow_mut().as_mut() { 11 | display.show(image); 12 | } 13 | }) 14 | } 15 | 16 | /// Clear the display (turn off all LEDs). 17 | pub fn clear_display() { 18 | interrupt_free(|cs| { 19 | if let Some(display) = DISPLAY.borrow(cs).borrow_mut().as_mut() { 20 | display.clear(); 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /mdbook/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | title = "Rust Embedded MB2 Discovery Book" 3 | description = "Discover the world of microcontrollers through Rust with the BB2 micro:bit v2" 4 | authors = ["Rust Embedded Resources Team"] 5 | language = "en" 6 | 7 | [output.html] 8 | git-repository-url = "https://github.com/rust-embedded/discovery-mb2/" 9 | additional-js = ["epub-link.js"] 10 | # additional-css = ["custom.css"] 11 | 12 | 13 | [output.epub] 14 | # Optional settings 15 | title = "Rust Embedded MB2 Discovery Book" 16 | author = "Rust Embedded Resources Team" 17 | 18 | [output.html.site] 19 | url = "/" 20 | # Optional settings 21 | # base-url = "/discovery-mb2/" 22 | # git-repository-url = " 23 | 24 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/examples/fast-blink.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::digital::OutputPin; 6 | use nrf52833_hal::{gpio, pac}; 7 | use panic_halt as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | let peripherals = pac::Peripherals::take().unwrap(); 12 | let p0 = gpio::p0::Parts::new(peripherals.P0); 13 | let _row1 = p0.p0_21.into_push_pull_output(gpio::Level::High); 14 | let mut row2 = p0.p0_22.into_push_pull_output(gpio::Level::Low); 15 | let _col1 = p0.p0_28.into_push_pull_output(gpio::Level::Low); 16 | 17 | loop { 18 | row2.set_high().unwrap(); 19 | row2.set_low().unwrap(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The challenge 2 | 3 | You are now well armed to face our challenge! Again, your application should look like this: 4 | 5 |

6 |

8 | 9 | If you can't exactly see what's happening here it is in a much slower version: 10 | 11 |

12 |

14 | 15 | If you need a hint, `templates/solution.rs` provides a mostly-filled-out chunk of code to finish. I 16 | would suggest you try it on your own first, though: it should be doable by now… 17 | 18 | Got it? 19 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | #[allow(unused_imports)] 5 | use registers::entry; 6 | 7 | #[entry] 8 | fn main() -> ! { 9 | registers::init(); 10 | 11 | unsafe { 12 | // A magic address! 13 | const PORT_P0_OUT: u32 = 0x50000504; 14 | 15 | // Turn on the top row 16 | *(PORT_P0_OUT as *mut u32) |= 1 << 21; 17 | 18 | // Turn on the bottom row 19 | *(PORT_P0_OUT as *mut u32) |= 1 << 19; 20 | 21 | // Turn off the top row 22 | *(PORT_P0_OUT as *mut u32) &= !(1 << 21); 23 | 24 | // Turn off the bottom row 25 | *(PORT_P0_OUT as *mut u32) &= !(1 << 19); 26 | } 27 | 28 | loop {} 29 | } 30 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::{delay::DelayNs, digital::OutputPin}; 6 | use microbit::hal::{gpio, timer}; 7 | use panic_halt as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | let board = microbit::Board::take().unwrap(); 12 | 13 | let mut row1 = board.display_pins.row1.into_push_pull_output(gpio::Level::High); 14 | let _col1 = board.display_pins.col1.into_push_pull_output(gpio::Level::Low); 15 | 16 | let mut timer0 = timer::Timer::new(board.TIMER0); 17 | 18 | loop { 19 | timer0.delay_ms(500); 20 | row1.set_high().unwrap(); 21 | timer0.delay_ms(500); 22 | row1.set_low().unwrap(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mdbook/src/appendix/4-licenses-and-attribution/README.md: -------------------------------------------------------------------------------- 1 | # Licenses and Attribution 2 | 3 | ## Licenses 4 | 5 | The documentation is licensed under 6 | 7 | - Creative Commons Attribution 4.0 License ([LICENSE-CC-BY](LICENSE-CC-BY) 8 | or https://creativecommons.org/licenses/by/4.0/legalcode) 9 | 10 | And the source code is licensed under either of 11 | 12 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or 13 | http://www.apache.org/licenses/LICENSE-2.0) 14 | 15 | - MIT License ([LICENSE-MIT](LICENSE-MIT) or 16 | https://opensource.org/licenses/MIT) 17 | 18 | at your option. 19 | 20 | ## Attribution 21 | 22 | The diagrams in the I2C chapter are by Tim Mathias. They are 23 | taken from Wikimedia Commons and used under the CC-BY-4.0 24 | license. 25 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/examples/spin-wait.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm::nop; 5 | use cortex_m_rt::entry; 6 | use embedded_hal::digital::OutputPin; 7 | use nrf52833_hal::{gpio, pac}; 8 | use panic_halt as _; 9 | 10 | fn wait() { 11 | for _ in 0..4_000_000 { 12 | nop(); 13 | } 14 | } 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | let peripherals = pac::Peripherals::take().unwrap(); 19 | let p0 = gpio::p0::Parts::new(peripherals.P0); 20 | let mut row1 = p0.p0_21.into_push_pull_output(gpio::Level::High); 21 | let _col1 = p0.p0_28.into_push_pull_output(gpio::Level::Low); 22 | 23 | loop { 24 | wait(); 25 | row1.set_high().unwrap(); 26 | wait(); 27 | row1.set_low().unwrap(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/nanoblinky.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use panic_halt as _; 5 | use riscv_rt::entry; 6 | use gd32vf103xx_hal::{pac, prelude::*, delay::McycleDelay}; 7 | use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | let dp = pac::Peripherals::take().unwrap(); 12 | 13 | let mut rcu = dp.RCU.configure().ext_hf_clock(8.mhz()).sysclk(108.mhz()).freeze(); 14 | 15 | let gpioc = dp.GPIOC.split(&mut rcu); 16 | let mut led = gpioc.pc13.into_push_pull_output(); 17 | let mut delay = McycleDelay::new(&rcu.clocks); 18 | 19 | loop { 20 | delay.delay_ms(500); 21 | led.set_high().unwrap(); 22 | delay.delay_ms(500); 23 | led.set_low().unwrap(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/display.rs: -------------------------------------------------------------------------------- 1 | pub mod interrupt; 2 | pub mod show; 3 | 4 | pub use show::{clear_display, display_image}; 5 | 6 | use core::cell::RefCell; 7 | use cortex_m::interrupt::{free as interrupt_free, Mutex}; 8 | use microbit::display::nonblocking::Display; 9 | use microbit::gpio::DisplayPins; 10 | use microbit::pac; 11 | use microbit::pac::TIMER1; 12 | 13 | static DISPLAY: Mutex>>> = Mutex::new(RefCell::new(None)); 14 | 15 | pub fn init_display(board_timer: TIMER1, board_display: DisplayPins) { 16 | let display = Display::new(board_timer, board_display); 17 | 18 | interrupt_free(move |cs| { 19 | *DISPLAY.borrow(cs).borrow_mut() = Some(display); 20 | }); 21 | unsafe { pac::NVIC::unmask(pac::Interrupt::TIMER1) } 22 | } 23 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/examples/timer-blinky.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::{delay::DelayNs, digital::OutputPin}; 6 | use nrf52833_hal::{gpio, pac, timer}; 7 | use panic_halt as _; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | let peripherals = pac::Peripherals::take().unwrap(); 12 | 13 | let p0 = gpio::p0::Parts::new(peripherals.P0); 14 | let mut row1 = p0.p0_21.into_push_pull_output(gpio::Level::High); 15 | let _col1 = p0.p0_28.into_push_pull_output(gpio::Level::Low); 16 | 17 | let mut timer0 = timer::Timer::new(peripherals.TIMER0); 18 | 19 | loop { 20 | timer0.delay_ms(500); 21 | row1.set_high().unwrap(); 22 | timer0.delay_ms(500); 23 | row1.set_low().unwrap(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/examples/receive-byte.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use panic_rtt_target as _; 6 | use rtt_target::{rprintln, rtt_init_print}; 7 | 8 | use microbit::hal::uarte::{self, Baudrate, Parity}; 9 | 10 | use serial_setup::UartePort; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | rtt_init_print!(); 15 | let board = microbit::Board::take().unwrap(); 16 | 17 | let mut serial = { 18 | let serial = uarte::Uarte::new( 19 | board.UARTE0, 20 | board.uart.into(), 21 | Parity::EXCLUDED, 22 | Baudrate::BAUD115200, 23 | ); 24 | UartePort::new(serial) 25 | }; 26 | 27 | loop { 28 | let byte = serial.read().unwrap(); 29 | rprintln!("{}", byte); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/controls.rs: -------------------------------------------------------------------------------- 1 | mod init; 2 | mod interrupt; 3 | 4 | pub use init::init_buttons; 5 | 6 | use crate::game::Turn; 7 | use core::cell::RefCell; 8 | use cortex_m::interrupt::{free as interrupt_free, Mutex}; 9 | use microbit::{board::Buttons, hal::gpiote::Gpiote}; 10 | pub static GPIO: Mutex>> = Mutex::new(RefCell::new(None)); 11 | pub static TURN: Mutex> = Mutex::new(RefCell::new(Turn::None)); 12 | 13 | /// Get the next turn (ie, the turn corresponding to the most recently pressed button). 14 | pub fn get_turn(reset: bool) -> Turn { 15 | interrupt_free(|cs| { 16 | let turn = *TURN.borrow(cs).borrow(); 17 | if reset { 18 | *TURN.borrow(cs).borrow_mut() = Turn::None 19 | } 20 | turn 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/windows.md: -------------------------------------------------------------------------------- 1 | # Windows 2 | 3 | ## `arm-none-eabi-gdb` 4 | 5 | Arm provides `.exe` installers for Windows. Grab one from [here][gcc], and follow the instructions. 6 | Just before the installation process finishes tick/select the "Add path to environment variable" 7 | option. Then verify that the tools are in your `%PATH%`: 8 | 9 | ``` console 10 | $ arm-none-eabi-gcc -v 11 | (..) 12 | gcc version 5.4.1 20160919 (release) (..) 13 | ``` 14 | 15 | [gcc]: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads 16 | 17 | ## PuTTY 18 | 19 | Download the latest `putty.exe` from [this site] and place it somewhere in your `%PATH%`. 20 | 21 | [this site]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html 22 | 23 | Now, go to the [next section]. 24 | 25 | [next section]: verify.md 26 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/game/rng.rs: -------------------------------------------------------------------------------- 1 | use crate::Rng; 2 | 3 | /// A basic pseudo-random number generator. 4 | pub struct Prng { 5 | value: u32, 6 | } 7 | 8 | impl Prng { 9 | pub fn seeded(rng: &mut Rng) -> Self { 10 | Self::new(rng.random_u32()) 11 | } 12 | 13 | pub fn new(seed: u32) -> Self { 14 | Self { value: seed } 15 | } 16 | 17 | /// Basic xorshift PRNG function: see 18 | fn xorshift32(mut input: u32) -> u32 { 19 | input ^= input << 13; 20 | input ^= input >> 17; 21 | input ^= input << 5; 22 | input 23 | } 24 | 25 | /// Return a pseudo-random u32. 26 | pub fn random_u32(&mut self) -> u32 { 27 | self.value = Self::xorshift32(self.value); 28 | self.value 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/README.md: -------------------------------------------------------------------------------- 1 | # Punch-o-meter 2 | 3 | In this section we'll be playing with the accelerometer that's in the board. 4 | 5 | What are we building this time? A punch-o-meter! We'll be measuring the power of your jabs. Well, 6 | actually the maximum acceleration that you can reach because acceleration is what accelerometers 7 | measure. Strength and acceleration are proportional though so it's a good approximation. 8 | 9 | As we already know from previous chapters the accelerometer is built inside the LSM303AGR package. 10 | And just like the magnetometer, it is accessible using the 11 | I2C bus. 12 | 13 | The accelerometer also has the same coordinate system as the magnetometer. Here's a reminder: 14 | 15 |

16 | 17 |

18 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/game/movement.rs: -------------------------------------------------------------------------------- 1 | use super::Coords; 2 | 3 | /// Define the directions the snake can move. 4 | pub enum Direction { 5 | Up, 6 | Down, 7 | Left, 8 | Right, 9 | } 10 | 11 | /// What direction the snake should turn. 12 | #[derive(Debug, Copy, Clone)] 13 | pub enum Turn { 14 | Left, 15 | Right, 16 | None, 17 | } 18 | 19 | /// The current status of the game. 20 | pub enum GameStatus { 21 | Won, 22 | Lost, 23 | Ongoing, 24 | } 25 | 26 | /// The outcome of a single move/step. 27 | pub enum StepOutcome { 28 | /// Grid full (player wins) 29 | Full, 30 | /// Snake has collided with itself (player loses) 31 | Collision, 32 | /// Snake has eaten some food 33 | Eat(Coords), 34 | /// Snake has moved (and nothing else has happened) 35 | Move(Coords), 36 | } 37 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The challenge 2 | 3 | Now it’s your turn to put polling into practice. Your task is to implement a simple program that uses button polling to display directional arrows based on user input: 4 | 5 | - If Button A is pressed, display a left arrow (←) on the LED matrix. 6 | - If Button B is pressed, display a right arrow (→) on the LED matrix. 7 | - If neither button is pressed, display a single lit LED at the center of the matrix. 8 | 9 | You'll need to: 10 | 11 | - Initialize the variables for the LED and the buttons. 12 | - Continuously poll Button A and Button B. 13 | - Update the LED display according to the button state with a clear indication of each state (left, right, or neutral). 14 | 15 | I hope you don't mess up! It's *so* hard to share the road with people who don't use their turn signals properly. 16 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/examples/send-byte.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm::wfi; 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::rtt_init_print; 8 | 9 | use microbit::{ 10 | hal::uarte, 11 | hal::uarte::{Baudrate, Parity}, 12 | }; 13 | 14 | use serial_setup::UartePort; 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | rtt_init_print!(); 19 | let board = microbit::Board::take().unwrap(); 20 | 21 | let mut serial = { 22 | let serial = uarte::Uarte::new( 23 | board.UARTE0, 24 | board.uart.into(), 25 | Parity::EXCLUDED, 26 | Baudrate::BAUD115200, 27 | ); 28 | UartePort::new(serial) 29 | }; 30 | 31 | serial.write(b'X').unwrap(); 32 | serial.flush().unwrap(); 33 | 34 | loop { 35 | wfi(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Initialization code 2 | 3 | #![deny(warnings)] 4 | #![no_std] 5 | 6 | use panic_rtt_target as _; // panic handler 7 | 8 | pub use cortex_m_rt::entry; 9 | 10 | 11 | use microbit::hal::{self,pac}; 12 | pub use pac::{p0, p1}; 13 | pub use rtt_target::rprintln; 14 | 15 | 16 | #[inline(never)] 17 | pub fn init() -> (&'static p0::RegisterBlock, &'static p1::RegisterBlock) { 18 | rtt_target::rtt_init_print!(); 19 | let device_periphs = pac::Peripherals::take().unwrap(); 20 | 21 | // `display_pins!` initializes the display pins as outputs in push-pull mode 22 | let port0 = hal::gpio::p0::Parts::new(device_periphs.P0); 23 | let port1 = hal::gpio::p1::Parts::new(device_periphs.P1); 24 | let _display_pins = microbit::display_pins!(port0, port1); 25 | 26 | (unsafe { &*pac::P0::ptr() }, unsafe { &*pac::P1::ptr() }) 27 | } 28 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The Challenge 2 | 3 | Let's make the MB2 into a siren! But not just any siren — an 4 | interrupt-driven siren. That way we can turn the siren on 5 | and the rest of our program can run on, ignoring it. 6 | 7 | Make your siren sweep the pitch from 440Hz to 660Hz and back 8 | over a one-second period. The main program should start the 9 | siren, then print a ten-second countdown from 10 to 1, then 10 | stop the siren and print "launch!". The main program should 11 | not mess with the siren during countdown — it should just be 12 | interrupt-driven. 13 | 14 | *Hint:* I found it easiest to use a global locked `Siren` 15 | struct that owned the state of the siren and the peripherals 16 | it needed to operate. 17 | 18 | This is a fancy program that introduces a lot of new 19 | ideas. Don't be surprised if it takes you a bit to figure it 20 | out. 21 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/controls/interrupt.rs: -------------------------------------------------------------------------------- 1 | use super::{Turn, GPIO, TURN}; 2 | 3 | use cortex_m::interrupt::free as interrupt_free; 4 | use microbit::pac::{self, interrupt}; 5 | 6 | #[pac::interrupt] 7 | fn GPIOTE() { 8 | interrupt_free(|cs| { 9 | if let Some(gpiote) = GPIO.borrow(cs).borrow().as_ref() { 10 | let a_pressed = gpiote.channel0().is_event_triggered(); 11 | let b_pressed = gpiote.channel1().is_event_triggered(); 12 | 13 | let turn = match (a_pressed, b_pressed) { 14 | (true, false) => Turn::Left, 15 | (false, true) => Turn::Right, 16 | _ => Turn::None, 17 | }; 18 | 19 | gpiote.channel0().reset_events(); 20 | gpiote.channel1().reset_events(); 21 | 22 | *TURN.borrow(cs).borrow_mut() = turn; 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/examples/button-a-bsp.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::digital::InputPin; 6 | use microbit::Board; 7 | use panic_rtt_target as _; 8 | use rtt_target::{rprintln, rtt_init_print}; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | rtt_init_print!(); 13 | let board = Board::take().unwrap(); 14 | 15 | let mut button_a = board.buttons.button_a; 16 | let mut button_state = false; 17 | 18 | loop { 19 | if button_a.is_low().unwrap() { 20 | if button_state == false { 21 | button_state = true; 22 | rprintln!("Button A pressed"); 23 | } 24 | } else { 25 | if button_state == true { 26 | button_state = false; 27 | rprintln!("Button A not pressed"); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/examples/send-string.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::fmt::Write; 5 | use cortex_m::asm::wfi; 6 | use cortex_m_rt::entry; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | use microbit::hal::uarte::{self, Baudrate, Parity}; 11 | 12 | use serial_setup::UartePort; 13 | 14 | #[entry] 15 | fn main() -> ! { 16 | rtt_init_print!(); 17 | let board = microbit::Board::take().unwrap(); 18 | 19 | let mut serial = { 20 | let serial = uarte::Uarte::new( 21 | board.UARTE0, 22 | board.uart.into(), 23 | Parity::EXCLUDED, 24 | Baudrate::BAUD115200, 25 | ); 26 | UartePort::new(serial) 27 | }; 28 | 29 | write!(serial, "The quick brown fox jumps over the lazy dog.\r\n").unwrap(); 30 | serial.flush().unwrap(); 31 | 32 | loop { 33 | wfi(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/examples/naive-send-string.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm::wfi; 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::rtt_init_print; 8 | 9 | use microbit::hal::uarte::{self, Baudrate, Parity}; 10 | 11 | use serial_setup::UartePort; 12 | 13 | #[entry] 14 | fn main() -> ! { 15 | rtt_init_print!(); 16 | let board = microbit::Board::take().unwrap(); 17 | 18 | let mut serial = { 19 | let serial = uarte::Uarte::new( 20 | board.UARTE0, 21 | board.uart.into(), 22 | Parity::EXCLUDED, 23 | Baudrate::BAUD115200, 24 | ); 25 | UartePort::new(serial) 26 | }; 27 | 28 | for byte in b"The quick brown fox jumps over the lazy dog.\r\n".iter() { 29 | serial.write(*byte).unwrap(); 30 | } 31 | serial.flush().unwrap(); 32 | 33 | loop { 34 | wfi(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/README.md: -------------------------------------------------------------------------------- 1 | # LED roulette 2 | 3 | Alright, let's build a "real" application. The goal is to get to this display of spinning lights: 4 | 5 |

6 |

8 | 9 | Since working with the LED pins separately is quite annoying (especially if you have to use 10 | basically all of them like here) you can use the `microbit-v2` BSP crate, discussed previously, to 11 | work with the MB2's LED "display". It works like this (`examples/light-it-all.rs`): 12 | 13 | ```rust 14 | {{#include examples/light-it-all.rs}} 15 | ``` 16 | 17 | The Rust array `light_it_all` shown in the example contains 1 where the LED is on and 0 where it is 18 | off. The call to `show()` takes a timer for the BSP display code to use for delaying, a *copy* of 19 | the array, and a length of time in milliseconds to show this display before returning. 20 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/examples/light-it-all.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::delay::DelayNs; 6 | use microbit::{board::Board, display::blocking::Display, hal::Timer}; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | #[entry] 11 | fn main() -> ! { 12 | rtt_init_print!(); 13 | 14 | let board = Board::take().unwrap(); 15 | let mut timer = Timer::new(board.TIMER0); 16 | let mut display = Display::new(board.display_pins); 17 | let light_it_all = [ 18 | [1, 1, 1, 1, 1], 19 | [1, 1, 1, 1, 1], 20 | [1, 1, 1, 1, 1], 21 | [1, 1, 1, 1, 1], 22 | [1, 1, 1, 1, 1], 23 | ]; 24 | 25 | loop { 26 | // Show light_it_all for 1000ms 27 | display.show(&mut timer, light_it_all, 1000); 28 | // clear the display again 29 | display.clear(); 30 | timer.delay_ms(1000_u32); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/templates/solution.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use microbit::{board::Board, display::blocking::Display, hal::Timer}; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | #[rustfmt::skip] 11 | const PIXELS: [(usize, usize); 16] = [ 12 | (0, 0), 13 | (0, 1), 14 | // ... 15 | ]; 16 | 17 | #[entry] 18 | fn main() -> ! { 19 | rtt_init_print!(); 20 | 21 | let board = Board::take().unwrap(); 22 | let mut timer = Timer::new(board.TIMER0); 23 | let mut display = Display::new(board.display_pins); 24 | #[rustfmt::skip] 25 | let mut leds = [ 26 | [0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0], 28 | [0, 0, 0, 0, 0], 29 | [0, 0, 0, 0, 0], 30 | [0, 0, 0, 0, 0], 31 | ]; 32 | 33 | let mut last_led = (0, 0); 34 | 35 | loop { 36 | for current_led in PIXELS.iter() { 37 | // ... 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/receive-a-single-byte.md: -------------------------------------------------------------------------------- 1 | # Receive a single byte 2 | 3 | So far we can send data from the microcontroller to your computer. It's time to try the opposite: 4 | receiving data from your computer (`examples/receive-byte.rs`). 5 | 6 | ``` rust 7 | {{#include examples/receive-byte.rs}} 8 | ``` 9 | 10 | The only part that changed, compared to our send byte program, is the loop at the end of 11 | `main()`. Here we use the `serial.read()` function in order to wait until a byte is available and 12 | read it. Then we print that byte into our RTT debugging console to see whether stuff is actually 13 | arriving. 14 | 15 | Note that if you flash this program and start typing characters inside `minicom` to send them to 16 | your microcontroller you'll only be able to see numbers inside your RTT console since we are not 17 | converting the `u8` we received into an actual `char`. Since the conversion from `u8` to `char` is 18 | quite simple, I'll leave this task to you if you really do want to see the characters inside the RTT 19 | console. 20 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/naive-approach-write.md: -------------------------------------------------------------------------------- 1 | # Naive approach and `write!` 2 | 3 | ## Naive approach 4 | 5 | You probably came up with a program similar to the following (`examples/naive-send-string.rs`): 6 | 7 | ```rs 8 | {{#include examples/naive-send-string.rs}} 9 | ``` 10 | 11 | While this is a perfectly valid implementation, at some point you might want to have all the nice 12 | perks of `print!` such as argument formatting and so on. If you are wondering how to do that, read 13 | on. 14 | 15 | ## `write!` and `core::fmt::Write` 16 | 17 | The `core::fmt::Write` trait allows us to use any struct that implements it in basically the same 18 | way as we use `print!` in the `std` world. In this case, the `Uart` struct from the `nrf` HAL does 19 | implement `core::fmt::Write` so we can refactor our previous program into this 20 | (`examples/send-string.rs`): 21 | 22 | ```rs 23 | {{#include examples/send-string.rs}} 24 | ``` 25 | 26 | If you flash this program onto your micro:bit, you'll see that it is functionally equivalent to the 27 | iterator-based program you came up with. 28 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/examples/volatile.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::ptr; 5 | 6 | #[allow(unused_imports)] 7 | use registers::entry; 8 | 9 | #[entry] 10 | fn main() -> ! { 11 | registers::init(); 12 | 13 | unsafe { 14 | // A magic address! 15 | const PORT_P0_OUT: u32 = 0x50000504; 16 | 17 | // Turn on the top row 18 | let out = ptr::read_volatile(PORT_P0_OUT as *mut u32); 19 | ptr::write_volatile(PORT_P0_OUT as *mut u32, out | 1 << 21); 20 | 21 | // Turn on the bottom row 22 | let out = ptr::read_volatile(PORT_P0_OUT as *mut u32); 23 | ptr::write_volatile(PORT_P0_OUT as *mut u32, out | 1 << 19); 24 | 25 | // Turn off the top row 26 | let out = ptr::read_volatile(PORT_P0_OUT as *mut u32); 27 | ptr::write_volatile(PORT_P0_OUT as *mut u32, out & !(1 << 21)); 28 | 29 | // Turn off the bottom row 30 | let out = ptr::read_volatile(PORT_P0_OUT as *mut u32); 31 | ptr::write_volatile(PORT_P0_OUT as *mut u32, out & !(1 << 19)); 32 | } 33 | 34 | loop {} 35 | } -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/square-wave.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | use embedded_hal::{delay::DelayNs, digital::OutputPin}; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | use microbit::{ 11 | Board, 12 | hal::{gpio, timer}, 13 | }; 14 | 15 | /// The "period" is the time per cycle. It is 16 | /// 1/f where f is the frequency in Hz. In this 17 | /// case we measure time in milliseconds. 18 | const PERIOD: u32 = 1000 / 220; 19 | 20 | /// Number of cycles for 5 seconds of output. 21 | const CYCLES: u32 = 5000 / PERIOD; 22 | 23 | #[entry] 24 | fn main() -> ! { 25 | rtt_init_print!(); 26 | let board = Board::take().unwrap(); 27 | let mut speaker_pin = board.speaker_pin.into_push_pull_output(gpio::Level::Low); 28 | let mut timer = timer::Timer::new(board.TIMER0); 29 | 30 | for _ in 0..CYCLES { 31 | speaker_pin.set_high().unwrap(); 32 | timer.delay_ms(PERIOD / 2); 33 | speaker_pin.set_low().unwrap(); 34 | timer.delay_ms(PERIOD / 2); 35 | } 36 | 37 | loop { 38 | asm::wfi(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The challenge 2 | 3 | The challenge for this chapter is to build a small application that communicates with the outside 4 | world via the serial interface introduced in the last chapter. It should expect to receive the 5 | commands "mag" for magnetometer as well as "acc" for accelerometer from the serial port. It should 6 | then be able to send the corresponding sensor data to the serial port in response. 7 | 8 | This time no template code will be provided since all you need is already provided in the 9 | [UART](../11-uart/index.html) and this chapter. However, here are a few clues: 10 | 11 | - You might be interested in `core::str::from_utf8` to convert the bytes in the buffer to a `&str`, since we need to compare with `"mag"` and `"acc"`. 12 | 13 | - You will have to read the documentation for the magnetometer API and functionality. While the `lsm303agr` crate provides the API interface, the [LSM303AGR datasheet](https://www.st.com/resource/en/datasheet/lsm303agr.pdf) details the sensor's magnetic field measurement parameters. See pages 13-15 for sensor characteristics and, importantly, pages 66-67 for the output register format. 14 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/spooky-action-at-a-distance.md: -------------------------------------------------------------------------------- 1 | # Spooky action at a distance 2 | 3 | `OUT` is not the only register that can control the pins of Port E. The `OUTSET` register also lets 4 | you change the value of the pins, as can `OUTCLR`. However, `ODRSET` and `OUTCLR` don't let you 5 | retrieve the current output status of Port E. 6 | 7 | `OUTSET` is documented in: 8 | 9 | > Subsection 6.8.2.2. OUTSET - Page 145 10 | 11 | Let's look at below program. The key to this program is `fn print_out`. This function prints the 12 | current value in `OUT` to the `RTT` console (`examples/spooky.rs`): 13 | 14 | ``` rust 15 | {{#include examples/spooky.rs}} 16 | ``` 17 | 18 | You'll see this if you run this program: 19 | 20 | ``` console 21 | $ cargo embed 22 | # cargo-embed's console 23 | (..) 24 | 15:13:24.055: P0.OUT = 0x000000 25 | 15:13:24.055: P0.OUT = 0x200000 26 | 15:13:24.055: P0.OUT = 0x280000 27 | 15:13:24.055: P0.OUT = 0x080000 28 | 15:13:24.055: P0.OUT = 0x000000 29 | ``` 30 | 31 | Side effects! Although we are reading the same address multiple times without actually modifying it, 32 | we still see its value change every time `OUTSET` or `OUTCLR` is written to. 33 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/controls/init.rs: -------------------------------------------------------------------------------- 1 | use super::{Buttons, GPIO}; 2 | 3 | use cortex_m::interrupt::free as interrupt_free; 4 | use microbit::{ 5 | hal::{ 6 | gpio::{Floating, Input, Pin}, 7 | gpiote::{Gpiote, GpioteChannel}, 8 | }, 9 | pac, 10 | }; 11 | 12 | /// Initialise the buttons and enable interrupts. 13 | pub fn init_buttons(board_gpiote: pac::GPIOTE, board_buttons: Buttons) { 14 | let gpiote = Gpiote::new(board_gpiote); 15 | 16 | fn init_channel(channel: &GpioteChannel<'_>, button: &Pin>) { 17 | channel.input_pin(button).hi_to_lo().enable_interrupt(); 18 | channel.reset_events(); 19 | } 20 | 21 | let channel0 = gpiote.channel0(); 22 | init_channel(&channel0, &board_buttons.button_a.degrade()); 23 | 24 | let channel1 = gpiote.channel1(); 25 | init_channel(&channel1, &board_buttons.button_b.degrade()); 26 | 27 | interrupt_free(move |cs| { 28 | *GPIO.borrow(cs).borrow_mut() = Some(gpiote); 29 | 30 | unsafe { 31 | pac::NVIC::unmask(pac::Interrupt::GPIOTE); 32 | } 33 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /mdbook/src/appendix/4-licenses-and-attribution/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Jorge Aparicio 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/examples/show-accel.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use microbit::{ 10 | hal::{twim, Timer}, 11 | pac::twim0::frequency::FREQUENCY_A, 12 | }; 13 | 14 | use lsm303agr::{AccelMode, AccelOutputDataRate, Lsm303agr}; 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | rtt_init_print!(); 19 | let board = microbit::Board::take().unwrap(); 20 | 21 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 22 | let mut timer0 = Timer::new(board.TIMER0); 23 | 24 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 25 | sensor.init().unwrap(); 26 | sensor 27 | .set_accel_mode_and_odr( 28 | &mut timer0, 29 | AccelMode::HighResolution, 30 | AccelOutputDataRate::Hz10, 31 | ) 32 | .unwrap(); 33 | loop { 34 | if sensor.accel_status().unwrap().xyz_new_data() { 35 | let (x, y, z) = sensor.acceleration().unwrap().xyz_mg(); 36 | rprintln!("Acceleration: x {} y {} z {}", x, y, z); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/README.md: -------------------------------------------------------------------------------- 1 | # Snake game 2 | 3 | We're now going to implement a basic [snake](https://en.wikipedia.org/wiki/Snake_(video_game_genre)) 4 | game that you can play on an MB2 using its 5×5 LED matrix as a display and its two buttons as 5 | controls. In doing so, we will build on some of the concepts covered in the earlier chapters of this 6 | book, and also learn about some new peripherals and concepts. 7 | 8 | ## Modularity 9 | 10 | The source code here is more modular than it probably should be. This fine-grained modularity allows 11 | us to look at the source code a little at a time. We will build the code bottom-up: we will first 12 | build three modules — `game`, `controls` and `display`, and then compose these to build the final 13 | program. Each module will have a top-level source file and one or more included source files: for 14 | example, the `game` module will consist of `src/game.rs`, `src/game/coords.rs`, 15 | `src/game/movement.rs`, etc. The Rust `mod` statement is used to combine the various components of 16 | the module. *The Rust Programming Language* has a good [description] of Rust's module system. 17 | 18 | [description]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html 19 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/examples/show-accel.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use microbit::{ 10 | hal::{twim, Timer}, 11 | pac::twim0::frequency::FREQUENCY_A, 12 | }; 13 | 14 | use lsm303agr::{AccelMode, AccelOutputDataRate, Lsm303agr}; 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | rtt_init_print!(); 19 | let board = microbit::Board::take().unwrap(); 20 | 21 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 22 | let mut timer0 = Timer::new(board.TIMER0); 23 | 24 | // Code from documentation 25 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 26 | sensor.init().unwrap(); 27 | sensor 28 | .set_accel_mode_and_odr( 29 | &mut timer0, 30 | AccelMode::HighResolution, 31 | AccelOutputDataRate::Hz50, 32 | ) 33 | .unwrap(); 34 | loop { 35 | if sensor.accel_status().unwrap().xyz_new_data() { 36 | let (x, y, z) = sensor.acceleration().unwrap().xyz_mg(); 37 | // RTT instead of normal print 38 | rprintln!("Acceleration: x {} y {} z {}", x, y, z); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The challenge 2 | 3 | To keep things simple, we'll measure the acceleration only in the X axis while the board remains 4 | horizontal. That way we won't have to deal with subtracting that *fictitious* `1g` we observed 5 | before which would be hard because that `1g` could have X Y Z components depending on how the board 6 | is oriented. 7 | 8 | Here's what the punch-o-meter must do: 9 | 10 | - By default, the app is not "observing" the acceleration of the board. 11 | - When a significant X acceleration is detected (i.e. the acceleration goes above some threshold), 12 | the app should start a new measurement. 13 | - During that measurement interval, the app should keep track of the maximum acceleration observed 14 | - After the measurement interval ends, the app must report the maximum acceleration observed. You 15 | can report the value using the `rprintln!` macro. 16 | 17 | Give it a try and let me know how hard you can punch `;-)`. 18 | 19 | > **NOTE** There is an additional API that should be useful for this task that we haven't 20 | > discussed yet: the [`set_accel_scale`] one which you need to measure high g values. 21 | > 22 | > [`set_accel_scale`]: https://docs.rs/lsm303agr/1.1.0/lsm303agr/struct.Lsm303agr.html#method.set_accel_scale 23 | -------------------------------------------------------------------------------- /mdbook/src/serial-setup/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::fmt; 4 | use embedded_io::{Read, Write}; 5 | use microbit::hal::uarte::{self, Instance, Uarte, UarteRx, UarteTx}; 6 | 7 | #[allow(unused)] 8 | pub struct UartePort(UarteTx, UarteRx); 9 | 10 | impl UartePort { 11 | pub fn new(serial: Uarte) -> UartePort { 12 | let tx_buf = cortex_m::singleton!(TX_BUF: [u8; 1] = [0u8; 1]).unwrap(); 13 | let rx_buf = cortex_m::singleton!(RX_BUF: [u8; 1] = [0u8; 1]).unwrap(); 14 | let (tx, rx) = serial.split(tx_buf, rx_buf).unwrap(); 15 | UartePort(tx, rx) 16 | } 17 | } 18 | 19 | impl fmt::Write for UartePort { 20 | fn write_str(&mut self, s: &str) -> fmt::Result { 21 | self.0.write_str(s) 22 | } 23 | } 24 | 25 | impl UartePort { 26 | pub fn write(&mut self, b: u8) -> Result<(), uarte::Error> { 27 | self.0.write(&[b])?; 28 | Ok(()) 29 | } 30 | 31 | pub fn flush(&mut self) -> Result<(), uarte::Error> { 32 | self.0.flush() 33 | } 34 | 35 | #[allow(unused)] 36 | pub fn read(&mut self) -> Result { 37 | let mut buf = [0u8; 1]; 38 | self.1.read(&mut buf)?; 39 | Ok(buf[0]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/examples/spooky.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::ptr; 5 | 6 | #[allow(unused_imports)] 7 | use registers::{entry, rprintln}; 8 | 9 | // Print the current contents of P0.OUT 10 | fn print_out() { 11 | const P0_OUT: u32 = 0x5000_0504; 12 | 13 | let out = unsafe { ptr::read_volatile(P0_OUT as *const u32) }; 14 | 15 | rprintln!("P0.OUT = {:#08x}", out); 16 | } 17 | 18 | #[entry] 19 | fn main() -> ! { 20 | registers::init(); 21 | 22 | unsafe { 23 | // A bunch of magic addresses! 24 | const P0_OUTSET: u32 = 0x5000_0508; 25 | const P0_OUTCLR: u32 = 0x5000_050C; 26 | 27 | // Print the initial contents of OUT 28 | print_out(); 29 | 30 | // Turn on the top LED row 31 | ptr::write_volatile(P0_OUTSET as *mut u32, 1 << 21); 32 | print_out(); 33 | 34 | // Turn on the bottom LED row 35 | ptr::write_volatile(P0_OUTSET as *mut u32, 1 << 19); 36 | print_out(); 37 | 38 | // Turn off the top LED row 39 | ptr::write_volatile(P0_OUTCLR as *mut u32, 1 << 21); 40 | print_out(); 41 | 42 | // Turn off the bottom LED row 43 | ptr::write_volatile(P0_OUTCLR as *mut u32, 1 << 19); 44 | print_out(); 45 | } 46 | 47 | loop {} 48 | } 49 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/examples/chip-id.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m::asm::wfi; 6 | use cortex_m_rt::entry; 7 | use panic_rtt_target as _; 8 | use rtt_target::{rprintln, rtt_init_print}; 9 | 10 | use embedded_hal::i2c::I2c; 11 | use microbit::{hal::twim, pac::twim0::frequency::FREQUENCY_A}; 12 | 13 | const ACCELEROMETER_ADDR: u8 = 0b0011001; 14 | const MAGNETOMETER_ADDR: u8 = 0b0011110; 15 | 16 | const ACCELEROMETER_ID_REG: u8 = 0x0f; 17 | const MAGNETOMETER_ID_REG: u8 = 0x4f; 18 | 19 | #[entry] 20 | fn main() -> ! { 21 | rtt_init_print!(); 22 | let board = microbit::Board::take().unwrap(); 23 | 24 | let mut i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 25 | 26 | let mut acc = [0u8]; 27 | let mut mag = [0u8]; 28 | 29 | // First write the address + register onto the bus, then read the chip's responses 30 | i2c.write_read(ACCELEROMETER_ADDR, &[ACCELEROMETER_ID_REG], &mut acc) 31 | .unwrap(); 32 | i2c.write_read(MAGNETOMETER_ADDR, &[MAGNETOMETER_ID_REG], &mut mag) 33 | .unwrap(); 34 | 35 | rprintln!("The accelerometer chip's id is: {:#b}", acc[0]); 36 | rprintln!("The magnetometer chip's id is: {:#b}", mag[0]); 37 | 38 | loop { 39 | wfi(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/examples/show-mag.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use microbit::{ 10 | hal::{twim, Timer}, 11 | pac::twim0::frequency::FREQUENCY_A, 12 | }; 13 | 14 | use lsm303agr::{Lsm303agr, MagMode, MagOutputDataRate}; 15 | 16 | #[entry] 17 | fn main() -> ! { 18 | rtt_init_print!(); 19 | let board = microbit::Board::take().unwrap(); 20 | 21 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 22 | let mut timer0 = Timer::new(board.TIMER0); 23 | 24 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 25 | sensor.init().unwrap(); 26 | sensor 27 | .set_mag_mode_and_odr( 28 | &mut timer0, 29 | MagMode::HighResolution, 30 | MagOutputDataRate::Hz10, 31 | ) 32 | .unwrap(); 33 | sensor.enable_mag_offset_cancellation().unwrap(); 34 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 35 | loop { 36 | if sensor.mag_status().unwrap().xyz_new_data() { 37 | let (x, y, z) = sensor.magnetic_field().unwrap().xyz_nt(); 38 | rprintln!("Magnetic Field: x {} y {} z {}", x, y, z); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/the-challenge.md: -------------------------------------------------------------------------------- 1 | # The Challenge 2 | 3 | We'll use some fancy math to get the precise angle that the magnetic field forms with the X and Y 4 | axes of the magnetometer. This will allow us to figure out which LED is pointing north. 5 | 6 | We'll use the `atan2` function. This function returns an angle in the `-PI` to `PI` range. The 7 | graphic below shows how this angle is measured: 8 | 9 |

10 | 11 |

12 | 13 | Although not explicitly shown, in this graph the X axis points to the right and the Y axis points 14 | up. Note that our coordinate system is rotated 180° from this. 15 | 16 | Here's the starter code (in `templates/compass.rs`). `theta`, in radians, has already been 17 | computed. You need to pick which LED to turn on based on the value of `theta`. 18 | 19 | ```rs 20 | {{#include templates/compass.rs}} 21 | ``` 22 | 23 | Suggestions/tips: 24 | 25 | - A whole circle rotation equals 360 degrees. 26 | - `PI` radians is equivalent to 180 degrees. 27 | - If `theta` is zero, which direction are you pointing at? 28 | - If `theta` is instead very close to zero, which direction are you pointing at? 29 | - If `theta` keeps increasing, at what value should you change the direction 30 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/game/coords.rs: -------------------------------------------------------------------------------- 1 | use super::Prng; 2 | 3 | use heapless::FnvIndexSet; 4 | 5 | /// A single point on the grid. 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 7 | pub struct Coords { 8 | // Signed ints to allow negative values (handy when checking if we have gone 9 | // off the top or left of the grid) 10 | pub row: i8, 11 | pub col: i8, 12 | } 13 | 14 | impl Coords { 15 | /// Get random coordinates within a grid. `exclude` is an optional set of 16 | /// coordinates which should be excluded from the output. 17 | pub fn random(rng: &mut Prng, exclude: Option<&FnvIndexSet>) -> Self { 18 | let mut coords = Coords { 19 | row: ((rng.random_u32() as usize) % 5) as i8, 20 | col: ((rng.random_u32() as usize) % 5) as i8, 21 | }; 22 | while exclude.is_some_and(|exc| exc.contains(&coords)) { 23 | coords = Coords { 24 | row: ((rng.random_u32() as usize) % 5) as i8, 25 | col: ((rng.random_u32() as usize) % 5) as i8, 26 | } 27 | } 28 | coords 29 | } 30 | 31 | /// Whether the point is outside the bounds of the grid. 32 | pub fn is_out_of_bounds(&self) -> bool { 33 | self.row < 0 || self.row >= 5 || self.col < 0 || self.col >= 5 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/toggle-it.md: -------------------------------------------------------------------------------- 1 | # Toggle it 2 | 3 | Let's turn the LED on and off repeatedly. That's how you make it blink, right? 4 | 5 | In `examples/fast-blink.rs` you'll find the next iteration of our blinky. I've decided to make it 6 | blink the next LED over, while leaving the original LED on. That is an easy change. 7 | 8 | ```rust 9 | {{#include examples/fast-blink.rs}} 10 | ``` 11 | 12 | The `embedded-hal` crate is being used here to provide the Rust traits needed to set and unset the 13 | LED. This means that this part of the code is portable to any Rust HAL that implements the 14 | `embedded-hal` traits as ours does. 15 | 16 | But wait: neither LED is blinking! The second one is slightly dimmer than the first one, but they 17 | are both solidly on… or are they? Out of the box, the MB2 executes 64 *million* instructions per 18 | second. Let's assume it takes a few dozen instructions under the hood to turn the LED on or 19 | off. (Maybe possibly that many compiled in debug mode, though way less in release mode. Though the 20 | pins take a while to change state. I don't know.) Anyhow, that second LED is actually turning on and 21 | off hundreds of thousands of times — perhaps millions of times — every second. Your eye just can't 22 | keep up. 23 | 24 | We'll need to wait a while between toggles. Turns out waiting is the hardest part. 25 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/README.md: -------------------------------------------------------------------------------- 1 | # Inputs and Polling 2 | 3 | In earlier chapters, we’ve explored GPIO pins primarily as outputs—driving LEDs on and off. However, GPIO pins can also be configured as inputs, allowing your program to read signals from the physical world, like button presses or switch toggles. In this chapter, we'll learn how to read these input signals and do something useful with them. 4 | 5 | ## Reading Button State 6 | 7 | The micro:bit v2 has two physical buttons, Button A and Button B, connected to GPIO pins configured as inputs. Specifically, Button A is connected to pin P0.14, and Button B to pin P0.23. (You can verify this from the official [pinmap table].) 8 | 9 | [pinmap table]: https://tech.microbit.org/hardware/schematic/#v2-pinmap 10 | 11 | Reading the state of a GPIO input involves checking whether the voltage level at the pin is high (3.3V, logic level 1) or low (0V, logic level 0). Each button on the micro:bit is connected to a pin. When the button is *not* pressed, that pin is held high; when the button is pressed, the pin is held low. 12 | 13 | Let's now apply this knowledge to reading the state of Button A by checking if the button is "low" (pressed). 14 | 15 | ```rust 16 | {{#include examples/button-a-bsp.rs}} 17 | ``` 18 | 19 | We spin looking at the button state, and report anytime that state changes. 20 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use microbit::{board::Board, display::blocking::Display, hal::Timer}; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | #[rustfmt::skip] 11 | const PIXELS: [(usize, usize); 16] = [ 12 | (0, 0), 13 | (0, 1), 14 | (0, 2), 15 | (0, 3), 16 | (0, 4), 17 | (1, 4), 18 | (2, 4), 19 | (3, 4), 20 | (4, 4), 21 | (4, 3), 22 | (4, 2), 23 | (4, 1), 24 | (4, 0), 25 | (3, 0), 26 | (2, 0), 27 | (1, 0), 28 | ]; 29 | 30 | #[entry] 31 | fn main() -> ! { 32 | rtt_init_print!(); 33 | 34 | let board = Board::take().unwrap(); 35 | let mut timer = Timer::new(board.TIMER0); 36 | let mut display = Display::new(board.display_pins); 37 | #[rustfmt::skip] 38 | let mut leds = [ 39 | [0, 0, 0, 0, 0], 40 | [0, 0, 0, 0, 0], 41 | [0, 0, 0, 0, 0], 42 | [0, 0, 0, 0, 0], 43 | [0, 0, 0, 0, 0], 44 | ]; 45 | 46 | let mut last_led = (0, 0); 47 | 48 | loop { 49 | for current_led in PIXELS { 50 | leds[last_led.0][last_led.1] = 0; 51 | leds[current_led.0][current_led.1] = 1; 52 | display.show(&mut timer, leds, 200); 53 | last_led = current_led; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/IDE.md: -------------------------------------------------------------------------------- 1 | # Getting the most out of your IDE 2 | 3 | All code in this book assumes that you use a simple terminal to build your code, 4 | run it, and interact with it. It also makes no assumption about your text editor. 5 | 6 | However, you may have your favourite IDEs, providing you auto-complete, type annotation, 7 | your preferred shortcuts and much more. This section explains how to get the most out 8 | of your IDE using the code obtained from this book's repo. 9 | 10 | # IDE configuration 11 | 12 | Below, we explain how to configure your IDE to get the most out of this book. 13 | If your IDE is not listed below, please improve this book by adding a section, so that the next 14 | reader can get the best experience out of it. 15 | 16 | ## How to build with IntelliJ 17 | 18 | When editing the IntelliJ build configuration, here are a few non-default values: 19 | * You should edit the command. When this book tells you to run `cargo embed FLAGS`, 20 | You'll need to replace the default value `run` by the command `embed FLAGS`, 21 | * You should enable "Emulate terminal in output console". Otherwise, your program will fail to print text to a terminal 22 | * You should ensure that the working directory is `microbit/src/N-name`, with `N-name` being the directory of the chapter you 23 | are reading. You can not run from the `src` directory since it contains no cargo file. 24 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/poke.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m::asm; 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use microbit::{ 10 | Board, 11 | hal::{ 12 | gpiote, 13 | pac::{self, interrupt}, 14 | }, 15 | }; 16 | 17 | /// This "function" will be called when an interrupt is received. For now, just 18 | /// report and panic. 19 | #[interrupt] 20 | fn GPIOTE() { 21 | rprintln!("ouch"); 22 | panic!(); 23 | } 24 | 25 | #[entry] 26 | fn main() -> ! { 27 | rtt_init_print!(); 28 | let board = Board::take().unwrap(); 29 | let button_a = board.buttons.button_a.into_floating_input(); 30 | 31 | // Set up the GPIOTE to generate an interrupt when Button A is pressed (GPIO 32 | // wire goes low). 33 | let gpiote = gpiote::Gpiote::new(board.GPIOTE); 34 | let channel = gpiote.channel0(); 35 | channel 36 | .input_pin(&button_a.degrade()) 37 | .hi_to_lo() 38 | .enable_interrupt(); 39 | channel.reset_events(); 40 | 41 | // Set up the NVIC to handle GPIO interrupts. 42 | unsafe { pac::NVIC::unmask(pac::Interrupt::GPIOTE) }; 43 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 44 | 45 | loop { 46 | // "wait for interrupt": CPU goes to sleep until an interrupt. 47 | asm::wfi(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/build.rs: -------------------------------------------------------------------------------- 1 | //! This build script copies the `memory.x` file from the crate root into 2 | //! a directory where the linker can always find it at build time. 3 | //! For many projects this is optional, as the linker always searches the 4 | //! project root directory (wherever `Cargo.toml` is). However, if you 5 | //! are using a workspace or have a more complicated build setup, this 6 | //! build script becomes required. Additionally, by requesting that 7 | //! Cargo re-run the build script whenever `memory.x` is changed, 8 | //! a rebuild of the application with new memory settings is ensured after updating `memory.x`. 9 | 10 | use std::env; 11 | use std::fs::File; 12 | use std::io::Write; 13 | use std::path::PathBuf; 14 | 15 | fn main() { 16 | // Put `memory.x` in our output directory and ensure it's 17 | // on the linker search path. 18 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 19 | File::create(out.join("memory.x")) 20 | .unwrap() 21 | .write_all(include_bytes!("memory.x")) 22 | .unwrap(); 23 | println!("cargo:rustc-link-search={}", out.display()); 24 | 25 | // By default, Cargo will re-run a build script whenever 26 | // any file in the project changes. By specifying `memory.x` 27 | // here, we ensure the build script is only re-run when 28 | // `memory.x` is changed. 29 | println!("cargo:rerun-if-changed=memory.x"); 30 | } 31 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/lsm303agr.md: -------------------------------------------------------------------------------- 1 | # LSM303AGR 2 | 3 | Both of the motion sensors on the micro:bit, the magnetometer and the accelerometer, are packaged in 4 | a single component: the LSM303AGR integrated circuit. These two sensors can be accessed via an I2C 5 | bus. Each sensor behaves like an I2C target and has a *different* address. 6 | 7 | Each sensor has its own memory where it stores the results of sensing its environment. Our 8 | interaction with these sensors will mainly involve reading their memory. 9 | 10 | The memory of these sensors is modeled as byte addressable registers. These sensors can be 11 | configured too; that's done by writing to their registers. So, in a sense, these sensors are very 12 | similar to the peripherals *inside* the microcontroller. The difference is that their registers are 13 | not mapped into the microcontrollers' memory. Instead, their registers have to be accessed via the 14 | I2C bus. 15 | 16 | The main source of information about the LSM303AGR is its [Data Sheet]. Read through it to see how 17 | one can read the sensors' registers. That part is in: 18 | 19 | [Data Sheet]: https://www.st.com/resource/en/datasheet/lsm303agr.pdf 20 | 21 | > Section 6.1.1 I2C Operation - Page 38 - LSM303AGR Data Sheet 22 | 23 | The other part of the documentation relevant to this book is the description of the registers. That 24 | part is in: 25 | 26 | > Section 8 Register description - Page 46 - LSM303AGR Data Sheet 27 | -------------------------------------------------------------------------------- /mdbook/src/04-meet-your-hardware/README.md: -------------------------------------------------------------------------------- 1 | # Meet your hardware 2 | 3 | Let's get familiar with the hardware we'll be working with. 4 | 5 | ## micro:bit 6 | 7 |

8 | 9 |

10 | 11 | Here are some of the many components on the board: 12 | 13 | - A [microcontroller]. 14 | - A number of LEDs, most notably the LED matrix on the back 15 | - Two user buttons as well as a reset button (the one next to the USB port). 16 | - One USB port. 17 | - A sensor that is both a [magnetometer] and an [accelerometer] 18 | 19 | [microcontroller]: https://en.wikipedia.org/wiki/Microcontroller 20 | [accelerometer]: https://en.wikipedia.org/wiki/Accelerometer 21 | [magnetometer]: https://en.wikipedia.org/wiki/Magnetometer 22 | 23 | Of these components, the most important is the microcontroller (sometimes 24 | shortened to "MCU" for "microcontroller unit"), which is the bigger of the two 25 | black squares sitting on the side of the board with the USB port. The MCU is 26 | what runs your code. You might sometimes read about "programming a board", when 27 | in reality what we are doing is programming the MCU that is installed on the board. 28 | 29 | If you happen to be interested in a more detailed description of the board you 30 | can checkout the [micro:bit website](https://tech.microbit.org/hardware/). 31 | 32 | Since the MCU is so important, let's take a closer look at the one sitting on our board. 33 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::fmt::Write; 5 | use cortex_m_rt::entry; 6 | use heapless::Vec; 7 | use microbit::hal::uarte::{self, Baudrate, Parity}; 8 | use panic_rtt_target as _; 9 | use rtt_target::rtt_init_print; 10 | use serial_setup::UartePort; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | rtt_init_print!(); 15 | let board = microbit::Board::take().unwrap(); 16 | 17 | let mut serial = { 18 | let serial = uarte::Uarte::new( 19 | board.UARTE0, 20 | board.uart.into(), 21 | Parity::EXCLUDED, 22 | Baudrate::BAUD115200, 23 | ); 24 | UartePort::new(serial) 25 | }; 26 | 27 | // A buffer with 32 bytes of capacity 28 | let mut buffer: Vec = Vec::new(); 29 | 30 | loop { 31 | buffer.clear(); 32 | 33 | loop { 34 | // We assume that the receiving cannot fail 35 | let byte = serial.read().unwrap(); 36 | 37 | if buffer.push(byte).is_err() { 38 | write!(serial, "error: buffer full\r\n").unwrap(); 39 | break; 40 | } 41 | 42 | if byte == b'\r' { 43 | for byte in buffer.iter().rev().chain(&[b'\n', b'\r']) { 44 | serial.write(*byte).unwrap(); 45 | } 46 | break; 47 | } 48 | } 49 | serial.flush().unwrap() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/examples/magnitude.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use embedded_hal::delay::DelayNs; 7 | use panic_rtt_target as _; 8 | use rtt_target::{rprintln, rtt_init_print}; 9 | 10 | use libm::sqrtf; 11 | 12 | use microbit::{ 13 | hal::{twim, Timer}, 14 | pac::twim0::frequency::FREQUENCY_A, 15 | }; 16 | 17 | use lsm303agr::{Lsm303agr, MagMode, MagOutputDataRate}; 18 | 19 | #[entry] 20 | fn main() -> ! { 21 | rtt_init_print!(); 22 | let board = microbit::Board::take().unwrap(); 23 | 24 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 25 | 26 | let mut timer0 = Timer::new(board.TIMER0); 27 | 28 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 29 | sensor.init().unwrap(); 30 | sensor 31 | .set_mag_mode_and_odr( 32 | &mut timer0, 33 | MagMode::HighResolution, 34 | MagOutputDataRate::Hz10, 35 | ) 36 | .unwrap(); 37 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 38 | 39 | loop { 40 | while !sensor.mag_status().unwrap().xyz_new_data() { 41 | timer0.delay_ms(1u32); 42 | } 43 | let (x, y, z) = sensor.magnetic_field().unwrap().xyz_nt(); 44 | let (x, y, z) = (x as f32, y as f32, z as f32); 45 | let magnitude = sqrtf(x * x + y * y + z * z); 46 | rprintln!("{} mG", magnitude / 100.0); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /reworkspace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # How to use this tool: 4 | # 1. cd to the root of this repo 5 | # 2. python3 reworkspace.py 6 | # 3. inspect Cargo.toml.new 7 | # 4. mv -f Cargo.toml.new Cargo.toml 8 | # 5. git commit -a 9 | 10 | import sys 11 | from pathlib import Path 12 | 13 | root = Path("mdbook/src") 14 | repos = [] 15 | for d0 in (root, root / "appendix"): 16 | for d in d0.iterdir(): 17 | if (d / "src").is_dir() or (d / "examples").is_dir(): 18 | repos.append(d) 19 | 20 | def next_line(lines): 21 | try: 22 | return next(lines) 23 | except StopIteration: 24 | return None 25 | 26 | with open("Cargo.toml", "r") as c: 27 | lines = iter(c.read().splitlines()) 28 | with open("Cargo.toml.new", "w") as nc: 29 | while True: 30 | line = next_line(lines) 31 | if line is None: 32 | break 33 | if line == "members = [": 34 | print(line, file=nc) 35 | while True: 36 | line = next_line(lines) 37 | if line is None: 38 | print("unclosed members directive", file=sys.stderr) 39 | break 40 | if line == "]": 41 | break 42 | for p in sorted(repos): 43 | print(f' "{str(p)}",', file=nc) 44 | print("]", file=nc) 45 | continue 46 | print(line, file=nc) 47 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/addendum-pwm.md: -------------------------------------------------------------------------------- 1 | # Addendum: PWM 2 | 3 | One last note before we move on. 4 | 5 | Interrupts are kind of expensive. The processor must finish 6 | or abort the currently-running instruction, then save enough 7 | state to restart execution, then call an interrupt 8 | handler. All this takes a few CPU cycles of precious 9 | runtime. 10 | 11 | The way the solution of the previous section is written, it 12 | will take two interrupts per cycle of speaker output. That's 13 | something like 1000 interrupts per second. On a processor 14 | like our nRF52833, that works fine. 15 | 16 | The nRF52833 does have an on-board peripheral that could cut 17 | our siren's interrupt rate way down. The Pulse-Width 18 | Modulation (PWM) unit can, among other things, generate 19 | cycles on the speaker pin at a rate controlled by a PWM 20 | register. This could be used to generate the basic square 21 | wave used for our siren. We would still need an interrupt 22 | every time we wanted to change the frequency, but this might 23 | be more like 10 interrupts per second than 1000. 24 | 25 | I did not use the PWM unit in my solution. This was partly 26 | because I wanted to focus on interrupts. Another big reason, 27 | though, was that the nRF52833 PWM unit is pretty complicated 28 | and hard to understand. Getting something working a simple 29 | way in the tight bare-metal environment is always 30 | attractive. 31 | 32 | If you are up for a challenge, I would encourage you to try 33 | using the PWM unit for your siren. 34 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::digital::InputPin; 6 | use microbit::{board::Board, display::blocking::Display, hal::Timer}; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | // Define LED patterns 11 | const LEFT_ARROW: [[u8; 5]; 5] = [ 12 | [0, 0, 1, 0, 0], 13 | [0, 1, 0, 0, 0], 14 | [1, 1, 1, 1, 1], 15 | [0, 1, 0, 0, 0], 16 | [0, 0, 1, 0, 0], 17 | ]; 18 | 19 | const RIGHT_ARROW: [[u8; 5]; 5] = [ 20 | [0, 0, 1, 0, 0], 21 | [0, 0, 0, 1, 0], 22 | [1, 1, 1, 1, 1], 23 | [0, 0, 0, 1, 0], 24 | [0, 0, 1, 0, 0], 25 | ]; 26 | 27 | const CENTER_LED: [[u8; 5]; 5] = [ 28 | [0, 0, 0, 0, 0], 29 | [0, 0, 0, 0, 0], 30 | [0, 0, 1, 0, 0], 31 | [0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0], 33 | ]; 34 | 35 | #[entry] 36 | fn main() -> ! { 37 | rtt_init_print!(); 38 | let board = Board::take().unwrap(); 39 | let mut timer = Timer::new(board.TIMER0); 40 | 41 | let mut display = Display::new(board.display_pins); 42 | let mut button_a = board.buttons.button_a; 43 | let mut button_b = board.buttons.button_b; 44 | 45 | loop { 46 | if button_a.is_low().unwrap() { 47 | display.show(&mut timer, LEFT_ARROW, 10); 48 | } else if button_b.is_low().unwrap() { 49 | display.show(&mut timer, RIGHT_ARROW, 10); 50 | } else { 51 | display.show(&mut timer, CENTER_LED, 10); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/the-general-protocol.md: -------------------------------------------------------------------------------- 1 | # General protocol 2 | 3 | The I2C protocol is more elaborate than the serial communication protocol because it has to support 4 | communication between several devices. Let's see how it works using examples: 5 | 6 | ## Controller → Target 7 | 8 | If the Controller wants to send data to the Target: 9 | 10 |

11 | 12 |

13 | 14 | 1. Controller: Broadcast START 15 | 2. C: Broadcast target address (7 bits) + the R/W (8th) bit set to WRITE 16 | 3. Target: Responds ACK (ACKnowledgement) 17 | 4. C: Send one byte 18 | 5. T: Responds ACK 19 | 6. Repeat steps 4 and 5 zero or more times 20 | 7. C: Broadcast STOP OR (broadcast RESTART and go back to (2)) 21 | 22 | > **NOTE** The target address could have been 10 bits instead of 7 bits long. Nothing else would have 23 | > changed. 24 | 25 | ## Controller ← Target 26 | 27 | If the controller wants to read data from the target: 28 | 29 |

30 | 31 |

32 | 33 | 1. C: Broadcast START 34 | 2. C: Broadcast target address (7 bits) + the R/W (8th) bit set to READ 35 | 3. T: Responds with ACK 36 | 4. T: Send byte 37 | 5. C: Responds with ACK 38 | 6. Repeat steps 4 and 5 zero or more times 39 | 7. C: Broadcast STOP OR (broadcast RESTART and go back to (2)) 40 | 41 | > **NOTE** The target address could have been 10 bits instead of 7 bits long. Nothing else would 42 | > have changed. 43 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/board-support-crate.md: -------------------------------------------------------------------------------- 1 | # Board support crate 2 | 3 | Working directly with the PAC and HAL is pretty neat. Most Arm MCUs and many other MCUs that Rust 4 | can compile for have a PAC crate. If you are working with one that does not, writing a PAC crate can 5 | be tedious but is pretty straightforward. Many MCUs that have a PAC crate also have a HAL crate — 6 | again, it's mostly just tedious work to build one if it is absent. Code written at the PAC and HAL 7 | level gives access to the fine details of the MCU. 8 | 9 | As we have seen, though, it becomes pretty annoying to keep track of just what is going on at the 10 | interface between our nRF52833 and the rest of our MB2. We have had to read schematics and whatnot 11 | to see how to use our off-board hardware. 12 | 13 | A "board support crate" — known in the non-Rust embedded community as a Board Support Package (BSP) 14 | — is a crate built on top of the HAL and PAC for a board to abstract away the details and provide 15 | conveniences. The board support crate we have been working with is the `microbit-v2` crate. 16 | 17 | Let's use `microbit-v2` to get a final, cleaned up blinky (`src/main.rs`). 18 | 19 | ```rust 20 | {{#include src/main.rs}} 21 | ``` 22 | 23 | In this case, we haven't changed much. Our board support crate has hidden the PAC (for now). More 24 | importantly, it has done so by letting us just use reasonable names for the row and column GPIO pins 25 | for the LED. 26 | 27 | The `microbit-v2` crate provides even fancier support for those "display" LEDs. We will see this 28 | support used soon to do things more fun than blinky. 29 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/examples/polling-led-toggle.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::delay::DelayNs; 6 | use embedded_hal::digital::{InputPin, OutputPin}; 7 | use microbit::hal::timer::Timer; 8 | use microbit::{hal::gpio, Board}; 9 | use panic_rtt_target as _; 10 | use rtt_target::rtt_init_print; 11 | 12 | #[entry] 13 | fn main() -> ! { 14 | rtt_init_print!(); 15 | let board = Board::take().unwrap(); 16 | let mut timer = Timer::new(board.TIMER0); 17 | 18 | // Configure buttons 19 | let mut button_a = board.buttons.button_a; 20 | let mut button_b = board.buttons.button_b; 21 | 22 | // Configure LED (top-left LED at row1, col1) 23 | let mut row1 = board 24 | .display_pins 25 | .row1 26 | .into_push_pull_output(gpio::Level::Low); 27 | let _col1 = board 28 | .display_pins 29 | .col1 30 | .into_push_pull_output(gpio::Level::Low); 31 | 32 | loop { 33 | let on_pressed = button_a.is_low().unwrap(); 34 | let off_pressed = button_b.is_low().unwrap(); 35 | match (on_pressed, off_pressed) { 36 | // Stay in current state until something is pressed. 37 | (false, false) => (), 38 | // Change to on state. 39 | (true, false) => row1.set_high().unwrap(), 40 | // Change to off state. 41 | (false, true) => row1.set_low().unwrap(), 42 | // Stay in current state until something is released. 43 | (true, true) => (), 44 | } 45 | timer.delay_ms(10_u32); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/final-assembly.md: -------------------------------------------------------------------------------- 1 | # Snake game: final assembly 2 | 3 | The code in our `src/main.rs` file brings all the previously-discussed machinery together to make 4 | our final game. 5 | 6 | ```rust 7 | {{#include src/main.rs}} 8 | ``` 9 | 10 | After initializing the board and its timer and RNG peripherals, we initialize a `Game` struct and a 11 | `Display` from the `microbit::display::blocking` module. 12 | 13 | In our "game loop" (which runs inside of the "main loop" we place in our `main` function), we 14 | repeatedly perform the following steps: 15 | 16 | 1. Get a 5×5 array of bytes representing the grid. The `Game::get_matrix` method takes three integer 17 | arguments (which should be between 0 and 9, inclusive) which will, eventually, represent how 18 | brightly the head, tail and food should be displayed. 19 | 20 | 2. Display the matrix, for an amount of time determined by the `Game::step_len_ms` method. As 21 | currently implemented, this method basically provides for 1 second between steps, reducing by 22 | 200ms every time the player scores 5 points (eating 1 piece of food = 1 point), subject to a 23 | floor of 200ms. 24 | 25 | 3. Check the game status. If it is `Ongoing` (which is its initial value), run a step of the game 26 | and update the game state (including its `status` property). Otherwise, the game is over, so 27 | flash the current image three times, then show the player's score (represented as a number of 28 | illuminated LEDs corresponding to the score), and exit the game loop. 29 | 30 | Our main loop just runs the game loop repeatedly, resetting the game's state after each iteration. 31 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/timers.md: -------------------------------------------------------------------------------- 1 | # Timers 2 | 3 | One of the big advantages of a "bare-metal" embedded system is that you control everything that 4 | happens on your machine. This allows you to have really precise control of time: nothing will slow 5 | you down unless you let it. 6 | 7 | However, we've seen that if we really want to get time right, we probably need help. Embedded MCUs 8 | like the nRF52833 all provide this kind of help in the form of "timers". A timer is a peripheral 9 | that, as its name implies, acts like a little clock that keeps very precise track of time. 10 | 11 | The nRF52833 contains four timers. If you look at the documentation for the chip, you'll find that 12 | they are pretty complicated to set up and use. Luckily, the HAL provides a wrapper around timers 13 | that makes common uses easy. The most common use of a timer is to delay for a precise amount of 14 | time: just what our `wait()` function of the previous sections was trying to do. 15 | 16 | Take a look at `examples/timer-blinky.rs`. This code sets up a timer and uses it to delay for 500ms 17 | (0.5s) between each toggle. 18 | 19 | ```rust 20 | {{#include examples/timer-blinky.rs}} 21 | ``` 22 | 23 | Run this code with `cargo run --release --example timer-blinky` and time it with a stopwatch. You'll 24 | find that it is exactly one second for each on-off cycle. 25 | 26 | Things you might notice: 27 | 28 | * We need to use the `embedded_hal::Delay` trait to get the `delay_ms()` method we're using. 29 | 30 | * As before, we dig the peripheral out of the PAC peripherals struct and give it to the HAL. 31 | 32 | Now we have a production-quality blinky. Let's talk a bit about the implications of all this. 33 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/count-once.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::cell::RefCell; 5 | 6 | use cortex_m::asm; 7 | use cortex_m_rt::entry; 8 | use critical_section::Mutex; 9 | use panic_rtt_target as _; 10 | use rtt_target::{rprintln, rtt_init_print}; 11 | 12 | use microbit::{ 13 | Board, 14 | hal::{ 15 | gpiote, 16 | pac::{self, interrupt}, 17 | }, 18 | }; 19 | 20 | static COUNTER: Mutex> = Mutex::new(RefCell::new(0)); 21 | 22 | /// This "function" will be called when an interrupt is received. For now, just 23 | /// report and panic. 24 | #[interrupt] 25 | fn GPIOTE() { 26 | critical_section::with(|cs| { 27 | let mut count = COUNTER.borrow(cs).borrow_mut(); 28 | *count += 1; 29 | rprintln!("count: {}", count); 30 | }); 31 | panic!(); 32 | } 33 | 34 | #[entry] 35 | fn main() -> ! { 36 | rtt_init_print!(); 37 | let board = Board::take().unwrap(); 38 | let button_a = board.buttons.button_a.into_floating_input(); 39 | 40 | // Set up the GPIOTE to generate an interrupt when Button A is pressed (GPIO 41 | // wire goes low). 42 | let gpiote = gpiote::Gpiote::new(board.GPIOTE); 43 | let channel = gpiote.channel0(); 44 | channel 45 | .input_pin(&button_a.degrade()) 46 | .hi_to_lo() 47 | .enable_interrupt(); 48 | channel.reset_events(); 49 | 50 | // Set up the NVIC to handle GPIO interrupts. 51 | unsafe { pac::NVIC::unmask(pac::Interrupt::GPIOTE) }; 52 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 53 | 54 | loop { 55 | // "wait for interrupt": CPU goes to sleep until an interrupt. 56 | asm::wfi(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/waiting-to-be-interrupted.md: -------------------------------------------------------------------------------- 1 | # Waiting for an interrupt 2 | 3 | You may have wondered why we have been using `asm::wfi()` (wait for instruction) in our main loop instead of something like `asm::nop()`. 4 | 5 | As discussed before, `asm::nop()` means no-op(eration), and is an instruction that the CPU executes without doing anything . We definitely could have used `asm::nop()` in our main loop instead, and the program would have behaved the same way. The microcontroller, on the other hand, would behave differently. 6 | 7 | Calling `asm::wfi()` puts the CPU into "Wait For Interrupt" (WFI) mode. When the CPU is in WFI mode, it will sleep until an interrupt wakes it up. During sleep, the CPU will stop fetching instructions, turn off some clocks and peripherals, and enter a low-power state, but still keep the core running. When an interrupt occurs, the CPU will wake up and execute as normal. 8 | 9 | The main difference between `asm::wfi()` and `asm::nop()` is that the NOP instruction completes immediately, and will thus be run repeatedly in a loop. The NOP still needs to be fetched from the program memory and be executed even though the execution doesn't do anything. Most microcontrollers you'll find out there have a low-power mode (some even have several, each with varying things staying on and each with different power consumption characteristics) that can (and should in a lot of cases) be used to save power. The WFI instruction halts execution *in a low-power mode* until an interrupt is received. 10 | 11 | You'll find some interrupt-driven programs that consist of nothing but `asm::wfi()` in the main loop, with all program logic being implemented in the interrupt handlers. 12 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/portability.md: -------------------------------------------------------------------------------- 1 | # Portability 2 | 3 | (This section is optional. Feel free to skip to the [next section], where we clean our code up a bit 4 | and call it a day.) 5 | 6 | [next section]: board-support-crate.html 7 | 8 | You may wonder whether all this fancy ecosystem is worth its weight. The setup for our blinky is 9 | pretty fancy, and uses a lot of Rust crates and features for such a simple job. 10 | 11 | One cool advantage, though, is that our code becomes really portable. On a different board, the 12 | setup may be different, but the actual blinky loop is identical! 13 | 14 | Let's take a look at a blinky for the Sipeed Longan Nano. This is a little $5 board that, like the 15 | MB2, is an embedded board with an MCU. Otherwise, it is completely different: different processor 16 | (the GD32VF103, with a RISC-V instruction set entirely unlike the Arm instruction set we're using), 17 | different peripherals, different board. But it has an LED attached to a GPIO pin, so we can blinky 18 | it. 19 | 20 | ```rust 21 | {{#include nanoblinky.rs}} 22 | ``` 23 | 24 | The differences in setup here are partly because different hardware, and partly because this code 25 | uses an older HAL crate that hasn't yet been updated for `embedded-hal` 1.0. Yet the main loop is 26 | identical as advertised, and the rest of the code is pretty recognizable. Because of the portability 27 | provided by Rust's easy cross-compilation and the embedded Rust ecosystem, blinky is just blinky. 28 | 29 | You can find a complete working [nanoblinky] example on GitHub, if you want to see all the 30 | details or even get your own board and try it yourself. 31 | 32 | [nanoblinky]: https://github.com/pdx-cs-rust/nanoblinky 33 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/count.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::sync::atomic::{AtomicUsize, Ordering::AcqRel}; 5 | 6 | use cortex_m::asm; 7 | use cortex_m_rt::entry; 8 | use critical_section_lock_mut::LockMut; 9 | use panic_rtt_target as _; 10 | use rtt_target::{rprintln, rtt_init_print}; 11 | 12 | use microbit::{ 13 | Board, 14 | hal::{ 15 | gpiote, 16 | pac::{self, interrupt}, 17 | }, 18 | }; 19 | 20 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 21 | static GPIOTE_PERIPHERAL: LockMut = LockMut::new(); 22 | 23 | #[interrupt] 24 | fn GPIOTE() { 25 | let count = COUNTER.fetch_add(1, AcqRel); 26 | rprintln!("ouch {}", count + 1); 27 | GPIOTE_PERIPHERAL.with_lock(|gpiote| { 28 | gpiote.channel0().reset_events(); 29 | }); 30 | } 31 | 32 | #[entry] 33 | fn main() -> ! { 34 | rtt_init_print!(); 35 | let board = Board::take().unwrap(); 36 | let button_a = board.buttons.button_a.into_floating_input(); 37 | 38 | // Set up the GPIOTE to generate an interrupt when Button A is pressed (GPIO 39 | // wire goes low). 40 | let gpiote = gpiote::Gpiote::new(board.GPIOTE); 41 | let channel = gpiote.channel0(); 42 | channel 43 | .input_pin(&button_a.degrade()) 44 | .hi_to_lo() 45 | .enable_interrupt(); 46 | channel.reset_events(); 47 | GPIOTE_PERIPHERAL.init(gpiote); 48 | 49 | // Set up the NVIC to handle GPIO interrupts. 50 | unsafe { pac::NVIC::unmask(pac::Interrupt::GPIOTE) }; 51 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 52 | 53 | loop { 54 | // "wait for interrupt": CPU goes to sleep until an interrupt. 55 | asm::wfi(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/reverse-a-string.md: -------------------------------------------------------------------------------- 1 | # Reverse a string 2 | 3 | Alright, next let's make the server more interesting by having it respond to the client with the 4 | reverse of the text that they sent. The server will respond to the client every time they press the 5 | ENTER key. Each server response will be in a new line. 6 | 7 | This time you'll need a buffer; you can use [`heapless::Vec`]. Here's the starter code: 8 | 9 | [`heapless::Vec`]: https://docs.rs/heapless/latest/heapless/struct.Vec.html 10 | 11 | ``` rust 12 | #![no_main] 13 | #![no_std] 14 | 15 | use cortex_m_rt::entry; 16 | use core::fmt::Write; 17 | use heapless::Vec; 18 | use rtt_target::rtt_init_print; 19 | use panic_rtt_target as _; 20 | 21 | use microbit::{ 22 | hal::prelude::*, 23 | hal::uarte, 24 | hal::uarte::{Baudrate, Parity}, 25 | }; 26 | 27 | mod serial_setup; 28 | use serial_setup::UartePort; 29 | 30 | #[entry] 31 | fn main() -> ! { 32 | rtt_init_print!(); 33 | let board = microbit::Board::take().unwrap(); 34 | 35 | let mut serial = { 36 | let serial = uarte::Uarte::new( 37 | board.UARTE0, 38 | board.uart.into(), 39 | Parity::EXCLUDED, 40 | Baudrate::BAUD115200, 41 | ); 42 | UartePort::new(serial) 43 | }; 44 | 45 | // A buffer with 32 bytes of capacity 46 | let mut buffer: Vec = Vec::new(); 47 | 48 | loop { 49 | buffer.clear(); 50 | 51 | // TODO Receive a user request. Each user request ends with ENTER 52 | // NOTE `buffer.push` returns a `Result`. Handle the error by responding 53 | // with an error message. 54 | 55 | // TODO Send back the reversed string 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/nop.md: -------------------------------------------------------------------------------- 1 | # NOP 2 | 3 | You might wonder what that `nop()` call is doing in the `wait()` loop in `src/bin/spin-wait.rs`. 4 | 5 | The answer is that it literally does nothing. The `nop()` function causes the compiler to put a 6 | `NOP` Arm machine instruction at that point in the program. `NOP` is a special instruction that 7 | causes the CPU to skip it. To ignore it. To literally do No OPeration with it (hence the name). 8 | 9 | So get rid of that line and recompile the program. Don't forget `--release` mode. Then run it. 10 | 11 | We're back to a slightly darker solid LED again. With no loop body, the compiler's optimizer decided 12 | that `wait()` function wasn't doing anything. So it just removed it for you at compile time. Thanks 13 | optimizer. You have made my wait loop infinitely fast. 14 | 15 | How does `nop()` do its job? Well, if you look at the implementation of `nop()` you will find 16 | (after a bunch of digging around) that it is implemented like this: 17 | 18 | ```rust 19 | asm!("nop", options(nomem, nostack, preserves_flags)); 20 | ``` 21 | 22 | The `nop()` function is "inlined", so when you "call" it an actual Arm `NOP` assembly instruction is 23 | inserted into your program's code at that point. Because details, this `NOP` will not be removed or 24 | moved around by the compiler: it will stay right there where you put it. 25 | 26 | The ability to insert assembly code into your program where needed is sometimes quite important in 27 | embedded programming. Sometime a CPU will have instructions the compiler doesn't know about, but 28 | that you still need in order to use the CPU effectively. Rust's `asm!()` directive gives you a way 29 | to do that. 30 | 31 | Our spin-wait is still terrible. Let's talk about doing better. 32 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/polling.md: -------------------------------------------------------------------------------- 1 | # Polling 2 | 3 | Now that we've learned how to read GPIO inputs, let's consider how we might use these reads practically. Suppose we want our program to turn on an LED when Button A is pressed and turn it off when Button B is pressed. We can do this by polling the state of both buttons in a loop, and responding accordingly when a button is read to be pressed. Here's how we might write this program: 4 | 5 | ```rust 6 | {{#include examples/polling-led-toggle.rs}} 7 | ``` 8 | 9 | This method of repeatedly checking inputs in a loop is called polling. When we check the state of some input, we say we are *polling* that input. In this case, we are polling both Button A and Button B. 10 | 11 | Polling is simple but allows us to do interesting things based on the external world. For all of our device's inputs, we can "poll" them in a loop, and respond to the results in some way, one by one. This kind of method is very conceptually simple and is a good starting point for many projects. We'll soon find out why polling might not be the best method for all (or even most) cases, but let's try it out first. 12 | 13 | **Note** "Polling" is often used on two levels of granularity. At one level, "polling" is used to refer to asking (once) what the state of an input is. At a higher level, "polling", or perhaps "polling in a loop", is used to refer to asking (repeatedly) what the state of an input is in a simple control flow like the one we used above. This kind of use of the word to refer to a control flow is used only in the simplest of programs, and seldom used in production (it's not practical as we'll soon see), so generally when embedded engineers talk about polling, they mean the former, i.e. to ask (once) what the state of an input is. 14 | 15 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/count-bounce.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::sync::atomic::{AtomicUsize, Ordering::{Acquire, AcqRel}}; 5 | 6 | use cortex_m::asm; 7 | use cortex_m_rt::entry; 8 | use critical_section_lock_mut::LockMut; 9 | use panic_rtt_target as _; 10 | use rtt_target::{rprintln, rtt_init_print}; 11 | 12 | use microbit::{ 13 | Board, 14 | hal::{ 15 | gpiote, 16 | pac::{self, interrupt}, 17 | }, 18 | }; 19 | 20 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 21 | static GPIOTE_PERIPHERAL: LockMut = LockMut::new(); 22 | 23 | #[interrupt] 24 | fn GPIOTE() { 25 | let _ = COUNTER.fetch_add(1, AcqRel); 26 | GPIOTE_PERIPHERAL.with_lock(|gpiote| { 27 | gpiote.channel0().reset_events(); 28 | }); 29 | } 30 | 31 | #[entry] 32 | fn main() -> ! { 33 | rtt_init_print!(); 34 | let board = Board::take().unwrap(); 35 | let button_a = board.buttons.button_a.into_floating_input(); 36 | 37 | // Set up the GPIOTE to generate an interrupt when Button A is pressed (GPIO 38 | // wire goes low). 39 | let gpiote = gpiote::Gpiote::new(board.GPIOTE); 40 | let channel = gpiote.channel0(); 41 | channel 42 | .input_pin(&button_a.degrade()) 43 | .hi_to_lo() 44 | .enable_interrupt(); 45 | channel.reset_events(); 46 | GPIOTE_PERIPHERAL.init(gpiote); 47 | 48 | // Set up the NVIC to handle GPIO interrupts. 49 | unsafe { pac::NVIC::unmask(pac::Interrupt::GPIOTE) }; 50 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 51 | 52 | loop { 53 | // "wait for interrupt": CPU goes to sleep until an interrupt. 54 | asm::wfi(); 55 | let count = COUNTER.load(Acquire); 56 | rprintln!("ouch {}", count); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/README.md: -------------------------------------------------------------------------------- 1 | # Meet your software 2 | 3 | In this chapter we will learn how to build, run and debug some *very* simple programs. The goal here 4 | is not to get into the details of MB2 Rust programming (yet), but to just familiarize yourself with 5 | the mechanics of the process. 6 | 7 | First, a quick note about the conventions used in the rest of this book. We expect you to get 8 | a copy of the whole book with 9 | 10 | ``` 11 | git clone http://github.com/rust-embedded/discovery-mb2 12 | ``` 13 | 14 | The book's "source code" is in `discovery-mb2/mdbook/src`. You should go there in your copy and look 15 | around a bit. Each chapter directory has both the source Markdown text *and* the complete source for 16 | all the programs in that chapter. When we refer to some path like `src/main.rs`, we mean that place 17 | starting from the chapter you are working in. For example, your `discovery-mb2` has a file called 18 | `mdbook/src/05-meet-your-software/examples/init.rs`. We will refer to that file as just 19 | `examples/init.rs` in this chapter. 20 | 21 | There are two basic kinds of Rust code: "binary" executable programs, and "library" code. The 22 | library code won't play a huge role in this book. Binary program source code can live in one of 23 | several places: 24 | 25 | * A program in `src/main.rs` will be automatically compiled and run by `cargo embed` or `cargo 26 | run`. No special flags are needed. 27 | 28 | * A program in `examples/foo.rs` can be compiled and run by `cargo embed --example foo` or 29 | `cargo run --example foo`. 30 | 31 | * A program in `src/bin/bar.rs` can be compiled and run by `cargo embed --bin bar` or 32 | `cargo run --bin bar`. 33 | 34 | This is confusing, but it's a standard convention of Cargo. 35 | 36 | Now let's move on and work with all this. 37 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/the-mb2-speaker.md: -------------------------------------------------------------------------------- 1 | # The MB2 Speaker 2 | 3 | Your MB2 has a built-in speaker — the large black square device labeled "SPEAKER" in the middle of 4 | the back of the board. 5 | 6 | The speaker works by moving air in response to a GPIO pin: when the speaker pin is high (3.3V) a 7 | diaphragm inside — the "speaker cone" — is pushed all the way out; when the speaker pin is low (GND) 8 | it is pulled all the way back in. As air is pushed out and sucked back in, it flows in and out of 9 | the tiny rectangular hole — the "speaker port" — on the side of the device. Do this fast enough, 10 | and the pressure changes will make a sound. 11 | 12 | 13 | 14 | With the right hardware driving it, this speaker cone could actually be moved to any position in its 15 | range with an appropriate current. This would allow fairly good reproduction of any sound, like a 16 | "normal" speaker. Unfortunately, limitations in the MB2 hardware controlling the speaker mean that 17 | only the full-in and full-out positions are readily available. 18 | 19 | Let's push the speaker cone out and then in 220 times per second. This will produce a "square" 20 | 220-cycles-per-second pressure wave. The unit "cycles-per-second" is Hertz; we will be producing a 21 | 220Hz tone (a musical "A3"), which is not unpleasant on this shrill speaker. 22 | 23 | We'll make our tone play for five seconds and then stop. It is important to remember that our 24 | program lives in flash on the MB2 — the tone will start up again each time we reset or even power on 25 | the MB2. If we let the tone run forever, this behavior can rapidly become quite annoying. 26 | 27 | Here's the code (`examples/square-wave.rs`). 28 | 29 | ```rust 30 | {{#include examples/square-wave.rs}} 31 | ``` 32 | -------------------------------------------------------------------------------- /mdbook/src/10-serial-communication/windows-tooling.md: -------------------------------------------------------------------------------- 1 | # Windows tooling 2 | 3 | Start by unplugging your micro:bit. 4 | 5 | Before plugging the micro:bit back in, run the following command on the terminal: 6 | 7 | ``` console 8 | $ mode 9 | ``` 10 | 11 | It will print a list of devices that are connected to your computer. The ones that start with `COM` 12 | in their names are serial devices. This is the kind of device we'll be working with. Take note of 13 | all the `COM` ports' `mode` outputs *before* plugging the serial module. 14 | 15 | Now, plug in the micro:bit and run the `mode` command again. If you see a new 16 | `COM` port appear on the list, then that's the COM port assigned to the 17 | serial functionality on the micro:bit. 18 | 19 | Now launch `putty`. A GUI will pop out. 20 | 21 |

22 | 23 |

24 | 25 | On the starter screen, which should have the "Session" category open, pick "Serial" as the 26 | "Connection type". On the "Serial line" field enter the `COM` device you got on the previous step, 27 | for example `COM3`. 28 | 29 | Next, pick the "Connection/Serial" category from the menu on the left. On this new view, make sure 30 | that the serial port is configured as follows: 31 | 32 | - "Speed (baud)": 115200 33 | - "Data bits": 8 34 | - "Stop bits": 1 35 | - "Parity": None 36 | - "Flow control": None 37 | 38 | Finally, click the Open button. A console will show up now: 39 | 40 |

41 | 42 |

43 | 44 | If you type on this console, the yellow LED on top of the micro:bit will blink. Each keystroke 45 | should make the LED blink once. Note that the console won't echo back what you type so the screen 46 | will remain blank. 47 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | mod controls; 5 | mod display; 6 | pub mod game; 7 | 8 | use controls::{get_turn, init_buttons}; 9 | use display::{clear_display, display_image, init_display}; 10 | use game::{Game, GameStatus}; 11 | 12 | use cortex_m_rt::entry; 13 | use embedded_hal::delay::DelayNs; 14 | use microbit::{ 15 | display::nonblocking::{BitImage, GreyscaleImage}, 16 | hal::{Rng, Timer}, 17 | Board, 18 | }; 19 | use panic_rtt_target as _; 20 | use rtt_target::rtt_init_print; 21 | 22 | #[entry] 23 | fn main() -> ! { 24 | rtt_init_print!(); 25 | let board = Board::take().unwrap(); 26 | let mut timer = Timer::new(board.TIMER0).into_periodic(); 27 | let mut rng = Rng::new(board.RNG); 28 | let mut game = Game::new(&mut rng); 29 | 30 | init_buttons(board.GPIOTE, board.buttons); 31 | init_display(board.TIMER1, board.display_pins); 32 | 33 | loop { 34 | loop { 35 | // Game loop 36 | let image = GreyscaleImage::new(&game.game_matrix(6, 3, 9)); 37 | display_image(&image); 38 | timer.delay_ms(game.step_len_ms()); 39 | match game.status { 40 | GameStatus::Ongoing => game.step(get_turn(true)), 41 | _ => { 42 | for _ in 0..3 { 43 | clear_display(); 44 | timer.delay_ms(200u32); 45 | display_image(&image); 46 | timer.delay_ms(200u32); 47 | } 48 | clear_display(); 49 | display_image(&BitImage::new(&game.score_matrix())); 50 | timer.delay_ms(2000u32); 51 | break; 52 | } 53 | } 54 | } 55 | game.reset(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/magnitude.md: -------------------------------------------------------------------------------- 1 | # Magnitude 2 | 3 | How strong is the Earth's magnetic field? According to the documentation about the 4 | [`magnetic_field()`] method the `x` `y` `z` values we are getting are in nanoteslas. That means the 5 | only thing we have to compute in order to get the magnitude of the magnetic field in nanoteslas is 6 | the magnitude of the 3D vector that our `x` `y` `z` values describe. As you might remember from 7 | school this is simply: 8 | 9 | ``` rust 10 | use libm::sqrtf; 11 | let magnitude = sqrtf(x * x + y * y + z * z); 12 | ``` 13 | 14 | [`magnetic_field()`]: https://docs.rs/lsm303agr/1.1.0/lsm303agr/struct.Lsm303agr.html#method.magnetic_field 15 | 16 | Rust does not have floating-point math functions such as `sqrtf()` in `core`, so our `no_std` 17 | program has to get an implementation from somewhere. We use the [libm] crate for this. 18 | 19 | [libm]: https://crates.io/crates/libm 20 | 21 | Putting all this together in a program (`examples/magnitude.rs`): 22 | 23 | ``` rust 24 | {{#include examples/magnitude.rs}} 25 | ``` 26 | 27 | Run this with `cargo run --example magnitude`. 28 | 29 | This program will report the magnitude (strength) of the magnetic field in nanotesla (`nT`) and 30 | milligauss (`mG`, where 1 `mG` = 100 `nT`). The magnitude of the Earth's magnetic field is in the 31 | range of `250 mG` to `650 mG` (the magnitude varies depending on your geographical location) so you 32 | ideally would see a value vaguely in that range. Your value will likely be off quite a bit because 33 | the sensor has not been calibrated: see [appendix 3] for calibration. With calibration, I see a 34 | magnitude of around `340 mG`. 35 | 36 | [appendix 3]: ../appendix/3-mag-calibration/index.html 37 | 38 | Some questions: 39 | 40 | - Without moving the board, what value do you see? Do you always see the same value? 41 | 42 | - If you rotate the board, does the magnitude change? Should it change? 43 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/using-a-driver.md: -------------------------------------------------------------------------------- 1 | # Using a driver 2 | 3 | As we already discussed in chapter 5 `embedded-hal` provides abstractions 4 | which can be used to write platform independent code that can interact with 5 | hardware. In fact all the methods we have used to interact with hardware 6 | in chapter 7 and up until now in chapter 8 were from traits, defined by `embedded-hal`. 7 | Now we'll make actual use of the traits `embedded-hal` provides for the first time. 8 | 9 | It would be pointless to implement a driver for our LSM303AGR for every platform 10 | embedded Rust supports (and new ones that might eventually pop up). To avoid this a driver 11 | can be written that consumes generic types that implement `embedded-hal` traits in order to provide 12 | a platform agnostic version of a driver. Luckily for us this has already been done in the 13 | [`lsm303agr`] crate. Hence reading the actual accelerometer and magnetometer values will now 14 | be basically a plug and play experience (plus reading a bit of documentation). In fact the `crates.io` 15 | page already provides us with everything we need to know in order to read accelerometer data but using a Raspberry Pi. We'll 16 | just have to adapt it to our chip: 17 | 18 | [`lsm303agr`]: https://crates.io/crates/lsm303agr 19 | 20 | Take a look at the linked page for the Raspberry Pi Linux sample code. 21 | 22 | Because we already know how to create an instance of an object that implements the 23 | [`embedded_hal::blocking::i2c`] traits from the [previous page](read-a-single-register.md), adapting 24 | the sample code is straightforward (`examples/show-accel.rs`): 25 | 26 | [`embedded_hal::blocking::i2c`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/i2c/index.html 27 | 28 | ```rust 29 | {{#include examples/show-accel.rs}} 30 | ``` 31 | 32 | Just like the last snippet you should just be able to try this out like this: 33 | ```console 34 | $ cargo embed --example show-accel 35 | ``` 36 | 37 | Furthermore if you (physically) move around your micro:bit a little you should see the 38 | acceleration numbers that are being printed change. 39 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/gravity-is-up.md: -------------------------------------------------------------------------------- 1 | # Gravity is up? 2 | 3 | What's the first thing we'll do? 4 | 5 | Perform a sanity check! 6 | 7 | You should already be able to write a program that continuously prints the accelerometer data on the 8 | RTT console from the [I2C chapter](../12-i2c/index.md). Mine is in `examples/show-accel.rs`. Do you 9 | observe something interesting even when holding the board parallel to the floor with the back side 10 | facing up? (Remember that the accelerometer is mounted on the back of the board, so holding it 11 | upside-down like this makes the Z axis point up.) 12 | 13 | What you should see when holding the board like this is that both the X and Y values are rather 14 | close to 0, while the Z value is at around 1000. Which is weird: the board is not moving, yet its 15 | acceleration is non-zero. What's going on? This must be related to the gravity, right? Because the 16 | acceleration of gravity is `1 g` (aha, `1 g` = -1000 from the accelerometer). But the gravity pulls 17 | objects downwards so the acceleration along the Z axis should be positive, not negative. 18 | 19 | Did the program get the Z axis backwards? Nope, you can test rotating the board to align the gravity 20 | to the X or Y axis but the acceleration measured by the accelerometer is always pointing up. 21 | 22 | What happens here is that the accelerometer is measuring the *proper acceleration* of the board, not 23 | the acceleration *you* are observing. This proper acceleration is the acceleration of the board as 24 | seen from an observer that's in free fall. An observer that's in free fall is moving toward the 25 | center of the Earth with an acceleration of `1g`; from its point of view the board is actually 26 | moving upwards (away from the center of the Earth) with an acceleration of `1g`. And that's why the 27 | proper acceleration is pointing up. This also means that if the board was in free fall, the 28 | accelerometer would report a proper acceleration of zero. Please, don't try that at home. Or do, if 29 | you're willing to risk your MB2 by dropping it. 30 | 31 | Yes, physics is hard. Let's move on. 32 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/debouncing.md: -------------------------------------------------------------------------------- 1 | ## Debouncing 2 | 3 | As I mentioned in the last section, hardware can be a little… special. This is definitely the case 4 | for the buttons on the MB2, and really for almost any pushbutton or switch in almost any system. If 5 | you are seeing several interrupts for a single keypress, it is probably the result of what is known 6 | as switch "bouncing". This is literally what the name implies: as the electrical contacts of the 7 | switch come together, they may bounce apart and then recontact several times rather quickly before 8 | establishing a solid connection. Unfortunately, our microprocessor is *very* fast by mechanical 9 | standards: each one of these bounces makes a new interrupt. 10 | 11 | To "debounce" the switch, you need to *not* process button press interrupts for a short time after 12 | you receive one. 50-100ms is typically a good debounce interval. Debounce timing seems hard: you 13 | definitely don't want to spin in an interrupt handler, and yet it would be hard to deal with this in 14 | the main program. 15 | 16 | The solution comes through another form of hardware concurrency: the `TIMER` peripheral we have used 17 | a bunch already. You can set the timer when a "good" button interrupt is received, and not respond 18 | to further interrupts for that button until the timer peripheral has counted enough time off. The 19 | timers in `nrf-hal` come configured with a 32-bit count value and a "tick rate" of 1 MHz: a million 20 | ticks per second. For a 100ms debounce, just let the timer count off 100,000 ticks. Anytime the 21 | button interrupt handler sees that the timer is running, it can just do nothing. 22 | 23 | The implementation of all this can be seen in the next example (`examples/count-debounce.rs`). When 24 | you run the example you should see one count per button press. 25 | 26 | ```rust 27 | {{#include examples/count-debounce.rs}} 28 | ``` 29 | 30 | **NOTE** The buttons on the MB2 are a little fiddly: it's pretty easy to push one down enough to 31 | feel a "click" but not enough to actually make contact with the switch. I recommend using a 32 | fingernail to press the button when testing. 33 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/src/led.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Direction{ 3 | North, 4 | NorthEast, 5 | East, 6 | SouthEast, 7 | South, 8 | SouthWest, 9 | West, 10 | NorthWest, 11 | } 12 | 13 | const NORTH: [[u8; 5]; 5] = [ 14 | [0, 0, 1, 0, 0], 15 | [0, 1, 1, 1, 0], 16 | [1, 0, 1, 0, 1], 17 | [0, 0, 1, 0, 0], 18 | [0, 0, 1, 0, 0], 19 | ]; 20 | 21 | const NORTH_WEST: [[u8; 5]; 5] = [ 22 | [1, 1, 1, 0, 0], 23 | [1, 1, 0, 0, 0], 24 | [1, 0, 1, 0, 0], 25 | [0, 0, 0, 1, 0], 26 | [0, 0, 0, 0, 1], 27 | ]; 28 | 29 | const WEST: [[u8; 5]; 5] = [ 30 | [0, 0, 1, 0, 0], 31 | [0, 1, 0, 0, 0], 32 | [1, 1, 1, 1, 1], 33 | [0, 1, 0, 0, 0], 34 | [0, 0, 1, 0, 0], 35 | ]; 36 | 37 | const SOUTH_WEST: [[u8; 5]; 5] = [ 38 | [0, 0, 0, 0, 1], 39 | [0, 0, 0, 1, 0], 40 | [1, 0, 1, 0, 0], 41 | [1, 1, 0, 0, 0], 42 | [1, 1, 1, 0, 0], 43 | ]; 44 | 45 | const SOUTH: [[u8; 5]; 5] = [ 46 | [0, 0, 1, 0, 0], 47 | [0, 0, 1, 0, 0], 48 | [1, 0, 1, 0, 1], 49 | [0, 1, 1, 1, 0], 50 | [0, 0, 1, 0, 0], 51 | ]; 52 | 53 | const SOUTH_EAST: [[u8; 5]; 5] = [ 54 | [1, 0, 0, 0, 0], 55 | [0, 1, 0, 0, 0], 56 | [0, 0, 1, 0, 1], 57 | [0, 0, 0, 1, 1], 58 | [0, 0, 1, 1, 1], 59 | ]; 60 | 61 | const EAST: [[u8; 5]; 5] = [ 62 | [0, 0, 1, 0, 0], 63 | [0, 0, 0, 1, 0], 64 | [1, 1, 1, 1, 1], 65 | [0, 0, 0, 1, 0], 66 | [0, 0, 1, 0, 0], 67 | ]; 68 | 69 | const NORTH_EAST: [[u8; 5]; 5] = [ 70 | [0, 0, 1, 1, 1], 71 | [0, 0, 0, 1, 1], 72 | [0, 0, 1, 0, 1], 73 | [0, 1, 0, 0, 0], 74 | [1, 0, 0, 0, 0], 75 | ]; 76 | 77 | pub fn direction_to_led(direction: Direction) -> [[u8; 5]; 5] { 78 | match direction { 79 | Direction::North => NORTH, 80 | Direction::NorthEast => NORTH_EAST, 81 | Direction::East => EAST, 82 | Direction::SouthEast => SOUTH_EAST, 83 | Direction::South => SOUTH, 84 | Direction::SouthWest => SOUTH_WEST, 85 | Direction::West => WEST, 86 | Direction::NorthWest => NORTH_WEST, 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use panic_rtt_target as _; 7 | use rtt_target::{rprintln, rtt_init_print}; 8 | 9 | use embedded_hal::delay::DelayNs; 10 | use microbit::{ 11 | display::blocking::Display, 12 | hal::{twim, Timer}, 13 | pac::twim0::frequency::FREQUENCY_A, 14 | }; 15 | 16 | use lsm303agr::{AccelMode, AccelOutputDataRate, Lsm303agr, MagMode, MagOutputDataRate}; 17 | 18 | use mag_cal::{calc_calibration, calibrated_measurement, Measurement}; 19 | 20 | #[entry] 21 | fn main() -> ! { 22 | rtt_init_print!(); 23 | let board = microbit::Board::take().unwrap(); 24 | 25 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 26 | 27 | let mut timer0 = Timer::new(board.TIMER0); 28 | let mut display = Display::new(board.display_pins); 29 | 30 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 31 | sensor.init().unwrap(); 32 | sensor 33 | .set_mag_mode_and_odr( 34 | &mut timer0, 35 | MagMode::HighResolution, 36 | MagOutputDataRate::Hz10, 37 | ) 38 | .unwrap(); 39 | sensor 40 | .set_accel_mode_and_odr( 41 | &mut timer0, 42 | AccelMode::HighResolution, 43 | AccelOutputDataRate::Hz10, 44 | ) 45 | .unwrap(); 46 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 47 | 48 | let calibration = calc_calibration(&mut sensor, &mut display, &mut timer0); 49 | rprintln!("Calibration: {:?}", calibration); 50 | rprintln!("Calibration done, entering busy loop"); 51 | loop { 52 | while !sensor.mag_status().unwrap().xyz_new_data() { 53 | timer0.delay_ms(1u32); 54 | } 55 | let raw_data = Measurement::new(sensor.magnetic_field().unwrap().xyz_nt()); 56 | let cal_data = calibrated_measurement(raw_data, &calibration); 57 | rprintln!( 58 | "raw (x {} y {} z {}); cal (x {} y {} z {})", 59 | raw_data.x, 60 | raw_data.y, 61 | raw_data.z, 62 | cal_data.x, 63 | cal_data.y, 64 | cal_data.z, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/templates/compass.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use embedded_hal::delay::DelayNs; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | // You'll find these useful ;-). 11 | use core::f32::consts::PI; 12 | use libm::{atan2f, floorf}; 13 | 14 | use microbit::{ 15 | display::blocking::Display, 16 | hal::{Timer, twim}, 17 | pac::twim0::frequency::FREQUENCY_A, 18 | }; 19 | 20 | use lsm303agr::{Lsm303agr, MagMode, MagOutputDataRate}; 21 | 22 | #[entry] 23 | fn main() -> ! { 24 | rtt_init_print!(); 25 | let board = microbit::Board::take().unwrap(); 26 | 27 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 28 | 29 | let mut timer0 = Timer::new(board.TIMER0); 30 | let mut display = Display::new(board.display_pins); 31 | 32 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 33 | sensor.init().unwrap(); 34 | sensor.set_mag_mode_and_odr( 35 | &mut timer0, 36 | MagMode::HighResolution, 37 | MagOutputDataRate::Hz10, 38 | ).unwrap(); 39 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 40 | 41 | let mut leds = [[0u8; 5]; 5]; 42 | 43 | // Indexes of the 16 LEDs to be used in the display, and their 44 | // compass directions. 45 | #[rustfmt::skip] 46 | let indices = [ 47 | (2, 0) /* W */, (3, 0) /* W-SW */, (3, 1) /* SW */, (4, 1) /* S-SW */, 48 | (4, 2) /* S */, (4, 3) /* S-SE */, (3, 3) /* SE */, (3, 4) /* E-SE */, 49 | (2, 4) /* E */, (1, 4) /* E-NE */, (1, 3) /* NE */, (0, 3) /* N-NE */, 50 | (0, 2) /* N */, (0, 1) /* N-NW */, (1, 1) /* NW */, (1, 0) /* W-NW */, 51 | ]; 52 | 53 | loop { 54 | // Measure the magnetic field. 55 | let (x, y) = todo!(); 56 | 57 | // Get an angle between -180° and 180° from the x axis. 58 | let theta = atan2f(y as f32, x as f32); 59 | 60 | // Figure out what LED index to blink. 61 | let index = todo!(); 62 | 63 | // Blink the given LED. 64 | let (r, c) = indices[index]; 65 | leds[r][c] = 255u8; 66 | display.show(&mut timer0, leds, 50); 67 | leds[r][c] = 0u8; 68 | display.show(&mut timer0, leds, 50); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /mdbook/src/14-punch-o-meter/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | const TICKS_PER_SEC: u32 = 400; 6 | const THRESHOLD: f32 = 1.5; 7 | 8 | use cortex_m::asm::nop; 9 | use cortex_m_rt::entry; 10 | use panic_rtt_target as _; 11 | use rtt_target::{rprintln, rtt_init_print}; 12 | 13 | use microbit::{ 14 | hal::{twim, Timer}, 15 | pac::twim0::frequency::FREQUENCY_A, 16 | }; 17 | 18 | use lsm303agr::{AccelMode, AccelOutputDataRate, AccelScale, Lsm303agr}; 19 | 20 | #[entry] 21 | fn main() -> ! { 22 | rtt_init_print!(); 23 | let board = microbit::Board::take().unwrap(); 24 | 25 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 26 | 27 | let mut delay = Timer::new(board.TIMER0); 28 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 29 | sensor.init().unwrap(); 30 | sensor 31 | .set_accel_mode_and_odr(&mut delay, AccelMode::Normal, AccelOutputDataRate::Hz400) 32 | .unwrap(); 33 | // Allow the sensor to measure up to 16 G since human punches 34 | // can actually be quite fast 35 | sensor.set_accel_scale(AccelScale::G16).unwrap(); 36 | 37 | let mut max_g = 0.; 38 | let mut countdown_ticks = None; 39 | 40 | loop { 41 | while !sensor.accel_status().unwrap().xyz_new_data() { 42 | nop(); 43 | } 44 | // x acceleration in g 45 | let (x, _, _) = sensor.acceleration().unwrap().xyz_mg(); 46 | let g_x = x as f32 / 1000.0; 47 | 48 | if let Some(ticks) = countdown_ticks { 49 | if ticks > 0 { 50 | // countdown isn't done yet 51 | if g_x > max_g { 52 | max_g = g_x; 53 | } 54 | countdown_ticks = Some(ticks - 1); 55 | } else { 56 | // Countdown is done: report max value 57 | rprintln!("Max acceleration: {}g", max_g); 58 | 59 | // Reset 60 | max_g = 0.; 61 | countdown_ticks = None; 62 | } 63 | } else { 64 | // If acceleration goes above a threshold, we start measuring 65 | if g_x > THRESHOLD { 66 | rprintln!("START!"); 67 | 68 | max_g = g_x; 69 | countdown_ticks = Some(TICKS_PER_SEC); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /mdbook/src/15-interrupts/examples/count-debounce.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::sync::atomic::{ 5 | AtomicUsize, 6 | Ordering::{AcqRel, Acquire}, 7 | }; 8 | 9 | use cortex_m::asm; 10 | use cortex_m_rt::entry; 11 | use critical_section_lock_mut::LockMut; 12 | use panic_rtt_target as _; 13 | use rtt_target::{rprintln, rtt_init_print}; 14 | 15 | use microbit::{ 16 | hal::{ 17 | self, gpiote, 18 | pac::{self, interrupt}, 19 | }, 20 | Board, 21 | }; 22 | 23 | static COUNTER: AtomicUsize = AtomicUsize::new(0); 24 | static GPIOTE_PERIPHERAL: LockMut = LockMut::new(); 25 | static DEBOUNCE_TIMER: LockMut> = LockMut::new(); 26 | 27 | // 100ms at 1MHz count rate. 28 | const DEBOUNCE_TIME: u32 = 100 * 1_000_000 / 1000; 29 | 30 | #[interrupt] 31 | fn GPIOTE() { 32 | DEBOUNCE_TIMER.with_lock(|debounce_timer| { 33 | if debounce_timer.read() == 0 { 34 | let _ = COUNTER.fetch_add(1, AcqRel); 35 | debounce_timer.start(DEBOUNCE_TIME); 36 | } 37 | }); 38 | GPIOTE_PERIPHERAL.with_lock(|gpiote| { 39 | gpiote.channel0().reset_events(); 40 | }); 41 | } 42 | 43 | #[entry] 44 | fn main() -> ! { 45 | rtt_init_print!(); 46 | let board = Board::take().unwrap(); 47 | let button_a = board.buttons.button_a.into_floating_input(); 48 | 49 | // Set up the GPIOTE to generate an interrupt when Button A is pressed (GPIO 50 | // wire goes low). 51 | let gpiote = gpiote::Gpiote::new(board.GPIOTE); 52 | let channel = gpiote.channel0(); 53 | channel 54 | .input_pin(&button_a.degrade()) 55 | .hi_to_lo() 56 | .enable_interrupt(); 57 | channel.reset_events(); 58 | GPIOTE_PERIPHERAL.init(gpiote); 59 | 60 | // Set up the debounce timer. 61 | let mut debounce_timer = hal::Timer::new(board.TIMER0); 62 | debounce_timer.disable_interrupt(); 63 | debounce_timer.reset_event(); 64 | DEBOUNCE_TIMER.init(debounce_timer); 65 | 66 | // Set up the NVIC to handle interrupts. 67 | unsafe { pac::NVIC::unmask(pac::Interrupt::GPIOTE) }; 68 | pac::NVIC::unpend(pac::Interrupt::GPIOTE); 69 | 70 | let mut cur_count = 0; 71 | loop { 72 | // "wait for interrupt": CPU goes to sleep until an interrupt. 73 | asm::wfi(); 74 | let count = COUNTER.load(Acquire); 75 | if count > cur_count { 76 | rprintln!("ouch {}", count); 77 | cur_count = count; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/embedded-setup.md: -------------------------------------------------------------------------------- 1 | # Embedded Setup 2 | 3 | Let's take a look at our first program to compile. Check the `examples/init.rs` file: 4 | 5 | ``` rust 6 | {{#include examples/init.rs}} 7 | ``` 8 | 9 | Microcontroller programs are different from standard programs in two aspects: `#![no_std]` and 10 | `#![no_main]`. 11 | 12 | The `no_std` attribute says that this program won't use the `std` crate, which assumes an underlying 13 | OS; the program will instead use the `core` crate, a subset of `std` that can run on bare metal 14 | systems (that is, systems without OS abstractions like files and sockets). 15 | 16 | The `no_main` attribute says that this program won't use the standard `main` interface, which is 17 | tailored for command line applications that receive arguments. Instead of the standard `main` we'll 18 | use the `entry` attribute from the [`cortex-m-rt`] crate to define a custom entry point. In this 19 | program we have named the entry point `main`, but any other name could have been used. The entry 20 | point function must have signature `fn() -> !`; this type indicates that the function can't return. 21 | This means that the program never terminates by returning from `main`: if the compiler detects that 22 | this would be possible it will refuse to compile your program. 23 | 24 | [`cortex-m-rt`]: https://crates.io/crates/cortex-m-rt 25 | 26 | If you are a careful observer, you'll also notice there is a possibly-hidden `.cargo` directory in 27 | the Cargo project as well. This directory contains a Cargo configuration file `.cargo/config.toml`. 28 | 29 | ```toml 30 | {{#include .cargo/config.toml}} 31 | ``` 32 | 33 | This file tweaks the linking process to tailor the memory layout of the program to the requirements 34 | of the target device. This modified linking process is a requirement of the `cortex-m-rt` 35 | crate. The `.cargo/config.toml` file also tells Cargo how to build and run code on our MB2. 36 | 37 | There is also an `Embed.toml` file here: 38 | 39 | ```toml 40 | {{#include Embed.toml}} 41 | ``` 42 | 43 | This file tells `cargo-embed` that: 44 | 45 | - We are working with an NRF52833. 46 | - We want to halt the chip after flashing it, so our program stops before `main`. 47 | - We want to disable RTT. RTT is a protocol that allows the chip to send text to a debugger. 48 | You have already seen RTT in action: it was the protocol that sent "Hello World" in chapter 3. 49 | - We want to enable GDB. This will be required for the debugging procedure. 50 | 51 | Now that we've seen what's going on, let's start by building this program. 52 | -------------------------------------------------------------------------------- /mdbook/src/appendix/1-general-troubleshooting/README.md: -------------------------------------------------------------------------------- 1 | # General troubleshooting 2 | 3 | ## `cargo-embed` problems 4 | 5 | Most `cargo-embed` problems are related to not having installed the `udev` rules properly on 6 | Linux, so make sure you got that right. 7 | 8 | If you are stuck, you can open an issue in the [`discovery` issue tracker] or visit the [Rust 9 | Embedded matrix channel] or the [probe-rs matrix channel] and ask for help there. 10 | 11 | [`discovery` issue tracker]: https://github.com/rust-embedded/discovery-mb2/issues 12 | [Rust Embedded matrix channel]: https://matrix.to/#/#rust-embedded:matrix.org 13 | [probe-rs matrix channel]: https://matrix.to/#/#probe-rs:matrix.org 14 | 15 | ## Cargo problems 16 | 17 | ### "can't find crate for `core`" 18 | 19 | *Symptoms:* 20 | 21 | ``` 22 | Compiling volatile-register v0.1.2 23 | Compiling rlibc v1.0.0 24 | Compiling r0 v0.1.0 25 | error[E0463]: can't find crate for `core` 26 | 27 | error: aborting due to previous error 28 | 29 | error[E0463]: can't find crate for `core` 30 | 31 | error: aborting due to previous error 32 | 33 | error[E0463]: can't find crate for `core` 34 | 35 | error: aborting due to previous error 36 | 37 | Build failed, waiting for other jobs to finish... 38 | Build failed, waiting for other jobs to finish... 39 | error: Could not compile `r0`. 40 | 41 | To learn more, run the command again with --verbose. 42 | ``` 43 | 44 | *Cause:* 45 | 46 | You forgot to install the proper target for your microcontroller `thumbv7em-none-eabihf`. 47 | 48 | *Fix:* 49 | 50 | Install the proper target. 51 | 52 | ``` console 53 | $ rustup target add thumbv7em-none-eabihf 54 | ``` 55 | 56 | ### Unable to flash the device: `No loadable segments were found in the ELF file` 57 | 58 | *Symptoms:* 59 | ```console 60 | > cargo embed 61 | Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s 62 | Config default 63 | Target /home/user/embedded/target/thumbv7em-none-eabihf/debug/examples/init 64 | WARN probe_rs::flashing::loader: No loadable segments were found in the ELF file. 65 | Error No loadable segments were found in the ELF file. 66 | ``` 67 | 68 | *Cause:* 69 | 70 | Cargo needs to know how to build and link the program to the requirements of the target device. 71 | You therefore need to set the correct parameters in the `.cargo/config.toml` file. 72 | 73 | *Fix:* 74 | 75 | Add a `.cargo/config.toml` file with the correct parameters: 76 | ```toml 77 | {{#include ../../05-meet-your-software/.cargo/config.toml}} 78 | ``` 79 | 80 | See [Embedded Setup](../../05-meet-your-software/embedded-setup.md) for further details. 81 | -------------------------------------------------------------------------------- /mdbook/src/16-snake-game/src/game/snake.rs: -------------------------------------------------------------------------------- 1 | use super::{Coords, Direction, FnvIndexSet, Turn}; 2 | 3 | use heapless::spsc::Queue; 4 | 5 | pub struct Snake { 6 | /// Coordinates of the snake's head. 7 | pub head: Coords, 8 | /// Queue of coordinates of the rest of the snake's body. The end of the tail is 9 | /// at the front. 10 | pub tail: Queue, 11 | /// A set containing all coordinates currently occupied by the snake (for fast 12 | /// collision checking). 13 | pub coord_set: FnvIndexSet, 14 | /// The direction the snake is currently moving in. 15 | pub direction: Direction, 16 | } 17 | 18 | impl Snake { 19 | pub fn make_snake() -> Self { 20 | let head = Coords { row: 2, col: 2 }; 21 | let initial_tail = Coords { row: 2, col: 1 }; 22 | let mut tail = Queue::new(); 23 | tail.enqueue(initial_tail).unwrap(); 24 | let mut coord_set: FnvIndexSet = FnvIndexSet::new(); 25 | coord_set.insert(head).unwrap(); 26 | coord_set.insert(initial_tail).unwrap(); 27 | Self { 28 | head, 29 | tail, 30 | coord_set, 31 | direction: Direction::Right, 32 | } 33 | } 34 | 35 | /// Move the snake onto the tile at the given coordinates. If `extend` is false, 36 | /// the snake's tail vacates the rearmost tile. 37 | pub fn move_snake(&mut self, coords: Coords, extend: bool) { 38 | // Location of head becomes front of tail 39 | self.tail.enqueue(self.head).unwrap(); 40 | // Head moves to new coords 41 | self.head = coords; 42 | self.coord_set.insert(coords).unwrap(); 43 | if !extend { 44 | let back = self.tail.dequeue().unwrap(); 45 | self.coord_set.remove(&back); 46 | } 47 | } 48 | 49 | fn turn_right(&mut self) { 50 | self.direction = match self.direction { 51 | Direction::Up => Direction::Right, 52 | Direction::Down => Direction::Left, 53 | Direction::Left => Direction::Up, 54 | Direction::Right => Direction::Down, 55 | } 56 | } 57 | 58 | fn turn_left(&mut self) { 59 | self.direction = match self.direction { 60 | Direction::Up => Direction::Left, 61 | Direction::Down => Direction::Right, 62 | Direction::Left => Direction::Down, 63 | Direction::Right => Direction::Up, 64 | } 65 | } 66 | 67 | pub fn turn(&mut self, direction: Turn) { 68 | match direction { 69 | Turn::Left => self.turn_left(), 70 | Turn::Right => self.turn_right(), 71 | Turn::None => (), 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/flash-it.md: -------------------------------------------------------------------------------- 1 | # Flash it 2 | 3 | Flashing is the process of moving our program into the microcontroller's persistent memory. Once 4 | flashed, the microcontroller will execute the flashed program every time it is powered on. 5 | 6 | Our program will be the *only* program in the microcontroller memory. By this I mean that there's 7 | nothing else running on the microcontroller: no OS, no "daemon", nothing. Our program has full 8 | control over the device. 9 | 10 | Flashing the binary itself is quite simple, thanks to `cargo embed`. 11 | 12 | Before executing that command though, let's look into what it actually does. If you look at the side 13 | of your micro:bit with the USB connector facing upwards, you will notice that there are actually 14 | three black squares on there. The biggest one is a speaker. Another is our MCU we already talked 15 | about… but what purpose does the remaining one serve? This chip is *another* MCU, an NRF52820 almost 16 | as powerful as the NRF52833 we will be programming! This chip has three main purposes: 17 | 18 | 1. Enable power and reset control of our NRF52833 MCU from the USB connector. 19 | 2. Provide a serial to USB bridge for our MCU (we will look into that in a later chapter). 20 | 3. Provide an interface for programming and debugging our NRF52833 (this is the relevant purpose for 21 | now). 22 | 23 | This chip acts as sort of bridge between our computer (to which it is connected via USB) and the MCU 24 | (to which it is connected via traces and communicates with using the SWD protocol). This bridge 25 | enables us to flash new binaries on to the MCU, inspect a program's state via a debugger and do 26 | other useful things. 27 | 28 | So lets flash it! 29 | 30 | ```console 31 | $ cargo embed --example init 32 | (...) 33 | Erasing sectors ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 4.21KiB/s (eta 0s ) 34 | Programming pages ✔ [00:00:00] [####################################################################################################################################################] 2.00KiB/ 2.00KiB @ 2.71KiB/s (eta 0s ) 35 | Finished flashing in 0.608s 36 | ``` 37 | 38 | You will notice that `cargo-embed` does not exit after outputting the last line. This is intended: 39 | you should not close `cargo-embed`, since we need it in this state for the next step — debugging it! 40 | Furthermore, you will have noticed that `cargo build` and `cargo embed` are actually passed the same 41 | flags. This is because `cargo embed` actually executes the build and then flashes the resulting 42 | binary on to the chip. This means you can leave out the `cargo build` step in the future if you want 43 | to flash your code right away. 44 | -------------------------------------------------------------------------------- /mdbook/src/appendix/3-mag-calibration/README.md: -------------------------------------------------------------------------------- 1 | # Magnetometer Calibration 2 | 3 | One very important thing to do before using a sensor and trying to develop an application using it 4 | is verifying that it's output is actually correct. If this does not happen to be the case we need 5 | to calibrate the sensor. Alternatively the sensor could be broken: health-checking sensors before 6 | and during use is a really good idea when possible. 7 | 8 | In my case, on two different MB2s the LSM303AGR's magnetometer without calibration is quite a bit 9 | off. (I also have one where the z-axis appears to be broken; the manufacturer has some extra 10 | hardware and a process to help detect this, but we won't deal with that complexity here.) 11 | 12 | There is a manufacturer-specified procedure for calibrating the magnetometer. The calibration 13 | involves quite a bit of math (matrices) so we won't cover it in detail here: this [Design Note] 14 | describes the procedure if you are interested in the details. 15 | 16 | [Design Note]: https://www.st.com/resource/en/design_tip/dt0103-compensating-for-magnetometer-installation-error-and-hardiron-effects-using-accelerometerassisted-2d-calibration-stmicroelectronics.pdf 17 | 18 | Luckily for us, the CODAL group that built the original C++ software for the micro:bit already 19 | implemented the manufacturer calibration mechanism (or something similar) in C++ over [here]. 20 | 21 | [here]: https://github.com/lancaster-university/codal-microbit-v2/blob/006abf5566774fbcf674c0c7df27e8a9d20013de/source/MicroBitCompassCalibrator.cpp 22 | 23 | You can find a translation of this C++ calibration to Rust in `src/lib.rs`. Note that this is a 24 | translation from Matlab to C++ to Rust, and that it makes some interesting choices. In particular, 25 | when reading calibrated values *the axes are flipped* so that viewed from the top with the USB 26 | connector forward the X, Y and Z axes of the calibrated value are in "standard" (right, forward, up) 27 | orientation. 28 | 29 | The usage of this calibrator is demonstrated in `src/main.rs` here. 30 | 31 | The way the user does the calibration is shown in this video from the C++ version. (Ignore the 32 | initial printing — the calibration starts about halfway through.) 33 | 34 |

35 |

37 | 38 | You have to tilt the micro:bit until all the LEDs on the LED matrix light up. The blinking cursor 39 | shows the current target LED. 40 | 41 | Note that the calibration matrix is printed by the demo program. This matrix can be hard-coded into 42 | a program such as the [chapter 12] compass program (or stored in flash somewhere somehow) to avoid 43 | the need to recalibrate every time the user runs the program. 44 | 45 | [chapter 13]: ../../13-led-compass/index.html 46 | -------------------------------------------------------------------------------- /mdbook/src/07-led-roulette/my-solution.md: -------------------------------------------------------------------------------- 1 | # My solution 2 | 3 | What solution did you come up with? 4 | 5 | Here's mine. It's probably one of the simplest (but of course not most beautiful) ways to generate 6 | the required matrix: 7 | 8 | ``` rust 9 | {{#include src/main.rs}} 10 | ``` 11 | 12 | One more thing! Check that your solution also works when compiled in "release" mode: 13 | 14 | ``` console 15 | $ cargo embed --release 16 | ``` 17 | 18 | If you want to debug your "release" mode binary you'll have to use a different GDB command: 19 | 20 | ``` console 21 | $ gdb ../../../target/thumbv7em-none-eabihf/release/led-roulette 22 | ``` 23 | 24 | The Rust compiler modifies the machine instructions generated in a release build (sometimes by a 25 | lot) in order to try to make the code faster or smaller. Unfortunately, GDB has a hard time figuring 26 | out what is going on after this. As a result, debugging release builds with GDB can be difficult. 27 | 28 | Binary size is something we should always keep an eye on! How big is your solution? You can check 29 | that using the `size` command on the release binary: 30 | 31 | ``` console 32 | $ cargo size --release -- -A 33 | Finished release [optimized + debuginfo] target(s) in 0.02s 34 | led-roulette : 35 | section size addr 36 | .vector_table 256 0x0 37 | .text 6332 0x100 38 | .rodata 648 0x19bc 39 | .data 0 0x20000000 40 | .bss 1076 0x20000000 41 | .uninit 0 0x20000434 42 | .debug_loc 9036 0x0 43 | .debug_abbrev 2754 0x0 44 | .debug_info 96460 0x0 45 | .debug_aranges 1120 0x0 46 | .debug_ranges 11520 0x0 47 | .debug_str 71325 0x0 48 | .debug_pubnames 32316 0x0 49 | .debug_pubtypes 29294 0x0 50 | .Arm.attributes 58 0x0 51 | .debug_frame 2108 0x0 52 | .debug_line 19303 0x0 53 | .comment 109 0x0 54 | Total 283715 55 | ``` 56 | 57 | Your numbers may differ somewhat depending on how your code is built: this is OK. 58 | 59 | Know how to read this output? The `text` section contains the program instructions. The `rodata` 60 | section contains read-only data stored with the program instructions. The `data` and `bss` sections 61 | contain variables statically allocated in RAM (`static` variables). If you remember the 62 | specification of the microcontroller on your micro:bit, you should notice that its flash memory is 63 | less than double the size of this extremely simple binary: can this be right? As we can see from 64 | the size statistics most of the binary is actually made up of debugging related sections. However, 65 | those are not flashed to the microcontroller at any time — after all they aren't relevant for the 66 | execution. 67 | -------------------------------------------------------------------------------- /mdbook/src/08-inputs-and-outputs/examples/blink-held.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use cortex_m_rt::entry; 5 | use embedded_hal::delay::DelayNs; 6 | use embedded_hal::digital::{InputPin, OutputPin}; 7 | use microbit::hal::timer::Timer; 8 | use microbit::{hal::gpio, Board}; 9 | use panic_rtt_target as _; 10 | use rtt_target::rtt_init_print; 11 | 12 | const ON_TICKS: u16 = 25; 13 | const OFF_TICKS: u16 = 75; 14 | 15 | #[derive(Clone, Copy)] 16 | enum Light { 17 | Lit(u16), 18 | Unlit(u16), 19 | } 20 | 21 | impl Light { 22 | fn flip(self) -> Self { 23 | match self { 24 | Light::Lit(_) => Light::Unlit(OFF_TICKS), 25 | Light::Unlit(_) => Light::Lit(ON_TICKS), 26 | } 27 | } 28 | 29 | fn tick_down(self) -> Self { 30 | match self { 31 | Light::Lit(ticks) => Light::Lit(ticks.max(1) - 1), 32 | Light::Unlit(ticks) => Light::Unlit(ticks.max(1) - 1), 33 | } 34 | } 35 | } 36 | 37 | #[derive(Clone, Copy)] 38 | enum Indicator { 39 | Off, 40 | Blinking(Light), 41 | } 42 | 43 | #[entry] 44 | fn main() -> ! { 45 | rtt_init_print!(); 46 | let board = Board::take().unwrap(); 47 | let mut timer = Timer::new(board.TIMER0); 48 | 49 | // Configure buttons 50 | let mut button_a = board.buttons.button_a; 51 | 52 | // Configure LED (top-left LED at row1, col1) 53 | let mut row1 = board 54 | .display_pins 55 | .row1 56 | .into_push_pull_output(gpio::Level::Low); 57 | let _col1 = board 58 | .display_pins 59 | .col1 60 | .into_push_pull_output(gpio::Level::Low); 61 | 62 | let mut state = Indicator::Off; 63 | loop { 64 | let button_pressed = button_a.is_low().unwrap(); 65 | match (button_pressed, state) { 66 | // Turn indicator off when no button. 67 | (false, _) => { 68 | row1.set_low().unwrap(); 69 | state = Indicator::Off; 70 | } 71 | // 72 | (true, Indicator::Off) => { 73 | row1.set_high().unwrap(); 74 | state = Indicator::Blinking(Light::Lit(ON_TICKS)); 75 | } 76 | (true, Indicator::Blinking(light)) => { 77 | match light { 78 | Light::Lit(0) | Light::Unlit(0) => { 79 | let light = light.flip(); 80 | match light { 81 | Light::Lit(_) => row1.set_high().unwrap(), 82 | Light::Unlit(_) => row1.set_low().unwrap(), 83 | } 84 | state = Indicator::Blinking(light); 85 | } 86 | Light::Lit(_) | Light::Unlit(_) => { 87 | state = Indicator::Blinking(light.tick_down()); 88 | } 89 | } 90 | } 91 | } 92 | timer.delay_ms(10_u32); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mdbook/src/README.md: -------------------------------------------------------------------------------- 1 | # `micro::bit v2 Embedded Discovery Book` 2 | 3 | > Discover the world of microcontrollers through [Rust]! 4 | 5 | [Rust]: https://www.rust-lang.org/ 6 | 7 | This book is an introductory course on microcontroller-based embedded systems that uses Rust as the 8 | teaching language rather than the usual C/C++. 9 | 10 | ## Scope 11 | 12 | The following topics will be covered (eventually, I hope): 13 | 14 | - How to write, build, flash and debug an "embedded" (Rust) program. 15 | 16 | - Functionality ("peripherals") commonly found in microcontrollers: Digital input and output, Pulse 17 | Width Modulation (PWM), Analog to Digital Converters (ADC), common communication protocols like 18 | Serial, I2C and SPI, etc. 19 | 20 | - Multitasking concepts: cooperative vs preemptive multitasking, interrupts, schedulers, etc. 21 | 22 | - Control systems concepts: sensors, calibration, digital filters, actuators, open loop control, 23 | closed loop control, etc. 24 | 25 | ## Approach 26 | 27 | - Beginner friendly. No previous experience with microcontrollers or embedded systems is required. 28 | 29 | - Hands on. Plenty of exercises to put the theory into practice. *You* will be doing most of the 30 | work here. 31 | 32 | - Tool centered. We'll make plenty use of tooling to ease development. "Real" debugging, with GDB, 33 | and logging will be introduced early on. Using LEDs as a debugging mechanism has no place here. 34 | 35 | ## Non-goals 36 | 37 | What's out of scope for this book: 38 | 39 | - Teaching Rust. There's plenty of material on that topic already. We'll focus on microcontrollers 40 | and embedded systems. 41 | 42 | - Being a comprehensive text about electric circuit theory or electronics. We'll just cover the 43 | minimum required to understand how some devices work. 44 | 45 | - Covering details such as linker scripts and the boot process. For example, we'll use existing tools 46 | to help get your code onto your board, but not go into detail about how those tools work. 47 | 48 | Also I don't intend to port this material to other development boards; this book will make exclusive 49 | use of the micro:bit development board. 50 | 51 | ## Reporting problems 52 | 53 | The source of this book is in [this repository]. If you encounter any typo or problem with the code 54 | report it on the [issue tracker]. 55 | 56 | [this repository]: https://github.com/rust-embedded/discovery-mb2 57 | [issue tracker]: https://github.com/rust-embedded/discovery-mb2/issues 58 | 59 | ## Other embedded Rust resources 60 | 61 | This Discovery book is just one of several embedded Rust resources provided by the 62 | [Embedded Working Group]. The full selection can be found at [The Embedded Rust Bookshelf]. This 63 | includes the list of [Frequently Asked Questions]. 64 | 65 | [Embedded Working Group]: https://github.com/rust-embedded/wg 66 | [The Embedded Rust Bookshelf]: https://docs.rust-embedded.org 67 | [Frequently Asked Questions]: https://docs.rust-embedded.org/faq.html 68 | -------------------------------------------------------------------------------- /mdbook/src/11-uart/send-a-single-byte.md: -------------------------------------------------------------------------------- 1 | # Send a single byte 2 | 3 | Our first task will be to send a single byte from the microcontroller to the computer over the 4 | serial connection. 5 | 6 | In order to do that we will use the following snippet (this one is already in 7 | `11-uart/examples/send-byte.rs`): 8 | 9 | ``` rust 10 | {{#include examples/send-byte.rs}} 11 | ``` 12 | 13 | You might notice that one of the libraries used here, the `serial_setup` module, is not from 14 | `crates.io`, but was written for this project. The purpose of `serial_setup` is to provide a nice 15 | wrapper around the UARTE peripheral. If you want, you can check out what exactly the module does, 16 | but it is not required to understand this chapter in general. 17 | 18 | We'll next discuss the initialization of UARTE. The UARTE is initialized with this piece of code: 19 | 20 | ```rs 21 | uarte::Uarte::new( 22 | board.UARTE0, 23 | board.uart.into(), 24 | Parity::EXCLUDED, 25 | Baudrate::BAUD115200, 26 | ); 27 | ``` 28 | 29 | This function takes ownership of the UARTE peripheral representation in Rust (`board.UARTE0`) and 30 | the TX/RX pins on the board (`board.uart.into()`) so nobody else can mess with either the UARTE 31 | peripheral or our pins while we are using them. After that we pass two configuration options to the 32 | constructor: the baud rate (that one should be familiar) as well as an option called "parity". Parity 33 | is a way to allow serial communication lines to check whether the data they received was corrupted 34 | during transmission. We don't want to use that here so we simply exclude it. Then we wrap it up in 35 | the `UartePort` type so we can use it. 36 | 37 | After the initialization, we send our `X` via the newly created uart instance. These serial 38 | functions are "blocking": they wait for the data to be sent before returning. This is not always 39 | what is wanted: the microcontroller can do a lot of work while waiting for the byte to go out on the 40 | wire. However, in our case it is convenient and we didn't have other work to do anyway. 41 | 42 | Last but not least, we `flush()` the serial port. This is because the UARTE may decide to buffer 43 | output until it has received a certain number of bytes to send. Calling `flush()` forces it to 44 | write the bytes it currently has right now instead of waiting for more. 45 | 46 | ## Testing it 47 | 48 | Before flashing this you should make sure to start your minicom/PuTTY as the data we receive via our 49 | serial communication is not backed up or anything: we have to view it live. Once your serial monitor 50 | is up you can flash the program just like in chapter 5: 51 | 52 | ``` 53 | $ cargo embed --example send-byte 54 | (...) 55 | ``` 56 | 57 | And after the flashing is finished, you should see the character `X` show up on your minicom/PuTTY 58 | terminal, congrats! 59 | 60 | If you missed it, you can hit the reset button on the back of the MB2. This will cause the program 61 | to start from the beginning and send an `X` again. 62 | -------------------------------------------------------------------------------- /mdbook/src/13-led-compass/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![no_main] 3 | #![no_std] 4 | 5 | use cortex_m_rt::entry; 6 | use embedded_hal::delay::DelayNs; 7 | use panic_rtt_target as _; 8 | use rtt_target::rtt_init_print; 9 | 10 | // You'll find these useful ;-). 11 | use core::f32::consts::PI; 12 | use libm::{atan2f, floorf}; 13 | 14 | use microbit::{ 15 | display::blocking::Display, 16 | hal::{twim, Timer}, 17 | pac::twim0::frequency::FREQUENCY_A, 18 | }; 19 | 20 | use lsm303agr::{Lsm303agr, MagMode, MagOutputDataRate}; 21 | 22 | #[entry] 23 | fn main() -> ! { 24 | rtt_init_print!(); 25 | let board = microbit::Board::take().unwrap(); 26 | 27 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 28 | 29 | let mut timer0 = Timer::new(board.TIMER0); 30 | let mut display = Display::new(board.display_pins); 31 | 32 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 33 | sensor.init().unwrap(); 34 | sensor 35 | .set_mag_mode_and_odr( 36 | &mut timer0, 37 | MagMode::HighResolution, 38 | MagOutputDataRate::Hz10, 39 | ) 40 | .unwrap(); 41 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 42 | 43 | let mut leds = [[0u8; 5]; 5]; 44 | 45 | // Indexes of the 16 LEDs to be used in the display, and their 46 | // compass directions. 47 | #[rustfmt::skip] 48 | let indices = [ 49 | (2, 0), /* W */ 50 | (3, 0), /* W-SW */ 51 | (3, 1), /* SW */ 52 | (4, 1), /* S-SW */ 53 | (4, 2), /* S */ 54 | (4, 3), /* S-SE */ 55 | (3, 3), /* SE */ 56 | (3, 4), /* E-SE */ 57 | (2, 4), /* E */ 58 | (1, 4), /* E-NE */ 59 | (1, 3), /* NE */ 60 | (0, 3), /* N-NE */ 61 | (0, 2), /* N */ 62 | (0, 1), /* N-NW */ 63 | (1, 1), /* NW */ 64 | (1, 0), /* W-NW */ 65 | ]; 66 | 67 | loop { 68 | while !sensor.mag_status().unwrap().xyz_new_data() { 69 | timer0.delay_ms(1u32); 70 | } 71 | let (x, y, _) = sensor.magnetic_field().unwrap().xyz_nt(); 72 | 73 | // Get an angle between -180° and 180° from the x axis. 74 | let theta = atan2f(y as f32, x as f32); 75 | 76 | // Cut the unit circle into thirty-two segments, 77 | // with pairs of adjacent segments corresponding to 78 | // each compass direction. 79 | let seg = floorf(16.0 * theta / PI) as i8; 80 | 81 | // Figure out what LED index to blink. 82 | let index = if seg >= 15 || seg <= -15 { 83 | 8 84 | } else if seg >= 0 { 85 | (seg / 2) as usize 86 | } else { 87 | ((31 + seg) / 2) as usize 88 | }; 89 | 90 | // Blink the given LED. 91 | let (r, c) = indices[index]; 92 | leds[r][c] = 255u8; 93 | display.show(&mut timer0, leds, 50); 94 | leds[r][c] = 0u8; 95 | display.show(&mut timer0, leds, 50); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | use core::str; 5 | 6 | use cortex_m_rt::entry; 7 | use embedded_hal::delay::DelayNs; 8 | use panic_rtt_target as _; 9 | use rtt_target::{rtt_init_print, rprintln}; 10 | 11 | use microbit::{ 12 | hal::uarte::{self, Baudrate, Parity}, 13 | hal::{twim, Timer}, 14 | pac::twim0::frequency::FREQUENCY_A, 15 | }; 16 | 17 | use core::fmt::Write; 18 | use heapless::Vec; 19 | use lsm303agr::{AccelMode, AccelOutputDataRate, Lsm303agr, MagMode, MagOutputDataRate}; 20 | 21 | use serial_setup::UartePort; 22 | 23 | #[entry] 24 | fn main() -> ! { 25 | rtt_init_print!(); 26 | let board = microbit::Board::take().unwrap(); 27 | 28 | let serial = uarte::Uarte::new( 29 | board.UARTE0, 30 | board.uart.into(), 31 | Parity::EXCLUDED, 32 | Baudrate::BAUD115200, 33 | ); 34 | let mut serial = UartePort::new(serial); 35 | 36 | let i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; 37 | let mut timer0 = Timer::new(board.TIMER0); 38 | 39 | let mut sensor = Lsm303agr::new_with_i2c(i2c); 40 | sensor.init().unwrap(); 41 | sensor 42 | .set_accel_mode_and_odr( 43 | &mut timer0, 44 | AccelMode::HighResolution, 45 | AccelOutputDataRate::Hz50, 46 | ) 47 | .unwrap(); 48 | sensor 49 | .set_mag_mode_and_odr( 50 | &mut timer0, 51 | MagMode::HighResolution, 52 | MagOutputDataRate::Hz50, 53 | ) 54 | .unwrap(); 55 | let mut sensor = sensor.into_mag_continuous().ok().unwrap(); 56 | let mut buffer: Vec = Vec::new(); 57 | rprintln!("setup complete"); 58 | 59 | loop { 60 | buffer.clear(); 61 | 62 | loop { 63 | let byte = serial.read().unwrap(); 64 | serial.write(byte).unwrap(); 65 | 66 | if byte == b'\r' { 67 | serial.write(b'\n').unwrap(); 68 | break; 69 | } 70 | 71 | if buffer.push(byte).is_err() { 72 | write!(serial, "error: buffer full\r\n").unwrap(); 73 | break; 74 | } 75 | } 76 | 77 | if str::from_utf8(&buffer).unwrap().trim() == "acc" { 78 | while !sensor.accel_status().unwrap().xyz_new_data() { 79 | timer0.delay_ms(1u32); 80 | } 81 | 82 | let (x, y, z) = sensor.acceleration().unwrap().xyz_mg(); 83 | write!(serial, "Accelerometer: x {} y {} z {}\r\n", x, y, z).unwrap(); 84 | } else if str::from_utf8(&buffer).unwrap().trim() == "mag" { 85 | while !sensor.mag_status().unwrap().xyz_new_data() { 86 | timer0.delay_ms(1u32); 87 | } 88 | 89 | let (x, y, z) = sensor.magnetic_field().unwrap().xyz_nt(); 90 | write!(serial, "Magnetometer: x {} y {} z {}\r\n", x, y, z).unwrap(); 91 | } else { 92 | write!(serial, "error: command not detected\r\n").unwrap(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/README.md: -------------------------------------------------------------------------------- 1 | # I2C 2 | 3 | We just saw the UART serial communication format. UART serial is widely used because it is simple 4 | and has been around almost forever. (Remember how the host device is called a "tty" for "TeleTYpe"? 5 | Yeah, that.) This ubiquity and simplicity makes it a popular choice for simple communications. 6 | 7 | Because of hardware limitations on line length *vs* signal quality and because of difficulty of 8 | accurate decoding, UART serial typically caps out at about 115200 baud under ideal conditions. A 9 | UART serial port has both low bandwidth (11.5KB/s) and high latency (87µs/byte). 10 | 11 | UART serial is point-to-point: there is no way to connect three or more devices to the same wire, 12 | and each wire requires a dedicated hardware device on each end. 13 | 14 | The good news (and the bad news) is that there are *plenty* of other hardware-assisted serial 15 | communication protocols in the embedded space that overcome these limitations. Some of them are 16 | widely used in digital sensors. 17 | 18 | The micro:bit board we are using has two motion sensors in it: an accelerometer and a magnetometer. 19 | Both of these sensors are packaged into a single component and can be accessed via an I2C bus. 20 | 21 | I2C is pronounced "EYE-SQUARED-CEE" and stands for Inter-Integrated Circuit. I2C is a *synchronous* 22 | serial *bus* communication protocol: it uses two lines to exchange data: a data line (SDA) and a 23 | clock line (SCL). The clock line is used to synchronize the communication. Synchronous serial can 24 | run faster and more reliably than async serial. I2C devices have *bus addresses*: the hardware 25 | implementation allows sending bytes to a particular device, with other devices connected to the same 26 | wires ignoring this communication. 27 | 28 |

29 | 30 |

31 | 32 | I2C uses a *controller*/*target* model: the controller is the device that *starts* and drives the 33 | communication with a target device. Several devices can be connected to the same bus at the same 34 | time, and can choose to act either as a controller or as a target. A controller device can 35 | communicate with a specific target device by first broadcasting the target address to the bus. This 36 | address can be 7 bits or 10 bits long. Once a controller has started a communication with a target, 37 | no device is other than the controller and target is allowed to transmit on the bus until the 38 | controller ends the communication. 39 | 40 | > **NOTE** "Controller/target" was formerly referred to as "master/slave". You may still see that in 41 | > literature or as labeling on boards. This terminology is now deprecated both in official standards 42 | > and newer documents, but is used in the Nordic manual for our nRF52833 part and in some embedded 43 | > Rust documentation. 44 | 45 | The clock line determines how fast data can be exchanged. The MB2 I2C interface can operate at 46 | speeds of 100, 250 or 400 Kbps. With other devices even faster modes are possible. 47 | -------------------------------------------------------------------------------- /mdbook/src/03-setup/verify.md: -------------------------------------------------------------------------------- 1 | # Verify the installation 2 | 3 | Let's verify that all the tools were installed correctly. 4 | 5 | ## Verifying cargo-embed 6 | 7 | First, connect the micro:bit to your Computer using a USB cable. 8 | 9 | At least an orange LED right next to the USB port of the micro:bit should light up. Furthermore, if 10 | you have never flashed another program on to your micro:bit, the default program the micro:bit ships 11 | with should start blinking the red LEDs on its back: you can ignore them, or you can play with the 12 | demo app. 13 | 14 | Now let's see if probe-rs, and by extensions cargo-embed can see your micro:bit. You can do this by 15 | running the following command: 16 | 17 | ``` console 18 | $ probe-rs list 19 | The following debug probes were found: 20 | [0]: BBC micro:bit CMSIS-DAP -- 0d28:0204:990636020005282030f57fa14252d446000000006e052820 (CMSIS-DAP) 21 | ``` 22 | 23 | Or if you want more information about the micro:bits debug capabilities then you can run: 24 | 25 | ``` console 26 | $ probe-rs info 27 | Probing target via JTAG 28 | 29 | Error identifying target using protocol JTAG: The probe does not support the JTAG protocol. 30 | 31 | Probing target via SWD 32 | 33 | Arm Chip with debug port Default: 34 | Debug Port: DPv1, DP Designer: Arm Ltd 35 | ├── 0 MemoryAP 36 | │ └── ROM Table (Class 1), Designer: Nordic VLSI ASA 37 | │ ├── Cortex-M4 SCS (Generic IP component) 38 | │ │ └── CPUID 39 | │ │ ├── IMPLEMENTER: Arm Ltd 40 | │ │ ├── VARIANT: 0 41 | │ │ ├── PARTNO: Cortex-M4 42 | │ │ └── REVISION: 1 43 | │ ├── Cortex-M3 DWT (Generic IP component) 44 | │ ├── Cortex-M3 FBP (Generic IP component) 45 | │ ├── Cortex-M3 ITM (Generic IP component) 46 | │ ├── Cortex-M4 TPIU (Coresight Component) 47 | │ └── Cortex-M4 ETM (Coresight Component) 48 | └── 1 Unknown AP (Designer: Nordic VLSI ASA, Class: Undefined, Type: 0x0, Variant: 0x0, Revision: 0x0) 49 | 50 | 51 | Debugging RISC-V targets over SWD is not supported. For these targets, JTAG is the only supported protocol. RISC-V specific information cannot be printed. 52 | Debugging Xtensa targets over SWD is not supported. For these targets, JTAG is the only supported protocol. Xtensa specific information cannot be printed. 53 | ``` 54 | 55 | Next, make sure you are in `src/03-setup` of this book's source code. Then run these commands: 56 | 57 | ``` 58 | $ rustup target add thumbv7em-none-eabihf 59 | $ cargo embed --target thumbv7em-none-eabihf 60 | ``` 61 | 62 | If everything works correctly cargo-embed should first compile the small example program 63 | in this directory, then flash it and finally open a nice text based user interface that 64 | prints Hello World. 65 | 66 | (If it does not, check out [general troubleshooting] instructions.) 67 | 68 | [general troubleshooting]: ../appendix/1-general-troubleshooting/index.html 69 | 70 | This output is coming from the small Rust program you just flashed on to your micro:bit. 71 | Everything is working properly and you can continue with the next chapters! 72 | -------------------------------------------------------------------------------- /mdbook/src/12-i2c/read-a-single-register.md: -------------------------------------------------------------------------------- 1 | # Read a single register 2 | 3 | Let's put all that theory into practice! 4 | 5 | First things first we need to know the target addresses of both the accelerometer and the 6 | magnetometer inside the chip, these can be found in the LSM303AGR's datasheet on page 39 and are: 7 | 8 | - 0011001 for the accelerometer 9 | - 0011110 for the magnetometer 10 | 11 | > **NOTE** Remember that these are only the 7 leading bits of the address, the 8th bit is going to 12 | > be the bit that determines whether we are performing a read or write. 13 | 14 | Next up we'll need a register to read from. Lots of I2C chips out there will provide some sort of 15 | device identification register for their controllers to read. Considering the thousands (or even 16 | millions) of I2C chips out there it is highly likely that at some point two chips with the same 17 | address will end up being built (after all the address is "only" 7 bit wide). With this device ID 18 | register a driver can make sure that it is indeed talking to a LSM303AGR and not some other chip 19 | that just happens to have the same address. As you can read in the LSM303AGR's datasheet 20 | (specifically on page 46 and 61) this part does provide two registers — `WHO_AM_I_A` at address 21 | `0x0f` and `WHO_AM_I_M` at address `0x4f` — which contain some bit patterns that are unique to the 22 | device. (The "A" is for "Accelerometer" and the "M" is for "Magnetometer".) 23 | 24 | The only thing missing now is the software part: we need to determine which API of the `microbit` or 25 | a HAL crate we should use for this. If you read through the datasheet of the nRF chip you are using 26 | you will soon find out that it doesn't actually have an I2C-specific peripheral. Instead, it has 27 | more general-purpose I2C-compatible peripherals called TWI ("Two-Wire Interface"), TWIM ("Two-Wire 28 | Interface Master") and TWIS ("Two-Wire Interface Slave"). We will normally be operating in 29 | controller mode and will use the newer TWIM, which supports "Easy DMA" — the TWI is provided mostly 30 | for backward compatibility with older devices. 31 | 32 | Now if we put the documentation of the [`twi(m)` module] from the `microbit` crate 33 | together with all the other information we have gathered so far we'll end up with this 34 | piece of code to read out and print the two device IDs (`examples/chip-id.rs`): 35 | 36 | [`twi(m)` module]: https://docs.rs/microbit-v2/0.11.0/microbit/hal/twim/index.html 37 | 38 | ``` rust 39 | {{#include examples/chip-id.rs}} 40 | ``` 41 | 42 | Apart from the initialization, this piece of code should be straight forward if you understood the 43 | I2C protocol as described before. The initialization here works similarly to the one from the UART 44 | chapter. We pass the peripheral as well as the pins that are used to communicate with the chip to 45 | the constructor; and then the frequency we wish the bus to operate on, in this case 100 kHz (`K100`, 46 | since identifiers can't start with a digit). 47 | 48 | ## Testing it 49 | As usual 50 | 51 | ```console 52 | $ cargo embed --example chip-id 53 | ``` 54 | in order to test our little example program. 55 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/spin-wait.md: -------------------------------------------------------------------------------- 1 | # Spin wait 2 | 3 | To blink the LED, we need to wait about a half-second between each change. How do we do that? 4 | 5 | Well, here's the dumb way. It's not good, but it's a start. Take a look at `examples/spin-wait.rs`. 6 | 7 | ```rust 8 | {{#include examples/spin-wait.rs}} 9 | ``` 10 | 11 | Run this with `cargo run --release --example spin-wait` — the `--release` is really important here — and 12 | you should see the LED on your MB2 flash on and off *about* once per second. 13 | 14 | Things you might be wondering: 15 | 16 | * **What are those `_` characters in that number?** Rust allows these in numbers and ignores them. 17 | It's really convenient to make big numbers more readable. Here we are using them as commas (or 18 | whatever the separator is for groups of three digits in your country). 19 | 20 | * **If the nRF52833 is running at 64MHz, why is the wait loop iterating only 4M times? Shouldn't it 21 | be 32M?** The wait loop executes several instructions each time through: the `nop` (see next 22 | section), some bookkeeping, and a branch back to the start of the loop. The code generated is 23 | roughly this for the first `wait()` call 24 | 25 | ```asm 26 | .LBB1_4: 27 | adds r3, #1 28 | nop 29 | cmp r3, r2 30 | bne .LBB1_4 31 | ``` 32 | 33 | and this for the second 34 | 35 | ```asm 36 | .LBB1_6: 37 | subs r3, #1 38 | nop 39 | bne .LBB1_6 40 | ``` 41 | 42 | This is only three or four instructions, but the backward branch may cost an extra bit. Notice 43 | that these *are not the same:* the compiler chooses to emit different instructions for the first 44 | and second wait loops. See "it varies depending" below. 45 | 46 | Still, we're executing about 4 instructions per loop iteration. This means that on our 64MHz CPU a 47 | half-second spin should take 64M/2/4 = 8M iterations to complete. So something is slowing us down 48 | by a factor of 2. What? I dunno. This whole thing is terrible. 49 | 50 | * **Why is `--release` so all-important?** Try without it. Notice that the LED is still flashing on 51 | and off, but with a period of *many* seconds. The wait loop is now unoptimized and is taking many 52 | instructions each time through. 53 | 54 | * **What is that `nop()` call and why is it there?** We shall answer this in the next section. 55 | 56 | * **Why do you refer to this as "the dumb way"?** 57 | 58 | * **It isn't precise.** Trying to tune that loop to reliably hit exactly 0.5 seconds is… not 59 | really a thing. 60 | 61 | * **It varies depending.** Different CPU? Different compilation flags? Different anything really? 62 | Now the timing has changed. 63 | 64 | * **It sucks power.** The CPU is running instructions as fast as it can, just to stay in place. 65 | If there's nothing else for it to do, it should quietly sleep until it is needed again. This 66 | doesn't matter much if you have USB power. But if you hook up your MB2 using the battery pack 67 | you'll really feel this. 68 | 69 | In the next section, we'll discuss `nop()`. After that, we'll talk more about the other things about 70 | our blinky that need improving. 71 | 72 | For such a simple program, this is a pretty complicated program. That's why we start with blinky. 73 | -------------------------------------------------------------------------------- /mdbook/src/02-requirements/README.md: -------------------------------------------------------------------------------- 1 | # Hardware/knowledge requirements 2 | 3 | The primary knowledge requirement to read this book is to know *some* Rust. It's hard for me to 4 | quantify *some*. Being familiar with the basics of generics and traits is quite helpful. You do need 5 | to know how to *use* closures. You also need to be familiar with the idioms of the current Rust 6 | [edition]. 7 | 8 | [edition]: https://rust-lang-nursery.github.io/edition-guide/ 9 | 10 | Also, to follow this material you'll need: 11 | 12 | - A [Micro:Bit v2] (MB2) board. 13 | 14 | [micro:bit v2]: https://tech.microbit.org/hardware/ 15 | 16 | You can purchase this board from many suppliers, including 17 | Amazon and Ali Baba. You can get a [list][0] of suppliers 18 | directly from the BBC, the manufacturers of MB2. 19 | 20 | [0]: https://microbit.org/buy/ 21 | 22 |

23 | 24 |

25 | 26 | There are several versions of the `V2` board 27 | available. While the material here was written for `V2.00`, 28 | things should work fine with with any `V2` board. 29 | 30 | - A micro-B USB cable (nothing special — you probably have many of these). This is required 31 | to power the micro:bit board when not on battery, and to communicate with it. Make sure 32 | that the cable supports data transfer, as some cables only support charging devices. 33 | 34 |

35 | 36 |

37 | 38 | > **NOTE** Some micro:bit kits ship with such cables. USB cables used with other mobile 39 | > devices should work, if they are micro-B and have the capability to transmit data. 40 | 41 | The official `micro:bit Go` kit provides both the USB cable and a nifty battery pack for powering 42 | the MB2 without USB. 43 | 44 | > **FAQ**: Wait, why do I need this specific hardware? 45 | 46 | It makes my life and yours much easier. 47 | 48 | The material is much, much more approachable if we don't have to worry about hardware differences. 49 | Trust me on this one. 50 | 51 | > **FAQ**: Can I follow this material with a different development board? 52 | 53 | Maybe? It depends mainly on two things: your previous experience with microcontrollers and/or 54 | whether a high level crate already exists for your development board somewhere. You probably want at 55 | least a HAL crate, like [`nrf52833-hal`] used here. You may prefer a board with a Board Support crate, 56 | like [`microbit-v2`] used here. If you intend to use a different microcontroller, you can look 57 | through [Awesome Embedded Rust] or just search the web to find supported crates. 58 | 59 | [`microbit-v2`]: https://docs.rs/microbit-v2 60 | [`nrf52833-hal`]: https://docs.rs/nrf52833-hal 61 | [Awesome Embedded Rust]: https://github.com/rust-embedded/awesome-embedded-rust 62 | 63 | With a different development board, this text loses most if not all its beginner friendliness and 64 | "easy to follow"-ness, in my opinion: you have been warned. 65 | 66 | If you have a different Arm-based development board and you don't consider yourself a total 67 | beginner, you might consider starting with the [quickstart] project template. 68 | 69 | [quickstart]: https://rust-embedded.github.io/cortex-m-quickstart/cortex_m_quickstart/ 70 | -------------------------------------------------------------------------------- /mdbook/src/09-registers/README.md: -------------------------------------------------------------------------------- 1 | # Registers 2 | 3 | This chapter is a technical deep-dive. You can safely [skip it] for now and come back to it later if 4 | you like. That said, there's a lot of good stuff in here, so I'd recommend you dive in. 5 | 6 | [skip it]: ../10-serial-communication/index.html 7 | 8 | ----- 9 | 10 | It's time to explore what calling `display_pins.row1.set_high()` or `button_a_pin.is_high()` does under the hood. 11 | 12 | In a nutshell, calling `display_pins.row1.set_high()` just writes to some special memory regions. Go into the `09-registers` directory 13 | and let's run the starter code statement by statement (`src/main.rs`). 14 | 15 | ``` rust 16 | {{#include src/main.rs}} 17 | ``` 18 | 19 | What's this magic? 20 | 21 | The address `0x50000504` points to a *register*. A register is a special region of memory that 22 | controls a *peripheral*. A peripheral is a piece of electronics that sits right next to the 23 | processor within the microcontroller package and provides the processor with extra functionality. 24 | After all, the processor, on its own, can only do math and logic. 25 | 26 | This particular register controls General Purpose Input/Output (GPIO) *pins* (GPIO *is* a 27 | peripheral) and can be used to *drive* each of those pins 28 | *low* or *high*. 29 | 30 | (On the nRF52833 there are more than 32 31 | GPIOs, yet the CPU is 32-bit. Thus, the GPIO 32 | pins are organized in two groups "P0" and "P1", with a set of registers 33 | for reading, writing and configuring each group. The address 34 | above is the address of the output register for the P0 pins.) 35 | 36 | ## An aside: LEDs, digital outputs and voltage levels 37 | 38 | Drive? Pin? Low? High? 39 | 40 | A pin is a electrical contact. Our microcontroller has several of them and some of them are 41 | connected to Light Emitting Diodes (LEDs). An LED will emit light when voltage is applied to it. As 42 | the name implies, an LED also acts as a "diode". A diode will only let electricity flow in one 43 | direction. Hook an LED up "forwards" and light comes out. Hook it up "backwards" and nothing 44 | happens. 45 | 46 |

47 | 48 |

49 | 50 | Luckily for us, the microcontroller's pins are connected such that we can drive the LEDs the right 51 | way round. All that we have to do is apply enough voltage across the pins to turn the LED on. The 52 | pins attached to the LEDs are normally configured as *digital outputs* and can output two different 53 | voltage levels: "low", 0 Volts, or "high", 3 Volts. A "high" (voltage) level will turn the LED on 54 | whereas a "low" (voltage) level will turn it off. 55 | 56 | These "low" and "high" states map directly to the concept of digital logic. "low" is `0` or `false` 57 | and "high" is `1` or `true`. This is why this pin configuration is known as digital output. 58 | 59 | The opposite of a digital output is a digital input. In the same way that a digital output can be either `0` or `1`, a digital input can be either `0` or `1`. The difference is that digital outputs can drive a voltages, but digital inputs *read* a voltage. When the microcontroller reads a voltage level above a high threshold, it will interpret that as a `1` and when it reads a voltage level below a low threshold, it will interpret that as a `0`. 60 | 61 | ----- 62 | 63 | OK. But how can one find out what this register does? Time to RTRM (Read the Reference Manual)! 64 | -------------------------------------------------------------------------------- /mdbook/src/06-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | In the last section, you wrote a sort of "Hello World" program. But for embedded programmers, the 4 | "real Hello World" is to blink an LED — any LED — on and off once per second. A program that does 5 | this is commonly known as a "blinky". 6 | 7 | Why blinky? Because this shows that you have enough control of the board you're working with to 8 | perform this simple task. You can get a program loaded onto the machine and running, you can find 9 | and turn on the appropriate pin on the MCU, you can delay for a fixed amount of time. Once you have 10 | this much control, other tasks become much more straightforward. 11 | 12 | In previous chapters, you found out several ways to load a program onto your MB2. Now it's just a 13 | question of which pin you turn on and off, and how you delay between these actions. 14 | 15 | Let's start by finding out how to work with the needed pins. There's a path you can follow for this 16 | if you know how to read electronic circuit "schematic" diagrams. You can find the [MB2 schematic], 17 | find an LED on that schematic that you want to turn on and off, and find what GPIO pins on the 18 | nRF52833 are attached to that LED. (The MB2 is a bit unusual in this regard: usually an LED is 19 | attached to just one pin that turns it on or off. The LED "display" on the MB2 is hooked up in a 20 | more complicated way to allow turning on and off combinations of LEDs at once: a feature that we 21 | will be using shortly.) 22 | 23 | [MB2 schematic]: https://github.com/microbit-foundation/microbit-v2-hardware/blob/main/V2.21/MicroBit_V2.2.1_nRF52820%20schematic.PDF 24 | 25 | We will work with the LED in the upper-left corner of the MB2 display. Tracing the `ROW1` and `COL1` 26 | wires this LED is connected to, we can see that they go to pins on the nRF52833 labeled 27 | `AC17`/`P0.21` and `B11`/`AIN4`/`P0.28`. Digging further through the documentation we find that 28 | `AC17` and `B11` are the row and column indices of the physical pins (solder balls, really) on the 29 | bottom of the chip — useless to us. `AIN4` just means that this pin can act as an "Analog Input", 30 | which is also currently useless to us. (It will come into play later.) 31 | 32 | This leaves `P0.21` and `P0.28`. These labels correspond to bits in the memory of the nRF52833 that 33 | can be turned on and off to get the LED to light up. Because electronics reasons, if pin `P0.21` is 34 | turned on (thus outputting 3.3V) and pin `P0.28` is turned off (thus accepting voltage) the LED will 35 | light up. 36 | 37 | But what do we do in software to cause this to occur? We will work at the level of the 38 | `nrf52833-hal` crate. The Hardware Abstraction Layer (HAL) is a chunk of software designed to make a 39 | particular microcontroller easier to work with. As can be seen from the name, we have one for the 40 | microcontroller on the MB2. It happens to contain everything needed to turn our target LED on. 41 | 42 | Take a look at `examples/light-up.rs` in this chapter's directory, and then try running it. 43 | You could use something fancy like before, but we have it set up so that 44 | 45 | ``` 46 | cargo run --example light-up 47 | ``` 48 | 49 | will load and run your program. That one LED should now be brightly lit! 50 | 51 | ``` rust 52 | {{#include examples/light-up.rs}} 53 | ``` 54 | 55 | Note that we access the Peripheral Access Crate (PAC) for this chip through our HAL crate. There's a 56 | complicated dance needed to get access to our pins. Finally, since we can just initialize the pins 57 | to the right levels, we don't need to set them. Wiggling the pins is a topic for the next section. 58 | -------------------------------------------------------------------------------- /mdbook/src/10-serial-communication/nix-tooling.md: -------------------------------------------------------------------------------- 1 | # Linux USB←→serial tooling 2 | 3 | The micro:bit's USB emulated serial device shows up in Linux when you connect the MB2 to a Linux USB 4 | port. 5 | 6 | ## Connecting the micro:bit board 7 | 8 | If you connect the micro:bit board to your computer you should see a new TTY device appear in 9 | `/dev`. 10 | 11 | ``` console 12 | $ sudo dmesg -T | tail | grep -i tty 13 | [63712.446286] cdc_acm 1-1.7:1.1: ttyACM0: USB ACM device 14 | ``` 15 | 16 | This is the USB←→serial device. On Linux, it's named `tty` (for "TeleTYpe", believe it or not). It 17 | should show up as `ttyACM0`, or maybe `ttyUSB0`. If other "ACM" devices are plugged in, the number 18 | will be higher. (On Mac OS `ls /dev/cu.usbmodem*` will show the serial device.) 19 | 20 | But what exactly is `ttyACM0`? It's a file of course! Everything is a file in Unix: 21 | 22 | ``` 23 | $ ls -l /dev/ttyACM0 24 | crw-rw----+ 1 root plugdev 166, 0 Jan 21 11:56 /dev/ttyACM0 25 | ``` 26 | 27 | Note that you will need to be either running as `root` (not advised) or a member of the group 28 | `plugdev` to read and write this device. You can then send out data by simply writing to this file: 29 | 30 | ``` console 31 | $ echo 'Hello, world!' > /dev/ttyACM0 32 | ``` 33 | 34 | You should see the orange LED on the micro:bit, right next to the USB port, blink for a moment, 35 | whenever you enter this command. 36 | 37 | ## minicom 38 | 39 | We'll use the program `minicom` to interact with the serial device using the keyboard. 40 | 41 | We must configure `minicom` before we use it. There are quite a few ways to do that but we'll use a 42 | `.minirc.dfl` file in the home directory. Create a file in `~/.minirc.dfl` with the following 43 | contents: 44 | 45 | ``` console 46 | $ cat ~/.minirc.dfl 47 | pu baudrate 115200 48 | pu bits 8 49 | pu parity N 50 | pu stopbits 1 51 | pu rtscts No 52 | pu xonxoff No 53 | ``` 54 | 55 | > **NOTE** Make sure this file ends in a newline! Otherwise, `minicom` will fail to read the last 56 | > line. 57 | 58 | That file should be straightforward to read (except for the last two lines), but nonetheless let's 59 | go over it line by line: 60 | 61 | - `pu baudrate 115200`. Sets baud rate to 115200 bps. 62 | - `pu bits 8`. 8 bits per frame. 63 | - `pu parity N`. No "parity check bit", which would be used for error detection. 64 | - `pu stopbits 1`. 1 stop bit. 65 | - `pu rtscts No`. No hardware flow control. 66 | - `pu xonxoff No`. No software flow control. 67 | 68 | Once that's in place, we can launch `minicom` on our ACM device, for example: 69 | 70 | ``` console 71 | $ minicom -D /dev/ttyACM0 -b 115200 72 | ``` 73 | 74 | This tells `minicom` to open the serial device at `/dev/ttyACM0` and set its 75 | baud rate to 115200. A text-based user interface (TUI) will pop out. 76 | 77 |

78 | 79 |

80 | 81 | You can now send data using the keyboard! Go ahead and type something. Note that 82 | the text UI will *not* echo back what you type. If you pay attention to the yellow LED 83 | on top of the micro:bit though, you will notice that it blinks whenever you type something. 84 | 85 | ## `minicom` commands 86 | 87 | `minicom` exposes commands via keyboard shortcuts. On Linux, the shortcuts start with `Ctrl+A`. (On 88 | Mac, the shortcuts start with the `Meta` key.) Some useful commands below: 89 | 90 | - `Ctrl+A` + `Z`. Minicom Command Summary 91 | - `Ctrl+A` + `C`. Clear the screen 92 | - `Ctrl+A` + `X`. Exit and reset 93 | - `Ctrl+A` + `Q`. Quit with no reset 94 | 95 | > **NOTE** Mac users: In the above commands, replace `Ctrl+A` with `Meta`. 96 | -------------------------------------------------------------------------------- /rechapter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # How to use this tool: 4 | # 1. Set up 5 | # 1.1 make sure to be on a clean branch so that undo is easy 6 | # 1.2 cd to mdbook/src 7 | # 1.3 ensure that all chapters are in directories named ##-name 8 | # 1.4 ensure that SUMMARY.md contains all chapters in correct order 9 | # 2. python3 ../../rechapter.py 10 | # 3. inspect rechapter.sh, rechapter.sed and SUMMARY.md.new 11 | # 4. mv -f SUMMARY.md.new SUMMARY.md 12 | # 5. sh rechapter.sh 13 | # 6. rm rechapter.sh rechapter.sed 14 | # 7. git commit -a 15 | # 8. run reworkspace.py (see insns there) to fix the Cargo workspace 16 | 17 | import re, sys 18 | from pathlib import Path 19 | 20 | CHAPTER=re.compile(r"([0-9]+)-([^/]*)") 21 | p = Path('.') 22 | chapters = dict() 23 | for c in p.iterdir(): 24 | if not c.is_dir(): 25 | continue 26 | parts = CHAPTER.fullmatch(str(c)) 27 | if not parts: 28 | continue 29 | chapters[parts[2]] = (c, parts[1]) 30 | nch = len(chapters) 31 | ch_digits = len(str(nch)) 32 | 33 | def next_line(lines): 34 | try: 35 | return next(lines) 36 | except StopIteration: 37 | return None 38 | 39 | CH_HEAD = re.compile(r"- \[([^]]*)]\(([0-9]+)-([^/]+)/README\.md\)") 40 | CH_TOPIC = re.compile(r"( +)- \[([^]]*)]\(([0-9]+)-([^/]+)/(.*\.md)\)") 41 | chs = [] 42 | summary_file = Path("SUMMARY.md") 43 | new_summary_file = Path("SUMMARY.md.new") 44 | renames = [] 45 | with summary_file.open(mode="r") as s: 46 | with new_summary_file.open(mode="w") as ns: 47 | summary = s.read() 48 | ch = 1 49 | lines = iter(summary.splitlines()) 50 | line = next_line(lines) 51 | while line is not None: 52 | parts = CH_HEAD.fullmatch(line) 53 | if parts: 54 | name = parts[3] 55 | old = parts[2] 56 | new = f"{ch:0{ch_digits}}" 57 | if old != new: 58 | renames.append((name, old, new)) 59 | line = f"- [{parts[1]}]({new}-{parts[3]}/README.md)" 60 | print(line, file=ns) 61 | line = next_line(lines) 62 | while line is not None: 63 | subparts = CH_TOPIC.fullmatch(line) 64 | if not subparts: 65 | break 66 | old_sub = subparts[3] 67 | if old_sub != old: 68 | print( 69 | f"chapter mismatch: old chapter {old}, old subchapter {old_sub}", 70 | file=sys.stderr, 71 | ) 72 | line = f"{subparts[1]}- [{subparts[2]}]({new}-{subparts[4]}/{subparts[5]})" 73 | print(line, file=ns) 74 | line = next_line(lines) 75 | ch += 1 76 | continue 77 | print(line, file=ns) 78 | line = next_line(lines) 79 | if line is not None: 80 | print(line, file=ns) 81 | if ch - 1 != nch: 82 | print(f"chapter count mismatch: summary {ch - 1}, dir {nch}", file=sys.stderr) 83 | exit(1) 84 | 85 | sed_script = Path("rechapter.sed") 86 | with sed_script.open(mode="w") as s: 87 | for name, old, new in renames: 88 | print(f's={old}-{name}={new}-{name}=g', file=s) 89 | 90 | shell_script = Path("rechapter.sh") 91 | with shell_script.open(mode="w") as s: 92 | for name, old, new in renames: 93 | print(f'git mv "{old}-{name}" "{new}-{name}"', file=s) 94 | print('find . -type f -name "*.md" -print |', file=s) 95 | print('while read i; do sed -i -f rechapter.sed "$i"; done', file=s) 96 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: # Run CI for all branches except GitHub merge queue tmp branches 5 | branches-ignore: 6 | - "gh-readonly-queue/**" 7 | pull_request: # Run CI for PRs on any branch 8 | merge_group: # Run CI for the GitHub merge queue 9 | 10 | jobs: 11 | # Check code build succeeds. 12 | build-book-code: 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: dtolnay/rust-toolchain@stable 17 | with: 18 | targets: thumbv7em-none-eabihf 19 | - name: Build book code 20 | working-directory: . 21 | run: cargo build 22 | 23 | # Check build succeeds for microbit docs. 24 | build-book-doc: 25 | runs-on: ubuntu-24.04 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: dtolnay/rust-toolchain@stable 29 | with: 30 | targets: thumbv7em-none-eabihf 31 | - name: Build docs for micro:bit v2 32 | working-directory: . 33 | run: cargo doc 34 | 35 | # Build the book HTML itself and optionally publish it. 36 | build-book: 37 | runs-on: ubuntu-24.04 38 | steps: 39 | - uses: actions/checkout@v4 40 | - uses: dtolnay/rust-toolchain@stable 41 | with: 42 | targets: thumbv7em-none-eabihf 43 | 44 | - name: Install Python dependencies 45 | run: | 46 | pip3 install --user python-dateutil linkchecker 47 | - name: Put pip binary directory into path 48 | run: echo "~/.local/bin" >> $GITHUB_PATH 49 | 50 | - name: Cache Cargo installed binaries 51 | uses: actions/cache@v4 52 | id: cache-cargo 53 | with: 54 | path: ~/cargo-bin 55 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 56 | restore-keys: | 57 | ${{ runner.os }}-cargo- 58 | 59 | - name: Put mdbook-epub where mdbook expects it 60 | if: steps.cache-cargo.outputs.cache-hit == 'true' 61 | run: | 62 | ls ~/cargo-bin 63 | mkdir -p ~/.cargo/bin 64 | cp ~/cargo-bin/mdbook-epub ~/.cargo/bin 65 | 66 | - name: Install mdbook 67 | if: steps.cache-cargo.outputs.cache-hit != 'true' 68 | run: cargo install --locked mdbook --version 0.4.51 69 | 70 | - name: Install mdbook-epub 71 | if: steps.cache-cargo.outputs.cache-hit != 'true' 72 | run: cargo install --locked mdbook-epub --version 0.4.48 73 | 74 | - name: Copy mdbook and mdbook-epub to cache directory 75 | if: steps.cache-cargo.outputs.cache-hit != 'true' 76 | run: | 77 | mkdir ~/cargo-bin 78 | cp ~/.cargo/bin/mdbook ~/cargo-bin 79 | cp ~/.cargo/bin/mdbook-epub ~/cargo-bin 80 | ls ~/cargo-bin 81 | 82 | - name: Put new cargo binary directory into path 83 | run: echo "~/cargo-bin" >> $GITHUB_PATH 84 | 85 | - name: Build book 86 | working-directory: mdbook 87 | run: mdbook build 88 | 89 | - name: Check book links 90 | working-directory: mdbook 91 | run: linkchecker --ignore-url "print.html" book 92 | 93 | - name: Copy EPUB to html directory 94 | working-directory: mdbook 95 | run: cp book/epub/*.epub book/html/ 96 | 97 | - name: Deploy book 98 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 99 | uses: peaceiris/actions-gh-pages@v4 100 | with: 101 | github_token: ${{ secrets.GITHUB_TOKEN }} 102 | publish_dir: mdbook/book/html 103 | force_orphan: true 104 | -------------------------------------------------------------------------------- /mdbook/src/04-meet-your-hardware/terminology.md: -------------------------------------------------------------------------------- 1 | # Rust Embedded terminology 2 | 3 | Before we dive into programming the micro:bit let's have a quick look at the libraries and 4 | terminology that will be important for all the future chapters. 5 | 6 | ## Abstraction layers 7 | 8 | For any fully supported microcontroller/board with a microcontroller, you will usually hear the 9 | following terms being used for their levels of abstraction: 10 | 11 | ### Peripheral Access Crate (PAC) 12 | 13 | The job of the PAC is to provide a safe (ish) direct interface to the peripherals of the chip, 14 | allowing you to configure every last bit however you want (of course also in wrong ways). Usually 15 | you only ever have to deal with the PAC if either the layers that are higher up don't fulfill your 16 | needs or when you are developing higher-level code for them. Unsurprisingly, the PAC we are (mostly 17 | implicitly) going to use is for the [nRF52]. 18 | 19 | ### Hardware Abstraction Layer (HAL) 20 | 21 | The job of the HAL is to build up on top of the chip's PAC and provide an abstraction that is 22 | actually usable for someone who does not know about all the special behaviour of this chip. Usually 23 | a HAL abstracts whole peripherals away into single structs that can, for example, be used to send 24 | data around via the peripheral. We are going to use the [nRF52-hal]. 25 | 26 | ### Board Support Crate (BSP) 27 | 28 | (In non-Rust situations this is usually called the Board Support Package, hence the acronym.) 29 | 30 | The job of the BSP is to abstract a whole board (such as the micro:bit) away at once. That means it 31 | has to provide abstractions to use both the microcontroller as well as the sensors, LEDs etc. that 32 | might be present on the board. Quite often (especially with custom-made boards) no pre-built BSP 33 | will be available. Instead you will be working with a HAL for the chip and build the drivers for the 34 | sensors either yourself or search for them on `crates.io`. Luckily for us though, the micro:bit does 35 | have a [BSP], so we are going to use that on top of our HAL as well. 36 | 37 | [nrF52]: https://crates.io/crates/nrf52833-pac 38 | [nrF52-hal]: https://crates.io/crates/nrf52833-hal 39 | [BSP]: https://crates.io/crates/microbit-v2 40 | 41 | ## Unifying the layers 42 | 43 | Next we are going to have a look at a very central piece of software 44 | in the Rust Embedded world: [`embedded-hal`]. As its name suggests it 45 | relates to the 2nd level of abstraction we got to know: the HALs. 46 | The idea behind [`embedded-hal`] is to provide a set of traits that 47 | describe behaviour which is usually shared across all implementations 48 | of a specific peripheral in all the HALs. For example one would always 49 | expect to have functions that are capable of turning the power on a pin 50 | either on or off: to switch an LED on and off on the board or whatever. 51 | 52 | `embedded-hal` allows us to write a driver for some piece of hardware, for example a temperature 53 | sensor, that can be used on any chip for which an implementation of the [`embedded-hal`] traits 54 | exists. This is accomplished by writing the driver in such a way that it only relies on the 55 | [`embedded-hal`] traits. Drivers that are written in such a way are called *platform-agnostic.* 56 | Luckily for us, the drivers we will be getting from `crates.io` are almost all platform agnostic. 57 | 58 | [`embedded-hal`]: https://crates.io/crates/embedded-hal 59 | 60 | 61 | ## Further reading 62 | 63 | If you want to learn more about these levels of abstraction, Franz Skarman (a.k.a. [TheZoq2]) held a 64 | talk about this topic during Oxidize 2020: [An Overview of the Embedded Rust Ecosystem]. 65 | 66 | [TheZoq2]: https://github.com/TheZoq2/ 67 | [An Overview of the Embedded Rust Ecosystem]: https://www.youtube.com/watch?v=vLYit_HHPaY 68 | -------------------------------------------------------------------------------- /mdbook/src/05-meet-your-software/light-it-up.md: -------------------------------------------------------------------------------- 1 | # Light it up 2 | 3 | We will finish this chapter by making one of the many LEDs on the MB2 light up. In order to get this 4 | task done we will use one of the traits provided by `embedded-hal`, specifically the [`OutputPin`] 5 | trait which allows us to turn a pin on or off. 6 | 7 | [`OutputPin`]: https://docs.rs/embedded-hal/0.2.6/embedded_hal/digital/v2/trait.OutputPin.html 8 | 9 | ## The micro:bit LEDs 10 | 11 | On the back of the micro:bit you can see a 5x5 square of LEDs, usually called an LED matrix. This 12 | matrix alignment is used so that instead of having to use 25 separate pins to drive every single one 13 | of the LEDs, we can just use 10 (5+5) pins in order to control which column and which row of our 14 | matrix lights up. 15 | 16 | Right now we will use the `microbit-v2` crate to manipulate the LEDs. In the [next chapter] we will 17 | go in detail through all of the options available. 18 | 19 | [next chapter]: ../06-hello-world/index.html 20 | 21 | ## Actually lighting it up! 22 | 23 | The code required to light up an LED in the matrix is actually quite simple but it requires a bit of 24 | setup. First take a look at `examples/light-it-up.rs`; then we can go through it step by step. 25 | 26 | ```rust 27 | {{#include examples/light-it-up.rs}} 28 | ``` 29 | 30 | The first few lines until the `main` function just do some basic imports and setup we mostly looked 31 | at before. However, the `main` function looks pretty different to what we have seen up to now. 32 | 33 | The first line is related to how most HALs written in Rust work internally. 34 | As discussed before they are built on top of PAC crates which own (in the Rust sense) 35 | all the peripherals of a chip. When we say 36 | 37 | let mut board = Board::take().unwrap(); 38 | 39 | We take all of these peripherals from the PAC and bind them to a variable. In this specific case we 40 | are not only working with a HAL but with an entire BSP, so this also takes ownership of the Rust 41 | representation of the other chips on the board. 42 | 43 | > **NOTE**: If you are wondering why we have to call `unwrap()` here, in theory it is possible for 44 | > `take()` to be called more than once. This would lead to the peripherals being represented by two 45 | > separate variables and thus lots of possible confusing behaviour because two variables modify the 46 | > same resource. In order to avoid this, PACs are implemented in a way that it would panic if you 47 | > tried to take the peripherals twice. 48 | 49 | (Again, if you are confused by all of this, the [next chapter] will go through it all again in 50 | greater detail.) 51 | 52 | Now we can light the LED connected to `row1`, `col1` up by setting the `row1` pin to high 53 | (i.e. switching it on). The reason we can leave `col1` set to low is because of how the LED matrix 54 | circuit works. Furthermore, `embedded-hal` is designed in a way that every operation on hardware can 55 | possibly return an error, even just toggling a pin on or off. Since that is highly unlikely in our 56 | case, we can just `unwrap()` the result. 57 | 58 | ## Testing it 59 | 60 | Testing our little program is quite simple. First put it into `src/main.rs`. Afterwards we simply 61 | have to run the `cargo embed` command from the last section again, and let it flash just like 62 | before. Then open our GDB and connect to the GDB stub: 63 | 64 | ``` 65 | $ # Your GDB debug command from the last section 66 | (gdb) target remote :1337 67 | Remote debugging using :1337 68 | cortex_m_rt::Reset () at /home/nix/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:489 69 | 489 pub unsafe extern "C" fn Reset() -> ! { 70 | (gdb) 71 | ``` 72 | 73 | We now let the program run via the GDB `continue` command: one of the LEDs on the front of the 74 | micro:bit should light up. 75 | --------------------------------------------------------------------------------