RT-Thread i.MX RT1180 EVK 双核 BSP 完整实战:从双核拉起到核间通信
【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread
想在双核芯片上跑起实时系统,还让两个核互相通信?这篇文章以 RT-Thread 的 i.MX RT1180 EVK 双核 BSP 为例,带你完整走一遍:先把 RT-Thread 在开发板上点亮,再用 MCMGR 让 CM33 主核拉起 CM7 从核,最后基于 RPMsg-Lite 在共享内存上跑通 ping-pong 核间通信。读完并动手做完,你将具备这块板子上独立编译、按需扩展和双核协作开发的完整能力。
认识芯片与双工程布局 🧩
i.MX RT1180 是一颗面向工业控制、电机驱动场景的跨界 MCU:一颗 800 MHz 的 Cortex-M7 负责算力密集的任务,一颗 240 MHz 的 Cortex-M33(带 TrustZone 安全扩展)负责安全启动与系统管理。板子还配有双千兆以太网(NETC 交换控制器)、EtherCAT 等工业通信接口,并支持宽温工作。
在这个 BSP 里,分工被固化为:CM33 是主核(master),CM7 是从核(remote)。两个核各自拥有完整的工程,目录结构如下:
bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/ ├── cm33/ # 主核工程 │ ├── applications/ # main.c、multicore_thread.c、mnt.c │ ├── board/ # MCUX_Config 时钟引脚配置、linker_scripts、ports │ ├── xip/ # FlexSPI NOR 启动配置 │ └── rtconfig.h / rtconfig.py └── cm7/ # 从核工程,结构与 cm33 对称两个rtconfig.h的差异一眼就能看出各自的"人格":
| 配置项 | cm33 工程 | cm7 工程 |
|---|---|---|
| 架构宏 | ARCH_ARM_CORTEX_M33+ARCH_ARM_CORTEX_SECURE | ARCH_ARM_CORTEX_M7+ARCH_ARM_CORTEX_FPU |
| 控制台设备 | uart1(LPUART1,RX/TX DMA 通道 0/1) | uart12(LPUART12,RX/TX DMA 通道 22/23) |
| 缓存 | 未启用 | RT_USING_CACHE(M7 数据/指令缓存) |
| SOC 宏 | SOC_MIMXRT1189CVM8C_CM33 | SOC_MIMXRT1189CVM8C_CM7 |
| 公共默认 | 均开启BSP_USING_QSPIFLASH、BSP_USING_DMA、BSP_USING_GPIO,链接脚本默认BSP_LINKER_SCRIPT_FLEXSPI_NOR(XIP) | 同左 |
记住两个控制台的对应关系——CM33 看 UART1,CM7 看 UART12,后面双核调试全靠它区分日志来源。
五步点亮第一个 RT-Thread 🔌
- 拉取仓库。在本地执行:
git clone https://gitcode.com/gh_mirrors/rt/rt-thread- 更新依赖包。这个 BSP 依赖 NXP IMXRT 驱动软件包(
PKG_USING_NXP_IMXRT_DRIVER),进入bsp/nxp/imx/imxrt/imxrt1180-nxp-evk/cm33/打开 ENV 后必须先执行:
pkgs --update跳过这一步,编译会直接因缺库失败——这是本 BSP 第一个也是最常见的坑。
- 生成或编译工程。三套工具链任选其一:
scons --target=iar # 生成 IAR 工程 scons --target=mdk5 # 生成 Keil MDK5 工程 scons -j8 # GCC 直接编译(8 线程并行)连接硬件并下载。用数据线把开发板接到 PC(板上自带调试器),打开电源开关。以 IAR 为例:双击工程目录下的
project.eww,编译通过后点击下载即可——工程默认走 CMSIS-DAP 烧录。串口验证。把板子对应串口接到终端工具,参数设为 115200-8-1-N,复位开发板,你会看到启动 banner:
\ | / - RT - Thread Operating System / | \ 5.2.2 build May 15 2026 17:19:35 2006 - 2024 Copyright by RT-Thread team随后cm33/applications/main.c打印MIMXRT1180_CM33 Hello_World,并以 500 ms 周期翻转GET_PIN(4, 27)对应的板载 LED。看到 banner、LED 闪烁,说明主核链路通了。
CM7 工程完全同法操作(独立编译、独立下载),只是验证时改看 UART12,应用打印的是MIMXRT1180_CM7 Hello_World。
能力扩展:逐项打开板子资源 ⚙️
默认配置只开了最小串口与 GPIO。想多用一块板载资源,套路永远是四步:menuconfig勾选 → 保存 →pkgs --update→scons --target=mdk5(或 iar)重新生成工程。开关全部集中在cm33/board/Kconfig与cm7/board/Kconfig(两核菜单大体对称),按"想用哪个功能"来查:
| 想用 | 勾哪里 | 说明 |
|---|---|---|
| 双千兆以太网 | Onboard → Ethernet:BSP_USING_ETH | 自动带入 lwIP/SAL/netdev;NETC_USE_SWT选 0(EP0 直连)或 1(switch 模式),PHY 默认开启 |
| 文件系统 | Onboard → File System:BSP_USING_FS | 下挂BSP_USING_QSPI_FLASH_FS(NOR Flash 上的 LittleFS,依赖 FAL 分区表,见cm33/board/ports/fal_cfg.h)或BSP_USING_SDCARD_FATFS(SD 卡 FATFS) |
| USB | On-chip → USB (CherryUSB):BSP_USING_USB | 先选控制器 OTG1/OTG2,再选 Device/Host;Host 模式可追加鼠标 shell 演示(usbh_mouse_demo.c) |
| 更多串口 | On-chip → UART | LPUART1~12 逐路使能,每路可单独开 RX/TX DMA 并指定通道号(0~32) |
| I2C / SPI / CAN | On-chip → I2C / SPI / CAN | LPI2C1/2/3/6 可选 100 kHz 或 400 kHz;LPSPI1~4 逐路开 DMA;CAN 对应 FLEXCAN3 |
| SDIO / RTC / DWT | On-chip 对应菜单 | SDIO 下可勾CODE_STORED_ON_SDCARD让 SD 卡充当启动设备;DWT 提供dwt_delay_us等微秒级忙等接口 |
| 核间通信 | Onboard → Multicore:BSP_USE_MULTICORE | 子项 Kick-off 与 RPMsg-Lite 默认全开,详见下一节 |
另外两个值得留意的开关:EtherCAT(BSP_USING_ETHERCAT,在 CM7 工程的菜单里)和 HyperRAM(BSP_USE_HYPERRAM,外部 HyperRAM 扩内存)。至于 PWM,目前 BSP 尚未支持,规划中,先别在菜单里找它。
切换内存布局:链接脚本怎么选
menuconfig 末尾的 Linker Script Selection 决定镜像落位,两个工程可选项略有差异:
| 选项 | 布局含义 |
|---|---|
BSP_LINKER_SCRIPT_RAM | 整个镜像放在片内 SRAM 运行 |
BSP_LINKER_SCRIPT_FLEXSPI_NOR(默认) | 代码经 FlexSPI 从外部 NOR Flash XIP 执行 |
BSP_LINKER_SCRIPT_FLEXSPI_NOR_HYPERRAM | XIP 执行 + HyperRAM 作附加 RAM |
BSP_LINKER_SCRIPT_HYPERRAM(仅 CM7) | 片内 SRAM + HyperRAM |
对应文件在cm33/board/linker_scripts/、cm7/board/linker_scripts/下,.ld(GCC)、.scf(Keil)、.icf(IAR)三种后缀一一对应。rtconfig.py会扫描rtconfig.h里的BSP_LINKER_SCRIPT_*宏自动挑选脚本;生成 Keil 工程时还会调用update_keil_active_target(),把project.uvoptx的当前 Target 同步为rtthread_ram/rtthread_flexspi_nor/rtthread_flexspi_nor_hyperram——所以你只需在 menuconfig 里改选项再重新生成,无需手动切 Target。
机制深挖:CM33 如何拉起 CM7、两核如何对话 🔍
什么是 Kick-off
从核上电后并不能自己启动,需要主核替它完成"搬运镜像 + 喂启动地址 + 释放复位"这一套动作,NXP 把这件事封装在 MCMGR(Multi-Config Manager)驱动里,即 Multicore Kick-off。BSP_USE_MULTICORE_KICK_OFF的帮助文本说得很直白:CM33(master)启动 CM7(remote)时,这是正确初始化顺序所必需的。
主核侧的六步启动序列
全部逻辑在cm33/applications/multicore_thread.c中。multicore_init()通过INIT_APP_EXPORT在应用初始化阶段创建名为multicore的线程(栈 2048 字节、优先级 10、时间片 5),线程内multicore_kickoff_init()依次执行:
MCMGR_Init()初始化多核管理单元;MCMGR_RegisterEvent(kMCMGR_RemoteApplicationEvent, ...)先注册 CM7 就绪事件回调——注意必须在启动从核之前注册,否则收不到就绪信号;BOARD_GetCore1ImageAddrSize()解析烧录容器,找出 CM7 可执行镜像的源地址、目的地址、大小与启动地址(容器解析逻辑在cm33/board/board.c,会遍历每个 container header 的 image entry,按 Type/核心 ID 匹配 CM7 executable);BOARD_PrepareCore1()准备从核:若 CM7 走 ITCM,则把镜像 memcpy 进 ITCM 并做 DSB/ISB;若是 XIP 运行,则初始化 TCM ECC 等前置状态(含 TCM ECC fuse 场景下的假向量表 workaround);- 同步启动 CM7,并借第三个参数把共享内存基址"递"给从核:
(void)MCMGR_StartCore(kMCMGR_Core1, (void *)core1_boot_addr, (uint32_t)rpmsg_lite_base, kMCMGR_Start_Synchronous);- 每 10 ms 轮询一次事件数据,直到 CM7 通过
MCMGR_TriggerEvent上报APP_RPMSG_READY_EVENT_DATA为止。
从核侧的"反向握手"
CM7 工程里的cm7/applications/multicore_thread.c逻辑正好对称:先用MCMGR_GetStartupData(kMCMGR_Core0)反复轮询取回主核递过来的共享内存基址,再经platform_patova()完成地址视角转换,以 remote 身份调用rpmsg_lite_remote_init()初始化;初始化一完成就MCMGR_TriggerEvent回报就绪,然后rpmsg_lite_wait_for_link_up()阻塞等待链路建立,最后用rpmsg_ns_announce()向全网广播自己的服务名rpmsg-openamp-demo-channel。
RPMsg-Lite:共享内存上的消息总线
📡 RPMsg-Lite 是 RPMsg 协议的轻量实现,不走中断风暴式的 mailbox,而是把消息队列直接建在一块双核都能访问的共享内存上:
- 共享内存区:主核侧定义 6144 字节的
rpmsg_lite_base[],并要求它落入与链接脚本匹配的段。三种编译器各有一套写法:IAR 用#pragma location = "rpmsg_sh_mem_section",Keil 用__attribute__((section("rpmsg_sh_mem_section"))),GCC 用.noinit.$rpmsg_sh_mem——改段名时三处必须与链接脚本同步; - 主核初始化(
rpmsg_lite_init(),cm33 侧):rpmsg_lite_master_init()→ 建接收队列 →rpmsg_lite_create_ept()创建本地端点(默认地址 40)→rpmsg_ns_bind()绑定名称服务后阻塞等待,直到 CM7 广播把远端端点地址写进remote_addr; - ping-pong 通信:
rpmsg_lite_communication()里 CM33 先发首个 ping,之后 100 轮rpmsg_lite_send()发送 +rpmsg_queue_recv()阻塞收应答,CM7 侧收到后数据自增再回发——一个最简单的连通性压力测试; - 清理:
rpmsg_lite_cleanup()依次销毁端点、队列、解绑名称服务并rpmsg_lite_deinit()。
相关参数都可在BSP_USE_MULTICORE_RPMSG_LITE下配置,源码里另有#ifndef兜底,不显式配置也能按默认值跑:
| 配置项 | 默认值 | 说明 |
|---|---|---|
RPMSG_LITE_LINK_ID | 0 | 链路 ID,即RL_PLATFORM_IMXRT1180_M33_M7_LINK_ID |
RPMSG_LOCAL_EPT_ADDR | CM33 侧 40 / CM7 侧 30 | 各自本地端点地址,范围 0~65535 |
RPMSG_NS_ANNOUNCE_STRING | rpmsg-openamp-demo-channel | 从核广播服务用的名称服务字符串 |
RPMSG_SHARED_MEM_SIZE | 6144 字节 | 共享内存区大小(CM33 侧菜单配置) |
为什么这么设计
主从划分让"谁先跑、谁负责搬运"有唯一答案;用共享内存承载消息,省去了从核侧额外的通信硬件;名称服务则把"对端端点在哪"变成可广播、可发现的动态信息——将来你加第三条通道、换端点地址,都只需改 announce 字符串,不用硬编码地址。这正是这套 BSP 可作为自研双核应用骨架的原因。
避坑清单 ⚠️
pkgs --update必须在第一次编译前执行,且每次 menuconfig 改动后建议再跑一次;- CM33 与 CM7 是两个完全独立的工程:分别生成、分别编译、分别下载,顺序上先让 CM33 起来再由它拉起 CM7;
- 控制台不互通:CM33 日志在 UART1、CM7 日志在 UART12,双核联调时两个终端都要开着,否则"看不到从核打印"多半是接错了串口;
- 切换链接脚本后必须重新生成工程,Keil 工程的当前 Target 会自动同步,但手动改动
project.uvoptx会被下次生成覆盖; - RPMsg 共享内存段名与链接脚本必须匹配,换编译器时确认
rpmsg_lite_base[]的段属性写法切换到了正确分支; - 中文 README 提到 Kick-off"将在后续版本支持",但当前仓库源码中
multicore_thread.c已完整实现主从双向逻辑,以源码为准; - 需要整板双核镜像时,除了独立编译两核,还可以用 NXP 的 MCUXpresso Secure Provisioning Tool 生成可启动的融合镜像。
写在最后 🎯
回看这条闭环:pkgs --update拉依赖 → 三套工具链任选编译 → CMSIS-DAP 下载 → 双串口验证 → menuconfig 按需扩资源 → MCMGR 拉起从核 → RPMsg-Lite 跑通消息。i.MX RT1180 EVK 的这份双核 BSP 是 RT-Thread 仓库里少见的"双核独立编译 + 主从协作"完整范例,也是工业控制、电机驱动、双千兆网关类项目的理想起点:算力密集的实时任务丢给 CM7,安全启动、网络协议栈留在 CM33,两核之间用 RPMsg-Lite 对话。
下一步你可以从两个方向延伸:把 ping-pong 换成自己定义的消息结构,实现传感器数据跨核上报;或者横向对比同系列其它双核/单核 BSP——bsp/nxp/imx/imxrt/目录下还有 imxrt1021、imxrt1052、imxrt1060、imxrt1064、imxrt1170 等工程,外设驱动与 Kconfig 组织方式大同小异,可作对照参考。
【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考