欢迎来到 Rust 嵌入式开发零基础入门教程的第三部分!在前面的教程中,我们已经搭建了开发环境,理解了 Rust 的核心概念,并准备好了真实的开发板。现在,是时候让你的 Rust 代码在硬件上跑起来,点亮一个 LED 了!
本节教程,我们将:
了解嵌入式项目的
Cargo.toml
配置。编写一个简单的 LED 闪烁程序。
烧录并运行程序到你的 STM32 开发板。
进行基本的调试。
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。
你需要做什么:
确定你的开发板上的 LED 连接到哪个 GPIO 引脚。
STM32 Nucleo-64 系列 (如 F401RE/F411RE): 板载绿色 LED 通常连接到 PA5 引脚。
其他板子: 请查阅你的开发板原理图或用户手册。
修改
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 毫秒
}
}
代码解释:
#![no_std]
和#![no_main]
: 再次强调,这是裸机嵌入式程序的标志。use panic_halt as _;
和use cortex_m_rt::entry;
: 同第一部分。use cortex_m::delay::Delay;
: 导入 Cortex-M 提供的基本延时功能。use stm32f4xx_hal::{pac, prelude::*};
:pac
: 外设访问层 (Peripheral Access Crate)。它提供了对芯片所有寄存器的底层、不安全的直接访问。HAL 库通常会建立在 PAC 之上。prelude::*
: 导入 HAL 库中常用的trait
,如constrain()
、into_push_pull_output()
等,让代码更简洁。
let dp = pac::Peripherals::take().unwrap();
: 获取对芯片所有外设 (Peripherals) 的唯一访问句柄。unwrap()
是因为take()
返回一个Option
类型,它只能被获取一次。let cp = cortex_m::Peripherals::take().unwrap();
: 获取对 Cortex-M 内核外设(如系统定时器 SYST)的唯一访问句柄。时钟配置 (
rcc
,clocks
): 微控制器需要精确的时钟来运行。这里我们配置了 RCC(复位和时钟控制)外设,设置系统时钟频率。这是嵌入式开发中非常重要的一步,因为所有外设的时序都依赖于正确的时钟配置。let mut delay = Delay::new(cp.SYST, clocks);
: 创建一个延时实例,它使用内核的系统定时器 (SYST) 和我们配置的时钟来提供精确的延时。let gpioa = dp.GPIOA.split();
: 获取对 GPIOA 外设的访问句柄,并使用split()
方法将其分解为独立的引脚。这是 HAL 库的一个常见模式,它允许你单独配置每个引脚。let mut led = gpioa.pa5.into_push_pull_output();
:gpioa.pa5
: 获取 GPIOA 端口的第 5 号引脚 (PA5)。into_push_pull_output()
: 将该引脚配置为推挽输出模式。这是最常见的输出模式,用于控制 LED、继电器等。mut
关键字表示led
是一个可变变量,因为我们需要改变它的状态(高电平/低电平)。
loop { ... }
: 一个无限循环,这是嵌入式程序的主循环,它会不断执行里面的代码。led.set_high();
和led.set_low();
: 这是 HAL 库提供的方法,用于设置 GPIO 引脚的电平。set_high()
: 将引脚设置为高电平 (通常是 3.3V 或 5V),点亮 LED。set_low()
: 将引脚设置为低电平 (0V),熄灭 LED。
delay.delay_ms(500_u32);
: 使用之前创建的延时实例,延时 500 毫秒。_u32
是一种类型后缀,明确表示 500 是一个u32
类型。
7.3 构建项目
保存 Cargo.toml
和 src/main.rs
文件后,在项目根目录(hello-cortex-m
)下,运行构建命令:
Bash
cargo build --release
如果一切顺利,编译将成功。生成的 .elf
文件(你的固件)将位于 target/thumbv7em-none-eabihf/release/hello-cortex-m
。
8. 烧录并运行程序到开发板
现在,我们把程序烧录到实际的开发板上。
确保你的开发板已通过 USB 线连接到电脑。
确保你已经正确安装了 ST-Link 驱动、OpenOCD 和
probe-run
。验证
.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
插件可以提供强大的调试能力。
安装 VS Code 插件:
打开 VS Code,前往扩展市场(
Ctrl+Shift+X
)。搜索并安装 "Cortex-Debug" 插件。
配置调试器 (launch.json): 如果你使用了
JSONcortex-m-quickstart
模板,你的项目根目录下的.vscode/launch.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.cfg
、interface/stlink-v3.cfg
。目标芯片文件:例如
target/stm32f4x.cfg
(针对 STM32F4 系列)。你可以在 OpenOCD 的安装目录下找到这些文件(通常在scripts/interface
和scripts/target
文件夹中)。
device
: 你的具体芯片型号,用于 GDB。svdFile
: 强烈推荐! SVD 文件描述了芯片内部的寄存器布局。下载你芯片对应的 SVD 文件(例如STM32F401.svd
),并在launch.json
中配置路径。这将允许你在调试时直接查看和修改寄存器值。
启动调试:
在 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)、或者中断处理等主题。