尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA测控项目实战:框架、模块与数据流设计要点

FPGA测控项目实战:框架、模块与数据流设计要点 做测控的FPGA项目有个很有意思的现象需求听起来都不难无非是“采几个模拟量、跑一下算法、输出控制信号、跟上位机通信”但真写起来很多人写着写着就乱了。我接手过不少这类工程单看每一个模块都没问题拼到顶层就各种数据错位、偶发跳变、通信丢帧查了一整天最后发现问题根本不是某段逻辑写错了而是模块之间的接口没约定好、数据流没理清楚。标题里“框架、模块、数据流”这六个字恰好点中了FPGA测控程序设计的三个根子问题。这篇文章不打算讲教科书式的原理而是结合我之前做过的几套测控项目聊聊整套程序应该怎么分层、模块怎么拆、数据流怎么走以及实操里踩过的坑。不管是刚上手FPGA测控的新手还是已经写了几个版本想理清思路的老手应该都能从里面找到点能直接拿去用的东西。1. 框架先搭骨架再填血肉1.1 像盖房子一样分层测控程序本质上就是一个“采样-运算-输出-通信”的闭环。总有人喜欢一上来就写代码写完顶层再回头补接口这种做法后期基本要返工。我现在的习惯是不管项目大小先按“三层”来规划第一层是物理接口层管的是FPGA引脚和外部器件打交道的事。比如ADC的采样时钟、数据线、转换完成信号DAC的输出串口的TX/RXFMC总线的地址和数据线。这一层只做时序对齐和电平适配不掺业务逻辑。第二层是数据链路层负责把物理层拿到的原始数据变成“干净数据”或者把内部数据送到物理层发出去。典型的事情包括跨时钟域处理、FIFO缓存、数据拼接与拆分、帧协议组包和解析。第三层是功能应用层就是真正的“测控逻辑”。滤波算法、PID控制、状态机、指令解析、阈值判断都在这一层。这个分层最大的好处是能单独调试。物理接口有毛病就抓引脚波形数据链路有毛病就抓FIFO读写应用层有毛病就抓算法输入输出不用每件事都从头查到底。另一个好处是模块可替换。比如客户临时把AD7606换成AD7606B或把SPI接口的ADC换成并行的只要物理接口层和链路层的驱动换掉算法和指令解析那部分完全不用动。我做过的项目里甚至有整块通信方式从串口改成FMC的应用层代码一行没改。1.2 模块划分的边界与接口约定分层定完之后就是切模块。我习惯按照数据流的方向把每个功能单元独立成模块。一个典型的FPGA测控工程会包含这些模块adc_captureADC采样时序与数据接收data_filter数字滤波与预处理control_corePID、状态机等控制核心dac_output控制量输出与DAC驱动comm_mcu与MCU或上位机的通信协议栈monitor看门狗、心跳、异常统计模块划分要遵循一条原则高内聚、低耦合。同一件事只在一个模块里做模块之间尽量少共享信息。我最怕看到的写法是两个模块直接用全局寄存器互相读来读去表面上省了接口实际上调试的时候根本不知道是谁在什么时候改了值还有可能把两个模块的时序耦合在一起一处改动牵全身。我建议模块之间统一用valid/ready握手信号来传数据。发送方把数据放到总线上拉高valid接收方准备好后拉高ready当valid和ready同时为高时代表这个周期数据被成功接收。这种接口一旦约定好所有模块的交互方式就统一了写testbench也好写接错线的概率大幅下降。下面是我常用的信号约定信号方向位宽说明s_axis_datain视数据而定输入数据总线s_axis_validin1输入数据有效s_axis_readyout1本模块可接收数据m_axis_dataout视数据而定输出数据总线m_axis_validout1输出数据有效m_axis_readyin1下游模块可接收使用AXI-Stream这种握手方式还有一个好处是后面如果要把数据接进DMA、PCIe这类高速接口或者挂到片上总线改造起来非常顺。很多IP核原生就是这个接口省去了转换适配的工作。1.3 工程目录和代码骨架提前立好框架的落地也体现在工程目录上。很多新手把.v文件全堆在一个目录里过两周连自己都分不清哪个文件是干什么的。我现在的目录结构基本固定成下面这样proj/ ├── rtl/ │ ├── adc_capture.v │ ├── data_filter.v │ ├── control_core.v │ ├── dac_output.v │ ├── comm_mcu.v │ └── top.v ├── sim/ │ ├── tb_top.v │ ├── tb_adc_capture.v │ └── tb_comm_mcu.v ├── ip/ │ ├── asy_fifo.xci │ └── clk_gen.xci ├── constraints/ │ ├── pin.xdc │ └── timing.xdc └── script/ ├── synth.tcl └── impl.tclrtl放源码sim放仿真文件ip放IP核配置constraints放约束script放自动编译脚本。这个结构本身花不了多少时间但能省掉后面数不清的麻烦。尤其是脚本目录我建议从一开始就留着哪怕先用一条命令把工程跑起来后面反复编译时会感谢自己当初没偷懒。2. 核心模块拆解与设计要点2.1 数据采集模块从“读引脚”到“读数据”采集模块是测控程序的地基。我拿AD7606举例这是一款在测控项目里非常常见的8通道16位同步采样ADC接口是并行输出的。它有一个CONVST引脚用来启动转换转换期间BUSY信号拉高转换完成后BUSY拉低此时FPGA再通过CS和RD引脚把8个通道的数据依次读出来。接口时序是整个模块的关键。很多新手直接把AD的数据线接到系统时钟域就开读结果偶尔读到跳变的数这是因为没关注RD相对数据和BUSY的有效窗口。我写采集状态机时基本是这么安排的typedef enum logic [2:0] { IDLE, START_CONV, WAIT_BUSY, READ_CH0, READ_CH1, // ... READ_CH7, STORE_FIFO } state_t; state_t state, next_state; always_ff (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next_state; end always_comb begin next_state state; case (state) IDLE: begin if (conv_trigger) next_state START_CONV; end START_CONV: next_state WAIT_BUSY; WAIT_BUSY: begin if (!adc_busy) next_state READ_CH0; end READ_CH0: next_state READ_CH1; // ... READ_CH7: next_state STORE_FIFO; STORE_FIFO: next_state IDLE; endcase end状态机只是一个大体框架实际写的时候要注意几点一是CONVST脉冲宽度要满足器件手册要求一般几十纳秒就行但别为了省时间把它压得太极限。二是读取数据时RD信号的有效宽度和CS的时序要跟器件手册对齐。有些ADC在RD下降沿之后数据才有效有些在上升沿采样这个必须查手册不能靠猜。三是数据位宽。16位ADC但总线上可能是16根线也可能是两根8位线分时复用模块内部要把数据拼接完整再往FIFO里写。四是多ADC同步采样时每个ADC的CONVST信号要同步触发读取时逐个读但标志着一帧采样开始的时间戳必须是同一个。这个时间戳在后续的数据流分析里非常重要。我建议在采集模块输出侧加一个异步FIFO把ADC采样时钟域和社会系统时钟域隔开。这样即使采样率变化后续逻辑也不会被拖累。FIFO深度按采样率乘上最大处理延时来估算一般512或1024就足够。2.2 数据处理与算法模块滤波与控制逻辑数据采集回来之后通常不能直接用。工业现场的信号多少都有噪声我的习惯是先做预处理再做控制算法。预处理最常用的是滑动均值滤波和一阶低通滤波。滑动均值滤波在FPGA里实现非常优雅用移位寄存器存最近N个采样值每来一个新值总和 旧总和 新值 - 最老的值输出就是总和除N。这里的数值移位要特别注意总和寄存器的位宽必须比单个采样值多log2(N)位否则累加会溢出。N取2的幂次时除法就是移位N16就是右移4位能省掉一个除法器。一阶低通滤波对应公式y[n] y[n-1] k*(x[n] - y[n-1])其中k是滤波系数。FPGA里k一般用定点小数表示比如Q12格式k0.1对应k_q12410。这样整个滤波器就变成乘法和加法不牵扯浮点资源开销很低。控制算法方面最多的需求是PID。位置式PID的公式写出来不复杂但定点化是坑。比例、积分、微分系数都需要缩放到合适的Q格式误差、积分累加器和输出都要做限幅。我在项目里会单独给PID做一个模块把Kp、Ki、Kd作为可配置寄存器由上位机或MCU下发给FPGA增益调整不用重新编译工程。积分饱和要特别注意。输出已经限幅在0~4095的时候积分项还在一直累加等误差反向就要过很久才能退饱和这在电机控制里会导致明显的超调。我处理的办法是当输出达到上限或下限时冻结积分累加不让它继续往饱和方向走。至于卡尔曼滤波如果你想在FPGA里跑标量一阶卡尔曼资源完全没问题公式里就是几个状态变量的更新用定点数或浮点IP都能做。但如果上到多维状态矩阵乘法和求逆就吃资源了这时候要先做算法裁剪尽量化简成标量或低维向量再考虑硬件映射。盲目往FPGA里塞复杂矩阵运算很容易把时序搞到收敛不了。2.3 通信接口模块和MCU对上话是重头戏测控系统里FPGA很少单独工作旁边通常挂着一颗MCU比如STM32H743。热词里提到的“stm32h743和fpga实现fmc通信”就是典型场景。STM32的FMC外设可以把FPGA映射成一段内存MCU直接往地址写数据就能实现控制读数据就能拿状态使用起来非常顺手。FPGA这边要做的事情就是在逻辑里实现一个异步SRAM从机接口响应FMC发出的读和写时序。信号主要是地址总线、双向数据总线、片选信号、读使能信号、写使能信号。我的做法是内部开一组寄存器把采集数据、滤波结果、状态字映射到寄存器地址同时把上位机下发的控制字、PID参数也映射到相应的寄存器地址。MCU写某个地址时FPGA在写使能上升沿采样地址和写数据然后更新寄存器MCU读某个地址时FPGA把寄存器内容放到数据总线上在读使能有效期间保持稳定。always_ff (posedge clk) begin if (fmc_cs_n 1b0 fmc_we_n 1b0) begin case (fmc_addr) REG_CTRL: ctrl_reg fmc_wdata; REG_KP: kp_reg fmc_wdata; REG_KI: ki_reg fmc_wdata; REG_KD: kd_reg fmc_wdata; default: ; endcase end end assign fmc_rdata (fmc_cs_n 1b0 fmc_oe_n 1b0) ? read_reg_value : 16hzzzz;这个逻辑看起来很简单真正搞起来的时候有几个点特别容易踩第一是FMC的时序参数和FPGA逻辑延迟要匹配。STM32的FMC配置里有一堆建立时间、保持时间参数如果配置得太激进FPGA这边来不及采样数据就会丢。我习惯先把FMC时序调到中间档位联调通了再逐步压时序压到出错就退回上一档。这是最省事的调法。第二是数据总线方向控制。FMC的数据总线是双向的FPGA里要处理好三态门。读使能有效时把数据放上去读使能无效时释放总线回高阻否则会跟STM32输出打架严重时可能损伤引脚。第三是地址对齐和寄存器位宽。FMC一次访问可能是16位或32位寄存器表设计时要考虑对齐避免MCU侧写16位数据时被拆成两个8位操作导致每次更新只写了一半。我在设计寄存器表时会确保每个寄存器都是32位对齐并保留保留地址方便以后扩展。除了FMC串口也是测控系统常客。串口的帧协议设计有个原则起点明确、长度有限、校验完整。我常用的帧格式是帧头0xAA 0x55 命令字 数据长度 数据体 CRC16校验 帧尾。FPGA里状态机按帧头、长度、数据、校验字逐字节迁移校验不过直接丢帧并置错误标志不进入处理流程。串口的带宽限制很多人没概念。115200波特率每秒最多约11.5KB如果一帧状态包是100字节那一秒最多发115帧如果想每毫秒上报一次状态帧也就是1000帧每秒串口显然不够。这时候要么降上报频率、压帧长、提高波特率要么换成CAN、RS485、以太网或者干脆用FMC配合MCU做数据网关。选方案之前先算一下带宽能省掉后面一大堆返工。2.4 系统监控模块不上桌的保险丝测控设备一旦跑起来最怕“死得不明不白”。所以我在任何测控工程里都会放一个很小的监控模块职责就三件事看门狗、心跳、错误计数。看门狗的逻辑是系统里每完成一次完整的控制循环就喂一次狗如果超过设定时间没喂狗说明逻辑卡死或状态机跑飞监控模块强制产生系统复位或进入安全状态。安全状态一般包括输出清零、指示灯报警、错误码写入寄存器。这里要注意复位不能把看门狗自身也复位掉否则看门狗失效就失去意义了。心跳则是一个每100ms翻转一次的方波信号接到板上的LED看一眼灯还闪不闪就知道逻辑有没有跑起来调试时非常直观。错误计数是对通信CRC错误、ADC超时、FIFO溢出等异常事件做累计每类错误一个16位计数器上位机通过寄存器就能查到设备历史上发生过多少次异常对于现场排查偶发故障特别有用。3. 数据流整套系统运转的“经脉”3.1 一条数据从传感器到上位机的完整链路框架和模块定好之后真正让系统转起来的是数据流。我用一个实际的多通道采集测控项目来举例它的完整数据链路是这样的环节数据内容位宽/格式时钟域去向ADC采样8通道电压/电流16bit二进制补码AD采样时钟域异步FIFO滤波预处理通道原始值、滤波值16bit系统时钟域控制算法、寄存器表控制算法PID输出量16bit定点系统时钟域DAC、PWM输出输出接口控制量到执行器12bit系统时钟域DAC芯片状态上报通道值、告警、心跳自定义帧格式系统时钟域FMC、MCU参数下发PID系数、阈值寄存器映射FMC时钟域控制算法模块这张表是我在写代码之前必画的图。画完了每个模块的输入和输出清楚了数据从哪个时钟域进入、哪个时钟域出去也清楚了后面写代码就是往表格里填内容。大多数数据流问题其实在一开始没画这张表的时候就已经埋下了。3.2 跨时钟域最容易翻车的环节只要系统里有不止一个时钟跨时钟域就躲不掉。ADC采样时钟和系统时钟不是同一个FMC总线的时钟和系统时钟也不是同一个跨时钟域是测控FPGA设计里最经典的重灾区。跨时钟域的本质问题叫亚稳态。当一个信号在时钟边沿附近发生变化时触发器可能无法判断它是高还是低输出进入不稳定状态然后以随机概率稳定成0或1。单个触发器出现亚稳态结果是不可预测的如果这个信号还被后续逻辑使用错误会像滚雪球一样越滚越大。解决手段分两类单bit信号用两级同步器多bit数据流用异步FIFO。两级同步器就是打两拍代码只有几行always_ff (posedge clk or negedge rst_n) begin if (!rst_n) begin sync_reg1 1b0; sync_reg2 1b0; end else begin sync_reg1 async_signal; sync_reg2 sync_reg1; end end assign safe_signal sync_reg2;注意两级同步器只适用于电平型单bit信号对于脉冲信号还要做脉冲同步器处理多bit并行数据光靠打拍是不行的因为各bit的传播延迟不同可能出现中间态这时候必须用异步FIFO。异步FIFO的读写时钟域不同之所以可靠是因为它的读写指针使用了格雷码编码对跨时钟域有天然的容错性。Xilinx和Intel的IP核都有现成的独立时钟FIFO配置好位宽和深度直接例化就行。我在做ADC采集模块时最开始图省事直接把AD数据接到系统时钟域不经过异步FIFO结果数据偶发跳变一会儿是正确值一会儿是另一个值查了一下午才意识到是跨时钟域采样导致的。从那以后我学乖了凡是进系统时钟域之前的数据一律先过FIFO或者同步器绝不赌时序。3.3 数据流调度与背压让系统不“堵车”测控系统里很少只有一个数据源。ADC有8个通道、FMC有上位机下发的指令、可能还有光模块数据、图像传感器数据进来这么多数据流同时涌向控制核心或输出接口就牵涉调度问题。最基础的调度是按优先级仲裁。多路数据请求同时到达时仲裁器按固定优先级放行比如实时控制指令永远优先于普通状态刷新。但高优先级请求一直占着会饿死低优先级数据所以更公平一点的做法是轮询仲裁每路轮流获得发送权。实际项目里我经常用加权轮询控制指令给高权重普通采集数据给低权重两边兼顾。数据流调度的另一个关键点是背压。当某个模块的处理速度跟不上输入速度时必须能把“我满了你先停一停”的信息往上游传。这正好用上前面提到的valid/ready握手。如果不用背压数据持续往里灌FIFO迟早溢出溢出后的行为如果不处理灾难是必然的。FIFO本身也要留水位线监控。我在设计时习惯把FIFO的半满信号接到监控模块一旦出现半满持续超过一定时间说明下游处理能力不足系统应该记下这个事件并考虑降速或报警。这也是整个数据流设计里比较容易被忽视的地方。3.4 实例一帧测控数据包的完整旅程为了把前面说的串起来我举一个具体的状态帧例子。这个系统的采集周期是1kHz也就是每毫秒产生一组8通道采样数据状态帧就是把这8个通道的电压、滤波值、控制输出、报警标志打包成80字节通过FMC上传给MCU。数据包的旅程是这样的ADC在采样时钟域完成转换后8通道数据写入一个异步FIFO系统时钟域的组包模块每隔1ms从FIFO里读出一整帧数据打上帧头、帧尾、CRC校验打包后的数据写入发送FIFOFMC通信模块在MCU读取时把FIFO里的数据按寄存器表地址送出去。这个流程里有两个小经验值得说。第一个是时间戳帧里要带一个自由运行的计数器值表示这帧数据是什么时刻采到的MCU拿到后即使处理有延迟也能还原出真实采样时刻第二是帧组装要在中断或定时基础上做千万不要在主状态机里用一段又长又慢的循环拼数据一旦过程中有更高优先级的事情打断整个帧就会错位或者丢字节。4. 实操过程从仿真到上板的完整链条4.1 工程搭建与会话后的约束文件前面讲了一堆理论和模块真正落到工具里又是另一回事。我用Xilinx的Vivado来举例。新建工程选好器件型号后第一件事不是写逻辑而是把约束文件的框架搭好。引脚约束最基础。每个用于外部通信的信号都要指定封装引脚和电平标准。测控板上通常存在多种电平ADC可能是3.3V但有些传感器是1.8VFPGA的不同bank要分别配置。这里最坑的是bank电压不匹配比如bank配置成3.3V但引脚接了1.8V器件轻则信号读不对重则烧引脚。时序约束同样重要。系统时钟必须通过create_clock告诉工具时钟频率否则工具默认按理想时序处理上板就露馅。多个异步时钟域之间要主动用set_clock_groups设为异步告诉工具不用分析它们之间的路径否则工具会花大量时间做无效时序收敛甚至报一堆假路径错误真正的关键路径反而不突出。我见过一个项目因为两个异步时钟域没指定group挤出了上百条违例路径里面全是吓人的假消息真正的两条关键路径反而被淹没了。4.2 仿真验证测试平台是“显影剂”代码写完先别急着上板仿真这关必须过。我的testbench习惯是给被测模块造一个“周围世界的模型”而不是只给几个信号乱跳。比如测adc_capture我会写一个模拟AD7606时序的信号生成器按要求产生BUSY信号和数据总线上的结果测通信模块我会写一个模拟MCU的脚本按FMC时序写寄存器再按串口协议发指令帧。仿真里面有个常见的坑信号初始值全是X。X在仿真里传播会导致很多无意义的报错所以复位逻辑一定要写干净。我一般在testbench里做的第一件事就是拉低复位信号至少100个时钟周期然后释放确保所有寄存器都完成初始化。另一个坑是死锁。两个模块互相等对方信号仿真里就卡住了波形一直不动。排查死锁最有效的方法是看波形里状态机停在哪一步往前推是等哪个信号。通常死锁的根源是握手逻辑写成了“先等valid再等ready同时ready又等valid”本质上就是逻辑漏了某种组合。写好握手逻辑后testbench里专门构造“verySlowSink”模型让下游模块每隔几十个周期才拉一次ready用这种极端场景验证背压逻辑是不是真的有效。4.3 上板调试Signal Tap/ILA是“照妖镜”仿真过完不等于上板就能跑。上板调试主要靠在线逻辑分析仪Vivado里叫ILAQuartus里叫Signal Tap。它的原理是把要观察的信号预先探出来通过JTAG实时上传波形。用ILA有两点要提前规划。一是探哪些信号要在综合前就想好否则重新综合又是半小时起我在框架设计阶段就会在关键数据通道上预留好测试点比如FIFO写使能、读写指针、握手信号、状态机当前状态。二是触发条件要定好片子运行起来数据是海量的全抓出来不现实。我调试偶发数据错误时触发条件会设成“CRC校验错误标志拉高”或者“FIFO空满异常”等错误发生的时刻把前后波形全部抓住基本就是精准命中。一次真实的调试经历设备运行了十几分钟才偶发一次数据错乱频率极低仿真完全复现不出来。我最后就是通过把触发条件设在异常标志上抓到了故障瞬间的波形发现是ADC采样值在跨时钟域同步时偶尔采样到了一个不稳定的中间电平。问题定位到后换成了异步FIFO再加上一级握手确认之后跑几天都没再出现过。5. 常见问题与排查技巧实录5.1 一张排查速查表整理一下我在测控FPGA项目里最常遇到的一批问题按症状、可能原因、排查路径和解决方法来列。这张表是我自己排查问题时固定使用的框架项目不同但逻辑是通用的。症状可能原因排查手段解决方法数据偶发跳变/毛刺跨时钟域未处理、采样窗口不对ILA抓异常点对比原始时序加异步FIFO或同步器修正采样点控制输出偶尔抖动积分饱和、输出限幅不生效抓PID模块内部信号输出限幅时冻结积分项MCU读到的数据不更新FMC地址映射错误、写时序不满足抓FMC写使能和地址调整FMC时序参数修正地址译码通信偶发丢帧校验错误、FIFO溢出抓帧状态机和FIFO水位加错误计数增大FIFO优化背压系统运行一段时间卡死状态机死锁、看门狗失效抓状态机当前状态增加超时跳转完善看门狗逻辑信号线上电平不对IOSTANDARD配置错、bank电压不符万用表量电平检查约束修改I/O标准约束复位后行为不正常复位释放时序不对、复位毛刺抓复位信号波形使用异步复位同步释放5.2 时序不收敛不是加约束就完事时序报错是FPGA开发里最常见的一道坎。测控系统里如果组合逻辑链条太长比如把整个PID计算在一个时钟周期里全部完成关键路径延迟就会超过时钟周期工具报时序违例。解决办法首推流水线。把一个复杂计算切成若干级每一级花一小段组合逻辑寄存器在每一级末缓存中间结果。PID计算如果用一个周期算完延迟可能到十几纳秒切三段流水线之后每段只有几纳秒时序轻松收敛。代价是多几个周期的输出延迟测控系统对这个通常不敏感。扇出太大是另一个常见问题。一个控制信号接了上百个触发器布线资源和时序都会恶化。解决办法是复制寄存器让一份信号接一部分负载工具通常也会帮忙做。关键是代码里不要手写过长的if-else链每个条件都依赖同一个信号扇出自然就高。还遇到过一种情况时序分析一片绿上板就是跑不对。这种多半是异步时钟域没有设置异步约束工具按同步路径优化了实际上两边时钟毫无关系逻辑行为完全不可控。跨时钟域路径该设false path就设false path该set_clock_groups就设异步不要心存侥幸。5.3 通信调试先查物理再查逻辑联调阶段最容易出问题的其实是“看起来没毛病”的通信。遇到丢帧我的排查顺序是先物理后逻辑。先拿示波器看MCU发送端的波形电平对不对、帧间隔是否正常再测FPGA接收引脚处的波形排除走线问题和电平转换芯片的问题。物理层正常了再进逻辑查。逻辑层要重点看三个位置接收侧的状态机是否把帧同步字识别正确数据体长度是否和实际接收到的字节数一致FIFO是否存在溢出导致丢数据。这三个问题如果同时排查效率最高。还有一个容易忽略的点是波特率容差。UART通信双方要求波特率偏差在一定范围内通常不超过2%。MCU用的是外部晶振FPGA用的是板载时钟两边如果都是标称值倒还好如果一边用了内部RC振荡器误差大了偶发错帧是必然的。用示波器量一下实际波特率比对着代码猜半天有效得多。5.4 复位设计不起眼的细节最要命复位是整个系统里最基础也最容易被忽视的东西。我现在统一用“异步复位、同步释放”的方式复位信号异步拉低立即生效释放时由本地时钟打拍后再释放避免复位释放瞬间出现亚稳态也保证整个系统在同一时刻进入工作状态。上电顺序也要注意。FPGA配置期间引脚处于未定义状态外部连接的DAC或者继电器驱动电路可能会瞬间动作或输出错误电平。解决办法是在设计里加上上电默认值约束配置完成后先让所有输出处于安全电平等软件层面把参数初始化好再开放控制输出。很多现场设备一上电就误动作排查了一圈才发现是FPGA配置期间引脚状态没管好。关于复位电平我的经验是尽量统一成低有效这跟大多数开发板上的按键复位一致写代码时也少踩坑。芯片引脚约束上复位的电平标准也要和外部电路匹配否则按键按下和不按下状态完全反过来。写在最后的一点私货这套“框架-模块-数据流”的思路我现在已经用了好几年前后套到过三个完全不同的测控项目上。每次换项目ADC换了、通信协议换了、控制算法换了但顶层架构基本没动过。改动最多的是模块内部实现模块之间的接口握手协议几乎原封不动复用。这就是我认为框架最大的价值它不帮你写任何一行业务逻辑但它让你每次改动都精确锁定在一个小范围内不会“改一处崩全面”。还想送大家一个调试工具之外的技巧给信号命名时加上模块前缀。比如adc_capture出来的valid信号就叫adc_validfilter模块输出的数据就叫filt_data控制模块的参数叫ctrl_kp。看着只是命名的习惯但真到ILA抓波形和写约束的时候你会谢谢自己当初没偷懒。名字起得清楚读波形快十倍。
返回列表