跳到主要内容 AD4630 四通道 SPI 模式配置与采集 FPGA 设计 | 极客日志
编程语言
AD4630 四通道 SPI 模式配置与采集 FPGA 设计 基于 FPGA 设计 AD4630 高精度 ADC 芯片的四通道 SPI 采集方案。内容涵盖芯片手册解析、SPI 信号协议与时序分析、寄存器配置流程及 FPGA 状态机代码实现。重点阐述了 CNV 采样时钟的硬件优化方法、回环模式验证配置以及测试模式下的数据采集校验。通过独热码状态机解决时序裕量问题,实现了稳定的数据读取与拼接,为类似高精度模拟信号采集系统提供了参考设计。
观心 发布于 2026/4/5 更新于 2026/4/13 0 浏览一、浅析芯片手册(Data Sheet)
1. 芯片概述
从 Feature 中可以看出,所用的 AD4630-24 为双通道,转换速率最高 2M,逐次逼近型(SAR)AD 转换芯片,其主要采用 SPI 协议通信,支持 1,2,4 路并行转换,降低了对 SCK 的频率要求,使得慢 SCK 也可以完成快速的采样。逐次逼近型 ADC,位数越多,转换的相对速率就要越慢,因为需要一位一位比较,因此 AD4630-24 适合对精度要求特别高,而对转换速率要求一般的场景(比如高精度传感器,医疗仪器等)。在 General Description 中,从' The wide differential input and common-mode ranges allow inputs to use the full voltage reference'可知该器件的输入为差分输入,这将有效的克服共模干扰而带来的 ADC 的误差,也就是意味着,我们需要将采集的信号转换为差分形式给到 AD4630,手册后面附了全差分的配置。
2. AD4630 的 SPI 信号协议介绍
SPI 是摩托罗拉公司提出的一种广泛用于主控于外设之间数据传输的协议,以 SCLK(本文引为 SCK)的时钟为同步时钟基准,收发使用同一源时钟(SCK)以保证同步,在通信中不同步很可能回造成乱码误码,SPI 的时钟速率决定其通信的速率,为全双工通信。SPI 通过拉低片选信号,可以实现多主从机之间的通信。典型的 SPI 的构成为 CS,SCK,SDI,SDO 四根线,有的 SPI 线多一点基本上多的是 SDO,比如 AD4630,SDO0-7,共八组。
从 Data Sheet 中可知:
CS:片选,低有效信号
SDI:接受 FPGA 发来的数据
CNV:关键引脚,AD4630 的转换基准时钟,CNV 的时钟频率决定转换速率,这里援引一下 Pin Function 中对 CNV 的补充介绍。
这表明 CNV 输入的上升沿会启动器件并开始新的转换,该信号必须具有低抖动才能实现 ADC 的指定性能,所以 CNV 时钟的质量会影响 AD4630 转换的性能。
SCK:时钟基准,AD4630 支持三种 SCK 的模式,正常模式(从机),ECHO 回环,HOST 使用内部自己的时钟(作为主机)。最大频率支持为 86MHz。
SDO 组:0-3 对应 1 通道的,4-7 对应 2 通道的,至于用几个就看你配置是几 LANE 了。
BUSY/SCKOUT:在正常 SPI 模式下,BUSY 信号是跟随 CNV 的,当正在转换的时候 BUSY 会置高,转换完了再拉低。(本设计将利用这一特性进行采集)。当 ECHO 回环和 HOST 模式的时候,会时滞一个周期输出 SCK 信号(本设计将利用这一特性辅助验证)。
3. 配置寄存器与时序
本文主要介绍基于 Altera 的 Cyclon4 系列 FPGA(主频为 50MHz)进行 4LANE,SPI 时钟模式,SDR 单倍速率,输出为 24 位差分数据的模式配置,并利用回环和测试模式,用于配置过程中的验证。AD4630 的寄存器配置和 SPI 协议稍微复杂一点,为此,本文采用逐步介绍 +Data Sheet 援引原文+FPGA 需求拆解的方式,力争简洁清晰。
Step1:重置复位
AD4630-24 复位为低有效,本设计采用的是 FPGA 接入 RST 引脚,也就是软件复位。
从'Perform a reset no sooner than 3 ms after the power supplies are valid and stable',可知,AD4630 在上电到稳定需要 3ms 的稳定时间,这段时间 RST 会置高,RST 拉低无效,因此需要 3ms 后再拉低复位。
从'The minimum RST pulse width is 50 ns'可知,RST 的复位低电平的信号至少持续 50ns。
从'After a hardware or software reset, no SPI commands or conversions can be started for 750µs'可知,在复位后存在 750μs 的 SPI 指令和转换时钟的静默期。
因此对于本步 FPGA 的设计,则是需要一个时钟计数器,计数 3ms,50ns,750μs。而且几步跳转,可以使用状态机。
Step2:寄存器配置模式——总体流程
重点在右下角的流程告诉我们,要配置寄存器一共要分三步,首先是读一个虚拟的寄存器地址 0X3FFF(实际是 0XBFFF),然后是你读或者写对应寄存器地址的对应指令(也就是你发送的码是由地址和数据构成的),如果你读完了,写完了所有的操作,那么你要往 0X0014 地址写一个 0X01 数据,以保存并配置模式。
Step3:寄存器配置模式——配置模式寄存器
AD4630 的 Data Sheet 的末尾很贴心的给了所以寄存器的地址和对应的功能,我们选我们要看的看。
重点关注模式配置寄存器(地址 0X20),这将决定我们的 AD4630 如何工作。本设计的工作模式配置为 4Lane,SPI 时钟模式,SDR 单倍速率,24 位差分数据输出。因此我们的数据码为 10_00_0_000=0X80。上面提到本设计会利用回环模式和测试模式辅助验证,回环模式的数据码为 10_01_0_000=0X90,测试模式的数据码为:10_00_0_100=0X84;
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
重点来了,时序,芯片时序的逻辑顺序最好一点都别错。要看清时间的要求和时序的逻辑。配置模式的 FPGA 时序要求还是比较宽松的,时序约束的时候这块可以适当放松一点。
读寄存器配置的时序流程为(本设计的 VIO 接口电压为 1.8V):
CS 至少应在 SCK 上升沿来之前的 11.6ns 就拉低
CS 至少要在 SCK 最后一个下降沿之后的 5.2ns 才能拉高
SDI 的数据至少在 SCK 上升沿来之前的 1.5ns 就应该准备好。
SCK 的周期不得低于 11.6ns,高电平、低电平有效时间不得低于 5.2ns
SCK 一共最多需要 24 个周期才能完成。
SDI 的构成为 1 位读写标志+15 位地址,最高位为 1 代表读,0 代表写(所以那个虚拟地址 0X3FFFF,其实是全码是 0XBFFF)
数据的写入和读出均遵循最高位在前,也就是 MSB 模式(所以上面的数据码也都是高位在前)。
读虚拟地址和写指令,不需要数据传回,不用关心 SDO0
本步的 FPGA,显然众多时序先后顺序和三条线的情况,优选状态机!配置模式的情况下,对时序要求一般,因此我们这里可以用部分独热码的状态机。
本设计的主频时钟为 50MHz,时钟周期为 20ns,显然不能为这些几点几的时钟单独各自拉 PLL,所以流程应该为:CS 拉低,一个时钟后;SDI 开始输出最高位,一个时钟后;SCK 拉高开始 24 个周期计数,SDI 同步发送数据,24 个周期计数完;一个时钟后,CS 拉高。
☆Step3 里面的地址是不含首位的也就是 15 位(但他却披着 0X 的外衣,hhhh 容易误判),所以我们 SDI 要准备的码为:读虚拟地址:0XBFFFFF;写配置模式 4lane,SPI 时钟,SDR 单倍速率,24 位差分数据:0X002080(因为 MSB 所以前面 00 不能少);(回环:0X002090,测试:0X002084);保存并退出:0X1401;
对于配置 AD4630 的 SPI 来说,最折磨的就是搞了一遍,结果不对,还不知道是卡在哪里了,所以我们做一步就验证一步,先保证配置模式生效,在 SPI 模式下是不好判断的。因此我们利用回环模式来验证,将寄存器模式码调到回环。
仿照上文,写一个程序,先将 CS 先拉低,一个周期后,SCK 给 24 个时钟,然后过一个周期再拉高 CS。用示波器/Quartus 的 Signal Tap/Vivado 的 ILA IP 核去检测在 BUSY/SCKOUT 上是否回环了 SCK 的时钟,并检查是否回环了 24 个周期,如果是,那么恭喜你,你的配置的代码是正确滴,别忘了再还原回去喔。
4.AD 数据转换与采集 下一个重点,SPI 模式下的 AD 的数据转换,这块相对复杂一点,我们一点点看。从第一段'Once the conversion completes, CS can be asserted, which causes the current conversion result to load into the output shift register'可知,转换完成后,CS 才可以拉低,当前的转换结果会寄存到输出寄存器上,确保输出的结果暂时不会丢失。
从左图中我们可以得知有两个读取转换结果的窗口区,也就是 Zone1,2。先看 Zone1,在 SPI 模式下,CS 的片选可以在 BUSY 信号结束后就立刻拉低,启动读取转换,如果是回环 Echo 或者主机 Host 模式下,等 300ns 后再拉低 CS 片选。以 2M 的 CNV 时钟为例 Zone1 给我们留的时间是 198.4ns。(这里的 300ns 主要是考虑到 CNV 的 2M 周期,在 SPI 模式下可以看到,是 CNV 低电平一段时间后,BUSY 才会拉低)
从右图中,可以解释一下 Quiet Zone(静默区),通俗点就是,这段时间不能有任何 SPI 的操作,CS,SCK,SDI 都维持该有的状态,不然容易毁坏程序所以这段时间我们是不能执行任何操作的。Zone1 留的窗口期时长有点短,因此 AD4630 还提供了时间更长一点的 Zone2 区。
Zone2 的 CS 拉低应该在下一个 CNV 的上升沿后的 9.8ns 之后(静默期),以 2M 转换速率为例,Zone2 提供了长达 470.6ns 的转换窗口期,如果你的 SDK 很慢,连 4LANE 都不能满足 Zone1 的时长限制的话,可以使用 Zone2 窗口期,但是你要注意你必须得在下一个采样周期的 BUSY 信号下降沿拉低之前的 25ns,就得把 CS 拉高,不然数据就会被下一轮采样覆盖。(简言之就是 BUSY 信号的下降沿更新数据)。
再来关注一下 SPI 模式下的数据转换的时序要求,可以看见 CS 仍然有相对 SCK 超前和滞后的时间。而且转换数据超前 SCK 上升沿就准备好了。
本设计的 SCK 时钟和主频时钟一样为 50Mhz,时钟周期为 20ns,198.4/20 大概是 9 个时钟周期,4LANE 模式,SCK 需要 6 个时钟周期,CS 前置拉低和滞后拉高各要 1 个周期,总共 8 个周期,时间裕量为 38.4ns,是可以满足 Zone1 时间要求的。
本步 FPGA 的需求:采集和转换涉及到多线的要求和逻辑先后顺序,因此也是很推荐状态机的。而且转换是 AD 的关键,我们这里的状态机推荐采用独热码状态机。
二、FPGA 代码设计
1.稳定与复位 需求:等待 3ms 稳定 + 复位信号拉低至少 50s+ 复位信号拉高后内 750us 内不得操作,为了确保稳定,我这里设计的是 6ms+200ns+1ms 的组合。
//============================= // 初始化时间参数定义 //=============================
localparam time_stab = 24'd300000, // 稳定时间(6ms,用于 ADC 上电稳定)
time_keep = 24'd300010, // 复位保持时间(6ms+200ns,低电平复位持续时间)
time_realse = 24'd350010; // 复位释放时间(6ms+200ns+1ms,完成复位后稳定时间)
//============================= // 初始化配置计数器 //=============================
reg cnt_flag; // 初始化完成标志(0:未完成 1:已完成)
reg [23:0] cnt; // 初始化时间计数器
// 计数器逻辑:从 0 计数到复位释放时间,完成后保持终值
always @(posedge CLK or negedge N_RST) begin
if(N_RST == 0) cnt <= 0;
else if (cnt_flag == 1) // 初始化完成后保持
cnt <= time_realse;
else
cnt <= cnt + 1'b1; // 计数递增
end
复位的时序要求没那么严,就用二进制的分离式状态机了,cnt_flag 标志着复位状态的结束,意味着这一阶段已经过完,但我并没有选择让计数器置零,因为这样要翻转很多位,不如直接停在最后一个数,反正也不影响后面。
//============================= // 初始化状态机(跳转与执行分离) // 功能:实现 ADC 上电复位时序控制 //=============================
reg [2:0] state_initial; // 初始化状态寄存器
// 初始化状态定义
localparam empty = 3'd0, // 空闲状态
wait_stab = 3'd1, // 等待稳定状态
rst_keep = 3'd2, // 复位保持状态
rst_relese = 3'd3, // 复位释放状态
rst_end = 3'd4; // 复位完成状态
// 状态机转换逻辑(基于计数器值跳转)
always @(posedge CLK or negedge N_RST) begin
if(N_RST == 0) state_initial <= empty;
else if (cnt <= time_stab) state_initial <= wait_stab;
else if (cnt <= time_keep) state_initial <= rst_keep;
else if (cnt < time_realse) state_initial <= rst_relese;
else state_initial <= rst_end;
end
// 状态机执行逻辑(控制 NRST 和初始化完成标志)
always @(posedge CLK or negedge N_RST) begin
if(N_RST == 0)begin
NRST <= 1; // 复位无效(高电平)
cnt_flag <= 0; // 初始化未完成
end
else begin
case (state_initial)
empty : NRST <= 1;
wait_stab : NRST <= 1;
rst_keep : NRST <= 0; // 触发复位(低电平)
rst_relese: NRST <= 1; // 释放复位(高电平)
rst_end : cnt_flag <= 1; // 初始化完成
default : ;
endcase
end
end
2.初始化模式配置 需求:先读虚拟地址 + 配置模式 + 退出。读/写都涉及,先拉低 CS+ 数据提前准备 + 开始读/写 + 滞后 CS 拉高。三步的读写为了不相互干扰,我每次读写完都延时了一段时间,复用了地址计数器做计数器,减少了资源开支,配置模式对时序的要求一般,但比 part1 要求高一点,因此我在这里设计的是 6 位的局部独热码状态机,前两位表明模式,后四位表明操作阶段,优化了一下时序,其实主要是考虑 read_pre,read 这种来回跳转去回填数时候的时序稳定。
//============================= // 配置寄存器命令定义 //=============================
localparam REG_CONF_MODE = 24'hBFFFFF, // 进入配置模式命令(读虚拟地址 0x3FFF)
MODES_REGISTER = 24'h002080, // 模式配置寄存器(4lane 模式,SPI 时钟,SDR 速率)
EXIT_CONFIGURATION_MODE = 24'h001401; // 退出配置模式命令
//============================= // 配置状态机(SPI 通信控制) // 功能:通过 SPI 接口配置 ADC 寄存器 //=============================
reg [5:0] state_setting; // 配置状态寄存器
// 配置状态定义(局部独热码风格)
localparam stand = 6'b11_0000, // 待机状态
read_cs = 6'b00_0001, // 读操作片选使能
read_pre = 6'b00_0010, // 读操作数据准备
read = 6'b00_0100, // 读操作移位输出
read_after = 6'b00_1000, // 读操作完成
write_cs = 6'b01_0001, // 写操作片选使能
write_pre = 6'b01_0010, // 写操作数据准备
write = 6'b01_0100, // 写操作移位输出
write_after = 6'b01_1000, // 写操作完成
back_cs = 6'b10_0001, // 退出操作片选使能
back_pre = 6'b10_0010, // 退出操作数据准备
back = 6'b10_0100, // 退出操作移位输出
back_after = 6'b10_1000, // 退出操作完成
finish = 6'b11_1111; // 配置完成状态
reg CS_S; // 配置阶段片选信号
reg SCK_S; // 配置阶段 SPI 时钟
reg [4:0] address; // 配置数据位索引
reg flag_setting; // 配置完成标志
// 配置状态机逻辑(控制 SPI 时序和数据输出)
always@(posedge CLK or negedge N_RST)begin
if(N_RST == 0)begin
state_setting <= stand;
CS_S <= 1; // 片选无效(高电平)
SCK_S <= 0; // SPI 时钟初始低
SDI <= 0; // 串行数据输出初始低
address <= 23; // 从最高位开始传输
flag_setting <= 0; // 配置未完成
end
else begin
case(state_setting)
stand: // 等待初始化完成后进入配置
state_setting <= (cnt_flag == 1) ? read_cs : stand;
read_cs: begin
CS_S <= 0; // 片选使能(低电平)
address <= 23;
state_setting <= read_pre;
end
read_pre: begin // 准备当前位数据
SDI <= REG_CONF_MODE[address];
SCK_S <= 0;
state_setting <= read;
end
read: begin // 时钟跳变,移位传输
address <= address - 1;
SCK_S <= 1;
state_setting <= (address == 0) ? read_after : read_pre;
end
read_after: begin // 读命令传输完成
SCK_S <= 0;
CS_S <= (address == 31) ? 0 : 1;
address <= address + 1;
state_setting <= (address == 23) ? write_cs : read_after;
end
write_cs: begin
CS_S <= 0;
address <= 23;
state_setting <= write_pre;
end
write_pre: begin
SDI <= MODES_REGISTER[address];
SCK_S <= 0;
state_setting <= write;
end
write: begin
address <= address - 1;
SCK_S <= 1;
state_setting <= (address == 0) ? write_after : write_pre;
end
write_after: begin
SCK_S <= 0;
SDI <= 0;
CS_S <= (address == 31) ? 0 : 1;
address <= address + 1;
state_setting <= (address == 23) ? back_cs : write_after;
end
back_cs: begin
CS_S <= 0;
address <= 23;
state_setting <= back_pre;
end
back_pre: begin
SDI <= EXIT_CONFIGURATION_MODE[address];
SCK_S <= 0;
state_setting <= back;
end
back: begin
address <= address - 1;
SCK_S <= 1;
state_setting <= (address == 0) ? back_after : back_pre;
end
back_after: begin
SCK_S <= 0;
CS_S <= (address == 31) ? 0 : 1;
address <= address + 1;
state_setting <= (address == 23) ? finish : back_after;
end
finish: begin // 配置完成
SCK_S <= 0;
CS_S <= 1;
SDI <= 0;
address <= 0;
flag_setting <= 1; // 配置完成标志置位
end
default:;
endcase
end
end
3.AD 数据转换与读取 需求:检测 busy 的下降沿 CS 拉低 + 六个通道转换 + 滞后 CS 拉高。对 AD 转换数据部分,这块对时序要求相对高一点,因此我设计的是全独热码状态机,并且不在数据转存期间进行复杂的计算,(比如这里就没用上面配置时候用的来回状态跳转填数据),先用多寄存器把数据存下来,在存完之后才进行整体拼接。数据的输出打了一拍增强一下时序的稳定性。
//============================= // 数据采集状态机 // 功能:从 ADC 读取转换后的数据 //=============================
localparam wait_setting = 11'b0000_0000_001, // 等待配置完成
wait_start = 11'b0000_0000_010, // 等待 ADC 就绪
cs_low = 11'b0000_0000_100, // 片选使能
sck_en = 11'b0000_0001_000, // 时钟使能
convert_1 = 11'b0000_0010_000, // 采集第 1 组数据
convert_2 = 11'b0000_0100_000, // 采集第 2 组数据
convert_3 = 11'b0000_1000_000, // 采集第 3 组数据
convert_4 = 11'b0001_0000_000, // 采集第 4 组数据
convert_5 = 11'b0010_0000_000, // 采集第 5 组数据
convert_6 = 11'b0100_0000_000, // 采集第 6 组数据
convert_end = 11'b1000_0000_000; // 采集完成
reg [10:0] state_convert; // 采集状态寄存器
reg SCK_C; // 采集阶段 SPI 时钟
reg CS_C; // 采集阶段片选信号
// 数据寄存器(每组 4 位,共 6 组拼接成 24 位数据)
reg [3:0] lane1_1,lane1_2,lane1_3,lane1_4,lane1_5,lane1_6;
reg [3:0] lane2_1,lane2_2,lane2_3,lane2_4,lane2_5,lane2_6;
reg [23:0] lane1_O,lane2_O; // 通道 1、2 的 24 位数据
reg [2:0] cnt_convert; // 采集计数器
// 采集状态机逻辑
always@(posedge CLK or negedge N_RST)begin
if(N_RST == 0)begin // 初始化采集相关寄存器
SCK_C <= 0;
CS_C <= 1;
state_convert <= wait_setting;
{lane1_1,lane1_2,lane1_3,lane1_4,lane1_5,lane1_6} <= 0;
{lane2_1,lane2_2,lane2_3,lane2_4,lane2_5,lane2_6} <= 0;
cnt_convert <= 5;
lane1_O <= 0;
lane2_O <= 0;
end
else begin
case (state_convert)
wait_setting: // 等待配置结束完成
state_convert <= (flag_setting == 1) ? wait_start : wait_setting;
wait_start:begin // 等待 ADC 忙信号结束
state_convert <= (BUSY_SCKOUT == 0) ? cs_low : wait_start;
cnt_convert <= 5;
end
cs_low: begin // 使能片选
CS_C <= 0;
state_convert <= sck_en;
end
sck_en: begin // 使能采集时钟
SCK_C <= 1;
state_convert <= convert_1;
end
convert_1: begin
lane1_1 <= {SDO0,SDO1,SDO2,SDO3}; // 通道 1 第 1 组数据
lane2_1 <= {SDO4,SDO5,SDO6,SDO7}; // 通道 2 第 1 组数据
state_convert <= convert_2;
end
convert_2: begin
lane1_2 <= {SDO0,SDO1,SDO2,SDO3}; // 通道 1 第 2 组数据
lane2_2 <= {SDO4,SDO5,SDO6,SDO7}; // 通道 2 第 2 组数据
state_convert <= convert_3;
end
convert_3: begin
lane1_3 <= {SDO0,SDO1,SDO2,SDO3};
lane2_3 <= {SDO4,SDO5,SDO6,SDO7};
state_convert <= convert_4;
end
convert_4: begin
lane1_4 <= {SDO0,SDO1,SDO2,SDO3};
lane2_4 <= {SDO4,SDO5,SDO6,SDO7};
state_convert <= convert_5;
end
convert_5: begin
lane1_5 <= {SDO0,SDO1,SDO2,SDO3};
lane2_5 <= {SDO4,SDO5,SDO6,SDO7};
state_convert <= convert_6;
end
convert_6: begin
lane1_6 <= {SDO0,SDO1,SDO2,SDO3};
lane2_6 <= {SDO4,SDO5,SDO6,SDO7};
SCK_C <= 0;
state_convert <= convert_end;
end
convert_end: begin
CS_C <= 1; //关闭片选
cnt_convert <= cnt_convert - 1; //稳定时序切换
state_convert <= (cnt_convert == 0) ? wait_start : convert_end;
lane1_O <= {lane1_1,lane1_2,lane1_3,lane1_4,lane1_5,lane1_6};
lane2_O <= {lane2_1,lane2_2,lane2_3,lane2_4,lane2_5,lane2_6};
end
default:;
endcase
end
end
//============================= // 输出数据同步 // 功能:打一拍输出,稳定时序 //=============================
always@(posedge CLK or negedge N_RST)begin
if(N_RST == 0)begin
lane_total1 <= 0;
lane_total2 <= 0;
end
else begin
lane_total1 <= lane1_O; // 通道 1 数据输出
lane_total2 <= lane2_O; // 通道 2 数据输出
end
end
补充说明,在配置寄存器阶段,我的 SCK 是人为的 1,0 写的,这其实已经在分频了,如果在转化过程中还人为的写 1,0 实际的 SCK 的时钟是 25M,周期为 40ns,就不够时序裕量了,因此我采用的是,在配置的时候使用人为定义的,在转化模式用系统时钟复用。CNV 的时钟来自 PLL 的 IP 核 2MHz 的输入,采用类似两路选择器控制。
//============================= // 信号控制 //=============================
assign CNV_CLK = (flag_setting == 1) ? CLK_2M : 0; // 配置完成后输出转换时钟
assign SCK = (flag_setting && SCK_C) ? CLK : SCK_S;// 配置阶段用 SCK_S,采集阶段用系统时钟
assign CS = (flag_setting == 1) ? CS_C : CS_S; // 配置阶段用 CS_S,采集阶段用 CS_C
三、CNV 优化与测试验证
1.CNV 采样时钟的硬件优化 前文提到 CNV 的时钟质量很影响转换的情况,在实测中,不论是自己单独写一个高低电平持续时间不一样的采样时钟,还是用 PLL 的 IP 核产生 2MHz 的时钟,他们的抖动感觉都比较大。
巧的是,我之前做的比赛正是基于 FPGA、DDS 的脉冲波信号发生器,甚至还专门设计方案优化了边沿时间过窄导致的过冲和振铃。主要是通过滤波,滤除波形的高频谐波分量,但我不能为一个 CNV 的采样时钟还专门写一套数字滤波器,这样很占 FPGA 资源。巧的是我之前还搞过硬件,所以想到可以利用电容和自身的阻抗进行 RC 低通滤波,经过试验,在我的板子上,CNV 与地并一个 MLCC X7R 0603 容值在 470pf-820pf 之间的电容都可以较好的实现目标效果(强调材质和封装是因为不同材质和封装大小,他自身的 ESR 不一样,可能会有点影响),至于滤波效果就看自己取了,多试几组,注意电容过大(大概 10nF 以上)会滤成三角波,相位时滞就很大了,小了滤波不到位。
来看看滤波后的效果吧,比之前平滑多了。(CNV 高电平启动转换,可根据设计的需要,在 PLL 里面把占空比调小一点,不过不影响,反正转换速率都是 2M)
2.回环模式验证配置 回环模式下,BUSY 作为 SCKOUT,相对 SCK 滞后一个周期,这表明我们的模式配置的程序是对的。(我这里回环的是 6 个)
3.测试模式验证 AD 采集转换 AD4630 贴心的为大家准备了验证采样的测试模式,该模式下,可以修改相应的输出寄存器以改变固定输出,我这里就不修改了,用它默认的 0X5A5A0F0F,这个值会用于两通道,也就是强制让两组 SDO 组都输出这组数据,因为我是 24 位差分数据,所以我的测试结果应该是 0X5A5A0F。
测试结果正确,从这里也能看到,BUSY 信号跟 CNV 是有时差的。(记得选取 MSB 模式,我这里的 total[0:23] 的因为位反了,用的是 LSB)