1、课程导学与环境准备:为什么选择Rust做嵌入式开发?
各位同学,欢迎来到《STM32移植Rust嵌入式HAL实战》的第一章。
我是你们这门课的老朋友。做了十几年嵌入式开发,从C到C++,再到这几年一头扎进Rust的世界,感触挺深的。今天咱们不急着敲代码,先聊聊为什么选Rust,再把吃饭的家伙——工具链和环境——给搭好。
1.1 为什么是Rust?
你可能会问:C语言用了这么多年,生态成熟,资料遍地,为什么要折腾Rust?
我个人的体会是:Rust解决了C语言里最让人头疼的两个问题——内存安全和数据竞争。
在嵌入式领域,这两个问题尤其致命。一个野指针,一个全局变量被中断和主循环同时修改,轻则跑飞,重则烧板子。我在项目中遇到过不止一次,排查起来真是欲哭无泪。
Rust的核心承诺:
- 内存安全:所有权和借用机制,编译期就帮你检查指针合法性,没有空悬指针、野指针。
- 无数据竞争:借用规则保证同一时刻只有一个写者或多个读者,不会出现并发修改。
- 零成本抽象:高级抽象(如迭代器、闭包)在编译后和手写C一样高效,不增加运行时开销。
- 现代工具链:包管理器Cargo、内置测试、格式化工具,开箱即用。
说白了,Rust把很多运行时才能暴露的问题,提前到编译期就给你堵死了。你想想看,这能省多少调试时间?
| 特性 | C语言 | Rust |
|---|---|---|
| 内存安全 | 依赖程序员经验,易出错 | 编译期强制检查 |
| 数据竞争 | 需手动加锁,易遗漏 | 借用规则自动保证 |
| 抽象开销 | 低,但抽象能力弱 | 零成本抽象,功能强大 |
| 包管理 | 手动管理,混乱 | Cargo,自动依赖解析 |
| 生态成熟度 | 极高 | 快速增长中 |
当然,Rust也有学习曲线。但一旦跨过那个坎,你会发现写嵌入式代码变成了一件很安心的事。
1.2 安装Rust工具链
好,理论说完了,咱们动手。第一步,装Rust。
- 安装rustup:Rust的版本管理器和工具链安装器。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
运行后按提示选择默认安装即可。装完后重启终端,或者执行 source $HOME/.cargo/env 让环境变量生效。
- 验证安装:
rustc --version
cargo --version
看到版本号输出,说明装好了。
- 添加ARM交叉编译目标:STM32是ARM Cortex-M内核,我们需要交叉编译。
rustup target add thumbv7m-none-eabi # 适用于STM32F1系列(Cortex-M3)
# 如果你用STM32F4(Cortex-M4),用这个:
rustup target add thumbv7em-none-eabi
# 如果你用STM32F0(Cortex-M0),用这个:
rustup target add thumbv6m-none-eabi
小提示: 不确定你的芯片用哪个target?查一下芯片手册里的内核型号,或者直接看STM32CubeMX里的Device页面。我一般先装全了,省得后面换芯片时再折腾。
1.3 配置STM32开发环境:OpenOCD和GDB
有了Rust,我们还需要一个调试器来烧录和调试。我习惯用OpenOCD + GDB的组合,免费、强大、跨平台。
1.3.1 安装OpenOCD
OpenOCD负责和调试器(如ST-Link、J-Link)通信,把程序烧进芯片,并提供GDB服务。
- Ubuntu/Debian:
sudo apt install openocd
- macOS:
brew install openocd
- Windows:去 https://openocd.org/ 下载安装包,或者用MSYS2、scoop等包管理器。
装完后验证:
openocd --version
1.3.2 安装GDB(ARM版本)
GDB是调试器,我们需要ARM交叉编译版本的GDB。
- Ubuntu/Debian:
sudo apt install gdb-multiarch
- macOS:
brew install arm-none-eabi-gdb
- Windows:可以从ARM官网下载GNU Arm Embedded Toolchain,里面包含arm-none-eabi-gdb。
注意: 我曾经在Windows上踩过一个坑——OpenOCD和GDB的路径里有空格,导致启动脚本报错。建议把安装路径改成纯英文、无空格的目录,比如 C:\tools\openocd。
1.4 创建第一个Rust嵌入式项目
环境搭好了,我们来写个经典的“点灯”程序。这是嵌入式界的Hello World。
1.4.1 使用cargo-generate创建项目
我推荐用现成的模板,省去手动配置的麻烦。这里用 cortex-m-quickstart 模板。
# 安装cargo-generate
cargo install cargo-generate
# 生成项目
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
按提示输入项目名称,比如 stm32-blink。
1.4.2 配置芯片型号
进入项目目录,打开 .cargo/config.toml,修改编译目标:
[target.thumbv7m-none-eabi]
# 替换为你的芯片型号
rustflags = ["-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tdefmt.x"]
[build]
target = "thumbv7m-none-eabi"
然后打开 memory.x 文件,根据你的芯片修改Flash和RAM大小。以STM32F103C8为例:
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
1.4.3 编写点灯代码
打开 src/main.rs,替换为以下内容:
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f1xx_hal::{
pac,
prelude::*,
};
#[entry]
fn main() -> ! {
// 获取外设访问权限
let dp = pac::Peripherals::take().unwrap();
let cp = cortex_m::Peripherals::take().unwrap();
// 初始化GPIO
let mut gpioc = dp.GPIOC.split();
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
// 初始化延时
let mut delay = cp.SYST.delay(&dp.RCC.clocks);
loop {
led.set_high().unwrap();
delay.delay_ms(1000u32);
led.set_low().unwrap();
delay.delay_ms(1000u32);
}
}
代码说明:
#![no_std]:告诉Rust不使用标准库,因为嵌入式环境没有操作系统支持。#![no_main]:不使用标准main函数入口,改用cortex_m_rt提供的入口。stm32f1xx_hal:STM32F1系列的硬件抽象层库,封装了寄存器操作。pc13:STM32F103C8板载LED通常接在PC13引脚。
1.4.4 编译和烧录
编译:
cargo build --release
烧录前,先启动OpenOCD。连接好ST-Link和开发板,在项目目录下执行:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg
保持OpenOCD窗口开着,另开一个终端,用GDB烧录:
arm-none-eabi-gdb -q target/thumbv7m-none-eabi/release/stm32-blink
(gdb) target remote :3333
(gdb) load
(gdb) continue
看到板载LED开始闪烁了吗?恭喜,你的第一个Rust嵌入式程序跑起来了!
避坑指南: 如果烧录时报错 target not halted,试试在GDB里先执行 monitor reset halt 再 load。我曾经被这个问题卡了半小时,后来发现是调试器供电不稳,换了个USB口就好了。
1.5 本章知识体系
下面这张图帮你梳理本章的核心脉络:
好了,第一章的内容就到这里。环境搭好了,项目跑起来了,后面我们就可以开始深入探索Rust嵌入式HAL的各个模块了。