rust-embedded
Skill1 starsChanged 6 months agoMITView on GitHub
Embedded Rust — no_std, bare-metal, microcontrollers. Use this skill when: - User writes #![no_std] or #![no_main] code - User targets ARM Cortex-M, RISC-V, ESP32, STM32, nRF, RP2040, or similar MCUs - User works with embedded-hal, RTIC, Embassy, or cortex-m crates - User asks about GPIO, SPI, I2C, UART, DMA, timers, or interrupts - User asks about linker scripts, memory layout, or .cargo/config.toml targets - User deals with volatile memory, register-level access, or PAC/HAL - User works with Embassy async executor on embedded targets - User asks about no_std alternatives for alloc, formatting, or collections Trigger for any embedded/no_std Rust question.
4 other projects use a version of this.
Adopted by
Most recently taken up by
- full-stack-skills/rust-skillstheir own version, first seen Oct 15, 2026
- botirkhaltaev/botir-skillstheir own version, first seen Jul 24, 2026
- Microbiosis/rust-claude-skillstheir own version, first seen May 28, 2026
- huiali/rust-skillstheir own version, first seen Feb 9, 2026
Other versions4 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 4
- huiali/rust-skills★ 47same name
- full-stack-skills/rust-skills★ 8same name
- botirkhaltaev/botir-skills★ 4same name
- Microbiosis/rust-claude-skills★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-concurrencySkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 3 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 269
Use the PAC for register access — never hand-roll *ptr = val:
- Distinctive: line 394
Start with GPIO blink before enabling clocks, DMA, networking, or RTOS features.
- Distinctive: line 395
Keep interrupt handlers bounded.
- Very specific: line 396
Use defmt/RTT logging in debug builds and size-check release builds.
- Distinctive: line 398
Test HAL-independent logic on host with normal unit tests.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/no_std.md
- references/drivers_hal.md
Embedded Rust
On embedded targets, safety guarantees matter even more — no OS to catch your mistakes.
The Embedded Rust Stack
Application code
│
HAL (Hardware Abstraction Layer)
│ e.g. stm32f4xx-hal, nrf52840-hal, esp-hal
PAC (Peripheral Access Crate)
│ e.g. stm32f4, nrf52840
cortex-m / riscv crates (CPU support)
│
HardwareMinimal no_std Binary
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _; // Defines panic handler
#[entry]
fn main() -> ! {
// Infinite loop — embedded programs don't return
loop {}
}# Cargo.toml
[package]
name = "my-firmware"
edition = "2021"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
# HAL for your specific chip:
stm32f4xx-hal = { version = "0.21", features = ["stm32f411"] }
[profile.release]
opt-level = "s" # Optimize for size
lto = true
codegen-units = 1
debug = true # Keep symbols for debugging# .cargo/config.toml
[build]
target = "thumbv7em-none-eabihf" # Cortex-M4F
[target.thumbv7em-none-eabihf]
rustflags = ["-C", "link-arg=-Tlink.x"]
runner = "probe-rs run --chip STM32F411CEUx"GPIO Example (embedded-hal)
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
// Configure clocks
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze();
// Configure GPIO
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
let button = gpioa.pa0.into_pull_down_input();
loop {
if button.is_high() {
led.set_high();
} else {
led.set_low();
}
}
}Embassy: Async Embedded
Embassy is the modern async framework for embedded Rust. No RTOS needed.
[dependencies]
embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread"] }
embassy-stm32 = { version = "0.2", features = ["stm32f411ce", "time-driver-tim2"] }
embassy-time = "0.4"#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_stm32::{gpio::{Level, Output, Speed}, time::Hertz};
use embassy_time::{Duration, Timer};
use panic_probe as _;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut led = Output::new(p.PC13, Level::High, Speed::Low);
loop {
led.set_high();
Timer::after(Duration::from_millis(500)).await;
led.set_low();
Timer::after(Duration::from_millis(500)).await;
}
}Embassy Multi-Task
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
spawner.spawn(blink_task(p.PC13)).unwrap();
spawner.spawn(uart_task(p.USART1, p.PA9, p.PA10)).unwrap();
// main can do work too or just wait
loop {
Timer::after(Duration::from_secs(60)).await;
}
}
#[embassy_executor::task]
async fn blink_task(pin: embassy_stm32::gpio::AnyPin) {
let mut led = Output::new(pin, Level::High, Speed::Low);
loop {
led.toggle();
Timer::after(Duration::from_millis(500)).await;
}
}RTIC: Real-Time Interrupt-Driven Concurrency
#[rtic::app(device = stm32f4xx_hal::pac, peripherals = true)]
mod app {
use stm32f4xx_hal::{gpio::*, prelude::*};
#[shared]
struct Shared {
led: PC13<Output<PushPull>>,
}
#[local]
struct Local {
button: PA0<Input<PullDown>>,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
let dp = cx.device;
let gpioc = dp.GPIOC.split();
let led = gpioc.pc13.into_push_pull_output();
let button = dp.GPIOA.split().pa0.into_pull_down_input();
(Shared { led }, Local { button })
}
#[task(binds = EXTI0, local = [button], shared = [led])]
fn button_pressed(cx: button_pressed::Context) {
cx.shared.led.lock(|led| led.toggle());
}
}no_std Alternatives
Common std types have no_std alternatives:
| Need | std | no_std alternative |
| Dynamic allocation | Vec, String, Box | alloc crate (requires allocator) |
| Fixed-size buffer | — | heapless::Vec, heapless::String |
| HashMap | HashMap | heapless::FnvIndexMap, hashbrown::HashMap |
| String formatting | format! | ufmt, write! to a buffer |
| Logging | log + env-logger | defmt (deferred formatting over RTT) |
| Random | rand | rand_core + hardware RNG |
| Float math | std::f32 | libm crate |
[dependencies]
heapless = "0.8" # Fixed-capacity data structures
defmt = "0.3" # Efficient logging for embedded
defmt-rtt = "0.4" # Transport over RTTuse heapless::Vec;
use heapless::String;
// Fixed-capacity Vec — no heap allocation
let mut buf: Vec<u8, 256> = Vec::new(); // Max 256 elements
buf.push(42).unwrap(); // Returns Err if full
// Fixed-capacity String
let mut s: String<64> = String::new();
s.push_str("hello").unwrap();defmt Logging
The embedded logging standard — serializes format strings on-device, decodes on host:
use defmt::{debug, error, info, warn};
info!("Initializing...");
debug!("Counter value: {}", count);
warn!("Low battery: {}%", battery_level);
error!("I2C error: {:?}", err);# .cargo/config.toml — add defmt runner
[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32F411CEUx"Volatile / Register Access
Use the PAC for register access — never hand-roll *ptr = val:
use stm32f4::stm32f411::Peripherals;
let dp = Peripherals::take().unwrap();
// Safe, typed register access via PAC
dp.RCC.ahb1enr.modify(|_, w| w.gpiocen().set_bit());
// Never do this (even if you know the address):
// *(0x40023830 as *mut u32) |= 1 << 2; // Wrong! Use PAC.For truly custom register access:
use core::ptr::{read_volatile, write_volatile};
const MY_REGISTER: *mut u32 = 0x4002_0000 as *mut u32;
unsafe {
// SAFETY: Register address is valid for this chip, single-core, no aliasing
write_volatile(MY_REGISTER, 0b1100);
let value = read_volatile(MY_REGISTER);
}Memory Layout
# memory.x (linker script for stm32f411)
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
}// Placing data in specific sections
#[link_section = ".ccmram"]
static mut DMA_BUFFER: [u8; 1024] = [0; 1024];
// Zero-cost static allocation
static STATE: cortex_m::interrupt::Mutex<
core::cell::RefCell<Option<State>>
> = cortex_m::interrupt::Mutex::new(core::cell::RefCell::new(None));Interrupt Safety Patterns
use cortex_m::interrupt::Mutex;
use core::cell::RefCell;
// Shared data between main and ISR
static SHARED: Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));
fn main() -> ! {
cortex_m::interrupt::free(|cs| {
*SHARED.borrow(cs).borrow_mut() = 42;
});
loop {}
}
#[interrupt]
fn TIM2() {
cortex_m::interrupt::free(|cs| {
let val = *SHARED.borrow(cs).borrow();
// use val
});
}Common Targets
| Target triple | CPU | Used for |
thumbv6m-none-eabi | Cortex-M0/M0+ | RP2040, nRF51 |
thumbv7m-none-eabi | Cortex-M3 | STM32F1xx |
thumbv7em-none-eabihf | Cortex-M4F/M7F | STM32F4xx, nRF52 |
riscv32imac-unknown-none-elf | RISC-V 32 | ESP32-C3, GD32VF103 |
xtensa-esp32-none-elf | Xtensa LX6 | ESP32 (needs esp toolchain) |
# Install targets
rustup target add thumbv7em-none-eabihf
rustup target add riscv32imac-unknown-none-elf
# Build for target
cargo build --target thumbv7em-none-eabihf --release
# Flash with probe-rs
probe-rs run --chip STM32F411CEUx target/thumbv7em-none-eabihf/release/firmwareAnti-Patterns
// Bad: blocking forever in an interrupt handler.
#[interrupt]
fn USART1() {
while !tx_ready() {}
send_byte(0x42);
}
// Good: keep ISRs short; signal work to the main loop or executor.
#[interrupt]
fn USART1() {
EVENTS.signal(Event::UartReady);
}// Bad: heap-dependent design in constrained no_std firmware.
let mut log = String::new();
// Good: fixed-capacity buffers make failure explicit.
let mut log: heapless::String<128> = heapless::String::new();Bring-Up Checklist
- Confirm target triple, linker script, memory layout, and probe configuration.
- Start with GPIO blink before enabling clocks, DMA, networking, or RTOS features.
- Keep interrupt handlers bounded; defer work to tasks or main loop.
- Use
defmt/RTT logging in debug builds and size-check release builds. - Model peripheral ownership so two drivers cannot own the same register block.
- Test HAL-independent logic on host with normal unit tests.
References
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

