ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AD7768与FPGA接口驱动设计:Verilog实现SPI配置与DCLK数据捕获

AD7768与FPGA接口驱动设计:Verilog实现SPI配置与DCLK数据捕获 简介这份资源面向FPGA开发工程师与嵌入式学习者提供基于XILINX平台控制AD7768高精度ADC的完整Verilog驱动程序可直接调用并已通过仿真验证适合用于数据采集系统设计、ADC接口调试及课程项目参考。压缩包共72个文件约28.81MB以Verilog源码与头文件为主包含18个.v与19个.vh文件另有7个.xdc约束文件用于引脚与时序定义以及6个VHDL文件、3个IP核配置文件xci/dcp和若干仿真与说明文本覆盖从RTL设计到综合实现的完整流程。内容预览显示顶层模块AD7768.v已集成时钟、电源使能、增益控制、模式配置及同步复位等接口并配套ila_0、fifo_generator_0、clk_wiz_0等IP便于快速搭建调试环境。目前已有46人学习下载读者可借此掌握AD7768的寄存器配置时序、SPI/接口控制逻辑与仿真验证方法也可作为XILINX FPGA驱动开发的参考模板。1. AD7768 与 FPGA 的接口为什么值得单独写一套驱动如果你手上有 AD7768 这块 8 通道 24 位同步采样 ADC又打算用 XILINX FPGA 做数据采集主控那么你迟早会面对同一个问题SPI 配置寄存器好写但真正把 8 通道数据以 256 kSPS 甚至更高吞吐率稳定收进来才是这套方案的核心难点。AD7768 不是普通 SPI ADC它的数据接口是每通道独立的 Σ-Δ 调制位流加 DCLK 同步配置口才走 SPI。这意味着 Verilog 驱动要同时处理两条完全不同的时序链路一条是低速 SPI 写寄存器一条是高速同步数据捕获加抽取滤波。标题里说的「已仿真可以直接调用」落到工程上就是一套可综合的 RTL 加 testbench能让你在 Vivado 里直接例化、跑通行为仿真、再上板验证。它解决的不是「AD7768 是什么」这种科普问题而是「我拿到板子后怎么在两周内把 8 通道数据稳定送进 FIFO」这种落地问题。适合谁适合已经会写 Verilog 状态机、用过 Vivado 做综合实现但对 AD7768 这种多通道同步 ADC 接口时序还没踩过坑的 FPGA 工程师。下面我按自己实际调通的顺序把配置、捕获、仿真、上板排查一条线讲清楚。2. AD7768 的接口时序拆解与 Verilog 顶层规划2.1 先分清 SPI 配置口和 DCLK 数据口AD7768 的引脚里有两组容易混淆的接口。第一组是 SPI 口CS、SCLK、SDI、SDO用来读写内部寄存器配置采样率、滤波器类型、通道使能、功耗模式。第二组是数据口DCLK、DOUTx每通道一根或复用、DRDY。数据口的工作方式是 ADC 在 DCLK 上升沿输出调制位流FPGA 在 DCLK 边沿采样然后自己内部做抽取滤波得到 24 位结果。很多人第一次翻车就是把 DOUT 当成 SPI 的 MISO 去读结果数据全是乱的。血泪经验是SPI 只在初始化阶段用一次数据采集阶段完全靠 DCLK 域的逻辑。所以 Verilog 顶层我一般拆成三个模块ad7768_spi_cfg负责寄存器配置ad7768_data_capture负责 DCLK 域位流捕获和抽取ad7768_ctrl_top做跨时钟域握手和 FIFO 写入。2.2 时钟域划分与复位策略AD7768 的 DCLK 由 FPGA 提供还是 ADC 提供取决于你选的模式。常见做法是 FPGA 用 MMCM 生成 DCLK 输出给 ADC同时用同一时钟的相移版本去采样 DOUT这样能保证建立保持时间。我一般会把 DCLK 设成 MCLK 的分频MCLK 给 16.384 MHzDCLK 给 4.096 MHz对应 256 kSPS 的抽取率。复位策略上SPI 配置完成前不能让数据捕获模块工作否则会收到无效位流。所以顶层用一个cfg_done信号做门控配置完成后拉高数据模块才开始计数和写 FIFO。跨时钟域的地方cfg_done从 SPI 时钟域同步到 DCLK 域用两级触发器打拍这是最基本的 CDC 处理别省。2.3 顶层端口与参数定义下面是我常用的顶层端口定义参数化通道数和抽取比方便复用到不同采样率场景。module ad7768_ctrl_top #( parameter CH_NUM 8, // AD7768 通道数 parameter DECIM_RATE 64, // 抽取比对应 256kSPS 16.384MHz MCLK parameter FIFO_DEPTH 512 // 每通道 FIFO 深度 )( input wire rst_n, input wire clk_spi, // SPI 配置时钟一般 10MHz 以下 input wire clk_dclk, // DCLK 采样时钟 // SPI 接口 output wire spi_cs_n, output wire spi_sclk, output wire spi_sdi, input wire spi_sdo, // AD7768 数据接口 input wire dclk_in, // 来自 ADC 的 DCLK若 ADC 主模式 input wire [CH_NUM-1:0] dout, // 每通道位流输入 input wire drdy_n, // 用户侧读接口 input wire rd_en, output wire [CH_NUM*24-1:0] rd_data, output wire rd_valid, output wire fifo_full );这段代码的关键点在于clk_dclk和dclk_in的关系要明确。如果 FPGA 是 DCLK 主模式dclk_in可以不用直接用clk_dclk输出给 ADC如果是从模式dclk_in接 ADC 输出的 DCLK再用它去采样 DOUT。参数DECIM_RATE决定抽取滤波器的阶数后面会展开。2.4 抽取滤波器的选型理由AD7768 输出的是 1 位 Σ-Δ 位流要得到 24 位 PCM 结果必须做抽取滤波。常见做法是三级级联第一级 CIC 滤波器降采样第二级补偿 FIR第三级半带滤波器。但在 FPGA 里如果资源紧张我一般先用一级 CIC 加一级 FIR 就能满足 256 kSPS 下的 24 位精度要求。CIC 滤波器的阶数 N 和抽取比 R 决定阻带衰减。AD7768 的位流是 1 位CIC 积分器位宽要够否则会溢出。我一般取积分器位宽 24 log2(R*N)比如 R64、N4 时位宽取 24832 位。这个参数设错仿真时数据会周期性跳变上板就是噪声。提示CIC 抽取比和 AD7768 内部滤波器是两回事。AD7768 内部有 sinc 滤波器但输出位流模式下它不生效抽取完全由 FPGA 做。别把 ADC 寄存器里的滤波器配置和 FPGA 抽取搞混。3. 用 Verilog 写 SPI 配置状态机与寄存器写入3.1 AD7768 寄存器写入时序要点AD7768 的 SPI 模式支持 4 线制SCLK 空闲高或低取决于 CPOL 配置。我一般用 CPOL0、CPHA0即 SCLK 空闲低、上升沿采样。每次写寄存器是 16 位1 位读写标志、7 位地址、8 位数据。CS 拉低后第一个 SCLK 上升沿采样第一位CS 拉高结束传输。配置顺序很重要先写通道使能寄存器再写采样率寄存器最后写功耗模式。如果顺序反了ADC 可能进入低功耗模式后不响应后续配置。我踩过的坑是先把所有寄存器写一遍结果发现某些寄存器写入无效后来查手册才知道部分寄存器需要先解锁。3.2 SPI 状态机的 Verilog 实现下面是一个可综合的 SPI 写状态机参数化寄存器地址和数据方便扩展成配置序列。module ad7768_spi_cfg #( parameter CLK_DIV 16 // clk_spi 分频得到 SCLK )( input wire rst_n, input wire clk_spi, input wire cfg_start, input wire [6:0] reg_addr, input wire [7:0] reg_data, output reg spi_cs_n, output reg spi_sclk, output reg spi_sdi, input wire spi_sdo, output reg cfg_done ); localparam IDLE 2d0; localparam WRITE 2d1; localparam DONE 2d2; reg [1:0] state; reg [4:0] bit_cnt; reg [15:0] shift_reg; reg [7:0] div_cnt; always (posedge clk_spi or negedge rst_n) begin if (!rst_n) begin state IDLE; spi_cs_n 1b1; spi_sclk 1b0; spi_sdi 1b0; cfg_done 1b0; bit_cnt 5d0; div_cnt 8d0; end else begin case (state) IDLE: begin cfg_done 1b0; if (cfg_start) begin // 组装 16 位bit150 写bit14:8 地址bit7:0 数据 shift_reg {1b0, reg_addr, reg_data}; spi_cs_n 1b0; bit_cnt 5d16; state WRITE; end end WRITE: begin if (div_cnt CLK_DIV-1) begin div_cnt 8d0; spi_sclk ~spi_sclk; if (spi_sclk 1b0) begin // 上升沿前更新 SDI spi_sdi shift_reg[15]; shift_reg {shift_reg[14:0], 1b0}; bit_cnt bit_cnt - 1b1; if (bit_cnt 5d1) begin spi_cs_n 1b1; state DONE; end end end else begin div_cnt div_cnt 1b1; end end DONE: begin cfg_done 1b1; state IDLE; end endcase end end endmodule逻辑说明状态机在 IDLE 等待cfg_start收到后组装 16 位帧拉低 CS进入 WRITE。WRITE 里用div_cnt分频产生 SCLK在 SCLK 低电平期间更新 SDI保证上升沿时数据稳定。bit_cnt从 16 递减到 1 后拉高 CS进入 DONE 输出cfg_done脉冲。参数说明CLK_DIV决定 SCLK 频率clk_spi50 MHz 时取 16 得到约 3.1 MHz满足 AD7768 的 SCLK 上限。reg_addr和reg_data由上层配置序列提供我一般用一个 ROM 或 case 语句依次发出多条配置。3.3 配置序列的组织方式实际工程里不会只写一个寄存器我一般用一个计数器加 case 语句按顺序发出配置。比如先写 0x01 通道使能再写 0x02 采样率再写 0x03 功耗模式。每条配置之间插入几十个时钟的间隔等cfg_done拉高后再发下一条。这样比一次性写一个长状态机更清晰也方便后期改寄存器值。注意AD7768 的 SDO 在写操作时也可以读回寄存器值做校验。如果上板后发现配置不生效先把 SDO 接上逻辑分析仪看回读值是否和写入一致。不一致多半是 SCLK 相位或 CS 建立时间问题。4. DCLK 域数据捕获与 CIC 抽取滤波的 Verilog 实现4.1 位流捕获的边沿选择与打拍AD7768 在 DCLK 上升沿输出数据FPGA 应该在 DCLK 下降沿采样或者用相移 90 度的时钟采样。我一般用 MMCM 生成 DCLK 的 90 度相移版本clk_dclk_90用它去采 DOUT。这样建立保持时间最宽裕。如果不想用 MMCM也可以在 DCLK 下降沿直接采样但要注意 PCB 走线延迟。采样后先打两拍同步到clk_dclk域再做后续处理。别小看这两拍省掉它仿真可能过上板就是亚稳态。4.2 CIC 抽取滤波器的 Verilog 结构CIC 滤波器由积分器和梳状器组成中间做降采样。对于 1 位输入积分器就是累加器。下面是一个 N4、R64 的 CIC 抽取实现。module cic_decim #( parameter R 64, // 抽取比 parameter N 4, // 阶数 parameter IN_W 1, // 输入位宽 parameter OUT_W 32 // 输出位宽 )( input wire rst_n, input wire clk, input wire din, input wire din_valid, output reg [OUT_W-1:0] dout, output reg dout_valid ); // 积分器级 reg [OUT_W-1:0] integrator [0:N-1]; // 梳状器级 reg [OUT_W-1:0] comb [0:N-1]; reg [OUT_W-1:0] comb_delay [0:N-1]; reg [15:0] dec_cnt; integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i0; iN; ii1) begin integrator[i] {OUT_W{1b0}}; comb[i] {OUT_W{1b0}}; comb_delay[i] {OUT_W{1b0}}; end dec_cnt 16d0; dout {OUT_W{1b0}}; dout_valid 1b0; end else begin dout_valid 1b0; if (din_valid) begin // 积分器级联 integrator[0] integrator[0] {{(OUT_W-IN_W){1b0}}, din}; for (i1; iN; ii1) integrator[i] integrator[i] integrator[i-1]; // 降采样计数 if (dec_cnt R-1) begin dec_cnt 16d0; // 梳状器级联 comb[0] integrator[N-1] - comb_delay[0]; comb_delay[0] integrator[N-1]; for (i1; iN; ii1) begin comb[i] comb[i-1] - comb_delay[i]; comb_delay[i] comb[i-1]; end dout comb[N-1]; dout_valid 1b1; end else begin dec_cnt dec_cnt 1b1; end end end end endmodule逻辑说明积分器在每个din_valid时累加N 级级联。dec_cnt计数到 R-1 时触发一次梳状器运算梳状器做差分N 级级联后输出。dout_valid在输出时拉高一个时钟。参数说明R和N决定滤波器的频率响应OUT_W要足够宽防止溢出。我一般取OUT_W 24 ceil(log2(R*N))R64、N4 时约 32 位。输出再截取高 24 位作为 PCM 数据。4.3 多通道并行与 FIFO 写入8 个通道各用一个cic_decim实例输出拼成 192 位写进 FIFO。FIFO 用 XILINX 的xpm_fifo_async或fifo_generatorIP写时钟是clk_dclk读时钟是用户侧时钟。写使能由 8 个通道的dout_valid相与得到保证 8 通道数据对齐。这里有个坑不同通道的 CIC 延迟可能因为复位不同步而差一拍。我一般让所有通道共用同一个复位和din_valid这样延迟一致。如果发现通道间有固定相位差先查复位同步。提示CIC 输出截位时别直接取高位先做舍入。直接截位会引入直流偏置做 FFT 时底噪会抬高。我一般加 0.5 LSB 再截。5. 仿真验证与上板调试的避坑记录5.1 用 testbench 模拟 AD7768 位流仿真时不需要真实 ADC我一般写一个行为级模型用正弦查表生成 1 位 Σ-Δ 位流。下面是一个简化 testbench 片段。// 生成 1kHz 正弦的 1 位位流DCLK 4.096MHz reg [15:0] phase_acc; wire [15:0] phase_inc 16d16; // 对应约 1kHz always (posedge clk_dclk or negedge rst_n) begin if (!rst_n) phase_acc 16d0; else phase_acc phase_acc phase_inc; end // 简单一阶 sigma-delta 调制 reg sd_out; reg [15:0] sd_integ; always (posedge clk_dclk or negedge rst_n) begin if (!rst_n) begin sd_out 1b0; sd_integ 16d0; end else begin sd_integ sd_integ {phase_acc[15], 15b0} - {sd_out, 15b0}; sd_out sd_integ[15]; end end把这个sd_out接到ad7768_ctrl_top的dout跑足够长的时间看 FIFO 读出的数据做 FFT主频点应该在 1kHzSFDR 和 SNR 符合预期。仿真通过不代表上板通过但仿真不过一定上板不过。5.2 避坑记录五条上板常见问题现象一SPI 配置回读全 0。原因CS 拉低到第一个 SCLK 上升沿之间建立时间不够。解决在 CS 拉低后插入至少 4 个clk_spi周期的延时再发 SCLK。现象二数据捕获全是 0 或全 1。原因DCLK 相位不对采样点落在数据跳变沿。解决用 MMCM 相移 90 度或改用 DCLK 下降沿采样用示波器看 DOUT 和采样时钟的关系。现象三8 通道数据只有前 4 通道有效。原因通道使能寄存器只写了低 4 位。解决检查配置序列里通道使能寄存器的值AD7768 的 8 通道使能是 8 位别只写 0x0F。现象四CIC 输出噪声大FFT 底噪高。原因积分器位宽不够溢出后回绕。解决把OUT_W加大 4 位或者降低抽取比。仿真时用 $display 打印积分器最大值确认。现象五FIFO 偶尔溢出。原因读侧时钟太慢或者写使能没有和dout_valid严格对齐。解决加大 FIFO 深度或者用rd_en反压写侧。我一般会在 FIFO 快满时拉低din_valid丢几个采样点比溢出后数据错位好排查。5.3 仿真与上板结果不一致时看什么先看时钟。仿真里时钟是理想的上板有抖动和偏斜。用 Vivado 的 ILA 抓 DCLK、DOUT、DRDY 三根线看 DRDY 下降沿和第一个 DCLK 的间隔是否符合手册。再看复位。仿真里复位可能只给一个脉冲上板复位释放时机和时钟不同步会导致状态机跑飞。我一般用rst_n同步释放每个时钟域单独同步。最后看约束。DCLK 和 DOUT 之间的建立保持时间要在 XDC 里写set_input_delay不写约束 Vivado 可能布出很差的时序。这一步很多人省省了就等着上板玄学。6. 把驱动封装成可复用 IP 的进阶技巧6.1 参数化抽取比与采样率切换实际项目里经常需要在不同采样率之间切换比如 256 kSPS 做振动监测8 kSPS 做温度采集。如果每次改抽取比都重新综合太浪费时间。我一般把DECIM_RATE做成运行时可配置的寄存器CIC 的dec_cnt比较值从寄存器取而不是参数。这样一套 bit 流可以覆盖多个采样率。具体做法是在ad7768_ctrl_top里加一个 AXI-Lite 从接口用xilinx的axi_lite_ipif或自己写一个简单寄存器组。上位机通过 AXI 写decim_rate寄存器CIC 模块实时读取。注意切换抽取比时 CIC 内部状态要复位否则会输出一段错误数据。我一般在切换后等 2 个抽取周期再使能dout_valid。6.2 用 ILA 和 VIO 做在线调试Vivado 的 ILA 是上板调试的后悔药。我一般会在ad7768_ctrl_top里例化一个 ILA抓dout、dout_valid、fifo_full、cfg_done这几个信号。触发条件设成fifo_full上升沿这样能抓到溢出前的数据。VIO 用来在线改decim_rate和复位不用重新综合。ILA 的采样深度别设太大8 通道 32 位数据深度 1024 就够看一个完整抽取周期。设太大综合时间会爆炸而且波形加载慢。6.3 资源占用与时序收敛的取舍8 通道 CIC 加 FIFO在 Artix-7 上大概占 3000 到 5000 LUT具体看抽取比和位宽。如果资源紧张可以把 CIC 的阶数从 4 降到 3阻带衰减会差一些但 24 位精度下影响不大。时序收敛的关键是 DCLK 域的路径我一般给 DCLK 域单独加set_max_delay约束别让它和 SPI 域混在一起优化。下面是我常用的资源对比基于 Artix-7 XC7A100TDCLK 4.096 MHz8 通道。配置LUTFFDSP备注CIC N4, R64420038000默认配置CIC N3, R64310029000阻带衰减降 10dBCIC N4, R128460041000128kSPS加 FIR 补偿680055008通带更平这张表是我实际综合后看的不同 Vivado 版本会有浮动。如果 LUT 超了先降 N再考虑时分复用 CIC。6.4 一个具体技巧用 DRDY 做帧同步AD7768 的 DRDY 下降沿表示一组新数据准备好。我一般用 DRDY 做帧同步而不是靠 CIC 的dout_valid计数。具体做法是DRDY 下降沿触发一个标志CIC 输出时检查这个标志只有标志有效时才写 FIFO。这样即使 CIC 延迟有波动也不会写错帧。DRDY 是慢速信号和 DCLK 不同域需要同步。我一般用 DCLK 采样 DRDY打两拍后做下降沿检测。这个技巧在采样率切换时特别有用能避免切换瞬间的错帧。注意DRDY 的极性可以通过 AD7768 寄存器配置别默认它是低有效。配置前先读手册确认或者用示波器看。这套驱动我从第一次调通到现在前后改过五六版最大的教训是别信仿真通过就上板。仿真里时钟理想、复位干净上板全是玄学。我现在的习惯是仿真跑通后先上 ILA 抓一轮确认 SPI 配置回读正确、DCLK 采样点居中、DRDY 帧同步稳定再跑长时间采集。希望帮到你。本文还有配套的精品资源点击获取
返回列表