ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32H745双核FreeRTOS入门:CubeMX配置、共享内存与核间通信全解析

2026/9/3 18:30:39 拓冰建站 浏览量
STM32H745双核FreeRTOS入门:CubeMX配置、共享内存与核间通信全解析 简介面向STM32H745双核开发入门者这份基于CubeMX 6.0与FreeRTOS的完整工程代码配套《用cubemx6.0玩转NUCLEO-H745ZI开发板一》博文能解决双核模板搭建、任务分配与启动流程梳理等常见困惑。包体约102.35MB共1286个文件以C源文件与头文件为主另含ICF链接脚本、汇编启动文件、静态库与说明文档目录清晰便于模块化检索。已有3132人学习下载适合正在研究H7系列双核协同的开发者。解压后可直接查看双核初始化代码、FreeRTOS任务配置以及主从核启动逻辑尤其适合对照源代码理解Cortex-M7与Cortex-M4核间通信机制所附数学库还可支撑信号处理实验配合原博文逐步实操能更快掌握双核调度、调试技巧与编译排错思路节省大量从零搭建工程的时间。 ST先别急着点 CubeMX 的生成按钮。STM32H745 这颗料从拿到手到第一个双核 FreeRTOS 程序真正在板子上跑起来中间隔着的不是“两个核”的距离而是对“异构双核”这件事的理解距离。很多人把 H745 当成两颗独立的 Cortex-M 芯片封装在一个封装里于是用单核的思维去配置结果 CPU2 永远跑不起来或者两个核各跑各的共享数据乱成一锅粥。这篇就是一次性把 CubeMX 6.0 下 H745 双核 FreeRTOS 的入门路径捋清楚包括启动顺序、RAM 分配、核间通信和必须避开的坑。1. M7主内、M4主外H745双核架构里必须先弄清楚的三个事实1.1 两个核不是对等的谁主频高谁该干什么H745 和 F7、H7 单核系列最大的不同是它板载了两颗异构内核一颗 Cortex-M7最高主频 480MHz另一颗 Cortex-M4最高主频 240MHz。注意“异构”这个词。M7 和 M4 的指令集有交集但不完全兼容M7 多了双精度浮点、更多的流水线级数和更强的总线吞吐M4 虽然弱一些但胜在低功耗和足够处理大多数外设类任务。所以这颗芯片的设计意图从一开始就不是“两个核一起做同一件事”而是“让 M7 干重活让 M4 干杂活”。比较合理的分法是这样M7 跑复杂的算法逻辑比如音频编解码、图像预处理、电机控制环路的中心计算M4 管外设交互比如按键扫描、LED 指示、通讯帧的收发解析。千万不要把两个核放在同一个任务里去抢同一份算力那样只会把缓存一致性和总线仲裁的问题全部引爆。1.2 不是上电后两个核一起跑CPU2 必须被 CPU1 释放STM32H7 双核系列里CPU1M7和 CPU2M4的复位行为是完全不同的。芯片上电后默认只有 CPU1 从 0x08000000 开始取指运行CPU2 处于复位保持状态不会自动去取它的程序。CPU2 什么时候开始跑取决于 CPU1 软件里是否清了 CPU2 的复位信号。在 H745 的 RCC 控制器里有一个专门的寄存器位控制 CPU2 的复位释放STM32Cube HAL 里封装出来就是HAL_RCC_CPU2_RESET_RELEASE()。你在 CubeMX 里生成的 CPU1 工程如果选了“双核”支持代码里会有这行调用但默认是被条件编译包裹的。很多人第一次跑 CPU1 程序发现 CPU2 一点动静都没有其实就是没执行这个释放动作。还有一个坑哪怕你释放了 CPU2 复位CPU2 的程序也得先加载到它自己的 RAM 或 Flash 区域并且把 PC 指针指过去。H745 里 CM4 的启动地址和 CM7 是分开映射的CPU2 从 0x08000000 开始是读不到自己代码的。正确做法是CPU1 的 linker 脚本里划出一块 RAM 放 CPU2 的镜像或者直接把 CM4 的工程擦写到 0x08100000 的 Flash 分区CPU1 把地址告诉 CPU2再释放复位。这一整套逻辑CubeMX 6.0 已经帮你把骨架搭好了但你要理解它否则 debug 阶段会一头雾水。1.3 外设和内存不是天然共享的谁访问什么总线矩阵说了算H745 的总线矩阵比单核 MCU 复杂得多。RAM 被分成 D1、D2、D3 三个域每个域各有各的 SRAM 块而且部分外设和 SRAM 默认只能被某一个 CPU 访问。举个例子D1 域的 AXI SRAM0x24000000是 M7 的主战场M4 如果要访问它必须通过总线矩阵的交叉访问路径而这条路径是否开启、是否有延迟取决于你在 CubeMX 里的总线配置。D2 域的 SRAM1/SRAM2/SRAM30x30000000 区段才是两个核都能比较方便访问的共享区。实际上H745 双核协作的常规套路是共享数据放在 D2 SRAMM7 和 M4 都映射同一块物理地址通过硬件信号量或者 Mailbox 做互斥和通知。这也决定了你在 CubeMX 里分配内存时不能把所有 RAM 都一股脑配给 CPU1 工程而是要把共享区单独划出来两个工程链接到同一物理地址。2. CubeMX 6.0里搭建双核FreeRTOS工程的完整动作2.1 器件选型和工程创建的隐藏细节CubeMX 6.0 对多核支持已经很成熟但前提是你得选对芯片型号。打开 CubeMX在 MCU Selector 里搜 STM32H745注意系列里有单核型号和双核型号比如 STM32H745AIH6、STM32H745ZIT6U 等它们都是双核而若是 STM32H750 这类成员则可能是单核务必查看 datasheet 确认是 Dual Core。选择型号后正常新建工程。在 Project Manager 页面里Toolchain / IDE 建议选择 STM32CubeIDE 或者 MDK-ARM这两个工具对双核调试支持都比较好如果你用 IAR也可以但要留意 IAR 的多核调试插件版本。这里有个关键CubeMX 6.0 在生成多核工程时会自动生成CM7 和 CM4 两个独立子工程每个子工程里会有自己独立的 main.c、FreeRTOS 配置和 linker 文件。不要试图在同一个工程里同时编译两个核这是很多人刚上手时容易犯的错误——把两颗核的代码放在一个工程里编译过了但是下载后完全不知道跑的是哪个核。2.2 时钟树配置两个核的主频要分开设时钟树是 H745 双核工程里最容易出错也最好玩的地方。在 CubeMX 的 Clock Configuration 页面里你会看到两个 CPU 时钟配置条目CPU1 CLK和CPU2 CLK。打开时钟树后先把 PLL1 配置成 480MHz 供 CPU1 使用再把 PLL2 配置成 240MHz 供 CPU2 使用最后分别把RCC_HCLK1_CLOCKSOURCE和RCC_HCLK2_CLOCKSOURCE这类的时钟源选项指向对应的 PLL。实测中有一个高频问题如果你把 CPU1 和 CPU2 的时钟配置成同一个 PLL 输出而两个核的主频需求不同CubeMX 会直接报错或者生成出来的时钟参数不合法。所以强烈建议两个 PLL 分开各自锁各自的频。时钟树配置完建议顺手看一眼RCC的D2CFGR寄存器配置界面确认 D2 域也就是 M4 所在域的时钟已经使能。很多双核程序跑不起来不是程序逻辑问题而是 D2 域的时钟没开或者分频不对M4 连指令都取不到。2.3 FreeRTOS 在双核里的配置方式CubeMX 里配置 FreeRTOS路径在Middleware and Software Packs - FREERTOS。这里有一个重要选项Interface。下拉菜单里有CMSIS_V1和CMSIS_V2建议直接选CMSIS_V2。V2 对 Cortex-M7 和 M4 的底层适配更完善任务切换和中断处理都更贴合 ARMv7-M 架构而且 CubeMX 6.0 生成的代码默认对 V2 的兼容性也更好。然后勾选Enable FreeRTOS在任务配置里添加你的任务函数。双核 FreeRTOS 的任务归属CubeMX 里并没有一个直接“分配 CPU”的下拉框——任务层面的分配是在代码层面实现的但 CubeMX 帮你把内核初始化代码和默认任务都铺好了。H745 的双核 FreeRTOS 其实有两种理解方式独立双系统两个核上各自跑一个独立的 FreeRTOSM7 上跑一套调度器M4 上跑另一套调度器任务之间不共享调度信息。AMP非对称多处理模式两个核上跑同一个 FreeRTOS 框架但每个核执行不同的任务集。CubeMX 默认生成的是第一种“双独立系统”因为它最简单也最可控——两个核互不干扰任务调度各自独立。等你把独立双系统跑通了再考虑用 OpenAMP 做更复杂的 AMP 方案大部分入门项目其实用不着。2.4 双工程的内存与链接脚本规划这是整个双核开发里技术含量最高的一步。CubeMX 生成的两个工程各自带一份链接脚本默认的 RAM 分配很保守——CM7 工程占用了大部分 D1 AXI SRAMCM4 工程占用 D2 SRAM。但双核协作的核心是共享内存你需要手动留出一块 D2 SRAM 作为共享区且这块内存必须在两个链接脚本里都出现。在 CM7 工程的链接脚本.ld 或 .sct里加入/* 在链接脚本中划分共享内存段 */ .shared_ram (NOLOAD) : { . ALIGN(4); *(.shared_ram) *(.shared_ram.*) } RAM_D2_SHARED然后在代码里把你定义的共享数据结构放到这个段里__attribute__((section(.shared_ram))) volatile uint32_t shared_flag; __attribute__((section(.shared_ram))) volatile uint16_t sensor_data[64];两个工程要链接到同一物理地址这个地址就是你配置的 D2 SRAM 里某个固定地址。CM7 和 CM4 里的指针变量只要都指向0x30040000这类在共享区内的地址读写就是同一份数据。3. 从Hello World到双核协作HSEM、Mailbox与共享内存的选择3.1 先想清楚两个核之间为什么要通信机制两个核本身是并行跑的如果你只是想让它们各干各的互不打扰那不需要任何通信机制两边程序独立编译、独立运行就够了。但实际项目里几乎没有“互不打扰”这回事M7 算出来的结果总得让 M4 拿去控制外设M4 采集到的数据总得交给 M7 做决策——这就引入了双核通信。H745 硬件上提供了三种典型的协作手段HSEM硬件信号量、Mailbox和共享内存。这三种不是互斥的实际工程里经常组合使用。理解它们各自的定位比会调用 API 更重要。3.2 HSEM别让两个核同时碰同一片共享内存HSEM 是 H7 系列双核专属的硬件信号量模块。它解决的核心问题很简单当 M7 和 M4 都要修改同一个共享变量时谁能保证不会互踩裸机程序里互斥通常靠关中断但双核系统里M7 关中断管不住 M4M4 关中断也管不住 M7。所以需要硬件层面的“锁”——HSEM 就是这层锁。它的用法很简单CubeMX 配置好 HSEM 外设后代码里直接用 HAL 库if (HAL_HSEM_FastTake(HSEM_ID_0) HAL_OK) { // 只有一个核能走到这里另一个核会在这里等待或重试 shared_flag 1; HAL_HSEM_Release(HSEM_ID_0, 0); }注意到HSEM_ID_0是信号量编号H745 的 HSEM 有 32 个信号量你可以给不同的共享资源分配不同的信号量。入门的经验是宁可多分配几个 HSEM也不要两个共享资源共用一个信号量否则会引发不必要的阻塞等待。3.3 Mailbox用来通知不负责搬运数据Mailbox 在 STM32H7 上是一个硬件外设它的工作方式跟邮箱差不多你可以往里面写入一个最长 8 字节的短消息对方读出来后触发中断。它和 HSEM 的本质区别是Mailbox 偏“通知”HSEM 偏“互斥”。M7 算完一帧数据往 Mailbox 里塞一个“数据就绪”的消息M4 收到中断后去共享内存里取数据路径就是这样。CubeMX 里配置 Mailbox 后初始化流程一般会生成两个核各自的初始化和中断处理代码。实际使用中M7 侧的发送函数大致是// CPU1 侧发送一条短消息到 CPU2 HAL_MAILBOX_Send(hmailbox, MAILBOX_ID_0, (uint32_t*)msg, MAILBOX_TIMEOUT_MS);M4 侧则在 Mailbox 中断回调函数里处理收到的消息。要注意的是 Mailbox 消息长度极短适合发状态标志和命令编码不适合发大数据包。数据本身放共享内存Mailbox 只负责“让你知道数据来了”。3.4 选型建议消息小用 Mailbox数据大用共享内存加 HSEM这是我在几个实际项目里沉淀下来的选择逻辑给你做参考通信场景推荐方案原因偶尔发送控制命令开机、停止、切换模式Mailbox短消息、低延迟、硬件中断唤醒、无需轮询周期性传输传感器数据几十到几百字节共享内存 HSEM传输效率高且 HSEM 保证互斥避免双写冲突大数据块图像帧、波形数据D2 SRAM 共享区 HSEM Mailbox 通知数据放共享内存Mailbox 做“数据就绪”通知HSEM 保证读写期间的独占权如果你的项目只是入门学习先用 Mailbox 互相发几个“hello”再用共享内存同步几个变量跑通之后你对双核通信的体感就完全不一样了。4. 双核FreeRTOS跑通之后我踩过的那些坑4.1 坑一CPU2 程序烧进去了为什么跑不起来这是绝大多数人遇到的第一个问题。现象是CPU1 的 FreeRTOS 任务跑得好好的打印信息都正常但 CPU2 的代码就是不执行。查了半天发现 CPU2 那边的程序确实烧进去了Flash 里的数据也对着可就是 CPU2 不干活。**根因在启动顺序。**CPU2 的复位释放必须由 CPU1 执行而且释放前要保证 CPU2 的代码已经加载到它自己的执行地址无论是 Flash 还是 RAM。如果你在 CPU1 的 main() 里没有调用HAL_RCC_CPU2_RESET_RELEASE()那一切都是白搭。CubeMX 生成的代码里这个释放动作通常在SystemClock_Config()之后、main()主循环之前由条件编译#ifdef CORE_CM4包裹着。另外还有一种情况你在调试器里单独给 CPU2 烧录程序时用的是 CM4 工程的 elf调试器把 PC 设到了 CM4 的复位向量这个没问题但当 CPU2 由 CPU1 释放复位时CPU2 会读取它自己的向量表如果向量表指向的地址上没有正确的栈顶和复位向量CPU2 会直接 HardFault。我的排查手段很简单在 CPU2 工程的main()最前面把一个 GPIO 拉高然后用示波器看这个 GPIO 有没有电平变化。没有变化就说明 CPU2 压根没进 main()问题大概率在启动和复位有变化说明任务调度或外设初始化有问题。把启动问题和代码逻辑问题先分开排查效率会高很多。4.2 坑二M7 开了 D-Cache 之后共享内存的数据全是“旧的”H745 的 M7 内核有 D-Cache 和 I-CacheM4 没有。这本来没什么但当你用共享内存做双核通信时Cache 一致性问题会立刻浮出水面。M7 写共享内存时数据先写进 D-Cache而不会立刻写回物理 RAMM4 读共享内存时读到的是物理 RAM 里的旧数据。反过来M4 往共享内存写数据M7 读的时候可能读的是 D-Cache 里缓存的旧副本。解决思路有两个最简单直接把共享内存区域的 Cache 关闭。在 MPU 配置里把共享内存区域设置为Device或Non-cacheable。CubeMX 里有 MPU 的图形化配置界面在System Core - MPU里新增一个 region覆盖你的共享内存地址范围配置为Normal memory, Non-cacheable。这种方式不影响性能毕竟共享区数据量不会太大而且彻底避免脏数据。进阶做法开启 Cache但每次读写共享内存前手动 Clean/Invalidate。即写之前SCB_CleanDCache()读之前SCB_InvalidateDCache()。这个做法能保留 Cache 的整体性能但很容易漏调用一旦漏了数据错误是随机复现且极难定位的。我给你的建议是入门阶段直接用 MPU 把共享区设置成 Non-cacheable先把通信逻辑跑通不要急着一上来就折腾 Cache 优化。4.3 坑三两个核的中断优先级设置不一致FreeRTOS 同步直接失效FreeRTOS 在 Cortex-M 上依赖 SysTick 和 PendSV 来实现任务调度而 SysTick 的优先级必须设置为 FreeRTOS 允许的最大优先级configMAX_SYSCALL_INTERRUPT_PRIORITY对应的值。这个东西在单核里很简单但双核系统里容易出问题的地方是CPU1 和 CPU2 的中断优先级分组Priority Group必须保持一致。如果 CPU1 配置成中断优先级分组为 44 位抢占优先级而 CPU2 分组为 00 位抢占优先级那 SysTick 在两边的实际优先级含义就完全不同FreeRTOS 的时间片滚动和任务切换会出现诡异的偏差——抓逻辑分析仪能看到 M4 侧任务切换周期忽快忽慢。解决办法就是在两个工程的SystemClock_Config()或HAL_Init()里统一设置中断优先级分组例如都用HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)。这个看起来很小的配置是双核实时系统稳定的关键之一。4.4 坑四FreeRTOS 的堆栈溢出检测双核下要分别开FreeRTOS 的堆栈溢出检测在单核里是一个 debug 利器在双核里你记得在两个核的 FreeRTOSConfig.h 里都打开。你的 CM4 工程和 CM7 工程是独立的两个固件各自维护自己的 FreeRTOS 内核configCHECK_FOR_STACK_OVERFLOW必须分别设为 1 或 2。我碰到过一次奇怪的现象M7 侧的任务一直正常M4 侧偶尔跑十几分钟就 HardFault。打开 M4 侧的堆栈溢出检测后用了不到十分钟就在vApplicationStackOverflowHook()里抓到了罪魁祸首——一个在 M4 侧声明的大局部数组把任务栈直接干穿了。定位速度比盲猜快了一个数量级。OpenOCD 或 ST-LINK 调试时建议同时连接两个核的调试会话观察两边任务的栈高水位线。CubeIDE 里支持同时调试两个工程创建 debug configuration 时把 CM7 和 CM4 的调试启动都添加到同一个 session 里Debug Configurations - 多工程调试这样可以同时看两个核的线程栈、变量和断点。5. 我可以直接抄的一个最小双核 FreeRTOS 实验的完整动作序列第一步CubeMX 新建 STM32H745 工程时钟树配好 PLL1 和 PLL2分别给 CPU1 480MHz、CPU2 240MHz。第二步两个核都开启 FreeRTOSCMSIS_V2各创建一个简单的任务比如 CM7 上建一个Task_Print_M7CM4 上建一个Task_Print_M4任务体里就是延时翻转 GPIO或者向串口打印不同的字符。第三步把共享内存区划到 D2 SRAM比如地址0x30040000配置 MPU 为 Non-cacheable在两边代码里声明同一个volatile结构体指针指向这块地址。第四步配置一个 MailboxCubeMX 里 Mailbox 外设配置界面很直观两边选好核归属通信测试逻辑M7 每秒通过 Mailbox 发一条命令M4 收命令后往共享内存写一个递增计数M7 再把这个计数打印出来。第五步编译 CM4 工程 → 编译 CM7 工程 → 先把 CM4 的固件烧录到其执行地址如果放到 RAM 可以省去如果放 Flash 就按 0x08100000 规划 → 再烧 CM7 的固件 → 复位运行。看到两边串口都在按预期打印且共享计数稳定递增你的 STM32H745 双核 FreeRTOS 入门程序就算真正跑通了。这个最小实验看起来简单但它把你带过了所有关键的门槛双核启动顺序、时钟域、内存规划、FreeRTOS 双独立调度、Mailbox 通知、共享内存和 Cache 策略。后续你再接外部传感器、通信协议栈、电机控制都是在这个骨架上做加法。我最初因为没搞懂 CPU2 复位释放和 D-Cache 一致性这两个点整整折腾了两天多代码每次跑起来都像是随机出差错。现在把这些经验整理出来你照着做应该能在一晚上以内把第一版双核 Hello World 调通。如果中途卡住别急着怀疑硬件拿示波器量一量 GPIO打开调试器同时看两个核的 PC 指针你会很快找到问题所在。本文还有配套的精品资源点击获取