引言
嵌入式开发长期被 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
项目结构
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 硬件连接
| 引脚 | STM32F4 | AHT20 传感器 |
|---|---|---|
| SCL | PB6 | SCL |
| SDA | PB7 | SDA |
| VCC | 3.3V | VDD |
| GND | GND | GND |
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 的核心优势:
- 编译期内存安全:无 GC,无运行时开销
- 所有权系统:防止数据竞争和悬垂指针
- 类型系统:强制处理错误情况
- 生态系统: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