引言

嵌入式开发长期被 C/C++ 主导,但内存安全问题频发。Rust 凭借零成本抽象编译期内存安全,正在成为嵌入式开发的新选择。STM32、ESP32、nRF 等主流 MCU 都已支持 Rust。

本文从零开始,带你掌握嵌入式 Rust 开发的核心技能。

为什么选择嵌入式 Rust?

1.1 内存安全 without GC

// C 代码:可能的空指针解引用
int *ptr = get_sensor_data();
int value = *ptr;  // ❌ 如果 ptr 为空,崩溃

// Rust 代码:编译期检查
let data = get_sensor_data();
let value = *data;  // ✅ Option 类型强制处理 None 情况

1.2 所有权系统防止数据竞争

// 多任务访问共享资源
static mut SENSOR_DATA: u32 = 0;  // ❌ C 的全局变量,不安全

// Rust 使用 Mutex 保护共享数据
static SENSOR_DATA: Mutex<u32> = Mutex::new(0);  // ✅ 编译期保证线程安全

1.3 零成本抽象

//  trait 在编译期展开,无运行时开销
trait Sensor {
    fn read(&self) -> u16;
}

struct TemperatureSensor { /* ... */ }

impl Sensor for TemperatureSensor {
    fn read(&self) -> u16 {
        // 直接编译为高效机器码
    }
}

环境搭建

2.1 安装 Rust 工具链

# 安装 rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 添加嵌入式目标
rustup target add thumbv7em-none-eabihf  # Cortex-M4/M7
rustup target add thumbv6m-none-eabi     # Cortex-M0/M0+

# 安装必要工具
cargo install cargo-binutils
rustup component add llvm-tools-preview

2.2 项目模板

# 使用 cargo-generate 创建项目
cargo install cargo-generate
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart

项目结构

my-embedded-app/Cargo.toml# 依赖配置build.rs# 构建脚本memory.x# 内存布局src/main.rs# 入口peripherals.rs# 外设驱动
嵌入式 Rust 项目结构

2.3 Cargo.toml 配置

[package]
name = "my-embedded-app"
version = "0.1.0"
edition = "2021"

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
embedded-hal = "0.2"

# 芯片特定 PAC
stm32f4xx-hal = { version = "0.21", features = ["rt", "stm32f407"] }

[profile.release]
lto = true
debug = true

核心概念

3.1 外设访问

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;
use stm32f4xx_hal::{
    pac,
    prelude::*,
};

#[entry]
fn main() -> ! {
    // 获取外设寄存器
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();
    
    // 配置时钟
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(168.MHz()).freeze();
    
    // 配置 GPIO
    let gpioa = dp.GPIOA.split();
    let mut led = gpioa.pa5.into_push_pull_output();
    
    // 闪烁 LED
    loop {
        led.set_high();
        cortex_m::asm::delay(8_000_000);
        led.set_low();
        cortex_m::asm::delay(8_000_000);
    }
}

3.2 中断处理

use cortex_m_rt::exception;
use stm32f4xx_hal::pac::interrupt;

// 系统异常
#[exception]
fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! {
    // 记录错误信息
    loop {}
}

// 外部中断
#[interrupt]
fn EXTI0() {
    // 清除中断标志
    let dp = unsafe { pac::Peripherals::steal() };
    dp.EXTI.pr.write(|w| w.pr0().set_bit());
    
    // 处理中断逻辑
}

3.3 使用 embedded-hal trait

use embedded_hal::digital::v2::OutputPin;
use embedded_hal::blocking::delay::DelayMs;

// 编写通用驱动
pub struct LedDriver<PIN> {
    pin: PIN,
}

impl<PIN> LedDriver<PIN>
where
    PIN: OutputPin,
{
    pub fn blink<D>(&mut self, delay: &mut D, times: u8)
    where
        D: DelayMs<u8>,
    {
        for _ in 0..times {
            self.pin.set_high().unwrap();
            delay.delay_ms(500);
            self.pin.set_low().unwrap();
            delay.delay_ms(500);
        }
    }
}

实战项目:温湿度传感器

4.1 硬件连接

引脚STM32F4AHT20 传感器
SCLPB6SCL
SDAPB7SDA
VCC3.3VVDD
GNDGNDGND

4.2 I2C 驱动代码

use stm32f4xx_hal::{
    i2c::{I2c, DutyCycle, Mode},
    pac::I2C1,
};

pub struct AHT20<I2C> {
    i2c: I2C,
}

impl<I2C> AHT20<I2C>
where
    I2C: embedded_hal::blocking::i2c::WriteRead
       + embedded_hal::blocking::i2c::Write,
{
    pub fn new(i2c: I2C) -> Self {
        Self { i2c }
    }
    
    pub fn measure(&mut self) -> Result<(f32, f32), ()> {
        // 启动测量
        self.i2c.write(0x38, &[0xAC, 0x33, 0x00]).map_err(|_| ())?;
        
        // 等待转换完成
        cortex_m::asm::delay(80_000);
        
        // 读取数据
        let mut buf = [0u8; 6];
        self.i2c.write_read(0x38, &[0x71], &mut buf).map_err(|_| ())?;
        
        // 解析温湿度
        let humidity = ((buf[1] as u32) << 12 | (buf[2] as u32) << 4 | (buf[3] >> 4) as u32) as f32;
        let temperature = (((buf[3] & 0x0F) as u32) << 16 | (buf[4] as u32) << 8 | buf[5] as u32) as f32;
        
        Ok((
            humidity / 1048576.0 * 100.0,
            temperature / 1048576.0 * 200.0 - 50.0,
        ))
    }
}

4.3 主程序

#[entry]
fn main() -> ! {
    let dp = pac::Peripherals::take().unwrap();
    
    // 配置时钟
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(168.MHz()).freeze();
    
    // 配置 I2C
    let gpiob = dp.GPIOB.split();
    let scl = gpiob.pb6.into_alternate::<4>();
    let sda = gpiob.pb7.into_alternate::<4>();
    
    let i2c = I2c::new(
        dp.I2C1,
        (scl, sda),
        Mode::Fast {
            frequency: 400.kHz(),
            duty_cycle: DutyCycle::Ratio2to1,
        },
        clocks,
    );
    
    // 创建传感器
    let mut sensor = AHT20::new(i2c);
    
    loop {
        match sensor.measure() {
            Ok((humidity, temperature)) => {
                // 通过串口打印
                println!("温度:{:.2}°C, 湿度:{:.2}%", temperature, humidity);
            }
            Err(_) => {
                println!("传感器读取失败");
            }
        }
        
        cortex_m::asm::delay(80_000_000);  // 1 秒
    }
}

调试技巧

5.1 使用 defmt 日志

# Cargo.toml
[dependencies]
defmt = "0.3"
defmt-rtt = "0.4"

[profile.dev]
debug = 2

[profile.release]
debug = 2
#[defmt::global_logger]
struct Logger;

unsafe impl defmt::Logger for Logger {
    fn acquire() {}
    unsafe fn flush() {}
    unsafe fn release() {}
    unsafe fn write(_bytes: &[u8]) {}
}

#[entry]
fn main() -> ! {
    defmt::info!("程序启动!");
    
    let (temp, hum) = sensor.measure().unwrap();
    defmt::info!("温度 = {:.2}°C, 湿度 = {:.2}%", temp, hum);
    
    loop {}
}

5.2 使用 probe-rs 调试

# 安装 probe-rs
cargo install probe-rs --features cli

# 烧录程序
probe-rs run --chip STM32F407VGTx target/thumbv7em-none-eabihf/debug/my-app

# GDB 调试
probe-rs gdb --chip STM32F407VGTx

性能优化

6.1 启用 LTO 和代码优化

[profile.release]
lto = true          # 链接时优化
codegen-units = 1   # 单个编译单元
opt-level = "s"     # 优化体积(或"z"极致优化)

6.2 使用 heapless 容器

use heapless::{Vec, String};

// 无需 alloc 的动态容器
let mut data: Vec<u8, 32> = Vec::new();
data.push(42).unwrap();

let mut text: String<64> = String::new();
text.push_str("Hello").unwrap();

6.3 零成本抽象实践

// 使用泛型而非 trait object
fn process<S: Sensor>(sensor: &S) {  // ✅ 单态化,无运行时开销
    let data = sensor.read();
}

// 避免使用 Box<dyn Sensor>  // ❌ 需要 heap 和虚表

常见问题

Q1: 编译错误"undefined reference"

原因:缺少 memory.x 或链接脚本配置错误。

解决

// build.rs
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
    File::create(out.join("memory.x"))
        .unwrap()
        .write_all(include_bytes!("memory.x"))
        .unwrap();
    println!("cargo:rustc-link-search={}", out.display());
    println!("cargo:rerun-if-changed=memory.x");
}

Q2: 栈溢出

原因:默认栈大小不足。

解决

// Cargo.toml
[profile.release]
panic = "abort"

// memory.x 中增大栈
_STACK_SIZE = 8K;  // 默认 2K

Q3: 外设无法访问

原因:时钟未使能或权限问题。

解决

// 确保调用 take() 只获取一次外设
let dp = pac::Peripherals::take().unwrap();  // ✅
let dp = pac::Peripherals::take().unwrap();  // ❌ 第二次返回 None

总结

嵌入式 Rust 的核心优势:

  1. 编译期内存安全:无 GC,无运行时开销
  2. 所有权系统:防止数据竞争和悬垂指针
  3. 类型系统:强制处理错误情况
  4. 生态系统:embedded-hal 提供统一接口

2026 年,Rust 已成为嵌入式开发的重要选择。掌握它,让你的代码更安全、更高效!


本文基于 Rust 官方文档和 2026 年嵌入式 Rust 生态现状编写。

参考资料

  • The Embedded Rust Book: https://docs.rust-embedded.org/book/
  • cortex-m-quickstart 模板:https://github.com/rust-embedded/cortex-m-quickstart
  • embedded-hal 文档:https://docs.rs/embedded-hal
  • probe-rs 调试工具:https://probe.rs