
1. 为什么要把STM32和FPGA塞进同一块板子第一次听到STM32FPGA双核系统这个说法很多人脑子里冒出来的第一个问题就是一个MCU不够用吗为什么还要再挂一颗FPGA我刚开始接触这套架构的时候也有同样的疑惑直到真正做过几个项目之后才明白这两颗芯片凑在一起解决的从来不是算力不够这么简单的问题而是控制流和数据流分离这件事。STM32这类MCU的强项在于顺序逻辑、协议栈处理、人机交互、任务调度它的中断响应、外设丰富度、开发生态都是FPGA比不了的。但一旦遇到需要并行处理的场景——比如同时采集8路ADC、实时做图像预处理、精确测量纳秒级时间间隔——MCU就开始力不从心了。你让STM32去轮询多路高速信号CPU占用率直接飙到80%以上稍微加点协议栈就卡顿。而FPGA天生就是干这个的它内部是真正的并行硬件逻辑几百个通道同时跑互不干扰延迟还是确定的。所以双核系统的本质分工是这样的STM32负责决策和通信FPGA负责采集和预处理。STM32跑FreeRTOS或者裸机主循环处理业务逻辑、跑LWIP协议栈做物联网网关、驱动屏幕做UIFPGA在背后默默地把高速数据流整理好通过FSMC、SPI或者并口把结果丢给STM32。两者之间用一条清晰的数据管道连接各司其职。这套架构特别适合几类人做工业数据采集的工程师需要多通道同步采样做图像处理的开发者摄像头原始数据量太大MCU扛不住做精密测量的比如用FPGA进位链TDC测时间间隔精度要求到皮秒级还有做电机控制的多轴联动需要硬件级PWM和编码器解码。如果你正在做STM32项目但发现某个环节CPU总是跑满或者时序抖动大得没法接受那大概率就是该考虑加一颗FPGA了。下面我会从选型、接口设计、数据流架构、调试方法几个维度把这套双核系统拆开讲清楚。内容基于我实际做过的几个项目经验包括踩过的坑和后来总结出来的稳定方案希望能让准备上手的朋友少走弯路。2. 双核选型STM32和FPGA到底怎么配2.1 STM32这一侧该选哪个系列选STM32不是越贵越好关键看你的数据吞吐需求和外设接口。我一般按三个档位来分档位推荐系列典型场景关键外设入门STM32F103/F407低速采集、简单网关FSMC、SPI、UART中端STM32F429/F743图像显示、多路ADCLTDC、DCMI、FMC高端STM32H743/H750高速数据流、复杂协议FMC、DCMI、以太网、USB HS如果FPGA要通过并口给STM32送数据那STM32必须有FMCFlexible Memory Controller老型号叫FSMC。F103只有FSMC位宽16位够用但速率一般F429和H743的FMC能到32位配合DMA可以做到很高的吞吐。我做过一个项目用H743的FMC接FPGA实测连续传输能稳定在80MB/s以上跑图像数据完全没问题。如果数据量不大用SPI也完全可以。SPI的问题是速率受限于时钟STM32的SPI最高一般到几十MHz而且每次传输有协议开销。但胜在接线简单FPGA那边实现也容易适合控制类指令的下发和少量状态回传。还有一个容易被忽略的点STM32的DMA通道分配。双核系统里FPGA送来的数据通常走DMA搬运到内存如果你同时还要跑ADC、UART、SPIDMA通道会打架。选型时一定要翻参考手册的DMA请求映射表确认你要用的外设不会抢同一个通道。我踩过一次坑F407上FMC和SPI2共用DMA1的某个流结果两个都跑不起来最后只能把SPI换到SPI1才解决。2.2 FPGA选型别一上来就冲高端FPGA这边选择面更广从国产的安路、紫光同创到Xilinx的Spartan、Artix再到Altera现在叫Intel的Cyclone系列价格从几十到几千不等。新手最容易犯的错就是按逻辑资源选型觉得LE越多越好结果买回来发现引脚不够、时钟资源不够、或者封装根本焊不了。我的选型顺序是这样的先数IO。你的应用需要多少路输入输出LVDS接收要成对算MIPI更是一大把差分对。IO数量不够逻辑资源再多也白搭。再看时钟资源。需要几个PLL有没有全局时钟缓冲做频率测量、TDC这类应用对时钟抖动很敏感要选带专用时钟网络的型号。然后看硬核资源。有没有DSP slice做滤波、FFT用、有没有块RAM做缓存用、有没有高速收发器做MIPI、LVDS用。最后才看逻辑单元数量。举个具体例子如果你要做FPGA图像处理从摄像头接收MIPI或者DVP数据做简单的ISP算法去噪、白平衡、边缘检测那Artix-7或者Cyclone IV这个级别就够了不需要上Kintex。但如果你要做复杂的卷积神经网络加速那DSP slice数量就是瓶颈得往上选。国产FPGA这几年进步很快安路的EG4系列、紫光同创的Logos系列在工业控制和图像处理场景已经能替代进口中低端型号。用纯Verilog写ISP算法优化在国产FPGA上跑通完全可行而且成本优势明显。选型时注意确认开发工具链是否成熟有些国产FPGA的时序约束和IP核生态还在完善中项目周期紧的话要留足调试时间。2.3 两者之间的物理接口怎么定接口方案直接决定了整个系统的数据吞吐上限和布线复杂度。常见的几种FMC/FSMC并口速率高适合大数据量。缺点是占用STM32大量IOPCB布线要等长处理。SPI接线少速率中等。适合控制指令和低速数据。UART最简单但速率最低一般只用来传调试信息。自定义并口握手信号灵活但两边都要写时序逻辑调试麻烦。我个人的经验是主数据通道用FMC控制通道用SPI调试通道用UART。三条通道各管各的互不干扰。FMC负责图像、ADC采样这类大块数据SPI负责下发配置参数、读取FPGA状态寄存器UART接个串口模块方便printf调试。这里有个细节要注意FMC的时序配置。STM32的FMC可以配置地址建立时间、数据保持时间等参数这些参数必须和FPGA那边的逻辑时序匹配。我一般先在FPGA里把读写时序做成最宽松的比如地址建立给足5个时钟周期等STM32能稳定读写了再逐步收紧时序提高速率。一上来就追求最高速很容易出现偶发性读写错误而且这种错误很难定位。3. 数据流架构谁负责什么边界在哪里3.1 把实时和非实时分开双核系统设计的第一原则硬实时任务放FPGA软实时和非实时任务放STM32。什么叫硬实时就是必须在确定的时间内完成晚一个时钟周期都不行。比如多路ADC同步采样通道间偏差不能超过几个ns编码器信号解码丢一个脉冲位置就错了高速数据流的缓存和打包不能溢出这些任务在STM32上做你永远无法保证确定性。中断延迟、任务切换、Cache命中率都会影响时序。但在FPGA里这些就是几行always块的事时序由综合工具保证只要约束写对了延迟就是固定的。反过来像协议解析、网络通信、文件系统、UI刷新这些任务放STM32上做效率高得多。你让FPGA去实现TCP/IP协议栈那简直是自找麻烦。所以数据流的典型路径是这样的传感器/ADC → FPGA采样、缓存、预处理→ FMC/SPI → STM32协议封装、业务处理→ 以太网/WiFi/4G → 上位机FPGA在这一链路里扮演的是数据搬运工预处理员的角色它把原始数据整理成STM32容易处理的格式减轻MCU的负担。3.2 双口RAM最常用的数据交换方式FPGA内部用Block RAM做一个双口RAM一边给FPGA逻辑写一边给STM32读这是最经典的做法。具体实现时要注意几个点地址映射要清晰。我一般把双口RAM分成几个区域状态区、命令区、数据区。状态区放FPGA的运行状态比如采样完成标志、FIFO空满标志命令区放STM32下发的控制字数据区才是真正的采样数据。这样STM32读的时候不用猜直接按地址偏移访问就行。握手信号不能省。光有双口RAM还不够必须有中断或者标志位告诉对方数据准备好了。我通常用两种方式一是FPGA写完后拉高一个GPIOSTM32用外部中断捕获二是STM32轮询状态寄存器的某个bit。中断方式实时性好但增加CPU开销轮询方式简单但浪费CPU时间。数据量大用中断数据量小用轮询。位宽要匹配。FPGA内部数据可能是12位ADC值但STM32的FMC是16位或32位总线。这时候要么在FPGA里做位宽转换把两个12位拼成一个24位或者补零成16位要么在STM32里做移位处理。我建议在FPGA里处理好让STM32拿到就是对齐好的数据减少MCU的运算负担。3.3 FIFO跨时钟域最容易出问题的地方FPGA内部经常有多个时钟域ADC采样时钟、系统时钟、FMC接口时钟。数据从一个时钟域传到另一个时钟域必须用FIFO或者双寄存器同步否则会出现亚稳态。我见过太多项目在这里翻车。现象是大部分时候数据正常偶尔出现一个错误值而且很难复现。用逻辑分析仪抓半天也抓不到因为亚稳态本身就是概率事件。正确的做法是任何跨时钟域的信号都必须经过同步处理。单bit信号用两级触发器同步多bit数据用异步FIFO如果是总线用握手协议。Xilinx和Intel的FPGA都有现成的异步FIFO IP核直接调用就行不要自己手写。还有一点FIFO的深度要算够。深度不够会导致溢出丢数据深度太大浪费Block RAM。计算方法写入速率乘以最坏情况下的读取延迟。比如ADC以10MHz写入STM32最坏情况1ms才来读一次那FIFO至少需要10M×1ms10000个字的深度。实际设计时再留一倍余量。4. 几个典型应用场景的实战拆解4.1 多通道ADC采集STM32切换通道 vs FPGA并行采样用STM32做多通道ADC采集常规做法是配置ADC规则组用DMA搬运然后靠定时器触发切换通道。但这里有个问题STM32的ADC是分时复用的多个通道轮流转换通道之间有时间差。如果你要做相位敏感的应用比如功率测量、阻抗分析这个时间差会引入误差。用FPGA就不一样了。你可以外挂多片高速ADC每片ADC配一个独立的采样时钟所有ADC同时启动转换真正实现并行同步采样。FPGA内部为每路ADC开一个FIFO采完一批数据后打包通过FMC送给STM32。STM32拿到的是已经对齐好的多通道数据直接做算法处理就行。具体实现时ADC的接口通常是SPI或者并行LVDS。SPI接口的ADC速率一般不高几MSPS适合中低速场景高速ADC几十到几百MSPS基本都是LVDS或者JESD204B接口必须用FPGA接收。FPGA的LVDS接收要注意差分对的阻抗匹配和端接电阻PCB上走线要等长否则眼图会很难看。4.2 频率测量STM32定时器捕获 vs FPGA等精度测量STM32测频率的常规方法是定时器输入捕获信号上升沿触发捕获记录计数值两次捕获之差就是周期。这种方法在低频段很准但频率越高误差越大因为定时器时钟频率有限。比如72MHz的定时器时钟测1MHz信号每个周期只有72个计数量化误差就有1.4%。FPGA做频率测量可以用等精度测量法用一个标准高频时钟比如100MHz去数被测信号的周期数同时用被测信号去数标准时钟的周期数两个计数相除就得到频率。这种方法在整个频率范围内精度都一致而且FPGA可以同时开多路测量通道互不干扰。如果要做时间间隔测量TDCFPGA的进位链是个好东西。利用FPGA内部进位链的固有延迟每个进位单元约几十ps可以把时间间隔测量精度做到皮秒级。这在激光测距、粒子物理实验里很常用。实现时要注意温度漂移进位链延迟会随温度变化需要做校准。4.3 图像处理从摄像头到显示的完整链路FPGA图像处理的典型链路是摄像头 → MIPI/DVP接收 → 图像预处理 → 缓存 → 显示或传输。MIPI接收对FPGA来说是个挑战因为MIPI的速率高、协议复杂。低端FPGA没有MIPI硬核只能用LVDS加软件解包资源消耗大。如果项目必须用MIPI建议选带MIPI硬核的FPGA或者用专门的MIPI转并行芯片。图像预处理在FPGA里做常见的操作有Bayer转RGB、白平衡、Gamma校正、边缘检测。这些算法都是像素级并行操作FPGA做起来效率极高。用纯Verilog写ISP算法一个时钟周期处理一个像素1080p60fps也就148.5MHz的像素时钟中端FPGA完全能跑。处理完的图像数据通过FMC送给STM32STM32用LTDC驱动LCD显示或者用JPEG编码后通过网络传出去。这里STM32的角色是显示控制器和网络传输器繁重的像素处理已经由FPGA完成了。4.4 电机控制STM32算PIDFPGA出PWM多轴电机控制是双核系统的经典应用。STM32跑PID算法计算量不大但需要定期执行FPGA负责生成多路PWM、解码编码器信号、做硬件保护。这样做的好处是PWM的精度和同步性由硬件保证。STM32的定时器虽然也能出PWM但多轴之间的同步需要软件协调会有抖动。FPGA里所有PWM通道共用一个时钟源相位关系精确可控而且可以做到纳秒级的分辨率。编码器解码也是同理。STM32用定时器的编码器模式高速时容易丢脉冲FPGA用硬件状态机解码再高的转速也不会丢。我做过一个项目电机转速到10000RPM编码器输出频率几百kHzSTM32根本处理不过来换成FPGA后稳如泰山。STM32和FPGA之间的接口STM32通过SPI或者FMC把目标位置/速度写给FPGAFPGA生成对应的PWMFPGA把编码器计数值和限位开关状态回传给STM32。整个控制环路STM32算PID1kHz左右FPGA执行底层驱动MHz级分工明确。5. 调试双核系统时那些让人抓狂的坑5.1 时序约束没写对综合出来能用是运气FPGA开发新手最容易忽略的就是时序约束。很多人写完Verilog综合通过了下载进去也能跑就觉得没问题了。但实际上没有时序约束的FPGA设计工作频率是看运气的。温度变了、电压变了、换一批芯片可能就不工作了。双核系统里FPGA和STM32之间的接口时序尤其重要。FMC的读写时序必须用set_input_delay和set_output_delay约束告诉综合工具外部信号的到达时间。如果约束写得太松综合工具可能给出一个不满足建立/保持时间的结果写得太紧又可能过度优化导致资源浪费。我的做法是先用示波器或者逻辑分析仪实测STM32的FMC输出信号量出地址、数据、控制信号之间的实际时序关系然后据此写约束。约束写完后一定要看时序报告确认所有路径都满足。有违例的路径必须解决不能心存侥幸。5.2 STM32读FPGA数据偶尔出错查了三天才发现是CacheSTM32H7系列有Cache这玩意儿在双核系统里是个大坑。FMC映射的地址空间如果被Cache了STM32读到的可能是缓存里的旧数据而不是FPGA刚写的新数据。现象是大部分时候数据正确偶尔读到旧值。你以为是FPGA写时序有问题查了半天FPGA最后发现是STM32的Cache没配置对。解决方法有两种一是把FMC的地址区域配置成Device模式或者Strongly Ordered模式禁止Cache二是在读之前调用SCB_InvalidateDCache_by_Addr()手动失效Cache。第一种方法简单粗暴但会降低访问速度第二种方法效率高但要小心Cache一致性问题。我一般用第一种因为双核系统里FMC的访问速度本来就不是瓶颈稳定压倒一切。5.3 FPGA的IO电平标准和STM32不匹配STM32的IO是3.3V LVCMOSFPGA的IO电平标准是可以配置的。如果FPGA的Bank电压设成1.8V而STM32输出3.3V长期工作可能会损坏FPGA的IO。更隐蔽的问题是Hysteresis Input Mode。有些FPGA的IO支持迟滞输入模式可以增强抗干扰能力但迟滞窗口会影响阈值判断。如果STM32输出的信号边沿不够陡或者有振铃FPGA在迟滞模式下可能误判。我的建议是接口电平统一用3.3V LVCMOSFPGA的IO Bank电压设成3.3V。如果必须做电平转换用专用的电平转换芯片不要用电阻分压分压会引入延迟和边沿退化。5.4 跨时钟域问题逻辑分析仪都抓不到前面提过跨时钟域这里再强调一次因为这是双核系统里最难查的问题。我遇到过一个案例FPGA内部ADC采样时钟是50MHzFMC接口时钟是100MHz数据从ADC时钟域传到FMC时钟域时偶尔出现一个错误值。用逻辑分析仪抓FMC总线数据看起来是对的抓ADC数据也是对的。但STM32读到的就是偶尔错一个。最后发现是异步FIFO的读使能信号没有做同步导致FIFO在读的时候写指针正在变化读出的数据不稳定。加上两级同步触发器后问题消失。这类问题的排查思路是先确认所有跨时钟域的信号都做了同步处理。单bit信号用双触发器多bit数据用异步FIFO或者握手协议。不要相信大部分时候是对的亚稳态是概率问题批量生产时一定会暴露。5.5 printf调试在双核系统里怎么用STM32上printf重定向到UART是常规操作但在双核系统里FPGA内部的状态你看不到。我的做法是在FPGA里也做一个UART发送模块把关键状态FIFO空满、状态机当前状态、错误计数实时打印出来。这样调试的时候STM32的printf和FPGA的printf可以接到同一个串口终端上注意加个前缀区分两边的情况一目了然。FPGA实现UART发送很简单一个状态机加一个移位寄存器就行资源消耗可以忽略。还有一个技巧用FPGA的嵌入式逻辑分析仪Xilinx叫ILAIntel叫SignalTap。把关键信号接到ILA上设置触发条件可以抓取FPGA内部任何信号的波形。这比用外部逻辑分析仪方便得多而且不占用IO引脚。缺点是消耗Block RAM采样深度有限但对于调试时序问题足够了。6. 从原型到产品双核系统的工程化建议6.1 PCB布局把数字噪声关在笼子里双核系统的PCB比单MCU复杂得多主要是高速信号和模拟信号共存。ADC的模拟输入、FPGA的高速差分对、STM32的晶振这些都对噪声敏感。我的布局原则分区模拟区、数字区、电源区分开布局地平面要完整不要被信号线割裂。晶振靠近芯片STM32的晶振和FPGA的晶振都要尽量靠近对应的引脚走线短而直下面不要走其他信号。差分对等长LVDS、MIPI这些差分信号正负两条线必须等长误差控制在5mil以内。电源去耦每个电源引脚都要有0.1uF的去耦电容大容量电容放在电源入口。FPGA的核电压和IO电压要分开供电用LDO或者DC-DC纹波要小。如果板子上有ADC模拟地和数字地一般建议单点连接避免数字噪声通过地平面串到模拟部分。但具体怎么处理要看ADC的型号和精度要求高精度ADC16位以上对布局要求很苛刻低速ADC可以适当放宽。6.2 固件升级STM32和FPGA都要能在线更新产品化之后固件升级是个必须考虑的问题。STM32的固件升级好办用Bootloader通过UART、USB或者网络接收新固件写到Flash里就行。STM32的LD文件链接脚本要规划好Bootloader区和App区别让它们重叠。FPGA的固件升级稍微麻烦一点。FPGA的配置数据存在外部SPI Flash里上电时FPGA从Flash加载配置。要在线升级可以让STM32通过SPI去写这颗Flash写完后再触发FPGA重新加载。具体做法STM32通过SPI接口连接到FPGA的配置Flash升级时STM32把新的bit流文件写入Flash然后拉低FPGA的PROGRAM_B引脚或者用软复位命令FPGA重新从Flash加载新配置。这样整个系统不用断电就能完成FPGA固件更新。注意升级过程中要保证电源稳定Flash写入时断电会导致FPGA无法启动。可以在Flash里存两份配置Golden Image和Update Image升级失败时回退到Golden Image。6.3 功耗和散热别小看FPGA的发热FPGA的功耗和它的工作频率、资源利用率成正比。一个中等规模的FPGA在100MHz下跑功耗可能到1-2W加上STM32和其他外设整板功耗可能到3-5W。如果是手持设备或者密闭外壳散热必须认真考虑。降低FPGA功耗的几个手段时钟门控不用的模块把时钟关掉动态功耗和时钟频率成正比。降低工作频率在满足性能的前提下尽量用低的时钟频率。很多应用不需要100MHz50MHz就够了。优化逻辑减少不必要的翻转比如用独热码One-hot编码状态机可以减少组合逻辑的毛刺但会增加触发器数量需要权衡。独热码和二进制编码的选择要看具体设计状态少的时候独热码有优势状态多的时候二进制编码更省资源。散热设计FPGA下面铺散热焊盘PCB上打过孔到背面的大面积铜皮必要时加散热片或者小风扇。6.4 量产测试怎么快速验证每一块板子双核系统的量产测试比单MCU复杂因为要同时验证STM32和FPGA。我的做法是做一个自测试固件STM32端跑一个测试程序依次测试UART通信、SPI通信、FMC读写、ADC采集、网络通信。FPGA端配合在收到测试命令后生成已知的测试数据比如递增序列、棋盘格图像STM32读回后比对判断FPGA是否正常。测试结果通过LED或者串口输出产线工人只需要看指示灯或者扫码记录就行。这套自测试固件在打样阶段就要开始写不要等到量产了才临时抱佛脚。还有一个经验关键信号留测试点。FMC总线、SPI时钟、FPGA的配置完成信号DONE、STM32的复位信号这些都要引出测试点方便产线用示波器快速判断问题。测试点不用多但一定要覆盖最可能出问题的环节。7. 一些零散但实用的经验关于STM32的GBK转UTF8如果双核系统要显示中文STM32端经常需要做编码转换。GBK是双字节UTF8是变长编码转换需要查表。如果字库不大可以在STM32里存一张GBK到UTF8的映射表如果字库很大建议在上位机端就转好STM32直接显示UTF8。关于STM32禁用JTAGF103系列上JTAG和某些GPIO复用如果项目里用到了这些引脚需要在代码里禁用JTAG保留SWD。调用__HAL_AFIO_REMAP_SWJ_NOJTAG()就行但要注意禁用后JTAG引脚就变成普通GPIO了不能再用来调试。关于FreeRTOS下的Flash写入STM32在写Flash时会阻塞总线如果此时有中断触发可能导致写入失败。正确做法是写Flash前挂起所有中断和调度器写完再恢复。H743这类双Bank Flash的芯片可以Bank对Bank操作但也要注意时序。关于FPGA实现串口发送ASCII字符串在FPGA里做一个简单的UART发送状态机把要发送的字符存在一个ROM里上电后依次读出并发送。调试信息用这种方式输出很方便不需要额外的调试器。关于STM32的DWTDWTData Watchpoint and Trace可以做精确的代码执行时间测量比用定时器更准。在调试PID算法、优化性能时很有用。初始化时使能DWT的CYCCNT然后在代码前后读这个寄存器差值就是时钟周期数。这套双核架构我用了几年从最初的磕磕绊绊到现在的得心应手最大的体会就是边界要清晰接口要简单调试要留后路。STM32和FPGA各干各擅长的事中间的接口越简单越好调试手段越丰富越好。只要这三点做到了双核系统其实比单核更稳定因为每个核的负载都轻了出问题的概率反而更低。