litex soc Rust 嵌入式开发环境-LiteX社区-FPGA CPLD-ChipDebug

litex soc Rust 嵌入式开发环境

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

图片[1]-litex soc Rust 嵌入式开发环境-LiteX社区-FPGA CPLD-ChipDebug

总体流程就是先创建 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 嵌入式程序了。

请登录后发表评论

    没有回复内容