在线时间4336 小时
UID3441752
注册时间2017-11-21
NXP金币693537
TA的每日心情 | 开心 前天 08:46 |
---|
签到天数: 295 天 [LV.8]以坛为家I
管理员
- 积分
- 34601
- 最后登录
- 2024-10-13
|
当MCX N947遇上Rust,打造未来嵌入式系统的黄金搭档! Rust 介绍
Rust 是一门注重安全的语言,相比于 C/C++/ASM 有着更高级的抽象能力、编译器带来的安全特性与广泛友好的社区支持。Linux 与 Windows 内核也都基于 Rust 的安全性和性能引入了 Rust。
Rust 有很多优势,内存安全、并发安全、生态系统、包管理与构建管理,同时也有与 C/C++ 相同等级的性能。Rust 通过强化所有权和借用的概念,尽力消除了开发过程中可能出现的内存问题。同时作为一门现代语言,有着许多方便的特性与丰富的生态资源,如统一的包管理,高可读代码等等。
但 Rust 也有美中不足,如缺乏对底层的完全控制,学习难度高,编译时间长等。由于 Rust 的安全与高抽象能力,许多非安全操作被禁止,许多在 C 中能够通过指针进行的简单操作在 Rust 中需要十分复杂的操作,这也导致 Rust 的学习难度更高。
本文会对在 NXP MCX 平台上使用 Rust 进行简单介绍。在本文中使用 FRDM-MCXN947 为例,所有的例子均运行在 Core0 上。
安装Rust工具链
Rust 工具链的安装十分简单,参考 Rustup 即可。默认状态下,Rustup 工具只会安装本机的 TARGET ,为了能够在我们的 MCU 上运行编译产物, 需要安装对应的 TARGET 。可以通过运行如下命令来添加 armv8m hard-float 支持。
- rustup target add thumbv8m.main-none-eabihf
复制代码 同样,如果我们想为其他平台编译,像 cortex-m3 ,则需要运行:
- rustup target add thumbv8m.main-none-eabihf
复制代码 如果要在没有 FPU 的 Core1 上运行,则需要使用命令rustup target add thumbv8m.main-none-eabi 添加 thumbv8m.main-none-eabi target ,注意无 hf 尾缀, 代表不使用硬件浮点数 ABI
创建项目
在添加支持后,在我们想要保存项目的文件夹运行命令 cargo new mcx-example 创建一个名为 mcx-example 的工程, 同时创建一个配置文件 .cargo/config.toml 来指定编译参数和默认 target。
创建的 .cargo/config.toml 文件内容如下:
- [build]
- target = "thumbv8m.main-none-eabihf"
- [target.thumbv8m.main-none-eabihf]
- rustflags = ["-C", "link-arg=-Tlink.x"]
复制代码 接下来添加必要的依赖, Cargo.toml 的内容应该与下面的内容类似:
- [package]
- name = "mcx-example"
- version = "0.1.0"
- edition = "2021"
- [dependencies]
- cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
- cortex-m-rt = "0.7.3"
- mcxn947-pac = "0.0.3"
- panic-halt = "0.2.0"
复制代码 让我简单介绍各个依赖的作用:
- cortex-m 该库引入了 Cortex-M 架构的定义和一些抽象,如常见的汇编指令,中断等
- cortex-m-rt 这个库是 Cortex-M 架构的通用运行时,提供一套内置的linker script 和 ResetHandler 的实现等等
- mcxn947-pac 包含了 MCXN947 的寄存器定义,中断定义
- panic-halt 实现默认的 panic handler
示例:点灯
说起具体介绍,当然举个例子-点灯:
- // src/main.rs
- #![no_std] // 无标准库
- #![no_main] // 无入口
- // 提供一个 panic 实现
- extern crate panic_halt;
- use cortex_m_rt::entry;
- use mcxn947_pac as pac;
- // entry 标志在 Reset 中跳转到此
- #[entry]
- fn main() -> ! {
- let dp = pac::Peripherals::take().unwrap();
- let cp = pac::CorePeripherals::take().unwrap();
- // 启用 PORT0 和 GPIO0 的时钟
- dp.SYSCON0
- .ahbclkctrl0()
- .modify(|_r, w| w.port0().enable().gpio0().enable());
- // 设置 PIO0_10 为推挽输出
- dp.PORT0.pcr(10).modify(|_r, w| w.mux().mux00());
- dp.GPIO0.pdor().modify(|_r, w| w.pdo10().clear_bit());
- dp.GPIO0.pddr().modify(|_r, w| w.pdd10().set_bit());
- // cortex-m 库提供的方便的抽象,使用 SysTick timer 来进行延时
- // 在默认情况下 SysTick 的频率与主频相同,在这段代码中我们没有对时钟进行配置,所以默认为48MHz
- let mut delay = cortex_m::delay::Delay::new(cp.SYST, 48_000_000u32);
- loop {
- delay.delay_ms(1000u32);
- dp.GPIO0.ptor().write(|w| w.ptto10().set_bit());
- }
- }
复制代码 #![no_main] 指定不向外暴露符号 main, 所以即使我们的代码中有 main 函数,它也不会被当作真正的 “main” 函数看待。同时 #[entry] 标志该函数被链接到 cortex-m-rt 库中内置链接脚本中的 “main” 函数。
添加一份 memory.x ,这是一份 linker script ,在 cortex-m-rt 库中包含的默认 linker script 中有 include memory.x 的定义。所以我们需要添加一份,顾名思义,这份文件包含内存定义,同时如果我们想把特定数据或函数放在某个段也是可以在这定义。
- MEMORY {
- FLASH : ORIGIN = 0x00000000, LENGTH = 2M
- RAM : ORIGIN = 0x20000000, LENGTH = 320K
- }
复制代码 运行命令 cargo build 进行构建,产物位于 target/thumbv8m.main-none-eabihf/debug/mcx-example 格式为 elf 。
使用任意工具把产物加载到 MCU 上, 就以 jlink 为例,首先把产物转成 hex 格式, arm-none-eabi-objcopy -O ihex target/thumbv8m.main-none-eabihf/debug/mcx-example mcx-example.hex, 然后使用 jflashLite 加载。
功能正常实现。
如果需要 Debug, 请参考使用VSCode调试嵌入式程序:配置与使用多样化的gdb server:
示例:按键开灯
此示例主要是介绍中断用法及Rust 的线程安全用法:
在上面这段代码中我们当然可以不使用 Mutex ,而是直接使用一个 static mut FLAG_BTN_PRESSED: bool = false ,但有关于该变量的所有操作都需要使用 unsafe 标签,这在正常的开发过程中应该是极力避免的,因为这种 unsafe 操作会导致 data race 。Mutex 是一个简单包装,使用一个 CriticalSection 标志来实现,cortex_m::interrupt::free 提供一个简单的临界区实现,即关闭所有中断。或者可以使用原子操作 core::sync::atomic::AtomicBool, static FLAG_BTN_PRESSED: AtomicBool = AtomicBool::new(false);
cortex_m::interrupt::free 使用一个 lambda 函数来在其操作前后添加关闭、打开中断的操作。
虽然看起来写起来很麻烦,但实际上编译结果并没有多余的操作,所有看上去繁琐的操作实际上只是指导编译器如何编译,并不会生成实际的代码,例如中断中的 cs 变量,它并没有实际的大小。
同样烧录进 MCU ,按下 ISP 按键即可控制灯的开关。
使用HAL
上述两个例子均直接使用寄存器进行操作,方法过于原始,可以使用 HAL 库来简化操作。HAL 库正在积极开发中,所以使用 GitHub 上的最新版本。添加依赖。
- <p><font size="3" face="微软雅黑">[package]</font></p><p><font size="3" face="微软雅黑">name = "mcx-example"</font></p><p><font size="3" face="微软雅黑">version = "0.1.0"</font></p><p><font size="3" face="微软雅黑">edition = "2021"</font></p><p><font size="3" face="微软雅黑">
- </font></p><p><font size="3" face="微软雅黑">[dependencies]</font></p><p><font size="3" face="微软雅黑">cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }</font></p><p><font size="3" face="微软雅黑">cortex-m-rt = "0.7.3"</font></p><p><font size="3" face="微软雅黑">mcxn947-pac = "0.0.3"</font></p><p><font size="3" face="微软雅黑">panic-halt = "0.2.0"</font></p><p><font size="3" face="微软雅黑">mcx-hal = { git = "https://github.com/mcx-rs/mcx-hal.git" }</font></p>
复制代码- #![no_std]
- #![no_main]
- use embedded_hal::digital::StatefulOutputPin;
- use panic_halt as _;
- use core::cell::{Cell, RefCell};
- use cortex_m::asm::wfi;
- use cortex_m::interrupt::Mutex;
- use cortex_m_rt::entry;
- use mcx_hal::{self as hal, pac, pac::interrupt};
- type BtnType = hal::gpio::gpio0::PIO0_6<hal::gpio::Input<hal::gpio::Floating>>;
- static FLAG_BTN_PRESSED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
- static BTN: Mutex<RefCell<Option<BtnType>>> = Mutex::new(RefCell::new(None));
- #[entry]
- fn main() -> ! {
- let dp = pac::Peripherals::take().unwrap();
- // 设置 pin 的状态更方便了
- let gpio0 = hal::gpio::gpio0::split(dp.GPIO0, dp.PORT0);
- let mut btn = gpio0.pio0_6.into_floating_input();
- let mut led_r = gpio0.pio0_10.into_push_pull_output();
- btn.enable_irq(
- hal::gpio::GPIOInterruptSource::FallingEdge,
- hal::gpio::GPIOInterruptSelect::IRQ0,
- );
- cortex_m::interrupt::free(|cs| {
- BTN.borrow(cs).replace(Some(btn));
- });
- // enable GPIO0 irq
- unsafe {
- pac::NVIC::unmask(pac::Interrupt::GPIO00);
- }
- loop {
- wfi();
- cortex_m::interrupt::free(|cs| {
- if FLAG_BTN_PRESSED.borrow(cs).get() {
- FLAG_BTN_PRESSED.borrow(cs).set(false);
- led_r.toggle().unwrap();
- }
- });
- }
- }
- #[interrupt]
- fn GPIO00() {
- cortex_m::interrupt::free(|cs| {
- let mut btn = BTN.borrow(cs).borrow_mut();
- btn.as_mut().unwrap().clear_irq_flag();
- FLAG_BTN_PRESSED.borrow(cs).set(true);
- });
- }
复制代码 如果我们想要使用其他中断,该怎么知道它的名字呢?十分简单,所有的中断定义都在 mcxn947-pac::Interrupt 中。
这个例子同样是使用 ISP 按键来控制红灯的开关。
Linker script
将特定的数据放置在特定的位置,也是嵌入式开发中常见的操作,那么怎么在 Rust 上实现呢?
修改 memory.x 即可:
- MEMORY {
- FLASH : ORIGIN = 0x00000000, LENGTH = 1M
- RAM : ORIGIN = 0x20000000, LENGTH = 128K
- MY_RAM : ORIGIN = 0x04000000, LENGTH = 16K
- }
- SECTIONS {
- .my_custom_data_in_my_ram (NOLOAD) : ALIGN(4) {
- (.my_custom_data_in_my_ram.my_custom_data_in_my_ram.);
- . = ALIGN(4);
- } > MY_RAM
- }
复制代码 如果要把中断函数放在 RAM 里,需要一点额外操作,首先需要去掉过程宏 #[interrupt] 。
#[interrupt] 可以看作是#[export_name = ...]和一段代码展开的缩写。
我们可以去掉它,手动加上一些宏来达到同样的效果。首先添加 #[no_mangle] 防止编译器对它重新命名,或者使用 #[export_name = ...] 来让它的名字是中断名。
然后添加#[link_section = ...] 来让它链接到 MY_RAM 中。
- #[link_section = ".my_custom_data_in_my_ram.my_custom_name"]
- #[no_mangle]
- fn GPIO00() {
- cortex_m::interrupt::free(|cs| {
- let mut btn = BTN.borrow(cs).borrow_mut();
- btn.as_mut().unwrap().clear_irq_flag();
- FLAG_BTN_PRESSED.borrow(cs).set(true);
- });
- }
复制代码 使用命令 arm-none-eabi-size -Ax target/thumbv8m.main-none-eabihf/debug/mcx-example 或者使用命令 cargo install cargo-binutils && rustup component add llvm-tools ,之后就可以 cargo size -- -Ax.
查看编译结果,确认中断被我们放进了 MY_RAM 里:
在 Debug 下,也可以看到中断时 PC 的地址:
希望以上内容可以对想使用 Rust 进行嵌入式开发的伙伴们提供指引与助力。
接下来,我们还将深入探索Rust 中的RTOS与实时工具,会为大家揭开更多技术奥秘,敬请持续关注,精彩不容错过!
|
|