# Embedded Rust - Getting started ## Why Rust in the first place ? 1. Very Similar to C * Ahead of time compiled * Works well with existing C projects * Statically typed * Similar control flow structures, no [exceptions](https://doc.rust-lang.org/book/ch09-00-error-handling.html#:~:text=Rust%20doesn%27t%20have%20exceptions,program%20encounters%20an%20unrecoverable%20error.) * Similar performance 2. Is Rust better ? * Memory safety -> One immutable or many immutables * No more include files * Generics + Traits ( Useful for fast prototyping) * Strong type systems 3. ISO 26262 (ASIL D) and IEC 61508 (SIL 4) Qualified toolchain now available * Ferrocene -> https://public-docs.ferrocene.dev/main/user-manual/targets/index.html#supported-targets Sources : [Rust compared to C by Andreas Hindborg](https://youtu.be/ubohmQSTeBY?si=LHrm83X4F1eXxSrr) ## How is embedded rust different from usual rust? 1. For baremetal programming -> [no_std] programming < https://docs.rust-embedded.org/book/intro/no-std.html > 2. Heapless crate -> https://crates.io/crates/heapless * https://japaric.github.io/heapless/heapless/index.html 3. Concurrency * https://docs.rust-embedded.org/book/concurrency/#rtic 4. Knurling-rs * https://github.com/knurling-rs ## New to Rust ? Start here => https://doc.rust-lang.org/stable/rust-by-example/ ## System/Hardware information * PC, WSL2 (Ubuntu 22.04) * [usbipd-win](https://github.com/dorssel/usbipd-win) to forward the devices to WSL * [nrf52840-DK](https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk) * Micro-USB cable ## IDE Setup VS Code Extensions * [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) * [Debugger for probe-rs](https://marketplace.visualstudio.com/items?itemName=probe-rs.probe-rs-debugger) * [crates](https://marketplace.visualstudio.com/items?itemName=serayuzgur.crates) <!-- * [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) --> <!-- * [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) --> > [IntelliJ Clion](https://www.jetbrains.com/clion/) is also a popular tool for [Embedded Rust development](https://www.jetbrains.com/help/clion/rust-support.html#language-support) as it supports both C/C++ and Rust tools. However the WSL integration offered by VS Code is much superior to IntelliJ tools hence I switched to VS code. ## Getting Started with NRF52840 ### 1. Create project from `cortex-m-quickstart` template > `cortex-m-quickstart` crate is the easiest way to get started with an ARM Cortex-M hardware since it does most of the cpu specific heavy lifting away from the user. However please note that the peripherals are not included in the crate, this would need setting up [Peripheral Access Crate (PAC)](https://docs.rust-embedded.org/book/start/registers.html#using-a-peripheral-access-crate-pac) which we will discuss in detail later. Ensure that cargo-generate is installed ``` cargo install cargo-generate ``` #### Todo , use a specific git tag to avoid issues in future Generate the project from the template ``` cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart ``` ### 2. Setting up cpu specifics #### 2.1 Identifying CPU and compilation target Inside the project compilation targets can be found in `.cargo/config.toml` . Based on the microcontroller CPU architecture select the right target. In the case of nrf52840 it's [Cortex M4 with FPU](https://www.nordicsemi.com/Products/nRF52840), hence we can change `[target.xxxxx]` and `[build]` section in `.cargo/config.toml` as follows: ``` [target.thumbv7em-none-eabihf] ... ... [build] # Pick ONE of these default compilation targets # target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ # target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) # target = "thumbv8m.base-none-eabi" # Cortex-M23 # target = "thumbv8m.main-none-eabi" # Cortex-M33 (no FPU) # target = "thumbv8m.main-none-eabihf" # Cortex-M33 (with FPU) ``` Change the first line target info More targets can be found here => https://rust-lang.github.io/rustup-components-history/ but they may not be supported. --- #### 2.2 Installing cross compilation target Ensure that the cross compilation target is installed locally. ``` rustup target add thumbv7em-none-eabihf ``` Installation can be verified by using: ``` rustup target list | grep thumbv7em-none-eabihf ``` ### 3. Memory Layout The [build.rs](https://github.com/rust-embedded/cortex-m-quickstart/blob/master/build.rs) file in the template needs [memory.x](https://github.com/rust-embedded/cortex-m-quickstart/blob/master/memory.x) to be properly configured for the linking process. Change the first part of [memory.x](https://github.com/rust-embedded/cortex-m-quickstart/blob/master/memory.x) file as follows: ``` MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x00000000, LENGTH = 1024K RAM : ORIGIN = 0x20000000, LENGTH = 256K } ``` These values come from [nrf52840 product specification](https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.2.pdf). See below: <br> ![](https://hackmd.io/_uploads/ByFMy2WMT.png) ### 4. Building binaries The example already comes with a `main.rs` file. Let's try compiling the same using the following command. ``` cargo build ``` To view the dependancy tree , use: ``` cargo tree ``` To view more info on the generated elf , use ``` readelf -W -S target/thumbv7em-none-eabihf/debug/nrf52840 ``` #### Optional - generating and viewing mapfiles. If you need the map file for some reason please add the following line to `rustflags` in `cargo/config.toml` ``` "-Clink-args=-Map=app.map" ``` The generated map files are likely to have the symbols in a [mangled](https://en.wikipedia.org/wiki/Name_mangling) state. To have the original symbols name intact, an outer [attribute](https://doc.rust-lang.org/beta/reference/attributes.html) called [no_mangle](https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute) may be set to symbols or functions: ``` #[no_mangle] pub fn write(...) { ... } ``` ## 5. Flashing Firmware [nrf52840-DK](https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk) comes with an on-board SEGGER J-Link debugger, hence there are several ways to flash. * [nrf-utils](https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/nrf-util-unified-command-line-utility) * Copying the binaries to mass storage device. * J-link tools, OpenOCD etc * **Rust way** by using [probe-rs](https://github.com/probe-rs/probe-rs) ### **The Rust way:** ### 5.1. Setup `probe-rs` run: ``` cargo install probe-rs --features cli ``` > probe-run was recently [deprecated](https://ferrous-systems.com/blog/probe-run-deprecation/) ### 5.2 Setup `udev` rules Refer to the this guide <https://hackmd.io/@aeefs2Y8TMms-cjTDX4cfw/r1fqAa_Da> and ensure that device has proper read write access. ### 5.3 Configure `.cargo/config.toml` ``` [target.'cfg(all(target_arch = "arm", target_os = "none"))'] runner = "probe-rs run --chip nRF52840_xxAA " ``` ### 5.4 Unlock nrf52840-DK using nrf-cli tool Newer versions of nrf52840-DK ( 3.0.1 and upwards) brings a [lot of changes](https://infocenter.nordicsemi.com/pdf/in_141_v1.1.pdf?cp=18_16) especially the flashing mechanism. By default the [access port protection/ Readback protection](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/device_guides/working_with_nrf/nrf53/nrf5340.html#readback-protection) is enabled. This means that when you try to flash, the following error can occur ![](https://hackmd.io/_uploads/BJgQTwzza.png) The easiest way to disable AP protection by using [ nrf-cli](https://www.nordicsemi.com/Products/Development-tools/nrf-command-line-tools/download) tool. This tool needs to be [setup](https://www.nordicsemi.com/Products/Development-tools/nRF-Command-Line-Tools/Download?lang=en#infotabs) on **windows** because of run time conflicts with `usbipd` tool. Once setup, in `powershell` run, ``` nrfjprog --family nrf52 --recover ``` This will result in the following output ``` $ nrfjprog --family nrf52 --recover Recovering device. This operation might take 30s. Erasing user code and UICR flash areas. Writing image to disable ap protect. ``` ### 5.5 Forward the device to WSL2 See this guide <https://hackmd.io/@aeefs2Y8TMms-cjTDX4cfw/r1fqAa_Da> Once configured, the device should show up in **wsl2**: ``` lsusb Bus 001 Device 003: ID 1366:1051 SEGGER J-Link ``` ### 5.6 **Flash** !! Run: ``` cargo run ``` This should result in the following output ``` $cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `probe-rs run --chip nRF52840_xxAA target/thumbv7em-none-eabihf/debug/nrf52840` Erasing sectors ✔ [00:00:00] [##########################################################################################################################] 4.00 KiB/4.00 KiB @ 10.15 KiB/s (eta 0s ) Programming pages ✔ [00:00:00] [###########################################################################################################################] 4.00 KiB/4.00 KiB @ 9.95 KiB/s (eta 0s ) Finished in 0.871s ``` ## 6. Debugging Firmware When it comes to debugging using VS Code there are the two commonly used approaches 1. Using [Cortex-debug extension](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) + OpenOCD/J-Link/St-Util as debug server. There are plety of tutorials on how to do this 2. Using `probe-rs` Probe-rs method: [Official doc](https://probe.rs/docs/tools/vscode/) contains information on how to setup the debugger, however for **WSL** setup we will need to make a few tweaks. ### 6.1 Setting up VS code tasks : Add `build` tasks in `.vscode/tasks.json` : ``` { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "options": { "shell": { "executable": "/usr/bin/bash", "args": ["-l", "-c"] } }, "tasks": [ { "label": "cargo build", "type": "shell", "command": "cargo", "args": [ "build" ], // "problemMatcher": [ // "$rustc" // ], "group": "build", }, { "label": "cargo clean", "type": "shell", "command": "cargo", "args": [ "clean" ], // "problemMatcher": [ // "$rustc" // ], "group": "build", }, { "label": "cargo run", "type": "shell", "command": "cargo", "args": [ "run" ], // "problemMatcher": [ // "$rustc" // ], "group": "build", }, ] } ``` Once the tasks are set, try the shortcut `Ctrl + Shift + b` to start `build` tasks ![](https://hackmd.io/_uploads/rkRk1iGMa.png) ### 6.2 Setting up VS code `launch` configurations for `probe-rs` `.vscode/launch.json` : ``` { "version": "0.2.0", "configurations": [ { // see https://probe.rs/docs/tools/debugger/ for more info "type": "probe-rs-debug", "request": "launch", "name": "probe_rs", "cwd": "${workspaceFolder}", "preLaunchTask": "cargo build", "runtimeExecutable": "/home/akshai/.cargo/bin/probe-rs", //!MODIFY "runtimeArgs": ["dap-server"], "chip": "nRF52840_xxAA", //!MODIFY "flashingConfig": { "flashingEnabled": true, "haltAfterReset": false, "formatOptions": { "format": "elf" //Valid values are: 'bin', 'hex', 'elf'(default), 'idf' } }, "coreConfigs": [ { "coreIndex": 0, "programBinary": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/nrf52840", //!MODIFY // "svdFile": "${workspaceFolder}/nrf52840.svd", //!MODIFY , required for register view "rttEnabled": true, "rttChannelFormats": [ { "channelNumber": 0, // Format RTT data as String data "dataFormat": "String", // Include host-side timestamps for every line of data transferred from the target RTT output "showTimestamps": true }, { "channelNumber": 1, // Treat data as raw binary data, and do not format in any way "dataFormat": "BinaryLE" } ] } ], "env": { //!MODIFY (or remove) "RUST_LOG": "info" // If you set this variable, check the VSCode console log window for the location of the log file. }, "consoleLogLevel": "Console" //Info, Debug } ] } ``` <br> > By default, for "debug" builds, the optimization level is `0`, hence debugging works as expected. However for debugging crates, ensure that Optimization Level 0 is selected in profiles in crates' config: https://doc.rust-lang.org/cargo/reference/profiles.html ### 6.3 Start Debugging! Use F5 or click on the start button near probe_rs configuration to start debugging. ![](https://hackmd.io/_uploads/SkfHE3MMT.png) ### 6.4 To debug registers, add svd files > To understand more about svd files see https://arm-software.github.io/CMSIS_5/SVD/html/index.html For register level debugging, modern debuggers for ARM targets use SVD files. For nrf52840, a sample svd file can be downloaded from [here](https://github.com/KizzyCode/Template-rust-nrf52840/blob/master/.vscode/nrf52840.svd) Also, configure svd file for debugger. `.vscode/launch.json` ``` "svdFile": "${workspaceFolder}/nrf52840.svd", //!MODIFY , ``` ### 6.5 Hello world Lets add some prints using RTT change `Cargo.toml` ``` [package] authors = ["name <name@domain.com>"] edition = "2023" readme = "README.md" name = "nrf52840" version = "0.0.0" [dependencies] cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} panic-probe = { version = "0.3.1", features = ["print-rtt"] } cortex-m-rt = "0.7.0" cortex-m-semihosting = "0.5.0" panic-halt = "0.2.0" rtt-target = {version = "0.4.0"} # this lets you use `cargo fix`! [[bin]] name = "nrf52840" test = false bench = false [profile.dev] # debug = 1 # default is `true`; not needed if not already overridden [profile.release] codegen-units = 1 # better optimizations debug = true # symbols are nice and they don't increase the size on Flash lto = false # better optimizations ``` Also, change `src/main.rs` to ``` #![no_std] #![no_main] // pick a panicking behavior use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics use cortex_m::asm; use cortex_m_rt::entry; use rtt_target::{rtt_init_print, rprintln}; #[entry] fn main() -> ! { let mut x: i32 = 1; rtt_init_print!(); // You may prefer to initialize another way rprintln!("Hello, world!"); loop { x += 1; rprintln!("Counter {}", x) // asm::bkpt() // software breakpoint } } ``` --- ## References Embedded Rust training course by Ferrous systems : https://embedded-trainings.ferrous-systems.com/ Creating a New Package: https://doc.rust-lang.org/cargo/guide/creating-a-new-project.html Blog on analyzing gnu-binutils and analyzing .elf files https://interrupt.memfault.com/blog/gnu-binutils Rust equivalent for gnu-binutils : https://github.com/rust-embedded/cargo-binutils Rust vs C internals: https://kornel.ski/rust-c-speed Optimizations : Speed vs Size: https://docs.rust-embedded.org/book/unsorted/speed-vs-size. GDB Core ideas : https://interrupt.memfault.com/blog/installing-gdb GDB Cheat sheet : https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf