$npx -y skills add mohitmishra786/low-level-dev-skills --skill embedded-rustEmbedded Rust skill for bare-metal microcontroller development. Use when using probe-rs or cargo-embed for flashing and debugging, setting up defmt logging, using the RTIC framework, configuring cortex-m-rt startup, writing no_std + no_main firmware, or choosing between panic-hal
| 1 | # Embedded Rust |
| 2 | |
| 3 | ## Purpose |
| 4 | |
| 5 | Guide agents through embedded Rust development: flashing and debugging with probe-rs/cargo-embed, structured logging with defmt, the RTIC concurrency framework, cortex-m-rt startup, no_std configuration, and panic handler selection. |
| 6 | |
| 7 | ## Triggers |
| 8 | |
| 9 | - "How do I flash my Rust firmware to an MCU?" |
| 10 | - "How do I debug my embedded Rust program?" |
| 11 | - "How do I use defmt for logging in embedded Rust?" |
| 12 | - "How do I use RTIC for interrupt-driven concurrency?" |
| 13 | - "What does #![no_std] #![no_main] mean for embedded Rust?" |
| 14 | - "How do I handle panics in no_std embedded Rust?" |
| 15 | |
| 16 | ## Workflow |
| 17 | |
| 18 | ### 1. Project setup |
| 19 | |
| 20 | ```toml |
| 21 | # Cargo.toml |
| 22 | [package] |
| 23 | name = "my-firmware" |
| 24 | version = "0.1.0" |
| 25 | edition = "2021" |
| 26 | |
| 27 | [dependencies] |
| 28 | cortex-m = { version = "0.7", features = ["critical-section-single-core"] } |
| 29 | cortex-m-rt = "0.7" |
| 30 | defmt = "0.3" |
| 31 | defmt-rtt = "0.4" |
| 32 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 33 | |
| 34 | # Embassy (async embedded) — alternative to RTIC |
| 35 | # embassy-executor = { version = "0.5", features = ["arch-cortex-m"] } |
| 36 | |
| 37 | [profile.release] |
| 38 | opt-level = "s" # size optimization for embedded |
| 39 | lto = true |
| 40 | codegen-units = 1 |
| 41 | debug = true # keep debug info for defmt/probe-rs |
| 42 | |
| 43 | # .cargo/config.toml |
| 44 | [build] |
| 45 | target = "thumbv7em-none-eabihf" # Cortex-M4F / M7 |
| 46 | |
| 47 | [target.thumbv7em-none-eabihf] |
| 48 | runner = "probe-rs run --chip STM32F411CEUx" # auto-run after build |
| 49 | rustflags = ["-C", "link-arg=-Tlink.x"] # cortex-m-rt linker script |
| 50 | ``` |
| 51 | |
| 52 | ### 2. Minimal bare-metal program |
| 53 | |
| 54 | ```rust |
| 55 | // src/main.rs |
| 56 | #![no_std] |
| 57 | #![no_main] |
| 58 | |
| 59 | use cortex_m_rt::entry; |
| 60 | use defmt::info; |
| 61 | use defmt_rtt as _; // RTT transport for defmt |
| 62 | use panic_probe as _; // panic handler that prints via defmt |
| 63 | |
| 64 | #[entry] |
| 65 | fn main() -> ! { |
| 66 | info!("Booting up!"); |
| 67 | |
| 68 | // Access peripherals via PAC or HAL |
| 69 | let _core = cortex_m::Peripherals::take().unwrap(); |
| 70 | // let dp = stm32f4xx_hal::pac::Peripherals::take().unwrap(); |
| 71 | |
| 72 | loop { |
| 73 | info!("Running..."); |
| 74 | cortex_m::asm::delay(8_000_000); // rough delay |
| 75 | } |
| 76 | } |
| 77 | ``` |
| 78 | |
| 79 | Target triples for common MCUs: |
| 80 | |
| 81 | | MCU family | Target triple | |
| 82 | |-----------|---------------| |
| 83 | | Cortex-M0/M0+ | `thumbv6m-none-eabi` | |
| 84 | | Cortex-M3 | `thumbv7m-none-eabi` | |
| 85 | | Cortex-M4 (no FPU) | `thumbv7em-none-eabi` | |
| 86 | | Cortex-M4F / M7 | `thumbv7em-none-eabihf` | |
| 87 | | Cortex-M33 | `thumbv8m.main-none-eabihf` | |
| 88 | | RISC-V RV32IMAC | `riscv32imac-unknown-none-elf` | |
| 89 | |
| 90 | ```bash |
| 91 | rustup target add thumbv7em-none-eabihf |
| 92 | ``` |
| 93 | |
| 94 | ### 3. probe-rs — flash and debug |
| 95 | |
| 96 | ```bash |
| 97 | # Install probe-rs |
| 98 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh |
| 99 | |
| 100 | # Flash firmware |
| 101 | probe-rs run --chip STM32F411CEUx target/thumbv7em-none-eabihf/release/firmware |
| 102 | |
| 103 | # Interactive debug session |
| 104 | probe-rs debug --chip STM32F411CEUx target/thumbv7em-none-eabihf/release/firmware |
| 105 | |
| 106 | # List connected probes |
| 107 | probe-rs list |
| 108 | |
| 109 | # Supported chips |
| 110 | probe-rs chip list | grep STM32 |
| 111 | ``` |
| 112 | |
| 113 | With `cargo`: |
| 114 | |
| 115 | ```bash |
| 116 | # Using the runner in .cargo/config.toml |
| 117 | cargo run --release # builds, flashes, and streams defmt logs |
| 118 | cargo build --release # build only |
| 119 | ``` |
| 120 | |
| 121 | ### 4. defmt — efficient logging |
| 122 | |
| 123 | defmt (de-formatter) encodes log strings to integers, transmits minimal bytes, decodes on host: |
| 124 | |
| 125 | ```rust |
| 126 | use defmt::{info, warn, error, debug, trace, Format}; |
| 127 | |
| 128 | // Basic logging |
| 129 | info!("Temperature: {} °C", temp); |
| 130 | warn!("Stack usage: {}/{}", used, total); |
| 131 | error!("I2C error: {:?}", err); |
| 132 | |
| 133 | // Derive Format for custom types |
| 134 | #[derive(Format)] |
| 135 | struct Packet { id: u8, len: u16 } |
| 136 | |
| 137 | info!("Received: {:?}", pkt); |
| 138 | |
| 139 | // Assertions (panic with defmt message) |
| 140 | defmt::assert_eq!(result, expected); |
| 141 | defmt::assert!(condition, "message with {}", value); |
| 142 | ``` |
| 143 | |
| 144 | defmt backends (choose one): |
| 145 | |
| 146 | ```toml |
| 147 | # RTT (fastest, needs debug probe connected) |
| 148 | defmt-rtt = "0.4" |
| 149 | |
| 150 | # Semihosting (slower, works without RTT support) |
| 151 | defmt-semihosting = "0.1" |
| 152 | ``` |
| 153 | |
| 154 | ### 5. RTIC — Real-Time Interrupt-driven Concurrency |
| 155 | |
| 156 | ```rust |
| 157 | // Cargo.toml |
| 158 | // rtic = { version = "2", features = ["thumbv7-backend"] } |
| 159 | |
| 160 | #[rtic::app(device = stm32f4xx_hal::pac, peripherals = true, dispatchers = [SPI1])] |
| 161 | mod app { |
| 162 | use stm32f4xx_hal::{pac, prelude::*}; |
| 163 | use defmt::info; |
| 164 | |
| 165 | #[shared] |
| 166 | struct Shared { |
| 167 | counter: u32, |
| 168 | } |
| 169 | |
| 170 | #[local] |
| 171 | struct Local {} |
| 172 | |
| 173 | #[init] |
| 174 | fn init(cx: init::Context) -> (Shared, Local) { |
| 175 | info!("RTIC init"); |
| 176 | periodic_task::spawn().unwrap(); |
| 177 | (Shared { counter: 0 }, Local {}) |
| 178 | } |
| 179 | |
| 180 | #[task(shared = [counter])] |
| 181 | as |