MMIO and Volatile Register Access / MMIO 与 Volatile 寄存器访问
What you’ll learn / 你将学到: Type-safe hardware register access in embedded Rust — volatile MMIO patterns, register abstraction crates, and how Rust’s type system can encode register permissions that C’s
volatilekeyword cannot.嵌入式 Rust 中的类型安全硬件寄存器访问 —— volatile MMIO 模式、寄存器抽象 crate,以及 Rust 的类型系统如何编码 C 语言中的
volatile关键字无法表示的寄存器权限。
- In C firmware, you access hardware registers via
volatilepointers to specific
-
- 在 C 语言固件中,你通过指向特定内存地址的
volatile指针来访问硬件寄存器。
- 在 C 语言固件中,你通过指向特定内存地址的
- memory addresses. Rust has equivalent mechanisms — but with type safety.
- Rust 具有等效的机制 —— 但具备类型安全性。
// C — typical MMIO register access
+// C — 典型的 MMIO 寄存器访问
#define GPIO_BASE 0x40020000
#define GPIO_MODER (*(volatile uint32_t*)(GPIO_BASE + 0x00))
#define GPIO_ODR (*(volatile uint32_t*)(GPIO_BASE + 0x14))
void toggle_led(void) {
- GPIO_ODR ^= (1 << 5); // Toggle pin 5
+ GPIO_ODR ^= (1 << 5); // Toggle pin 5 / 翻转引脚 5
}
#![allow(unused)]
fn main() {
// Rust — raw volatile (low-level, rarely used directly)
+// Rust — 原始 volatile(底层,极少直接使用)
use core::ptr;
const GPIO_BASE: usize = 0x4002_0000;
const GPIO_ODR: *mut u32 = (GPIO_BASE + 0x14) as *mut u32;
/// # Safety
- /// Caller must ensure GPIO_BASE is a valid mapped peripheral address.
+ /// Caller must ensure / 调用者必须确保 GPIO_BASE 是有效的映射外设地址。
unsafe fn toggle_led() {
- // SAFETY: GPIO_ODR is a valid memory-mapped register address.
+ // SAFETY / 安全说明:GPIO_ODR 是有效的内存映射寄存器地址。
let current = unsafe { ptr::read_volatile(GPIO_ODR) };
- unsafe { ptr::write_volatile(GPIO_ODR, current ^ (1 << 5)) };
+ unsafe { ptr::write_volatile(GPIO_ODR, current ^ (1 << 5)) }; // 翻转位 5
}
}
- In practice, you never write raw volatile pointers. Instead,
svd2rustgenerates
- 在实践中,你绝不会手动编写原始 volatile 指针。相反,
svd2rust会根据芯片的 SVD 文件(与 IDE 调试视图中使用的 XML 文件相同)生成 外设访问 Crate (PAC):
- a Peripheral Access Crate (PAC) from the chip’s SVD file (the same XML file used by
- your IDE’s debug view):
#![allow(unused)]
fn main() {
- // Generated PAC code (you don't write this — svd2rust does)
+ // Generated PAC code / 生成的 PAC 代码(不是你写的,是 svd2rust 生成的)
- // The PAC makes invalid register access a compile error
+ // PAC 将无效的寄存器访问变为编译错误
- // Usage with PAC:
+ // Usage with PAC / 配合 PAC 使用:
use stm32f4::stm32f401; // PAC crate for your chip / 芯片对应的 PAC crate
fn configure_gpio(dp: stm32f401::Peripherals) {
- // Enable GPIOA clock — type-safe, no magic numbers
+ // Enable GPIOA clock / 启用 GPIOA 时钟 —— 类型安全,无魔法数字
dp.RCC.ahb1enr.modify(|_, w| w.gpioaen().enabled());
- // Set pin 5 to output — can't accidentally write to a read-only field
+ // Set pin 5 to output / 将引脚 5 设为输出 —— 不会意外写入只读字段
dp.GPIOA.moder.modify(|_, w| w.moder5().output());
- // Toggle pin 5 — type-checked field access
+ // Toggle pin 5 / 翻转引脚 5 —— 类型检查过的字段访问
dp.GPIOA.odr.modify(|r, w| {
- // SAFETY: toggling a single bit in a valid register field.
+ // SAFETY / 安全说明:在有效寄存器字段中翻转单个位。
unsafe { w.bits(r.bits() ^ (1 << 5)) }
});
}
}
-| C register access | Rust PAC equivalent |
+| C register access / C 寄存器访问 | Rust PAC equivalent / Rust PAC 等价物 |
|—————––|———————|
-| #define REG (*(volatile uint32_t*)ADDR) | PAC crate generated by svd2rust |
-| #define REG (*(volatile uint32_t*)ADDR) | 由 svd2rust 生成的 PAC crate |
-| REG |= BITMASK; | periph.reg.modify(\|_, w\| w.field().variant()) |
-| REG |= BITMASK; | periph.reg.modify(...) |
-| value = REG; | let val = periph.reg.read().field().bits() |
-| value = REG; | let val = periph.reg.read()... |
-| Wrong register field → silent UB | Compile error — field doesn’t exist |
+| Wrong field / 错误字段 → silent UB | Compile error / 编译错误 —— 字段不存在 |
-| Wrong register width → silent UB | Type-checked — u8 vs u16 vs u32 |
+| Wrong width / 错误宽度 → silent UB | Type-checked / 类型检查 —— u8 vs u16 vs u32 |
- C firmware uses
__disable_irq()/__enable_irq()and ISR functions withvoid
- C 语言固件使用
__disable_irq()/__enable_irq()以及签名为void的 ISR 函数。
- signatures. Rust provides type-safe equivalents.
- Rust 提供了类型安全的等价方案。
// C — traditional interrupt handler
+// C — 传统中断处理器
volatile uint32_t tick_count = 0;
- void SysTick_Handler(void) { // Naming convention is critical — get it wrong → HardFault
+ void SysTick_Handler(void) { // Naming critical / 命名规范至关重要 —— 弄错会导致 HardFault
tick_count++;
}
uint32_t get_ticks(void) {
__disable_irq();
- uint32_t t = tick_count; // Read inside critical section
+ uint32_t t = tick_count; // Critical section / 在临界区内读取
__enable_irq();
return t;
}
#![allow(unused)]
fn main() {
// Rust — using cortex-m and critical sections
+// Rust — 使用 cortex-m 和临界区
use core::cell::Cell;
use cortex_m::interrupt::{self, Mutex};
- // Shared state protected by a critical-section Mutex
+ // Shared state / 由由临界区 Mutex 保护的共享状态
static TICK_COUNT: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
- #[cortex_m_rt::exception] // Attribute ensures correct vector table placement
+ #[cortex_m_rt::exception] // Ensures vector table / 属性确保正确放置在向量表中
- fn SysTick() { // Compile error if name doesn't match a valid exception
+ fn SysTick() { // Error if name mismatch / 如果名称与有效异常不匹配,则报错
- interrupt::free(|cs| { // cs = critical section token (proof IRQs disabled)
+ interrupt::free(|cs| { // cs = token / cs = 临界区令牌(证明 IRQ 已禁用)
let count = TICK_COUNT.borrow(cs).get();
TICK_COUNT.borrow(cs).set(count + 1);
});
}
fn get_ticks() -> u32 {
interrupt::free(|cs| TICK_COUNT.borrow(cs).get())
}
}
- For complex firmware with multiple interrupt priorities, RTIC (formerly RTFM) provides
- 对于具有多个中断优先级的复杂固件,RTIC(原名 RTFM)提供
- compile-time task scheduling with zero overhead:
- 零开销的编译时任务调度:
#![allow(unused)]
fn main() {
#[rtic::app(device = stm32f4xx_hal::pac, dispatchers = [USART1])]
mod app {
use stm32f4xx_hal::prelude::*;
#[shared]
struct Shared {
- temperature: f32, // Shared between tasks — RTIC manages locking
+ temperature: f32, // Shared / 任务间共享 —— RTIC 管理加锁
}
#[local]
struct Local {
led: stm32f4xx_hal::gpio::Pin<'A', 5, stm32f4xx_hal::gpio::Output>,
}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
let dp = cx.device;
let gpioa = dp.GPIOA.split();
let led = gpioa.pa5.into_push_pull_output();
(Shared { temperature: 25.0 }, Local { led })
}
- // Hardware task: runs on SysTick interrupt
+ // Hardware task / 硬件任务:在 SysTick 中断上运行
#[task(binds = SysTick, shared = [temperature], local = [led])]
fn tick(mut cx: tick::Context) {
cx.local.led.toggle();
cx.shared.temperature.lock(|temp| {
- // RTIC guarantees exclusive access here — no manual locking needed
+ // Exclusive access / RTIC 保证此处的排他性访问 —— 无需手动加锁
*temp += 0.1;
});
}
}
}
- Why RTIC matters for C firmware devs:
- 为什么 RTIC 对 C 语言固件开发者很重要:
-
- The
#[shared]annotation replaces manual mutex management
- The
-
#[shared]标注取代了手动的互斥锁管理。
-
- Priority-based preemption is configured at compile time — no runtime overhead
-
- 基于优先级的抢占是在编译时配置的 —— 无运行时开销。
-
- Deadlock-free by construction (the framework proves it at compile time)
-
- 结构上无死锁(框架在编译时证明了这一点)。
-
- ISR naming errors are compile errors, not runtime HardFaults
-
- ISR 命名错误会变为编译错误,而不是运行时的 HardFault。
- In C, when something goes wrong in firmware, you typically reset or blink an LED.
- 在 C 语言仪表中,当固件出错时,你通常会复位或闪烁 LED。
- Rust’s panic handler gives you structured control:
- Rust 的 panic 处理器为你提供了结构化的控制:
#![allow(unused)]
fn main() {
- // Strategy 1: Halt (for debugging — attach debugger, inspect state)
+ // Strategy 1: Halt / 策略 1:停机(用于调试 —— 连接调试器,检查状态)
use panic_halt as _; // Infinite loop on panic
- // Strategy 2: Reset the MCU
+ // Strategy 2: Reset / 策略 2:复位 MCU
use panic_reset as _; // Triggers system reset
- // Strategy 3: Log via probe (development)
+ // Strategy 3: Log via probe / 策略 3:通过探针记录日志(开发阶段)
use panic_probe as _; // Sends panic info over debug probe (with defmt)
- // Strategy 4: Log over defmt then halt
+ // Strategy 4: Log then halt / 策略 4:通过 defmt 记录日志后停机
use defmt_panic as _; // Rich panic messages over ITM/RTT
- // Strategy 5: Custom handler (production firmware)
+ // Strategy 5: Custom handler / 策略 5:自定义处理器(生产环境固件)
use core::panic::PanicInfo;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
- // 1. Disable interrupts to prevent further damage
+ // 1. Disable IRQs / 禁用中断以防止进一步损坏
cortex_m::interrupt::disable();
- // 2. Write panic info to a reserved RAM region (survives reset)
- // SAFETY: PANIC_LOG is a reserved memory region defined in linker script.
+ // 2. Write info / 将 panic 信息写入预留的 RAM 区域(复位后依然存在)
+ // SAFETY / 安全说明:PANIC_LOG 是链接脚本中定义的预留内存区域。
unsafe {
let log = 0x2000_0000 as *mut [u8; 256];
- // Write truncated panic message
+ // Write message / 写入截断的 panic 消息
use core::fmt::Write;
let mut writer = FixedWriter::new(&mut *log);
let _ = write!(writer, "{}", info);
}
- // 3. Trigger watchdog reset (or blink error LED)
+ // 3. Reset or Blink / 触发看门狗复位(或闪烁错误 LED)
loop {
- cortex_m::asm::wfi(); // Wait for interrupt (low power while halted)
+ cortex_m::asm::wfi(); // Wait for interrupt / 等待中断(停机时降低功耗)
}
}
}
- C firmware devs write linker scripts to define FLASH/RAM regions. Rust embedded
- C 语言固件开发者编写链接脚本来定义 FLASH/RAM 区域。
- uses the same concept via
memory.x:
- 嵌入式 Rust 通过
memory.x使用相同的概念:
- /* memory.x — placed at crate root, consumed by cortex-m-rt */
+ /* memory.x — placed at root / 放置在 crate 根目录,由 cortex-m-rt 使用 */
MEMORY
{
- /* Adjust for your MCU — these are STM32F401 values */
+ /* STM32F401 values / 针对你的 MCU 进行调整 —— 这些是 STM32F401 的值 */
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 96K
}
- /* Optional: reserve space for panic log (see panic handler above) */
+ /* Optional: panic log / 可选:为 panic 日志预留空间(见上文) */
_panic_log_start = ORIGIN(RAM);
_panic_log_size = 256;
- # .cargo/config.toml — set the target and linker flags
+ # .cargo/config.toml — 设置目标和链接标志
[target.thumbv7em-none-eabihf]
- runner = "probe-rs run --chip STM32F401RE" # flash and run via debug probe
+ runner = "probe-rs run --chip STM32F401RE" # 使用调试探针烧录并运行
rustflags = [
- "-C", "link-arg=-Tlink.x", # cortex-m-rt linker script
+ "-C", "link-arg=-Tlink.x", # cortex-m-rt 的链接脚本
]
[build]
- target = "thumbv7em-none-eabihf" # Cortex-M4F with hardware FPU
+ target = "thumbv7em-none-eabihf" # 带有硬件 FPU 的 Cortex-M4F
-| C linker script | Rust equivalent |
+| C linker script / C 链接脚本 | Rust equivalent / Rust 等价物 |
|—————–|—————–|
-| MEMORY { FLASH ..., RAM ... } | memory.x at crate root |
-| MEMORY { FLASH ..., RAM ... } | crate 根目录下的 memory.x |
-| __attribute__((section(".data"))) | #[link_section = ".data"] |
-| __attribute__((section(".data"))) | #[link_section = ".data"] |
-| -T linker.ld in Makefile | -C link-arg=-Tlink.x in .cargo/config.toml |
-| -T linker.ld in Makefile | .cargo/config.toml 中的链接标志 |
-| Startup assembly (startup.s) | cortex-m-rt #[entry] macro |
+| Startup (startup.s) | cortex-m-rt #[entry] macro / 宏 |
- The
embedded-halcrate defines traits for SPI, I2C, GPIO, UART, etc. Drivers
embedded-halcrate 定义了 SPI、I2C、GPIO、UART 等 trait。
- written against these traits work on any MCU — this is Rust’s killer feature
- 基于这些 trait 编写的驱动程序可以在任何 MCU 上运行 —— 这是 Rust 在嵌入式领域复用性方面的“杀手级特性”。
-
for embedded reuse.
-
C vs Rust: A Temperature Sensor Driver
// C — driver tightly coupled to STM32 HAL
+// C — 驱动程序与 STM32 HAL 紧耦合
#include "stm32f4xx_hal.h"
float read_temperature(I2C_HandleTypeDef* hi2c, uint8_t addr) {
uint8_t buf[2];
HAL_I2C_Mem_Read(hi2c, addr << 1, 0x00, I2C_MEMADD_SIZE_8BIT,
buf, 2, HAL_MAX_DELAY);
int16_t raw = ((int16_t)buf[0] << 4) | (buf[1] >> 4);
return raw * 0.0625;
}
- // Problem: This driver ONLY works with STM32 HAL. Porting to Nordic = rewrite.
+ // Problem / 问题:驱动仅适用于 STM32 HAL。移植到 Nordic = 重写。
#![allow(unused)]
fn main() {
// Rust — driver works on ANY MCU that implements embedded-hal
+// Rust — 驱动程序适用于任何实现 embedded-hal 的 MCU
use embedded_hal::i2c::I2c;
pub struct Tmp102<I2C> {
i2c: I2C,
address: u8,
}
impl<I2C: I2c> Tmp102<I2C> {
pub fn new(i2c: I2C, address: u8) -> Self {
Self { i2c, address }
}
pub fn read_temperature(&mut self) -> Result<f32, I2C::Error> {
let mut buf = [0u8; 2];
self.i2c.write_read(self.address, &[0x00], &mut buf)?;
let raw = ((buf[0] as i16) << 4) | ((buf[1] as i16) >> 4);
Ok(raw as f32 * 0.0625)
}
}
- // Works on STM32, Nordic nRF, ESP32, RP2040 — any chip with an embedded-hal I2C impl
+ // Works on STM32, Nordic, ESP32, RP2040 — any MCU / 适用于 STM32、nRF、ESP32、RP2040 以及任何具有 I2C 实现的芯片
}
graph TD
- subgraph "C Driver Architecture"
+ subgraph "C Driver Architecture / C 驱动架构"
- CD["Temperature Driver"]
+ CD["Temperature Driver / 温度驱动"]
- CD --> STM["STM32 HAL"]
+ CD --> STM["STM32 HAL"]
- CD -.->|"Port = REWRITE"| NRF["Nordic HAL"]
+ CD -.->|"Port = REWRITE / 移植 = 重写"| NRF["Nordic HAL"]
- CD -.->|"Port = REWRITE"| ESP["ESP-IDF"]
+ CD -.->|"Port = REWRITE / 移植 = 重写"| ESP["ESP-IDF"]
end
- subgraph "Rust embedded-hal Architecture"
+ subgraph "Rust embedded-hal Architecture / Rust embedded-hal 架构"
- RD["Temperature Driver<br/>impl<I2C: I2c>"]
+ RD["Temperature Driver / 温度驱动<br/>impl<I2C: I2c>"]
- RD --> EHAL["embedded-hal::I2c trait"]
+ RD --> EHAL["embedded-hal::I2c trait"]
- EHAL --> STM2["stm32f4xx-hal"]
+ EHAL --> STM2["stm32f4xx-hal"]
- EHAL --> NRF2["nrf52-hal"]
+ EHAL --> NRF2["nrf52-hal"]
- EHAL --> ESP2["esp-hal"]
+ EHAL --> ESP2["esp-hal"]
- EHAL --> RP2["rp2040-hal"]
+ EHAL --> RP2["rp2040-hal"]
- NOTE["Write driver ONCE,<br/>runs on ALL chips"]
+ NOTE["Write ONCE / 一次编写<br/>runs on ALL / 运行在所有芯片上"]
end
style CD fill:#ffa07a,color:#000
@@ -194,11 +195,11 @@
style NOTE fill:#91e5a3,color:#000
- The
alloccrate gives youVec,String,Box— but you need to tell Rust
alloccrate 为你提供了Vec、String、Box—— 但你需要告诉 Rust
- where heap memory comes from. This is the equivalent of implementing
malloc()
- 堆内存从何处而来。这相当于为你的平台实现
malloc():
- for your platform:
#![allow(unused)]
#![no_std]
fn main() {
@@ -211,21 +212,21 @@
// Initialize the allocator with a memory region
// (typically a portion of RAM not used by stack or static data)
+ // 初始化带有内存区域的分配器(通常是栈和静态数据之外的 RAM 区域)
{
const HEAP_SIZE: usize = 4096;
static mut HEAP_MEM: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
- // SAFETY: HEAP_MEM is only accessed here during init, before any allocation.
+ // SAFETY / 安全说明:HEAP_MEM 仅在分配开始前的初始化期间在此处访问。
unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
}
- // Now you can use heap types!
+ // Now use heap types / 现在可以使用堆类型了!
let mut log_buffer: Vec<u8> = Vec::with_capacity(256);
let name: String = String::from("sensor_01");
- // ...
+ // // ...
loop {}
}
}
-| C heap setup | Rust equivalent |
+| C heap setup / C 堆设置 | Rust equivalent / Rust 等价物 |
|———––|—————–|
-| _sbrk() / custom malloc() | #[global_allocator] + Heap::init() |
-| _sbrk() / custom malloc() | #[global_allocator] + Heap::init() |
-| configTOTAL_HEAP_SIZE (FreeRTOS) | HEAP_SIZE constant |
-| configTOTAL_HEAP_SIZE (FreeRTOS) | HEAP_SIZE 常量 |
-| pvPortMalloc() | alloc::vec::Vec::new() — automatic |
-| pvPortMalloc() | 自动完成 |
-| Heap exhaustion → undefined behavior | alloc_error_handler → controlled panic |
+| Exhaustion / 堆耗尽 → UB | alloc_error_handler → controlled panic |
- Real projects (like a large Rust workspace) often have:
- 实际项目(如大型 Rust 工作区)通常包含:
-
no_stdlibrary crates for hardware-portable logic
-
- 包含硬件可移植逻辑的
no_std库 crate
- 包含硬件可移植逻辑的
-
stdbinary crates for the Linux application layer
-
- 针对 Linux 应用层的
std二进制 crate
- 针对 Linux 应用层的
workspace_root/
@@ -242,12 +243,12 @@
└── src/main.rs # Uses std::fs, std::net, etc.
- The key pattern: the
protocolcrate uses#![no_std]so it compiles for both
- 关键点在于:
protocolcrate 使用了#![no_std],因此它可以同时为
- the MCU firmware and the Linux host tool. Shared code, zero duplication.
- MCU 固件和 Linux 主机工具编译。代码共享,零冗余。
- # protocol/Cargo.toml
+ # protocol/Cargo.toml / 协议 crate 的配置
[package]
name = "protocol"
@@ -255,11 +256,11 @@
default = []
std = [] # Optional: enable std-specific features when building for host
[dependencies]
serde = { version = "1", default-features = false, features = ["derive"] }
- # Note: default-features = false drops serde's std dependency
+ # Note / 注意:default-features = false 删除了 serde 对 std 的依赖
#![allow(unused)]
fn main() {
// protocol/src/lib.rs
#![cfg_attr(not(feature = "std"), no_std)]
@@ -274,48 +275,48 @@
pub value: i32,
pub fault_code: u16,
}
- // This function works in both no_std and std contexts
+ // Works in both / 此函数同时适用于 no_std 和 std 环境
pub fn parse_packet(data: &[u8]) -> Result<DiagPacket, &'static str> {
if data.len() < 8 {
return Err("packet too short");
}
Ok(DiagPacket {
sensor_id: u16::from_le_bytes([data[0], data[1]]),
value: i32::from_le_bytes([data[2], data[3], data[4], data[5]]),
fault_code: u16::from_le_bytes([data[6], data[7]]),
})
}
}
- Write a
no_stddriver for a hypothetical LED controller that communicates over SPI.
- 为一个通过 SPI 通信的虚拟 LED 控制器编写
no_std驱动程序。
- The driver should be generic over any SPI implementation using
embedded-hal.
- 驱动程序应对使用
embedded-hal的任何 SPI 实现泛型化。
- Requirements:
- 需求:
-
- Define a
LedController<SPI>struct
- Define a
-
- 定义
LedController<SPI>结构体
- 定义
-
- Implement
new(),set_brightness(led: u8, brightness: u8), andall_off()
- Implement
-
- 实现
new()、set_brightness(led: u8, brightness: u8)和all_off()
- 实现
-
- SPI protocol: send
[led_index, brightness_value]as 2-byte transaction
- SPI protocol: send
-
- SPI 协议:将
[led_index, brightness_value]作为 2 字节事务发送
- SPI 协议:将
-
- Write tests using a mock SPI implementation
-
- 使用 Mock SPI 实现编写测试
#![allow(unused)]
fn main() {
- // Starter code
+ // Starter code / 入门代码
#![no_std]
use embedded_hal::spi::SpiDevice;
pub struct LedController<SPI> {
spi: SPI,
num_leds: u8,
}
- // TODO: Implement new(), set_brightness(), all_off()
+ // TODO: Implement / 待办:实现相关方法
- // TODO: Create MockSpi for testing
+ // TODO: Create MockSpi / 待办:创建测试用的 MockSpi
}
Solution (click to expand)
Solution (click to expand) / 解决方案(点击展开)
#![allow(unused)]
#![no_std]
fn main() {
use embedded_hal::spi::SpiDevice;
@@ -324,11 +325,11 @@
}
pub fn set_brightness(&mut self, led: u8, brightness: u8) -> Result<(), SPI::Error> {
if led >= self.num_leds {
- return Ok(()); // Silently ignore out-of-range LEDs
+ return Ok(()); // Ignore out-of-range / 静默忽略越界引脚
}
self.spi.write(&[led, brightness])
}
pub fn all_off(&mut self) -> Result<(), SPI::Error> {
@@ -342,11 +343,11 @@
mod tests {
use super::*;
- // Mock SPI that records all transactions
+ // Mock SPI / 记录所有事务的 Mock SPI
struct MockSpi {
transactions: Vec<Vec<u8>>,
}
- // Minimal error type for mock
+ // Minimal error / Mock 使用的极简错误类型
#[derive(Debug)]
struct MockError;
impl embedded_hal::spi::Error for MockError {
@@ -402,11 +403,11 @@
</details>
- ## Debugging Embedded Rust — probe-rs, defmt, and VS Code
+ ## Debugging Embedded Rust — probe-rs, defmt, and VS Code / 嵌入式 Rust 调试 —— probe-rs、defmt 与 VS Code
- C firmware developers typically debug with OpenOCD + GDB or vendor-specific IDEs
+ C 固件开发者通常使用 OpenOCD + GDB 或厂商特定的 IDE(Keil、IAR、Segger Ozone)进行调试。
- (Keil, IAR, Segger Ozone). Rust's embedded ecosystem has converged on **probe-rs**
+ Rust 嵌入式生态系统已经趋向于使用 **probe-rs**
- as the unified debug probe interface, replacing the OpenOCD + GDB stack with a
+ 作为统一的调试探针接口,用一个 Rust 原生工具取代了 OpenOCD + GDB 栈。
- single, Rust-native tool.
- ### probe-rs — The All-in-One Debug Probe Tool
+ ### probe-rs — The All-in-One Debug Probe Tool / probe-rs —— 全能调试探针工具
- `probe-rs` replaces the OpenOCD + GDB combination. It supports CMSIS-DAP,
+ `probe-rs` 取代了 OpenOCD + GDB 的组合。它开箱即用地支持 CMSIS-DAP、
- ST-Link, J-Link, and other debug probes out of the box:
+ ST-Link、J-Link 等调试探针:
```bash
- # Install probe-rs (includes cargo-flash and cargo-embed)
+ # Install probe-rs / 安装 probe-rs (包含 cargo-flash 和 cargo-embed)
cargo install probe-rs-tools
- # Flash and run your firmware
+ # Flash and run / 烧录并运行固件
cargo flash --chip STM32F401RE --release
- # Flash, run, and open RTT (Real-Time Transfer) console
+ # Flash and RTT / 烧录、运行并打开 RTT(实时传输)控制台
cargo embed --chip STM32F401RE
}
- probe-rs vs OpenOCD + GDB:
- probe-rs vs OpenOCD + GDB 对比:
-| Aspect | OpenOCD + GDB | probe-rs |
+| Aspect / 维度 | OpenOCD + GDB | probe-rs |
|––––|–––––––|–––––|
-| Install | 2 separate packages + scripts | cargo install probe-rs-tools |
+| Install / 安装 | 2 separate pkgs / 两个独立包 + 脚本 | cargo install |
-| Config | .cfg files per board/probe | --chip flag or Embed.toml |
+| Config / 配置 | 针对开发板/探针的 .cfg 文件 | --chip 或 Embed.toml |
-| Console output | Semihosting (very slow) | RTT (~10× faster) |
+| Console / 控制台 | Semihosting (极慢) | RTT (约快 10 倍) |
-| Log framework | printf | defmt (structured, zero-cost) |
+| Log / 日志 | printf | defmt (结构化、零成本) |
-| Flash algorithm | XML pack files | Built-in for 1000+ chips |
-| Flash / 烧录 | XML pack 文件 | 内置支持 1000+ 芯片 |
-| GDB support | Native | probe-rs gdb adapter |
+| GDB support / 支持 | Native / 原生 | probe-rs gdb 适配器 |
- Instead of juggling
.cfgand.gdbinitfiles, probe-rs uses a single config:
- probe-rs 使用单一配置,省去了在
.cfg和.gdbinit文件之间周旋的烦恼:
- # Embed.toml — placed in your project root
+ # Embed.toml — 放置在项目根目录
[default.general]
chip = "STM32F401RETx"
[default.rtt]
- enabled = true # Enable Real-Time Transfer console
+ enabled = true # 启用 RTT 控制台
channels = [
{ up = 0, mode = "BlockIfFull", name = "Terminal" },
]
[default.flashing]
- enabled = true # Flash before running
+ enabled = true # 运行前进行烧录
restore_unwritten_bytes = false
[default.reset]
- halt_afterwards = false # Start running after flash + reset
+ halt_afterwards = false # 烧录并复位后开始运行
[default.gdb]
- enabled = false # Set true to expose GDB server on :1337
+ enabled = false # 设为 true 以在 1337 端口暴露 GDB 服务
gdb_connection_string = "127.0.0.1:1337"
- # With Embed.toml, just run:
+ # With Embed.toml / 有了配置后只需运行:
- cargo embed # Flash + RTT console — zero flags needed
+ cargo embed # 烧录 + RTT 控制台 —— 无需额外参数
- cargo embed --release # Release build
+ cargo embed --release # Release 构建
defmt(deferred formatting) replacesprintfdebugging. Format strings are
defmt取代了printf调试。格式化字符串存储在 ELF 文件中,而不是
- stored in the ELF file, not in flash — so log calls on the target send only
- FLASH 中 —— 因此目标机上的日志调用仅发送索引和参数字节。
- an index + argument bytes. This makes logging 10–100× faster than
printf
- 这使得日志记录比
printf快 10–100 倍,且仅消耗极小一部分 FLASH 空间:
- and uses a fraction of the flash space:
#![no_std]
#![no_main]
use defmt::{info, warn, error, debug, trace};
- use defmt_rtt as _; // RTT transport — links the defmt output to probe-rs
+ use defmt_rtt as _; // RTT transport / RTT 传输 —— 将 defmt 输出链接到 probe-rs
#[cortex_m_rt::entry]
fn main() -> ! {
info!("Boot complete, firmware v{}", env!("CARGO_PKG_VERSION"));
let sensor_id: u16 = 0x4A;
let temperature: f32 = 23.5;
- // Format strings stay in ELF, not flash — near-zero overhead
+ // Minimal overhead / 格式化字符串留在 ELF 中,不在 FLASH 中 —— 近乎零开销
debug!("Sensor {:#06X}: {:.1}°C", sensor_id, temperature);
if temperature > 80.0 {
warn!("Overtemp on sensor {:#06X}: {:.1}°C", sensor_id, temperature);
}
loop {
- cortex_m::asm::wfi(); // Wait for interrupt
+ cortex_m::asm::wfi(); // Wait for IRQ / 等待中断
}
}
- // Custom types — derive defmt::Format instead of Debug
+ // Custom types / 自定义类型 —— 派生 defmt::Format 替代 Debug
#[derive(defmt::Format)]
struct SensorReading {
id: u16,
value: i32,
status: SensorStatus,
}
#[derive(defmt::Format)]
enum SensorStatus {
Ok,
Warning,
Fault(u8),
}
- // Usage:
- // info!("Reading: {:?}", reading); // <-- uses defmt::Format, NOT std Debug
+ // info!("Reading: {:?}", reading); // <-- 使用 Format trait,而非 std Debug
- defmt vs
printfvslog:
- defmt vs
printfvslog对比:
-| Feature | C printf (semihosting) | Rust log crate | defmt |
+| Feature / 特性 | C printf | Rust log | defmt |
|———|———————––|—————––|———|
-| Speed | ~100ms per call | N/A (needs std) | ~1μs per call |
+| Speed / 速度 | ~100ms / call | N/A (needs std) | ~1μs / call |
-| Flash usage | Full format strings | Full format strings | Index only (bytes) |
-| Flash / 占用 | 全量格式化字符串 | 全量字符串 | 仅索引 (字节) |
-| Transport | Semihosting (halts CPU) | Serial/UART | RTT (non-blocking) |
-| Transport / 传输 | Semihosting (停掉 CPU) | 串口/UART | RTT (无阻塞) |
-| Structured output | No | Text only | Typed, binary-encoded |
-| 结构化输出 | ❌ 否 | 仅文本 | ✅ 有类型、二进制编码 |
-| no_std | Via semihosting | Facade only (backends need std) | ✅ Native |
+| no_std | Semihosting / 模拟 | Facade only / 门面 | ✅ Native / 原生 |
-| Filter levels | Manual #ifdef | RUST_LOG=debug | defmt::println + features |
+| Filter / 过滤 | 手动 #ifdef | 环境变量 | 通过特性开关 |
- With the
probe-rsVS Code extension, you get full graphical debugging —
- 通过
probe-rs的 VS Code 扩展,你可以获得全图形化调试 ——
- breakpoints, variable inspection, call stack, and register view:
- 断点、变量检查、调用栈以及寄存器视图:
- // .vscode/launch.json
+ // .vscode/launch.json / VS Code 启动配置
{
"version": "0.2.0",
"configurations": [
@@ -482,12 +483,12 @@
]
}
- Install the extension:
- 安装扩展:
#![allow(unused)]
fn main() {
ext install probe-rs.probe-rs-debugger
}
graph LR
- subgraph "C Workflow (Traditional)"
+ subgraph "C Workflow (Traditional) / C 传统工作流"
- C1["Write code"] --> C2["make flash"]
+ C1["Write / 编写代码"] --> C2["make flash / 烧录"]
- C2 --> C3["openocd -f board.cfg"]
+ C2 --> C3["openocd -f board.cfg"]
- C3 --> C4["arm-none-eabi-gdb<br/>target remote :3333"]
+ C3 --> C4["gdb<br/>target remote :3333"]
- C4 --> C5["printf via semihosting<br/>(~100ms per call, halts CPU)"]
+ C4 --> C5["printf via semihosting<br/>(~100ms/call, 停掉 CPU)"]
end
- subgraph "Rust Workflow (probe-rs)"
+ subgraph "Rust Workflow (probe-rs) / Rust 工作流"
- R1["Write code"] --> R2["cargo embed"]
+ R1["Write / 编写代码"] --> R2["cargo embed / 一键操作"]
- R2 --> R3["Flash + RTT console<br/>in one command"]
+ R2 --> R3["Flash + RTT console / 烧录 + RTT 控制台"]
- R3 --> R4["defmt logs stream<br/>in real-time (~1μs)"]
+ R4["defmt logs / 实时日志 (~1μs)"]
- R2 -.->|"Or"| R5["VS Code F5<br/>Full GUI debugger"]
+ R3 --> R4
+ R2 -.->|"Or / 或"| R5["VS Code F5 / 图形化调试"]
end
style C5 fill:#ffa07a,color:#000
@@ -496,25 +497,22 @@
style R5 fill:#91e5a3,color:#000
-| C Debug Action | Rust Equivalent |
+| C Debug Action / C 调试动作 | Rust Equivalent / Rust 等价物 |
|—————|—————–|
-| openocd -f board/st_nucleo_f4.cfg | probe-rs info (auto-detects probe + chip) |
+| openocd ... | probe-rs info (auto-detect / 自动检测) |
-| arm-none-eabi-gdb -x .gdbinit | probe-rs gdb --chip STM32F401RE |
+| gdb -x .gdbinit | probe-rs gdb --chip ... |
-| target remote :3333 | GDB connects to localhost:1337 |
-| target remote :3333 | GDB 连接至 localhost:1337 |
-| monitor reset halt | probe-rs reset --chip ... |
-| monitor reset halt | probe-rs reset ... |
-| load firmware.elf | cargo flash --chip ... |
-| load firmware.elf | cargo flash ... |
-| printf("debug: %d\n", val) (semihosting) | defmt::info!("debug: {}", val) (RTT) |
-| Keil/IAR GUI debugger | VS Code + probe-rs-debugger extension |
-| Keil/IAR GUI | VS Code + probe-rs 扩展 |
-| Segger SystemView | defmt + probe-rs RTT viewer |
-| Segger SystemView | defmt + probe-rs RTT 查看器 |
-
Cross-reference: For advanced unsafe patterns used in embedded drivers
-
交叉引用:有关嵌入式驱动中应用的高级 unsafe 模式(引脚映射、自定义 arena/slab 分配器),请参阅配套的《Rust 设计模式》指南中的“引脚映射 —— 结构化固定(Pin Projections — Structural Pinning)”和“自定义分配器 —— Arena 与 Slab 模式”章节。
-
(pin projections, custom arena/slab allocators), see the companion
-
Rust Patterns guide, sections “Pin Projections — Structural Pinning”
-
and “Custom Allocators — Arena and Slab Patterns.”