用 Rust 开发 STM32H743:从零搭建一个能烧录、能看日志的项目

目标:在 Windows 上,用 Rust 从零搭出一个 STM32H743 工程------外置 25MHz 晶振,SYSCLK 跑到 480MHz,通过 RTT 输出日志,并用 PG2~PG6 跑流水灯。

全程使用 cargo 构建 + probe-rs 烧录,不碰 Keil / CubeIDE。


一、为什么用 Rust 写 STM32?

  • 内存安全 :no_std 下没有 GC,编译期杜绝空指针、缓冲区溢出等一整类问题。
  • 零成本抽象 :HAL 的封装在 --release 下基本等价于手写寄存器。
  • 类型安全的引脚与时钟 :引脚复用、时钟树在编译期校验,配错直接编译不过------这是 C 宏做不到的。
  • 生态 :stm32-rs 提供全系列 PAC(寄存器访问层)与配套 HAL,cargo 一条命令搞定依赖与构建。

代价是学习曲线略陡、生态不如 C 庞大。但对想认真做嵌入式的新项目,值得一试。


二、开发环境准备

2.1 Rust 工具链

powershell 复制代码
# 安装 rustup(已装可跳过)
# 添加 Cortex-M7 目标(H743 是 M7 + 双精度 FPU + DSP)
rustup target add thumbv7em-none-eabihf

# 建议顺手升级到最新版,避免依赖要求更高的 MSRV
rustup update stable

2.2 烧录 / 调试工具:probe-rs

probe-run 已经停止维护(其依赖 time crate 的新版本会因 MSRV 直接编译失败),现在统一用 probe-rs:

powershell 复制代码
cargo install probe-rs-tools

注意:probe-rs-tools 没有 cli 这个 feature,不要写成 --features cli。

2.3 调试器驱动(最容易卡住的一步)

调试器 probe-rs 需要的驱动 是否需要额外操作
ST-Link V2/V3 ST 官方驱动 一般免驱
CMSIS-DAP / DAP-Link Windows 原生 HID 免驱
J-Link WinUSB 需用 Zadig 替换 SEGGER 驱动

J-Link 默认安装的是 SEGGER 官方驱动,probe-rs 打不开。用 Zadig:

  1. Options → List All Devices
  2. 下拉框选中 J-Link
  3. 目标驱动选 WinUSB(或 libusb-win32)
  4. Replace Driver

代价:替换后 SEGGER 自家的 J-Link Commander / RTT Viewer 就不能用了,需要时再用 Zadig 切回来。

2.4 国内镜像(可选,强烈建议)

新建 C:\Users\<你>\.cargo\config.toml:

toml 复制代码
[source.crates-io]
replace-with = 'rsproxy-sparse'

[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"

[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"

[net]
git-fetch-with-cli = true

三、创建工程骨架

powershell 复制代码
cargo new stm32h743_rust_project
cd stm32h743_rust_project

最终目录结构:

text 复制代码
stm32h743_rust_project/
├── .cargo/
│   └── config.toml      # 目标芯片、链接参数、运行器
├── build.rs             # 让链接器找到 memory.x
├── memory.x             # 内存布局
├── Cargo.toml
└── src/
    └── main.rs

3.1 Cargo.toml

toml 复制代码
[package]
name = "stm32h743_rust_project"
version = "0.1.0"
edition = "2021"

[dependencies]
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
rtt-target = "0.5.0"
panic-rtt-target = "0.1.3"
stm32h7xx-hal = { version = "0.16.0", features = ["stm32h743v"] }

[[bin]]
name = "stm32h743_rust_project"
path = "src/main.rs"

[profile.release]
codegen-units = 1
debug = true
lto = true
opt-level = "s"

关键点:feature 用 stm32h743v 而不是 stm32h743。

  • stm32h743 对应早期硅片(Rev Y/X),最高 400MHz;
  • stm32h743v 对应 Rev V ,会自动带入 revision_v + rm0433,才能用 VOS Scale0 跑满 480MHz。

3.2 .cargo/config.toml

toml 复制代码
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# 用 probe-rs 烧录 + 自动回显 RTT
runner = "probe-rs run --chip STM32H743ZITx"

[target.thumbv7em-none-eabihf]
rustflags = [
  "-C", "link-arg=--nmagic",
  "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv7em-none-eabihf"
  • --chip STM32H743ZITx 里的型号按你板子实际芯片改(如 STM32H743VITx)。
  • -Tlink.x 是 cortex-m-rt 提供的链接脚本。

3.3 memory.x

ld 复制代码
/* STM32H743 内存布局
 *   FLASH  2MB   @ 0x08000000
 *   RAM    512KB @ 0x24000000   AXI SRAM,放 .data/.bss/堆/栈
 * 栈顶由 cortex-m-rt 自动放在 RAM 末尾。
 */
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 2048K
  RAM   : ORIGIN = 0x24000000, LENGTH = 512K
}

H743 还有 128KB 的 DTCM(@0x20000000,速度最快)。要单独用 DTCM 需自定义链接脚本,属于进阶内容,这里先用最简布局。

3.4 build.rs

rust 复制代码
//! 让链接器在工程根目录搜索 memory.x
//! cortex-m-rt 的 link.x 通过 `INCLUDE memory.x` 引用它
fn main() {
    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    println!("cargo:rustc-link-search={}", manifest_dir);
}

没有这个文件,链接会报 cannot find linker script memory.x。

3.5 src/main.rs

rust 复制代码
//! STM32H743 最小工程
//!
//! - 时钟:外置 25MHz 晶振 → PLL1 → SYSCLK 480MHz
//! - 输出:RTT 日志(`rprintln!`)+ PG2~PG6 流水灯
//!
//! 前提:芯片为 Revision V(DBGMCU_IDCODE = 0x2003_6450),支持 VOS Scale0。
//! 故 Cargo.toml 启用 `stm32h743v` feature(自动带入 revision_v + rm0433)。
//! PLL1 的 M/N/P 由 HAL 按目标频率反推,无需(也无法)手写。

#![no_std]
#![no_main]

use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print};
use stm32h7xx_hal::{gpio::ErasedPin, pac, prelude::*, rcc::PllConfigStrategy, time::Hertz};

/// 外置晶振频率 (MHz)
const HSE_MHZ: u32 = 25;
/// 目标系统主频 (MHz)
const SYSCLK_MHZ: u32 = 480;
/// 流水灯每步点亮时长 (ms)
const STEP_MS: u32 = 200;

#[cortex_m_rt::entry]
fn main() -> ! {
    rtt_init_print!();
    rprintln!("==== STM32H743 boot ====");

    let _cp = cortex_m::Peripherals::take().unwrap();
    let dp = pac::Peripherals::take().unwrap();

    // VOS Scale0:跑 480MHz 的必要条件
    let pwr = dp.PWR.constrain().vos0(&dp.SYSCFG).freeze();

    // HSE 25MHz → PLL1 → 480MHz(Iterative 策略自动求解分频)
    let ccdr = dp
        .RCC
        .constrain()
        .use_hse(Hertz::MHz(HSE_MHZ))
        .sys_ck(Hertz::MHz(SYSCLK_MHZ))
        .pll1_strategy(PllConfigStrategy::Iterative)
        .freeze(pwr, &dp.SYSCFG);

    let sysclk_hz = ccdr.clocks.sys_ck().to_Hz();
    rprintln!("SYSCLK = {} Hz", sysclk_hz);

    // PG2~PG6 推挽输出;.erase() 抹掉引脚编号泛型,便于放进同一数组
    let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG);
    let mut leds: [ErasedPin<_>; 5] = [
        gpiog.pg2.into_push_pull_output().erase(),
        gpiog.pg3.into_push_pull_output().erase(),
        gpiog.pg4.into_push_pull_output().erase(),
        gpiog.pg5.into_push_pull_output().erase(),
        gpiog.pg6.into_push_pull_output().erase(),
    ];
    // 全部熄灭(本板低电平点亮,高电平熄灭)
    for led in &mut leds {
        let _ = led.set_high();
    }

    // 流水灯:逐个点亮 STEP_MS 毫秒
    let step_cycles = sysclk_hz / 1000 * STEP_MS;
    let mut round = 0u32;
    loop {
        for led in &mut leds {
            let _ = led.set_low();
            cortex_m::asm::delay(step_cycles);
            let _ = led.set_high();
        }
        round += 1;
        rprintln!("round #{}", round);
    }
}

四、代码详解

4.1 #![no_std] / #![no_main] / #[entry]

裸机没有操作系统,不能用标准库、也没有 main 的启动逻辑。cortex-m-rt 提供启动代码(设置栈指针、初始化 .data/.bss),#[cortex_m_rt::entry] 标记真正的入口。

4.2 时钟:25MHz HSE → 480MHz

H743 的 PLL 参数受 VCO 频率范围约束(Rev V 上限 836MHz),手算 M/N/P 很容易踩坑。stm32h7xx-hal 的做法是只给目标频率,让 HAL 反推:

rust 复制代码
.use_hse(Hertz::MHz(25))            // 启用外置 25MHz 晶振
.sys_ck(Hertz::MHz(480))            // 目标主频 480MHz
.pll1_strategy(PllConfigStrategy::Iterative)  // 自动求解分频

注意 :PllConfig 结构体里没有 m/n/p 字段,HAL 也没有 pll1_m() 这类方法------别去手写分频参数,编译器会直接报 no method named。

而 480MHz 还依赖供电等级:

rust 复制代码
let pwr = dp.PWR.constrain().vos0(&dp.SYSCFG).freeze();
  • VOS Scale0 → 最高 480MHz(仅 Rev V 支持)
  • VOS Scale1(默认)→ 最高 400MHz

若芯片不是 Rev V,只能 Scale1 + 400MHz,否则运行时会断言失败。

4.3 RTT 日志输出

rtt-target 把日志写入芯片的 RTT 缓冲区,调试器经 SWD 读取,不占用串口:

rust 复制代码
rtt_init_print!();
rprintln!("SYSCLK = {} Hz", sysclk_hz);

panic-rtt-target 让 panic! 也走 RTT,崩溃时能在终端直接看到原因。

4.4 GPIO 流水灯

PG2~PG6 配置为推挽输出。into_push_pull_output() 返回的类型带有引脚编号泛型,直接放进数组会类型不一致;.erase() 抹掉编号后统一为 ErasedPin<_>,方便用循环批量操作。


五、编译、烧录、看日志

powershell 复制代码
# 仅编译,检查代码
cargo build --release

# 编译 + 烧录 + 自动启动 RTT(runner 已在 config.toml 配好)
cargo run --release

正常应看到:

text 复制代码
==== STM32H743 boot ====
SYSCLK = 480000000 Hz
round #1
round #2
round #3
...

同时 PG2~PG6 依次点亮,形成流水灯。

只想编译不想烧录时 ,把 .cargo/config.toml 里的 runner 行注释掉即可。


六、踩坑记录

坑 1:probe-run 装不上

复制代码
error: could not compile `time`

probe-run 已弃用,其依赖的 time 新版本 MSRV 过高。换用 probe-rs:

powershell 复制代码
cargo install probe-rs-tools

坑 2:probe-rs-tools 报 does not contain this feature: cli

该包没有 cli feature,去掉 --features cli。

坑 3:assertion failed: sys_d1cpre_ck <= sys_d1cpre_ck_max

供电等级不够。目标 480MHz 必须 VOS Scale0(.vos0(&dp.SYSCFG)),且需要 Rev V 芯片 + stm32h743v feature。

坑 4:Failed to open probe: ... use Zadig to install WinUSB

J-Link 还是 SEGGER 驱动。用 Zadig 替换为 WinUSB(见 2.3)。

坑 5:怎么确认芯片是不是 Rev V?

读 DBGMCU_IDCODE 寄存器。H743 的 DBGMCU 基址是 0x5C001000 (不是 F4 系列的 0xE0042000):

powershell 复制代码
probe-rs read --chip STM32H743ZITx b32 0x5C001000 1

输出 20036450 时:

  • DEV_ID = 0x450 → STM32H743/753
  • REV_ID = 0x2003 → Revision V(支持 480MHz)

版本对照:0x1001=Rev Z,0x1003=Rev Y,0x2001=Rev X,0x2003=Rev V。


七、小结

用 Rust 写 STM32H743 的完整链路其实很清爽:

  1. rustup target add thumbv7em-none-eabihf
  2. cargo install probe-rs-tools + 用 Zadig 给 J-Link 装 WinUSB
  3. 配 Cargo.toml / .cargo/config.toml / memory.x / build.rs
  4. 写 main.rs,cargo run --release 一键烧录 + RTT 看日志

难点集中在时钟 (VOS 等级 + PLL 策略)和调试器驱动两处,把这两点搞定,剩下就是纯粹的 Rust 了。

引脚、时钟频率、LED 极性请按自己板子调整;本文以 PG2~PG6、外置 25MHz 晶振为例。


参考

  • stm32h7xx-hal 文档与 pwr.rs / rcc 源码
  • STM32H742/743/750/753 勘误表 DM00368411(硅片版本编码)
  • STM32H743 参考手册 RM0433
  • probe-rs 官方文档
相关推荐
碧海银沙音频科技研究院2 小时前
基于炬芯ATS308X内置音乐铃声与扫频音频功能实现
单片机·嵌入式硬件
硅基造物主3 小时前
18.从 IP 到 MQTT:STM32 用 AT 指令驱动 ESP-12F 上云的完整链路(含全套实战代码 + 踩坑清单)
网络·stm32·单片机·嵌入式硬件·网络协议·tcp/ip
qq_406981033 小时前
大白话rust系列02原始数据类型
rust
霖霖7143 小时前
【ESP32笔记】SmartConfig一键配网
笔记·单片机·mcu·物联网
霖霖7143 小时前
【ESP32笔记】WIFI连接—STA模式
笔记·单片机·mcu·物联网
HRTOS3 小时前
HRTOS 4.0 实测:时间片模式与调度位模式的任务切换性能对比
经验分享·单片机·嵌入式硬件·51单片机
一个风轻云淡3 小时前
扒开系统调用的三层皮:从 getpid 到手写 int 0x80
stm32·单片机·嵌入式硬件
qq_401700414 小时前
CAN 开源库大全(嵌入式/上位机,适配STM32、Linux、Qt,附带Github地址)
linux·stm32·开源
BT-BOX4 小时前
2026最新Keil 5软件下载安装详细教程含MDK和C51(附下载链接)
stm32·proteus