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 嵌入式程序了。

请登录后发表评论

    没有回复内容