Rust嵌入式开发实战——从ARM裸机编程到RTOS应用

一、学习目标与重点
1.1 学习目标
1.2 学习重点
💡 三大核心难点:
⚠️ 三大高频错误点:
二、嵌入式开发基础
2.1 嵌入式系统的定义与特点
嵌入式系统是一种以应用为中心、以计算机技术为基础、软件硬件可裁剪、适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。嵌入式系统的核心特点是:
2.2 嵌入式系统架构
嵌入式系统的架构通常分为以下几种:
2.3 Rust在嵌入式开发中的优势
Rust作为一门系统编程语言,非常适合嵌入式开发,其优势包括:
三、开发环境搭建
3.1 安装Rust交叉编译工具链
使用rustup安装ARM和RISC-V的交叉编译工具链:
# 安装ARM Cortex-M0/M3/M4的交叉编译工具链
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
# 安装RISC-V的交叉编译工具链
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开发环境
安装以下VS Code扩展:
四、Rust裸机编程基础
4.1 创建裸机项目
使用cargo generate创建一个新的Rust裸机项目:
# 安装cargo generate
cargo install cargo-generate
# 使用cortex-m-quickstart模板创建项目
cargo generate –git https://github.com/rust-embedded/cortex-m-quickstart –name stm32f411re-demo
4.2 项目配置
4.2.1 Cargo.toml配置
在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() -> ! {
// 获取硬件抽象层(HAL)
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();
// 配置GPIO
let gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output();
// 配置定时器
let mut timer = dp.TIM2.timer(&clocks).start_ticker();
// 配置系统定时器(SysTick)
let mut systick = cp.SYST.counter_us(&clocks);
// LED闪烁
loop {
led.toggle();
systick.delay(500_000); // 延迟500ms
}
}
4.3.2 串口通信
在src/main.rs文件中添加串口通信的代码:
use stm32f4xx_hal::serial::*;
#[entry]
fn main() -> ! {
// 获取硬件抽象层(HAL)
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();
// 配置GPIO
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命令编译项目:
# 编译为ARM Cortex-M4架构
cargo build –target thumbv7em-none-eabihf –release
4.4.2 烧录
使用OpenOCD烧录程序:
# 连接开发板
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
# 在另一个终端中使用GDB烧录
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社区开发的实时操作系统框架,它基于中断驱动的并发模型,支持:
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 {
// 获取硬件抽象层(HAL)
let dp = cx.device;
let cp = cx.core;
// 配置系统时钟
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
// 配置GPIO
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);
// 切换LED状态
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 硬件连接
| 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::*;
// 定义DHT11传感器类型
struct DHT11 {
pin: gpiob::PB12<Input<Floating>>,
}
impl DHT11 {
// 初始化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); // 延迟18ms
pin.set_high();
cortex_m::asm::delay(30); // 延迟30us
// 等待DHT11响应
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); // 延迟40us
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 {
// 获取硬件抽象层(HAL)
let dp = cx.device;
let cp = cx.core;
// 配置系统时钟
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.sysclk(84.MHz()).freeze();
// 配置GPIO
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”。
解决方案:
7.2 寄存器未初始化
问题现象:硬件功能无法正常实现。
解决方案:
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进行硬件调试
8.2 展望
下一篇文章,我们将深入学习Rust的区块链开发,包括理解区块链的核心概念、Rust在区块链领域的优势、开发智能合约、实现区块链节点、编写DApp应用,通过这些知识我们将能够使用Rust开发高性能、安全的区块链应用。
