ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于FMQL45T900的工业信号采集平台:国产化替代设计与实现

基于FMQL45T900的工业信号采集平台:国产化替代设计与实现 去年年中接了一个工业设备改造的活儿要求把原来基于进口芯片的信号采集板整体替换成国产方案。设备要处理16路同步模拟量输入100kSPS采样率、16bit精度还要兼顾Modbus TCP通信和本地HMI逻辑。评估了一圈下来最后定了复旦微FMQL45T900核心板。这篇文章就把完整过程捋一遍——从选型逻辑、硬件连接到BSP配置、PL/PS协同开发再到调试时踩过的三个典型坑。内容偏工程实战适合正在做国产化替代、或者刚拿到FMQL系列板卡不知道怎么下手的工程师参考。1. 为什么是FMQL45T900工控信号处理场景下的选型逻辑1.1 异构SoC架构对信号处理的价值先解决一个很多人刚接触时会困惑的问题FMQL45T900到底是个什么东西你可以把它理解成一块同时集成了高性能FPGA和ARM处理器的单芯片SoC。在复旦微的FMQL系列里型号命名的逻辑和行业惯例一致45代表逻辑资源规模450K级别等效逻辑单元900代表900引脚的BGA封装。PS端集成了一对ARM Cortex-A9处理器能跑Linux负责协议栈、任务调度、人机交互这些复杂但不极致实时的工作PL端就是FPGA逻辑负责16路同步采集、数据预处理、时序控制这些简单但要求极高并行度和确定性的工作。为什么不用纯MCU方案16路同步采样意味着要在同一个时钟沿把所有通道的转换结果同时锁存MCU的GPIO和顺序执行模型根本跟不上。为什么不用纯FPGA方案以太网协议栈、文件系统、网络诊断、远程升级这些功能在FPGA里实现一遍开发周期会拖到天荒地老。FMQL45T900这种ARMFPGA的异构架构本质上是把实时并行和复杂逻辑两条路线合成一条。对工控信号处理平台来说这个架构还有一个隐蔽优势PL端的逻辑可以直接访问PS端的DDR内存通过AXI DMA完成数据搬运整个过程不需要CPU参与。数据采集路径上的延迟是硬件级的不是软件中断级的这在做闭环控制或者高速监测时非常关键。1.2 不同方案对比为什么不是MCU、DSP或纯FPGA如果你也在做同类选型下面这个对比表可以直接参考方案并行采集能力复杂逻辑承载实时性开发成本MCU弱弱中等低DSP弱弱中等中纯FPGA强强高高ARMFPGA SoC强强高中纯DSP方案在数值计算上有优势但工控信号处理平台不只是做运算它还需要大量的IO接口、通信接口和外部设备管理。FMQL45T900这样的SoC把PS端的通用外设UART、SPI、I2C、千兆网、USB和PL端的自定义逻辑结合起来相当于一个平台同时覆盖了控制器和数据采集器两个角色。还有一个现实因素是国产化率要求。复旦微这套方案在全流程工具链、IP授权、芯片供货上都更容易满足项目审查需求这是进口方案替代过程中最实在的考量。1.3 核心板资源盘点我用的这块核心板是第三方厂商做的集成好了FMQL45T900主芯片、1GB DDR3、256MB QSPI Flash、8GB eMMC、千兆PHY、USB PHY和电源树。核心板引出的资源包括PS端MIO/EMIO若干、PL端IO若干以及高速串行收发器具体引出了多少路取决于板卡厂商的引脚定义文档使用前一定要先把这份文档吃透。资源规格用途处理器双核ARM Cortex-A9跑Linux、协议栈、应用逻辑FPGA逻辑450K级逻辑单元并行采集、信号预处理、自定义时序内存1GB DDR3 32bit数据缓冲、系统运行存储QSPI Flash / eMMC启动镜像、根文件系统网络千兆以太网 x2Modbus TCP、远程运维主要IOMIO / EMIO / PL IO外设控制、数据接口拿到核心板的第一件事不是写代码而是确认启动模式跳线、串口位置、调试器连接方式。这一步做扎实后面能省大量时间。2. 硬件平台搭建从核心板接口到ADC信号链路的连接2.1 信号链整体设计信号链路的设计我按这个思路走工业现场的传感器信号4-20mA电流环、热电偶、PT100热电阻这类先进信号调理板经过滤波、放大、偏置调整后输出0~5V或±10V范围的电压信号送到ADC。ADC我选用的是AD76068通道同步采样、16bit分辨率、最高200kSPS吞吐率通过并行总线或SPI接到PL端IO上。两片AD7606并联就能覆盖16路同步采集需求。这里有个值得注意的点两个ADC芯片之间的同步。AD7606提供BUSY信号当所有通道转换完成后它会拉高PL端逻辑可以同时等待两片ADC的BUSY信号确认全部转换完成后统一读回数据这样就能确保16路信号的采样时刻完全一致不存在通道间相位差。2.2 PL端管脚分配与约束要点把ADC接到PL端时有几个关键点必须处理好否则后面调试会非常痛苦。第一Bank电压匹配。PL端IO是按Bank分组的每个Bank的供电电压决定了IO电平标准。ADC如果工作在3.3V那连接它的PL Bank必须配置成3.3V电平否则轻则通信失败重则烧毁IO。接到核心板之前先确认每个Bank的供电范围再规划信号分配。第二IO约束。管脚分配需要在工程里显式声明。示意约束如下set_property PACKAGE_PIN AB12 [get_ports {adc_convst}] set_property IOSTANDARD LVCMOS33 [get_ports {adc_convst}] set_property PACKAGE_PIN AB10 [get_ports {adc_busy}] set_property IOSTANDARD LVCMOS33 [get_ports {adc_busy}]第三信号完整性。ADC数据总线如果很长建议在PL侧加输入寄存器减少时序压力。用并行总线的时候尤其要注意数据总线的建立保持时间AD7606的并行读时序相对宽松但PCB走线过长时还是要留余量。实测下来数据线尽量等长、远离时钟线和电源开关节点能省去后期一堆信号完整性问题。2.3 电源、时钟与地的处理电源是整个模拟采集系统最容易翻车的地方。ADC的AVCC和DVCC要分开供电中间加磁珠或电感隔离。模拟地AGND和数字地DGND单点相连连接点放在ADC芯片下方不要在PCB大面积铺铜时直接连成一片。核心板上的DCDC开关电源噪声如果通过地平面注入到模拟部分会导致采样值出现明显抖动。我在ADC电源脚位置并联了10uF0.1uF470pF的组合电容PCB走线时让电源线远离开关节点再增加一级LC滤波实测噪声改善非常明显。这个细节虽然基础但很多工程师为了图省事直接跳过最后采集数据里出现莫名其妙的杂散分量排查起来反而更费时间。时钟方面采样时钟信号的质量直接影响信噪比。推荐直接在PL端用MMCM/PLL产生采样脉冲不要把外部晶振直接接到ADC的转换触发脚。PL内部生成的时钟经过PLL整形后抖动更低采样时刻更稳定。后面调试ADC噪声问题时这个设计帮了大忙。3. BSP配置全流程引导程序、内核、设备树与根文件系统3.1 搞清楚BSP到底包含什么BSP这个词被很多人挂在嘴边但每个人说的可能不是同一个东西。在这类SoC平台上一个能用的BSP至少包含这几样交叉编译工具链、一级引导程序FSBL、U-Boot、Linux内核、设备树、根文件系统。它不是某一个文件而是一条完整的启动链。你可以把这条链理解成接力赛上电后FSBL最先起跑完成CPU最底层的初始化和DDR初始化然后把U-Boot加载进内存U-Boot负责加载Linux内核和设备树内核启动后挂载根文件系统最终才进入应用程序的世界。任何一个环节断了整条链就起不来。复旦微这套流程对有过Zynq开发经验的人来说非常友好基本思路一致只是具体工具名称和界面不同。如果你是从纯MCU开发转过来的建议先花两天时间专门熟悉这套启动链的概念理解每一级引导程序负责什么。3.2 从硬件工程导出HDFBSP的源头BSP配置的第一步不是写代码而是从FPGA硬件工程里导出一个硬件描述文件HDFHardware Definition File。HDF文件里包含了PS端的完整配置信息DDR型号和时序参数、MIO管脚分配、外设时钟频率、PL端IP的地址映射。可以把它理解成一份硬件体检报告所有引导程序都是根据这份报告生成的。硬件工程综合实现的步骤跟普通FPGA工程一样我在PL里加了AXI DMA IP、中断控制器和自定义ADC采集IP分配好地址后直接导出HDF。有一个容易踩的坑HDF里的DDR配置参数必须和核心板上实际使用的DDR颗粒完全一致。如果厂商给的BSP包是标准配置而你的核心板换用了不同品牌的DDR颗粒一定要在硬件工程里核对颗粒型号、位宽、时序参数否则U-Boot会在DDR初始化阶段卡死。后面调试章节会详细说这个坑的具体表现。3.3 编译FSBL与U-Boot有了HDF文件就可以生成FSBL了。在厂商配套的IDE里新建一个FSBL工程选择对应的模板编译后产出fsbl.elf。FSBL负责初始化CPU、初始化DDR、配置时钟然后把U-Boot镜像从启动介质读入DDR并跳转执行。这个过程看起来简单但DDR初始化参数不对、启动介质配置错误都会直接导致启动失败。U-Boot的编译也类似使用厂商提供的分支代码按目标板卡配置后交叉编译。配置时有几个要点启动设备类型QSPI Flash、SD卡还是eMMC对应不同的驱动配置和启动参数。串口波特率打印信息常用115200调试阶段不建议用过低的速率否则启动日志刷起来很费时间。环境变量包括bootcmd、bootargs等决定了内核镜像和设备树的加载顺序及地址。编译完成后把fsbl.elf、U-Boot和FPGA bitstream打包成BOOT.BIN文件。注意打包顺序和地址分配如果地址配置错误启动时同样会黑屏无输出。3.4 设备树与Linux内核适配Linux内核的编译相对成熟重点是设备树文件。设备树用来告诉内核硬件长什么样包括串口、网口、DMA、中断等信息。如果设备树里描述的硬件和实际硬件不一致内核启动后外设会工作异常甚至导致启动崩溃。一个典型的设备树节点示意如下/ { model FMQL45T900 Industrial Signal Processing Board; compatible fudan-micro,fmql45t900; chosen { bootargs consolettyPS0,115200 root/dev/mmcblk0p2 rw rootwait; }; }; axi_dma_0 { status okay; compatible xlnx,axi-dma-1.00.a; #dma-cells 1; dma-channels 1; xlnx,addrwidth 32; };这部分最容易踩的坑是外设地址和中断号与硬件工程不一致。建议直接把厂商SDK根据HDF生成的标准设备树拿来改而不是从零手写可以减少很多低级错误。内核配置时按下述选项打开基本就够用了DMA engine framework、AXI DMA驱动、SD卡/eMMC驱动、千兆以太网驱动、GPIO子系统。3.5 根文件系统的选择与构建根文件系统决定了你最终在Linux里能用什么工具。轻量应用我用Buildroot配置几个选项就能编出一个包含BusyBox、sshd、Python的最小文件系统非常适合这种工控板卡。如果需要更完整的包管理生态也可以用Yocto但编译时间会长很多前期调研阶段没必要。文件系统类型选ext4放在SD卡或eMMC的独立分区。启动时U-Boot会读取分区并挂载根文件系统。如果系统启动后登录不了或者执行命令报错先检查一下文件系统里是不是缺了关键设备节点和动态链接库。另外一个必须在早期确定的事情是启动介质。我总结了一个对比启动介质镜像存放优点适用场景QSPI FlashBOOT.BIN uImage dtb启动快、抗震动工业量产SD卡BOOT.BIN uImage dtb rootfs调试方便、可插拔开发调试eMMCBOOT.BIN uImage dtb rootfs容量大、速度快需要大量日志存储从SD卡启动调试最方便代码稳定后再把镜像固化到QSPI Flash或eMMC。固化的时候先在SD模式启动系统通过命令把Flash分区写好再切启动模式引脚到对应介质整个过程不需要额外的烧录器。4. 信号处理平台的核心实现PL采集逻辑与PS驱动配合4.1 PL端数据采集逻辑设计PL端的逻辑我把整个数据通路切成几段来写采样控制状态机、ADC数据接收模块、异步FIFO、AXI Stream输出模块。采样控制状态机负责产生CONVST脉冲触发ADC转换。ADC完成转换后拉高BUSY信号状态机等待BUSY下降沿然后按时序要求开始读数据。AD7606支持并行读和串行读我采用并行总线方式每片8通道数据分两次读取每次读4字节效率比串行高不少。ADC数据接收模块的核心是时序状态机。并行读模式下每一个读脉冲对应一次数据输出时序电平要求非常明确。这块的代码风格要写成三段式状态机保证时序边界清晰不要贪图代码简短而采用条件判断套条件判断的写法后面调试会非常麻烦。异步FIFO在这里承担跨时钟域转换任务。ADC采集时钟域和AXI总线时钟域是不同频率的数据不能直接从一个时钟域往另一个时钟域扔FIFO就是最稳妥的缓冲方案。FIFO深度我留了4096足够应对两片ADC一帧数据的突发写入。4.2 AXI DMA与数据搬运数据从PL进入DDR走的是AXI DMA。我配置成scatter-gather模式PL端采完一帧数据后DMA通过描述符列表中记录的地址把数据直接写入DDR指定区域写完以后产生一个中断通知PS端。这里有个关键理解DMA搬运过程完全不需要CPU参与。CPU要做的只是事先准备好描述符列表告诉DMA数据搬到哪里去然后等待中断发生。这比MCU开发里每个字节都由CPU搬的模型效率高一个量级。DMA中断信号接到PS端的中断控制器驱动里注册中断处理函数。中断处理器不需要做太多事情只是唤醒等待队列里的应用程序真正的数据搬运已经由DMA硬件完成了。初始化DMA的简化流程如下static int fmql_adc_probe(struct platform_device *pdev) { struct dma_chan *rx_chan; rx_chan dma_request_chan(pdev-dev, rx); if (IS_ERR(rx_chan)) return PTR_ERR(rx_chan); buf dma_alloc_coherent(pdev-dev, BUF_SIZE, dma_handle, GFP_KERNEL); if (!buf) { dma_release_channel(rx_chan); return -ENOMEM; } desc dmaengine_prep_dma_cyclic(rx_chan, dma_handle, BUF_SIZE, BUF_SIZE / 2, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dma_release_channel(rx_chan); return -ENOMEM; } desc-callback fmql_adc_dma_callback; dmaengine_submit(desc); dma_async_issue_pending(rx_chan); return 0; }代码里用到的cyclic模式是个工程技巧它把缓冲区切成两半DMA持续循环写入数据填满前半段触发一次中断填满后半段再触发一次。这就等于实现了乒乓缓冲一半缓冲区在采集新数据的同时另一半缓冲区里的旧数据可以被应用程序处理采集不中断处理不阻塞。4.3 PS端驱动与用户空间交互驱动层做的事情其实很收敛申请DMA缓冲区、配置DMA通道、注册中断回调、实现read或mmap接口供用户空间调用。中断回调里只需要做一件事记录当前是前半段还是后半段满了然后唤醒在等待数据的进程。真正的大块数据处理放到应用层或者内核工作队列不要在中断上下文里做耗时操作这是实时性的铁律。应用层最简单的方式是read()堵塞读取每次读取半帧数据。这种方式实现简单适合验证阶段。性能要求高的场景推荐用mmap直接把DMA缓冲区映射到用户空间省去一次copy_to_user的系统调用开销。做实时采集时还可以用sched_setscheduler把核心进程设置为实时调度策略降低调度延迟。4.4 应用层数据处理框架用户空间程序我按这样的结构组织一个数据读取线程负责从设备节点拿数据并放入环形缓冲区多个处理线程负责从环形缓冲区取数据做FFT、特征提取或波形显示另有一个通信线程负责通过Modbus TCP向外发送处理结果。线程间通信用无锁环形缓冲区实现通过原子变量维护读写指针实测在高采样率下没有数据覆盖问题。FFT我用的是arm_neon优化的库在Cortex-A9上性能足够。如果你需要做更高阶的信号处理比如小波变换、自适应滤波也可以在PL端做把PL当成一个可重配置的硬件加速器来用这是异构平台对付算力瓶颈的正确姿势。5. 调试实录三个典型问题背后的完整排查链路5.1 启动全程无打印U-Boot卡死的排查拿到核心板的第一步永远是烧写最小系统把BOOT.BIN写进SD卡插卡上电期待串口出打印。结果第一次上电串口完全没反应一个字符都没有。排查链路我按优先级排开检查核心板电源轨电压。拿万用表量各路电压是否正常重点看DDR的VTT电压如果VTT不对DDR初始化永远过不去。确认启动模式引脚。拨码开关或者焊盘电阻配置是否正确这个低级错误最先排除。检查BOOT.BIN烧写是否正确。在PC端用十六进制查看工具确认文件头没问题。核对HDF里的DDR配置。这个是最隐蔽的问题——核心板使用的DDR颗粒型号如果和默认配置不一致初始化参数就是错的U-Boot会一直卡死但串口没有任何错误提示。确认串口线是否接到了正确的调试串口。如果你接了非调试串口自然什么输出都没有但这条建议我放在最后因为多数人不会犯这个错。最终排查结果就是DDR颗粒时序参数不匹配。我找核心板厂商要到了颗粒的具体参数更新到硬件工程里重新导出HDF再生成FSBL一次通过。这个问题告诫我国产板卡到手第一件事就是把硬件手册和实际芯片型号核对一遍不要盲信BSP包里所谓的默认配置。5.2 DMA数据错位一次扎实的二分定位系统能启动、驱动能加载之后最折磨人的问题出现了DMA搬运到内存的数据每过一段时间就出现一段错位或重复看起来像是采集卡丢帧了。我没有直接改代码而是做了定位。先在PL端加逻辑分析仪核观察AXI Stream接口上的数据确认PL端输出的数据本身是否连续、tlast信号是否在正确位置再用一个固定pattern的测试源比如0xA5A5、递增斜坡替换真实ADC数据跑DMA传输对比收到的内存数据。两步下来PL侧的数据完全正确问题锁定在PS端驱动或DMA配置。这轮排查发现两个问题第一是DMA缓冲区的cache一致性没有处理。在带MMU的Linux系统里CPU的cache和DMA搬运之间存在可见性差异。如果用的是普通malloc内存再转换成物理地址传给DMACPU的cache里可能有旧数据DMA搬完数据后cache也没失效读到的就是旧值。正确做法是使用dma_alloc_coherent申请一致内存或者用dma_map_single配合dma_sync_for_cpu/dma_sync_for_device做同步。第二是DMA描述符的对齐要求。scatter-gather模式下描述符表的目标地址必须满足总线对齐要求我当时用了非对齐地址导致DMA在搬运过程中把数据写到了错误的位置。修改为对齐地址后数据完全正确。这个问题的教训很典型DMA调试一定要先确认数据到了DDR之后对不对再往上层查。处理cache一致性问题时不要试图用invalidate_cache这种土办法绕过框架老老实实用DMA API才是正道。5.3 ADC采样噪声异常偏大从时域到频谱信号链路调试到最后采集1kHz标准正弦波FFT一看除了主信号以外低频段和高频段都有大量杂散。这个现象很典型我按三步定位。第一步看时域波形确认噪声是叠加在信号上的毛刺还是整体漂移。用示波器直接测ADC输入端的信号注意探头地线要短否则会引入新的噪声。这一步能看出噪声是50Hz工频分量还是高频毛刺。第二步做频谱分析观察杂散频谱的具体位置。如果是50Hz及其整数倍谐波大概率是模拟地的接地方式有问题形成了地环路。如果杂散分散且高频大概率是DCDC开关电源的噪声耦合进了模拟部分。我们对模拟电源加了LC滤波把ADC基准电压芯片换成低纹波型号在ADC电源脚加去耦电容后高频噪声毛刺明显下降。第三步关注采样时钟抖动。采样时刻抖动会被当成噪声出现在频谱上表现为宽带噪声基底抬高。解决办法是在PL里给采样主时钟经过一级PLL整形对CONVST脉冲加同步打拍处理避免时钟域抖动直接传递到采样时刻。这一步做完噪声从几毫伏降到了0.2毫伏以内满足项目要求。6. 稳定性加固让平台在工业现场扛住7×24小时6.1 中断延迟与CPU亲和性调优工控场景下中断延迟直接关系到控制环路的实时性。Linux默认的中断处理可能在任何CPU上执行迁移过程中会有cache开销这对高频采集来说是多余的延迟。处理方法是给中断绑定CPU核心。我通过irq_set_affinity_hint把DMA中断、ADC中断绑定到CPU1业务处理进程也设置CPU亲和性在CPU1中断和数据处理的缓存热点就固定在同一个核上减少了cache-miss带来的抖动。实测中断响应时间从几十微秒的波动压缩到了几微秒级别。如果中断触发频率特别高还可以考虑用中断线程化配合实时调度策略把部分处理工作放到可被抢占的线程中避免把整个系统阻塞在中断上下文里。6.2 DMA双缓冲与环形缓冲区的配合虽然DMA cyclic模式自带乒乓效果但应用层做多线程处理时还是建议再维护一个环形缓冲区作为数据分发层。这样读取线程只负责把DMA数据写入环形缓冲区处理线程不需要关心DMA细节只从环形缓冲区取数。两者之间通过内存屏障保证可见性不依赖锁避免锁竞争。调试时发现如果处理速度跟不上采集速度数据会被覆盖。环形缓冲区里加一个丢帧计数器一旦发现丢帧立刻记录日志并报警这在工业现场非常重要——有些故障不是瞬时发生的有计数器就能事后追溯。6.3 看门狗、温度监测与数据校验7×24小时运行的系统必须考虑异常恢复。我在系统里部署了两级看门狗一级是PS端的硬件看门狗由一个内核线程周期性喂狗喂狗线程卡死则系统自动重启另一级在PL里实现如果PS端超过设定时间没有通过AXI寄存器写操作通知PLPL就把整个系统复位。双看门狗设计是为了防止PS端CPU死机在关中断状态导致软件看门狗也无法工作。温度监测方面驱动读取核心板上的温度传感器当温度超过阈值时降低CPU主频并报警。散热方面给核心板加了散热片机箱内保持风道整机连续跑了一周多核心温度稳定在合理范围内。数据校验上我在每帧数据的头部加了序列号和CRC32校验码。接收端如果发现序列号不连续或者CRC校验失败立刻标记这一帧异常并重新同步。这个设计在调试阶段就已经显露出价值能第一时间判断是DMA丢帧还是数据被破坏而不是等系统跑一段时间后出现各种奇怪故障再回头查。最后再分享一个调试技巧排查DMA或者采集链路的数据异常时先在PL里生成一个已知pattern作为测试源比如递增斜坡或者固定0xA5A5然后用它替换真实ADC数据跑完整个链路和应用收到的内存数据做逐字节对比。我靠这个办法在十分钟内就确认了硬件侧数据完全正确成功把问题焦点从PL转移到了驱动侧少走了一天弯路。这套平台从立项到跑通大约用了六周其中BSP适配和驱动调试占了大部分时间。如果你也在做类似项目我建议拿到板卡后先花至少两天把厂商提供的官方例程完整跑一遍确认工具链、启动链路、DMA通路都正常再动手改自己的逻辑。国产FPGA的软件生态这几年进步已经很大但和进口工具链相比仍有小坑多查release notes多和厂商FAE保持沟通能少踩不少坑。
返回列表