请选择 进入手机版 | 继续访问电脑版
查看: 1506|回复: 8

[分享] 当MCX N947遇上Rust,打造未来嵌入式系统的黄金搭档!

[复制链接]
  • TA的每日心情
    开心
    前天 08:46
  • 签到天数: 295 天

    [LV.8]以坛为家I

    3488

    主题

    6922

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    34601
    最后登录
    2024-10-13
    发表于 2024-7-25 09:08:18 | 显示全部楼层 |阅读模式
    当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 支持。
    1. rustup target add thumbv8m.main-none-eabihf
    复制代码
    同样,如果我们想为其他平台编译,像 cortex-m3 ,则需要运行:
    1. 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 文件内容如下:
    1. [build]

    2. target = "thumbv8m.main-none-eabihf"

    3. [target.thumbv8m.main-none-eabihf]

    4. rustflags = ["-C", "link-arg=-Tlink.x"]
    复制代码
    接下来添加必要的依赖, Cargo.toml 的内容应该与下面的内容类似:
    1. [package]

    2. name = "mcx-example"

    3. version = "0.1.0"

    4. edition = "2021"

    5. [dependencies]

    6. cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }

    7. cortex-m-rt = "0.7.3"

    8. mcxn947-pac = "0.0.3"

    9. panic-halt = "0.2.0"
    复制代码
    让我简单介绍各个依赖的作用:
    • cortex-m 该库引入了 Cortex-M 架构的定义和一些抽象,如常见的汇编指令,中断等
    • cortex-m-rt 这个库是 Cortex-M 架构的通用运行时,提供一套内置的linker script 和 ResetHandler 的实现等等
    • mcxn947-pac 包含了 MCXN947 的寄存器定义,中断定义
    • panic-halt 实现默认的 panic handler

    示例:点灯
    说起具体介绍,当然举个例子-点灯:
    1. // src/main.rs

    2. #![no_std] // 无标准库

    3. #![no_main] // 无入口


    4. // 提供一个 panic 实现

    5. extern crate panic_halt;

    6. use cortex_m_rt::entry;

    7. use mcxn947_pac as pac;

    8. // entry 标志在 Reset 中跳转到此

    9. #[entry]

    10. fn main() -> ! {

    11.     let dp = pac::Peripherals::take().unwrap();

    12.     let cp = pac::CorePeripherals::take().unwrap();

    13.     // 启用 PORT0 和 GPIO0 的时钟

    14.     dp.SYSCON0

    15.         .ahbclkctrl0()

    16.         .modify(|_r, w| w.port0().enable().gpio0().enable());

    17.     // 设置 PIO0_10 为推挽输出

    18.     dp.PORT0.pcr(10).modify(|_r, w| w.mux().mux00());

    19.     dp.GPIO0.pdor().modify(|_r, w| w.pdo10().clear_bit());

    20.     dp.GPIO0.pddr().modify(|_r, w| w.pdd10().set_bit());

    21.     // cortex-m 库提供的方便的抽象,使用 SysTick timer 来进行延时

    22.     // 在默认情况下 SysTick 的频率与主频相同,在这段代码中我们没有对时钟进行配置,所以默认为48MHz

    23.     let mut delay = cortex_m::delay::Delay::new(cp.SYST, 48_000_000u32);

    24.     loop {

    25.         delay.delay_ms(1000u32);

    26.         dp.GPIO0.ptor().write(|w| w.ptto10().set_bit());

    27.     }

    28. }
    复制代码
    #![no_main] 指定不向外暴露符号 main, 所以即使我们的代码中有 main 函数,它也不会被当作真正的 “main” 函数看待。同时 #[entry] 标志该函数被链接到 cortex-m-rt 库中内置链接脚本中的 “main” 函数。


    添加一份 memory.x ,这是一份 linker script ,在 cortex-m-rt 库中包含的默认 linker script 中有 include memory.x 的定义。所以我们需要添加一份,顾名思义,这份文件包含内存定义,同时如果我们想把特定数据或函数放在某个段也是可以在这定义。
    1. MEMORY {

    2.      FLASH : ORIGIN = 0x00000000, LENGTH = 2M

    3.      RAM   : ORIGIN = 0x20000000, LENGTH = 320K

    4. }
    复制代码
    运行命令 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 加载。
    11.png
    功能正常实现。

    如果需要 Debug, 请参考使用VSCode调试嵌入式程序:配置与使用多样化的gdb server:
    12.png
    示例:按键开灯
    此示例主要是介绍中断用法及Rust 的线程安全用法:
    1. #![no_std]

    2. #![no_main]

    3. extern crate panic_halt;

    4. use core::cell::{Cell, RefCell};

    5. use cortex_m::{asm::wfi, interrupt::Mutex};

    6. use cortex_m_rt::entry;

    7. use mcxn947_pac as pac;

    8. use pac::interrupt;

    9. // Rust 的安全特性要求

    10. static FLAG_BTN_PRESSED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));

    11. static GPIO0: Mutex<RefCell<Option<pac::GPIO0>>> = Mutex::new(RefCell::new(None));

    12. #[entry]

    13. fn main() -> ! {

    14.     let dp = pac::Peripherals::take().unwrap();

    15.     let cp = pac::CorePeripherals::take().unwrap();

    16.     // 启用 PORT0 和 GPIO0 的时钟

    17.     dp.SYSCON0

    18.         .ahbclkctrl0()

    19.         .modify(|_r, w| w.port0().enable().gpio0().enable());

    20.     // 设置 PIO0_10 为推挽输出

    21.     dp.PORT0.pcr(10).modify(|_r, w| w.mux().mux00());

    22.     dp.GPIO0.pdor().modify(|_r, w| w.pdo10().clear_bit());

    23.     dp.GPIO0.pddr().modify(|_r, w| w.pdd10().set_bit());

    24.     // 设置 PIO0_6

    25.     dp.PORT0.pcr(6).modify(|_r, w| w.mux().mux00());

    26.     dp.GPIO0.pddr().modify(|_r, w| w.pdd6().clear_bit());

    27.     dp.GPIO0

    28.         .icr(6)

    29.         .write(|w| w.isf().clear_bit_by_one().irqs().irqs0().irqc().irqc10());

    30.     // 启用 GPIO00 中断

    31.     unsafe { pac::NVIC::unmask(pac::Interrupt::GPIO00) }

    32.     // 在关闭中断的情况下向全局变量写入数据

    33.     // why: GPIO0 可能在 main 与 GPIO00 **享

    34.     cortex_m::interrupt::free(|cs| {

    35.         GPIO0.borrow(cs).replace(dp.GPIO0.into());

    36.     });


    37.     loop {

    38.         wfi();

    39.         cortex_m::interrupt::free(|cs| {

    40.             if FLAG_BTN_PRESSED.borrow(cs).get() {

    41.                 GPIO0

    42.                     .borrow(cs)

    43.                     .borrow_mut()

    44.                     .as_mut()

    45.                     .unwrap()

    46.                     .ptor()

    47.                     .write(|w| w.ptto10().set_bit());

    48.             }

    49.         })

    50.     }

    51. }


    52. // GPIO00 中断

    53. #[interrupt]

    54. fn GPIO00() {

    55.     cortex_m::interrupt::free(|cs| {

    56.         let mut gpio = GPIO0.borrow(cs).borrow_mut();

    57.         gpio.as_mut()

    58.             .unwrap()

    59.             .icr(6)

    60.             .modify(|_r, w| w.isf().clear_bit_by_one());

    61.         FLAG_BTN_PRESSED.borrow(cs).set(true);

    62.     })

    63. }
    复制代码
    在上面这段代码中我们当然可以不使用 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 上的最新版本。添加依赖。
    1. <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="微软雅黑">
    2. </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>
    复制代码
    1. #![no_std]

    2. #![no_main]

    3. use embedded_hal::digital::StatefulOutputPin;

    4. use panic_halt as _;

    5. use core::cell::{Cell, RefCell};

    6. use cortex_m::asm::wfi;

    7. use cortex_m::interrupt::Mutex;

    8. use cortex_m_rt::entry;

    9. use mcx_hal::{self as hal, pac, pac::interrupt};

    10. type BtnType = hal::gpio::gpio0::PIO0_6<hal::gpio::Input<hal::gpio::Floating>>;

    11. static FLAG_BTN_PRESSED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));

    12. static BTN: Mutex<RefCell<Option<BtnType>>> = Mutex::new(RefCell::new(None));

    13. #[entry]

    14. fn main() -> ! {

    15.     let dp = pac::Peripherals::take().unwrap();

    16.     // 设置 pin 的状态更方便了

    17.     let gpio0 = hal::gpio::gpio0::split(dp.GPIO0, dp.PORT0);

    18.     let mut btn = gpio0.pio0_6.into_floating_input();

    19.     let mut led_r = gpio0.pio0_10.into_push_pull_output();

    20.     btn.enable_irq(

    21.         hal::gpio::GPIOInterruptSource::FallingEdge,

    22.         hal::gpio::GPIOInterruptSelect::IRQ0,

    23.     );

    24.     cortex_m::interrupt::free(|cs| {

    25.         BTN.borrow(cs).replace(Some(btn));

    26.     });


    27.     // enable GPIO0 irq

    28.     unsafe {

    29.         pac::NVIC::unmask(pac::Interrupt::GPIO00);

    30.     }


    31.     loop {

    32.         wfi();


    33.         cortex_m::interrupt::free(|cs| {

    34.             if FLAG_BTN_PRESSED.borrow(cs).get() {

    35.                 FLAG_BTN_PRESSED.borrow(cs).set(false);

    36.                 led_r.toggle().unwrap();

    37.             }

    38.         });

    39.     }

    40. }

    41. #[interrupt]

    42. fn GPIO00() {

    43.     cortex_m::interrupt::free(|cs| {

    44.         let mut btn = BTN.borrow(cs).borrow_mut();

    45.         btn.as_mut().unwrap().clear_irq_flag();

    46.         FLAG_BTN_PRESSED.borrow(cs).set(true);

    47.     });

    48. }
    复制代码
    如果我们想要使用其他中断,该怎么知道它的名字呢?十分简单,所有的中断定义都在 mcxn947-pac::Interrupt 中。
    13.png
    这个例子同样是使用 ISP 按键来控制红灯的开关。

    Linker script
    将特定的数据放置在特定的位置,也是嵌入式开发中常见的操作,那么怎么在 Rust 上实现呢?

    修改 memory.x 即可:
    1. MEMORY {

    2.     FLASH : ORIGIN = 0x00000000, LENGTH = 1M

    3.     RAM : ORIGIN = 0x20000000, LENGTH = 128K

    4.     MY_RAM : ORIGIN = 0x04000000, LENGTH = 16K

    5. }



    6. SECTIONS {

    7.     .my_custom_data_in_my_ram (NOLOAD) : ALIGN(4) {

    8.         (.my_custom_data_in_my_ram.my_custom_data_in_my_ram.);

    9.         . = ALIGN(4);

    10.     } > MY_RAM

    11. }
    复制代码
    如果要把中断函数放在 RAM 里,需要一点额外操作,首先需要去掉过程宏 #[interrupt] 。
    #[interrupt] 可以看作是#[export_name = ...]和一段代码展开的缩写。
    我们可以去掉它,手动加上一些宏来达到同样的效果。首先添加 #[no_mangle] 防止编译器对它重新命名,或者使用 #[export_name = ...] 来让它的名字是中断名。
    然后添加#[link_section = ...] 来让它链接到 MY_RAM 中。
    1. #[link_section = ".my_custom_data_in_my_ram.my_custom_name"]

    2. #[no_mangle]

    3. fn GPIO00() {

    4.     cortex_m::interrupt::free(|cs| {

    5.         let mut btn = BTN.borrow(cs).borrow_mut();

    6.         btn.as_mut().unwrap().clear_irq_flag();

    7.         FLAG_BTN_PRESSED.borrow(cs).set(true);

    8.     });

    9. }
    复制代码
    使用命令 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 里:
    14.jpg
    在 Debug 下,也可以看到中断时 PC 的地址:
    15.png

    希望以上内容可以对想使用 Rust 进行嵌入式开发的伙伴们提供指引与助力。

    接下来,我们还将深入探索Rust 中的RTOS与实时工具,会为大家揭开更多技术奥秘,敬请持续关注,精彩不容错过!


    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    19 分钟前
  • 签到天数: 704 天

    [LV.9]以坛为家II

    63

    主题

    2750

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    8780
    最后登录
    2024-10-14
    发表于 2024-7-25 09:15:27 | 显示全部楼层
    不错的内容  有时间玩以下 体验下安全的语言是啥样的
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    昨天 20:46
  • 签到天数: 1556 天

    [LV.Master]伴坛终老

    53

    主题

    3868

    帖子

    21

    金牌会员

    Rank: 6Rank: 6

    积分
    8138
    最后登录
    2024-10-14
    发表于 2024-7-25 09:37:29 | 显示全部楼层
    整这么高端
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    1 小时前
  • 签到天数: 274 天

    [LV.8]以坛为家I

    18

    主题

    504

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    1355
    最后登录
    2024-10-14
    发表于 2024-7-25 09:43:05 | 显示全部楼层
    官方提供对N947的RUST包的支持,这非常好呀,一定试一下。
    哎...今天够累的,签到来了~
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    2024-9-26 13:39
  • 签到天数: 85 天

    [LV.6]常住居民II

    8

    主题

    168

    帖子

    0

    高级会员

    Rank: 4

    积分
    628
    最后登录
    2024-10-9
    发表于 2024-7-25 10:02:45 | 显示全部楼层
    有完整的资料码?可以知道每个步骤为什么这么做的资料?
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    23 分钟前
  • 签到天数: 162 天

    [LV.7]常住居民III

    48

    主题

    721

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    1634
    最后登录
    2024-10-14
    发表于 2024-7-25 16:39:13 | 显示全部楼层
    听说最近一次的window蓝屏事件貌似就是野指针导致的,那这个rust 貌似要火了呀
    哎...今天够累的,签到来了~
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 00:03
  • 签到天数: 1616 天

    [LV.Master]伴坛终老

    203

    主题

    3万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    103644
    最后登录
    2024-10-13
    发表于 2024-7-25 21:38:10 | 显示全部楼层
    rust在Linux上确实越来越受到重视了
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    0

    主题

    1

    帖子

    0

    新手上路

    Rank: 1

    积分
    3
    最后登录
    2024-7-26
    发表于 2024-7-26 03:06:20 | 显示全部楼层
    stm1024 发表于 2024-7-25 21:38
    rust在Linux上确实越来越受到重视了

    怎么说??
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    奋斗
    昨天 00:03
  • 签到天数: 1616 天

    [LV.Master]伴坛终老

    203

    主题

    3万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    103644
    最后登录
    2024-10-13
    发表于 2024-7-26 20:19:56 | 显示全部楼层

    都进linux内核了,听说还打算搞一个rust原生linux内核
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-10-14 09:25 , Processed in 0.152814 second(s), 30 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

    快速回复 返回顶部 返回列表