4. Cortex-M协处理器开发

STM32MP1 内部并非只有 Cortex-A7 一个执行域。以 STM32MP157 为例,它同时集成双核 Cortex-A7 与一颗 Cortex-M4,二者共享 DDR、外设总线与部分外设资源,但运行在完全不同的软件栈上:A7 通常运行 Linux,负责网络、文件系统、AI 推理框架与业务调度;M4 则运行裸机或 RTOS 固件,适合承担硬实时采样、电机控制、传感器预处理、低延迟中断响应等任务。本节要解决的核心问题是:如何用 STM32CubeMP1 固件包构建 M4 侧固件,如何通过 OpenAMP/RPMsg 让两个核互相通信,以及 M4 固件如何被编译、加载并进入协作运行状态。

STM32CubeMP1 固件包的结构与定位

STM32CubeMP1 是 ST 为 MP1 系列 M4 协处理器提供的固件开发包,它不替代 Linux 侧的 STM32CubeProgrammer 或 Yocto 构建系统,而是专门面向 M4 裸机/RTOS 工程。解压后典型目录包括:Drivers/(HAL、CMSIS、BSP)、Middlewares/(OpenAMP、FreeRTOS)、Projects/(按开发板与示例划分,如 STM32MP157C-DK2/Applications/OpenAMP/OpenAMP_TTY_echo)、Utilities/ 等。其中 OpenAMP 示例是理解异构通信最直接的入口。

需要特别区分两个概念:M4 固件本身是一个独立的 ELF/二进制镜像,而 A7 侧 Linux 需要知道“把这段镜像放到哪里、什么时候启动它”。STM32CubeMP1 提供的是前者,后者由 Linux 的 remoteproc 框架配合资源表(resource table)完成。

OpenAMP 与 RPMsg:异构核间通信的骨架

OpenAMP 是一套面向异构多核的通信框架,核心由三部分组成:remoteproc 负责生命周期管理(加载、启动、停止远端核);virtio 提供共享内存上的虚拟队列;RPMsg 则在 virtio 之上建立面向消息的通道,让两个核像使用本地消息队列一样收发数据。在 STM32MP1 上,A7 与 M4 之间通过一块预留的共享内存(通常位于 DDR 低地址区域,例如 0x10040000 附近,具体以设备树 reserved-memory 节点为准)交换 vring 与缓冲区。

RPMsg 的通信端点由“通道名 + 源地址 + 目的地址”标识。典型约定是:A7 侧地址为 0x00,M4 侧地址为 0x01,通道名如 rpmsg-tty 或自定义的 rpmsg-ai。M4 固件在启动后调用 OPENAMP_init()、RPMSG_Init(),再通过 RPMSG_CreateEndpoint() 注册接收回调;A7 侧 Linux 则通过 /dev/ttyRPMSG0 或 rpmsg 字符设备访问同一通道。数据流方向是双向的:A7 写入的数据经 vring 通知 M4,M4 的回调被触发后读取并处理,处理结果再通过 RPMSG_Send() 回传。

STM32MP1 异构核间协作与 RPMsg 通信路径 Cortex-A7 · Linux AI 推理 / 业务调度 TensorFlow Lite / ONNX Runtime remoteproc 框架 加载 M4 固件 / 启动 / 停止 rpmsg 字符设备 /dev/ttyRPMSG0 vring 虚拟队列 共享内存描述符 共享内存 DDR reserved-memory 节点 vring0 / vring1 TX / RX 缓冲区 资源表 resource table 通道名 rpmsg-ai A7:0x00 ↔ M4:0x01 Cortex-M4 · 裸机/RTOS 实时采样 / 预处理 ADC / 定时器 / 中断 OpenAMP 初始化 OPENAMP_init / RPMSG_Init 端点与回调 RPMSG_CreateEndpoint vring 虚拟队列 RPMSG_Send / 接收回调 写入 通知 回传 读取 数据流:A7 写入 vring → 触发 M4 中断 → M4 回调处理 → RPMSG_Send 回传 → A7 读取结果

M4 固件的编译与加载

编译 M4 固件通常使用 STM32CubeIDE 或 Makefile 工具链(arm-none-eabi-gcc)。以 OpenAMP_TTY_echo 示例为例,关键步骤是:在工程中确认链接脚本把代码段定位到 M4 可访问的 DDR 区域(如 0x10000000 起始),并确保资源表结构体被放置在约定位置。编译产物为 .elf 与 .bin,其中 .elf 用于调试,.bin 用于 remoteproc 加载。

加载方式有两种。第一种是 Linux 启动后手动加载:将 OpenAMP_TTY_echo.elf 复制到目标板,执行 echo OpenAMP_TTY_echo.elf > /sys/class/remoteproc/remoteproc0/firmware,再 echo start > /sys/class/remoteproc/remoteproc0/state。第二种是在设备树中配置 st,stm32-rproc 节点并让系统启动时自动加载。加载成功后,/sys/class/remoteproc/remoteproc0/state 应显示 running,同时 dmesg 中会出现 remoteproc0: remote processor is now up 之类的日志。

# 示例:手动加载并启动 M4 固件
cp OpenAMP_TTY_echo.elf /lib/firmware/
echo OpenAMP_TTY_echo.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
cat /sys/class/remoteproc/remoteproc0/state
# 期望输出:running

异构核间协作的三种典型模式

第一种是主从控制模式:A7 负责决策与 AI 推理,M4 作为执行器,接收 A7 下发的控制指令并回传执行状态。第二种是数据预处理模式:M4 以固定周期采集 ADC 或传感器数据,做滤波、FFT 或特征提取后,通过 RPMsg 把特征向量送给 A7,A7 只做轻量推理。第三种是实时守护模式:M4 独立运行安全监控或电机换相逻辑,A7 仅通过 RPMsg 读取健康状态,不参与实时环路。选择哪种模式,取决于实时性要求、数据量和 A7 的负载。

调试 RPMsg 时,优先确认三件事:设备树中 reserved-memory 与 remoteproc 节点是否匹配;M4 固件资源表中的 vring 地址是否与 Linux 侧一致;通道名是否在两端完全一致。任何一处不一致,都会表现为 /dev/ttyRPMSG0 无法打开或收发无响应。

实际工程中,建议先用 OpenAMP_TTY_echo 示例跑通“A7 发、M4 回显”的最小闭环,再替换为自定义通道与数据结构。M4 侧回调函数应尽量短小,避免在中断上下文中做耗时运算;大数据块传输应分片,并约定固定长度的消息头(如 4 字节长度 + 1 字节类型 + 负载),以便两端解析一致。只有把加载、通信、协作模式三件事都验证清楚,后续把 AI 推理结果下发到 M4 执行才有可靠基础。