为什么是“只使用寄存器”
网上很多教程都是直接调用对应芯片的HAL库,让初学者认为操作都被Rust嵌入式开源组封装好了,直接调就好。这在目前Rust嵌入式生态还不成熟的情况下可能是个误解。
实际上Rust嵌入式有自己独特的层级关系,这一方面可以参考b站up主“爆米花胡了”的视频。其中负责访问寄存器的是PAC,与C语言直接访问内存地址不同,PAC层的代码依赖每个新品提供的svd文件自动生成,相当参考svd层的寄存器访问规范,为开发者提供了一系列寄存器的Rust访问方式。依赖PAC层的Rust嵌入式寄存器编程就是调用这些访问寄存器的api。
如何用依赖PAC层编程?
先别急,先搭建Rust嵌入式开发环境
1.对于编译,我们需要为rustc添加交叉编译的target
rustup target add armv7em-none-eabi
2.为了简单的烧录,我们选择使用cargo-flash这样的玩具工具
cargo install cargo-flash
3.接着,为你的烧录器安装驱动
完成这三步之后,我们就可以开始对付源码了
1.新建一个cargo项目
2.在项目根目录下新建 .cago 隐藏文件夹,在 .cargo 内新建config.toml
在config.toml内编辑
[build]
target = "thumbv7em-none-eabihf"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
这一步是指定target和编辑、链接参数
3.在项目根目录下新建build.rs
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
}
4.我们再建立上面的编译脚本指定的 memory.x
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 64K
}
5.随后修改项目根目录Cargo.toml,加入PAC包
[dependencies]
cortex-m = "0.7.4"
cortex-m-rt = "0.7.1"
stm32f4 = { version = "0.14.0", features = ["stm32f412", "rt"] }
这里的具体新品型号根据自己芯片选择,其中cortex-m的两个是cortex-m内核通用的内核编程接口,stm32f4即是厂商特定的PAC编程接口,用于访问各种寄存器和芯片功能。
6.终于可以开始编辑main.rs
#![no_std]
#![no_main]
use core::panic::PanicInfo;
use cortex_m_rt::entry;
use cortex_m::{asm, Peripherals};
use stm32f4::stm32f412 as device;
#[panic_handler]
fn my_panic_handler(_panic: &PanicInfo) -> ! {
loop {}
}
fn my_delay() {//简陋的延迟函数
let mut count = 2;
while count > 0 {
count = count - 1;
let mut count2 = 120000;
while count2 > 0 {
count2 = count2 - 1;
}
}
}
开头是一段普通的裸机代码,同时引用了上文注册到的包。
7.PAC包怎么调?
PAC是由svd2rust自动生成的,我们需要打开stm32f4的文档:stm32f4 - Rust
进入子模块如stm32f413;
选择你要配置的寄存器,如要开启gpioB的时钟,你需要配置ahb1enr;
接着选择操作的闭包如W,然后你就会知道怎么做
最后得到类似以下的代码:
#[entry]
fn main() -> !{
let mut p = device::Peripherals::take().unwrap();
let rcc = p.RCC;
rcc.ahb1enr.modify(|_, w| w.gpioben().set_bit());
p.GPIOB.moder.modify(|_, w| unsafe{w.moder0().bits(1)});
loop {
p.GPIOB.odr.modify(|_, w| w.odr0().bit(false)); //灯亮
my_delay();
p.GPIOB.odr.modify(|_, w| w.odr0().bit(true)); //灯灭
my_delay();
}
}
8.烧录:
cargo flash --chip stm32f412zgtx
enjoy it