svd2rust

简介

svd2rust,顾名思义,可以直接从SVD(System View Description)文件生成Rust代码,经过svd2rust生成的Rust代码其实是一个PAC(Peripheral Access Crate),初次接触的时候,可以将其理解为最贴近寄存器的一个库。

本次教程会优先演示如何从svd到点亮led的完整流程,教程的末尾会给出两个脚本,结合svd文件,可以一键完成点灯。

如何使用

  1. 获取SVD文件,这一步的途径有很多:某些IDE的文件资源;ARM官网;芯片官网。本教程演示的芯片为STM32F405RGT6,可以直接在官网搜索到其SVD文件。

  2. 安装必要的工具

cargo install svd2rust 
cargo install flip-link
cargo install form
  1. 使用svd2rust生成pac

这部分其实也可以直接在svd2rust的文档里找到,以下直接给出具体命令。

svd2rust -i STM32F405.svd

rm -rf src

form -i lib.rs -o src/ && rm lib.rs

cargo fmt

单看以上命令,或许可以大概看懂,但是有一些前置条件是默认用户已经掌握的。例如需要在一个lib工程里执行以上操作,使用以下命令创建一个lib工程。

cargo new stm32f405_pac --lib

随即,将以下依赖添加到lib工程的Cargo.toml文件里

[dependencies]
critical-section = { version = "1.0", optional = true }
cortex-m = "0.7.6"
cortex-m-rt = { version = "0.6.13", optional = true }
vcell = "0.1.2"

[features]
rt = ["cortex-m-rt/device"]

完成以上命令之后,就得到了一个默认的pac,按理来说就可以直接开始调用这个pac完成点灯了。

但由于工具链的不断更新,以及依赖的更新,可能导致部分不兼容,所以,需要对默认生成的pac进行微调,还需要修改依赖版本。

开始点灯

pac里存在的部分问题会在这一部分做调整。

  1. 创建一个bin工程
cargo new blinky --bin 
  1. 配置工程,适配嵌入式开发
  • 新增文件:.cargo/config.toml

[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
rustflags = [
    "-C", "link-arg=-Tlink.x",
    "-C", "linker=flip-link",
]
runner = "probe-rs run --chip STM32F405RG"
  • 新增文件:.vscode/settings.json
{
    "rust-analyzer.check.allTargets": false
}
  • 新增文件:'memory.x'
MEMORY {
  FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM   : ORIGIN = 0x20000000, LENGTH = 128K
  CCMRAM : ORIGIN = 0x10000000, LENGTH = 64K
}
  • 添加依赖:Cargo.toml
[dependencies]
stm32f405_pac ={ path = "../stm32f405_pac", features = ["critical-section", "rt"]}
cortex-m ={ version = "0.7.7", features = ["critical-section-single-core"]}
cortex-m-rt ={ version = "0.7.5"}
panic-halt ={ version = "1.0.0"}

至此,工程配置完成,但还是会有错误,这部分错误来自pac,以下对pac进行微调。

  1. 微调pac

每个fix里分为两部分,第一部分是默认的pac,第二部分是修改过后的pac

总结下来就是在适当的位置加了一些unsafe关键字。可以直接在vscode里跳转添加。

  • fix 1
#[doc = "Cryptographic processor"]
pub mod cryp;
#[no_mangle]
static mut DEVICE_PERIPHERALS: bool = false;
#[doc = r" All the peripherals."]
#[allow(non_snake_case)]
pub struct Peripherals
#[doc = "Cryptographic processor"]
pub mod cryp;
#[unsafe(no_mangle)]
static mut DEVICE_PERIPHERALS: bool = false;
#[doc = r" All the peripherals."]
#[allow(non_snake_case)]
pub struct Peripherals
  • fix 2
extern "C"
unsafe extern "C"

-fix 3

#[link_section = ".vector_table.interrupts"]
#[no_mangle]
#[unsafe(link_section = ".vector_table.interrupts")]
#[unsafe(no_mangle)]

完成以上的微调之后,应该就没有报错了。可以开始编写代码点灯了。

  1. 将以下内容添加到src/main.rs
#![no_std]
#![no_main]

use cortex_m::asm;
use cortex_m_rt::entry;
use panic_halt as _;
use stm32f405_pac::Peripherals;

fn delay(cycles: u32) {
    for _ in 0..cycles {
        asm::nop();
    }
}

#[entry]
fn main() -> ! {
    let dp = Peripherals::take().unwrap();
    dp.rcc.ahb1enr().modify(|_, w| w.gpioben().set_bit());
    dp.gpiob
        .moder()
        .modify(|_, w| unsafe { w.moder13().bits(0b01) });

    loop {
        dp.gpiob.bsrr().write(|w| w.bs13().set_bit());
        delay(50_000);
        dp.gpiob.bsrr().write(|w| w.br13().set_bit());
        delay(50_000);
    }
}
  1. 烧录代码,查看效果

使用ST-Link或者DAP-Link连接开发板到电脑,执行以下命令即可完成编译和烧录。

cargo run --release

如果使用JLink,则需要使用zadig替换USB驱动,推荐使用ST-Link或者DAP-Link

之后的教程会使用JLink配合Ozone进行调试,更加方便。

脚本介绍

一方面考虑到如果每次都需要执行这么多命令,才能点亮一个LED未免有些过于繁琐,考虑到大部分的命令,其实都是固定的,何不将其写到脚本里,一键执行即可。

另一方面考虑到部分读者/观众可能确实感兴趣,但是跟完了以上的流程后,却并不能完成点灯,未免会挫败感,不利于后续学习,故将其封装为两个脚本,分别为pac.shblinky.sh,读者/观众只需要获取到svd文件,并将这三个文件放置到同级目录下即可一键生成。

脚本使用

  • pac.sh脚本
#!/bin/bash

# install some tools needed
cargo install svd2rust 
cargo install flip-link
cargo install form

cargo new stm32f405_pac --lib

echo "lib created"

cp -r ./STM32F405.svd ./stm32f405_pac

echo "svd copied"

cd stm32f405_pac

# svd2rust part
svd2rust -i ./STM32F405.svd 

rm -rf src

form -i lib.rs -o src/ && rm lib.rs

cargo fmt

# add dependencies to pac/Cargo.toml
sed -i '/^\[dependencies\]$/d' Cargo.toml
cat >> Cargo.toml << 'EOF'
[dependencies]
critical-section = { version = "1.0", optional = true }
cortex-m = "0.7.7"
cortex-m-rt = { version = "0.7.5", optional = true }
vcell = "0.1.2"

[features]
rt = ["cortex-m-rt/device"]

EOF

echo "done"
  • blinky.sh
#!/bin/bash

cargo new blinky --bin

cd blinky

mkdir -p .cargo

cat > .cargo/config.toml << 'EOF'

[build]
target = "thumbv7em-none-eabihf"

[target.thumbv7em-none-eabihf]
rustflags = [
    "-C", "link-arg=-Tlink.x",
    "-C", "linker=flip-link",
]
runner = "probe-rs run --chip STM32F405RG"

EOF

sed -i '/^\[dependencies\]$/d' Cargo.toml
cat >> Cargo.toml << 'EOF'
[dependencies]
stm32f405_pac ={ path = "../stm32f405_pac", features = ["critical-section", "rt"]}
cortex-m ={ version = "0.7.7", features = ["critical-section-single-core"]}
cortex-m-rt ={ version = "0.7.5"}
panic-halt ={ version = "1.0.0"}

EOF

touch memory.x

cat > memory.x << 'EOF'
MEMORY {
  FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM   : ORIGIN = 0x20000000, LENGTH = 128K
  CCMRAM : ORIGIN = 0x10000000, LENGTH = 64K
}

EOF

mkdir -p .vscode

cat > .vscode/settings.json << 'EOF'
{
    "rust-analyzer.check.allTargets": false
}
EOF

echo "done"

先执行./pac.sh,再执行./blinky.sh

打开脚本生成的工程进行编译

由于脚本并没有对pac进行微调,所以还需要使用vscode打开blinky工程,点击跳转到error的位置,添加unsafe关键字。

脚本使用注意事项

脚本仅针对STM32F405RG芯片,若你是用的是其他芯片,需要提供相应的svd文件,以及修改脚本里的一些关键配置。

  • .cargo/config.toml

target需要与芯片架构对应,先使用以下命令安装对应的target

# M0/M0+内核芯片,常见芯片:STM32F0xx,PY32F0xx,....
rustup target add thumbv6m-none-eabi

# M3内核芯片,常见芯片:STM32F1xx,...
rustup target add thumbv7m-none-eabi

# M4F和M7F内核芯片,F意指带有浮点运算,常见芯片:STM32F4xx,STM32G4xx,STM32H7xx,...
rustup target add thumbv7em-none-eabihf

# M33F内核芯片,常见芯片,树莓派Pico 2, RP2350
rustup target add thumbv8m.main-none-eabihf

--chip STM32F405RG 也需要适配自己的型号,例如:--chip STM32F103C8

  • momory.x
MEMORY {
  FLASH : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM   : ORIGIN = 0x20000000, LENGTH = 128K
  CCMRAM : ORIGIN = 0x10000000, LENGTH = 64K
}

只需要修改LENGTH相应的大小即可,ST系列单片机的起始地址一般都是0x080000000x20000000

对于没有CCRAM的型号,直接删除CCRAM一行即可

总结

此次教程为读者/观众演示了从寄存器层面点亮led的完整流程,并且编写了一键运行的脚本帮助读者/观众理解整个流程。

此次教程的目的在于为读者/观众演示Rust嵌入式开发的底层控制能力,读者/观众可以根据自己的能力来评估是否还需要继续深入了解学习更加底层的知识。

推荐读者/观众先在ST系列的单片机上跑通整个流程,再去考虑国产芯片,一些国产芯片的svd文件规范性实在不敢恭维。

许多国产芯片,类似py32ch32以及hpm等,都有国内开发者已经编写了大部分的hal,甚至还添加了embassy的支持,这无疑是一件好事,初学者也不必纠结非要自己维护一套hal,也可以考虑去这些仓库进行pr或者issue

建议

此篇教程另一个目的是为了向读者/观众展示“长江水从何处来”,至于实际生产和学习的时候,更加推荐去使用一些别人已经在pac基础上封装的hal。例如rticembassy以及诸多的xxx_hal

参考资料