痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法

痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法
一、双核通信的三种方法因为今天这个话题涉及双核架构有必要先简单和大家谈一谈双核间通信的三种方式及其优缺点注意这里仅指双核间传递消息数据侧重通知而不涉及所谓信号量 semaphore 概念侧重互斥比如两个核抢同一个“硬件锁寄存器”谁先抢到谁就独占某个共享资源。1.1 共享内存第一种方法就是最常见的共享内存只要是两个核均能访问的 SRAMRegister 等均可以用作消息数据传递。比如定义如下结构体 g_sharedMsg 存储两个 msg 数据core0 发出的消息存入 core0_msgcore1 发出消息存入 core1_msg在两个核的工程链接文件里均将 .shared_mem 段指向同一个物理地址即可需要是 Non‑Cacheable 属性的内存且是 4 字节对齐地址。// 对 ARM Cortex‑M 来说4 字节对齐的 uint32_t 读写是原子的不会出现“读到一半新、一半旧”的撕裂问题 typedef struct _shared_msg { uint32_t core0_msg; uint32_t core1_msg; } shared_msg_t; __attribute__((section(.shared_mem))) volatile shared_msg_t g_sharedMsg;这种方法的优点是非常通用不依赖任何硬件且消息数据量不限取决于内存大小缺点是消息数据需要内核以 polling 方式获取消息交互实时性不高。1.2 硬件MU模块第二种方法就是借助专用于双核通信的 Messaging Unit (MU) 模块这是一个硬件模块双核 MCU 里一般都会有其提供了中断驱动的消息传递机制。这种方法的优点是实时性最佳消息交互通过中断驱动不需要内核去 polling缺点是依赖专用硬件模块且消息寄存器数量有限比如 RT1180 上 MU 一次最多传递 4 个 uint32_t 数据。1.3 硬件通信模块最后一种方法就是借助一般通信外设模块比如 GPIO/UART/SPI/I2C 等两个内核各控制一个通信外设片外通过 pin 脚将两个外设相连。这种方法的优点是实时性也不错消息交互也可通过通信外设自身中断驱动并且消息数据量也不限缺点是消耗硬件通信外设且占用外部 pin 脚。二、MU模块及其驱动简介综合比较本文选取了第二种方法即借助于片内专用 MU 模块实现双核通信下面简单介绍一下 MU 模块的基本特性和驱动用法。RT1180 内部共有两个 MU 模块每个 MU 内部包含两个子模块 MUA 和 MUB分别对应主核 CM33 Processor A和从核 CM7 Processor BMUA 与 MUB 子模块之间通过内部总线相连。每个 MUA/B 模块均提供了 4 个 32-bit 的消息寄存器发送通过 TR0-3接收通过 RR0-3用于数据传递以及相应的中断机制。比如当 Processor A 向 MUA_TR0 写入数据时Processor B 可以通过 MUB_RR0 读取该数据同时会触发 Processor B 的中断反之亦然。下面是一个基于 SDK v25.12 里 fsl_mu 驱动代码的简单示例两个内核各自初始化自己的 MU 子模块这里定义了两个 MU_CMD通过 MUx_TR0/RR0 交互CM7 先通过 MUB 发出 MU_CMD_ECHO 消息CM33 通过 MUA 中断得到该消息后立刻返回 MU_CMD_ACK 消息CM7 收到返回消息即结束。#include fsl_mu.h typedef enum { MU_CMD_ECHO 0xA5A50001, // CM7 - CM33 MU_CMD_ACK 0xA5A50002, // CM33 - CM7 } mu_cmd_t; // 下列代码应用于 CM33 工程 void mc_cm33_init(void) { MU_Init(MU1_MUA); MU_EnableInterrupts(MU1_MUA, kMU_Rx0FullInterruptEnable); NVIC_EnableIRQ(MU1_IRQn); } void MU1_IRQHandler(void) { uint32_t flag MU_GetStatusFlags(MU1_MUA); if ((flag kMU_Rx0FullFlag) kMU_Rx0FullFlag) { uint32_t msg MU_ReceiveMsgNonBlocking(MU1_MUA, kMU_MsgReg0); if (msg MU_CMD_ECHO) { MU_SendMsgNonBlocking(MU1_MUA, kMU_MsgReg0, MU_CMD_ACK); } } } // 下列代码应用于 CM7 工程 void mc_cm7_init(void) { MU_Init(MU1_MUB); MU_EnableInterrupts(MU1_MUB, kMU_Rx0FullInterruptEnable); NVIC_EnableIRQ(MU1_IRQn); MU_SendMsgNonBlocking(MU1_MUB, kMU_MsgReg0, MU_CMD_ECHO); } void MU1_IRQHandler(void) { uint32_t flag MU_GetStatusFlags(MU1_MUB); if ((flag kMU_Rx0FullFlag) kMU_Rx0FullFlag) { uint32_t msg MU_ReceiveMsgNonBlocking(MU1_MUB, kMU_MsgReg0); if (msg MU_CMD_ACK) { // Do something } } }三、双核管理驱动MCMGR用法由于 MU 模块仅仅是最底层的裸消息数据传输不带协议、不带缓冲、不带管理这里不建议直接在应用程序里大量使用因为代码易写错、维护成本高。NXP 官方提供了一个更高级的双核管理驱动 MCMGRMulticore Manager它是对 MU 模块的进一步封装提供了更加便捷的双核通信接口。我们将上一节里的代码示例实现用 MCMGR 驱动改写这时候完全不用碰 MU 寄存器与驱动代码变得更加清晰易读。MCMGR 驱动的核心是事件Event机制每个事件都有一个 ID 类型和关联的回调函数当一个核向另一个核发送事件时接收端会触发相应的回调函数。#include mcmgr.h #define MCMGR_EVENT_ECHO (1U) // CM7 - CM33 #define MCMGR_EVENT_ACK (2U) // CM33 - CM7 // 下列代码应用于 CM33 工程 void mc_cm33_init(void) { (void)MCMGR_Init(); MCMGR_RegisterEvent( kMCMGR_RemoteApplicationEvent, cm33_mc_cb, NULL); } static void cm33_mc_cb(mcmgr_core_t coreNum, uint16_t eventData, void *context) { if (eventData MCMGR_EVENT_ECHO) { MCMGR_TriggerEvent(kMCMGR_Core1, kMCMGR_RemoteApplicationEvent, MCMGR_EVENT_ACK); } } // 下列代码应用于 CM7 工程 void mc_cm7_init(void) { (void)MCMGR_Init(); MCMGR_RegisterEvent( kMCMGR_RemoteApplicationEvent, cm7_mc_cb, NULL); MCMGR_TriggerEvent(kMCMGR_Core0, kMCMGR_RemoteApplicationEvent, MCMGR_EVENT_ECHO); } static void cm7_mc_cb(mcmgr_core_t coreNum, uint16_t eventData, void *context) { if (eventData MCMGR_EVENT_ACK) { // Do something } }MCMGR 驱动一共定义了 9 种不同类型的事件其中 kMCMGR_RemoteApplicationEvent 是用于用户自定义跨核事件的唯一正确类型。typedef enum _mcmgr_event_type_t { kMCMGR_RemoteCoreUpEvent 1, // 内部事件核状态管理 kMCMGR_RemoteCoreDownEvent, // 内部事件核状态管理 kMCMGR_RemoteExceptionEvent, // 内部事件 kMCMGR_StartupDataEvent, // 启动数据 kMCMGR_FeedStartupDataEvent, kMCMGR_RemoteRPMsgEvent, // RPMsg 专用IPC kMCMGR_RemoteApplicationEvent, // 唯一用户事件 kMCMGR_FreeRtosMessageBuffersEvent, // 系统用Buffer 回收 kMCMGR_EventTableLength } mcmgr_event_type_t;四、XIP应用里实现Flash IAP的方法前面铺垫了那么多看到这里相信你肯定已经对这个客户需求如何解决有了答案其实也不复杂。我们可以直接基于 SDK multicore_examples/hello_world 例程来修改实现该例程 CM33 是 XIPCM7 在 ITCM 执行跟客户情况一致我们只需要在此基础上加上 MCMGR 和 IAP 代码核心思想是 CM7 做 Flash IAP 之前必须先给 CM33 发 notify 消息让其跳转到 RAM 里驻留等待脱离 XIP得到 CM33 发来的 ready 消息时CM7 才能真正开始做 Flash IAP等 IAP 结束 CM7 再通知 CM33 重返 XIP。这里最大的注意点是 CM33 端 RAM 驻留函数设计与 FlexSPI 状态清理还有就是因为要保留 MU 中断接收 CM7 来的消息因此不能简单粗暴地关全局中断CM33 端需要将中断向量表以及使能的中断的服务程序代码全部重定向到 RAM 中。CM7 端 MCMGR_EVENT_FLASH_IAP_NOTIFY通知CM33即将做IAP MCMGR_EVENT_FLASH_IAP_READY得到来自CM33的ready信号后开始做IAP MCMGR_EVENT_FLASH_IAP_PASSIAP成功后返回结果给CM33 MCMGR_EVENT_FLASH_IAP_FAILIAP失败后返回结果给CM33 CM33 端 MCMGR_EVENT_FLASH_IAP_NOTIFY当接收到来自CM7的notify信号后清除FLEXSPI cache等待bus idle MCMGR_EVENT_FLASH_IAP_READY此时已驻留进RAM loop中给CM7发ready信号 MCMGR_EVENT_FLASH_IAP_PASS得到来自CM7的IAP成功结果直接重返XIP MCMGR_EVENT_FLASH_IAP_FAIL得到来自CM7的IAP失败结果需重新初始化FLEXSPI重返XIPTBD