创建 Rust 嵌入式项目 (RISC-V)

软件开发大郭
0 评论
/
7 阅读
/
6189 字
03 2023-12

四年前 (2019 年),以内存安全 (memory-safe) 闻名的 Rust 语言,最大的特点是:围观的人多,使用的人少;永远在学习 Rust 的路上,学习过程中又持续被劝退。

特别是在嵌入式领域,大家都在想象 (envision) 一个 Rust 替代 C/C++ 的未来,但是项目真要完全用 Rust 替代,走起来一步一个坑,可能 Rust 项目框架还没搭起来,就重新投入了 C 的怀抱。

如今已经是 2023 年,实际使用 Rust 的项目越来越多,例如服务器后端、数据库、游戏引擎;另一方面,Rust 也正式成为 Linux 内核官方接受 (officially accepted) 的语言;嵌入式领域 Rust 支持的开发板也越来越多。于是最近又重新尝试,看看创建一个 Rust 嵌入式项目会不会变得很轻松。

这篇文章以 GD32VF103 Start 开发板为例 (当然,Longan Nano 也是用的同一款芯片),创建一个 Rust 经典的 blinky,又名点灯项目。最终创建的项目在 Win / Linux / MacOS 上都可以一行命令  cargo build  自动安装依赖、编译,并通过  cargo objcopy  生成最终的固件,不再需要单独安装 risc-v gcc 工具链

null

GD32VF103 Start 开发板

0. TLDR;

如果不想一步步尝试,直接看到最后的结果,这篇文章使用到的全部命令和代码都在这个 github 仓库里:

[Github] A minimal rust embedded project for RISC-V MCU.​github.com/wuhanstudio/gd32vf103-rust-blinky

$ git clone https://github.com/wuhanstudio/gd32vf103-rust-blinky
$ cd gd32vf103-rust-blinky

$ cargo build --release
$ cargo objcopy --target riscv32imac-unknown-none-elf --release -- -O binary firmware.bin

这样就会生成  firmware.bin  可以用 GD-Link Programmer 或者 dfu-utils 上传到开发板。

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  才会重新执行程序,不然即使复位,开发板也一直和调试器连接等待调试,不会自动执行程序。

null

总的来看,如今 2023 年,这块开发板 Rust 嵌入式的层层支持已经非常完善了,项目创建运行过程非常顺利,没有碰到几年前  cargo objcopy  后生成的固件无法运行的错误。

标签:
    暂无数据