
简介面向Xilinx FPGA开发者的ADS8681高精度ADC驱动完整工程包适用于数据采集、医疗仪器与测试测量等需要16位采样精度的场景。压缩包内共129个文件以VHDL源码为核心同时包含ISE/Vivado工程配置.xise、.ucf、.gise、综合布局布线报告.rpt、.twr、.par以及bit位流文件.bit等便于直接编译烧录与时序分析。已有938人下载学习。该资源价值在于提供了从接口定义、转换时序控制、数据读取到多通道管理的完整驱动逻辑并附带工程生成过程中的日志与约束文件可辅助开发人员快速定位时序问题、理解Xilinx平台下ADC驱动设计思路适合正在学习FPGA数据采集或需要集成ADS8681的工程师参考。1. 一块 16 位 ADC 卡住采集链ADS8681 驱动的问题不在速率而在时序工业控制里PLC 模拟量采集卡和电机控制器常把 ±10V 信号直接送到 ADC省掉一大片运放衰减电路。ADS8681 是这类场景里出镜率很高的一颗单通道 16 位 SAR ADC输入范围大、自带基准但把 FPGA 和它接在一起时很多人发现速率不是瓶颈反而是数据每次错位一个 bit 或根本读不到正确值。原因在于 ADS8681 的数字接口不是普通 SPI 从机那么简单CS_N 下降沿不仅是片选还是转换触发源转换窗口没结束就移时钟读到的会是上一帧甚至全 FFFF。下面从帧结构开始给出一套不依赖 Xilinx、Altera、高云、易灵思任何一家厂商原语的 Verilog 驱动状态机以及仿真模型和上板排错清单。适合手头有 FPGA 开发板、正在被 16 位采集数据对齐问题折磨的工程师。2. ADS8681 的 SPI 帧结构与 FPGA 侧时序要求2.1 把 ADS8681 当普通 SPI 从机接注定要踩坑ADS8681 的数字接口只有四根线CS_N、SCLK、SDI、SDO看起来和普通 SPI 从机无异。但它有一个关键区别CS_N 下降沿不只是“选中芯片”而是直接启动一次模数转换。这意味着一次完整采样事务里CS_N 拉低后必须先等待转换完成再给 SCLK 把数据移出来如果像操作普通从机那样CS_N 刚拉低就立刻开时钟芯片内部可能还在建立采样电容SDO 上输出的就是上一次转换的残留值。把一次事务拆开看更清楚。第一段是触发加转换CS_N 拉低内部采样保持电路固定输入电压SAR 逻辑开始逐次逼近这段时间里 SCLK 必须保持静止。第二段是读结果转换结束后主机提供 16 个 SCLK 脉冲SDO 在每一个 SCLK 下降沿更新一位MSB 在前主机在上升沿采样。整个流程共用同一个 CS_N 低电平区间CS_N 拉高后该帧结束。对刚做 fpga 入门项目的人来说第一反应往往是去调片上 SPI IP 的波特率和触发模式但这里真正需要做的是让片选逻辑和转换完成时间互相配合。我见过不少工程把 ADS8681 接到 ZYNQ 的 PS 端 SPI 控制器上配置了 CPOL/CPHA 却仍然读到乱码原因就是控制器在 CS 有效后自动开始发时钟而不是等待转换窗口。对 fpga 开发来说与其在 IP 核参数里找“delay after CS”不如直接用状态机控制 CS 和 SCLK 的先后顺序这样每一拍发生在哪个时钟周期都完全可预期。2.2 SDO 在下降沿更新采样沿与 SPI 模式要对应ADS8681 的 SDO 在 SCLK 下降沿刷新所以主机的采样沿必须避开这个变化点。手册通常推荐模式 1CPOL0CPHA1或模式 2 来配合用 FPGA 自带硬核或软核 SPI 时把模式设对是第一道门槛。我写 FPGA 驱动时不依赖模式下定义直接按物理时序走在 SCLK 上升沿读取 SDO因为下降沿之后 SDO 已经稳定了半个 SCLK 周期。这里有一个容易忽略的细节SCLK 的低电平宽度决定了 SDO 翻转后有多少建立时间。如果你的 SCLK 是 50% 占空比从下降沿到上升沿隔着整整半周期对几百纳秒的 SDO 翻转延迟来说足够但如果 SCLK 是用门控时钟或组合逻辑拼出来的边沿可能带毛刺SDO 的建立时间会被压缩。所以驱动里生成 SCLK 的逻辑建议只在时钟上升沿改变 SCLK 电平不要在组合逻辑里直接赋值。2.3 三个决定驱动成败的时序参数下面这张表是写驱动时必须在手册里确认的时间参数。不同批次的芯片手册给的名字略有差异但含义一致。参数含义对 FPGA 驱动的影响t_CONVCS_N 下降沿到转换完成的时间决定触发后要等多少个时钟周期才允许发 SCLKt_CS-SCLKCS_N 有效后到 SCLK 开始翻转的最小间隔上电后第一笔 SCLK 不能来得太急t_ODSCLK 下降沿后 SDO 翻转的最大延迟决定你的采样沿放在哪个位置t_CS_HIGH两次 CS_N 低电平之间的最小间隔限制连续采集时的最高帧率t_CONV 是状态机里最重要的参数。它决定了一次采集的最短周期也直接影响你能达到的最大采样率。t_CS-SCLK 在大多数板卡上由状态机的初始化分支天然满足但如果你用内部延迟链去凑时间要格外小心。t_OD 则用于验证采样沿仿真或 ILA 里看 SDO 相对 SCLK 的相位只要采样点落在 SDO 翻转后、下一次翻转前的稳定区间即可。2.4 IO 电平、推挽和 FPGA 引脚约束ADS8681 的数字 IO 电压由 VIO 引脚决定常用配置是 3.3V。Xilinx 开发板接 3.3V bank 时引脚约束写成 LVCMOS33SCLK、SDI、CS_N 都是推挽输出不需要外部上拉。FPGA 的 IO 并没有类似 ARM 芯片那种完整的上拉下拉模式矩阵Xilinx 和 Altera 的普通 IO 默认就是推挽SDO 作为输入更不需要上拉电阻悬空的高阻态反而会带来噪声。国产 FPGA 的逻辑和 Xilinx 类似高云和易灵思的 LVCMOS33 约束只是名字略有差别。注意 SDI 不能悬空如果只使用默认输入范围、不写寄存器把 SDI 固定拉低即可上电瞬间不会被随机电平写坏配置。引脚约束里我一般这样写。set_property -dict {PACKAGE_PIN M14 IOSTANDARD LVCMOS33} [get_ports cs_n] set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVCMOS33} [get_ports sclk] set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports sdo]逻辑说明PACKAGE_PIN 后面的引脚号要按你的板卡原理图改这里只是示例。把 sdo 约束成普通输入后综合工具就知道它是外部芯片的反馈信号不会被优化掉。上板前还要用万用表量一下 VIO 引脚的电压确认 ADC 和 FPGA 在同一个电平域否则即使时序全对读回来的数据也会因为电平窗口不匹配而偶发跳变。3. 用 Verilog 写一套自带转换窗口的 ADS8681 驱动状态机3.1 状态机骨架先等转换再移 16 位数据驱动状态机只做三件事空闲等待启动、拉低 CS 等待转换、产生 16 个 SCLK 读取结果。状态定义用两个 bit 的本地参数不引入外部枚举类型方便在旧版综合工具和国产工具链里直接跑。设计核心是让 CS_N 拉低的时间窗口足够长并且在这个窗口内严格只产生 16 次 SCLK 翻转。这样下游拿到的 data 永远是完整的一帧不会因为多一个时钟或少一个时钟导致整帧错位。用状态机而不是 SPI IP还有一个好处是将来如果要接两片 ADS8681只需要复制模块并给每一片独立的 cs_nSCLK 和 SDO 共享即可可扩展性更好。3.2 参数化转换窗口与 SCLK 生成下面这个模块把转换等待时间参数化成 TC_US系统时钟频率用 CLK_HZ 传入。TC_CNT 在模块内部自动换算成时钟周期数改板卡、改时钟都不需要手改计数器位宽。module ads8681_spi_drv #( parameter CLK_HZ 50_000_000, // 系统时钟频率 parameter TC_US 1 // 转换等待时间按手册确认 )( input wire clk, input wire rst_n, input wire start, // 单周期脉冲启动一次转换 output reg busy, // 忙标志 output reg [15:0] data, // 采样结果 output reg valid, // 数据有效单周期脉冲 output reg cs_n, // 片选低有效 output reg sclk, // SPI 时钟 output reg sdi, // 配置数据默认拉低 input wire sdo // ADC 数据输出 ); localparam TC_CNT (TC_US * CLK_HZ / 1_000_000) - 1; localparam IDLE 2d0; localparam CONV 2d1; localparam READ 2d2; reg [1:0] state; reg [15:0] shift; reg [4:0] bit_cnt; reg [15:0] wait_cnt; reg sclk_high; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; cs_n 1b1; sclk 1b0; sclk_high 1b0; bit_cnt 5d0; wait_cnt 16d0; busy 1b0; valid 1b0; data 16d0; shift 16d0; end else begin valid 1b0; case (state) IDLE: begin busy 1b0; if (start) begin cs_n 1b0; // 拉低片选启动转换 wait_cnt TC_CNT; busy 1b1; state CONV; end end CONV: begin if (wait_cnt 16d0) begin state READ; bit_cnt 5d0; sclk_high 1b0; sclk 1b0; end else begin wait_cnt wait_cnt - 1b1; end end READ: begin if (sclk_high) begin // SCLK 高电平结束拉低 sclk_high 1b0; sclk 1b0; if (bit_cnt 5d16) begin state IDLE; cs_n 1b1; // 释放片选 data shift; // 锁存结果 busy 1b0; valid 1b1; end end else begin // SCLK 低电平结束拉高并采样 SDO sclk_high 1b1; sclk 1b1; shift {shift[14:0], sdo}; bit_cnt bit_cnt 1b1; end end default: state IDLE; endcase end end assign sdi 1b0; // 默认不写寄存器接固定低电平 endmodule逻辑说明SCLK 由 sclk_high 寄存器控制翻转每个 SCLK 周期占两个系统时钟周期所以 SCLK 频率是系统时钟的一半。50MHz 系统时钟下得到 25MHz SCLK对大多数 SPI ADC 来说足够也不需要额外 PLL。采样发生在 SCLK 从低拉高的同一拍把 sdo 移入 shift 寄存器每移入一位 bit_cnt 加一。当 bit_cnt 达到 16 时说明 16 位已经全部移完下一拍拉高 CS_N 并锁存数据到 data 输出同时给出单周期 valid 脉冲。参数说明TC_US 是转换等待时间单位为微秒。代码里 TC_CNT 用 CLK_HZ 自动换算例如 50MHz、TC_US1 时TC_CNT49等待时间约 1us。手册如果给的是负温度系数或最坏情况下的数值建议按最坏值再留 20% 余量采样率损失不大但稳定性提升明显。bit_cnt 定义成 5 位是因为它要计到 164 位会在第 16 次采样时溢出成 0导致状态机提前结束。3.3 需要改输入范围时怎么补写寄存器帧ADS8681 默认输入范围是 ±10.24V很多变送器和工业板卡直接用这个档位就好。如果需要改成 ±5.12V、±2.56V 或 0-5V 单极性范围就要在初始化阶段写一次 range 寄存器。写帧仍然复用这四根线CS_N 拉低后先在 SCLK 上按 MSB-first 移出 16 位写指令指令字里包含写/读标记、寄存器地址和待写数据位域定义以手册为准随后可以补一个 16 位数据字或者直接拉高 CS_N。我一般做成单独的初始化状态上电复位释放后执行一次然后再进 IDLE 等待转换启动。提示如果你拿到的驱动工程里已经有 range 配置时序优先复用并按你的输入信号幅度把范围选到还有余量的档位。写完寄存器后拉高 CS_N 至少空一拍避免下一次 CS 下降沿把寄存器写帧误判成转换触发。4. 用 testbench 验证驱动SDO 模型、波形与常见故障4.1 先做一个行为级 SDO 模型把时序写清楚没有真实芯片时testbench 里挂一个行为级 SDO 模型就能把驱动跑起来。这个模型不模拟内部 ADC 精度只模拟三件事CS_N 下降沿启动转换、转换完成后允许输出、每个 SCLK 下降沿更新一位 SDO。我用固定值 0x1234 作为仿真样本主机的 data 如果能稳定读出 0x1234说明采样沿和帧长度都正确。module ads8681_bfm #( parameter TC_US 1 )( input wire cs_n, input wire sclk, input wire sdi, output reg sdo ); reg [15:0] sample 16h1234; reg conv_done; reg [4:0] bit_cnt; always (negedge cs_n) begin conv_done 1b0; #(TC_US * 1000) conv_done 1b1; end always (negedge sclk) begin if (conv_done) begin sdo sample[15 - bit_cnt]; bit_cnt bit_cnt 1b1; if (bit_cnt 5d15) bit_cnt 5d0; end else begin sdo 1b0; end end endmodule逻辑说明第一个 always 在 cs_n 下降沿把 conv_done 清零再经过 TC_US 微秒的仿真延时拉高模拟转换时间。第二个 always 在每次 sclk 下降沿把 sample 对应位送到 sdo和真实芯片“下降沿更新”的行为一致。bit_cnt 循环输出 16 位sample[15] 到 sample[0] 依次出现。如果你的驱动采样沿是下降沿而不是上升沿BFM 会立刻暴露问题读到的值往往会是 0x2468 这类左移或右移后的结果。4.2 用 ModelSim 或 Vivado 仿真时的观察姿势Intel FPGA 可以直接用 Quartus 自带的 ModelSim Intel FPGA Starter Edition不需要额外装第三方工具Xilinx 用 Vivado 自带的 xsim 也够用。testbench 里实例化 DUT 和 BFM连接四根 SPI 线然后每 10us 给一个 start 脉冲跑 100us。仿真时间设长一点没关系关键是看几组连续转换确认每一帧的 data 和 valid 稳定。波形窗口里至少拉五根信号cs_n、sclk、sdo、data、valid。重点确认三件事cs_n 拉低到第一个 sclk 下降沿之间的间隔大于 TC_USsclk 上正好数出 16 个下降沿valid 和 data 在同一拍出现。如果 data 一直等于 16h0000先看 BFM 的 conv_done 有没有正常拉高如果 data 是 0x2468 这种错位值几乎可以断定是采样沿放错位置。4.3 板上实测之前先对号入座仿真通过后上板还会遇到一些环境问题。下面这张表是我在调试 ADS8681 时最常碰到的现象和排查顺序。现象常见原因检查顺序读数全部是 0xFFFFSDO 一直为高CS_N 没拉低VIO 供电异常量 VIO看 SDO 空闲电平读数全部是 0x0000SDI 悬空导致配置被破坏地线接触不良查 SDI 固定电平测参考电压读数错 1 位左移或右移采样沿放错位置仿真和 ILA 对比 sdo 相位第一个数正常后面全错SCLK 多给了或者帧间隔太短在波形里数 SCLK 个数偶尔跳变误差很大SDO 走线太长信号反射PCB 加串阻降低 SCLK 频率排查顺序上建议先用示波器测 SDO 的静态电平CS_N 拉高、没有任何时钟时SDO 如果稳定在某一电平至少说明芯片供电和复位没问题。然后再抓一组 CS_N 和 SCLK 的时序确认片选窗口宽度。把廉价的信息先拿到手再动代码。5. 让 ADS8681 驱动能上线复位顺序、多片级联和 FIFO 收口5.1 复位顺序与上电时序ADS8681 在 VIO 上电后会经历一段内部复位时间复位完成前给它发 SCLK 会被忽略或引起状态紊乱。我一般在上电后、发第一帧之前安排一个等待计数器系统复位释放后cs_n 保持高电平至少若干微秒再进入 IDLE 等待 start。如果需要写 range 寄存器把写帧放在这段等待之后、正式采集之前。不要用纯延时循环硬靠时钟数最好把等待时间也参数化方便不同板卡上电慢的场合加长。5.2 多片级联共享 SCLK 与 SDO分 CS_N多通道系统如果用了两片或四片 ADS8681常见做法是共享 SCLK 和 SDI每一片单独接一根 cs_n。读取时先让需要转换的片同时拉低等待转换窗口结束然后一次只把当前要读的片选拉低其他片保持高电平避免多片 SDO 并联冲突。每片的结果用转换通道编号一起写入 FIFOvalid 作为写使能。从 fpga 与 pcb 开发互动的角度看SDO 是多片共用的信号走线长了会产生反射。我的经验是 SDO 走线尽量控制在 5cm 以内靠近 FPGA 引脚处加 22Ω 到 33Ω 串阻并且不要在 SDO 上额外接上拉。多片共用 SDO 时如果没有三态控制驱动状态机必须在每一帧结束后把 cs_n 拉高让未选中的芯片 SDO 处于高阻状态。5.3 FIFO 收口与固定样本验证最后一个建议是在驱动模块和算法模块之间加一个 FIFO而不是让算法直接对接驱动输出。data 和 valid 作为 FIFO 写入端算法侧按自己的节拍读取。这样做的好处是将来换采样率、改输入范围、从 16 位补码改成无符号数都只动 FIFO 出口的转换逻辑不用碰驱动状态机。Xilinx 可以直接用 FIFO IP 的异步读端口高云和易灵思也提供类似的 Standard FIFO IP。FIFO 的写侧要注意跨时钟域问题如果 FIFO 读时钟和 ADC 驱动时钟不是同一个valid 信号必须先打两拍同步。虽然这里只有一个脉冲但数据总线上多位信号不能只靠打拍解决正确做法是数据随 valid 写入跨时钟 FIFO由 FIFO 内部指针保证可靠性。5.3.1 用固定样本验证整条链路新板卡到手后我的第一个验证手段是把 ADC 输入短接到 AGND连续读三次。如果三次结果都在 ±2 LSB 以内说明片选时序、采样沿、电平配置三个关键点全部正确接下来再考虑校准和滤波。本文还有配套的精品资源点击获取