基于FPGA的64路12位数据采集与NAND存储系统设计

发布时间:2026/11/3 22:03:07
基于FPGA的64路12位数据采集与NAND存储系统设计

简介:围绕FPGA实现的数据采集系统,这份文档面向电子、自动化、测控等专业的本科毕业设计与课程设计场景,帮助读者梳理多通道数据采集系统的整体方案与硬件实现思路。资源包仅含1个doc文件,大小约526KB,内容以毕业设计说明书形式组织,涵盖课题背景、系统组成、整体设计方案以及模拟量、开关量、数字脉冲采集通道和FPGA中心控制、存储与PC接口等模块。文中结合64路0~5V模拟信号、8路无源开关量和1路TTL/12V脉冲信号的采集需求,展开硬件结构与控制逻辑说明,可作为方案论证、器件选型和论文撰写的参考。目前已有638人学习,适合需要快速理解FPGA数据采集系统框架、整理设计文档与查漏补缺的读者。

1. 六十四路信号摆上一块板,难点不在“采集”两个字

带过几届做基于FPGA的数据采集系统这个题目的学生,开题时几乎都把重点写成“用FPGA做控制逻辑”。真正把板子焊出来、线接上,才发现卡人的地方在后面:六十四路 0~5V 模拟信号、八路无源开关量、一路 TTL 或 0~12V 脉冲同时进板,通道怎么轮、切换后等多久才敢采样、12 位数据往哪里存、存满以后怎么回读给 PC。市面上的现成采集卡要么通道数不够,要么只吃差分电压不吃开关量,要么价格直接把预算打穿。这套方案按“模拟通道 + 开关量通道 + 脉冲通道 + FPGA 中心控制 + NAND 存储 + 并口回读”来搭,适合要交一份完整毕设或课设、并且希望实物能测出数据的人照着重做。

2. 指标倒推选型:64 路、12 位、1.5 MSPS 之间的时间预算

选型这一步最容易翻车的地方,是先挑芯片再算时间。等电路画完才发现模拟开关的切换时间加上建立时间,已经把每通道的采样窗口吃干净,剩下的采样率连工频干扰都看不出来。我的习惯是反过来:先把系统指标拆成单通道的时间预算,再拿预算去卡器件参数,卡不住的直接淘汰。下面这张预算表就是整套方案的基准,后面所有器件选择都服从它。

2.1 单通道时间预算怎么算出来

被测物理量大多是缓变量(温度、压力、位移、应变),并不需要每通道都跑到兆级采样率。真正决定指标的是三个约束:模拟开关的切换与建立时间、ADC 的转换节拍、NAND 的写入带宽。把三者的数值列成一张表,选型就有依据了。

环节取值依据
模拟开关切换时间200 ns16 选 1 模拟开关的典型导通/关断时间
通道建立时间约 1 μs开关导通电阻 200 Ω 与后级采样电容构成的 RC 建立
ADC 时钟周期666.7 ns1.5 MSPS 上限,实际按此时钟运行
每通道分配周期数4 个 ADC 周期前 2 个丢弃,后 2 个取有效样点
单通道占用时间2.667 μs4 × 666.7 ns
64 通道一轮扫描170.7 μs64 × 2.667 μs
单通道等效采样率约 5.86 kSPS1 / 170.7 μs
原始码流约 4.5 Mbps64 × 5.86 k × 12 bit

关键在“每通道 4 个 ADC 周期”这个取值。模拟开关切换完成的瞬间,后级运放和 ADC 内部采样电容还在充电,此时转换出来的码字是过渡值,直接存进去就是通道间串扰的源头。留两个周期给建立、只用后两个周期的结果,是成本最低的做法。如果被测信号源内阻偏大(比如某些应变桥路),两个周期不够,就要把每通道周期数加到 6 个,扫描周期相应拉长到 256 μs,采样率降到 3.9 kSPS,这个账在方案阶段就要算清楚,别等调试时才发现波形跳。

再往下推存储。12 位数据两通道拼成 3 字节,64 通道一帧就是 96 字节,加 8 字节帧头(通道号、时间戳、状态位),单帧 104 字节。NAND 一页 2048 字节主区加 64 字节备用区,2048 / 104 = 19.69,取 19 帧一页,剩 72 字节空着。128 MB 容量按 5.86 k 帧/秒、每帧 104 字节算,数据率约 609 KB/s,能连续录制约 210 秒。这个数字直接决定了答辩时能不能做一次三分钟以上的连续采集演示,所以它不是可有可无的补充说明,而是方案指标的一部分。

2.2 四类器件的选型对比与落点

模拟开关这一环,机械继电器导通电阻小但切换慢、有抖动,六十四路全用继电器,体积和功耗都不现实。集成模拟开关体积小、切换快、无抖动,缺点是导通电阻偏大、通道间有漏电流。在低频段(f < 10 MHz)用 CMOS 工艺的片子就够,选型时看四个参数:通道数、导通电阻、开关时间、断开漏电流。通道数越多,寄生电容和漏电流越大,通道间干扰越明显,所以六十四路不是找一片 64 选 1,而是用 4 片 16 选 1 级联,每片只承担 16 路,漏电流和寄生参数都可控。落点选 ADG506:16 选 1,断开漏电流典型值 20 pA,导通电阻约 200 Ω,导通与关断时间各 200 ns,A0~A3 加使能端全部由 FPGA 直接驱动。

ADC 这一环,把并行比较型、逐次逼近型、积分型、压频变换型、Σ-Δ 型、流水线型摆在一起看,能得到一个很清晰的结论:要 12 位、要 1 MSPS 以上、要并行输出接口、要单电源,交集基本只剩流水线型。逐次逼近型速率能到 1 MSPS,但内部采样保持对多通道轮询不友好;Σ-Δ 型分辨率能上 24 位,可转换速率被过采样比拖住,做 64 通道轮询反而更慢;积分型精度高但速率只有百级 SPS,直接出局。落点选 12 位、1.5 MSPS 的流水线型 ADC,内部含量化器、宽带采样保持和可编程基准,单 +5V 供电,输出并行接口兼容 TTL,能直接被 FPGA 端口读取,省掉一级电平转换。

中心控制选 FPGA 而不是单片机,理由不是“FPGA 快”,而是这套系统需要同时产生十几路节拍严格对齐的控制信号:模拟开关的 A0~A3、ADC 时钟、NAND 的 CLE/ALE/CE/WE/RE、FIFO 读写指针、并口握手。用单片机分时复用这些信号,时序抖动会让 NAND 写入偶发失败,而这种失败在现场几乎查不出来,属于典型的黑匣子问题。落点选 Spartan-II 系列的 XC2S50,配一片配置存储器做上电加载,供电用双路低压差芯片给出 3.3V 和 1.2V 两档。

存储器这一环,SRAM 速度快但掉电丢数据,要保住数据就得背电池,体积和可靠性都不划算;NOR 型随机读快、可直接执行代码,但擦除单元大、写入慢,不适合连续码流;NAND 型单元密度高、写入和擦除都快,缺点是需要 I/O 接口和驱动程序、有坏块。存储测试的场景是“先录后读”,写入是顺序的、读取是事后的,正好落在 NAND 的长处上。落点选容量 128M×8 bit 的 NAND 型 Flash,页 2048+64 字节,块 64 页,共 1024 块,单 3.3V 供电,掉电后信息不丢。

2.3 三个被否掉的方案,以及否掉的理由

第一个被否的是“每通道一套独立 ADC”。六十四路各配一片 12 位 ADC,理论上不存在通道切换和串扰问题,采样率也能拉满。但六十四片 ADC 的功耗、布板面积、供电去耦成本会把整块板推到不可实现,而且六十四路并行的 12 位数据线根本进不了单片 FPGA 的可用 I/O 预算。这个方案只在通道数少于 8 且要求同步采样的场合才成立。

第二个被否的是“SRAM 加备用电池保数据”。听上去简单,实际上电池切换电路、充电管理、掉电检测三块都要做,任何一处失效就是整段数据丢失,而且电池本身在高温环境下的寿命没法在说明书里承诺。毕设场景里,答辩前一周电池失效意味着全部重测。

第三个被否的是“NOR Flash + 文件系统”。NOR 的擦写次数虽高,但擦除粒度大、写入速度慢,连续 609 KB/s 的码流会把写入带宽吃满,稍有抖动就丢帧。NAND 的页编程一次 2048 字节,正好匹配一帧数据的打包粒度,写放大最小。

提示:选型表不是写给评审看的装饰,每一行都要能在调试时对上号。导通电阻对应建立时间的计算,漏电流对应串扰的排查方向,页大小对应打包格式,写不清楚的项说明还没想明白。

3. 总体架构设计:五个模块的职责边界与信号流

架构图不用画得漂亮,但每一个箭头上传的是什么信号、多少位、什么电平、什么节拍,必须在纸上写清楚。这套系统按五个模块切分:信号采集模块负责三类信号的前端调理与量化,中心控制模块负责全部时序,存储器模块负责落地,接口电路负责回读,外围辅助电路负责电源、时钟与复位。模块之间只通过明确的信号线相连,不做跨模块的隐式依赖,这条纪律在后期改板时能省下大量返工。

3.1 从传感器到 PC 的五段数据链路

完整链路分五段。第一段是模拟前端:六十四路 0~5V 信号先经过电压跟随器提高输入阻抗、稳定信号源,再进模拟开关做 16 选 1 的选择,四片级联成 64 选 1,选出的一路送 ADC。第二段是量化:ADC 在 FPGA 给出的时钟节拍下把模拟量转成 12 位并行码,同时给出溢出指示。第三段是打包:FPGA 把 12 位码按通道号顺序拼帧,加帧头,写入内部 FIFO。第四段是落地:FIFO 攒够一页的数据量后,由 NAND 控制器发起一次页编程。第五段是回读:采集结束后板子通过并口与 PC 相连,按页读出、校验、交给上位机还原成通道波形。

每一段的宽度要对齐。模拟通道一轮 64 个样点,打包后 96 字节,加上帧头 104 字节,FIFO 深度至少能存一页(2048 字节)才不会在 NAND 忙的时候溢出。NAND 一次页编程的典型耗时在数百微秒量级,而一页数据从 FIFO 攒满只需要 19 × 170.7 μs ≈ 3.24 ms,所以只要页编程时间小于 3.24 ms,链路就能持续跑下去不丢数。这个不等式是判断整条链路是否成立的硬条件,实际调试时先量这个,比逐个模块查效率高得多。

3.2 三类信号通道的差异化结构

模拟通道的重点是把 0~5V 搬进 ADC 的输入窗口。ADC 的模拟输入范围由基准电压决定,典型配置下是 1V 到 3V 的 2V 峰峰值窗口,直接灌 0~5V 会把输入端打坏或者削顶。我的做法是用运放搭一级衰减加偏置:增益取 0.4,把 0~5V 压成 0~2V,再叠加 1V 偏置搬移到 1~3V。电压跟随器选四运放集成器件,单电源可用、静态功耗小、价格低,六十四路用十六片刚好,每片四路独立,布板也规整。

开关量通道的重点是隔离。八路无源开关量来自现场侧的干接点,与板内数字地之间存在地电位差,还夹着继电器线圈和接触器带来的电磁干扰,直接进 FPGA 引脚迟早出事。隔离器件选光电耦合器:发光二极管把电信号变成光,光敏三极管再把光变回电,输入输出在电气上完全断开,隔离电压可到千伏以上,响应速度也够配 TTL 使用。这一级同时兼有反相和电平转换的作用——输入低电平时二极管不亮、三极管截止、输出高电平;输入高电平时二极管导通、三极管饱和、输出低电平。

耦合器之后接一级施密特输入反相器做整形驱动。现场侧的开关信号边沿往往很缓,或者带毛刺,施密特输入的翻转电平有回差,上跳沿和下降沿会被整成陡峭边沿,等于在数字域里做了一次整形,后面再进 FPGA 就干净了。限流电阻的取值决定发光二极管的导通电流,一般取数毫安;输出侧的上拉电阻要按电流传输比反推,取大了带负载能力弱、暗电流会抬高输出高电平,取小了功耗上去了,这两个电阻是这套电路里最需要动手算的地方。

数字脉冲通道只有一路,输入形式是 TTL 电平或者低电平 0V、高电平 12V 的脉冲,而存储器输入电压是 3.3V,所以必须先做电平调整。做法是串限流电阻后用 3.3V 稳压管钳位:输入超过 3.3V 时钳在 3.3V,低于 3.3V 时原样通过。这一级还承担了测频和计数的前置整形,脉冲信号既可以直接送 FPGA 计数,也可以作为采样时基的同步源。

3.3 时钟、复位与三个供电域

全系统的时基只有一处产生:板上晶振进 FPGA 内部的时钟管理单元,分频后得到系统主时钟和 ADC 时钟。ADC 时钟不能由外部独立晶振给,否则 ADC 的转换节拍和 FPGA 的采样寄存器之间会缓慢漂移,采出来的码字会周期性错位一位,这种故障的排查过程极其不友好,属于玄学级别。所有需要严格对齐的节拍,都必须从同一个时钟源分频出来。

供电分三个域。模拟域 5V 供运放和 ADC,数字域 3.3V 供 FPGA 的 I/O、NAND 和光耦输出侧,内核域 1.2V 供 FPGA 内部逻辑。两路低压差稳压器分别给出 3.3V 和 1.2V,模拟 5V 单独从外部输入。三个域的地在单点汇合,模拟地和数字地分区域铺铜,ADC 下方不铺数字地。这一条在原理图上只是几根线,布板上是决定 12 位精度能不能保住的关键,我见过太多板子逻辑全对、波形全是毛刺,最后查出来是数字回流从模拟地穿过去了。

复位用上电复位加手动按键两级。上电复位保证配置存储器加载完成之前 FPGA 处于确定的初始状态,手动复位留给调试时反复触发用。复位信号必须同步到系统时钟域再分发,异步复位、同步释放是标准做法,能避免释放瞬间的亚稳态。

3.4 回读通路与数据格式约定

板子和 PC 之间的接口用并口,原因是实现简单、不需要额外的协议栈、上位机侧用现成的端口读写就能拿到原始字节,毕设场景里够用。并口按字节并行传输,数据各位同时上总线,握手靠两根控制线:板子把数据放上总线后拉一根状态线,PC 读走数据后回一根应答线,板子收到应答再准备下一字节。握手的每一次都要等对方确认,不能靠延时猜,靠延时猜的结果就是高速机器上丢字节、慢速机器上重复字节。

数据格式在方案阶段就要定死。每页 2048 字节主区里,前 16 字节是页头:页号(4 字节)、帧计数(4 字节)、本页帧数(1 字节)、通道掩码(8 字节中只用低 64 位)、校验和(2 字节)、保留(1 字节)。之后按帧排列,每帧 104 字节,19 帧共 1976 字节,剩余 56 字节填固定填充码。备用区 64 字节用来记录坏块标记和写入计数,坏块标记按 NAND 的惯例放在块首页或次页的固定列上,避免和主数据混在一起。

格式定死的好处是上位机可以按固定偏移直接取数,不需要在 PC 侧再做一遍解析逻辑。回读流程也简单:PC 先发一条读页命令带页号,板子从 NAND 读出一页进 FIFO,再按字节握手送出,PC 侧校验页头里的校验和,不对就重读该页。一次完整回读 128 MB 的耗时主要花在并口握手上,实测在可接受范围内,答辩演示时只需回读前若干页就够画出波形。

4. FPGA 内部逻辑落地:通道扫描、采样对齐与 NAND 页写

FPGA 内部的逻辑量不大,但要写得确定。三个状态机就够:通道扫描、ADC 采样对齐、NAND 命令序列。所有状态机的节拍都从系统主时钟分频得到,任何一个状态机都不允许用“等一会儿”的写法,所有等待都要有明确的状态迁移条件。下面贴的三段代码是骨架,实际工程里还要加边界保护和调试计数器。

4.1 通道扫描状态机与相位划分

扫描状态机的核心是把每个通道的 4 个 ADC 周期拆成两个相位:相位 0 和 1 做切换与建立,相位 2 和 3 做有效采样。通道号在相位翻转到 0 时推进,这样切换动作总是发生在采样窗口之外。

// 64 通道扫描:每通道 4 个 ADC 节拍,前 2 拍建立、后 2 拍取数 module ch_scan ( input wire clk, // 50 MHz 系统主时钟 input wire rst_n, // 低有效同步复位 input wire adc_tick, // 1.5 MHz 单周期使能,由主时钟分频得到 output reg [5:0] ch_idx, // 0~63 全局通道号 output reg [1:0] phase, // 通道内相位 0~3 output reg sample_vld // 有效采样窗口指示,高电平 1 拍 ); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ch_idx <= 6'd0; phase <= 2'd0; sample_vld <= 1'b0; end else if (adc_tick) begin phase <= phase + 2'd1; // 相位循环 if (phase == 2'd3) begin // 本次通道结束,推进通道号 ch_idx <= (ch_idx == 6'd63) ? 6'd0 : ch_idx + 6'd1; end sample_vld <= (phase == 2'd3); // 第 4 拍为有效样点窗口 end else begin sample_vld <= 1'b0; // 非节拍周期拉低,避免重复取数 end end endmodule

adc_tick是 1.5 MHz 的单周期使能,由主时钟分频产生,占空比不重要,重要的是每次只有一拍。phase记录通道内的 4 个相位,ch_idx在相位回到 0 之前推进。sample_vld只在第 4 拍拉高,配合下一级的延迟对齐逻辑使用。

这里的代码看着简单,但节奏一旦出错,全部现象的解读都会出错。我一开始把有效窗口放在相位 1 上,结果所有通道的数据都比真实值偏低一点,因为此时模拟开关刚切换完,后级采样电容还没充满。现象是“低频信号测出来整体缩水”,很容易误判成 ADC 基准电压不准,实际是取数窗口早了。改到相位 3 之后曲线立刻正常。

4.2 ADC 采样对齐与流水线延迟补偿

流水线型 ADC 有固定的转换延迟,数据在时钟沿之后若干拍才出现在输出口上,同时刻给出的通道号和若干拍之后出现的码字必须重新对齐,否则每一帧的通道号都会整体错位。

// 流水线延迟补偿:AD9221 约 7 拍延迟,通道号与有效沿同步延迟后打包 localparam ADC_LAT = 7; reg [11:0] adc_pipe [0:ADC_LAT-1]; reg [5:0] idx_pipe [0:ADC_LAT-1]; reg vld_pipe [0:ADC_LAT-1]; integer i; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (i = 0; i < ADC_LAT; i = i + 1) begin adc_pipe[i] <= 12'd0; idx_pipe[i] <= 6'd0; vld_pipe[i] <= 1'b0; end end else begin adc_pipe[0] <= adc_dout; // ADC 并行数据直接进第一级 idx_pipe[0] <= ch_idx; // 通道号与数据同拍进入 vld_pipe[0] <= sample_vld; for (i = 1; i < ADC_LAT; i = i + 1) begin adc_pipe[i] <= adc_pipe[i-1]; idx_pipe[i] <= idx_pipe[i-1]; vld_pipe[i] <= vld_pipe[i-1]; end end end // 末级输出:数据、通道号、有效标志三者严格同拍 wire [11:0] adc_aligned = adc_pipe[ADC_LAT-1]; wire [5:0] idx_aligned = idx_pipe[ADC_LAT-1]; wire vld_aligned = vld_pipe[ADC_LAT-1];

ADC_LAT这个参数必须实测确认。数据手册给的延迟是一个范围,不同批次、不同时钟频率下会有出入。确认方法是给一个已知的阶梯输入,抓 ADC 输出和通道号,看码字跳变发生在哪个通道号上,把差值填进参数里。这一步不做,后面整帧数据的通道映射全错,波形看起来有模有样,实际上每一个通道都对错了物理量。

打包逻辑接在末级输出上:vld_aligned有效时,把 12 位数据按通道号写入双口 RAM 的对应地址,一个完整扫描周期结束后,由存储控制器一次性读出 64 个样点拼帧。用双口 RAM 而不是直接进 FIFO,是为了保证帧内顺序,不受 NAND 写入时序反向压迫。

4.3 NAND 命令序列与状态查询

NAND 型的写入不是“把地址和数据放上总线”就行,它有一套命令序列。所有命令、地址、数据都从 8 位 I/O 口分时写入,靠 CLE 和 ALE 两根线区分当前周期传的是什么:CLE 为高时锁命令,ALE 为高时锁地址,两者都为低时锁数据。锁存动作发生在写使能的上升沿。

// K9F1G08 页编程主序列:复位 → 页编程建立 → 4 拍地址 → 数据 → 执行 → 等就绪 localparam S_IDLE = 3'd0, S_CMD80 = 3'd1, // 发 0x80 页编程建立命令 S_ADDR = 3'd2, // 2 拍列地址 + 2 拍行地址 S_DATA = 3'd3, // 连续写 2048 字节主区数据 S_CMD10 = 3'd4, // 发 0x10 执行页编程 S_WAIT = 3'd5; // 读状态、等 R/B 回高 // 单拍写口:we_n 低 2 拍(40 ns),满足 >=12 ns 的脉宽要求 task nand_write_cycle(input [7:0] d, input cle, input ale); begin cle_n <= ~cle; ale_n <= ~ale; ce_n <= 1'b0; io <= d; we_n <= 1'b0; // 第一拍拉低 @(posedge clk); we_n <= 1'b1; // 第二拍拉高,上升沿锁存 @(posedge clk); ce_n <= 1'b1; // 释放片选 end endtask always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= S_IDLE; we_n <= 1'b1; ce_n <= 1'b1; cle_n <= 1'b0; ale_n <= 1'b0; byte_cnt <= 12'd0; end else begin case (state) S_IDLE: if (page_ready) state <= S_CMD80; S_CMD80: begin nand_write_cycle(8'h80, 1'b1, 1'b0); // CLE 高,锁命令 state <= S_ADDR; end S_ADDR: begin // 列地址低字节、高字节,再行地址低字节、高字节,共 4 拍 nand_write_cycle(col_addr[7:0], 1'b0, 1'b1); nand_write_cycle(col_addr[15:8], 1'b0, 1'b1); nand_write_cycle(row_addr[7:0], 1'b0, 1'b1); nand_write_cycle(row_addr[15:8], 1'b0, 1'b1); state <= S_DATA; end S_DATA: begin if (byte_cnt == 12'd2047) begin state <= S_CMD10; byte_cnt <= 12'd0; end else begin nand_write_cycle(fifo_q, 1'b0, 1'b0); // ALE/CLE 都低,锁数据 byte_cnt <= byte_cnt + 12'd1; end end S_CMD10: begin nand_write_cycle(8'h10, 1'b1, 1'b0); // 执行页编程 state <= S_WAIT; end S_WAIT: if (rb_high) state <= S_IDLE; // 状态线回高表示编程完成 endcase end end

几个参数必须对上。we_n的低电平宽度是 2 个主时钟周期即 40 ns,比手册给出的最小脉宽 12 ns 留了三倍裕量,这是故意的,因为 NAND 的时序参数是在特定测试条件下给出的,实际布板的走线延迟和电源噪声会吃掉一部分裕量。地址四拍的顺序不能错:先列地址低、高,再行地址低、高,列地址决定页内偏移,行地址决定页号,顺序反了会写到完全不相干的位置上去。

S_WAIT里读的是 R/B 状态线的电平,不是固定的延时等待。用固定延时等编程完成,在大部分时候也能跑,但换一批片子或者温度变化就会偶发失败,现象是“某些页写进去读出来是全 0xFF”,排查起来非常费劲。正确做法是每次编程后读状态寄存器确认,同时在文件里记录坏块,第一次发现某块擦除后不是全 0xFF 就标记掉,不再使用。

注意:坏块标记要写在备用区的固定列上,且必须和主区分开。有人把坏块标记写进主区的第一个字节,结果上位机解析时把标记当成数据帧头,整页数据全部错位。

4.4 三个时钟域与 FIFO 的跨域处理

系统里实际存在三个时钟域:50 MHz 的系统主时钟、1.5 MHz 的 ADC 节拍、NAND 控制器自己的状态机节拍(也是 50 MHz 但相位独立)。ADC 侧写数据到双口 RAM 用 1.5 MHz 使能,读出到打包模块用 50 MHz,这两个域之间的交接必须经过异步 FIFO 或者带握手的双口 RAM,不能直接跨域读。

FIFO 深度按最坏情况算。NAND 页编程期间,ADC 侧最多持续产出一页的数据量,即 2048 字节主区对应的帧数。取 19 帧一页,FIFO 深度取 32 位宽 × 512 深,折合 2 KB,刚好覆盖一页,留一点余量。读侧在 FIFO 剩余量达到一页阈值时才发起页编程,避免频繁的短包写入拖慢整体速度。

跨域标志位用格雷码指针,读写指针各自在自己的域内递增,比较时做两级同步。这个结构在教科书上很常见,但实际写的候容易漏掉“同步后要再做一次边沿检测”,结果是 FIFO 空满判断抖动,偶尔多写一帧或者少写一帧。少写一帧的现象是回读时某一帧的位置被下一帧顶掉,波形上看起来像是某个通道丢了一段,实际上是 FIFO 指针的问题。

5. 常见问题排查:串扰、基准漂移与写入失效

调试阶段遇到的问题,绝大多数能在设计阶段规避,只是当时没想到。下面五条是这套方案里出现频率最高、也最容易误判的,每条按现象、原因、解决来写,遇到的时候先按这个顺序对一遍,能省下大量时间。

5.1 相邻通道数值互相沾染

现象很典型:给 1 号通道接一个稳定直流,其余通道悬空,扫出来的 2 号、3 号通道数值跟着 1 号一起动,动幅度大概几个到十几个码字,通道间隔越近耦合越强。

原因是模拟开关断开时并不是理想的高阻,断开漏电流典型值是皮安级,但六十四路并在一起、加上 PCB 上相邻走线的寄生电容,切换之后后级采样电容上残留的电荷要重新分配,建立时间不够就会把上一通道的残留量带进来。另一个来源是通道间没有做地隔离,走线并排走很长一段,容性耦合直接串过去。

解决分三层。第一层在时序上,把每通道的建立周期从 2 个加到 3 个,用时间换隔离,扫描周期相应变长;第二层在电路上,每个模拟输入串一个小电阻并在开关输入端对地加小电容,构成低通,把高频耦合压掉;第三层在布板上,通道之间铺地并打过孔,模拟走线不要和数字走线长距离并行。三层里第一层最容易做,先试它,效果不够再动板子。

5.2 全通道数据整体偏移或低位跳动

现象是六十四路都偏低(或偏高)一个固定值,或者最低两三位一直在跳,采样率降低后跳动减少。这时候很容易怀疑 ADC 坏了,其实大多数情况是基准和地的问题。

第一个原因是基准电压的走线太长、去耦不足。基准源的输出阻抗不为零,ADC 转换瞬间的电荷注入会在基准上产生尖峰,转换结果跟着抖。解决办法是在基准引脚旁边就近放去耦电容,基准走线单独走、不与其他信号并行。

第二个原因是模拟地和数字地的汇合点选错了。数字回流电流从 ADC 下方的地平面穿过,产生毫伏级的压降,而 12 位 ADC 在 2V 窗口下的一个码字只有 0.49 mV,地弹直接体现为码字跳动。解决办法是把汇合点挪到电源入口处,ADC 下方只铺模拟地,数字信号线不从模拟区上方跨过。

第三个原因是前端运放的偏置没有校准。衰减加偏置那一级的电阻精度直接决定零点位置,1% 精度的电阻在 0.4 倍增益下引入的误差足以让零点漂移几个码字。要么换 0.1% 电阻,要么在方案里留一个软件零点校准的入口,上电时短接输入端采一组零点值存起来,后面每帧减去。

5.3 NAND 写入偶发失败、读回全 FF

现象是连续采集几分钟后,回读发现某几页数据全 0xFF,或者页头校验和对不上,重测时失败的位置又变了。这类问题最消耗耐心,因为它不可复现。

原因通常有三个。一是没读状态寄存器,用固定延时等编程完成,某些块本身擦写慢,延时不够就提前发下一条命令,NAND 内部还在忙,命令被丢弃。二是没做坏块管理,出厂就存在坏块,第一次写到坏块上必然失败,而坏块位置是随机的,所以现象也跟着随机。三是备用区的使用和主区冲突,坏块标记被后续的数据写入覆盖掉,下一轮又写到同一块上。

解决办法是把状态查询和坏块管理都做进控制器:每次擦除后读一遍整块,不是全 0xFF 就标记为坏块并跳过;每次编程后读状态寄存器,失败则把该页重写到下一块;坏块表在上电时从备用区扫描重建,不依赖外部存储。这套机制写起来大概两百行状态机,但它是这套系统能不能连续录满容量的分水岭。

5.4 十二伏脉冲打坏输入引脚

现象是接上 12V 脉冲源之后,FPGA 那一路输入彻底不响应,换一片 FPGA 又好了,接上再坏。这是电平钳位电路没做对。

原因是只用稳压管钳位、没有串限流电阻,或者限流电阻取值太小。稳压管在导通瞬间阻抗很低,12V 加在它上面会产生很大的瞬时电流,虽然钳位电压被限制在 3.3V,但电流是从 FPGA 引脚和稳压管之间硬灌的,长期下来引脚就废了。正确做法是输入端先串一个限流电阻,阻值按 (12 - 3.3) / 数毫安 来取,再并稳压管,让多余的能量落在电阻上。同时确认稳压管的功率余量,脉冲占空比高的时候,平均功耗并不低。

5.5 并口回读丢字节或重复字节

现象是回读的数据长度对,但内容错位,校验和隔几页错一次,错的位置不固定。查起来像是在传输环节,实际上是握手时序的问题。

原因是用延时代替了握手确认。板子把字节放上总线,延时一小段时间就认为对方读走了,接着放下一个字节;PC 侧如果因为系统调度晚读了几微秒,就会读到已经被覆盖的数据。反过来,PC 侧读得太快,板子还没更新,就读到了重复值。两边的节奏靠延时对齐,在负载变化时必然错。

解决办法是把握手做成真正的双向确认:板子放数据后拉状态线,等 PC 的应答线有效才更新下一个字节;PC 侧读到状态线有效才取数,取完拉应答。状态线和应答线都要做同步处理,避免亚稳态。改完之后回读的稳定性会立刻上来,而且这个改动不涉及硬件。

6. 验收与答辩:八项指标怎么测、怎么讲

验收不是把板子通电、波形出来就完事。评审关心的只有一件事:你说的指标,能不能用一套可重复的方法测出来。我一般会带着学生做一张测试表,每一项都写清楚测试条件、测量方法和判据,答辩时按这张表讲,比放十张波形图都管用。

测试项测试条件测量方法判据
通道数六十四路模拟、八路开关量、一路脉冲逐通道接入已知信号,检查映射全部通道可寻址、无错位
分辨率直流输入输入阶梯电压,看码字台阶台阶数达到 12 位量级
通道隔离单通道加满量程,其余接地读其余通道码字串扰小于约定码字数
采样率满通道轮询抓有效样点使能,测扫描周期与预算表一致
零点漂移输入端短接,连续运行定时记录零点码字漂移在约定范围内
存储容量连续采集至存满读回并统计有效帧数与容量理论值相符
回读正确性回读全程逐页校验和比对无错页、无丢帧
掉电保持采集中途断电重新上电回读已写数据完整可读

答辩讲的时候,顺序比内容重要。第一句话讲约束:六十四路、0~5V、八路开关量、一路脉冲,这个组合决定了不能用现成采集卡。第二句讲取舍:模拟开关级联而不是每通道独立 ADC,理由是面积和 I/O 预算;NAND 而不是 SRAM,理由是掉电保持和写入粒度。第三句讲关键点:通道扫描的建立时间、ADC 流水线延迟的对齐、NAND 的状态查询,这三个点各讲一句为什么容易错、怎么确认对了。最后再放波形和测试表。

有两点要提前准备。一是延迟参数怎么来的,评审很可能会问“你凭什么说延迟是 7 拍”,回答必须是实测:给阶梯输入,抓通道号和码字的跳变位置,量出差值。二是坏块怎么处理,回答要具体到“擦除后读全块、非全 FF 即标记、标记写备用区固定列”。这两个问题答得出来,说明真的调过,答不出来,前面的波形再漂亮也会被追问到底。

从那以后我每次带这类题,都强制要求先写测试表再动烙铁,指标没落到可测的条目上,电路就不开工。这套流程救过我不止一次,也让学生在答辩现场少挨很多问。希望帮到你。

本文还有配套的精品资源,点击获取