0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

未来嵌入式系统的黄金搭档 MCX N947遇上Rust

恩智浦MCU加油站 来源: 恩智浦MCU加油站 2024-07-25 09:14 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

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::take().unwrap();
    let cp = pac::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::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 加载。

3cc56cae-4a20-11ef-b8af-92fbcf53809c.png

功能正常实现。

如果需要 Debug, 请参考使用VSCode调试嵌入式程序:配置与使用多样化的gdb server:

3cd8cb96-4a20-11ef-b8af-92fbcf53809c.png

示例:按键开灯

此示例主要是介绍中断用法及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 的时钟
    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());


    // 设置 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) }


    // 在关闭中断的情况下向全局变量写入数据
    // 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);
    })
}

在上面这段代码中我们当然可以不使用 Mutex ,而是直接使用一个 static mut FLAG_BTN_PRESSED: bool = false ,但有关于该变量的所有操作都需要使用 unsafe 标签,这在正常的开发过程中应该是极力避免的,因为这种 unsafe 操作会导致 data race 。Mutex 是一个简单包装,使用一个 CriticalSection 标志来实现,cortex_m::free 提供一个简单的临界区实现,即关闭所有中断。或者可以使用原子操作 core::AtomicBool, static FLAG_BTN_PRESSED: AtomicBool = AtomicBool::new(false);

cortex_m::free 使用一个 lambda 函数来在其操作前后添加关闭、打开中断的操作。

虽然看起来写起来很麻烦,但实际上编译结果并没有多余的操作,所有看上去繁琐的操作实际上只是指导编译器如何编译,并不会生成实际的代码,例如中断中的 cs 变量,它并没有实际的大小。

同样烧录进 MCU ,按下 ISP 按键即可控制灯的开关。

使用HAL

上述两个例子均直接使用寄存器进行操作,方法过于原始,可以使用 HAL 库来简化操作。HAL 库正在积极开发中,所以使用 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();


    // 设置 pin 的状态更方便了
    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);
    });
}

如果我们想要使用其他中断,该怎么知道它的名字呢?十分简单,所有的中断定义都在 mcxn947-pac::Interrupt 中。

3cdd4266-4a20-11ef-b8af-92fbcf53809c.png

这个例子同样是使用 ISP 按键来控制红灯的开关。

Linkerscript

将特定的数据放置在特定的位置,也是嵌入式开发中常见的操作,那么怎么在 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::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 里:

3cea4628-4a20-11ef-b8af-92fbcf53809c.png

在 Debug 下,也可以看到中断时 PC 的地址:

3cee4d68-4a20-11ef-b8af-92fbcf53809c.png

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

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


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式系统
    +关注

    关注

    41

    文章

    3841

    浏览量

    134043
  • RTOS
    +关注

    关注

    25

    文章

    871

    浏览量

    123269
  • Rust
    +关注

    关注

    1

    文章

    241

    浏览量

    7679

原文标题:当MCX N947遇上Rust,打造未来嵌入式系统的黄金搭档!

文章出处:【微信号:NXP_SMART_HARDWARE,微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    开源RTOS塑造嵌入式系统未来

    嵌入式系统早已不再是隐藏在产品内部、安静运行、只承担单一功能的组件。它们正变得更加智能、互联,并且对安全性至关重要——为从智能家居、工业自动化到医疗设备和能源基础设施等各类应用提供核心动力。随着嵌入式
    的头像 发表于 04-22 16:16 414次阅读

    MCXN947 EVK 无法初始化 PHY是怎么回事?如何处理?

    我正在使用 MCXN947 开始一个新项目。我有 FRDM-MCXN947MCX-N9XX-EVK 演示板。我将使用 LWIP,因此我下载了两块板的 LWIP-PING 示例。该示例在
    发表于 04-17 08:39

    知识分享-嵌入式系统可靠性模型

    嵌入式系统可靠性设计技术及案例解析1.3嵌入式系统可靠性模型嵌入式系统可靠性模型分为两种:串联结
    的头像 发表于 03-11 16:43 487次阅读
    知识分享-<b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>可靠性模型

    什么是嵌入式应用开发?

    概述 所谓的嵌入式应用开发就是在嵌入式操作系统下进行开发、软硬件综合开发 ‌嵌入式应用开发‌是指在嵌入式操作
    发表于 01-12 16:13

    嵌入式与FPGA的区别

    ,一是嵌入式软件开发,主要与嵌入式cao作系统、应用软件等有关。第二是嵌入式硬件开发,需要掌握硬件设计、模拟仿真、 PCB设计等技能。 ✅2、FPGA:它是在PAL、GAL、CPLD等
    发表于 11-20 07:12

    嵌入式和FPGA的区别

    嵌入式系统与FPGA的核心差异:软件定义功能VS硬件可重构。嵌入式适合通用计算,开发门槛低;FPGA凭借并行处理实现纳秒级响应,但成本高、开发难。二者融合的SoC器件正成为未来趋势,平
    发表于 11-19 06:55

    嵌入式系统的定义和应用领域

    嵌入式系统,简而言之,就是一种专为特定设备或装置设计的计算机系统。它们通常配备一个嵌入式处理器,其控制程序被存储在ROM中。这些系统在许多日
    发表于 11-17 06:49

    2025年最佳的嵌入式编程语言有哪些呢?

    嵌入式系统是现代科技不可或缺的一部分。它们存在于家用电器、汽车、住宅、医院、商店等各个领域。它们与我们的社会紧密相连。选择合适的语言来构建嵌入式系统对于成功至关重要。那么,2025年最
    的头像 发表于 11-14 10:27 1898次阅读
    2025年最佳的<b class='flag-5'>嵌入式</b>编程语言有哪些呢?

    嵌入式实时操作系统的特点

    实时嵌入式操作系统(Real-Time Embedded Operating System)是专门设计用于嵌入式系统的实时操作系统
    发表于 11-13 06:30

    RusT-Thread:基于Rust面向资源受限嵌入式设备的操作系统的实践 | 技术集结

    摘要随着物联网和嵌入式系统的发展,实时操作系统(RTOS)的安全性和性能需求日益提高。传统基于C语言的RTOS在内存安全和并发控制方面存在局限,容易导致缓冲区溢出、数据竞争等问题。本项目以
    的头像 发表于 11-07 17:37 7149次阅读
    <b class='flag-5'>RusT</b>-Thread:基于<b class='flag-5'>Rust</b>面向资源受限<b class='flag-5'>嵌入式</b>设备的操作<b class='flag-5'>系统</b>的实践 | 技术集结

    嵌入式Linux新手入门:为什么迅为RK3568+迅为资料是黄金组合

    嵌入式Linux新手入门:为什么迅为RK3568+迅为资料是黄金组合
    的头像 发表于 11-04 14:05 817次阅读
    <b class='flag-5'>嵌入式</b>Linux新手入门:为什么迅为RK3568+迅为资料是<b class='flag-5'>黄金</b>组合

    2025嵌入式行业现状如何?

    2025嵌入式行业现状如何? 一、市场规模与增长趋势1.1 全球市场概况总体规模:2025年全球嵌入式系统市场规模预计突破1.2万亿美元,相当于每天诞生3个“光谷”级产业集群。 驱动因素:物联网
    发表于 08-25 11:34

    RT-Thread 遇上 Rust:安全内核 RusT-Thread 的诞生

    大家好,我们是中国科学技术大学操作系统原理与设计(H)课oooooS小组。这个项目是我们的课程大作业:参考RT-Thread架构,使用Rust搭建一个原生的嵌入式操作系统内核。初识
    的头像 发表于 08-02 11:03 3826次阅读
    RT-Thread <b class='flag-5'>遇上</b> <b class='flag-5'>Rust</b>:安全内核 <b class='flag-5'>RusT</b>-Thread 的诞生

    为什么GNSS/INS组合被誉为导航界的&amp;quot;黄金搭档&amp;quot;?

    在导航技术领域,GNSS(全球导航卫星系统)和INS(惯性导航系统)的结合,一直被业界誉为"黄金搭档"。它们优势互补,克服了单一系统的局限性,为高精度、高可靠性的导航提供了完美
    的头像 发表于 07-09 17:12 1270次阅读
    为什么GNSS/INS组合被誉为导航界的&amp;quot;<b class='flag-5'>黄金搭档</b>&amp;quot;?

    运行在嵌入式系统上的emApps

    在当今快节奏的嵌入式系统世界中,灵活性和适应性是嵌入式系统实现的关键。SEGGER推出了其最新创新:Embedded apps(emApps)应用,类似于手机上的应用程序,可以运行在
    的头像 发表于 06-18 09:53 1116次阅读
    运行在<b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>上的emApps