☰
FPGA配置高速ADC:SPI时序24时钟沿与三线四线模式详解
2026/11/3 13:54:27 网站建设 项目流程

简介:这份文档面向FPGA开发工程师与高速ADC应用学习者,聚焦如何用FPGA通过SPI接口完成ADC内部寄存器的读写配置,解决实际项目中ADC上电后无法正常工作、配置时序难以把握的问题。包内共1个docx文件,约787KB,以系列文章形式由浅入深组织内容,便于按主题顺序阅读。文档从SPI基本概念讲起,说明四线模式中SDI、SDO、SCLK、CS各信号的作用,以及三线模式将SDI与SDO合并为SDIO的差异;随后以TI的ADS52J90为例剖析四线SPI写时序,指出SEN拉低使能、地址加数据共24bit连续写入、上升沿采样等共同准则,并解读tSCLK、tSEN_SU、tDSU等时序参数与裕量要求,还涉及读操作中SDOUT在下降沿输出、FPGA宜在上升沿采样的要点。已有1872人学习,适合需要对照datasheet落地SPI配置代码的读者参考。

1. SPI 配置 ADC:为什么 24 个时钟沿就能决定一颗高速 ADC 能不能工作

很多人第一次接触 FPGA 配置 ADC,会以为难点在高速数据接口——LVDS 差分对、源同步采样、眼图收敛。但真正让板子起不来的,往往是那根看起来最不起眼的 SPI 配置线。一颗多通道高速 ADC,上电后内部寄存器全是默认值,增益、偏置、测试模式、通道使能、时钟分频统统没配,数据口出来的就是一堆无意义的码字。SPI 在这里不是“辅助接口”,而是整条信号链的开关。

SPI(Serial Peripheral Interface)本身并不复杂:四根线、全双工、主机给时钟从机跟着走。可一旦把它放到 ADC 配置这个场景里,问题就变成了“时序契约”——地址几位、数据几位、哪个沿写、哪个沿读、片选什么时候拉低拉高、三态门什么时候翻转。这些细节在数据手册里画成时序图,落到 Verilog 里就是状态机加计数器。搞懂这套机制,你才能明白为什么有些配置代码仿真全对、上板却读回全 0,也才能在看别的 ADC 手册时快速抓到关键参数。

2. 四线 SPI 的读写时序:地址加数据为什么必须凑成 24 bit

2.1 信号命名不统一,但角色是固定的

先解决一个容易让人犯迷糊的地方:不同厂商对 SPI 信号的叫法不一样。通用叫法是 SDI、SDO、SCLK、CS,但到了具体芯片上,TI 的高速 ADC 用 SEN、SDIN、SDOUT、SCLK,ADI 的器件又用 CSB、SDIO、SCLK。名字变了,角色没变:SEN/CSB 就是片选,低电平有效;SDIN 是 FPGA 写进 ADC 的数据;SDOUT 是 ADC 回给 FPGA 的数据;SCLK 永远是主机提供的时钟。

四线模式的价值在于读写通道物理分离,FPGA 的输入和输出不需要抢同一根线,逻辑上最干净。代价是多占一根管脚。对于管脚资源紧张的 FPGA 工程,这个代价有时候不能忽略,于是就有了三线模式。但四线是理解一切的基础,先把四线的时序契约吃透,三线只是多了一层三态控制。

2.2 写操作:8 bit 地址加 16 bit 数据,一个沿都不能少

以 TI 的 ads52j90 为例,一次写寄存器的完整动作是:SEN 拉低,然后在 SCLK 的上升沿依次把 8 bit 寄存器地址 A7~A0 和 16 bit 寄存器数据 D15~D0 送上 SDIN,总共 24 个上升沿,写完 SEN 拉高。这里有两个硬约束:第一,24 bit 必须连续不间断,中间不能插入额外的时钟;第二,SEN 在整个 24 bit 期间保持低电平,提前拉高会导致写入被截断。

为什么是“地址在前、数据在后”而不是反过来?因为 ADC 内部的 SPI 从机状态机是按位计数的:它先收满 8 位当作地址锁进地址寄存器,再收满 16 位当作数据写进对应地址。这个顺序是芯片内部逻辑决定的,改不了。所以 FPGA 侧的状态机必须严格按“先发地址、再发数据”组织移位寄存器。

// 4线SPI写操作核心移位逻辑(简化示意) // 假设系统时钟40MHz,8分频得到5MHz SCLK reg [23:0] shift_reg; // {8bit地址, 16bit数据} reg [4:0] bit_cnt; // 0~23,共24个SCLK上升沿 reg [2:0] clk_div; // 8分频计数器 always @(posedge sys_clk) begin if (spi_en) begin clk_div <= clk_div + 1'b1; if (clk_div == 3'd3) begin sclk <= 1'b1; // 上升沿:ADC采样SDIN sdin <= shift_reg[23]; // 先送最高位(地址A7) shift_reg <= {shift_reg[22:0], 1'b0}; bit_cnt <= bit_cnt + 1'b1; end else if (clk_div == 3'd7) begin sclk <= 1'b0; // 下降沿:准备下一位 end end end

这段代码的关键点不在语法,而在“哪个沿送数据”。SCLK 上升沿是 ADC 采样 SDIN 的时刻,所以数据必须在上升沿之前就稳定在 SDIN 上。上面的写法是在产生上升沿的同一拍更新 sdin,实际工程里更稳妥的做法是提前半个周期把数据摆好,让建立时间有余量。bit_cnt 从 0 数到 23,数满即表示 24 bit 发完,此时拉高 SEN,一次写操作结束。

2.3 时序参数不是摆设,是代码里的裕量来源

数据手册给的 tSCLK 最小值 50ns,意味着 SCLK 最高 20MHz。这个数字直接决定分频系数:系统时钟 40MHz 时,至少 2 分频才能不超频,但实际工程里很少有人贴着极限跑,5MHz 甚至更低更常见,因为配置阶段对速度没有要求,稳定压倒一切。

tSEN_SU 最小值 8ns,说的是 SEN 下降沿要比第一个 SCLK 上升沿至少提前 8ns。tDSU 最小值 5ns,说的是 SDIN 数据要比 SCLK 上升沿提前 5ns 稳定。这些参数在写 Verilog 时不会自动满足,得靠状态机在“拉低 SEN”和“发第一个时钟”之间插入足够的空拍。常见的翻车就是状态机切得太快,SEN 和第一个 SCLK 几乎同时动作,仿真看不出来,上板偶发写失败。

提示:配置时钟宁可慢,不要快。SPI 配置通常只在上电时跑一次,多花几微秒换来的是确定性。

3. 读操作的采样沿选择:为什么 FPGA 要在上升沿抓 SDOUT

3.1 读时序的两段结构

读操作不是“直接读”,而是“先写地址、再收数据”。FPGA 先在 SDIN 上发 8 bit 地址,ADC 在收齐地址后,于接下来的 SCLK 下降沿逐位把 16 bit 寄存器值从 SDOUT 送出。也就是说,读操作的前 8 个时钟是写方向,后 16 个时钟是读方向,同一组 SCLK 承担了两个阶段。

这里有个容易被忽略的细节:ADC 在下降沿输出数据,但输出不是瞬间稳定的,要经过 tOUT_DV(12ns~28ns)才有效。如果 FPGA 也在下降沿采样,采到的很可能是正在跳变的电平。正确做法是在上升沿采样——此时距离上一个下降沿已经过了半个 SCLK 周期,数据早已稳定。这就是“从机下降沿发、主机上升沿收”的经典搭配,本质是用半个周期的相位差换建立时间。

3.2 读回值不对,先查采样沿再查地址

实际调试中读回全 0 或全 F,常见原因按概率排:采样沿选错、地址位宽对不上、SEN 提前拉高、SDOUT 没接对管脚。采样沿选错最隐蔽,因为波形上看时钟和数据都在动,但采到的位全是错位的。用逻辑分析仪同时抓 SCLK 和 SDOUT,对照数据手册看数据跳变是否发生在下降沿之后、上升沿之前稳定,一眼就能定位。

读操作的意义不只是“验证写成功”。很多 ADC 的某些寄存器是只读的状态位,比如锁相环锁定指示、过温标志,这些只能通过读操作获取。所以读通路不是可选项,是配置流程的一部分。

4. 三线 SPI 的三态控制:一根线怎么同时当输入和输出

4.1 SDIO 的方向切换点在哪

三线模式把 SDI 和 SDO 合并成一根 SDIO,省了一根线,代价是方向控制。写操作时 SDIO 是 FPGA 输出、ADC 输入;读操作时先写地址(仍是 FPGA 输出),地址最后一位 A0 在上升沿被 ADC 锁存后,ADC 把 SDIO 切成输出,开始回送数据,FPGA 必须同步把 SDIO 切成输入。

这个切换时刻就是整个三线 SPI 最要命的地方。切早了,地址还没发完就被打断;切晚了,ADC 已经在驱动这根线,FPGA 还在输出,两边对顶,轻则读错重则伤管脚。ADI 的 AD9249 手册给了两个参数:tEN_SDIO 最小 10ns,参考点是 SCLK 下降沿,描述 ADC 从输入切到输出的时间;tDIS_SDIO 最小 10ns,参考点是 SCLK 上升沿,描述 ADC 从输出切回输入的时间。FPGA 侧的三态使能必须卡在这两个窗口之外。

4.2 Verilog 里的 inout 与三态门

FPGA 实现三线的核心是把 SDIO 声明为 inout,内部拆成 sdio_out、sdio_oe、sdio_in 三个信号。sdio_oe 为 1 时驱动管脚输出 sdio_out,为 0 时管脚高阻,sdio_in 始终回读管脚电平。

// 三线SPI的SDIO三态控制 inout sdio_pin; // 顶层管脚 wire sdio_in; reg sdio_out; reg sdio_oe; // 1=FPGA驱动, 0=高阻(ADC驱动) assign sdio_pin = sdio_oe ? sdio_out : 1'bz; assign sdio_in = sdio_pin; // 读操作:发完13bit地址后拉低sdio_oe // 写操作:全程sdio_oe=1 always @(posedge sclk or negedge rst_n) begin if (!rst_n) sdio_oe <= 1'b1; else if (state == READ_ADDR_DONE) sdio_oe <= 1'b0; // 地址发完,让出总线给ADC else if (state == READ_DATA_DONE) sdio_oe <= 1'b1; // 读完收回总线 end

sdio_oe 拉低的时刻要落在“地址最后一位上升沿之后、ADC 开始驱动之前”的窗口里。工程上通常用状态机在地址计数满时延迟若干拍再拉低,给 tDIS_SDIO 留裕量。这里没有后悔药,切错一次读回的数据就全乱,而且波形上看起来“线在动”,很容易误判成 ADC 没配置好。

4.3 三线的数据格式比四线多一个命令字节

四线是“地址+数据”,三线是“控制命令+地址+数据”。AD9249 的格式是 1 bit R/W(高读低写)+ 2 bit W1W0(字节数,一般设 0 表示单寄存器)+ 13 bit 地址 + 8 bit 数据。注意地址是 13 位、数据是 8 位,和四线例子的 8 位地址、16 位数据不同——位宽完全由具体芯片决定,不能套用。

写操作发 1+2+13+8=24 bit,读操作先发 1+2+13=16 bit,然后收 8 bit。读写标志位 R/W 是新手最容易漏的:写的时候发 0,读的时候发 1,发反了芯片根本不响应,SDOUT 一直高阻,FPGA 读回全 1。

5. 避坑与排查:配置代码上板失败的几个典型现场

5.1 现象:仿真波形完美,上板读回全 0

原因:仿真里 ADC 模型是理想从机,不体现 tSEN_SU、tDSU 这些建立保持时间。上板后 SEN 和第一个 SCLK 挨得太近,ADC 没识别到片选有效。 解决:在拉低 SEN 和发第一个时钟之间插入至少 2~3 个系统时钟空拍,确保大于手册给的 tSEN_SU 并留裕量。

5.2 现象:写操作偶发失败,重试又好了

原因:SCLK 频率贴着 tSCLK 下限跑,或者 SDIN 在上升沿同一拍才更新,建立时间不足。温度、电压波动时余量被吃掉。 解决:降低 SCLK 频率,数据提前半周期摆好。配置阶段不需要速度,5MHz 以下最稳。

5.3 现象:三线读操作读回值错位一位

原因:sdio_oe 拉低时刻偏早或偏晚。偏早则地址最后一位没发完,偏晚则和 ADC 输出对顶。 解决:用逻辑分析仪抓 SDIO 和 SCLK,确认方向切换发生在地址最后一位上升沿之后、第一个数据位下降沿之前,对照 tDIS_SDIO 调整延迟拍数。

5.4 现象:读操作返回全 1

原因:R/W 位发成了写(0),或者地址位宽搞错(把 13 位当 8 位发)。 解决:核对数据手册的帧格式,逐位对照 R/W、W1W0、地址、数据的位宽和顺序。

5.5 现象:多片 ADC 共用 SPI 时互相干扰

原因:片选译码错误,或者某片的 SEN 没拉高就切换了总线。 解决:确保任一时刻只有一片 SEN 有效,切换前先把上一片 SEN 拉高并等待若干拍。

6. 从时序图到状态机:配置逻辑的设计取舍

把数据手册的时序图翻译成 Verilog,本质是在做一组取舍。第一个取舍是时钟频率:贴着 tSCLK 上限跑能缩短配置时间,但配置只发生在上电瞬间,省下的微秒毫无意义,所以工程上普遍选低速,用确定性换速度。第二个取舍是状态机粒度:把“拉 SEN、发地址、发数据、拉高 SEN”拆成独立状态,代码长但每个状态的时序关系一目了然;揉成一个计数器虽然紧凑,出问题时却很难定位是哪个阶段偏了。我倾向于拆细,因为 SPI 配置的调试成本远高于代码行数成本。

第三个取舍是四线还是三线。四线逻辑简单、无三态风险,管脚够就用四线;三线省管脚但引入方向切换,切换点必须严格对照 tEN_SDIO、tDIS_SDIO 计算。这里没有通用答案,取决于板子布局和 FPGA 剩余资源。

验证方法上,仿真只能证明逻辑自洽,证明不了时序合规。真正靠谱的做法是上板后用逻辑分析仪抓 SCLK、SEN、SDIN、SDOUT 四根线,对照手册逐项量 tSEN_SU、tDSU、tOUT_DV,确认每个参数都在最小值以上且有余量。读操作再配合“写一个已知值、读回比对”的闭环,才算真正验证通过。

我自己的习惯是:任何 SPI 配置模块第一次上板,先不接 ADC 业务逻辑,单独跑一个“写-读-比对”的最小测试,把 24 bit 帧格式和采样沿确认死,再往系统里集成。这个习惯来自一次读回全 0 查了两天的血泪经验——最后发现只是 SEN 拉低和第一个时钟只隔了一个系统时钟。从那以后我每次写 SPI 状态机,都强制在片选和首时钟之间留空拍,并且把时序参数直接写成 parameter 放在文件头,改频率时一眼能看到约束。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询