
最近公司接了个工业数据采集的活儿要求平台关键器件用国产型号我选来选去最终定了复旦微FMQL45T900核心板。板子到手后从零开始把BSP配置、内核裁剪、设备树调整、信号处理逻辑全部跑通前前后后踩了不少坑。这篇笔记不是产品介绍完全是实战记录适用于任何正在评估FMQL45T900或者在做国产化工控平台的朋友。先说说这板子解决什么问题工控现场信号种类多、干扰大、实时性要求高MCU往往算力不够纯FPGA做复杂应用又太费劲FMQL45T900这种ARMFPGA异构方案刚好卡在中间——FPGA负责高速采集和信号预处理ARM负责跑Linux做控制、通信和上位机交互两边通过片内总线交换数据整体可靠性和开发效率都能兼顾。1. 项目整体设计与选型思路1.1 FMQL45T900核心板到底是一块什么样的板子FMQL45T900不是普通单片机开发板它属于可编程SoC。芯片内部集成了ARM处理器子系统PS和FPGA可编程逻辑PL封装里既有硬核处理器又有可重新配置的逻辑阵列。PS端有两个ARM Cortex-A9核心主频能跑到900MHz上下带DDR控制、中断控制器、DMA以及UART、SPI、CAN、USB、Ethernet等常用外设PL端则对应型号里的“45T”大致是几十万逻辑单元规模还有不少DSP Slice和Block RAM做并行信号处理绰绰有余。我拿到的这块核心板板上已经配好了一片DDR3、一片QSPI Flash、电源管理电路和晶振用户只需要把电源和必要的启动配置接上就能跑起来。官方把这种形态叫做核心板是因为它把BSP里最容易出问题的DDR初始化、电源时序、Flash分区这些事都提前收敛了用户在外面做底板时不用再面对几百个BGA引脚和复杂的DDR走线。对工控项目来说这个特性非常友好因为硬件工程师可以把精力集中在信号前端和外设接口上。1.2 选型时对比过哪些方案工控信号处理平台常见的路线有这么几条MCUADC、DSPFPGA、纯FPGA、ARMFPGA SoC。我列个表给大家参考。方案优点缺点适合场景MCUADC开发简单成本低高速多通道同步采集吃力实时性不够低速监控、单通道采集DSPFPGA算法能力强时序控制灵活器件多板级设计复杂联调周期长雷达、音频等高算力算法纯FPGA并行性极强延时可控跑协议栈、界面和复杂控制太痛苦接口转换、高速数据流处理ARMFPGA SoC软硬件兼顾单芯片集成度高入门门槛比MCU高BSP和Linux需要投入时间工控信号处理、机器视觉、运动控制我最后选FMQL45T900核心原因有三点一是平台集成度高一个芯片把ARM、FPGA、DDR控制器和大量外设都包了底板上不用再串联一颗FPGA和一颗ARM处理器可靠性好很多二是硬实时任务和软实时任务能分开高速采样、数字滤波、触发检测放在PL参数配置、网络通信、数据上传放在PS两边并行跑互不拖累三是这颗芯片的资料和BSP已经比早期国产型号完善很多还有一个看得见摸得着的社区生态遇到问题不是完全抓瞎。1.3 整体架构软硬件怎么分工设计整个平台之前我先在纸上把数据流画了一遍这个习惯帮了大忙。现场传感器信号经过隔离放大和抗混叠滤波后进入ADC变成数字量ADC输出的并行或串行数据送到PLPL里做通道切换、数字滤波、触发判断、数据打包再通过AXI总线写入DDRPS端的Linux应用从DDR里取数据完成算法分析、界面显示和网络上传。反过来ARM也可以下发配置参数给PL比如滤波器系数、采样率、触发阈值这样整套系统就是双向可控的。这种架构的好处是PL不必关心应用层ARM也不必逐字节地去抓高速ADC数据两边通过DMA交换数据CPU占用率可以压得很低。实测下来8通道100kSPS的16位采集PS端CPU占用率只剩下不到百分之十剩下算力全部留给协议解析和业务逻辑这在纯MCU方案里很难做到。2. 核心板到系统硬件平台搭建的关键细节2.1 供电、时钟、复位与启动模式FMQL45T900核心板虽然集成了大部分电源但底板上仍然要提供输入电源和启动配置。我这边底板输入用的12V直流板上DCDC转换成5V后再给核心板供电核心板内部需要的0.95V内核、1.8V IO、1.5V DDR等电源都是核心板自己产生的外部不用管。但是有一点必须注意输入电源的纹波要控制在合理范围内工控现场电源干扰大输入端最好加共模电感和TVS管否则DDR初始化偶尔会失败现象就是Uboot起来后莫名其妙重启。时钟方面核心板PS端已经配置好了系统时钟外部不需要额外提供。但如果你在PL里做了高速接口比如千兆以太网、HDMI或者高速ADC那最好在底板上预留一个独立的差分时钟源不要直接靠内部PLL从低速时钟倍频上去抖动会比较大。复位设计我推荐用专门的复位芯片一个按键加一个电源监控比单纯RC复位靠谱尤其在低温环境下。启动模式这一块要重点检查。FMQL45T900支持从QSPI Flash、SD卡和JTAG启动具体是几个启动引脚的电平组合决定的。我习惯在底板上做一个三位拨码开关把启动模式引出来调试时拨到JTAG固化后拨到QSPI Flash这样就不用反复改电路。注意拨码开关状态必须在复位前确定否则可能进入错误的启动流程。2.2 信号前端与ADC接口设计既然是信号处理平台信号链路的设计质量直接决定整个系统的性能。这一路我选用了一款8通道同步采样ADC分辨率为16位最大采样率为200kSPS接口是SPI模式带8个独立的模拟输入通道。选同步采样是因为工控现场经常要对比多路相位关系如果通道间有采样延时差计算出来的相位信息就是错的。ADC前端的调理电路我用了一个两级结构第一级是输入保护和分压衰减第二级是二阶低通滤波。比如现场信号是±10VADC输入范围是±5V那前端就需要2:1的衰减如果信号很弱只有0到100mV反而要加放大运放选择低噪声精密运放。滤波器的截止频率要按采样定理设计我当时把采样率定在100kSPS信号最高频率按5kHz考虑低通截止设在10kHz附近这样既不会因为带外噪声混叠也不会把有用信号滤掉。ADC和FPGA之间的电气连接也要看仔细。我们选的这颗ADC输出是3.3V CMOS电平直接接到PL的普通IO bank即可。但要注意给这个bank供电为3.3V并且在PCB布局时让ADC尽量靠近核心板连接器走线保持等长避免高速采样时出现数据错位。如果用的是高速LVDS接口ADC还要检查PL bank是否支持LVDS标准通常需要接终端电阻。2.3 通信接口与电平匹配工控设备免不了和PLC、上位机、传感器通信所以底板上要预留足够的通信接口。FMQL45T900的PS端资源比较丰富我这边使用了以下几种接口一路RS485走UART外接485收发器两路CAN走PS端CAN控制器一路RS232走调试串口一路千兆以太网走PS端RGMII接口外接PHY芯片。大部分工控协议比如Modbus RTU、Modbus TCP、CANopen都能直接在这套硬件上实现。接口设计中容易忽略的是电平转换和隔离。RS485和CAN都是差分信号收发器本身就有电平转换作用但供电要匹配目前常用的收发器很多支持3.3V供电可以直接和PS端IO连接。以太网PHY则要注意RGMII接口的电压和时序FMQL45T900的PS端RGMII电平是1.8V还是3.3V务必查清楚不能想当然接3.3V否则会烧IO或不稳定。我这边PHY供电是3.3V但接口电平通过VCCIO配置到2.5V中间加了电平转换电路才解决。工控现场还经常要求隔离RS485和CAN我都在收发器后级加了隔离模块电源也要隔离不然地环路会把现场干扰串进核心板。隔离后波形边沿会有一些变缓但用在1Mbps以下的现场总线完全没问题实测通信非常稳定。3. BSP配置指南从零构建Linux运行环境3.1 BSP包组成和使用前的准备很多第一次接触核心板的人看到“BSP”三个字母就头大其实BSP就是板级支持包是一整套让Linux在指定硬件上跑起来的软件集合。复旦微FMQL45T900的BSP包里通常包含交叉编译工具链、U-Boot源码、Linux内核源码、设备树源码DTS、第一级引导程序FSBL源码、驱动程序、根文件系统构建脚本以及各种自动化编译脚本。它不是“一个驱动”或者“一个补丁”而是从上电到应用跑起来的全链路支持。拿到BSP后第一件事不是马上编译而是看README和版本发布说明。不同批次的核心板DDR容量、PHY型号、Flash大小可能不一样BSP版本和板级配置必须匹配。我第一次就是用了旧BSP导致U-Boot起来后DDR检测失败白白折腾了一天。厂家通常会提供一个“出厂默认方案”用这个方案能最大概率复现官方硬件环境。先把这个默认方案编译、烧录、跑起来再在这个基础上去改自己的设备树和驱动效率会高得多。3.2 交叉编译工具链和SDK装好之后做什么在PC端Linux环境下编译ARM平台程序需要交叉编译工具链。FMQL45T900的PS是Cortex-A9因此需要使用ARM 32位工具链通常是arm-linux-gnueabihf。把官方提供的工具链解压到/opt目录后要手动把它的bin目录加入PATH环境变量。我当时用export方式临时加入每次开终端都要重新设后来干脆写进.bashrc省心很多。加入后执行arm-linux-gnueabihf-gcc -v看到版本信息就说明工具链可用。工具链就绪后进入BSP目录通常会有顶层Makefile或脚本一次性完成U-Boot、内核、设备树的编译。如果你不熟悉建议先执行make help它会列出所有可用的目标。我以一个常见的BSP包为例一条命令可以编译出fsbl、u-boot.bin、uImage、dtb等文件。这个过程第一次会比较慢我在笔记本上编译了大约十几分钟属于正常现象。这里要说一个重要思路BSP不是一次编译就能一劳永逸随着你的硬件设计调整设备树要随时修改内核模块也要重编。所以强烈建议在开始前建立一个工作目录固定BSP版本记录所有修改点避免后面找不到自己改了哪里。3.3 U-Boot、内核和设备树联动配置FMQL45T900的上电启动流程是片上BootROM先加载FSBLFSBL主要完成DDR初始化和最基本的串口、时钟初始化然后跳转到U-BootU-Boot负责加载Linux内核和设备树举交给内核启动。整个过程中FSBL做硬件初始化U-Boot做引导内核负责操作系统层面的管理设备树告诉内核硬件长什么样。三者缺一不可。设备树是这套流程里最容易出问题的地方。它用DTS文件描述硬件的地址、中断、GPIO、时钟、外设引脚复用等属性。比如我要在PS端启用UART1就需要在dts里添加或确认对应的节点包含寄存器地址、中断号、时钟引用等。如果外设挂在PL侧还需要在dts里定义“reg”和“compatible”保证驱动能正确找到设备。下面是我设备树里一个SPI外设节点的简化示例spi0 { status okay; num-cs 1; adc_spi: ad76060 { compatible somevendor,ad7606; reg 0; spi-max-frequency 1000000; interrupt-parent gpio0; interrupts 54 2; }; };这个节点的含义是SPI0控制器使能在片选0上挂了一颗ADCSPI时钟最高1MHz用GPIO0的第54号作为触发中断中断触发类型是下降沿。编译设备树时BSP通常提供命令直接生成dtb文件。修改后记得把dtb拷到启动分区否则内核不会感知新硬件。3.4 根文件系统制作与固化内核起来后必须有一个根文件系统否则Linux跑不完启动流程。为了快速验证我用的是厂商预编译的buildroot根文件系统里面已经带好了busybox、SSH和基本工控组件。先把SD卡分成两个区第一个分区放BOOT.BIN、uImage和dtb第二个分区放根文件系统格式化成ext4然后把buildroot根文件系统解压进去。相关启动参数是U-Boot环境变量里的bootargs一个经典设置如下consolettyPS0,115200 root/dev/mmcblk0p2 rw rootwait含义是调试串口为ttyPS0波特率115200根文件系统在SD卡第二个分区。如果要从QSPI Flash启动则需要改成root/dev/mtdblock3或其他分区名并且要把内核、dtb、根文件系统都写入Flash。固化时注意烧写过程断电会变砖但可以重新进入JTAG模式启动后再次烧写所以先不要着急焊死启动模式选择至少留出JTAG接口。4. 信号处理平台从逻辑到应用的核心实现4.1 PL侧的数据采集逻辑在我的设计里ADC输出通过SPI接口连接到PLPL侧用一段状态机控制采样周期。状态机首先启动一次SPI传输然后把采样值写入一个环形FIFOFIFO另一端接到AXI-DMA的写通道由DMA自动把数据搬到DDR。为什么非要多一个FIFO因为ADC时钟域频率和AXI总线时钟域并不相同跨时钟域处理不能直接抓信号FIFO是解决数据同步最稳妥的办法。下面是一段简化后的Verilog采集控制模块它主要实现SPI的主模式和FIFO写使能控制module adc_spi_master ( input clk, input rst_n, output spi_cs_n, output spi_sclk, input spi_miso, output spi_mosi, input [31:0] tx_data, input start, output reg [15:0] rx_data, output reg done ); reg [5:0] bit_cnt; reg [15:0] shift_tx; reg [15:0] shift_rx; reg cs_n_r; always (posedge clk or negedge rst_n) begin if (!rst_n) begin bit_cnt 0; cs_n_r 1; done 0; end else if (start) begin shift_tx tx_data; bit_cnt 16; cs_n_r 0; done 0; end else if (bit_cnt 0) begin spi_sclk bit_cnt[0]; // 简化时钟生成 if (bit_cnt[0]) shift_rx {shift_rx[14:0], spi_miso}; bit_cnt bit_cnt - 1; end else begin cs_n_r 1; rx_data shift_rx; done 1; end end assign spi_cs_n cs_n_r; assign spi_mosi shift_tx[15]; endmodule这段代码只能展示核心思路真正工程上会用更高性能的SPI控制器或直接使用Xilinx/复旦微的IP核。核心理解是PL完成那些“周期严苛”的事情比如按精确采样率启动转换、把数据接连不断地搬到内存这些事如果交给ARM很难保证持续稳定。4.2 PS侧驱动与应用程序怎么配合PL把数据搬进DDR后PS端需要一个驱动或应用把数据从内存中读出来。最快捷的方法是使用UIOUserspace I/O或者直接mmap一段物理内存。我在项目里用了/mem接口先通过open打开设备再mmap到用户空间然后启动DMA搬运最后在循环里采集数据。这样省去写内核模块的时间原型验证阶段非常快。下面是一个简单的读取采样数据并做FFT分析的C语言骨架#define MMAP_PATH /dev/mem #define DATA_BASE 0x20000000 // PL侧DMA缓冲区地址 #define DATA_SIZE (8 * 1024 * 1024) unsigned char *map_base; int fd open(MMAP_PATH, O_RDWR | O_SYNC); map_base mmap(NULL, DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, DATA_BASE); while (running) { // 等待DMA传输完成可以通过中断或轮询标志位 process_samples((short *)map_base, sample_count); // 这里可以做滤波、FFT、阈值判断等 } munmap(map_base, DATA_SIZE); close(fd);这种方式的优点是直观、调试方便缺点是没有在中断层面做精准同步。如果后续对时序要求更高应该写一个简单的字符设备驱动注册中断在中断处理函数里唤醒等待队列让应用层更准时地拿到新数据。我当时先用mmap把算法跑通后来又封装了一层RingBuffer在用户空间自己维护读写指针配合DMA传输完成标志实际效果已经很稳定。4.3 关键的参数计算与性能验证系统性能设计不是拍脑袋参数都要算一遍。以我这套平台为例8通道采集每通道100kSPS分辨率16位总数据量是8乘以100000再乘以2字节约1.6MB/s。对于DDR和DMA来说这个速率很低带宽根本不是瓶颈但如果要实时处理算法比如128点实数FFT每帧处理时间就得控制在10ms以内。按ARM Cortex-A9跑400MHz的频率估算128点FFT大概几十微秒到一百多微秒完全来得及。滤波方面我用了一个32阶FIR低通滤波器截止频率5kHz系数由MATLAB用窗函数法算出来导出成C数组后放到PS端运算。这个滤波器在100kSPS采样率下每点运算量是32次乘加8通道加起来也就是不到1000次乘加每秒压力不大。如果换到高速ADC采样率几十MSPS乘法量就会指数上升那种场景必须把FIR放到PL里做用DSP Slice完全不是问题。性能验证我用的是信号发生器输入1kHz正弦波幅度4V偏置2V用这套平台采集后做FFT基波峰值出现位置正好在1000Hz信噪比在70dB以上说明整套信号链路从硬件到逻辑都没有明显问题。再对比原始ADC输入和FPGA输出波形的延时大概30微秒左右完全满足工控系统对实时性的要求。5. 常见问题与排查技巧实录5.1 启动失败、DDR不稳定现象是接上调试串口后没有任何输出或者U-Boot反复重启。我遇到这个问题时先检查电源用示波器看各轨电压上电顺序确认3.3V、1.8V、0.95V不是同时瞬变而是按核心板手册要求的顺序建立。如果电压正常再查启动模式配置确认拨码开关位置对应的是QSPI还是SD卡有时候单纯是拨码没拨对。如果U-Boot启动后DDR自检不过重点检查VTT电压和DDR终端电阻。核心板上的DDR初始化本来是工厂测好的但底板如果影响了核心板的供电洁净度DDR也会不稳定。此时可以用JTAG挂FSBL单步执行到DDR初始化函数观察打印信息是卡在哪个地址。还有一个土办法把系统降频到较低频率比如ARM降一半DDR降333MHz如果此时稳定说明时序裕量不够要去检查电源纹波和PCB走线。5.2 设备树和驱动加载错误Linux起来后发现某个外设驱动加载失败第一反应不是查驱动代码而是查设备树。设备树就是内核看硬件的“说明书”说明书里描述的资源地址、中断号、引脚复用只要有一个对不上驱动就会报No such device或忙等。我在调试SPI ADC时遇到过中断注册失败最后发现是我在设备树里写的中断号54和FSBL里配置的GPIO编号不一致两者差了一个偏移。排查这类问题建议仔细看dmesg的输出。内核启动时会打印设备树解析、驱动申请资源等关键信息凡是带fail、error、timeout的行都值得多看几遍。另外/sys/bus/platform/devices目录下列出的device如果驱动成功绑定通常会在对应设备节点下出现driver链接。设备树修改后要重新编译dtb并且确认启动时加载的是这个新dtb而不是还留在旧分区里的老文件。5.3 PL时序收敛问题PL逻辑如果比较复杂时序收敛会变成一个反复折腾的过程。我第一次把8通道数字滤波和DMA控制器都放在一个500MHz的时钟域下综合结果时序报告里setup违例一堆。后来把设计拆成两个时钟域ADC采样时钟100MHzAXI总线时钟150MHz中间用FIFO隔离问题立刻缓解了很多。时序不收敛的原因通常是组合逻辑路径太长或者没有写时钟约束。不要指望工具帮你自动推算出所有约束至少要在XDC或SDC文件里把主时钟、生成时钟和输入输出延时都声明清楚。比如输入ADC时钟是100MHz就要写create_clock -period 10.000 -name adc_clk然后对ADC输入引脚做set_input_delay约束。Vivado或复旦微配套工具时序报告里的WNS最差负裕量如果为正说明路径能收敛如果为负就要考虑把跨时钟域信号打拍、插入流水寄存器或者直接降频调整约束。5.4 用工具说话一套高效排查流程一个人闷头查问题很容易钻牛角尖后来我总结了一套“工具箱式”排查流程。硬件层用示波器和万用表涉及高速信号时用逻辑分析仪抓关键引脚波形软件层用串口终端、dmesg、/proc和sysfs节点逻辑层用Vivado或配套的调试工具做在线逻辑分析。这套组合拳基本能覆盖九成以上问题。举个例子一次ADC读回来的数据全是0xFF我第一反应是SPI时序问题于是用逻辑分析仪抓CS、SCLK、MISO信号结果发现MISO根本没有波形一路查下去才发现是底板上的ADC电源没焊好VCC悬空导致输出引脚电平被拉死。这种问题如果不用逻辑分析仪光看数据根本猜不到根因。排查时切记一次只改一个变量不要同时改设备树、驱动和FPGA逻辑否则问题定位会变得非常困难。项目复盘时我有一个很深的体会FMQL45T900这类异构SoC难点不在单点技术而在“软硬件协同”的节奏。BSP配置阶段最忌跳步骤老老实实按工具链、FSBL、U-Boot、内核、设备树、根文件系统的顺序走每一步都能验证再进入下一步后面才省心。信号处理部分尽量把实时数据处理放到PLARM只做控制和通信能少掉很多意外。最后再分享一个小技巧拿到核心板之后先把厂商提供的出厂镜像完整烧录跑一遍确认板卡硬件没有问题同时保留一份原始BSP包和工程备份后面哪怕改乱了也能快速恢复。这个习惯帮我省了至少两天时间。