跳到主要内容Rust 嵌入式开发实战:从 ARM 裸机编程到 RTOS 应用 | 极客日志Rust
Rust 嵌入式开发实战:从 ARM 裸机编程到 RTOS 应用
Rust 在嵌入式领域的开发实战,涵盖 ARM 裸机编程与 RTOS 应用。内容包含开发环境搭建(交叉编译工具链、OpenOCD、GDB)、裸机项目创建(Cargo.toml、memory.x 配置)、GPIO 操作、串口通信及中断处理。重点讲解 RTIC 框架实现多任务调度、资源共享与中断管理。通过 STM32F411RE 开发板案例,演示温湿度传感器读取等实战项目。涉及内存管理、栈溢出处理、寄存器初始化及硬件调试优化方法。
DevOpsTeam18 浏览 Rust 嵌入式开发实战:从 ARM 裸机编程到 RTOS 应用

一、学习目标与重点
1.1 学习目标
- 理解嵌入式开发基础:深入掌握嵌入式系统的定义、特点、架构(ARM、RISC-V),对比 Rust 与传统嵌入式开发语言(C/C++)的优势
- 搭建 Rust 嵌入式开发环境:安装交叉编译工具链(arm-none-eabi、riscv64-unknown-elf)、调试工具(OpenOCD、GDB),配置 VS Code/CLion 开发环境
- 掌握 Rust 裸机编程:使用
cortex-m、cortex-m-rt 库进行 ARM 裸机开发,实现 GPIO 操作、串口通信、中断处理
- 学习 RTOS 开发:使用 RTIC(Real-Time Interrupt-driven Concurrency)实现多任务编程,理解任务调度、资源共享、中断管理
- 实战嵌入式项目:结合 STM32F4xx 系列开发板、Raspberry Pi Pico,实现 LED 闪烁、温度传感器数据读取、I2C/SPI 通信、电机控制
- 优化与调试:学习 Rust 嵌入式代码的优化方法,使用 GDB/OpenOCD 进行硬件调试,解决内存泄漏、栈溢出等问题
1.2 学习重点
💡 三大核心难点:
- 内存管理:理解嵌入式系统的内存架构(Flash、RAM、寄存器),使用
core、alloc 库管理内存,避免内存泄漏
- 中断处理:掌握 ARM Cortex-M 的中断向量表、中断优先级,使用
cortex-m 库实现中断函数和临界区保护
- RTOS 调度:深入理解 RTIC 的任务调度机制(优先级调度、时间片轮询),解决资源共享时的互斥问题
⚠️ 三大高频错误点:
- 栈溢出:未正确设置栈大小,导致程序崩溃
- 寄存器未初始化:未正确初始化硬件寄存器,导致功能无法正常实现
- 时序问题:在 I2C/SPI 通信时,未正确处理时序,导致数据传输错误
二、嵌入式开发基础
2.1 嵌入式系统的定义与特点
嵌入式系统是一种以应用为中心、以计算机技术为基础、软件硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统的核心特点是:
- 资源受限:内存(RAM)、存储空间(Flash)、处理能力(CPU)有限
- 实时性要求高:对响应时间有严格要求(硬实时、软实时)
- 低功耗:通常使用电池供电,需要优化功耗
- 可靠性高:需要长时间稳定运行,无人工干预
- 专用性强:针对特定应用场景设计
2.2 嵌入式系统架构
嵌入式系统的架构通常分为以下几种:
- :程序和数据存放在同一内存空间,如 ARM Cortex-M0
冯·诺依曼架构
哈佛架构:程序和数据存放在不同的内存空间,如 ARM Cortex-M4、RISC-V2.3 Rust 在嵌入式开发中的优势
Rust 作为一门系统编程语言,非常适合嵌入式开发,其优势包括:
- 内存安全:通过所有权、借用、生命周期机制,在编译时检查内存访问错误,避免悬挂指针、内存泄漏
- 无垃圾回收:不需要垃圾回收器,减少内存开销和不确定性
- 高性能:执行速度接近 C/C++,适合实时性要求高的应用
- 类型安全:强类型系统,编译时检查类型错误
- 现代语言特性:支持泛型、模式匹配、异步编程等,提高开发效率
三、开发环境搭建
3.1 安装 Rust 交叉编译工具链
使用 rustup 安装 ARM 和 RISC-V 的交叉编译工具链:
rustup target add thumbv6m-none-eabi
rustup target add thumbv7m-none-eabi
rustup target add thumbv7em-none-eabi
rustup target add thumbv8m.base-none-eabi
rustup target add thumbv8m.main-none-eabi
rustup target add thumbv8m.main-none-eabihf
rustup target add riscv32imc-unknown-none-elf
rustup target add riscv64gc-unknown-none-elf
3.2 安装调试工具
3.2.1 安装 OpenOCD
OpenOCD 是开源的调试器和编程器,支持多种调试接口(JTAG、SWD)。
⌨️ Ubuntu/Debian 安装 OpenOCD:
sudo apt-get install openocd
⌨️ Windows 安装 OpenOCD:
下载 OpenOCD 的 Windows 版本,解压后添加到系统 PATH 中。
3.2.2 安装 GDB
GDB 是GNU 调试器,支持 ARM 和 RISC-V 架构的调试。
⌨️ Ubuntu/Debian 安装 ARM GDB:
sudo apt-get install gdb-multiarch
⌨️ Windows 安装 ARM GDB:
下载 ARM GDB 的 Windows 版本,解压后添加到系统 PATH 中。
3.3 配置 VS Code 开发环境
- Rust Analyzer:Rust 语言支持
- Cortex-Debug:ARM Cortex-M 调试支持
- OpenOCD:OpenOCD 调试支持
四、Rust 裸机编程基础
4.1 创建裸机项目
使用 cargo generate 创建一个新的 Rust 裸机项目:
cargo install cargo-generate
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart --name stm32f411re-demo
4.2 项目配置
4.2.1 Cargo.toml 配置
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "0.2"
[dependencies.stm32f4xx-hal]
version = "0.18"
features = ["stm32f411", "rt"]
4.2.2 memory.x 配置
在 memory.x 文件中配置 Flash 和 RAM 的地址和大小:
/* Linker script for STM32F411RE Nucleo */
MEMORY {
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
RAM : ORIGIN = 0x20000000, LENGTH = 128K
}
4.3 编写裸机代码
4.3.1 LED 闪烁
在 src/main.rs 文件中编写 LED 闪烁的代码:
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::timer::*;
#[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(84.MHz()).freeze();
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
let mut timer = dp.TIM2.timer(&clocks).start_ticker();
let mut systick = cp.SYST.counter_us(&clocks);
loop {
led.toggle();
systick.delay(500_000);
}
}
4.3.2 串口通信
在 src/main.rs 文件中添加串口通信的代码:
use stm32f4xx_hal::serial::*;
#[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(84.MHz()).freeze();
let gpioa = dp.GPIOA.split();
let tx = gpioa.pa2.into_alternate();
let rx = gpioa.pa3.into_alternate();
let mut serial = dp.USART2.serial((tx, rx), 9600.Bd(), &clocks).unwrap();
serial.listen(Event::Rxne);
serial.write_str("Hello, Rust Embedded!\n").unwrap();
loop {
if let Ok(byte) = nb::block!(serial.read()) {
serial.write(byte).unwrap();
}
}
}
4.4 编译与烧录
4.4.1 编译
cargo build --target thumbv7em-none-eabihf --release
4.4.2 烧录
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
gdb-multiarch target/thumbv7em-none-eabihf/release/stm32f411re-demo
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset run
五、RTOS 开发
5.1 RTIC 简介
RTIC(Real-Time Interrupt-driven Concurrency)是Rust 社区开发的实时操作系统框架,它基于中断驱动的并发模型,支持:
- 任务调度:优先级调度、时间片轮询
- 资源共享:通过
resource 宏实现互斥访问
- 中断管理:通过
interrupt 宏实现中断函数
- 通信机制:通过
channel 宏实现任务间通信
5.2 创建 RTIC 项目
使用 cargo generate 创建一个新的 RTIC 项目:
cargo generate --git https://github.com/rtic-rs/cortex-m-quickstart --name rtic-demo
5.3 编写 RTIC 代码
5.3.1 LED 闪烁与串口通信
在 src/main.rs 文件中编写 RTIC 代码:
#![no_std]
#![no_main]
use panic_halt as _;
use rtic::app;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::serial::*;
use stm32f4xx_hal::timer::*;
#[app(device = stm32f4xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
led: gpioc::PC13<Output<PushPull>>,
timer: Timer<pac::TIM2>,
serial: Serial<pac::USART2, (gpioa::PA2<Alternate<AF7>>, gpioa::PA3<Alternate<AF7>>), 9600>,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
let dp = cx.device;
let cp = cx.core;
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
let gpioc = dp.GPIOC.split();
let led = gpioc.pc13.into_push_pull_output();
let gpioa = dp.GPIOA.split();
let tx = gpioa.pa2.into_alternate();
let rx = gpioa.pa3.into_alternate();
let serial = dp.USART2.serial((tx, rx), 9600.Bd(), &clocks).unwrap();
serial.listen(Event::Rxne);
let timer = dp.TIM2.timer(&clocks).start_ticker();
serial.write_str("RTIC Demo Initialized!\n").unwrap();
init::LateResources { led, timer, serial }
}
#[task(binds = TIM2, resources = [led, timer])]
fn timer_interrupt(cx: timer_interrupt::Context) {
cx.resources.timer.clear_interrupt(TimerInterrupt::Update);
cx.resources.led.toggle();
}
#[task(binds = USART2, resources = [serial])]
fn serial_interrupt(cx: serial_interrupt::Context) {
cx.resources.serial.clear_interrupt(Event::Rxne);
if let Ok(byte) = nb::block!(cx.resources.serial.read()) {
cx.resources.serial.write(byte).unwrap();
}
}
};
六、真实案例应用
6.1 案例 1:STM32F411RE Nucleo 板——温湿度传感器数据读取
💡 场景分析:需要使用 STM32F411RE Nucleo 板连接 DHT11 温湿度传感器,读取温湿度数据,并通过串口发送到电脑。
6.1.1 硬件连接
| STM32F411RE | DHT11 |
|---|
| 5V | VCC |
| GND | GND |
| PB12 | DATA |
6.1.2 编写代码
在 src/main.rs 文件中添加温湿度传感器数据读取的代码:
#![no_std]
#![no_main]
use panic_halt as _;
use rtic::app;
use stm32f4xx_hal::gpio::*;
use stm32f4xx_hal::pac;
use stm32f4xx_hal::prelude::*;
use stm32f4xx_hal::serial::*;
use stm32f4xx_hal::timer::*;
struct DHT11 {
pin: gpiob::PB12<Input<Floating>>,
}
impl DHT11 {
fn new(pin: gpiob::PB12<Input<Floating>>) -> Self {
DHT11 { pin }
}
fn read(&mut self) -> Option<(u8, u8)> {
let mut data: [u8; 5] = [0; 5];
let mut pin = self.pin.into_push_pull_output();
pin.set_low();
cortex_m::asm::delay(18000);
pin.set_high();
cortex_m::asm::delay(30);
let pin = pin.into_floating_input();
while pin.is_high() {}
while pin.is_low() {}
while pin.is_high() {}
for i in 0..5 {
let mut byte = 0;
for j in 0..8 {
while pin.is_low() {}
cortex_m::asm::delay(40);
if pin.is_high() {
byte |= 1 << (7 - j);
}
while pin.is_high() {}
}
data[i] = byte;
}
let checksum = data[0] + data[1] + data[2] + data[3];
if data[4] == checksum {
Some((data[0], data[2]))
} else {
None
}
}
}
#[app(device = stm32f4xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
dht11: DHT11,
serial: Serial<pac::USART2, (gpioa::PA2<Alternate<AF7>>, gpioa::PA3<Alternate<AF7>>), 9600>,
timer: Timer<pac::TIM3>,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
let dp = cx.device;
let cp = cx.core;
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
let gpiob = dp.GPIOB.split();
let dht11 = DHT11::new(gpiob.pb12.into_floating_input());
let gpioa = dp.GPIOA.split();
let tx = gpioa.pa2.into_alternate();
let rx = gpioa.pa3.into_alternate();
let serial = dp.USART2.serial((tx, rx), 9600.Bd(), &clocks).unwrap();
serial.listen(Event::Rxne);
let timer = dp.TIM3.timer(&clocks).start_ticker();
init::LateResources { dht11, serial, timer }
}
#[task(binds = TIM3, resources = [dht11, serial])]
fn timer_interrupt(cx: timer_interrupt::Context) {
cx.resources.timer.clear_interrupt(TimerInterrupt::Update);
if let Some((temperature, humidity)) = cx.resources.dht11.read() {
let message = format!("温度: {}°C, 湿度: {}%\n", temperature, humidity);
cx.resources.serial.write_str(&message).unwrap();
} else {
cx.resources.serial.write_str("读取温湿度数据失败\n").unwrap();
}
}
#[task(binds = USART2, resources = [serial])]
fn serial_interrupt(cx: serial_interrupt::Context) {
cx.resources.serial.clear_interrupt(Event::Rxne);
if let Ok(byte) = nb::block!(cx.resources.serial.read()) {
cx.resources.serial.write(byte).unwrap();
}
}
};
七、常见问题与解决方案
7.1 栈溢出
问题现象:程序崩溃,调试器显示'HardFault'。
- 增大栈大小:在
memory.x 文件中修改 RAM 的大小或调整链接器脚本
- 减少栈使用:避免在中断函数中使用大量局部变量,避免递归调用
- 使用堆分配:对于大的数据结构,使用
alloc 库进行堆分配
7.2 寄存器未初始化
- 确保所有相关的寄存器都已正确初始化
- 查看芯片的参考手册,了解寄存器的功能和设置方法
- 使用 HAL 库提供的函数,避免直接操作寄存器
7.3 时序问题
- 调整通信频率:降低 I2C/SPI 的通信频率
- 检查时钟配置:确保系统时钟和外设时钟配置正确
- 使用硬件中断:使用硬件中断代替软件延时,提高时序精度
八、总结与展望
8.1 总结
✅ 理解了嵌入式开发基础:深入掌握了嵌入式系统的定义、特点、架构,对比了 Rust 与传统嵌入式开发语言的优势
✅ 搭建了 Rust 嵌入式开发环境:安装了交叉编译工具链、调试工具,配置了 VS Code 开发环境
✅ 掌握了 Rust 裸机编程:使用 cortex-m、cortex-m-rt 库进行 ARM 裸机开发,实现了 GPIO 操作、串口通信、中断处理
✅ 学习了 RTOS 开发:使用 RTIC 实现了多任务编程,理解了任务调度、资源共享、中断管理
✅ 实战了嵌入式项目:结合 STM32F4xx 系列开发板,实现了 LED 闪烁、温湿度传感器数据读取、串口通信
✅ 优化与调试:学习了 Rust 嵌入式代码的优化方法,使用 GDB/OpenOCD 进行硬件调试
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online