1. 项目缘起与整体架构设计
1.1 为什么选择 LabVIEW + FlexRIO 这套组合
三年前我接手一个质谱仪信号采集与分析系统的项目,硬件端用的是红外探测器配高速ADC,要求对微弱离子流信号做实时峰值检测和飞行时间计算,数据率大概在40MS/s左右,通道数8路。最初考虑过纯FPGA方案,用VHDL从零写整套逻辑,但评估下来开发周期至少半年,而且后期算法迭代非常痛苦——每次改个滤波参数都要重新综合布线,调试周期太长。也考虑过纯上位机方案,用高速采集卡把数据全传到PC再做处理,但实测发现USB3.0的持续吞吐在8通道40MS/s下根本扛不住,丢包严重。
最终选定的方案是NI FlexRIO作为核心采集处理平台,LabVIEW FPGA做底层逻辑开发,LabVIEW RT跑实时操作系统做数据聚合和预处理,上位机用LabVIEW做界面和最终分析。这套组合的核心优势在于:FPGA端用LabVIEW图形化编程,开发效率比VHDL高出一个数量级,而且NI的FlexRIO模块自带高速ADC/DAC子卡,PXIe背板带宽足够,8通道40MS/s的数据在FPGA内部就能完成峰值检测和TOF计算,只把特征值传给RT端,数据量从每秒几个GB压缩到几MB,彻底解决了传输瓶颈。
提示:FlexRIO的FPGA芯片型号选择很关键。我们用的是PXIe-7975R,Kintex-7 410T,逻辑资源足够跑8通道并行处理。如果通道数少或者算法简单,7971R甚至7961R也能胜任,成本能省不少。
1.2 三个月时间线是怎么排的
很多人觉得三个月搭出质谱分析系统不可思议,其实关键在于把工作拆成可并行的模块,而不是串行开发。我的时间分配大致是这样的:
- 第1-2周:硬件选型、机箱配置、LabVIEW和FPGA模块安装调试,跑通第一个FPGA点灯程序,确认ADC子卡能正常采样。
- 第3-5周:FPGA端核心逻辑开发,包括ADC数据接收、数字滤波、峰值检测、TOF计算,这是最耗时的部分。
- 第6-7周:RT端和上位机开发,包括TCP通信、数据存储、质谱图显示、峰识别算法。
- 第8-10周:系统联调,用标准样品做校准,优化算法参数,解决丢包和时序问题。
- 第11-12周:稳定性测试、用户界面完善、文档整理。
这个排期的前提是FPGA逻辑不能从零写,必须大量复用NI自带的IP核和范例程序。比如ADC接口用NI提供的IO Module范例,滤波用FPGA MathScript RT模块,峰值检测参考了NI社区的一个开源项目。如果每个模块都自己造轮子,三个月绝对不够。
1.3 系统整体数据流设计
整个系统的数据流是这样的:离子打到探测器上产生电流脉冲,经过前置放大器转换成电压信号,送入FlexRIO的ADC子卡(我们用的是NI 5734,4通道40MS/s,14位)。FPGA内部对每路ADC数据做实时处理:先做数字带通滤波去掉工频干扰和高频噪声,然后用阈值加峰值检测算法找出有效脉冲,记录每个脉冲的到达时间和幅度,最后把这两个值打包通过DMA FIFO传给RT端。
RT端收到数据后做两件事:一是根据TOF计算离子的质荷比,二是把原始波形和特征值一起存到TDMS文件里。上位机通过TCP从RT端拉数据,实时显示质谱图,同时提供峰识别、同位素分析、浓度计算等功能。
这个架构的核心思想是边缘计算——把最耗时的信号处理放在FPGA里做,只传特征值,而不是把原始波形全传上来。实测下来,8通道40MS/s的原始数据率是2.56GB/s,而特征值数据率只有几MB/s,差了三个数量级。这个设计决策直接决定了系统能不能跑通。
2. FPGA端核心逻辑开发实操
2.1 ADC数据接收与时钟域处理
FlexRIO的ADC子卡通过LVDS接口把数据送到FPGA,时钟是随路时钟,需要做IDDR接收。NI的IO Module范例里已经提供了完整的LVDS接收逻辑,但有几个坑我踩过,这里重点说一下。
第一个坑是时钟域交叉。ADC送过来的数据是随路时钟域,而FPGA内部逻辑跑的是板载晶振时钟域,两者频率虽然标称一样,但实际有微小偏差,直接跨时钟域采样会偶发亚稳态。我的做法是用Xilinx的FIFO IP核做跨时钟域缓冲,写端用随路时钟,读端用内部时钟,FIFO深度设成512,实测下来从没出现过数据错位。
第二个坑是通道间同步。8个通道的ADC是独立工作的,虽然共用同一个触发信号,但LVDS走线长度不同会导致采样时刻有偏差。我在FPGA里给每个通道加了一个可编程延迟单元,用IDELAY原语实现,步进78ps,通过上位机校准可以精确对齐8个通道的采样时刻。这个功能在质谱分析里特别重要,因为TOF的精度直接取决于通道间的一致性。
-- LVDS接收核心逻辑片段(简化版) process(clk_adc) begin if rising_edge(clk_adc) then data_i_dly <= data_i; data_q_dly <= data_q; -- IDDR原语例化 iddr_inst : IDDR generic map (DDR_CLK_EDGE => "SAME_EDGE_PIPELINED") port map ( Q1 => data_rise, Q2 => data_fall, C => clk_adc, CE => '1', D => data_i_dly, R => '0', S => '0' ); end if; end process;注意:IDELAY的参考时钟必须是200MHz,这是Xilinx原语的硬性要求。如果参考时钟不对,延迟步进就不是78ps,校准会完全乱套。
2.2 数字滤波器的FPGA实现
质谱信号的特点是脉冲宽度窄(典型值10-50ns),幅度小(uV到mV级别),而且伴随大量白噪声和工频干扰。FPGA端必须做实时滤波,否则峰值检测的误判率会很高。
我用的方案是两级滤波:第一级是FIR带通滤波器,通带2MHz到15MHz,用MATLAB的FDATool设计系数,量化成16位定点数,在FPGA里用乘累加结构实现。第二级是移动平均滤波器,窗口长度8个采样点,用来平滑脉冲顶部,提高峰值定位精度。
FIR滤波器的阶数选择很关键。我最初用了64阶,阻带衰减能到60dB,但FPGA资源占用太高,8个通道并行跑下来时序根本收敛不了。后来降到32阶,阻带衰减降到45dB,但实测对峰值检测的影响很小,因为噪声主要分布在低频段,带通滤波器已经能滤掉大部分。资源占用从原来的78%降到52%,时序余量也够了。
-- 32阶FIR滤波器乘累加结构(简化) architecture rtl of fir_filter is type coeff_array is array (0 to 31) of signed(15 downto 0); constant coeff : coeff_array := ( x"0001", x"FFFE", x"0005", x"FFFA", ... -- 实际系数略 ); type delay_array is array (0 to 31) of signed(15 downto 0); signal delay_line : delay_array; signal acc : signed(35 downto 0); begin process(clk) begin if rising_edge(clk) then -- 移位寄存器 for i in 31 downto 1 loop delay_line(i) <= delay_line(i-1); end loop; delay_line(0) <= data_in; -- 乘累加 acc <= (others => '0'); for i in 0 to 31 loop acc <= acc + delay_line(i) * coeff(i); end loop; data_out <= acc(34 downto 19); -- 截位输出 end if; end process; end rtl;实测下来,32阶FIR在Kintex-7 410T上跑8通道并行,时钟约束到120MHz没问题,每个通道占用约1200个LUT和24个DSP48。如果资源紧张,可以考虑用半带滤波器做多相分解,能省一半DSP,但设计复杂度会高不少。
2.3 峰值检测与TOF计算逻辑
峰值检测是质谱分析的核心算法。我的实现思路是阈值触发加抛物线插值:当滤波后的信号超过预设阈值时,启动一个状态机,记录峰值位置和幅度,然后用峰值前后各两个采样点做抛物线拟合,精确计算峰顶位置。
阈值不是固定的,而是自适应的。FPGA里维护一个滑动窗口,计算最近1024个采样点的噪声均值和标准差,阈值设成均值加5倍标准差。这样即使基线漂移,也不会误触发。这个自适应逻辑用FPGA实现其实很简单,就是一个累加器加一个除法器,但效果比固定阈值好太多。
TOF计算需要两个时间戳:一个是离子产生的触发时刻,一个是峰值到达时刻。触发时刻由外部同步信号给出,FPGA收到触发后启动一个48位计数器,计数频率是ADC采样时钟的4倍(160MHz),分辨率6.25ns。峰值到达时锁存计数值,两者相减就是TOF。48位计数器在160MHz下能计到约20天不溢出,完全够用。
-- 峰值检测状态机(简化) type state_type is (IDLE, RISING, PEAK, FALLING); signal state : state_type := IDLE; signal peak_val : signed(15 downto 0); signal peak_time : unsigned(47 downto 0); signal counter : unsigned(47 downto 0); process(clk_160m) begin if rising_edge(clk_160m) then case state is when IDLE => if data_filt > threshold then state <= RISING; peak_val <= data_filt; peak_time <= counter; end if; when RISING => if data_filt > peak_val then peak_val <= data_filt; peak_time <= counter; else state <= PEAK; end if; when PEAK => -- 抛物线插值计算精确峰位 state <= FALLING; when FALLING => if data_filt < threshold then state <= IDLE; -- 输出峰值和TOF valid <= '1'; end if; end case; end if; end process;实操心得:抛物线插值的系数计算可以用移位和加法代替乘法,省DSP资源。具体做法是把插值公式展开成二次多项式,然后用Horner算法逐步计算,只需要两个乘法器。
2.4 DMA FIFO与数据打包传输
FPGA处理完的数据要通过DMA FIFO传给RT端。这里有个关键决策:传原始波形还是只传特征值。我最初想传原始波形,方便上位机做二次分析,但算了一下带宽:8通道40MS/s,16位采样,就是5.12Gbps,PXIe Gen2 x8的理论带宽是4GB/s,实际能到3GB/s左右,勉强够用但余量太小。而且RT端的CPU要处理这么大的数据流,实时性没法保证。
最终决定只传特征值:每个有效脉冲传4个值——通道号、TOF、峰值幅度、脉冲宽度。每个值16位,一共64位。质谱仪的脉冲率典型值是每秒10万个,8通道加起来80万,数据率是51.2Mbps,PXIe带宽的零头都不到。RT端处理起来毫无压力。
DMA FIFO的深度设成16384,位宽64位。FPGA端用非阻塞写入,RT端用DMA读取。实测下来,即使脉冲率突然增大10倍,FIFO也不会溢出,因为RT端的读取速度远大于写入速度。
// FPGA端DMA写入(LabVIEW FPGA代码片段) // 在单周期定时循环内 if (peak_valid) { DMA_FIFO_Write(Channel, TOF, Amplitude, Width); }注意:DMA FIFO的写入必须在单周期定时循环内完成,否则时序会乱。如果逻辑复杂,可以先用寄存器缓存,再在单周期循环里写入FIFO。
3. RT端与上位机开发要点
3.1 RT端数据聚合与TDMS存储
RT端跑的是NI Linux Real-Time系统,CPU是Intel Atom,性能一般但胜在稳定。RT端的主要任务有三个:从DMA FIFO读数据、计算质荷比、存TDMS文件。
质荷比的计算公式是 m/z = 2 * (TOF - t0)^2 / L^2 * e * V,其中t0是触发延迟,L是飞行管长度,V是加速电压,e是电子电荷。这些参数在RT端做成可配置的,用户可以根据实际硬件调整。计算本身很简单,但要注意浮点运算的性能。Atom CPU的浮点性能不强,如果每个脉冲都做一次完整计算,80万脉冲/秒会吃掉大量CPU。我的做法是查表加线性插值:预先算好TOF到m/z的映射表,存成数组,实际运行时只做查表和插值,CPU占用从45%降到8%。
TDMS存储是NI的二进制文件格式,写入速度很快,而且自带索引,后期用DIAdem或Python都能读。我按每小时一个文件切分,每个文件大概几百MB,方便管理。文件头里存了校准参数和实验条件,后期分析时不用再翻记录本。
// RT端主循环伪代码 while (running) { // 从DMA FIFO读取数据 elements_read = DMA_FIFO_Read(data_buffer, 1000, timeout); for (i = 0; i < elements_read; i++) { // 查表计算质荷比 mz = lookup_mz(data_buffer[i].TOF); // 写入TDMS TDMS_Write(mz, data_buffer[i].Amplitude, data_buffer[i].Width, data_buffer[i].Channel); } // 检查是否需要切换文件 if (elapsed_time > 3600) { TDMS_Close(); TDMS_Open(new_filename); } }实操心得:TDMS写入一定要用缓冲,不要每个点都写一次。我设了一个1000点的缓冲区,满了再一次性写入,磁盘IO次数减少99%,文件写入速度从20MB/s提升到200MB/s。
3.2 TCP通信与上位机数据交互
RT端和上位机之间用TCP通信,协议是自定义的简单二进制格式。上位机发送命令帧(比如开始采集、停止采集、设置参数),RT端返回数据帧(质谱图数据、状态信息)。这里的关键是信息量的查询和控制,很多新手搞不清楚怎么查TCP交互的数据量。
我的做法是在RT端维护两个64位计数器:发送字节数和接收字节数。上位机可以随时发命令查询这两个值,用来监控通信状态。实测下来,正常运行时数据帧的发送速率是2MB/s左右,命令帧的接收速率是几KB/s,完全在千兆以太网的承载范围内。
TCP通信的坑主要是粘包和断线重连。TCP是流式协议,没有消息边界,必须自己定义帧头帧尾。我用的是“帧头(0xAA55) + 长度(4字节) + 数据 + 校验(2字节)”的格式,接收端先找帧头,再读长度,再读数据,最后校验。断线重连用LabVIEW的TCP函数自带的重连机制,但要注意重连后要重新同步状态,否则会丢数据。
// 上位机TCP接收循环 while (connected) { // 读帧头 TCP_Read(2, header); if (header != 0xAA55) continue; // 读长度 TCP_Read(4, length); // 读数据 TCP_Read(length, payload); // 读校验 TCP_Read(2, checksum); // 校验并处理 if (verify_checksum(payload, checksum)) { process_payload(payload); } }3.3 质谱图显示与峰识别算法
上位机的质谱图显示用LabVIEW的XY Graph,但直接画80万个点会卡死。我的做法是降采样加分层显示:先把数据按m/z分箱,每个箱取最大值,这样80万个点降到8000个点,显示流畅。用户放大某个区域时,再动态加载该区域的原始数据,保证细节不丢失。
峰识别算法用的是连续小波变换加局部最大值搜索。小波变换对质谱峰的检测效果比简单阈值法好很多,特别是对重叠峰和弱峰。LabVIEW里没有现成的小波变换函数,我用MathScript节点调用了MATLAB的cwt函数,虽然效率低一点,但开发速度快。如果要做成产品,建议用C语言重写小波变换,性能能提升10倍以上。
同位素分析是质谱的另一个核心功能。我的实现是模板匹配:预先算好常见元素的同位素分布模板,然后跟实际峰群做互相关,相关系数最高的模板就是该峰的元素组成。这个算法在LabVIEW里用数组运算实现,速度很快,单次分析不到10ms。
4. 常见问题与排查技巧实录
4.1 FPGA编译错误与时序收敛问题
FPGA开发最头疼的就是编译错误和时序不收敛。我遇到过的典型问题有这几个:
问题一:时序不收敛,建立时间违例。最常见的原因是逻辑层级太深,组合逻辑路径太长。解决办法是插入流水线寄存器,把长路径打断。我最初写的FIR滤波器是纯组合逻辑的乘累加,32个乘法器串在一起,时序根本收敛不了。后来改成流水线结构,每4个乘法器加一级寄存器,时序余量从-2ns变成+1.5ns。
问题二:DSP48资源不够。Kintex-7 410T有1540个DSP48,8通道FIR滤波器每个通道24个,一共192个,看起来够用。但NI的IO Module范例本身也占了不少DSP,加上其他逻辑,实际可用的大概只有1200个。如果算法再复杂一点,DSP就不够了。解决办法是用时分复用:多个通道共用一个FIR滤波器,通过提高时钟频率来补偿。比如8通道共用1个滤波器,时钟跑到320MHz,相当于每个通道40MHz,完全够用。
问题三:编译时间太长。完整的FPGA编译(综合+实现+布线)要2-3小时,如果每次改一点都要等这么久,开发效率极低。我的做法是分模块编译:先用LabVIEW FPGA的“编译到仿真”功能验证逻辑正确性,再用“快速编译”只编译修改的模块,最后才做完整编译。这样大部分调试在几分钟内就能完成。
| 问题类型 | 典型现象 | 排查方法 | 解决方案 |
|---|---|---|---|
| 时序违例 | 编译报建立/保持时间错误 | 看时序报告,找关键路径 | 插入流水线寄存器 |
| 资源不足 | 编译报LUT/DSP/BRAM超限 | 看资源利用率报告 | 时分复用或降低并行度 |
| 编译超时 | 编译超过4小时未完成 | 看编译日志卡在哪一步 | 分模块编译,减少顶层逻辑 |
| 时钟域错误 | 仿真正常,实测数据错乱 | 检查跨时钟域信号 | 加FIFO或双寄存器同步 |
4.2 LabVIEW安装与版本兼容性坑
LabVIEW的版本兼容性是个大坑。FlexRIO的FPGA模块对LabVIEW版本有严格要求,比如LabVIEW 2015能用的FPGA模块,2018不一定能用,反过来也一样。我最初用LabVIEW 2018,结果发现FlexRIO的驱动只支持到2017,折腾了两天才换成2017版。
安装路径也有讲究。LabVIEW默认装在C盘,但FPGA编译会产生大量临时文件,C盘空间不够会导致编译失败。我的做法是把LabVIEW装在D盘,编译临时目录也设到D盘,C盘只放系统。另外,中文版LabVIEW和英文版在FPGA模块上有差异,中文版的某些函数在FPGA里不支持,建议FPGA开发用英文版,上位机可以用中文版。
注意:LabVIEW安装错误最常见的原因是.NET Framework版本不对。FlexRIO驱动要求.NET 4.6.2以上,如果系统里装的是4.5,安装会静默失败,没有任何提示。装之前先检查.NET版本。
4.3 数据丢包与同步问题排查
数据丢包是采集系统最常见的问题。我遇到过的丢包场景有三种:
场景一:DMA FIFO溢出。现象是RT端收到的数据不连续,TOF值跳变。原因是FPGA写入速度大于RT端读取速度。排查方法是看FIFO的溢出标志,如果溢出标志置位,说明确实溢出了。解决办法是增大FIFO深度,或者提高RT端读取优先级。我把FIFO深度从4096增加到16384,问题就解决了。
场景二:TCP丢包。现象是上位机显示的质谱图有缺失。原因是网络拥塞或接收缓冲区太小。排查方法是看TCP的接收队列长度,如果经常满,说明缓冲区不够。解决办法是增大TCP接收缓冲区,从默认的64KB增加到1MB。另外,上位机的接收循环优先级要设成最高,避免被其他任务抢占。
场景三:触发同步丢失。现象是TOF值整体偏移。原因是外部触发信号和FPGA内部计数器不同步。排查方法是用示波器同时看触发信号和FPGA的计数器清零信号,如果两者有延迟,就是同步问题。解决办法是在FPGA里加一个触发同步器,用双寄存器同步外部触发,消除亚稳态。
| 丢包类型 | 现象 | 排查工具 | 解决措施 |
|---|---|---|---|
| FIFO溢出 | 数据不连续,TOF跳变 | FIFO溢出标志 | 增大FIFO深度 |
| TCP丢包 | 质谱图缺失 | TCP接收队列长度 | 增大缓冲区 |
| 触发丢失 | TOF整体偏移 | 示波器 | 加触发同步器 |
| 时钟抖动 | 峰位漂移 | 频谱分析仪 | 用低抖动时钟源 |
4.4 质谱峰识别误判与校准技巧
峰识别误判主要有两种:假阳性(把噪声当成峰)和假阴性(漏掉弱峰)。假阳性的原因是阈值太低,解决办法是提高阈值或者加脉宽限制——真实峰的脉宽在10-50ns,噪声脉冲通常只有几个ns,加一个脉宽窗口就能滤掉大部分假阳性。
假阴性的原因是阈值太高或者滤波器把弱峰滤掉了。解决办法是降低阈值,同时用自适应滤波——根据信号强度动态调整滤波器带宽。强信号用窄带滤波,弱信号用宽带滤波,这样既能滤噪声又不丢弱峰。
校准是质谱分析的关键。我用的是两点校准法:用两个已知质荷比的标准品(比如m/z 100和m/z 1000)做校准,得到TOF到m/z的线性映射参数。实测下来,两点校准的精度能到0.1%,完全满足常规分析需求。如果要做高精度分析,可以用多点校准加二次拟合,精度能到0.01%。
实操心得:校准用的标准品要新鲜配制,放置超过一周的标准品会挥发或降解,导致校准偏差。我吃过这个亏,校准曲线漂了5%,查了半天才发现是标准品过期了。
5. 系统性能优化与扩展方向
5.1 从8通道扩展到16通道的改造方案
系统跑通后,用户提出要扩展到16通道。直接复制8通道逻辑的话,FPGA资源肯定不够。我的改造方案是时分复用加流水线重构:把16个通道分成两组,每组8个,共用一套FIR滤波器和峰值检测逻辑,通过提高时钟频率到240MHz来实现。这样资源占用只增加30%,而不是翻倍。
具体做法是:ADC数据先存入双端口BRAM,然后一个高速状态机轮流读取16个通道的数据,每个通道处理8个时钟周期,16个通道一共128个周期,在240MHz下就是533ns,远小于脉冲的最小间隔(典型值1us)。这样16个通道共用一套处理逻辑,资源占用从原来的52%增加到68%,还有余量。
5.2 算法加速:从LabVIEW FPGA到VHDL混合开发
LabVIEW FPGA开发效率高,但生成的逻辑效率不如手写VHDL。如果对性能有极致要求,可以把关键模块用VHDL重写,然后通过LabVIEW的CLIP节点调用。我试过把FIR滤波器用VHDL重写,资源占用从1200个LUT降到800个,时序余量从1.5ns提升到3ns。
CLIP节点的使用方法是:在VHDL里定义好实体和端口,用LabVIEW的CLIP向导生成接口文件,然后在FPGA VI里像调用子VI一样调用。需要注意的是,CLIP节点的时钟域必须和LabVIEW FPGA的时钟域一致,否则会出问题。另外,CLIP节点的仿真只能用ModelSim,不能用LabVIEW自带的仿真器。
5.3 数据后处理:Python与LabVIEW的协同
LabVIEW做界面和实时控制很擅长,但做数据后处理和机器学习就不行了。我的做法是LabVIEW负责采集和显示,Python负责后处理。TDMS文件用Python的nptdms库读取,然后用scipy做峰拟合,用sklearn做分类。这样各取所长,开发效率最高。
Python和LabVIEW的接口有两种:一种是文件交换,LabVIEW存TDMS,Python读TDMS;另一种是TCP直连,LabVIEW把数据实时发给Python,Python处理完再发回来。文件交换简单可靠,适合离线分析;TCP直连实时性好,适合在线分析。我两种都用过,实测下来文件交换更稳定,TCP直连偶尔会丢包。
# Python读取TDMS文件示例 from nptdms import TdmsFile import numpy as np tdms_file = TdmsFile.read("mass_spec_data.tdms") group = tdms_file["MassSpec"] mz = group["mz"][:] amplitude = group["amplitude"][:] # 峰识别 from scipy.signal import find_peaks peaks, properties = find_peaks(amplitude, height=100, distance=10) print(f"找到 {len(peaks)} 个峰")5.4 长期运行稳定性与散热处理
系统连续运行72小时后,出现了数据漂移和偶发死机。排查下来是散热问题:FlexRIO的FPGA芯片温度超过85度,触发了降频保护。解决办法是加装散热风扇,把机箱温度控制在40度以下,FPGA温度降到70度以下,问题就消失了。
长期运行的另一个问题是内存泄漏。LabVIEW的TCP函数如果不用了不关闭,会慢慢泄漏内存。我的做法是每个TCP连接用完后强制关闭,并且定期重启RT端的应用程序。实测下来,连续运行30天没问题。
注意:PXIe机箱的散热设计很重要。如果机箱风扇坏了,FPGA温度会在10分钟内飙升到90度以上,轻则降频,重则烧芯片。建议加装温度监控,超过80度就报警。
6. 项目复盘与个人体会
这套系统从立项到交付用了三个月,中间踩了不少坑,但也积累了很多经验。最大的体会是架构设计比编码实现重要得多。如果一开始没有把FPGA、RT、上位机的职责划分清楚,后期改起来会非常痛苦。我见过很多项目,FPGA端什么都做,RT端也什么都做,结果两边都做不好,数据流乱成一团。
另一个体会是不要重复造轮子。NI的范例程序和IP核虽然不一定完全符合需求,但改一改就能用,比从零写快得多。我的ADC接收逻辑、DMA FIFO、TCP通信都是基于NI范例改的,真正从零写的只有峰值检测和质谱计算这两块。
最后说一个容易被忽视的点:文档和注释。FPGA代码的注释尤其重要,因为图形化代码的可读性不如文本代码,过两个月自己都看不懂。我的做法是每个VI都写详细说明,每个状态机都画状态转移图,每个参数都标注单位和范围。这些文档在后期维护和交接时省了大量时间。
这个项目后续还可以扩展的方向包括:增加MS/MS功能(需要加碰撞池和二级TOF)、集成自动进样器控制、开发远程监控功能。如果读者有类似需求,建议先从单通道做起,跑通了再扩展,不要一上来就搞多通道,否则问题会指数级增加。