> 技术文档 > rust嵌入式开发零基础入门教程(三)

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



欢迎来到 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 \"]edition = \"2021\" # Rust 版本,推荐 2021[dependencies]# 嵌入式 Rust 的核心运行时cortex-m-rt = \"0.7.0\"# 用于在调试时打印信息(通过半主机模式)cortex-m-semihosting = \"0.5.0\"# 用于处理 panic,这里选择在 panic 时停止 CPUpanic-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 \"]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 时停止 CPUuse 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)、或者中断处理等主题。