嵌入式 Rust 开发
在 Rust 精通篇中,我们将深入探索 Rust 在嵌入式系统开发中的应用。Rust 的零成本抽象、内存安全和无运行时特性使其成为嵌入式开发的理想选择。在本章中,我们将学习如何在资源受限的环境中使用 Rust,掌握嵌入式 Rust 开发的核心概念和最佳实践。
嵌入式开发基础
什么是嵌入式系统?
嵌入式系统是专为特定功能设计的计算机系统,通常具有以下特点:
- 资源受限(内存、处理能力、能源等)
- 实时性要求(需要在确定的时间内响应)
- 直接与硬件交互
- 长时间运行且可靠性要求高
为什么选择 Rust 进行嵌入式开发?
Rust 在嵌入式开发中具有以下优势:
- 内存安全:编译时检查防止内存错误,无需垃圾收集
- 零成本抽象:高级抽象不带来运行时开销
- 精确控制:可以直接控制内存布局和硬件访问
- 丰富的工具链:强大的构建系统和包管理器
- 跨平台支持:支持多种嵌入式目标平台
嵌入式 Rust 开发环境
工具链设置
首先,我们需要为目标平台安装 Rust 工具链:
# 安装 rustup(如果尚未安装)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 安装嵌入式开发所需的组件
rustup component add llvm-tools-preview
rustup target add thumbv7m-none-eabi # ARM Cortex-M3
rustup target add thumbv7em-none-eabihf # ARM Cortex-M4F 和 M7F(带硬件浮点)
rustup target add riscv32imac-unknown-none-elf # RISC-V
# 安装辅助工具
cargo install cargo-binutils
cargo install cargo-embed # 用于烧录和调试
项目设置
创建一个新的嵌入式 Rust 项目:
# 创建新项目
cargo new --bin my_embedded_project
cd my_embedded_project
编辑 Cargo.toml
,添加必要的依赖:
[package]
name = "my_embedded_project"
version = "0.1.0"
edition = "2021"
# 不使用标准库
[dependencies]
cortex-m = "0.7.6" # Cortex-M 处理器支持
cortex-m-rt = "0.7.2" # Cortex-M 运行时
panic-halt = "0.2.0" # 发生 panic 时停止执行
[profile.release]
opt-level = "s" # 优化代码大小
lto = true # 链接时优化
codegen-units = 1 # 更好的优化,但编译更慢
debug = true # 在 release 构建中包含调试信息
panic = "abort" # 发生 panic 时直接中止,减小二进制大小
创建 .cargo/config.toml
文件,配置目标平台和链接器:
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-run --chip STM32F103C8" # 根据你的芯片调整
[build]
target = "thumbv7m-none-eabi" # Cortex-M3
[unstable]
build-std = ["core", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
裸机编程基础
无标准库开发
嵌入式 Rust 通常在 no_std
环境中运行,这意味着不能使用标准库:
#![no_std] // 不使用标准库
#![no_main] // 不使用标准的 main 函数入口
use cortex_m_rt::entry;
use panic_halt as _;
#[entry]
fn main() -> ! {
// 程序入口点
loop {
// 无限循环,防止程序退出
}
}
内存管理
在嵌入式系统中,内存管理尤为重要:
// 静态分配内存,避免堆分配
static mut BUFFER: [u8; 1024] = [0; 1024];
#[entry]
fn main() -> ! {
// 安全地访问静态可变变量
let buffer = unsafe { &mut BUFFER };
// 使用缓冲区
buffer[0] = 42;
loop {}
}
中断处理
处理硬件中断是嵌入式编程的核心部分:
use cortex_m::peripheral::NVIC;
use cortex_m_rt::exception;
#[entry]
fn main() -> ! {
// 获取外设访问
let peripherals = cortex_m::Peripherals::take().unwrap();
let mut nvic = peripherals.NVIC;
// 配置中断
unsafe {
// 启用 EXTI0 中断
nvic.enable(Interrupt::EXTI0);
}
loop {}
}
#[interrupt]
fn EXTI0() {
// 中断处理代码
// 注意:中断处理函数不能返回 !
}
外设访问
寄存器抽象
使用 svd2rust
生成的外设访问代码:
use stm32f1xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 获取特定外设
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
// 配置时钟
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 获取 GPIO 端口
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
// 配置 PC13 为推挽输出(许多 STM32 开发板上的 LED)
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
// 获取延时提供者
let mut delay = cp.SYST.delay(&clocks);
loop {
// 点亮 LED
led.set_low().ok();
delay.delay_ms(500_u16);
// 熄灭 LED
led.set_high().ok();
delay.delay_ms(500_u16);
}
}
使用 HAL 库
硬件抽象层(HAL)提供了更高级的外设访问接口:
use stm32f1xx_hal::{delay::Delay, pac, prelude::*};
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 获取特定外设
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
// 配置时钟
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 获取 GPIO 端口
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
// 配置 PC13 为推挽输出(许多 STM32 开发板上的 LED)
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
// 创建延时对象
let mut delay = Delay::new(cp.SYST, clocks);
loop {
// 点亮 LED
led.set_low().ok();
delay.delay_ms(500_u16);
// 熄灭 LED
led.set_high().ok();
delay.delay_ms(500_u16);
}
}
串口通信
实现串口通信是嵌入式系统的常见需求:
use core::fmt::Write;
use stm32f1xx_hal::{pac, prelude::*, serial::{Config, Serial}};
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 获取特定外设
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
// 配置时钟
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 获取 GPIO 端口
let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
// 配置 USART 引脚
let tx = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh);
let rx = gpioa.pa10.into_floating_input(&mut gpioa.crh);
// 配置串口
let mut serial = Serial::usart1(
dp.USART1,
(tx, rx),
&mut rcc.apb2,
Config::default().baudrate(9600.bps()),
clocks,
);
// 创建延时对象
let mut delay = cp.SYST.delay(&clocks);
loop {
// 发送消息
writeln!(serial.tx, "Hello, Rust embedded world!").ok();
delay.delay_ms(1000_u16);
}
}
实时操作系统 (RTOS)
RTIC 框架
RTIC (Real-Time Interrupt-driven Concurrency) 是 Rust 的实时并发框架:
#![no_std]
#![no_main]
use panic_halt as _;
use rtic::app;
#[app(device = stm32f1xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
led: stm32f1xx_hal::gpio::gpioc::PC13<stm32f1xx_hal::gpio::Output<stm32f1xx_hal::gpio::PushPull>>,
state: bool,
}
#[init]
fn init(ctx: init::Context) -> init::LateResources {
// 设备特定的初始化
let dp = ctx.device;
// 配置时钟和 GPIO
let mut rcc = dp.RCC.constrain();
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
// 配置 LED
let led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
// 配置系统定时器以每 1 秒触发一次中断
let mut syst = ctx.core.SYST;
syst.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core);
syst.set_reload(8_000_000); // 假设 8MHz 时钟
syst.enable_counter();
syst.enable_interrupt();
init::LateResources {
led,
state: false,
}
}
#[idle]
fn idle(_: idle::Context) -> ! {
loop {
// 等待中断
cortex_m::asm::wfi();
}
}
#[task(binds = SYS_TICK, resources = [led, state])]
fn sys_tick(ctx: sys_tick::Context) {
// 切换 LED 状态
if *ctx.resources.state {
ctx.resources.led.set_high().ok();
} else {
ctx.resources.led.set_low().ok();
}
// 更新状态
*ctx.resources.state = !*ctx.resources.state;
}
};
使用 Embassy
Embassy 是一个基于异步 Rust 的嵌入式框架:
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::time::Hertz;
use embassy_stm32::{interrupt, Config};
use embassy_time::{Duration, Timer};
use panic_halt as _;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
// 配置设备
let config = Config::default();
let p = embassy_stm32::init(config);
// 创建 LED 输出
let mut led = Output::new(p.PC13, Level::High, Speed::Low);
loop {
// 切换 LED 状态
led.toggle();
// 等待 500 毫秒
Timer::after(Duration::from_millis(500)).await;
}
}
嵌入式调试技术
使用 probe-rs
probe-rs 是一个现代化的嵌入式调试工具:
# 安装 probe-rs 工具
cargo install probe-rs-cli
# 烧录程序
probe-rs-cli download --chip STM32F103C8 target/thumbv7m-none-eabi/release/my_embedded_project
# 运行并调试程序
probe-rs-cli run --chip STM32F103C8 target/thumbv7m-none-eabi/release/my_embedded_project
日志和跟踪
使用 RTT (Real-Time Transfer) 进行日志记录:
use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
#[entry]
fn main() -> ! {
// 初始化 RTT
rtt_init_print!();
rprintln!("程序启动");
let mut counter = 0;
loop {
counter += 1;
rprintln!("计数: {}", counter);
// 延时
for _ in 0..1_000_000 {
cortex_m::asm::nop();
}
}
}
使用 defmt
defmt 是一个专为嵌入式系统设计的日志框架:
use defmt_rtt as _;
use panic_probe as _;
#[entry]
fn main() -> ! {
// 程序启动日志
defmt::info!("程序启动");
let mut counter = 0;
loop {
counter += 1;
defmt::debug!("计数: {}", counter);
// 延时
for _ in 0..1_000_000 {
cortex_m::asm::nop();
}
}
}
实际应用案例
温度传感器读取
使用 I2C 读取温度传感器数据:
use stm32f1xx_hal::{i2c::{BlockingI2c, DutyCycle, Mode}, pac, prelude::*};
// 传感器地址(示例)
const SENSOR_ADDR: u8 = 0x48;
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 配置时钟和 GPIO
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 配置 I2C 引脚
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
// 配置 I2C
let i2c = BlockingI2c::i2c1(
dp.I2C1,
(scl, sda),
&mut afio.mapr,
Mode::Fast {
frequency: 400_000.hz(),
duty_cycle: DutyCycle::Ratio2to1,
},
clocks,
&mut rcc.apb1,
1000,
10,
1000,
1000,
);
// 创建延时对象
let mut delay = cp.SYST.delay(&clocks);
// 读取寄存器的缓冲区
let mut buffer = [0u8; 2];
loop {
// 读取温度寄存器
match i2c.read(SENSOR_ADDR, &mut buffer) {
Ok(_) => {
// 处理温度数据(根据传感器规格)
let temp_raw = ((buffer[0] as u16) << 8) | buffer[1] as u16;
let temp_c = temp_raw as f32 * 0.0625; // 示例转换,根据实际传感器调整
// 在实际应用中,可以将温度发送到串口或显示在屏幕上
},
Err(_) => {
// 处理错误
}
}
// 每秒读取一次
delay.delay_ms(1000_u16);
}
}
简单的数字时钟
使用 OLED 显示屏实现数字时钟:
use embedded_graphics::{fonts::{Font6x8, Text}, pixelcolor::BinaryColor, prelude::*, style::TextStyle};
use embedded_hal::digital::v2::OutputPin;
use rtcc::Rtcc;
use ssd1306::{prelude::*, Builder, I2CDIBuilder};
use stm32f1xx_hal::{i2c::{BlockingI2c, DutyCycle, Mode}, pac, prelude::*};
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 配置时钟和 GPIO
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 配置 I2C 引脚
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
// 配置 I2C
let i2c = BlockingI2c::i2c1(
dp.I2C1,
(scl, sda),
&mut afio.mapr,
Mode::Fast {
frequency: 400_000.hz(),
duty_cycle: DutyCycle::Ratio2to1,
},
clocks,
&mut rcc.apb1,
1000,
10,
1000,
1000,
);
// 创建 OLED 显示器实例
let mut display = I2CDIBuilder::new().init(i2c);
display.init().unwrap();
display.clear().unwrap();
// 创建延时对象
let mut delay = cp.SYST.delay(&clocks);
// 模拟时钟(在实际应用中,可以使用 RTC 芯片)
let mut hours = 12;
let mut minutes = 0;
let mut seconds = 0;
loop {
// 清除显示
display.clear().unwrap();
// 显示时间
let time_str = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
Text::new(&time_str, Point::new(10, 20))
.into_styled(TextStyle::new(Font6x8, BinaryColor::On))
.draw(&mut display)
.unwrap();
// 更新显示
display.flush().unwrap();
// 更新时间
seconds += 1;
if seconds >= 60 {
seconds = 0;
minutes += 1;
if minutes >= 60 {
minutes = 0;
hours += 1;
if hours >= 24 {
hours = 0;
}
}
}
// 等待一秒
delay.delay_ms(1000_u16);
}
}
嵌入式 Rust 最佳实践
资源管理
- 静态分配:尽量使用静态分配而非动态分配
- 避免递归:递归可能导致栈溢出
- 限制栈使用:注意函数调用深度和局部变量大小
中断安全
- 最小化关键区域:中断禁用时间应尽可能短
- 使用原子操作:在可能的情况下使用原子操作而非锁
- 避免死锁:小心设计中断处理程序,避免死锁情况
电源管理
use stm32f1xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
// 获取外设访问
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 配置时钟和 GPIO
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// 创建延时对象
let mut delay = cp.SYST.delay(&clocks);
loop {
// 执行任务
perform_task();
// 进入低功耗模式
cortex_m::asm::wfi(); // 等待中断
// 或使用更深的睡眠模式
// unsafe { dp.PWR.cr.modify(|_, w| w.pdds().set_bit().lpds().set_bit()); }
// cortex_m::asm::wfi();
}
}
fn perform_task() {
// 执行必要的任务
}
代码组织
- 模块化设计:将代码分解为逻辑模块
- 硬件抽象层:创建硬件抽象层,使代码更易于移植
- 配置分离:将硬件配置与业务逻辑分离
// 硬件抽象模块
mod hal {
pub struct Led {
// 硬件特定实现
}
impl Led {
pub fn new() -> Self {
// 初始化硬件
Led { /* ... */ }
}
pub fn on(&mut self) {
// 硬件特定操作
}
pub fn off(&mut self) {
// 硬件特定操作
}
}
// 其他硬件抽象...
}
// 应用逻辑模块
mod app {
use super::hal::Led;
pub struct Blinker {
led: Led,
state: bool,
}
impl Blinker {
pub fn new(led: Led) -> Self {
Blinker {
led,
state: false,
}
}
pub fn toggle(&mut self) {
self.state = !self.state;
if self.state {
self.led.on();
} else {
self.led.off();
}
}
}
}
练习
- 创建一个基本的 LED 闪烁程序,使用不同的闪烁模式(例如 SOS 信号)
- 实现一个使用按钮控制 LED 的程序,包括去抖动处理
- 使用 I2C 或 SPI 与传感器通信,读取环境数据
- 创建一个简单的数字时钟,使用 OLED 显示屏显示时间
- 实现一个低功耗应用,在不活动时进入睡眠模式
通过本章的学习,你应该能够理解嵌入式 Rust 开发的核心概念,并能够开始在各种嵌入式平台上使用 Rust 进行开发。嵌入式 Rust 结合了 Rust 的安全性和性能,为嵌入式系统开发提供了强大