STM32H747 这颗芯片在圈子里一直有点"叫好不叫座"的味道——双核架构、480MHz 的 Cortex-M7 加 240MHz 的 Cortex-M4、内置大容量 SRAM、带 LCD-TFT 控制器和 DCMI 摄像头接口,纸面参数拉满,但真到项目里落地,很多人第一反应是"用起来太麻烦"。我前后用 H747 做过三个方向的东西:一套工业现场的采集控制板、一套基于 DCMI 的摄像头采集方案、还有一块车载中控的显示交互板。三个项目踩的坑各不相同,但底层逻辑是通的。这篇就把这三条线拆开讲,重点放在双核怎么分工、外设怎么配、以及那些手册上不会写、只有真正调过才知道的细节上。
如果你手上正好有一块 H747 的开发板,或者正在评估要不要用它做工业控制、图像采集、车载显示这类场景,这篇内容应该能帮你少走不少弯路。我会尽量把"为什么这么设计"讲清楚,而不是只丢一堆配置代码。
1. 先搞清楚 H747 双核到底该怎么分工
1.1 双核不是"两个核一起跑更快"
很多人第一次接触 H747,会下意识觉得双核就是性能翻倍,把任务平均分到两个核上就行。实际完全不是这么回事。H747 的 M7 和 M4 是两个独立的子系统,各自有独立的 NVIC、独立的时钟域,共享一部分 SRAM 和外设。它们之间通信要走 IPCC(Inter-Processor Communication Controller)加共享内存,这个通信本身是有开销的。
所以正确的思路不是"平均分",而是"按实时性和复杂度分"。M7 主频高、带 Cache、带 FPU 和双精度浮点,适合跑复杂算法、协议栈、图形渲染这类重活;M4 主频低但响应确定、没有 Cache 带来的不确定性,适合跑硬实时的控制环路、看门狗喂狗、紧急事件处理。
我在工业控制项目里的分工是这样的:M7 负责 Modbus TCP 协议栈、数据打包上传、以及 LCD 上的状态显示刷新;M4 专门跑 1kHz 的电流环 PID 和过流保护逻辑。这样即使 M7 那边因为网络突发卡了一下,M4 的控制环照样稳定运行,不会因为协议栈的抖动影响到执行机构。
1.2 共享内存的分配要提前规划
H747 的 SRAM 分好几块,D1、D2、D3 域各有各的 SRAM,还有 TCM。哪些内存给 M7 独占、哪些给 M4 独占、哪些做共享,这个必须在项目初期就定下来,后期改起来非常痛苦。
我的习惯是:M7 的 TCM 和 D1 SRAM 归 M7 自己用,M4 的 TCM 归 M4,然后从 D2 域划出一块固定大小的共享区,比如 4KB,专门放双核交换的数据结构。这个结构体要用__attribute__((section(".shared")))强制放到指定段,并且在链接脚本里把这块地址对齐好。
提示:共享内存里的数据结构千万不要放指针,两个核的地址空间视图虽然大部分一致,但一旦涉及 Cache 一致性就会出问题。全部用固定长度的数组和标量。
1.3 IPCC 通信的实战用法
IPCC 本质上就是一组硬件信号量加中断通道。M7 和 M4 各有自己的 IPCC 寄存器组,通过置位 channel 的 flag 来通知对方。我一般会封装一层简单的消息队列:共享内存里放一个环形缓冲区,IPCC 中断只负责"通知有新消息",真正的数据读写走环形缓冲区。
这里有个坑:IPCC 的 channel 数量有限,不要一个功能占一个 channel,而是把所有双核通信收敛到一两个 channel 上,用消息类型字段区分。我在车载中控项目里就是 M7 和 M4 之间只用一个 channel,消息头里带 type 字段,M4 收到中断后解析 type 再分发。
2. 工业控制项目:实时性与可靠性的平衡
2.1 为什么工业控制场景特别适合 H747
工业现场对控制器的要求很矛盾:一方面要跑通信协议、要有人机界面、要记录数据,另一方面控制环路又必须硬实时。用单核方案,要么通信卡顿影响控制,要么控制占用 CPU 导致界面卡死。H747 的双核刚好把这两类需求物理隔离。
我做的这套板子,现场要求是 8 路模拟量输入、4 路继电器输出、1 路 RS485 和 1 路以太网。模拟量采样用 ADC + DMA,采样率 10kHz,数据直接进 M4 的 TCM。M4 跑一个简单的滑动平均滤波加阈值判断,超限就直接拉继电器,不经过 M7。M7 那边则通过共享内存定期读取采样值,做趋势记录和上传。
2.2 ADC 采样与 DMA 的配置细节
H747 的 ADC 是 16 位,支持多重采样和过采样。工业场景里我一般开 4 倍过采样,等效把有效位数再提一点,同时抑制高频噪声。DMA 用循环模式,缓冲区开双缓冲,一半采满触发中断,M4 在中断里处理上一半的数据。
配置上要注意:ADC 的时钟源要选对,H747 的 ADC 时钟来自专门的 ADC 时钟域,分频系数要算准。我一般把 ADC 时钟设在 50MHz 左右,采样时间设长一点,保证高阻抗信号源也能采准。
// ADC 过采样配置示例(基于 HAL 库思路) hadc1.Init.OversamplingMode = ENABLE; hadc1.Init.Oversample.Ratio = ADC_OVERSAMPLING_RATIO_4; hadc1.Init.Oversample.RightBitShift = ADC_RIGHTBITSHIFT_2; hadc1.Init.Oversample.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;过采样比率 4、右移 2 位,等效就是 4 次采样求平均,信噪比能改善约 6dB。这个在电机电流采样这种噪声大的场景里效果很明显。
2.3 控制环放在 M4 上的实际收益
把 PID 环放 M4 之后,最直观的变化是抖动小了。M7 那边跑以太网协议栈,中断优先级一高,单核方案里控制环的周期就会被拉长。分开之后,M4 的定时器中断优先级设到最高,M7 的任何中断都影响不到它。
实测下来,M4 跑 1kHz 控制环,CPU 占用大概 15% 左右,还有大量余量。我甚至在里面加了简单的故障录波功能:一旦检测到异常,把前后 100ms 的采样数据存到 M4 的 TCM 里,事后 M7 再读出来上传。这个功能在排查现场偶发故障时特别有用。
2.4 工业现场的隔离与保护
这块必须单独说。工业现场的电磁环境很恶劣,H747 再强,前端不做隔离照样烧。我的做法是:模拟量输入走隔离运放或者隔离 ADC,数字输出走光耦或者磁隔离,RS485 用隔离收发器,以太网用带隔离变压器的 PHY。
电源部分,H747 的 3.3V 和模拟部分的 3.3V 要分开,用磁珠或者 0 欧电阻单点连接。ADC 的参考电压一定要干净,我一般用专门的基准源芯片,不用 MCU 的 VDDA 直接做参考。
注意:H747 的 VDDA 和 VREF+ 是分开的引脚,VREF+ 一定要接稳定的基准,否则 ADC 精度根本达不到标称值。这个在手册里写得不显眼,但实际影响很大。
3. 摄像头采集项目:DCMI 与 V4L2 思路的碰撞
3.1 DCMI 接口的硬件连接要点
H747 自带 DCMI(Digital Camera Interface),支持 8/10/12/14 位并行接口,最高能跑到 80MHz 左右的像素时钟。我用的是一颗 200 万像素的并口摄像头,输出 RGB565,PCLK 大概 24MHz。
硬件连接上,DCMI 的数据线、行同步、场同步、像素时钟都要接到指定的引脚上,H747 的 DCMI 引脚是固定的,不能随便映射。布线的时候这几根线尽量等长,尤其是 PCLK,走线要短,否则高速下容易采到错位的数据。
DCMI 的数据可以走 DMA 直接搬到 SDRAM 或者内部 SRAM。我一般用 SDRAM 做帧缓冲,因为一帧 RGB565 的 640x480 就要 600KB,内部 SRAM 放不下。H747 的 FMC 接 SDRAM 很成熟,配置好时序就行。
3.2 帧缓冲与双缓冲机制
摄像头采集最怕的就是撕裂——显示的时候数据还在被覆盖。解决办法是双缓冲甚至三缓冲。DCMI 的 DMA 配置成循环模式,指向两块帧缓冲,采满一块触发中断,M7 在中断里切换显示源。
// DCMI DMA 双缓冲配置思路 HAL_DCMI_Start_DMA(&hdcmi, DCMI_MODE_CONTINUOUS, (uint32_t)frameBuffer0, FRAME_SIZE); // 在 DMA 半完成和完成中断里分别处理两块缓冲这里有个细节:DCMI 的 DMA 半完成中断对应第一块缓冲采满,完成中断对应第二块采满。处理的时候要保证在下一帧覆盖之前把当前帧用完,否则还是会撕裂。如果 M7 处理不过来,就得上三缓冲,用 FMC 的 SDRAM 开三块区域轮转。
3.3 从 V4L2 思路借鉴采集框架设计
虽然 H747 是裸机或者 RTOS 环境,没有 Linux 的 V4L2 框架,但 V4L2 的设计思路很值得借鉴。V4L2 的核心是 buffer 队列:应用层把空 buffer 入队,驱动采集满后出队,应用处理完再入队。这个模型在裸机上一样能用。
我在 H747 上实现了一个简化版的 buffer 管理:维护一个 buffer 数组,每个 buffer 有状态(空闲、采集中、已满、处理中)。DCMI 中断只负责把采满的 buffer 标记为已满并唤醒处理任务,处理任务处理完把 buffer 标记为空闲。这样采集和处理解耦,不会因为处理慢丢帧。
3.4 图像处理的算力分配
采集回来的图像要做什么处理,决定了算力怎么分。如果只是简单显示,M7 直接刷 LCD 就行。如果要做边缘检测、颜色识别这类算法,M7 的 480MHz 加 FPU 能扛一部分,但复杂算法还是吃力。
我的做法是:轻量处理(比如 ROI 裁剪、二值化)放 M7,重算法(比如卷积、特征提取)如果实在跑不动,就考虑外挂一颗专用的图像处理芯片,或者降分辨率、降帧率。H747 的 M7 虽然有双精度 FPU,但毕竟不是 DSP,做大规模卷积还是勉强。
提示:H747 的 Cache 对图像处理影响很大。DCMI 的 DMA 写入 SDRAM 后,M7 读之前一定要做 Cache 无效化操作,否则读到的是 Cache 里的旧数据。这个坑我踩过,图像上会出现莫名其妙的横条纹。
4. 车载中控项目:显示、交互与系统稳定性
4.1 LCD-TFT 控制器的配置与图层管理
H747 自带 LCD-TFT 控制器(LTDC),支持多层叠加。车载中控一般至少需要两层:底层是背景或者视频,上层是 UI 控件。LTDC 的图层可以配透明度、位置、大小,硬件直接合成,不占 CPU。
配置 LTDC 的关键是时序参数:HSYNC、VSYNC、HBP、HFP、VBP、VFP 这些要根据屏幕手册来。我用的是一块 7 寸 800x480 的屏,时序参数调了两天才稳定,一开始画面总是偏移几个像素。
// LTDC 时序配置示例 hltdc.Init.HorizontalSync = 7; hltdc.Init.VerticalSync = 1; hltdc.Init.AccumulatedHBP = 8; hltdc.Init.AccumulatedVBP = 2; hltdc.Init.AccumulatedActiveW = 808; hltdc.Init.AccumulatedActiveH = 482; hltdc.Init.TotalWidth = 816; hltdc.Init.TotalHeigh = 484;这些值不是随便填的,要对着屏幕的 datasheet 算。HBP 是水平后沿,等于 HSYNC 加 HBP,容易搞混。我建议先用示波器量一下屏幕的时序,再对着填。
4.2 触摸交互与响应延迟优化
车载中控的触摸一般是电容屏,走 I2C 接口。触摸响应延迟直接影响体验,我的优化思路是:触摸中断优先级设高,中断里只做坐标读取和事件入队,实际的处理放低优先级任务。这样即使 UI 渲染卡顿,触摸也不会丢。
另外,触摸坐标要做滤波,否则手指抖动会导致点击不准。我用的是简单的滑动平均加去抖,连续几次坐标接近才认为是有效点击。
4.3 车载环境的电源与可靠性设计
车载环境比工业现场还恶劣:电压波动大(9V 到 16V 甚至更高)、有负载突降、有冷启动。H747 的供电要经过宽压输入加多级稳压,输入端要加 TVS 和共模电感。
冷启动是个大问题:发动机启动瞬间电压可能掉到 6V 以下,这时候 MCU 必须能扛住或者优雅复位。我的做法是加一个大电容做掉电保持,同时软件上检测电压,低于阈值就提前保存关键数据到 Flash。
注意:车载项目里 Flash 的擦写寿命要算清楚。如果频繁保存数据,要用磨损均衡算法,否则某一块扇区很快就写坏了。H747 的内部 Flash 擦写次数标称 10 万次左右,实际使用要留余量。
4.4 双核在车载中控里的分工实践
车载中控里,M7 负责 UI 渲染、LTDC 刷新、文件系统、蓝牙/WiFi 协议栈;M4 负责 CAN 总线通信、车辆状态采集、以及安全相关的监控逻辑。这样分工的好处是,即使 UI 卡死,CAN 通信和车辆状态监控照样运行。
M4 跑 CAN 的好处是响应确定。CAN 总线的报文有实时性要求,M7 那边一旦被 UI 渲染占住,CAN 报文就可能丢。M4 独立处理 CAN,收到报文直接进共享内存,M7 定期读取更新 UI。
5. 三个项目共通的调试与踩坑经验
5.1 双核调试的痛点与解法
双核调试最烦的是断点。你在 M7 上打断点,M4 还在跑,共享内存的状态可能已经变了。我的习惯是:调试双核交互逻辑时,用 GPIO 翻转加逻辑分析仪,比断点靠谱得多。在关键路径上翻转 GPIO,用逻辑分析仪看时序,能直观看到两个核的配合有没有问题。
另外,STM32CubeIDE 支持双核调试,但配置起来有点绕。要分别建 M7 和 M4 的调试配置,M4 的调试配置要在 M7 启动之后再 attach。这个流程第一次配会花点时间,配好之后就顺了。
5.2 Cache 一致性:最容易忽略的坑
H747 的 M7 带 Cache,这是性能利器,也是坑王。DMA 和外设访问的内存,如果 M7 也通过 Cache 访问,就会出现数据不一致。解决办法有两个:要么把 DMA 缓冲区放到非 Cache 区域(MPU 配置成 Write-Through 或者 Non-Cacheable),要么在访问前后手动做 Cache 维护。
我一般把 DMA 缓冲区统一放到 D2 SRAM 的非 Cache 区域,省得每次都要手动维护。MPU 配置的时候,把这块区域设成 Normal、Non-Cacheable,这样 CPU 和 DMA 看到的数据永远一致。
5.3 时钟树配置的连锁反应
H747 的时钟树很复杂,PLL1、PLL2、PLL3 各自管不同的域。配置的时候要理清楚:M7 的时钟来自 PLL1,M4 的时钟可以来自 PLL1 或者 HSI,外设时钟来自 PLL2/PLL3 或者各自的时钟源。
我踩过的坑是:改了 PLL2 的分频,结果 SDRAM 的时钟跟着变了,SDRAM 时序就不对了,系统跑一会儿就死机。所以改时钟树之前,一定要把每个外设的时钟来源和分频都列出来,改完逐个验证。
5.4 电源域与低功耗的取舍
H747 分了 D1、D2、D3 三个电源域,可以独立开关。低功耗场景下,可以把不用的域关掉。但要注意,关掉某个域之前,要确保那个域里的外设都停了,否则会有异常。
车载项目里我试过在待机时关掉 D1 域(M7 所在的域),只留 M4 在 D3 域跑低功耗监控。唤醒的时候再开 D1,M7 重新初始化。这个方案能显著降低待机功耗,但唤醒流程要调仔细,否则容易卡在某个外设的初始化上。
6. 选型与扩展:H747 适合什么样的项目
6.1 什么场景该选 H747,什么场景不该选
H747 适合的场景很明确:需要一定算力做显示或者算法、同时又有硬实时控制需求、还希望单芯片搞定不想上 Linux 的场合。工业 HMI 加控制、车载中控、高端仪器仪表,这些都是它的主场。
不适合的场景也很明确:如果只需要简单控制,F4 或者 G4 就够了,没必要上 H747 增加复杂度;如果需要跑 Linux 或者复杂 GUI 框架,那应该上 A 系列加 DDR,H747 的 SRAM 和算力撑不起完整的 Linux 桌面。
6.2 从 H747 往上升级或往下降级的路径
往下降级:如果发现双核用不满,M4 大部分时间闲着,那可以考虑 H743(单 M7)或者 F7 系列,成本更低,开发更简单。
往上升级:如果发现 M7 算力不够,比如图像处理跑不动,那可以考虑带 GPU 或者 DSP 的型号,或者干脆上 MPU 跑 Linux。H747 的定位是"高性能 MCU",不是"低端 MPU",这个边界要清楚。
6.3 生态与工具链的现实考量
H747 的生态还算成熟,STM32CubeMX 能生成双核的初始化代码,HAL 库也支持双核。但双核的例程相对少,很多问题要自己啃参考手册。我的建议是:先把官方的双核例程跑通,理解 IPCC 和共享内存的用法,再往自己的项目上套。
调试工具方面,ST-LINK 够用,但如果要同时调试两个核,建议用带 SWO 的调试器,能输出 printf 到 IDE,比串口方便。
三个项目做下来,我对 H747 的感受是:它是一颗"上限很高、下限也不低"的芯片。用好了,单芯片能顶一个小系统;用不好,双核的复杂度会让你怀疑人生。关键还是那句话——先想清楚两个核各自负责什么,把边界划清楚,剩下的就是耐心调外设和时序了。工业控制里把实时性交给 M4,摄像头采集里把 Cache 一致性处理好,车载中控里把电源和可靠性做扎实,这三点抓住了,H747 的项目基本就稳了。