rust嵌入式开发零基础入门教程(三)

发布于:2025-07-25 ⋅ 阅读:(20) ⋅ 点赞:(0)

欢迎来到 Rust 嵌入式开发零基础入门教程的第三部分!在前面的教程中,我们已经搭建了开发环境,理解了 Rust 的核心概念,并准备好了真实的开发板。现在,是时候让你的 Rust 代码在硬件上跑起来,点亮一个 LED 了!

本节教程,我们将:

  1. 了解嵌入式项目的 Cargo.toml 配置。

  2. 编写一个简单的 LED 闪烁程序。

  3. 烧录并运行程序到你的 STM32 开发板。

  4. 进行基本的调试。


6. 理解嵌入式项目的 Cargo.toml

在第二部分中,我们通过 cortex-m-quickstart 模板创建了一个 Rust 嵌入式项目。现在让我们深入了解一下这个项目中的 Cargo.toml 文件。它是 Rust 项目的核心配置文件,定义了项目的元数据、依赖项、构建配置等。

打开你项目根目录下的 Cargo.toml 文件(例如 hello-cortex-m/Cargo.toml)。它看起来可能像这样:

Ini, TOML

# hello-cortex-m/Cargo.toml

[package]
name = "hello-cortex-m"
version = "0.1.0"
authors = ["Your Name <your@email.com>"]
edition = "2021" # Rust 版本,推荐 2021

[dependencies]
# 嵌入式 Rust 的核心运行时
cortex-m-rt = "0.7.0"
# 用于在调试时打印信息(通过半主机模式)
cortex-m-semihosting = "0.5.0"
# 用于处理 panic,这里选择在 panic 时停止 CPU
panic-halt = "0.2.0"

# 针对你的具体芯片的 HAL (Hardware Abstraction Layer) 库
# 以 STM32F401RE 为例,你需要选择对应的 HAL 库
# 例如:stm32f4xx-hal = { version = "0.15.0", features = ["stm32f401", "rt"] }
# 请根据你的开发板芯片型号替换这里的 HAL 库
# 例如,如果是 STM32F411RE,可能需要 "stm32f411" feature
# 如果是 STM32F103C8T6 (俗称"蓝 pill"),可能需要 "stm32f103"
# 这里我们先注释掉,在后面点灯时再启用

# [features]
# default = ["stm32f4xx-hal/stm32f401"] # 如果你希望默认使用某个芯片型号

[profile.dev]
# 优化级别,0 表示不优化,方便调试
opt-level = 0

[profile.release]
# 发布版本优化级别,s 表示代码大小优化
opt-level = "s"

# 其他一些配置,例如二进制大小报告等
# [build-dependencies]
# cargo-binutils = "0.3.0" # 如果你想使用 binutils (objcopy, objdump)

关键部分解释:

  • [package]: 定义了项目的基本信息,如名称、版本、作者和 Rust 版本 (edition)。

  • [dependencies]: 核心部分,列出了项目所依赖的外部库 (crates)。

    • cortex-m-rt: 提供了 Cortex-M 微控制器的运行时启动代码,处理 CPU 初始化、中断向量表等。

    • cortex-m-semihosting: 实现了通过调试器将程序输出发送到主机的功能(我们在第一部分用它打印 "Hello World!")。

    • panic-halt: 定义了当 Rust 程序遇到不可恢复的错误(panic)时如何处理,这里是让 CPU 停止。在嵌入式环境中,你通常不会有完整的操作系统来打印错误信息,所以停止 CPU 或复位是常见的处理方式。

    • HAL (Hardware Abstraction Layer) 库: 这是最重要的部分。针对你的具体微控制器型号,你需要引入对应的 HAL 库。这些库封装了底层寄存器操作,提供了更高层次、更安全的 API 来控制外设(GPIO、定时器、串口等)。

      • 示例:stm32f4xx-hal: 这是一个常见的 STM32F4 系列的 HAL 库。features = ["stm32f401", "rt"] 中的 stm32f401 是指明你使用的具体芯片型号(例如 STM32F401RET6),rt 表示启用运行时支持。

  • [profile.dev][profile.release]: 定义了不同构建模式下的编译选项。

    • dev (开发模式): 优化级别通常较低 (opt-level = 0),以便快速编译和调试。

    • release (发布模式): 优化级别较高 (opt-level = "s""z"),以减小程序体积和提高运行速度。"s" 表示大小优化,"z" 表示更激进的大小优化。


7. 编写你的第一个 "Hello, LED!" 闪烁程序

现在,我们来编写点亮 LED 的程序。这个程序将循环点亮和熄灭开发板上的一个板载 LED。

你需要做什么:

  1. 确定你的开发板上的 LED 连接到哪个 GPIO 引脚。

    • STM32 Nucleo-64 系列 (如 F401RE/F411RE): 板载绿色 LED 通常连接到 PA5 引脚。

    • 其他板子: 请查阅你的开发板原理图或用户手册。

  2. 修改 Cargo.toml,引入正确的 HAL 库。

7.1 修改 Cargo.toml

STM32F401RE Nucleo-64 开发板为例,需要在 [dependencies] 下添加 stm32f4xx-hal 库。

Ini, TOML

# hello-cortex-m/Cargo.toml

[package]
name = "hello-cortex-m"
version = "0.1.0"
authors = ["Your Name <your@email.com>"]
edition = "2021"

[dependencies]
cortex-m-rt = "0.7.0"
cortex-m-semihosting = "0.5.0" # 暂时保留,调试时有用
panic-halt = "0.2.0"

# --- 新增/修改部分开始 ---
# 添加 STM32F4xx HAL 库,并启用对应的芯片型号特性
# 根据你的板子型号选择正确的 feature,例如 "stm32f401"
stm32f4xx-hal = { version = "0.15.0", features = ["stm32f401", "rt"] } # 如果是F411,则改成"stm32f411"
# --- 新增/修改部分结束 ---

[profile.dev]
opt-level = 0

[profile.release]
opt-level = "s"

重要: 请务必根据你自己的 STM32 芯片型号来选择 features 中的正确值。如果你不确定,可以查看 stm32f4xx-hal crate 的文档或 GitHub 仓库,它会列出所有支持的芯片型号。

7.2 编写 src/main.rs

现在,打开 src/main.rs 文件,并将其内容替换为以下代码:

Rust

#![no_std] // 不使用 Rust 标准库
#![no_main] // 不使用 Rust 的默认 main 函数

// 导入必要的库和宏
use panic_halt as _; // panic 时停止 CPU
use cortex_m_rt::entry; // Cortex-M 运行时入口
use cortex_m::delay::Delay; // 延时功能

// 导入你选择的 HAL 库
// 例如:stm32f4xx_hal 库
use stm32f4xx_hal::{pac, prelude::*}; // pac: 外设访问层, prelude: 常用 trait

#[entry]
fn main() -> ! {
    // 获取对外设的访问权限
    let dp = pac::Peripherals::take().unwrap();
    let cp = cortex_m::Peripherals::take().unwrap(); // Cortex-M 内核外设

    // 配置 RCC (复位和时钟控制)
    let rcc = dp.RCC.constrain();
    // 设置时钟频率,例如 HSE (外部高速晶振) 为 8MHz,系统时钟为 84MHz (F401RE 的默认最高频率)
    // 根据你的板子外部晶振和需求调整时钟配置
    let clocks = rcc.cfgr.use_hse(8.MHz()).sysclk(84.MHz()).freeze();

    // 初始化延时结构体
    let mut delay = Delay::new(cp.SYST, clocks);

    // 获取 GPIOA 外设(通常 LED 连接到 GPIOA)
    let gpioa = dp.GPIOA.split();

    // 配置 PA5 引脚为推挽输出模式
    // `into_push_pull_output` 是 HAL 库提供的方法
    let mut led = gpioa.pa5.into_push_pull_output();

    // 无限循环,闪烁 LED
    loop {
        led.set_high(); // 点亮 LED (输出高电平)
        delay.delay_ms(500_u32); // 延时 500 毫秒
        led.set_low();  // 熄灭 LED (输出低电平)
        delay.delay_ms(500_u32); // 延时 500 毫秒
    }
}

代码解释:

  1. #![no_std]#![no_main]: 再次强调,这是裸机嵌入式程序的标志。

  2. use panic_halt as _;use cortex_m_rt::entry;: 同第一部分。

  3. use cortex_m::delay::Delay;: 导入 Cortex-M 提供的基本延时功能。

  4. use stm32f4xx_hal::{pac, prelude::*};:

    • pac: 外设访问层 (Peripheral Access Crate)。它提供了对芯片所有寄存器的底层、不安全的直接访问。HAL 库通常会建立在 PAC 之上。

    • prelude::*: 导入 HAL 库中常用的 trait,如 constrain()into_push_pull_output() 等,让代码更简洁。

  5. let dp = pac::Peripherals::take().unwrap();: 获取对芯片所有外设 (Peripherals) 的唯一访问句柄。unwrap() 是因为 take() 返回一个 Option 类型,它只能被获取一次。

  6. let cp = cortex_m::Peripherals::take().unwrap();: 获取对 Cortex-M 内核外设(如系统定时器 SYST)的唯一访问句柄。

  7. 时钟配置 (rcc, clocks): 微控制器需要精确的时钟来运行。这里我们配置了 RCC(复位和时钟控制)外设,设置系统时钟频率。这是嵌入式开发中非常重要的一步,因为所有外设的时序都依赖于正确的时钟配置。

  8. let mut delay = Delay::new(cp.SYST, clocks);: 创建一个延时实例,它使用内核的系统定时器 (SYST) 和我们配置的时钟来提供精确的延时。

  9. let gpioa = dp.GPIOA.split();: 获取对 GPIOA 外设的访问句柄,并使用 split() 方法将其分解为独立的引脚。这是 HAL 库的一个常见模式,它允许你单独配置每个引脚。

  10. let mut led = gpioa.pa5.into_push_pull_output();:

    • gpioa.pa5: 获取 GPIOA 端口的第 5 号引脚 (PA5)。

    • into_push_pull_output(): 将该引脚配置为推挽输出模式。这是最常见的输出模式,用于控制 LED、继电器等。mut 关键字表示 led 是一个可变变量,因为我们需要改变它的状态(高电平/低电平)。

  11. loop { ... }: 一个无限循环,这是嵌入式程序的主循环,它会不断执行里面的代码。

  12. led.set_high();led.set_low();: 这是 HAL 库提供的方法,用于设置 GPIO 引脚的电平。

    • set_high(): 将引脚设置为高电平 (通常是 3.3V 或 5V),点亮 LED。

    • set_low(): 将引脚设置为低电平 (0V),熄灭 LED。

  13. delay.delay_ms(500_u32);: 使用之前创建的延时实例,延时 500 毫秒。_u32 是一种类型后缀,明确表示 500 是一个 u32 类型。

7.3 构建项目

保存 Cargo.tomlsrc/main.rs 文件后,在项目根目录(hello-cortex-m)下,运行构建命令:

Bash

cargo build --release

如果一切顺利,编译将成功。生成的 .elf 文件(你的固件)将位于 target/thumbv7em-none-eabihf/release/hello-cortex-m


8. 烧录并运行程序到开发板

现在,我们把程序烧录到实际的开发板上。

  1. 确保你的开发板已通过 USB 线连接到电脑。

  2. 确保你已经正确安装了 ST-Link 驱动、OpenOCD 和 probe-run

  3. 验证 .cargo/config.toml 中的 runner 配置与你的芯片型号匹配。

在项目根目录下,运行 probe-run 命令来烧录和运行:

Bash

cargo run --release

cargo run --release 的背后: cargo run 会首先编译你的项目(如果需要),然后查找 .cargo/config.toml 中为你的目标配置的 runner。在这里,runner = "probe-run --chip STM32F401RETx" 会被执行。 probe-run 会:

  • 连接到你的调试探针 (例如 ST-Link)。

  • 使用探针找到并连接到你的微控制器。

  • 将编译好的 .elf 固件烧录到微控制器的 Flash 内存中。

  • 启动程序执行。

  • 如果你的代码使用了 hprintln!,它会捕获半主机输出并显示在你的终端。


9. 进行基本的调试 (可选但推荐)

在嵌入式开发中,调试是不可或缺的。VS Code 配合 cortex-debug 插件可以提供强大的调试能力。

  1. 安装 VS Code 插件:

    • 打开 VS Code,前往扩展市场(Ctrl+Shift+X)。

    • 搜索并安装 "Cortex-Debug" 插件。

  2. 配置调试器 (launch.json): 如果你使用了 cortex-m-quickstart 模板,你的项目根目录下的 .vscode/launch.json 文件应该已经包含了一个基本的调试配置。它可能看起来像这样:

    JSON

    // .vscode/launch.json
    {
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Cortex-M Debug",
                "cwd": "${workspaceRoot}",
                "executable": "./target/thumbv7em-none-eabihf/debug/hello-cortex-m", // 注意这里是 debug 版本
                "request": "launch",
                "type": "cortex-debug",
                "servertype": "openocd",
                // 请根据你的调试探针选择正确的 OpenOCD 接口配置
                // 例如:stlink-v2.cfg 或 stlink-v3.cfg
                "interface": "swd",
                "device": "STM32F401RE", // 你的芯片型号,用于 GDB
                "configFiles": [
                    "interface/stlink-v2.cfg", // 你的调试器配置文件
                    "target/stm32f4x.cfg"     // 你的芯片配置文件
                ],
                "svdFile": "path/to/your/STM32F401.svd", // 可选:用于寄存器查看
                "runToMain": true,
                "preLaunchTask": "Cargo Build (debug)" // 确保先构建 debug 版本
            }
        ]
    }
    
    • 重要调整:

      • executable: 确保路径正确,指向你的 debug 构建版本(因为调试通常在 debug 模式下进行)。

      • interface: 根据你的调试器类型选择,例如 swd (SWD 接口)。

      • configFiles: 这是最容易出错的地方。 你需要根据你的 ST-Link 版本芯片系列 来选择正确的 OpenOCD 配置文件。

        • ST-Link 接口文件:通常是 interface/stlink.cfg 或更具体的 interface/stlink-v2.cfginterface/stlink-v3.cfg

        • 目标芯片文件:例如 target/stm32f4x.cfg (针对 STM32F4 系列)。你可以在 OpenOCD 的安装目录下找到这些文件(通常在 scripts/interfacescripts/target 文件夹中)。

      • device: 你的具体芯片型号,用于 GDB。

      • svdFile: 强烈推荐! SVD 文件描述了芯片内部的寄存器布局。下载你芯片对应的 SVD 文件(例如 STM32F401.svd),并在 launch.json 中配置路径。这将允许你在调试时直接查看和修改寄存器值。

  3. 启动调试:

    • 在 VS Code 中打开 src/main.rs

    • 点击左侧的“运行与调试”图标(或按 Ctrl+Shift+D)。

    • 在顶部的下拉菜单中选择 "Cortex-M Debug" 配置。

    • 点击绿色的“开始调试”按钮(或按 F5)。

如果配置正确,VS Code 将连接到你的开发板,并将程序加载到微控制器中。你可以在代码中设置断点,单步执行,查看变量,甚至观察寄存器状态。


恭喜你!

你已经成功迈出了 Rust 嵌入式开发最重要的几步:理解项目结构、编写代码、烧录到真实硬件,并进行了初步的调试!

点亮 LED 是嵌入式开发的 "Hello, World!"。从这里开始,你可以尝试:

  • 改变 LED 闪烁的速度。

  • 尝试控制其他 GPIO 引脚(如果你的板子上有其他可控的 LED 或跳线)。

  • 阅读 stm32f4xx-hal 或你所用 HAL 库的文档,了解如何控制更多外设(如按钮、串口等)。

在接下来的教程中,我们可能会探讨更复杂的传感器读取、通信协议(如 I2C/SPI)、或者中断处理等主题。