ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA测控程序设计与实现:从模块化框架到数据流实战

FPGA测控程序设计与实现:从模块化框架到数据流实战 做测控的兄弟对FPGA应该都不陌生我这些年经手的测控程序十个里有八个最终都落在FPGA上原因其实很朴素测控最看重实时性而FPGA的核心优势是并行执行、延迟可控。一提到FPGA里的测控程序大家关心的其实就三件事框架怎么搭、模块怎么划、数据流怎么走。这三件事处理好了一个复杂系统就能拆成小块的逻辑一层一层拼起来处理不好后面全是拼命的时序和调式改一版坏一版。这篇文章我就按自己的实际项目习惯从整体框架讲起再拆模块再重点聊数据流最后给一个能直接参考的工程样本和一组排查问题的经验希望能帮你少走点弯路。1. 测控程序整体框架先画好骨架再动手1.1 测控程序到底在解决什么问题测控程序本质上是把一个物理过程的“测量”和“控制”闭环起来。举几个最常见的场景工业现场的温度、压力采集伺服电机的电流环和速度环旋转编码器的位置读取以及实验室里的信号发生器、数据采集卡。这些场景有个共同点不仅要持续拿到数据还要在一定时间内做出控制和响应。偏差一点点系统性能就差很多甚至直接故障。FPGA为什么适合干这个因为它是硬件电路在跑不是软件任务在排队。ARM或者单片机会遇到中断嵌套、任务被抢占的问题一个优先级高的任务进来其他任务只能等。而FPGA里每个模块都是独立电路采样模块在处理当前数据的同时PWM模块可以照常输出通信模块也可以照常回传数据。换句话说测控里的“同时性”在FPGA上是天然满足的前提是你的框架没有把它破坏掉。所以设计测控程序第一步不是写代码是画框图。你要先想清楚整个系统有几个输入、几个输出哪些数据是必须闭环走的哪些数据只需要旁路回传。我习惯画一张A3纸的框图把所有模块用箭头连起来标清楚每个箭头的位宽和数据率。框架清楚了后面写代码就是填空。1.2 顶层框架采集、处理、输出、通信四步走我通常把FPGA里的测控程序分成四个功能区采集区、处理区、控制区、通信区。看起来简单但整个系统的复杂度基本就由这四个区之间的连接关系决定。采集区解决的是“怎么把外部物理量变成FPGA内部的数字信号”。常见接口包括SPI接口的ADC、并行的CMOS图像传感器、LVDS高速ADC、BISS-C编码器等。处理区负责对采样数据进行滤波、标定、单位换算、特征提取还会跑PID、卡尔曼滤波这类控制算法。控制区把处理出来的指令变成真实的执行信号比如PWM占空比、DA输出电压、数字开关量。通信区负责和上位机或者MCU打交道常用UART、SPI、FMC并行总线、以太网或PCIe。在实际项目里这四个区不一定是物理上独立的部分但逻辑上一定要分清楚。我把这个框架简化成一个口诀一收、一算、一控、一传。收指的是采集算是处理控是输出传是通信。核心数据流永远是“收 - 算 - 控”通信只是旁路负责监控和参数下发不要让它插在控制主环路的中间否则一旦通信拥塞整个控制周期都会受到牵连。1.3 为什么模块化在FPGA测控里更重要软件工程的模块化是为了代码复用和维护FPGA的模块化目的更直接让每一个功能块都能独立仿真、独立验证、独立替换。一个测控系统往往会经历多次硬件改版比如换一颗更快的ADC芯片或者把电机驱动器从普通的H桥换成带使能控制的智能驱动这时候如果采集逻辑和控制逻辑混在一个巨大的always块里改动一个参数就可能影响另一条路径的时序。硬件模块化要“高内聚、低耦合”。内聚的意思是每个模块只干一件事比如UART接收模块就只负责把串行数据变成并行字节解析帧格式的事情放到业务层去做。耦合的意思是模块之间只通过明确的接口通信最常用的就是时钟、复位、valid、ready和data。现在很多工程采用AXI-Stream协议做模块互联valid和ready两个信号就解决了上游和下游的握手机制非常方便。我遇到过很多半路接手的老项目最大的问题不是代码难懂而是没有边界。顶层的always块里有ADC时钟生成、FIFO读写、PID计算、PWM比较甚至UART的波特率分频也写在里面。这种设计在仿真时可能没问题一旦约束紧张时序收敛很难做出问题也不知道从哪里查。模块化不是形式主义是给后续调试留一条活路。2. 核心模块拆解每个功能块的接口与实现思路2.1 采集前端ADC、LVDS和BISS-C这些接口怎么接采集前端是所有控制闭环的数据源头这个模块一旦出错后面再好的滤波和控制算法都是白搭。先说最常见的SPI接口ADC。现在很多ADC芯片用4线SPIsclk是时钟cs_n是片选mosi是配置输入miso是转换结果输出。FPGA侧就是写一个状态机产生片选和时钟脉冲在正确的边沿把转换结果移位进来。这里有个常见的坑很多ADC要求数据在sclk下降沿输出在sclk上升沿稳定采样。如果你对相位理解错了读回来的数据可能会整体错位表现就是数值像噪声一样跳变。如果测控系统用的是高速ADC比如125MSPS以上的采样率那么SPI通常就不够用了必须用LVDS接口。LVDS是差分信号抗干扰能力强数据速率可以到几百Mbps。FPGA内部要把串行的LVDS比特流转成并行数据这就要用到ISERDES或者Gemini接口这类高速串并转换原语。同时输入时钟也要从LVDS时钟接收器进来然后通过MMCM/PLL生成采样时钟。这一步的时序约束特别重要走线长度、idelay参数、比特顺序都影响最后的数据对齐。我常用的硬提示是宁可先花一小时查LVDS datasheet的时序图也不要上板后拿ILA瞎猜。另一种典型接口是BISS-C主要用在伺服电机编码器上。它是一条双向的串行同步总线FPGA做主站通过MA线发时钟通过SLO线读数据。BISS-C协议里包含位置信息、状态位、CRC校验还能支持寄存器读写。FPGA实现BISS-C主站的关键是精确控制MA时钟频率并按时序要求切换数据方向。一个常见问题如果编码器线缆比较长返回的数据边沿变差就需要在接收端加打拍延迟或调整采样点。我的经验是先跑一个“读寄存器”的命令做链路自检确认CRC无误后再进位置闭环。采集模块的接口设计也值得多说一句。我给采集模块定的标准输出一般是这样的module adc_ltc2324_if #( parameter WIDTH 16 )( input logic clk_adc, // 来自MMCM的采样时钟 input logic rst_n, input logic [3:0] adc_ser, // LVDS串行数据输入 output logic [WIDTH-1:0] sample_data, output logic sample_valid // 单周期有效脉冲 );sample_valid半年是关键后面的处理模块只需要采样sample_data和sample_valid不需要关心ADC是哪颗芯片、是SPI还是LVDS。这样封装的好处很明显以后换ADC芯片只改这个模块。2.2 信号处理链从简单滤波到卡尔曼滤波落FPGA原始采样数据基本都不能直接用必须先滤波。最简单的是滑动平均滤波原理就是保存最近N个采样点去掉最旧的一个、加入最新一个这样一直保持一个累加和。FPGA里可以用一个移位寄存器加一个累加器实现但是如果N比较大累加和需要较宽的位宽否则会溢出。我的做法是定义一个满量程的位宽做饱和处理然后用移位代替除法或者把除法放到后面模块处理。如果对频率响应有要求就要用FIR滤波器。FPGA里的FIR最常用的是乘累加结构比如一个32阶的FIR每出一个点要32次乘加。如果采样率是几千赫兹时钟是100MHz那完全可以用一个乘法器分时复用跑32个周期如果采样率到了几十MHz可能就要用多个乘法器并行处理。工程上选择直接使用Xilinx或Intel自带的FIR Compiler参数可以先用MATLAB的FDATool算好再导出系数。再说卡尔曼滤波。很多人一听卡尔曼滤波就头大实际上在FPGA里实现并不复杂它就是一个预测加更新的两步循环。典型应用是传感器融合比如编码器测位置、陀螺仪测角速度两个数据源都有噪声卡尔曼滤波可以估计出比单一传感器更稳的状态。FPGA实现卡尔曼滤波时矩阵运算和浮点运算是主要挑战。如果状态变量只有两三个完全可以用状态机写定点运算控制周期做到几十微秒没问题。如果状态维度偏高建议先用MATLAB把浮点模型仿真好再把参数转成定点Q格式最后在FPGA里实现。卡尔曼滤波模块的接口也可以统一成下面的样子module kalman_filter #( parameter Q0 16h0100, parameter R0 16h1000 )( input logic clk, input logic rst_n, input logic [15:0] measure, input logic measure_valid, output logic [15:0] state_out, output logic state_valid );这种接口的好处是滤波模块不关心数据从哪来只要输入有效就更新一次。我在实际项目中通常会在卡尔曼滤波前加一个简单的限幅判断把明显异常的数据先剔除否则一个坏采样会把整个估计状态带偏。2.3 控制输出端PWM、DA和电机驱动模块的衔接采集和处理之后控制器会输出一个控制量它得变成执行机构看得懂的信号。最常用的输出是PWM。FPGA里产生PWM只需要一个自由运行的计数器和比较器计数器从0数到周期值当计数值小于占空比比较值时输出高电平否则输出低电平。听起来简单但细节很多电机驱动不能直接让PWM输出乱跳不然会产生大量毛刺和EMI因此输出级一定要用寄存器打一拍或者用ODDR原语直接输出到引脚避免组合逻辑出现毛刺。PWM和电机驱动模块之间还涉及方向和使能信号。比如TB6612这种电机驱动模块它需要IN1、IN2和PWM三个信号来控制正转、反转和速度。FPGA侧只需要提供一个方向信号和一个占空比信号再由一小段逻辑映射成IN1、IN2和PWM的组合。我写PWM模块的时候一般会把最小占空比和最大占空比做限制防止输出为0或100%时驱动器状态异常。还要注意死区时间特别是桥式驱动上下桥臂不能同时导通否则会烧器件。DA模块也是一类重要输出。比如AD9833是一个通过SPI接口配置的波形发生器FPGA要发送频率和相位寄存器来产生正弦波、三角波等信号。如果你的测控系统需要一个模拟激励源这类DDS芯片很常用。FPGA侧就是一个SPI主机发送寄存器配置值就可以。关键点是AD9833的寄存器写入顺序和写保护位不看datasheet贸然写的话输出频率经常不对。PWM模块的接口我一般这样设计module pwm_gen #( parameter COUNTER_WIDTH 16 )( input logic clk, input logic rst_n, input logic [COUNTER_WIDTH-1:0] duty, output logic pwm_out );内部就是一个计数器duty作为比较阈值。但我在比较之前一定会先做限幅比如限制在0和周期值-1之间。这个操作看起来多余实际能避免很多边界条件导致输出一直拉高或拉低的问题。2.4 通信回传UART、SPI、FMC与PCIe的取舍测控系统再强大如果上位机拿不到数据、下不了参数那也是白搭。通信模块的选择取决于数据量、实时性、开发周期和硬件接口条件。UART适合低速率监控比如上报设备状态、接收参数配置。波特率一般115200或者460800简单可靠但带宽很低不能承载高速连续波形。SPI适合PCB板上短距离高速通信很多FPGA和MCU之间就通过SPI交换数据。FMC是另一种选择比如STM32H743通过FMC接口和FPGA通信时FPGA这边其实可以把自己伪装成一个SRAM或者NOR Flash器件。MCU像读写普通存储器一样读写FPGA的地址空间FPGA在总线侧解码地址与读写信号完成数据交换。这个方案并行度很高一条总线可以映射多个寄存器很适合做高速数据交互。如果上位机是PC要用FPGA传大量波形数据那PCIe几乎是绕不开的。PCIe接口的FPGA实现通常用Xilinx的XDMA IP或者Intel的PCIe IPFPGA作为Endpoint上电后主机就可以枚举到设备。PCIe的好处是带宽大、延迟低、生态成熟但工程复杂度也高需要处理MSI中断、DMA描述符、BAR空间等。很多项目会用“FPGA PCIe DMA”的组合来传高速AD数据或图像数据。通信方式选择还有一个原则协议解析和业务逻辑分离。UART接收到一帧数据接收模块只负责把字节收进来用valid信号通知业务层业务层再解析帧头、命令、CRC。这样通信波特率变了或者协议升级了只会改协议解析部分不会动到核心控制环路。下面是常用接口的对比表接口典型带宽开发复杂度适用场景UART1Mbps以内低状态监控、参数配置SPI10Mbps左右低板级芯片间通信FMC并行100MB/s以上中FPGA与MCU高速互操作PCIe Gen2 x2约1GB/s高上位机连续采数据以太网10M/100M/1G中高分布式测控系统3. 数据流设计决定系统实时性的命脉3.1 单条主数据流采集-处理-输出-上报框架和模块都定了接下来最关键的就是把数据流画清楚。我常用的主数据流是传感器 - 采集模块 - FIFO - 滤波/处理模块 - PID/控制模块 - 输出模块同时处理结果和状态通过通信模块上报上位机。这条主链路上每级模块都只关心自己的输入输出几乎是流水线式的推进。举个例子一个电机电流闭环系统。ADC以50kHz采样率采集电流每20us出一次采样数据。如果FPGA系统时钟100MHz那么每个采样周期内有2000个时钟周期足够完成滤波、PID计算和PWM更新。延迟分配可能是ADC接口模块需要20个周期FIR滤波需要200个周期PID计算需要100个周期PWM比较器1个周期合计不超过1000个周期。也就是说在下一个采样点到来之前控制量已经更新完了。这个“延迟预算”在项目初期就要算清楚不然到了后期发现延迟太大要么提高时钟要么重新调整流水线。数据流图虽然好画但实际数据流里不止一条路。除了主环路上的数据还有参数配置数据、状态上报数据、异常报警数据。我建议把所有旁路数据都别放在主环路上而是通过寄存器接口由通信模块写入。比如PID参数Kp、Ki、Kd上位机通过UART接收到之后只是更新一组寄存器PID模块在下一个控制周期读到新参数这样控制系统不会被通信拖慢。3.2 跨时钟域设计握手、FIFO和乒乓缓存测控系统里存在多个时钟域几乎是必然的。ADC有采样时钟FPGA有系统时钟通信模块又可能是以太网时钟或PCIe参考时钟。数据从一个时钟域进入另一个时钟域最危险的是亚稳态和多bit数据不一致。我见过不少工程功能仿真一切正常上板之后数据偶尔跳变最后查出来就是跨时钟域没处理好。单bit信号的跨时钟域处理常用两级同步器也就是用两个寄存器连续打两拍。多bit数据跨时钟域不能用简单打拍因为多个bit可能在不同时刻变化采样时可能采到半新半旧的数据。这种情况用异步FIFO最稳。现代FPGA都自带FIFO IP可以配置读时钟和写时钟不同而且能输出空满 flag。开发时只需要把写端数据、写时钟、写使能接上去读端数据通过读时钟读出来空满作为反压信号。乒乓缓存也是很实用的结构尤其在图像采集和连续波形采集中。假设采集模块正在往A缓存写数据处理模块在同时读B缓存等A写满之后交换角色处理模块读A采集模块写B。这样采集和处理并行进行不用等对方完成。代价是双倍存储。跨时钟域设计里最容易忽略的是FIFO溢出。如果读速率低于写速率且没有反压机制FIFO满之后继续写就会丢数据。我的习惯是在每个FIFO的写端都检查full信号一旦满就通过valid信号暂停上游模块的推进。必要的时候还要在数据里加帧号或序列号上位机如果发现序列号不连续就知道丢了数据。3.3 数据流中的时序与资源平衡数据流的宽度和数据率决定了系统资源需求。举个例子一个8通道的数据采集系统每通道采样率1MSPS每通道16bit总数据率就是8 * 1M * 16bps 128Mbps。如果FPGA系统时钟是100MHz数据总线32bit那么每拍能传32bit照理说吞吐完全够。但是在实际设计里不能只看平均吞吐还要看瞬时峰值和模块的并行度。假设处理模块只有一个FIR滤波器滤波器的计算能力是每32拍输出一个结果也就是吞吐率大约100MHz/32 3.125Msps。如果输入是8个通道每个1Msps峰值时候有8个数据等一个滤波器这个滤波器会塞车。两种方案一是把时钟提高到400MHz用更高的计算密度跑同一个滤波器二是复制8份滤波器每个通道独立处理。前者省逻辑但时序压力大后者逻辑资源翻倍时序和延迟更好。没有绝对优劣只能根据目标FPGA的资源余量和控制周期要求来定。数据流的握手协议也很重要。我建议在所有处理模块之间采用valid/ready接口。上游在valid拉高时表示数据有效下游在ready拉高时表示可以接收只有当两个信号同时为高这一拍才完成传输。这种机制天然处理了背压下游慢的时候上游会等待。虽然增加了一点点逻辑但让模块之间解耦了调试时看着波形更容易定位卡点。4. 工程实现参考一个简化测控链路示例4.1 代码结构设计前面讲了很多框架和数据流这部分我给出一个简化但完整的工程参考。项目目标8路SPI ADC采集16bit分辨率1kSa/s采样率每路数据经过滑动平均滤波后做PID闭环输出PWM控制一路小型直流电机同时通过UART上报数据和接收PID参数。这个系统不大但麻雀虽小五脏俱全。代码目录我一般这样组织rtl/ top.sv adc_spi.sv async_fifo_wrapper.sv moving_avg.sv pid_ctrl.sv pwm_gen.sv uart_tx.sv uart_rx.sv sim/ tb_top.sv tb_adc.sv文件命名和模块名保持一致这个习惯看起来不起眼实际上对多人协作特别重要。顶层只做例化不做具体逻辑这样整个系统的结构一眼能看明白。每个模块内部还要保留一个寄存器配置端口方便调试时手动控制内部状态。4.2 顶层模块与接口示例顶层接口包括系统时钟复位、ADC SPI接口、PWM输出、UART接口。代码比完整产品简单但已经能看出框架的轮廓module top #( parameter ADC_WIDTH 16, parameter N_CH 8 )( input logic clk_50m, input logic rst_n, output logic adc_sclk, output logic adc_mosi, input logic adc_miso, output logic [N_CH-1:0] adc_cs_n, output logic motor_pwm, output logic motor_dir, output logic uart_tx, input logic uart_rx ); logic [ADC_WIDTH-1:0] adc_data; logic adc_valid; logic [ADC_WIDTH-1:0] filtered_data; logic filtered_valid; logic [ADC_WIDTH-1:0] pid_duty; logic pid_valid; adc_spi #( .DATA_WIDTH(ADC_WIDTH) ) u_adc_spi ( .clk (clk_50m), .rst_n (rst_n), .adc_sclk(adc_sclk), .adc_mosi(adc_mosi), .adc_miso(adc_miso), .adc_cs_n(adc_cs_n), .data (adc_data), .valid (adc_valid) ); moving_avg #( .DATA_WIDTH(ADC_WIDTH), .AVG_N (16) ) u_moving_avg ( .clk (clk_50m), .rst_n (rst_n), .din (adc_data), .din_valid (adc_valid), .dout (filtered_data), .dout_valid(filtered_valid) ); pid_ctrl #( .DATA_WIDTH(ADC_WIDTH) ) u_pid_ctrl ( .clk (clk_50m), .rst_n (rst_n), .setpoint (16h8000), // 目标值 .feedback (filtered_data), .fb_valid (filtered_valid), .output_val (pid_duty), .output_valid(pid_valid) ); pwm_gen u_pwm_gen ( .clk (clk_50m), .rst_n (rst_n), .duty (pid_duty[15:1]), .pwm_out(motor_pwm) ); endmodule这个顶层其实还缺UART上报这里为了篇幅没有全部例化。实际上UART模块和PID模块一样只需要把自己的数据接到一个发送FIFO或者状态寄存器上就能独立工作。4.3 关键内部模块实现思路PID控制器的FPGA实现可以很规整。输入是设定值和反馈值误差e setpoint - feedback。比例项直接乘Kp积分项累加后乘Ki微分项用最近两次误差差乘Kd。三个项相加后限幅输出。工程上必须处理积分饱和也就是只要输出已经到限幅值就不让积分继续累加否则会出现系统超调后很久都拉不回来。我用Verilog表示控制核心时通常这样写// 伪代码实际需要补充位宽和时钟控制 if (pid_valid) begin err setpoint - feedback; if (pid_saturated) begin integ integ; end else begin integ integ err; end output_val Kp * err Ki * integ Kd * (err - err_prev); err_prev err; end实际工程里Kp、Ki、Kd都是可配置寄存器而且乘法结果要按Q格式截位避免中间位宽爆炸。截位要注意采用饱和而不是简单的丢弃高位否则控制量在边界上会突然跳变。滑动平均模块相比之下简单很多。维护一个累加器每个新数据到来时从累加器中减去最老的数据再加上新数据得到新的窗口和。最老数据存在一个移位寄存器里深度等于平均窗口。这个方法资源消耗很低适合做ADC预处理。但要注意如果窗口是2的幂次除法直接右移如果不是就要用除法器或者干脆把结果往后传由下游决定怎么缩放。4.4 FPGA选型与资源评估选型时不要只盯着FPGA厂商或者型号参数先算算资源账。刚才那个例子滑动平均加PID占用的资源非常少一个普通Artix-7或者Cyclone 10就绰绰有余。但如果整个系统换成8路128MHz LVDS ADC且每路都要跑32阶FIR那需要的DSP和Block RAM会明显上升。32阶FIR每路大约需要16个DSP乘法器8路就是128个DSP这就直接排除了很多小芯片。还要考虑Bank电压和IO标准。ADC的LVDS接口一般需要1.8V或2.5V的Bank电压如果FPGA对应Bank的VCCO接错了采样完全无法工作。BISS-C或RS485这类接口通常要外接收发器芯片FPGA侧走LVCMOS或LVDS即可PHY芯片再转换。PCIe则需要选择带高速串行收发器的FPGA同时供电和参考时钟都要仔细规划。我习惯在选型表里列一栏“保守资源占用”LUT、FF、BRAM、DSP使用率都控制在70%以内给布局布线留出余量否则时序收敛很难受。5. 调试方法与常见坑实录5.1 上板前的仿真要点测控程序千万不能一上来就上板。我先做模块级仿真再做系统级仿真。模块级仿真里ADC模型要能模拟真实的接口时序比如每1000个时钟周期给一次采样结果。PID模块需要给一个阶跃输入观察输出是否收敛到设定值、有没有超调。滑动平均模块则要检查窗口边界前几个点输出是否正确补齐。系统级仿真会把所有模块例化在一起用虚拟的ADC数据源来替换真实ADC检查整条链路的valid信号是否始终推进会不会出现某一步长期等待。跨时钟域验证尤其重要。在testbench里要制造两个独立的时钟一个给ADC域一个给系统域让它们频率和相位都不固定模拟真实FIFO行为。我还会在关键valid信号上写SystemVerilog断言比如检查FIFO满时写使能不能拉高PID输出永远在限幅范围内。仿真跑到十万周期无错误再考虑上板。5.2 实测中容易踩的坑上板之后最常见的问题我一个个说。第一个坑是ADC读回来的数据全0或者全1。大概率不是ADC坏了而是SPI模式或者采样边沿不对。很多ADC在datasheet里写明数据在sclk下降沿更新在上升沿采样你反着做就会导致数据移位。还有可能是片选信号低有效时间不够ADC还没来得及完成转换就被你读走了。这时候用逻辑分析仪看SPI四根线逗号对比datasheet时序图一眼能看出哪一拍有问题。第二个坑是数据偶发跳变。这种问题最容易让人抓狂。现象是大部分数据正常偶尔跳一个很大的值。我见过三类原因一是跨时钟FIFO溢出数据被丢掉下游把旧数据当新数据用二是ADC的采样时钟不够干净导致偶尔采到亚稳态三是有控制逻辑内部跨时钟域的多bit信号没有同步读到了半新半旧的数据。排查顺序一般先看FIFO的空满信号是否频繁拉起再看ADC采样时钟波形最后查跨时钟域路径。第三个坑是PWM输出毛刺。PWM比较器的输出如果直接从组合逻辑引出很容易在计数边界产生毛刺。解决办法就是在输出端用寄存器打一拍或者用ODDR原语把信号送到IOB。另一个容易被忽视的是PWM抖动出现在duty值来自跨时钟域时没有经过同步。duty偶尔变化一两个Lsb电机就能听得出来。第四个坑是闭环发散。控制环一旦发散第一反应是检查PID符号。反馈接反了是市面上最常见的低级错误。其次检查控制周期是否太慢如果每隔很长时间才更新一次PWM系统会产生大延迟也会振荡。最后看积分限幅和输出限幅给得太狠会造成积分饱和导致超调和振荡。第五个坑是UART乱码。一般就是波特率分频没算对。板子上的晶振不一定是准确的50MHz可能实际是49.152MHz或者其他频率按50M去分频就会产生累积误差。正确的做法是根据实际时钟频率重新计算分频值配置串口助手按同一波特率。5.3 问题排查速查表把上面这些经验整理成一张速查表方便现场排查现象可能原因排查方法解决方向ADC读回全0/全1SPI模式错误、采样边沿错逻辑分析仪对比datasheet时序调整时钟极性和相位数据偶发跳变跨时钟域未同步、FIFO溢出查空满flag和跨时钟路径加FIFO、加同步器、加反压PWM输出毛刺组合逻辑输出、无输出寄存器看输出引脚波形输出打拍或ODDR闭环振荡发散PID符号错、积分饱和、控制周期太慢检查符号、延迟预算修正方向、加限幅、缩小延迟UART乱码波特率分频错误、时钟源不匹配数波形时间宽度按实际时钟重新计算分频排查问题我有一条原则一次只改一个变量。如果同时改了滤波器参数、PID参数和数据总线位宽出了问题根本没法定位。上板调试时先分模块打点比如先确认ADC模块输出波形正确再确认FIFO读写计数一致最后再让PID闭环。用ILA或者信号分析仪把valid、data、full、empty这些信号抓出来很多问题都能直接看出来。调试和仿真之间也不要互相矛盾。如果仿真通过但上板失败优先怀疑异步问题、引脚约束问题和复位释放问题而不是盲目改逻辑。仿真环境里时钟是理想化的真实电路里存在时钟偏斜、时钟抖动、复位毛刺这些在约束里都要合理处理。做FPGA测控这些年我的一个习惯是无论项目多急先把数据流画出来再把模块接口定死最后才写逻辑。框架乱了后面所有时序问题都会来报复你。前面提到的模块拆分和跨时钟域方法都是我用真金白银换回来的经验希望这次梳理能帮你在设计阶段就避开大部分坑。如果你也在做类似的测控项目欢迎把数据流画出来一起聊聊。
返回列表