RustEmb_2. HelloWorld
在传统的C编写STM32代码时,有两种使用方式:寄存器版本和HAL库版本。
使用Rust编写时也一样,你需要考虑使用寄存器版本或者HAL库版本。
toolchain与STM32对应关系
使用Rust编写实质上是将Rust代码编译为无STD库的arm平台代码(二进制,汇编为arm指令集),然后将其中的代码段根据嵌入式硬件的要求,写入到芯片中,从而实现在STM32中跑RUST的效果。
我们这里使用的芯片STM32F407ZGT6 是一颗Cortex-M4架构的芯片,其对应RUST toolchain关系为:
1
2 3 4 |
# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
# target = "thumbv7m-none-eabi" # Cortex-M3 # target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) # target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) |
准备工作
本章节以LED闪烁为目标,尝试编写代码。
其中,IO使用GPIOF的Pin9和Pin10。
使用Cargo工具创建一个bin项目,名字为stm32app, 然后在根目录添加一个文件memory.x
1
2 3 4 5 6 |
MEMORY
{ /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x08000000, LENGTH = 512K RAM : ORIGIN = 0x20000000, LENGTH = 100K } |
特别注意FLASH和RAM值的大小,只能比真实的小,不能比真实的大。
这里的memory.x是为未来使用的一个库cortex-m-rt准备的。
编译时,使用如下命令行:
cargo build --release --target=thumbv7em-none-eabihf -C link-arg=Tlink.x
其中,target可以是v7m或者v7em中任何一个,但是不能是v6m.
就和使用C写代码一样,我们完全可以全手工构造寄存器映射关系,然后进行读写,但是为了方便,我们使用开源的库加快我们的代码速度。
其中,寄存器版本主要依赖 stm32f4 这个库,而HAL版本依赖stm32f4xx-hal。
他们没有本质上的区别,只是寄存器版本库对寄存器操作简单明了,而HAL版本集成程度更高一点,你可以根据自己的情况进行选择。
寄存器版本
Cargo.toml文件
1
2 3 4 5 6 7 8 9 |
[dependencies]
cortex-m = "*" cortex-m-rt = "*" cortex-m-semihosting = "*" panic-halt = "*" [dependencies.stm32f4] features = ["stm32f407", "rt"] version = "*" |
main.rs
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#![no_std]
#![no_main] extern crate stm32f4; extern crate panic_halt; extern crate cortex_m_rt; use cortex_m_rt::entry; use stm32f4::stm32f407; // use `main` as the entry point of this application #[entry] fn main() -> ! { // get handles to the hardware let peripherals = stm32f407::Peripherals::take().unwrap(); let pf = &peripherals.GPIOF; let rcc = &peripherals.RCC; // enable system clock RCC for gpiof rcc.ahb1enr.write(|w|{ w.gpiofen().set_bit() }); // config GPIOF pin9 and pin10 for led { // 1. mode 01 通用输出 pf.moder.write(|w|{ w.moder9().output() .moder10().output() }); // 2. otype 0 推挽输出 pf.otyper.write(|w|{ w.ot9().push_pull() .ot10().push_pull() }); // 3. ospeed 10, 50MHz high speed pf.ospeedr.write(|w|{ w.ospeedr9().high_speed() .ospeedr10().high_speed() }); // 4. pup 01, 上拉 pf.pupdr.write(|w|{ w.pupdr9().pull_up() .pupdr10().pull_up() }); // 5. idr/odr/bsrr // by condition to read or set pf.odr.reset(); } loop{ pf.odr.write(|w| { w.odr9().set_bit() .odr10().clear_bit() }); // 这个延时不准,只用来演示使用 cortex_m::asm::delay(168*10000*3); pf.odr.write(|w| { w.odr9().clear_bit() .odr10().set_bit() }); cortex_m::asm::delay(168*10000*3); } } |
使用准备工作中的命令行编译后得到一个stm32app的arm程序,
通过arm-none-eabi-objdump -f stm32app就可以得到这个文件的详细头部信息了。
HAL库版本
1
2 3 4 5 6 7 8 9 10 11 |
[dependencies]
embedded-hal = "*" nb = "*" cortex-m = "*" cortex-m-rt = "*" # Panic behaviour, see https://crates.io/keywords/panic-impl for alternatives panic-halt = "*" [dependencies.stm32f4xx-hal] version = "*" features = ["rt", "stm32f407"] # replace the model of your microcontroller here |
main.rs
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#![deny(unsafe_code)]
#![no_main] #![no_std] // Halt on panic // #[allow(unused_extern_crates)] // NOTE(allow) bug rust-lang/rust#53964 extern crate panic_halt; // panic handler use cortex_m; use cortex_m_rt::entry; use stm32f4xx_hal as hal; use crate::hal::{prelude::*, stm32}; #[entry] fn main() -> ! { if let (Some(dp), Some(cp)) = ( stm32::Peripherals::take(), cortex_m::peripheral::Peripherals::take(), ) { // Set up the LED. On the Nucleo-446RE it's connected to pin PA5. let gf = dp.GPIOF.split(); let mut led = gf.pf10.into_push_pull_output(); // Set up the system clock. We want to run at 48MHz for this one. let rcc = dp.RCC.constrain(); let clocks = rcc.cfgr.sysclk(168.mhz()).freeze(); // Create a delay abstraction based on SysTick let mut delay = hal::delay::Delay::new(cp.SYST, clocks); loop { // On for 1s, off for 1s. led.set_high().unwrap(); delay.delay_ms(500_u32); led.set_low().unwrap(); delay.delay_ms(500_u32); } } loop {} } |
使用准备工作中的编译指令就可以得到同样的stm32app程序。
生成HEX/BIN文件
首先,上一步中生成的是一个arm程序,不是单片机可识别的HEX或者BIN程序,我们需要使用工具进行提取/转换:
- bin文件 arm-none-eabi-objcopy -O binary stm32app stm32app.bin
- hex文件 arm-none-eabi-objcopy -O ihex stm32app stm32app.hex
其中,相比BIN文件,HEX文件多了机器码的存储地址等信息,建议转换为HEX文件进行下载。
下载
下载前,请确保开发板USB已经接好,并且CH340驱动成功,能在设备管理器里面找到对应的COM口。
下载STM32 HEX文件有很多方式,我们是借助串口进行ISP下载,这里需要用到一些工具:
-
FlyMcu
这个软件比较古老,但是基本能用。缺点是下载失败率较高,建议波特率不要超过76800. -
PZ-ISP
这个是买开发板提供的下载工具,比较推荐,成功率很高,不容易出问题。 -
STM32 ISP下载工具
这个据说是STM32专供的,没怎么使用。
以上工具使用方法建议自己查询,方法都比较简单。
注意:
如果使用J-Link进行下载需要配合OpenOCD使用,此方法比较复杂,建议有需求了再尝试。
总结
RUST编写STM32程序十分简单,并且下载过程需要借助arm工具集进行代码转换,其他的与普通方式一样。
目前,ISP下载无法进行调试,如有需要,建议使用串口功能进行交互,OpenOCD+J-Link理论上可行,但是因为涉及指令较多,这里不做讨论和尝试。
当然,使用RUST开发处理需要RUST基础知识之外,还需要对STM32本身有深入的理解,此方面的知识建议学习正点原子的教材:
手把手教你学STM32 系列视频之 STM32F4-基于探索者F407
只有对C编写STM32程序有了基础的认知,才有可能编写出高质量的RUST代码。
有人说,既然这样,我为什么要用RUST写嵌入式?
- RUST本身语言特性比C更安全,规范,可以写出高质量的代码。
- RUST工具集本身可实现更高层次的抽象,用高阶语言写嵌入式代码可实现更复杂的功能和复用。
- 因为我喜欢RUST呀(_)
其实,由于我们使用的是arm芯片,可以上一些RTOS或者Linux系统,未来我们也可以在这些系统的基础上进行更深入的开发实践,这时候,RUST就是一个比较好的选择。
RUST官方对于嵌入式领域也很感兴趣,有专门的团队进行此方向的维护,再加上这个语言本身的特性,我相信未来它的工具链和生态会更加完善,最终与C一教高下。
说到底,这里只是进行RUST嵌入式开发的一个学习和实践,不要问太多问什么,喜欢就好。
https://www.codenong.com/cs107030352/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?