资讯动态

FPGA测控程序核心三件事:框架搭建、模块划分与数据流设计

发布时间:2026/9/8 17:02:30 来源:尧图企业网站定制
FPGA这活儿干测控是真合适但也是真容易翻车。早些年我带一个采集项目ADC和DAC都选好了FPGA型号也定下来了结果一写代码就发现不是那么回事——接口时序对不上、跨时钟域乱采、FIFO溢出、上位机时不时收到错数整个系统跟得了帕金森似的。后来把框架捋顺、模块划清、数据流理明白之后项目才稳下来。今天就把这些经验掰开揉碎讲清楚标题就一句话FPGA里的测控程序核心就三件事——框架、模块、数据流。这篇内容适合正在做数据采集、运动控制、实时监测、仪器仪表这类工控产品的工程师也适合刚入门FPGA、想在测控方向扎根的学习者。我会先讲整体框架怎么搭再拆解核心模块怎么设计然后把数据流这部分讲透最后用一个STM32H743 FPGA做FMC通信的实例把前面讲的串起来。文末附上我在实际调试中踩过的坑和排查技巧都是文档里查不到的那种。1. 整体框架怎么搭才不至于后期改到怀疑人生1.1 先想清楚测控程序的本质任务测控程序在FPGA里要干的事情说穿了就是三件事采集数据、处理数据、输出控制。听起来简单但一旦牵扯到多通道同步、实时响应、长时间运行稳定性问题就开始变复杂了。我见过不少项目上来就直接在顶层文件里写逻辑所有信号拉通模块边界模糊数据通路混乱。前期改起来快等做到中后期每改一个功能都要担心会不会把别的功能带崩。所以框架的意义在哪在于给你划定边界。就像一栋楼要分区采集是采集、控制是控制、通信是通信每个区域有自己的门牌号。模块之间只通过约定好的接口往来谁也别越界。这个习惯一旦养成后期的维护成本会急剧下降。1.2 三种主流框架结构按场景选型我归纳了一下FPGA测控程序有3种常见的顶层框架各有各的适用场景。第一种状态机主导型框架。这种框架的核心是一个大状态机采集、处理、输出都在状态机的调度下有序进行。典型的应用场景是慢速控制类项目比如温度控制、步进电机定位、继电器逻辑控制。状态机框架的好处是思路清晰单条执行路径调试容易坏处是吞吐量有限不适合高速连续的数据流。举个例子一次温度采集周期可能100ms这期间状态机从IDLE到SAMPLE再到CALC再到OUTPUT完全来得及人也容易理清楚。第二种流水线并行处理型框架。这种框架面向的是高速连续数据流比如高速ADC采集、图像传感器数据处理、通信信号的调制解调。每一个数据样本都要经过滤波、校准、格式转换等多个环节这些环节像工厂流水线一样串起来同时每个环节都在并行工作。流水线框架的关键是逐级握手上一级处理完立刻交给下一级全程无阻塞。当然代价是时序约束复杂调试难度大。第三种CPUFPGA协同框架。这种框架是当下最主流的做法FPGA负责高速采集和实时控制CPU比如STM32、Zynq的ARM核负责算法调度、指令解析、数据上报。你搜到的“stm32h743和fpga实现fmc通信”就是这种框架的典型代表。FPGA把采集到的数据整理好通过FMC总线交给STM32处理STM32下发的指令也通过FMC总线传给FPGA执行。这种框架的核心优势是分工明确FPGA干它擅长的并行活CPU干它擅长的逻辑活。我给你的选型建议很简单采集速率低于100kSPS、控制逻辑偏简单的直接上状态机数据率毫秒级都要处理一批的用流水线项目里需要人机交互、网络上报、协议解析的不用犹豫直接上CPUFPGA协同。1.3 框架落地前先把资源估算和接口定义做扎实很多开发者在框架阶段容易犯一个毛病不估算资源也不提前定义跨模块接口直接就开始编码。等代码写到一半发现LUT不够了、DSP不够了、BRAM不够了或者模块A给模块B的信号位宽变了牵一发动全身改到哭。资源估算看两件事。第一逻辑资源。比如你要做32通道的FIR滤波每通道128阶那乘法器消耗就是32x128这数字在资源表上一查就知道够不够。第二存储资源。比如ADC采样率1MSPS、16bit精度FIFO深度要缓存1ms的数据那就是1M x 16bit 2MBFPGA内部的BRAM一般几十KB到几MB不等不够就得挂外部SRAM或者DDR或者调整缓存策略。接口定义这个事我建议在项目初期就定出版本用文档写清楚不要只存在脑子里。每个模块的接口信号包括信号名称、方向、位宽、功能说明、时序要求。跨时钟域的信号必须要标注。这个文档在后期联调的时候是救命稻草不然两个工程师各写各的模块最后信号对不上排错排三天。2. 核心模块怎么设计每个模块都是一台独立的小机器2.1 信号采集模块ADC接口和SPI/I2C/LVDS时序测控系统的数据源头是信号采集模块。FPGA这里要做的事情是和ADC建立可靠的通信把模拟量变成数字量再送到后续模块。ADC的接口五花八门低速的多用SPI或I2C中速的用并行CMOS接口高速的用LVDS或JESD204B。SPI接口的ADC比如AD7680、ADS1118这些关键是把握好SCLK的极性和相位。FPGA作为主机要严格按照ADC数据手册的时序图来产生SCLK。我个人的做法是先读时序图把几个关键参数标出来——SCLK最高频率、CS建立时间、数据输出有效时间然后设计一个状态机来驱动。SPI的速度上限决定了这类ADC的采样率上限一般几MHz以内。高速LVDS接口的ADC比如ADS42LB69这类时序就讲究多了。LVDS是差分信号每一bit都在时钟的上升沿或下降沿被采样位对齐和帧对齐是绕不开的两个问题。FPGA内部要先用ISERDES把串行数据转成并行然后通过训练序列找到帧边界。这个环节一旦出错出来的数据就是乱的而且通常是偶发性的乱特别难排查。后端无论接什么接口的ADC我都会在采集模块出口加一个数据有效标志信号。ADC数据准备好后valid拉高一个时钟周期后面的处理模块只在valid为高时才采数。这个习惯看起来简单但能把数据同步问题从根上解决。我见过很多新手把valid忽略掉直接用时钟沿采数结果在高速采集的时候经常丢数或者错位。2.2 信号处理模块滤波、校准、阈值判断采集进来的数据一般不能直接用先要做滤波。FPGA里做滤波首选是FIR滤波器Xilinx和Intel都有现成的IP核比自己写省心得多。FIR滤波器设计的关键是系数计算——通常用MATLAB的FDATool或者Python的scipy来生成系数量化位宽要和FPGA的DSP单元匹配不然精度不够。除了FIR还有一类常用处理叫滑动平均其实就是最简单的FIR系数全为1。它在平滑噪声方面效果好计算量小在低速测控场景里非常实用。比如你采集一个温度信号采样率1kSPS期望0.1Hz以内的缓变信息这时候做64点滑动平均噪声能被压下去好几倍而信号本身几乎不受影响。校准这块是国产仪器普遍容易忽略的事情。传感器和模拟链路的增益误差和偏置误差是必然存在的。我之前做一款数据采集卡在FPGA里做了两点校准采集模块前面加一个标准电压源测出实际输出和理论输出的差值算出增益系数和偏置量存在EEPROM里上电后自动加载。校准做完全量程误差从±2%降到±0.05%效果立竿见影。阈值判断是测控里的另一项常驻工作采集值超过上限就报警低于下限就置故障标志。阈值判断看起来简单但一定要加上滞回比较。比如上限是100超过100报警低于100就恢复那当信号在100附近抖动时报警信号就会不停翻转整个系统像抽搐一样。解决方法是报警条件是超过105恢复条件是低于95中间留一段缓冲区让信号在阈值附近抖动时报警状态稳定不下来。这个10%的滞回区间是我用了很久的默认值帮我在现场省了很多麻烦。2.3 通信交互模块串口、网口、PCIe、FMC测控系统的通信模块是跟外界打交道的窗口。低速场景用得最多的是UART。UART看着简单实际上一堆坑波特率误差、停止位长短、接收超时、数据帧粘连……我在FPGA里写UART收发器时接收端用16倍过采样在每一位的中间时刻采样这样能最大程度规避波特率偏差和信号抖动的影响。发送端老老实实按波特率产生移位时钟一个字节发完之后给个完成标志。当数据量上去了UART就不够用了。这时候网口是个好选择。FPGA里面做千兆以太网需要用到MAC IP核再加上外部PHY芯片比如RTL8211。如果只是UDP协议可以绕开CPU直接硬件实现吞吐量能做到接近线速。但TCP就麻烦很多状态机复杂最好交给软核处理器比如MicroBlaze跑协议栈。个人经验能少碰TCP就少碰用UDP加应答重传机制实时性比TCP好得多。PCIe和FMC是高速测控的典型接口。PCIe适合做板卡插到电脑主机里比如数据采集卡吞吐量按GB/s算。FPGA做PCIe一般直接调用IP核用户侧拿到的是AXI-Stream接口读数据、写数据都走这个标准。FMC更多是板级互联比如STM32H743接FPGASTM32一侧内存映射访问FPGA一侧把FMC接口转成内部寄存器读写总线。第4部分我将用这个例子做完整演示。2.4 控制输出模块DAC、PWM、步进电机脉冲测控系统的输出模块是执行机构。DAC输出模拟电压关键指标是建立时间和稳定时间。FPGA向DAC写数据时要注意DAC是否有流水线延迟比如某些高速DAC从数据写入到模拟输出稳定需要好几个时钟周期这在闭环控制里会引入相位裕度问题需要仔细计算。PWM输出在电机控制、加热器控制里用得最多。FPGA生成PWM的方式很灵活一个计数器加上一个比较寄存器就完事。周期由计数器上限决定占空比由比较值决定。如果要实现死区控制、多路互补输出计数器的复杂度就上来了还得跟IO引脚的电平极性、互补关系配合好。步进电机控制是测控里常见又容易写错的部分。FPGA给步进驱动器发脉冲和方向信号脉冲频率对应速度脉冲个数对应位移方向信号对应正反转。我建议用一个独立的模块来管理脉冲生成内部实现加减速曲线。直接匀速起停的问题在于频率高了电机会丢步频率低了效率又低。S型加减速曲线是标准方案速度从0升到目标再降到0但不是直线加而是平滑的S曲线加速度本身是连续变化的电机的机械冲击最小。FPGA里实现S曲线可以离线算好速度-时间表存在ROM里运行时查表输出。3. 数据流设计所有模块靠它才能协同工作3.1 先区分控制流和数据流别搅成一锅粥我刚做FPGA的时候最大的毛病就是把控制流和数据流搅在一起。所谓控制流就是状态机跳转、使能信号、启动停止这类“指挥”信号所谓数据流就是ADC采到的数值、计算结果的中间值这类“货”信号。为什么强调区分因为一旦混着写代码的维护难度会成倍增加。你可能会在一个always块里既改控制寄存器又改数据寄存器最后仿真的时候数据明明对了控制不对控制对了数据又错。我的做法是控制信号和数据信号分开写always块控制信号上升沿触发数据信号用validdata的握手方式传递。这样调试的时候把valid拉出来看数据的节拍一目了然风控点风控住。3.2 用valid/data握手协议统一模块通信FPGA模块间通信我强烈推荐AXI-Stream的基本思路valid、data、ready这三个信号。发送方拉高valid表示数据有效接收方拉高ready表示可以接收当valid和ready同时为高数据传输发生拍一个时钟。这个协议的优势在于发送方不用管接收方是否准备好只要valid拉高、数据稳住接收方ready为高的时候数据必然被正确拿到。反过来接收方忙的时候拉低ready发送方自然等待。这在多级流水线里简直不要太舒服每一级只管自己跟邻居的关系不用关心整条链路的状态。看一个最简单的Verilog片段感受一下这个握手协议落地有多简单// 发送方产生数据 always (posedge clk or negedge rst_n) begin if (!rst_n) begin tx_valid 1b0; tx_data 16d0; end else if (tx_ready) begin tx_valid 1b1; tx_data next_data; // 来自采集模块或处理模块 end end // 接收方接收数据 always (posedge clk or negedge rst_n) begin if (!rst_n) begin rx_valid_r 1b0; rx_data_r 16d0; end else if (rx_valid) begin rx_valid_r 1b1; rx_data_r rx_data; end else begin rx_valid_r 1b0; end end现实中接收方逻辑比这个复杂但万变不离其宗。valid/data/ready这套协议贯穿了FPGA的几乎一切数据流动。3.3 FIFO是数据缓冲的大杀器但别忽视反压问题模块之间速率不匹配最常见的解法是FIFO。FIFO在FPGA里的本质是RAM读写指针分为同步FIFO和异步FIFO。同步FIFO的读写时钟相同用于单时钟域内的速率平滑异步FIFO的读写时钟不同用于跨时钟域的数据传递。跨时钟域这个场景我在多通道采集板上遇到太多次了ADC的采样时钟由外部晶振提供FPGA内部的逻辑时钟是PLL锁出来的两个时钟的频率和相位都不一样。如果直接把ADC的采样数据打到FPGA逻辑时钟域亚稳态问题会频繁出现——就是那种偶尔采到一个错误的中间值数据突然跳一下的现象。解决方法是在ADC数据和FPGA逻辑时钟之间插入一个异步FIFO读时钟用FPGA逻辑时钟写时钟用ADC采样时钟。这样两边都能在自己的时序约束下工作互不干涉。FIFO有个关键参数叫深度。深度太浅数据会溢出深度太深又浪费Block RAM。深度怎么定呢算一个例子ADC采样率1MSPSFPGA逻辑时钟100MHz数据位宽16bit。如果逻辑侧每1ms读取一次FIFO中累积的数据那么至少需要缓存1M SPS x 1ms 1000个采样点也就是1000 x 16bit 16kbit取整到标准深度选2048深、16位宽的FIFO就够用。但深度解决了反压问题还得处理。FIFO快满了怎么办要么赶紧读要么丢掉来不及处理的数据。丢数据在测控系统里通常是不能接受的。所以我会在FIFO快满的时候拉高一个almost_full信号告诉下游“别发了我要炸了”。下游模块收到反压信号后要么暂停CMD要么把数据暂存到另一个缓冲。整个链路有了反压机制数据不会因为优先级处理不过来而无辜丢掉。3.4 一个完整的数据通路实例ADC采集到上位机显示捋一个最简单却覆盖完整的数据通路。假设你有一个温度采集系统ADC输出16bit温度数据采样速率10kSPSFPGA内做64点滑动平均然后通过UART发送到上位机显示。数据流的路径是ADC接口模块把并行数据配上valid信号交给滤波模块 - 滤波模块做滑动平均每64点输出一个平均值到FIFO - FIFO缓存滤波结果 - UART发送模块读取FIFO按波特率把数据发出去。每一步的瓶颈在哪ADC接口模块的关键是正确产生采样时钟和读取数据滤波模块的关键是valid的节拍FIFO的关键是深度是否够缓存一帧数据UART发送的关键是波特率是否匹配、发送完成标志是否及时产生。这些环节一个个调通之后整体才能跑起来。所以数据流设计做得好其实是把一个大问题拆成了若干个小问题每个小问题都有明确的验收标准。4. 实战案例STM32H743和FPGA通过FMC通信4.1 项目背景和方案选型前面把框架、模块、数据流这三个基础的原理讲清楚了现在用一个实际项目把它们串起来。这个项目是做一台多通道高速数据采集器采集速率需要2MSPS、16位精度、8通道数据要实时显示到LCD屏上同时既要响应用户的按键和触屏控制又要能通过以太网上传数据到PC。这种需求FPGA单独干也能干但人机交互界面、TCP协议栈、文件系统这些功能在FPGA里做太痛苦了。正好STM32H743这颗MCU资源非常强Cortex-M7核心跑到480MHz带FMC接口可以做LCD驱动和以太网协议栈。但STM32的内部ADC最快也到不了4MSPS的采样率就算到了8通道轮流采样同步性也保证不了。所以方案很清晰FPGA做模拟前端控制和高速采集STM32做界面、协议和系统管理。两者之间用FMC接口互联。4.2 硬件连接与FMC接口设计FMC是STM32的一个总线接口对外表现为类似SRAM的接口地址线、数据线、读写控制线、片选线。STM32可以按内存映射方式访问外部设备也就是你向某个内存地址写数据实际上就是在往FPGA内部的某个寄存器写数据。硬件连接上FMC数据线用16位地址线用8位就够用了。我们需要的寄存器不多控制寄存器启动采集、停止采集、复位、状态寄存器FIFO状态、采集状态、数据FIFO端口读取采样数据。连线的注意事项有两处第一FMC总线的时序参数要在STM32侧配置好。关键是地址建立时间、数据建立时间如果FPGA侧反应慢这两个参数就得放宽。第二FMC数据线是双向的FPGA侧的IO要考虑方向和使能信号的控制。用Verilog写的话数据线方向要跟在读写控制信号后面切换不能有瞬间的双向驱动冲突否则容易烧片子。4.3 FPGA侧模块设计寄存器组、采集控制、FIFO读取逻辑FPGA侧要做的内容包括三个模块FMC从接口模块、采集控制模块、数据FIFO模块。FMC从接口模块的核心功能是把FMC总线上的读写操作翻译成内部寄存器的读写操作。STM32往地址0x60000000写数据0x0001FMC从接口模块解析出地址和数据把采集控制寄存器置为启动采集。STM32从地址0x60000008读数据FMC从接口模块就把FIFO里当前数据放到总线上。采集控制模块负责状态机的运转空闲时等待启动命令启动后产生采样时钟读取ADC数据采样数据经过滤波处理写入FIFO当FIFO半满时往状态寄存器写一个标志供STM32查询。FIFO读取逻辑要配合FMC的读时序来安排。FMC读操作有时序要求片选拉低后数据必须在规定时间内稳定。如果FIFO读延迟太长会导致STM32读到无效数据。解决办法有两种一是把FIFO用show-ahead模式也就是数据提前出现在读数据线上FMC一读就有二是把FMC的等待周期调长给FPGA足够的反应时间。实测下来show-ahead模式对时序要求友好得多推荐优先用。4.4 数据流的打通和实测效果系统上电后STM32初始化FMC控制器然后向FPGA写控制字启动采集。FPGA按2MSPS的速率采样8通道数据送到内部FIFO。STM32用DMA的方式周期性地从FMC数据端口读取FIFO里的数据DMA传输完成后触发中断在主循环里处理数据显示到LCD、组包上以太网。调试的时候先在小速率跑通链路再慢慢提速。我先用100kSPS验证通信正常再看FIFO有没有溢出最后才把采样率拉到2MSPS。实测下来8通道、2MSPS、16bit的数据率是32MByte/sFMC总线的理论带宽是足够支撑的。跑了一个小时FIFO的溢出标志始终为0DMA传输也没有丢帧整个系统稳如老狗。关键经验是STM32的DMA读取必须和FPGA侧FIFO的写入节奏匹配。如果STM32读得慢了FPGA侧FIFO就会满需要判断almost_full信号来暂停采集否则数据溢出。我们最终把FIFO的深度设到4096配合DMA中断每半满读一次实测下来一点压力都没有。5. 常见问题与排查技巧实录5.1 典型问题速查表我把做FPGA测控项目时遇到的典型问题整理成一张表方便你以后遇到同款问题时快速定位。现象可能原因排查方法采集数据偶发性乱码跨时钟域未做同步亚稳态检查采样时钟是否跨域在跨域路径加异步FIFO或者双触发器同步FIFO溢出数据丢失FIFO深度不够或读取不及时统计数据产生速率和读取速率增加FIFO深度或优化读取时机仿真正常上板不正常时序约束缺失或约束错误检查约束文件中的时钟约束、IO约束跑一下时序报告看看有没有违例寄存器读写偶尔错位FMC时序参数设置不当调整STM32 FMC的地址建立/保持时间或增加读等待周期电机控制不稳定丢步加减速曲线设置不当检查启动频率和目标频率是否在电机响应范围内加入S型加减速多通道采集通道串扰ADC前端采样保持时间不够检查采样窗口时间必要时降低采样率或增大采样电容充电时间5.2 我踩过的几个坑你必须知道第一个坑是跨时钟域的忽视。我第一次做双通道高速采集时ADC的采样时钟和FPGA逻辑时钟来自两个晶振中间没有做跨时钟域缓存结果上位机收到的数据每隔几十秒就跳一下。排查了很久最后用示波器看才发现是亚稳态在作祟。从那以后凡是不同时钟域的信号我几乎一律用异步FIFO来隔离省心又靠谱。第二个坑是复位信号的处理。很多入门级的代码喜欢用异步复位按键按一下就复位整个FPGA。但实际测控系统里复位信号往往抖动一抖动逻辑就反复启动系统被迫重启。我的做法是加一个复位滤波模块检测到外部复位信号持续拉低超过100个时钟周期才执行全局复位否则忽略。这个模块不到20行代码但能省掉大量莫名其妙的“死机”问题。第三个坑是仿真不做充分就上板。仿真确实不能替代上板测试但上板前务必把基本的功能仿真跑到位。特别是FIFO读写、握手协议这类的逻辑仿真一次能看到的时序细节比你上板用逻辑分析仪抓半天还得不出结论。我的习惯是每个模块都写一个testbench至少跑一遍功能覆盖再进板级调试。5.3 调试利器逻辑分析仪和在线观测FPGA调试阶段逻辑分析仪是少不了的。早期芯片集成的逻辑分析仪资源很有限现在赛灵思的Vivado里有ILA集成逻辑分析仪可以挂在你关心的内部信号上上板的时候实时抓波形。我调试数据流的时候就会把FIFO写使能、读使能、almost_full、valid这些信号拖出来看数据流的节拍对不对。另外FPGA内部资源有一类叫debug bridge的东西可以通过JTAG把内部寄存器的值读出来数据上传到PC上分析。我甚至做过一个“内部状态在线观测”模块把所有关键寄存器的值通过UART定时上报上位机出曲线。这个做法在调试多通道长时间运行的稳定性时特别好用哪个通道出了问题一眼就能看到是采集类故障还是通信类故障。6. 性能优化与兜底策略6.1 采样率不够用试试并行处理如果系统采样率不够先别急着换更贵的FPGA。很多时候是数据通路在某一个环节成了瓶颈。可以用多路并行处理的方式提升吞吐量。例如FIR滤波器原本一个时钟周期处理一个数据如果逻辑资源还够分配给4个DSP并行处理4个不同通道的数据系统整体吞吐量可以翻4倍。这种“面积换速度”的做法在FPGA领域非常经典。6.2 长时间运行的稳定性问题工业测控系统经常要求7x24小时不间断工作。FPGA虽然稳定性高但代码写得不讲究照样出问题。比如有符号数溢出没有处理、FIFO读写指针在极端情况下错位、复位信号毛刺导致逻辑状态机跳飞。我的兜底策略是状态机里设一个看门狗定时器每个状态停留时间超过预设值就强制回IDLE所有FIFO加上上电复位写指针和读指针必须清零关键变量值加奇偶校验位校验不过直接报错上位机提示重启。6.3 算法上FPGA的优化空间测控系统里的算法比如卡尔曼滤波、PID控制、FFT在FPGA里实现时都有聊胜于无的技巧。卡尔曼滤波在FPGA里的计算量主要是矩阵乘法和求逆实际使用中一般会事先把矩阵求逆离线算好在线只做乘法和加法复杂度大幅下降。PID控制则要注意整数运算的精度处理我一般会把误差乘以一个系数变成大整数算完再截断。这些都是工程上的折中但效果非常明显。7. 尾声框架选型、模块划分、数据流设计三者不可偏废做FPGA测控程序框架、模块、数据流这三件事就像长跑前的热身、途中配速和最后冲刺缺哪一个都跑不出好成绩。框架决定了系统的可扩展性和可维护性模块划分决定了每个功能的独立性和可测试性数据流设计则直接决定了系统能否稳定、高效地运转起来。以我个人这几年的实操体会来说框架和模块的设计阶段花的时间会在项目后期十倍百倍地还回来。一次把接口定义清楚把数据流理清楚后面联调的时间会大幅缩短。反过来前期随意编码、后期反复返工的例子我见过太多。最后再分享一个小细节每个模块文件的开头一定要写注释注明这个模块的功能、输入输出信号定义、修改记录。这在项目规模小的时候看不出价值等模块数量超过20个你就会发现没有注释的代码就是一个隐藏的地雷阵。好的注释不是给别人看的是给三个月后的自己看的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价