1. 课程导论与开发环境搭建

各位同学,欢迎来到《Rust嵌入式HAL外设驱动性能调优与调试实战》的第一章。我是你们这门课的主讲,一个在嵌入式领域摸爬滚打了十几年的老工程师。说实话,从C语言转向Rust做嵌入式开发,我一开始也是抱着怀疑态度的。但真正用起来之后,嗯,真香。

这一章我们不急着写代码,先把地基打牢。我会带你看看Rust嵌入式生态到底长什么样,HAL和PAC这两个概念到底有什么区别,然后咱们把开发环境搭起来,最后跑一个点亮LED的程序——算是嵌入式世界的"Hello World"。

Rust嵌入式生态概览

先说说Rust在嵌入式领域的位置。你想想看,传统嵌入式开发用C,虽然灵活,但内存安全问题让人头疼。我当年做的一个项目,就因为一个野指针导致设备在现场死机,差点被客户骂到怀疑人生。Rust的所有权模型和类型系统,说白了就是帮你把这些问题在编译阶段就扼杀掉。

目前Rust嵌入式生态的核心组件包括:

组件 作用 典型库/工具
编译器与工具链 交叉编译、调试、烧录 rustup, cargo, probe-rs, cargo-flash
底层硬件抽象 直接操作寄存器 PAC (Peripheral Access Crate)
硬件抽象层 封装寄存器操作,提供高级接口 HAL (Hardware Abstraction Layer)
板级支持包 针对特定开发板的初始化配置 BSP (Board Support Package)
运行时与RTOS 任务调度、中断管理 RTIC, Embassy

我个人习惯把Rust嵌入式开发分成三个层次:最底层是PAC,直接跟寄存器打交道;中间层是HAL,把寄存器操作封装成更友好的接口;最上层是BSP和应用代码,你只需要关心业务逻辑。这种分层设计,说白了就是让你不用每次都从零开始写寄存器操作。

HAL与PAC的关系

很多初学者搞不清HAL和PAC的区别。我举个例子你就明白了:

  • PAC 就像芯片的数据手册,告诉你每个寄存器的地址、位域、读写规则。它很底层,很精确,但用起来很繁琐。
  • HAL 就像封装好的库函数,比如 set_pin_high(),你不需要知道它背后操作了哪个寄存器。

为什么会这样设计?因为PAC是自动生成的,由芯片厂商提供SVD文件,工具(比如svd2rust)直接生成Rust代码。而HAL是社区开发者手动封装的,提供了更人性化的接口。我在项目中通常的做法是:先用HAL快速开发,遇到性能瓶颈时再直接调用PAC操作寄存器。

核心原则:HAL是默认选择,PAC是性能调优时的利器。不要一开始就钻到寄存器层面,除非你明确知道HAL的抽象带来了性能损失。

目标硬件平台介绍:STM32F4

这门课我们选用STM32F4系列作为目标平台。为什么选它?三个原因:

  1. 生态成熟:STM32F4的Rust支持非常完善,HAL库和PAC库都很稳定。
  2. 性能适中:Cortex-M4内核带FPU,主频168MHz,足够我们做性能调优实验。
  3. 外设丰富:GPIO、UART、SPI、I2C、定时器、DMA一应俱全,适合做各种驱动开发。

具体到开发板,我推荐使用STM32F407VET6或STM32F429ZI。如果你手头有其他型号,也没关系,代码稍作修改就能适配。

小提示:如果你用的是STM32F4 Discovery板,板载的ST-Link调试器可以直接用probe-rs驱动,省去额外买调试器的钱。

工具链安装

好了,理论说完了,咱们开始动手。工具链安装分三步走:

1. 安装rustup

rustup是Rust的工具链管理器。打开终端,执行:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装完成后,添加ARM Cortex-M4的目标:

rustup target add thumbv7em-none-eabihf

这里要注意,thumbv7em-none-eabihf 中的 hf 表示硬件浮点支持,STM32F4带FPU,必须用这个目标。

2. 安装probe-rs

probe-rs是一个跨平台的调试和烧录工具,支持ST-Link、J-Link等常见调试器。安装命令:

cargo install probe-rs --features cli

安装完成后,用 probe-rs list 检查是否能识别到你的调试器。我曾经遇到过一次,插上ST-Link后probe-rs死活识别不到,后来发现是USB线的问题——嗯,这种坑踩过一次就记住了。

3. 安装cargo-flash

cargo-flash是probe-rs的一个子命令,专门用来烧录固件:

cargo install cargo-flash

烧录命令很简单:

cargo flash --chip STM32F407VGTx --release

注意:芯片型号要写对。STM32F4系列有很多变种,比如STM32F407VET6和STM32F407VGT6的Flash大小不同,烧录参数也不一样。用 probe-rs chip list | grep STM32F4 查看支持的型号列表。

第一个点亮LED的HAL程序

环境搭好了,我们来写第一个程序。这个程序会点亮开发板上的一个LED,用的是HAL库。

创建项目

cargo new --bin stm32f4-led-blink
cd stm32f4-led-blink

配置Cargo.toml

打开 Cargo.toml,添加依赖:

[dependencies]
cortex-m-rt = "0.7"
cortex-m-semihosting = "0.5"
panic-halt = "0.2"
stm32f4xx-hal = { version = "0.15", features = ["stm32f407"] }

[[bin]]
name = "stm32f4-led-blink"
path = "src/main.rs"

注意 features 字段要跟你手上的芯片型号匹配。我用的是STM32F407,所以写 stm32f407。如果你用F429,就改成 stm32f429。

编写main.rs

#![no_std]
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;
use stm32f4xx_hal::{
    prelude::*,
    pac,
    gpio::Output,
};

#[entry]
fn main() -> ! {
    // 获取外设实例
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap();

    // 初始化GPIO
    let gpiob = dp.GPIOB.split();
    let mut led = gpiob.pb0.into_push_pull_output();

    // 初始化系统时钟(使用外部晶振)
    let rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(168.mhz()).freeze();

    // 初始化定时器
    let mut timer = cp.SYST;
    timer.set_clock_source(cortex_m::peripheral::syst::SystClkSource::Core);
    timer.set_reload(168_000_000 / 2); // 0.5秒
    timer.enable_counter();

    loop {
        led.set_high().unwrap();
        while !timer.has_wrapped() {}
        timer.clear_current();

        led.set_low().unwrap();
        while !timer.has_wrapped() {}
        timer.clear_current();
    }
}

烧录运行

在项目根目录下执行:

cargo flash --chip STM32F407VGTx --release

如果一切顺利,你会看到LED以0.5秒的间隔闪烁。嗯,这就是你的第一个Rust嵌入式程序。

调试技巧:如果LED不亮,先检查GPIO引脚对不对。STM32F4 Discovery板的LED通常接在PD12-PD15或PB0-PB1上。用万用表量一下引脚电平,比盯着代码看效率高多了。

本章知识体系

为了让你更直观地理解本章的知识结构,我画了一张图:

Rust嵌入式开发环境搭建知识体系 目标:点亮LED 工具链安装 HAL与PAC理解 硬件平台认识 rustup → 目标添加 PAC: 寄存器操作 STM32F4 Cortex-M4 probe-rs / cargo-flash HAL: 高级接口封装 外设: GPIO/定时器 第一个HAL程序:LED闪烁 从工具链到硬件平台,最终汇聚到第一个HAL程序,形成完整闭环

这张图把本章的知识点串起来了。从工具链安装到硬件平台认识,再到HAL与PAC的理解,最终汇聚到第一个LED程序。你想想看,这个流程其实就是嵌入式开发的通用路径:搭环境 → 理解硬件 → 写代码 → 烧录验证。

好了,第一章的内容就到这里。环境搭好了,LED也亮了,接下来我们就可以开始真正的外设驱动开发了。记住,基础打得越牢,后面调优的时候就越从容。