抛弃 C 语言:在 STM32 上跑 Rust 是种什么体验?
前言:苦 C 久矣
作为一个嵌入式开发的“小白”,宏定义地狱、悬垂指针、复杂的构建系统 (Makefile/CMake)……每一次写代码都像是在走钢丝。稍微不注意一个 memcpy 溢出,整个系统就崩了,还找不到堆栈信息,真的让我头痛。
去年开始,Rust 官方宣布了对嵌入式设备的正式支持(Embedded Rust)。我怀着“为了更好的开发体验”的单纯目的,买了一块 STM32F4 Discovery 开发板,开始尝试用 Rust 点灯。
结论在最前面:真香。但是门槛真高。
1. 为什么要在单片机上用 Rust?
你可能会问,C 语言不是嵌入式的通过语言吗?为什么要换?
- 内存安全:没有 Buffer Overflow,没有 Null Pointer。对于需要长期稳定运行的工控设备来说,这简直是救命稻草。
- 现代构建系统:Cargo 完爆 Makefile/CMake。添加一个驱动库只需要在
Cargo.toml里加一行,而不是去 GitHub clone 下来手动配 include path。 - 零成本抽象:Rust 的高级特性(Trait、Enum、Iterator)在编译后会优化成和 C 一样高效的汇编。
2. 核心概念:PAC, HAL, BSP
Embedded Rust 的生态被分为三层,这比 C 语言的生态要清晰得多:
Layer 1: Peripheral Access Crate (PAC)
这是最底层。通过 svd2rust 工具,自动从芯片厂商提供的 SVD (System View Description) XML 文件生成 Rust 代码。
它提供了对寄存器的原始访问权限。
1 | |
Layer 2: Hardware Abstraction Layer (HAL)
这是中间层。它封装了寄存器操作,提供了类型安全的接口。
在 C 语言里,你可能会误把 GPIOA 的配置写到 GPIOB 上。但在 Rust 里,这根本编译不过! 因为 PA0 和 PB0 是不同的类型。
Layer 3: Board Support Package (BSP)
针对具体开发板的封装。比如 stm32f4-discovery crate,直接把板子上的 LED 定义好了。
3. 实战:Blinky (点灯)
我们跳过环境配置(其实就是装 rustup target add thumbv7em-none-eabihf),直接看代码。
这也是 Embedded Rust 最令人惊艳的地方:主板上的资源(Peripherals)也是受 Ownership 保护的。你拿走了 GPIOA,别人就不能再动它了。
1 | |
注意看 gpiod.pd12.into_push_pull_output()。这个函数不仅改变了引脚模式,还改变了类型。
- 之前的类型:
PD12<Input<Floating>> - 之后的类型:
PD12<Output<PushPull>>
这意味着,如果你试图在 Input 类型的引脚上调用 .set_high(),编译器会直接报错:method not found。这种编译期的状态机检查,能帮你规避掉 90% 的低级配置错误。
4. 并发陷阱:Interrupts 与 Mutex
在嵌入式里,我们经常要在中断服务函数 (ISR) 和主循环 (main) 之间共享数据。
在 C 语言里,我们通常用 volatile 并且手动开关中断。
在 Rust 里,所有权的优势再次体现。
如果你想共享数据,必须使用 cortex_m::interrupt::Mutex 配合 RefCell。这看起来很繁琐,但它强迫你思考:你在中断里访问这个数据时,确定主循环不会正在改写它吗?
graph TD
Main([Main Loop])
ISR([Interrupt Service Routine])
Resource[(Shared Global Data)]
Main -- Needs Critical Section --> Resource
ISR -- Needs Critical Section --> Resource
style Resource fill:#f9f
Rust 强制要求你在访问共享全局变量时,显式进入 Critical Section(关中断):
1 | |
5. 总结:痛并快乐着
优势:
- Safe: 只要不用
unsafe块,基本不会跑飞。 - Tooling: Cargo build, Cargo flash, Cargo doc,体验极佳。
- Abstraction: 可以在单片机上用
async/await(Embassy 框架),这是 C 语言无法想象的。
劣势:
- 学习曲线陡峭:你需要懂 Rust,还要懂芯片。
- 编译体积:如果不开启 LTO (Link Time Optimization),生成的 bin 文件可能比 C 大。
- 生态不全:常用芯片 (STM32/ESP32) 支持很好,但冷门芯片可能只有 PAC,没有 HAL。
总体来说,如果你是一个追求极致代码质量的嵌入式工程师,Rust 绝对值得你投入时间去学习。它也许不能马上替换掉你公司祖传的 C 代码基建,但在新项目里,它是目前最好的选择。