Rust 有很多優(yōu)勢(shì),內(nèi)存安全、并發(fā)安全、生態(tài)系統(tǒng)、包管理與構(gòu)建管理,同時(shí)也有與 C/C++ 相同等級(jí)的性能。Rust 通過(guò)強(qiáng)化所有權(quán)和借用的概念,盡力消除了開(kāi)發(fā)過(guò)程中可能出現(xiàn)的內(nèi)存問(wèn)題。同時(shí)作為一門(mén)現(xiàn)代語(yǔ)言,有著許多方便的特性與豐富的生態(tài)資源,如統(tǒng)一的包管理,高可讀代碼等等。
但 Rust 也有美中不足,如缺乏對(duì)底層的完全控制,學(xué)習(xí)難度高,編譯時(shí)間長(zhǎng)等。由于 Rust 的安全與高抽象能力,許多非安全操作被禁止,許多在 C 中能夠通過(guò)指針進(jìn)行的簡(jiǎn)單操作在 Rust 中需要十分復(fù)雜的操作,這也導(dǎo)致 Rust 的學(xué)習(xí)難度更高。
本文會(huì)對(duì)在 NXP MCX 平臺(tái)上使用 Rust 進(jìn)行簡(jiǎn)單介紹。在本文中使用 FRDM-MCXN947 為例,所有的例子均運(yùn)行在 Core0 上。
安裝Rust工具鏈Rust 工具鏈的安裝十分簡(jiǎn)單,參考Rustup即可。默認(rèn)狀態(tài)下,Rustup 工具只會(huì)安裝本機(jī)的 TARGET ,為了能夠在我們的 MCU 上運(yùn)行編譯產(chǎn)物, 需要安裝對(duì)應(yīng)的 TARGET 。可以通過(guò)運(yùn)行如下命令來(lái)添加 armv8m hard-float 支持。
rustup target add thumbv8m.main-none-eabihf
同樣,如果我們想為其他平臺(tái)編譯,像 cortex-m3 ,則需要運(yùn)行:
rustup target add thumbv8m.main-none-eabihf
如果要在沒(méi)有 FPU 的 Core1 上運(yùn)行,則需要使用命令rustup target add thumbv8m.main-none-eabi添加thumbv8m.main-none-eabi target,注意無(wú) hf 尾綴, 代表不使用硬件浮點(diǎn)數(shù) ABI
創(chuàng)建項(xiàng)目在添加支持后,在我們想要保存項(xiàng)目的文件夾運(yùn)行命令cargo new mcx-example創(chuàng)建一個(gè)名為 mcx-example 的工程, 同時(shí)創(chuàng)建一個(gè)配置文件 .cargo/config.toml 來(lái)指定編譯參數(shù)和默認(rèn) target。
創(chuàng)建的 .cargo/config.toml 文件內(nèi)容如下:
[build]
target = "thumbv8m.main-none-eabihf"
[target.thumbv8m.main-none-eabihf]
rustflags = ["-C", "link-arg=-Tlink.x"]
接下來(lái)添加必要的依賴, Cargo.toml 的內(nèi)容應(yīng)該與下面的內(nèi)容類似:
[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"
讓我簡(jiǎn)單介紹各個(gè)依賴的作用:
-
cortex-m 該庫(kù)引入了 Cortex-M 架構(gòu)的定義和一些抽象,如常見(jiàn)的匯編指令,中斷等
-
cortex-m-rt 這個(gè)庫(kù)是 Cortex-M 架構(gòu)的通用運(yùn)行時(shí),提供一套內(nèi)置的linker script 和 ResetHandler 的實(shí)現(xiàn)等等
-
mcxn947-pac 包含了 MCXN947 的寄存器定義,中斷定義
-
panic-halt 實(shí)現(xiàn)默認(rèn)的 panic handler
說(shuō)起具體介紹,當(dāng)然舉個(gè)例子-點(diǎn)燈:
// src/main.rs
#![no_std] // 無(wú)標(biāo)準(zhǔn)庫(kù)
#![no_main] // 無(wú)入口
// 提供一個(gè) panic 實(shí)現(xiàn)
extern crate panic_halt;
use cortex_m_rt::entry;
use mcxn947_pac as pac;
// entry 標(biāo)志在 Reset 中跳轉(zhuǎn)到此
#[entry]
fn main() -> ! {
let dp = pac::take().unwrap();
let cp = pac::take().unwrap();
// 啟用 PORT0 和 GPIO0 的時(shí)鐘
dp.SYSCON0
.ahbclkctrl0()
.modify(|_r, w| w.port0().enable().gpio0().enable());
// 設(shè)置 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 庫(kù)提供的方便的抽象,使用 SysTick timer 來(lái)進(jìn)行延時(shí)
// 在默認(rèn)情況下 SysTick 的頻率與主頻相同,在這段代碼中我們沒(méi)有對(duì)時(shí)鐘進(jìn)行配置,所以默認(rèn)為48MHz
let mut delay = cortex_m::new(cp.SYST, 48_000_000u32);
loop {
delay.delay_ms(1000u32);
dp.GPIO0.ptor().write(|w| w.ptto10().set_bit());
}
}
#![no_main] 指定不向外暴露符號(hào) main, 所以即使我們的代碼中有 main 函數(shù),它也不會(huì)被當(dāng)作真正的 “main” 函數(shù)看待。同時(shí) #[entry] 標(biāo)志該函數(shù)被鏈接到 cortex-m-rt 庫(kù)中內(nèi)置鏈接腳本中的 “main” 函數(shù)。
添加一份 memory.x ,這是一份 linker script ,在 cortex-m-rt 庫(kù)中包含的默認(rèn) linker script 中有 include memory.x 的定義。所以我們需要添加一份,顧名思義,這份文件包含內(nèi)存定義,同時(shí)如果我們想把特定數(shù)據(jù)或函數(shù)放在某個(gè)段也是可以在這定義。
MEMORY {
FLASH : ORIGIN = 0x00000000, LENGTH = 2M
RAM:ORIGIN=0x20000000,LENGTH=320K
}
運(yùn)行命令 cargo build 進(jìn)行構(gòu)建,產(chǎn)物位于 target/thumbv8m.main-none-eabihf/debug/mcx-example 格式為 elf 。
使用任意工具把產(chǎn)物加載到 MCU 上, 就以 jlink 為例,首先把產(chǎn)物轉(zhuǎn)成 hex 格式,arm-none-eabi-objcopy -O ihex target/thumbv8m.main-none-eabihf/debug/mcx-example mcx-example.hex, 然后使用 jflashLite 加載。
功能正常實(shí)現(xiàn)。
如果需要 Debug, 請(qǐng)參考使用VSCode調(diào)試嵌入式程序:配置與使用多樣化的gdb server:
示例:按鍵開(kāi)燈
此示例主要是介紹中斷用法及Rust 的線程安全用法:
#![no_std]
#![no_main]
extern crate panic_halt;
use core::cell::{Cell, RefCell};
use cortex_m::{asm::wfi, interrupt::Mutex};
use cortex_m_rt::entry;
use mcxn947_pac as pac;
use pac::interrupt;
// Rust 的安全特性要求
static FLAG_BTN_PRESSED: Mutex> = Mutex::new(false)); |
static GPIO0: Mutex>> = Mutex::new(None));
#[entry]
fn main() -> ! {
let dp = pac::take().unwrap();
let cp = pac::take().unwrap();
// 啟用 PORT0 和 GPIO0 的時(shí)鐘
dp.SYSCON0
.ahbclkctrl0()
.modify(|_r, w| w.port0().enable().gpio0().enable());
// 設(shè)置 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());
// 設(shè)置 PIO0_6
dp.PORT0.pcr(6).modify(|_r, w| w.mux().mux00());
dp.GPIO0.pddr().modify(|_r, w| w.pdd6().clear_bit());
dp.GPIO0
.icr(6)
.write(|w| w.isf().clear_bit_by_one().irqs().irqs0().irqc().irqc10());
// 啟用 GPIO00 中斷
unsafe { pac::GPIO00) }
// 在關(guān)閉中斷的情況下向全局變量寫(xiě)入數(shù)據(jù)
// why: GPIO0 可能在 main 與 GPIO00 中共享
cortex_m::free(|cs| {
GPIO0.borrow(cs).replace(dp.GPIO0.into());
});
loop {
wfi();
cortex_m::free(|cs| {
if FLAG_BTN_PRESSED.borrow(cs).get() {
GPIO0
.borrow(cs)
.borrow_mut()
.as_mut()
.unwrap()
.ptor()
.write(|w| w.ptto10().set_bit());
}
})
}
}
// GPIO00 中斷
#[interrupt]
fn GPIO00() {
cortex_m::free(|cs| {
let mut gpio = GPIO0.borrow(cs).borrow_mut();
gpio.as_mut()
.unwrap()
.icr(6)
.modify(|_r, w| w.isf().clear_bit_by_one());
FLAG_BTN_PRESSED.borrow(cs).set(true);
})
}
在上面這段代碼中我們當(dāng)然可以不使用 Mutex ,而是直接使用一個(gè) static mut FLAG_BTN_PRESSED: bool = false ,但有關(guān)于該變量的所有操作都需要使用 unsafe 標(biāo)簽,這在正常的開(kāi)發(fā)過(guò)程中應(yīng)該是極力避免的,因?yàn)檫@種 unsafe 操作會(huì)導(dǎo)致 data race 。Mutex 是一個(gè)簡(jiǎn)單包裝,使用一個(gè) CriticalSection 標(biāo)志來(lái)實(shí)現(xiàn),cortex_m::free 提供一個(gè)簡(jiǎn)單的臨界區(qū)實(shí)現(xiàn),即關(guān)閉所有中斷。或者可以使用原子操作 core::AtomicBool, static FLAG_BTN_PRESSED: AtomicBool = AtomicBool::new(false);
cortex_m::free 使用一個(gè) lambda 函數(shù)來(lái)在其操作前后添加關(guān)閉、打開(kāi)中斷的操作。
雖然看起來(lái)寫(xiě)起來(lái)很麻煩,但實(shí)際上編譯結(jié)果并沒(méi)有多余的操作,所有看上去繁瑣的操作實(shí)際上只是指導(dǎo)編譯器如何編譯,并不會(huì)生成實(shí)際的代碼,例如中斷中的 cs 變量,它并沒(méi)有實(shí)際的大小。
同樣燒錄進(jìn) MCU ,按下 ISP 按鍵即可控制燈的開(kāi)關(guān)。
使用HAL
上述兩個(gè)例子均直接使用寄存器進(jìn)行操作,方法過(guò)于原始,可以使用 HAL 庫(kù)來(lái)簡(jiǎn)化操作。HAL 庫(kù)正在積極開(kāi)發(fā)中,所以使用 GitHub 上的最新版本。添加依賴。
[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"
mcx-hal = { git = "https://github.com/mcx-rs/mcx-hal.git" }
#![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::PIO0_6>;
static FLAG_BTN_PRESSED: Mutex> = Mutex::new(false)); |
static BTN: Mutex>> = Mutex::new(None));
#[entry]
fn main() -> ! {
let dp = pac::take().unwrap();
// 設(shè)置 pin 的狀態(tài)更方便了
let gpio0 = hal::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::FallingEdge,
hal::IRQ0,
);
cortex_m::free(|cs| {
BTN.borrow(cs).replace(Some(btn));
});
// enable GPIO0 irq
unsafe {
pac::GPIO00);
}
loop {
wfi();
cortex_m::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::free(|cs| {
let mut btn = BTN.borrow(cs).borrow_mut();
btn.as_mut().unwrap().clear_irq_flag();
FLAG_BTN_PRESSED.borrow(cs).set(true);
});
}
如果我們想要使用其他中斷,該怎么知道它的名字呢?十分簡(jiǎn)單,所有的中斷定義都在 mcxn947-pac::Interrupt 中。
這個(gè)例子同樣是使用 ISP 按鍵來(lái)控制紅燈的開(kāi)關(guān)。
Linkerscript
將特定的數(shù)據(jù)放置在特定的位置,也是嵌入式開(kāi)發(fā)中常見(jiàn)的操作,那么怎么在 Rust 上實(shí)現(xiàn)呢?
修改 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
}
如果要把中斷函數(shù)放在 RAM 里,需要一點(diǎn)額外操作,首先需要去掉過(guò)程宏#[interrupt]。
#[interrupt]可以看作是#[export_name = ...]和一段代碼展開(kāi)的縮寫(xiě)。
我們可以去掉它,手動(dòng)加上一些宏來(lái)達(dá)到同樣的效果。首先添加#[no_mangle]防止編譯器對(duì)它重新命名,或者使用#[export_name = ...]來(lái)讓它的名字是中斷名。
然后添加#[link_section = ...]來(lái)讓它鏈接到MY_RAM中。
#[link_section=".my_custom_data_in_my_ram.my_custom_name"]
#[no_mangle]
fn GPIO00() {
cortex_m::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.
查看編譯結(jié)果,確認(rèn)中斷被我們放進(jìn)了 MY_RAM 里:在 Debug 下,也可以看到中斷時(shí) PC 的地址:
希望以上內(nèi)容可以對(duì)想使用 Rust 進(jìn)行嵌入式開(kāi)發(fā)的伙伴們提供指引與助力。
接下來(lái),我們還將深入探索Rust 中的RTOS與實(shí)時(shí)工具,會(huì)為大家揭開(kāi)更多技術(shù)奧秘,敬請(qǐng)持續(xù)關(guān)注,精彩不容錯(cuò)過(guò)!
-
嵌入式系統(tǒng)
+關(guān)注
關(guān)注
41文章
3593瀏覽量
129466 -
RTOS
+關(guān)注
關(guān)注
22文章
813瀏覽量
119632 -
Rust
+關(guān)注
關(guān)注
1文章
228瀏覽量
6607
原文標(biāo)題:當(dāng)MCX N947遇上Rust,打造未來(lái)嵌入式系統(tǒng)的黃金搭檔!
文章出處:【微信號(hào):NXP_SMART_HARDWARE,微信公眾號(hào):恩智浦MCU加油站】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論