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与实时工具,会为大家揭开更多技术奥秘,敬请持续关注,精彩不容错过!


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

    关注

    40

    文章

    3511

    浏览量

    128742
  • RTOS
    +关注

    关注

    20

    文章

    801

    浏览量

    119071
  • Rust
    +关注

    关注

    1

    文章

    226

    浏览量

    6487

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

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

收藏 人收藏

    评论

    相关推荐

    开启全新AI时代 智能嵌入式系统快速发展——“第六届国产嵌入式操作系统技术与产业发展论坛”圆满结束

    探索及实践”的专题报告。张云飞介绍了麒麟软件基于RUST语言实现的嵌入式虚拟化软件-Kvisor,,该软件支持实时与非实时操作系统混合部署与通信。最后分享了针对实际应用场景应用探索与实践。 本届论坛
    发表于 08-30 17:24

    嵌入式系统怎么学?

    嵌入式系统怎么学? 随着物联网、智能制造等新技术的兴起,嵌入式系统的应用范围更加广泛。包括但不限于工业控制、汽车电子、医疗设备、智能家居、智慧城市、消费电子、通信设备等。学习
    发表于 07-02 10:10

    AI引爆边缘计算变革,塑造嵌入式产业新未来AI引爆边缘计算变革,塑造嵌入式产业新未来——2024研华嵌入式

    中国北京,2024年5月30日 - 2024年研华嵌入式产业合作伙伴会议在北京·中关村皇冠假日酒店成功举办,现场参会嘉宾逾300人。会议以“AI引爆边缘计算变革,塑造嵌入式产业新未来”为主题展开
    发表于 05-31 13:53 195次阅读
    AI引爆边缘计算变革,塑造<b class='flag-5'>嵌入式</b>产业新<b class='flag-5'>未来</b>AI引爆边缘计算变革,塑造<b class='flag-5'>嵌入式</b>产业新<b class='flag-5'>未来</b>——2024研华<b class='flag-5'>嵌入式</b>

    MCXN947与Zephyr的跨界合作 嵌入式技术的新飞跃

    MCX N947成功初步移植Zephyr,标志着嵌入式技术的新飞跃,为物联网应用注入更强动力与智能。 简介 搭建 Zephyr 环境 参考Zephyr Getting Started。在这
    发表于 05-23 09:19 411次阅读
    MCXN<b class='flag-5'>947</b>与Zephyr的跨界合作 <b class='flag-5'>嵌入式</b>技术的新飞跃

    【5月25日-上海】恩智浦新品MCX N系列线下培训来啦!LVGL、AI等超多精彩Demo演示,快来报名吧!

    、以及基于 FRDM-MCX N947 和 RT-Thread 的动手实践,率先完成的同学有福利哦~同时,我们还特别设置了现场交流环节,RT-Thread和恩智浦
    的头像 发表于 05-20 12:40 657次阅读
    【5月25日-上海】恩智浦新品<b class='flag-5'>MCX</b> <b class='flag-5'>N</b>系列线下培训来啦!LVGL、AI等超多精彩Demo演示,快来报名吧!

    富昌电子推荐两款恩智浦的MCX A和MCX N系列微控制器

    富昌电子为您推荐恩智浦的 MCX A 和 MCX N 系列微控制器开发板 FRDM-MCXA153 和 FRDM-MCXN947,帮助您快速开始基于
    的头像 发表于 05-09 16:44 763次阅读

    嵌入式系统中集成Rust和Qt的实践

    Rust 拥有丰富的库生态系统,用于序列化和反序列化、异步操作、解析不安全输入、线程、静态分析等,而 Qt 是一个 C++ 工具包,支持跨各种平台的丰富的、基于 GUI 的应用程序,从 iOS 到嵌入式Linux。Qt 应用程序
    发表于 05-03 10:26 1276次阅读
    在<b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>中集成<b class='flag-5'>Rust</b>和Qt的实践

    嵌入式系统发展前景?

    嵌入式系统发展前景? 嵌入式系统,从定义上来说,是一种专用的计算机系统,它被设计用来控制、监视或者帮助操作一些设备、装置或机器。在过去的几年
    发表于 02-22 14:09

    嵌入式系统的应用实例

    嵌入式系统的三个基本要素是嵌入性、专用性与计算机系统
    的头像 发表于 01-22 09:57 797次阅读

    基于Rust嵌入式符合ACID的键值数据库

    surrealkv -- 基于 Rust 的 low level、版本化、嵌入式、符合 ACID 的键值数据库
    的头像 发表于 12-28 11:29 750次阅读

    什么是嵌入式系统嵌入式系统的具体应用

    嵌入式,一般是指嵌入式系统。用于控制、监视或者辅助操作机器和设备的装置。
    的头像 发表于 12-20 13:33 2039次阅读

    嵌入式系统原理与设计

    电子发烧友网站提供《嵌入式系统原理与设计.zip》资料免费下载
    发表于 11-17 14:10 0次下载
    <b class='flag-5'>嵌入式</b><b class='flag-5'>系统</b>原理与设计

    未来嵌入式在哪些方向会更火?

    的劳动力。在大型厂房里面所有的工业化的设备都是一个硬件,在硬件中嵌入一些传感器和程序,所以目前甚至是未来嵌入式系统在工业自动化中都扮演着非常重要的角色,它可以控制各种机器设备,并将生产
    发表于 10-27 10:23

    什么是嵌入式Linux?

    的“香饽饽”。 近年来国家社会愈发重视嵌入式发展,落实到各种省级,国家级竞赛,可参与的选择以及含金量大多高于其他。(包括大学生电子设计竞赛等等)。在未来发展当中,由于嵌入式芯片不断迭代,芯片朝着时代
    发表于 10-11 13:47

    适合嵌入式设备开发的编程语言—Rust语言

    Rust语言是二十一世纪的语言新星。Rust被人广泛承认的一点,就是因为它能运行在多样的目标上,从桌面和服务器设备,到资源有限的嵌入式设备。
    发表于 09-12 09:39 2552次阅读
    适合<b class='flag-5'>嵌入式</b>设备开发的编程语言—<b class='flag-5'>Rust</b>语言