本文转自https://zhuanlan.zhihu.com/p/598103391
安装 Rust 工具链
我们需要先安装 Rust 对 RISC-V 的支持,这里不需要单独安装 riscv-gcc 工具链,直接使用 Cargo 就可以了。
$ rustup target add riscv32imac-unknown-none-elf
$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview
如果想直接体验 Rust 固件的话,可以用 cargo objcopy
生成 bin 固件。
$ cd app
$ cargo objcopy --target riscv32i-unknown-none-elf --release -- -O binary app.bin
$ icesprog -o 0x40000 app.bin
这个固件只是在串口每隔 500ms 打印一个 Hello LiteX SoC
:
Hello LiteX SoC
Hello LiteX SoC
...
下面我会介绍怎么生成这个 Rust 项目的。
SVD 生成 Rust Crate
为了使用 Rust 开发固件,我们需要生成开发板的 Rust Peripheral Access Crate (PAC),例如在 Github 仓库里我把它命名成了 icesugar-pac
,这个 crate 提供了底层寄存器相关的定义和控制,例如 TIMER0,UART0,LED 等。
这个 Rust PAC 并不需要我们自己从头写,可以从 SVD 文件 (System View Description) 自动生成,前面我们用 LiteX 生成 SoC 的时候,就可以传入 --csr-svd
选项生成 SoC 的 SVD 文件。
$ python3 -m litex_boards.targets.muselab_icesugar --csr-json csr.json --timer-uptime --build --csr-svd icesugar.svd
例如我们生成的 icesugar.svd
文件里包含了外设寄存器相关的定义,寄存器大小、地址、默认值等:
<peripheral>
<name>LEDS</name>
<baseAddress>0xF0001000</baseAddress>
<groupName>LEDS</groupName>
<registers>
<register>
<name>OUT</name>
<description><![CDATA[Led Output(s) Control.]]></description>
<addressOffset>0x0000</addressOffset>
<resetValue>0x00</resetValue>
<size>32</size>
<fields>
<field>
<name>out</name>
<msb>2</msb>
<bitRange>[2:0]</bitRange>
<lsb>0</lsb>
</field>
</fields>
</register>
</registers>
<addressBlock>
<offset>0</offset>
<size>0x4</size>
<usage>registers</usage>
</addressBlock>
</peripheral>
有了这个 SVD 文件,我们可以用 svd2rust 生成对应的 Rust 库。
$ cargo install svd2rust
$ cargo new --lib icesugar-pac && cd icesugar-pac
$ cp ../icesugar.svd ./
$ svd2rust -i icesugar.svd
$ mv generic.rs src/
这样就会生成这样一个文件结构:
.
├── build.rs
├── Cargo.toml
├── device.x
├── icesugar.svd
└── src
├── generic.rs
└── lib.rs
我们需要手动在 Cargo.toml
文件里添加依赖:
[dependencies]
bare-metal = "1.0"
riscv = "0.10"
vcell = "0.1"
riscv-rt = { optional = true, version = "0.10" }
[build-dependencies]
svd2rust = { version = "0.28", default-features = false }
[features]
default = ["rt"]
rt = ["dep:riscv-rt"]
有了 Rust PAC 支持,最后我们就可以开始 Rust 嵌入式开发了,创建 Rust 嵌入式项目可以参考这篇文章:
1. 安装 Rust
Rust 的安装和几年前一样,依旧很轻松,Rust 官网 提供了不同操作系统的安装软件包。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装完成后,可以查看 Rust 版本。
$ rustc --version
rustc 1.68.0-nightly (3020239de 2023-01-09)
$ cargo version
cargo 1.68.0-nightly (8c460b223 2023-01-04)
2. 添加 RISC-V 支持
前面提到,现在虽然不需要单独安装 RISC-V GCC 工具链,但默认的 Rust 只支持 x64。因此,我们还是需要添加 RISC-V 的支持。
$ rustup target add riscv32imac-unknown-none-elf
$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview
可以看到,Rust 可以使用 LLVM 生成最终的二进制文件。
3. 创建 Rust 嵌入式项目
我们首先创建一个默认的 hello world 项目:
$ cargo new gd32vf103-rust-blinky
这样会自动创建下面的文件结构:
.
├── Cargo.toml
├── .gitignore
└── src
└── main.rs
可以看到 Cargo
默认生成的项目自带了 git 支持 (.gitignore
),我们可以直接进入创建的目录用 cargo run
执行程序,但是这样生成的可执行程序默认是 x64 的,我们需要生成 riscv-non-embedded 的格式。
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target\debug\gd32vf103-rust-blinky`
Hello, world!
为了生成 RISC-V 嵌入式的固件,我们需要创建一个 .cargo
目录,并在里面修改 cargo
的默认配置,文件结构看起来是这样:
.
├── Cargo.toml
├── .gitignore
├── src
└── main.rs
├── .cargo
└── config
└── memory-c8.x
其中.cargo/config
的文件内容:
[target.riscv32imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tmemory-c8.x",
"-C", "link-arg=-Tlink.x",
]
[build]
target = "riscv32imac-unknown-none-elf"
这个配置文件告诉 cargo
生成我们第二步添加的 riscv32imac-unknown-none-elf
格式固件,并且按照 memory-c8.x
进行链接。
其中 memory-c8.x
的文件内容定义了 MCU 的 flash 和 ram 的地址、大小:
/* GD32VF103C8 */
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64k
RAM : ORIGIN = 0x20000000, LENGTH = 20k
}
REGION_ALIAS("REGION_TEXT", FLASH);
REGION_ALIAS("REGION_RODATA", FLASH);
REGION_ALIAS("REGION_DATA", RAM);
REGION_ALIAS("REGION_BSS", RAM);
REGION_ALIAS("REGION_HEAP", RAM);
REGION_ALIAS("REGION_STACK", RAM);
接下来我们就可以在 Cargo.toml
文件里添加相关的依赖了,cargo build
会自动下载对应依赖的版本:
[dependencies]
longan-nano = "0.3.0"
gd32vf103xx-hal = "0.5.0"
embedded-hal = "0.2.6"
nb = "1.0.0"
riscv = "0.6.0"
riscv-rt = "0.10.0"
panic-halt = "0.2.0"
可以看到 Rust 层级非常明显,从底层的 riscv
CPU 支持,到 riscv-rt
最小运行环境,接下来有通用的嵌入式抽象 embedded-hal
,到 MCU 的 HAL 支持 gd32vf103xx-hal
,最顶层是开发板 bsp 的支持 longan-nano
。这里我使用了 riscv-rust 维护的 Longan Nano 的 bsp。
最后当然就是 main.rs
调用 GPIO 库:
#![no_std]
#![no_main]
use panic_halt as _;
use riscv_rt::entry;
use longan_nano::hal::{pac, prelude::*};
use longan_nano::hal::delay::McycleDelay;
use embedded_hal::digital::v2::OutputPin;
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let mut rcu = dp.RCU.configure().freeze();
let gpioa = dp.GPIOA.split(&mut rcu);
let mut pa7 = gpioa.pa7.into_push_pull_output();
let mut delay = McycleDelay::new(&rcu.clocks);
loop {
pa7.set_low().unwrap();
delay.delay_ms(500);
pa7.set_high().unwrap();
delay.delay_ms(500);
}
}
我们可以从 longan_nano::hal
里找到各种外设所需要的库函数。
最终,编译生成 firmware.bin
$ cargo build --release
$ cargo objcopy --target riscv32imac-unknown-none-elf --release -- -O binary firmware.bin
这里提醒一下,如果使用 GD-Link Programmer 上传程序到开发板,Connect
连接后,Program
完,记得点击 Run App
才会重新执行程序,不然即使复位,开发板也一直和调试器连接等待调试,不会自动执行程序。
总体流程就是先创建 Rust 项目模板,在 .cargo/config
里定义生成的目标硬件 RISC-V 以及链接脚本。
$ cargo new app
在 .cargo/config
里指定 riscv32i-unknown-none-elf
和链接脚本 memory.x
:
# .cargo/config
[target.riscv32i-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tmemory.x",
"-C", "link-arg=-Tlink.x",
"-C", "linker-plugin-lto",
# The following option can decrease the code size significantly. We don't
# have it enabled by default as it gets rid of panic information we do want
# to have those when developing code.
# "-C", "force-frame-pointers=no",
]
[build]
target = "riscv32i-unknown-none-elf"
这里 memory.x
文件里定义的 layout 需要和之前 SoC 的定义的布局匹配,比如我们从偏移地址 0x40000 启动固件:
MEMORY {
sram : ORIGIN = 0x00000000, LENGTH = 0x00010000
spiflash : ORIGIN = 0x00800000, LENGTH = 0x00800000
rom : ORIGIN = 0x00840000, LENGTH = 0x00008000
}
REGION_ALIAS("REGION_TEXT", rom);
REGION_ALIAS("REGION_RODATA", rom);
REGION_ALIAS("REGION_DATA", sram);
REGION_ALIAS("REGION_BSS", sram);
REGION_ALIAS("REGION_HEAP", sram);
REGION_ALIAS("REGION_STACK", sram);
当然,还需要在 cargo.toml
里添加上一步 SVD 生成的 icesugar-pac
等依赖:
[package]
name = "app"
version = "0.1.0"
edition = "2023"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
icesugar-pac = { path = "../icesugar-pac"}
panic-halt = "0.2.0"
riscv-rt = { version = "0.10.0"}
[profile.release]
# Keep debug information for release builds, for easier debugging.
# It will be removed during the conversion to the .dfu file.
# debug = true
# Improve code generation
lto = true
# codegen-units = 1
最终我们就可以开始写 Rust 主程序了,初始化定时器并每隔 500 毫秒打印字符到串口:
#![no_std]
#![no_main]
extern crate panic_halt;
use icesugar_pac;
use riscv_rt::entry;
mod timer;
mod print;
use timer::Timer;
const SYSTEM_CLOCK_FREQUENCY: u32 = 24_000_000;
// This is the entry point for the application.
// It is not allowed to return.
#[entry]
fn main() -> ! {
let peripherals = unsafe { icesugar_pac::Peripherals::steal() };
// let peripherals = icesugar_pac::Peripherals::take().unwrap();
print::print_hardware::set_hardware(peripherals.UART);
let mut timer = Timer::new(peripherals.TIMER0);
loop {
print!("Hello LiteX SoC\r\n");
msleep(&mut timer, 500);
}
}
fn msleep(timer: &mut Timer, ms: u32) {
timer.disable();
timer.reload(0);
timer.load(SYSTEM_CLOCK_FREQUENCY / 1_000 * ms);
timer.enable();
// Wait until the time has elapsed
while timer.value() > 0 {}
}
完整的 Rust 主程序在 app/src/main.rs 可以找到,前面提到我们可以用 cargo objcopy
编译生成最终的固件 app.bin
:
$ cargo objcopy --target riscv32i-unknown-none-elf --release -- -O binary app.bin
$ icesprog -o 0x40000 app.bin
终于到这里,我们就成功在 LiteX 定制的 SoC 上运行 Rust 嵌入式程序了。
没有回复内容