☰
用 Rust 开发 STM32H743:从零搭建一个能烧录、能看日志的项目
2026/10/15 15:53:15 网站建设 项目流程

目标:在 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 工具链

# 安装 rustup(已装可跳过)# 添加 Cortex-M7 目标(H743 是 M7 + 双精度 FPU + DSP)rustup target add thumbv7em-none-eabihf# 建议顺手升级到最新版,避免依赖要求更高的 MSRVrustup update stable

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

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

cargo install probe-rs-tools

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

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

调试器probe-rs 需要的驱动是否需要额外操作
ST-Link V2/V3ST 官方驱动一般免驱
CMSIS-DAP / DAP-LinkWindows 原生 HID免驱
J-LinkWinUSB需用 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:

[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

三、创建工程骨架

cargo new stm32h743_rust_project cd stm32h743_rust_project

最终目录结构:

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

3.1 Cargo.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

[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

/* 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

//! 让链接器在工程根目录搜索 memory.x//! cortex-m-rt 的 link.x 通过 `INCLUDE memory.x` 引用它fnmain(){letmanifest_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

//! 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]usepanic_rtt_targetas_;usertt_target::{rprintln,rtt_init_print};usestm32h7xx_hal::{gpio::ErasedPin,pac,prelude::*,rcc::PllConfigStrategy,time::Hertz};/// 外置晶振频率 (MHz)constHSE_MHZ:u32=25;/// 目标系统主频 (MHz)constSYSCLK_MHZ:u32=480;/// 流水灯每步点亮时长 (ms)constSTEP_MS:u32=200;#[cortex_m_rt::entry]fnmain()->!{rtt_init_print!();rprintln!("==== STM32H743 boot ====");let_cp=cortex_m::Peripherals::take().unwrap();letdp=pac::Peripherals::take().unwrap();// VOS Scale0:跑 480MHz 的必要条件letpwr=dp.PWR.constrain().vos0(&dp.SYSCFG).freeze();// HSE 25MHz → PLL1 → 480MHz(Iterative 策略自动求解分频)letccdr=dp.RCC.constrain().use_hse(Hertz::MHz(HSE_MHZ)).sys_ck(Hertz::MHz(SYSCLK_MHZ)).pll1_strategy(PllConfigStrategy::Iterative).freeze(pwr,&dp.SYSCFG);letsysclk_hz=ccdr.clocks.sys_ck().to_Hz();rprintln!("SYSCLK = {} Hz",sysclk_hz);// PG2~PG6 推挽输出;.erase() 抹掉引脚编号泛型,便于放进同一数组letgpiog=dp.GPIOG.split(ccdr.peripheral.GPIOG);letmutleds:[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(),];// 全部熄灭(本板低电平点亮,高电平熄灭)forledin&mutleds{let_=led.set_high();}// 流水灯:逐个点亮 STEP_MS 毫秒letstep_cycles=sysclk_hz/1000*STEP_MS;letmutround=0u32;loop{forledin&mutleds{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 反推:

.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 还依赖供电等级:

letpwr=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 读取,不占用串口:

rtt_init_print!();rprintln!("SYSCLK = {} Hz",sysclk_hz);

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

4.4 GPIO 流水灯

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


五、编译、烧录、看日志

# 仅编译,检查代码cargo build--release# 编译 + 烧录 + 自动启动 RTT(runner 已在 config.toml 配好)cargo run--release

正常应看到:

==== 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:

cargo install probe-rs-tools

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

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

坑 3:assertion failed: sys_d1cpre_ck <= sys_d1cpre_ck_max

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

坑 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):

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 官方文档

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询