本帖最后由 instead 于 2024-2-26 19:01 编辑
全文 10574 字,预计阅读时间 7-9 分钟
开发资料一览
下文中的大部分内容都会参考以上官方手册的内容,有需要的可以下载到本地看。
了解手上的硬件
手上开发板的基础信息可以通过屏蔽罩丝印表示,例如我手上的这个开发板屏蔽罩上的丝印是 BLOFN8IR4
,对照规格书后可以知道其表示的是:
- BL:BL618
- O:外置
- F:FLASH
- N:模组温度版本为常温
- 8:FLASH 大小为 8MB
- I:内置
- R:PSRAM
- 4:PSRAM 大小为 4MB
这代表着我手上这个板子有 4MB 的 PSRAM(不是内存,但可以扩展为内存)和 8MB 的 FLASH。
确定 RAM 和 FLASH 的起始地址
通过查阅参考手册中的 1.4 地址映射
,可以得到如下的内存地址映射表:
模块 |
大小 |
Cache 开始地址 |
Non-cache 开始地址 |
OCRAM |
320KB |
0x62FC0000 |
0x22FC0000 |
WRAM |
160KB |
0x63010000 |
0x23010000 |
以及如下的地址表(部分):
模块 |
目标 |
开始地址 |
大小 |
描述 |
FLASH |
Flash |
0xA0000000 |
128MB |
应用程序地址空间 |
PSRAM |
pSRAM |
0xA8000000 |
128MB |
pSRAM 存储器地址空间 (可选项,依赖芯片具体型号) |
RAM |
HBN RAM |
0x20010000 |
4KB |
HBN RAM, 主要用于超低功耗模式下的数据保存 |
ROM |
ROM |
0x90000000 |
128KB |
Bootrom 区域地址空间 |
- OCRAM(On-Chip RAM):OCRAM 是集成在芯片(SoC,System-on-Chip)上的内部 RAM,通常位于处理器或其他核心周围。它的优势在于对于处理器等核心来说,它位于芯片内部,访问速度较快,可以更轻松地与其他芯片内部组件进行通信。由于它是在芯片上的 RAM,因此也称为内嵌式 RAM。
对照一下规格书中的地址映射表可以知道,Flash 的起始地址是 0xA0000000
,大小为 8MB;OCRAM 的起始地址是 0x62FC0000
,大小为 320KB。
(有人看见映射表里面的 RAM 可能会疑惑为什么不用这个,因为那个 RAM 是 HBN RAM,一般情况下用不上它)
确定外设
Ai-M61-32S-Kit 开发板给的外设其实还挺多的,不过这里因为只是入个门,所以那些复杂的外设我们不碰它,只是玩玩它上面的那个 RGB Logo 灯。通过规格书可知:
- 红色灯:IO12
- 绿色灯:IO14
- 蓝色灯:IO15
这几盏灯都接在 12、14 和 15 GPIO 口上,并且是高电平有效。
PAC
PAC 全称是 Peripheral Access Crate,可以用于访问嵌入式系统外设寄存器。PAC 提供了对硬件寄存器和外设功能的类型安全的抽象,使得在嵌入式系统上进行编程更加方便和可靠。
在嵌入式系统中,通常会有一些外设(如 GPIO、UART、SPI 等),这些外设的控制寄存器位于特定的内存地址。PAC 就是为了方便地访问这些寄存器而创建的。PAC 提供了一个类型安全的 API,使得在编写嵌入式系统代码时,可以避免使用裸指针和不安全的代码。
PAC 通常是由芯片厂商或社区创建的,以支持特定的芯片或开发板。通过使用 PAC,开发者可以使用 Rust 的安全性和表达力,同时又能够方便地访问底层硬件。在 Rust 中,PAC 通常是一个代码生成工具生成的库,根据硬件描述文件自动生成对应的寄存器访问代码。
如何获取 PAC
拿手上的这块 Ai-M61-32S 为例,它所搭载的芯片是 BL618,通过翻阅博通官方库可以知道找到官方发布的bl616-pac - crates.io: Rust Package Registry,因此可以直接拿这个来用并不,摸鱼的时候用它写的我焦头烂额,仔细看了看 C 源码和隔壁公司为 bl602 维护的 pac 后发现,里面用于生成的 SVD 文件里寄存器不全,至少我目前看着缺少 AON,以及一部分 GPIO 控制寄存器的,因此我这边要先补上缺失的寄存器再重新用最新的版本来生成一遍。
生成 PAC
环境准备
由于需要修补厂商提供的 SVD 文件,这里用到的是rust-embedded/svdtools,执行如下命令安装:
cargo install svdtools
生成 PAC 需要使用 svd2rust
与 form
,使用如下命令安装:
cargo install svd2rust form
同时我们还需要用到 SVD 文件,这个文件可以在官方 PAC 的仓库中找到。
修补 SVD 文件
找一个合适的位置执行以下命令来生成一个空的 Rust 库:
cargo new bl61x-pac --lib
上面的 bl61x-pac
可以是自己觉得方便的名称,在执行完成后会生成该名称的目录。进入该目录后创建 svd 子文件夹,将前面取得的 SVD 文件放入该目录内。
而后创建 bl618-pac/peripherals
文件夹,在里面放入修补文件。然后在 bl61x-pac
下新建文件 bl61x.yaml
作为修补的配置文件即可。
例如,我这边创建了 AON
寄存器的修补文件 AON.yaml
,则配置文件的内容如下:
# Path to the SVD file we're targeting. Relative to this file.
# This must be included only in the device YAML file.
_svd: "./svd/bl616.svd"
# Include other YAML files. Path relative to this file.
_include:
- "./peripherals/AON.yaml"
完成了这些工作后,目录的结构大概是这样:
bl61x-pac
├─ .gitignore
├─ bl61x.yaml
├─ Cargo.toml
├─ svd
│ └─ bl616.svd
├─ src
│ └─ lib.rs
└─ peripherals
└─ AON.yaml
随后我们便运行如下的命令生成修补后的 SVD 文件即可:
svdtools patch bl61x.yaml
生成 Rust 文件
编辑一下 Cargo.toml
文件,使得其应该具有如下的内容:
[package]
name = "bl61x-pac"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
riscv = "0.11.1"
vcell = "0.1.3"
[dependencies.critical_section]
package = "critical-section"
optional = true
version = "1.1.2"
[features]
critical-section = ["critical_section", "riscv/critical-section-single-hart"]
rt = []
并在目录内执行:
svd2rust -i svd/bl616.svd.patched --target=riscv
rm -rf src
form -i lib.rs -o src/ && rm lib.rs
cargo fmt
到这,你便可以调用该 PAC 库进行开发了。如果你们不想自己生成,也可以使用我上面生成的 PAC 库:wsndshx/bl61x-pac (github.com)。
Hello World!
这里就按照老规矩,先把板子给点亮(物理)。
创建 Rust 项目
随便找个目录,输入如下的命令创建新 Rust 项目:
cargo new blinky --bin
执行后,会生成具有如下结构的目录:
blinky
├── Cargo.toml
└── src
└── main.rs
在 blinky 目录内创建 .cargo
文件夹,并在里面创建配置文件 config.toml
,配置文件的内容如下:
[target.riscv32imac-unknown-none-elf]
rustflags = [
"-C", "link-arg=-Tmemory.x",
"-C", "link-arg=-Tlink.x",
]
[build]
target = "riscv32imac-unknown-none-elf"
上面的配置文件指定了编译时使用 riscv32imac-unknown-none-elf 作为编译时的目标平台,并且使用 memory.x
作为链接脚本。因此接下来在 blinky 目录下新建一个 memory.x
文件,内容如下:
MEMORY
{
RAM : ORIGIN = 0x62FC0000, LENGTH = 320K
FLASH : ORIGIN = 0xA0000000, LENGTH = 8M
}
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);
在链接脚本中我定义了 RAM 和 FLASH 的起始地址与长度为文章所确定的数值。但很明显仅靠这小段脚本还不足以声明好运行所需的所有段,不过 riscv-rt 运行库已经内置了通用的链接脚本,我们仅仅再创建一段编译时自动拷贝 memory.x
的 build script 即可。为此需要新建一个 build.rs
文件,并将如下的内容写入到 build.rs
中:
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put the linker script somewhere the linker can find it
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
// Only re-run the build script when memory.x is changed,
// instead of when any part of the source code changes.
println!("cargo:rerun-if-changed=memory.x");
}
处理依赖
该项目需要如下的依赖:
- riscv:CPU 寄存器访问与内联汇编函数
- riscv-rt:启动代码和中断处理程序
- panic-halt:设置恐慌行为为停止
- bl61x-pac:用于 BL616/BL618 微控制器的嵌入式 Rust 外设访问库
执行如下命令添加:
cargo add riscv --features critical-section-single-hart
cargo add riscv-rt --features single-hart
cargo add panic-halt
cargo add bl61x-pac --features critical-section
编写 PAC 点灯代码
在用 Rust 点灯之前,先去看看在官方 SDK 中是怎么用 C 实现的。在 SDK 中找到 GPIO 口的输入输出例程,可以看见其中关键的代码是如下的几行:
// 获取名为 gpio 的外设
gpio = bflb_device_get_by_name("gpio");
// 初始化指定 gpio 端口
// 分别对指定的 gpio_config_xx 寄存器中的如下位进行了操作:
// - GPIO_OUTPUT:将 reg_gpio_xx_oe 置 1,使能 gpio 输出
// - GPIO_PULLUP:将 reg_gpio_xx_pu 置 1,使能内部上拉功能
// - GPIO_SMT_EN:将 reg_gpio_xx_smt 置 1,功能不明,使能 SMT
// - GPIO_DRV_0:将 reg_gpio_xx_drv 置 0,输出能力最低
bflb_gpio_init(gpio, GPIO_PIN_0, GPIO_OUTPUT | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_0);
// 指定 gpio 口高电平
// 该函数对寄存器 gpio_cfg138 与 gpio_cfg139 进行操作
// 这两个寄存器共有 35 个有效位,分别设置 35 个 GPIO 口的输出值
// 只有在 GPIO 口处于设置/清除模式时生效
// 若同时进行设置和清除操作,仅设置操作生效
bflb_gpio_set(gpio, GPIO_PIN_0);
// 指定 gpio 口低电平
// 该函数对寄存器 gpio_cfg140 与 gpio_cfg141 进行操作
// 这两个寄存器共有 35 个有效位,分别清除 35 个 GPIO 口的输出值
// 只有在 GPIO 口处于设置/清除模式时生效
// 若同时进行设置和清除操作,仅设置操作生效
bflb_gpio_reset(gpio, GPIO_PIN_0);
分析完上面的例程后,便可以根据例程编写如下的 Rust 代码:
#![no_std]
#![no_main]
extern crate panic_halt;
use riscv_rt::entry;
use bl61x_pac as pac;
#[entry]
fn main() -> ! {
// 获取外设
let peripherals = pac::Peripherals::take().unwrap();
// 配置 GPIO 口第 15 引脚为 GPIO 输出模式,进入 SW-GPIO 模式,使能设置/清除输出模式,并且使能内部上拉功能
peripherals.GLB.gpio_config(15).write(|w| {
w.output_function()
.set_bit()
.alternate()
.gpio()
.pin_mode()
.set_clear()
.pull_up()
.set_bit()
});
loop {
// 将 15 引脚设置为高电平
peripherals.GLB.gpio_set_0().write(|w| w.gpio_15_set().set_bit());
peripherals.GLB.gpio_clear_0().write(|w| w.gpio_15_clr().clear_bit());
// BL618 默认的主频为 320MHz,即每秒 320,000,000 个周期
// 则如果需要延迟 1 毫秒最多需要消耗 320,000 个周期
// 并且延迟时间过短会导致常亮
// 这里延迟 500 毫秒
for _ in 0..500{
riscv::asm::delay(320000);
}
// 将 15 引脚设置为低电平
peripherals.GLB.gpio_set_0().write(|w| w.gpio_15_set().clear_bit());
peripherals.GLB.gpio_clear_0().write(|w| w.gpio_15_clr().set_bit());
for _ in 0..500{
riscv::asm::delay(320000);
}
}
}
编写完之后,使用如下的命令编译项目:
cargo build -r
并使用如下的命令生成固件镜像:
rust-objcopy --strip-all target/riscv32imac-unknown-none-elf/release/blinky -O binary target/riscv32imac-unknown-none-elf/release/blinky.bin
烧录固件(首次)
为什么是首次呢,因为你此时不清楚板子内部到底烧录了什么程序,因此先完整跑一次烧录流程。
这里需要用到博流的图形化工具 Bouffalo Lab Dev Cube 烧录编译出来的固件,下载地址见:下载 | 博流智能开发者社区 (bouffalolab.com)。
下载并解压后,双击 BLDevCube.exe,选择 BL616/BL618:
进入软件主界面后,按照下图的说明进行配置:
其中:
配置完成后,将开发板使用 USB 连接线连接至电脑,并在按着下图中的③按键的同时短按②按键进入烧录模式。
进入烧录模式后,点击软件的 Refresh
,刷新端口,再点击 Create & Download
开始烧录。当软件里面的那个大大的进度条跑到 100% 并且是绿色的时候,固件便烧录成功了。
再次短按开发板上的复位键,此时就可以观察到开发板上的蓝灯以一秒一次的速度亮起来了~