说明
在传统的C编写STM32代码时,有两种使用方式:寄存器版本和HAL库版本。
使用Rust编写时也一样,你需要考虑使用寄存器版本或者HAL库版本。
toolchain与STM32对应关系
使用Rust编写实质上是将Rust代码编译为无STD库的arm平台代码(二进制,汇编为arm指令集),然后将其中的代码段根据嵌入式硬件的要求,写入到芯片中,从而实现在STM32中跑RUST的效果。
我们这里使用的芯片STM32F407ZGT6 是一颗Cortex-M4架构的芯片,其对应RUST toolchain关系为:
# 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
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文件
[dependencies]
cortex-m = "*"
cortex-m-rt = "*"
cortex-m-semihosting = "*"
panic-halt = "*"
[dependencies.stm32f4]
features = ["stm32f407", "rt"]
version = "*"
main.rs
#![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库版本
[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
#![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嵌入式开发的一个学习和实践,不要问太多问什么,喜欢就好。