SPI配置ADC:4线与3线时序及FPGA实现要点
简介:这份FPGA通过SPI配置ADC的系列文档,面向已具备数字电路基础、希望把SPI通信落到ADC寄存器读写上的FPGA开发者与高年级学生,围绕如何用FPGA稳定完成ADC配置这一实际问题展开。内容从SPI全双工总线的基本概念讲起,逐步过渡到4线模式与3线模式的信号差异,并以TI的ADS52J90为例拆解读写时序、SEN/SCLK/SDIN/SDOUT的配合关系以及tSCLK、tSEN_SU、tDSU等参数约束,兼顾原理理解与代码实现前的时序核对。整包为1个docx文档,约787KB,篇幅紧凑,适合作为专题笔记连续阅读或按章节检索。已有1871人学习,说明其在ADC配置入门与工程排错场景中具有一定参考价值。读者可从中获得SPI配置的流程框架、读写操作关键准则、时序裕量评估思路,以及将手册时序图转化为FPGA控制逻辑的切入点。
1. 一块 ADC 焊上板子却不输出数据:SPI 到底是个什么
一块 ADC 焊到板子上、电源也供上了,示波器探头搭上去却只有一条平线——这种事在新手阶段几乎人人遇到过一次。芯片没坏,它只是在等一句指令:用哪个通道、数据用什么格式、采样率怎么分频。负责把这句指令送进去的那条通路,就是 SPI。
SPI 的全称是 Serial Peripheral Interface,串行外设接口,一种高速、全双工、靠四根线就能跑起来的通信总线。它简单到几乎没有协议栈,却能让一颗 FPGA 把配置数据一位一位写进 ADC 内部的寄存器。搞懂它,就搞懂了“芯片之间怎么说话”这件最底层的事——因为绝大多数 ADC、传感器、存储器,上电后第一件事都是等人来配置。
2. 一颗 ADC 为什么必须先被“配置”:寄存器、管脚与四根线的取舍
很多人对 ADC 的想象停留在“输入模拟电压、输出数字码”这一层,觉得它是个纯硬件的黑匣子。实际上现在的高速 ADC 内部更像一台带菜单的小机器:开几个通道、时钟怎么分频、数据用补码还是偏移二进制、输出驱动摆幅多大、要不要打开内部测试图案,全是可选项。这些选项存放在内部的控制寄存器里,上电时是一套保守的默认值,之后靠外部控制器改写。改写这套默认值的通道,就是 SPI。
2.1 上电那一刻,ADC 其实还没准备好干活
寄存器听起来抽象,落到具体功能上就很实在。通道使能是一个:多通道芯片为了控制功耗,默认状态可能只打开部分通道,甚至整颗芯片处于待机,不写寄存器就永远不会出数据。输出数据格式是另一个:二进制补码和偏移二进制之间差了一个最高位的翻转,选错了,你在 FPGA 里解出来的波形会整体上下颠倒,很多人第一反应是去查模拟前端和焊接,白白耗掉几天。
再比如输出接口模式,LVDS 还是 CMOS、几位并行、双沿还是单沿,这些决定了后面数据接收模块怎么写。还有一类容易被忽略的:内部测试模式,芯片能自己产生锯齿波或固定码流,用来验证数字链路通不通。调试时先把测试图案打开、确认 FPGA 收到的数据正确,再去接真实模拟信号,这条路径能省掉大量“到底是模拟问题还是数字问题”的扯皮。
所以“FPGA 通过 SPI 配置 ADC”不是锦上添花的附加功能,它是开机流程里的一环:上电、等电源和时钟稳定、复位、按顺序写一串寄存器、读回校验、然后才开始收采样数据。顺序错了或者漏写,后面全是玄学问题。
2.2 四根线换来的,是封装和布线的自由度
配置接口如果走并行,需要 8 位或 16 位的地址数据复用总线,加上读写控制、片选、若干握手信号,十几个管脚起步。高速 ADC 本身的数据输出就要占掉几十根差分对,封装管脚极其宝贵,每多一根都是成本。
串行方案把这件事压缩到四根线:
| 信号 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SDI | Serial Data In | FPGA 到 ADC | 写入地址和数据 |
| SDO | Serial Data Out | ADC 到 FPGA | 读回寄存器值 |
| SCLK | Serial Clock | FPGA 到 ADC | 位同步时钟 |
| CS | Chip Select | FPGA 到 ADC | 低电平使能 |
管脚少了,封装可以做小,PCB 上的走线也短,对多通道采集板尤其明显:几片 ADC 可以共用同一组 SCLK 和数据线,只给每片单独一根片选。板子层数、走线密度、串扰控制,全都跟着轻松一点。这是工程上非常实际的收益,不是什么理论上的优雅。
2.3 为什么是 SPI,不是 I2C,也不是并行总线
三种接口的取舍很清楚。I2C 只要两根线,但半双工、带地址和仲裁机制,标准速率在几百 kHz 到几 MHz 量级,配置几十个寄存器够用,代价是每次读写都要走一遍起始位、地址字节、应答位,帧开销不小。并行总线最快,但线太多,只在极端场合才值得。SPI 站在中间:四根线、全双工、没有地址仲裁、没有固定帧开销,速率完全由器件手册决定。
以常见的一类高速 ADC 为例,手册给出的时钟周期最小值是 50ns,换算下来 SCLK 的上限就是 20MHz。这个数字对配置几十上百个寄存器来说绰绰有余,写满一整套配置也就几百微秒。
提示:SPI 有一件事必须早点接受——它只有“约定”,没有“标准”。同样叫 SPI,有的芯片先发地址再发数据,有的要先发一字节命令;有的地址 8 位、数据 16 位,有的地址 13 位、数据 8 位;有的上升沿采样,有的下降沿采样。换一颗芯片就要重看一遍时序图,这是常态。
3. 4 线 SPI 的读写时序:地址加数据这 24 个 bit 怎么走
一次 SPI 写操作,本质上是“在若干个时钟周期里,把若干个 bit 按顺序推到一根线上”。SCLK 是节拍器,片选是“我要开始说话了”的旗子。把这三者的关系在脑子里对齐,时序图就不再是天书。
先说明一点:不同厂商对同一组信号叫法不一样。某款高速 ADC 的手册里写的是 SEN、SCLK、SDIN、SDOUT,另一款写的是 CSB、SCLK、SDIO。名字不同,功能对应关系是明确的——SEN 和 CSB 都是片选,SDIN 是数据输入,SDOUT 是数据输出。看时序图时盯波形,不要被字母组合带偏。
3.1 写操作:24 个时钟周期里到底发生了什么
这类 ADC 的写操作非常规整:一次事务固定 24 个 bit,前 8 个是寄存器地址,后 16 个是要写进去的数值,高位在前。
| SCLK 上升沿序号 | SDIN 上的内容 | 说明 |
|---|---|---|
| 1 到 8 | A7 到 A0 | 目标寄存器地址 |
| 9 到 24 | D15 到 D0 | 该地址要写入的 16 位数值 |
| 第 24 个之后 | 无 | 片选拉高,一次事务结束 |
片选在整个 24 个周期里必须一直保持低电平,事务开始前是高电平,结束后回到高电平。这条规则看着琐碎,但它是一条硬边界:片选一旦拉高,芯片就认为这一帧结束了,后面再来的时钟一律不认。
由此可以归纳出三条几乎适用于所有配置型 ADC 的共同准则。第一,无论读还是写,片选必须拉低,否则 SPI 完全不工作,既不读也不写,读写完成后必须拉高。第二,数据在 SCLK 的上升沿被写入芯片,这是采样时刻。第三,送进去的数据一定是“先地址、后数值”,顺序不能反。
把这三条记住,再看任何一颗新芯片的时序图,至少能快速定位到“哪里是地址段、哪里是数据段、片选什么时候有效”。
3.2 时序参数不是装饰:tSCLK、tSEN_SU、tDSU 各管什么
时序图上除了波形,还有一堆标着名字的时间参数。初学者容易把它们当装饰跳过,实际上每一个都对应一段真实的物理延迟。
tSCLK 是时钟周期的最小值,这里给的是 50ns,直接决定了 SCLK 最快只能到 20MHz。tSEN_SU 是片选建立时间,最小值 8ns,意思是片选的下降沿至少要早于第一个 SCLK 上升沿 8ns。为什么需要这个提前量?芯片从“待命”切换到“听”的状态,内部使能电路有延迟,片选刚拉低的那一瞬间它还没准备好接收。
tDSU 是数据建立时间,最小值 5ns,表示 SDIN 上的电平必须在 SCLK 上升沿到来之前至少 5ns 就稳定下来。与之配套的还有保持时间,要求在上升沿之后再维持一段时间。这两个参数合起来,定义了数据在时钟边沿附近必须保持不动的一个窗口。
注意:手册给的是器件本身能保证的边界值,不是让你踩着用的目标值。FPGA 输出寄存器到管脚有几纳秒延迟,PCB 走线每厘米也有延迟,再加上时钟抖动、温度漂移、供电波动,全都在吃掉这点裕量。
这就是为什么代码里要主动留出时间裕量,通常按 20% 到 30% 来放。举个具体的做法:手册要求片选比第一个上升沿早 8ns,而在 5MHz 的 SCLK 下,一个完整周期是 200ns,干脆让片选提前一整个周期拉低,余量极其充裕,代价只是每次事务多花 200ns。写配置这种事总共也没几次,用这点时间换稳定,完全划算。
真跑不通的时候,最省时间的动作是把 SCLK、SDIN、片选三根线同时抓到示波器上看:片选是不是真的在第一个上升沿之前就拉低了,SDIN 在上升沿附近有没有正在跳变。这两眼能排掉大半问题。
3.3 读操作:地址写完之后,为什么要等 tOUT_DV
读操作分两段。前 8 个上升沿,FPGA 往 SDIN 上写 8 位寄存器地址;地址送完之后,ADC 开始在 SDOUT 上把该地址对应的 16 位数值一位一位推出来。
关键在推出来的时机。数据是在 SCLK 的下降沿开始输出的,但从下降沿到电平真正稳定,中间有一段延迟,手册上叫 tOUT_DV,范围是 12ns 到 28ns。也就是说,下降沿刚过的那一小段时间里,SDOUT 上的电平正在翻转,谁采谁错。
所以 FPGA 在上升沿采样是最稳的:上升沿正好落在下降沿之后半个周期的位置,躲开了翻转区。5MHz 时半个周期是 100ns,远大于最坏的 28ns,非常从容。
这里有一笔账值得算清楚。写操作受 tSCLK 最小值 50ns 限制,理论上可以跑到 20MHz。但读操作要同时满足 tOUT_DV,20MHz 下半个周期只有 25ns,比 28ns 的最坏值还小。按最坏情况推,读寄存器时如果还用满速时钟,就可能正好采在数据翻转的瞬间,读回来的值时对时错,看起来像是“偶发通信错误”,实际是频率超了。
实用的结论是:写配置可以跑快,读回校验时把 SCLK 降下来,比如降到 5MHz 到 10MHz,稳得多。读寄存器本来也不追求速度。
4. 3 线 SPI 与三态门:省一根线要付出什么代价
从四根线变三根线,表面上只是把数据输入和数据输出合并成一根 SDIO。但合并之后,同一根线在写的时候是输入口,在读的时候要变成输出口,方向切换的时机就成了一整套配置里最容易翻车的地方。
4.1 命令字变了:1 bit 读写标志、2 bit 字节数、13 bit 地址
4 线模式的帧是“地址加数据”,3 线模式的帧是“命令加地址加数据”。多出来的命令段定义了三件事:这次是要读还是要写,一次操作几个字节,以及目标寄存器的地址。
拆开看:第 1 个 bit 是读写标志,低电平表示写、高电平表示读;接着 2 个 bit 是字节数选择,一般固定写 00,表示每次只操作一个寄存器;然后是 13 位地址;最后是要写入的 8 位数据。
| 项目 | 4 线模式 | 3 线模式 |
|---|---|---|
| 帧头 | 无 | 1 bit 读写标志加 2 bit 字节数 |
| 地址位宽 | 8 bit | 13 bit |
| 数据位宽 | 16 bit | 8 bit |
| 一次写操作总位数 | 24 | 24 |
| 一次读操作总位数 | 8 加 16 | 16 加 8 |
有个巧合值得记一下:两种模式的写操作都是 24 个时钟周期,读操作也都是 24 个时钟周期,只是一个把位数花在地址上、一个花在数据上。记不住具体位宽的时候,先记“每帧 24 拍”这个总数,再去分配。
另外要注意地址空间的变化。4 线模式这里是 8 位地址配 16 位数据,3 线模式变成 13 位地址配 8 位数据。地址从 256 个扩到 8192 个,数据从 16 位缩到 8 位。所以同一份寄存器配置表,换模式之后不能直接照搬,位宽得重新对一遍。
4.2 三态门:SDIO 什么时候该“松手”
读操作是整件事的核心。片选拉低后,FPGA 先把 16 位命令和地址推到 SDIO 上。最后一个地址位在上升沿被锁存之后,ADC 会把这根线的方向从输入切成输出,准备往外推数据。手册给出的切换时间是 tEN_SDIO,最小值 10ns,参考点是 SCLK 的下降沿。
与此同时,FPGA 这一侧必须做同样的事:把自己的 SDIO 从输出切成输入,也就是进入高阻态。两边必须同步,谁慢一步都出问题。
慢了的后果很直接。如果 FPGA 还保持着输出,而 ADC 已经开始驱动,两个推挽输出一个想推高、一个想拉低,中间就形成一条低阻通路,电流很大。轻则读回来的数据全是乱的,重则 IO 长期过热,芯片受伤。这就是总线冲突,是硬件调试里最不想遇到的一类问题。
反过来,如果 FPGA 松手太早,地址还没送完就把线放掉了,ADC 收到的地址是残缺的,读出来的数据来自一个莫名其妙的寄存器。
读完之后还有一次反向切换:ADC 推完 8 位数据要松手,tDIS_SDIO 最小值同样是 10ns,参考点是 SCLK 的上升沿。FPGA 要等这个时间过去,才能重新把 SDIO 拿回来当输出用,准备下一帧。
提示:三态切换的时机不要靠“感觉差不多”,一定要对着时序图数到第几个边沿。晚一个边沿和早一个边沿,症状完全不同,但看起来都像“读不到数据”。
4.3 用 Verilog 落地:分频、状态机、inout 端口
写法上其实和 4 线模式大同小异,多出来的就是三态控制。先解决时钟。假设系统时钟是 40MHz,用一个计数器分频得到 SCLK:
// 40MHz 系统时钟,8 分频得到 5MHz 的 SPI 时钟 // 计数器数到 3 翻转一次,一个完整 SCLK 周期占 8 个系统时钟 reg [2:0] div_cnt; reg sclk_r; always @(posedge clk_40m or negedge rst_n) begin if (!rst_n) begin div_cnt <= 3'd0; sclk_r <= 1'b0; end else if (div_cnt == 3'd3) begin div_cnt <= 3'd0; sclk_r <= ~sclk_r; // 每 4 个系统时钟翻转一次 end else begin div_cnt <= div_cnt + 1'b1; end end这段逻辑的参数含义很直白:比较值取 3,翻转周期是 4 个系统时钟,一个完整 SCLK 周期就是 8 个系统时钟,40MHz 除以 8 等于 5MHz,占空比 50%。想要 10MHz,把比较值改成 1;想要 2.5MHz,改成 7。改完记得回去核对手册里 tSCLK 的最小值,别改出一个超规格的频率。
需要说明的是,这里用的是“计数器加使能”的思路生成时钟波形,而不是把 sclk_r 当成真正的时钟去驱动一堆 always 块。后者在 FPGA 里属于衍生时钟,时序分析工具处理起来很麻烦,能不用就不用。
三态控制是 3 线模式独有的部分:
// SDIO 定义为 inout,tri_en 为高时 FPGA 驱动,为低时高阻 assign sdio = tri_en ? sdio_out : 1'bz; // 读操作:写完最后一个地址位后立刻交出总线 always @(posedge sclk_r or negedge rst_n) begin if (!rst_n) tri_en <= 1'b1; // 复位后默认由 FPGA 驱动 else if (state == ST_RD_ADDR && bit_cnt == 4'd15) tri_en <= 1'b0; // 16 位命令地址发完,切为输入 else if (state == ST_RD_DONE) tri_en <= 1'b1; // 读完,重新拿回总线 endtri_en 这个信号就是三态开关。写操作全程保持高电平,FPGA 独占这根线;读操作在命令地址段保持高电平,等最后一位地址发出去之后拉低,把自己的输出关掉,让 ADC 接管。位计数器 bit_cnt 数到 15 是因为命令加地址一共 16 位,正好在最后一个上升沿之后切换,与手册要求的 10ns 切换时间对得上——5MHz 下半周期有 100ns,余量足够。
状态机的骨架可以很简单:空闲、写寄存器、写完成拉高片选、读地址段、读数据段、结束。每个状态里管好片选电平、位计数、移位寄存器和三态开关这四样东西,逻辑就不容易乱。
5. 最容易搞混的几件事:片选与使能、上升沿与下降沿、三线与四线
SPI 是个“有名字没标准”的接口,各家在命名、帧格式、采样边沿上各行其是。写代码之前如果不把这几个概念分清楚,调试时会走很多本来可以避免的弯路。
5.1 片选和使能信号是不是同一个东西
易混点:手册上写 SEN,另一份写 CSB,还有写 SS 的,看起来像三种不同的信号。
为什么混:命名习惯完全由厂商决定,没有任何强制约定。同一个功能,有人按 Chip Select 缩写,有人按 Serial Enable 缩写,翻译成中文又变成“片选”“使能”“同步信号”。
怎么区分:别看名字,看功能定义。低电平期间芯片响应、读写全程保持低、事务结束拉高——符合这三条的,就是片选,不管它叫什么。有一种情况需要留意:少数芯片会有一个“同步”引脚,作用是把内部时序对齐到外部帧,它可能也是低有效,但只在帧起始时给一个脉冲,不是全程拉低。判断依据还是时序图上的波形形状,不是名字。
5.2 上升沿采样还是下降沿采样,四种模式记不住
易混点:SPI 有极性(CPOL)和相位(CPHA)两个参数,组合出四种模式,网上教程经常让初学者背“模式 0 到模式 3”。
为什么混:模式编号是抽象出来的,不同的教材编号约定还不完全一样,背编号等于背密码。
怎么区分:直接看你要配置的那颗芯片的时序图,找两件事——时钟空闲时是高还是低,数据在哪个边沿被采。这两条一确定,代码怎么写就定了。以本文涉及的这两类 ADC 为例,写入都是在 SCLK 上升沿发生,读出的数据在下降沿推出、FPGA 在上升沿采样最稳。不要去套模式号,套错了反而更乱。
5.3 3 线 SPI 只是 4 线少接一根线
易混点:直觉上以为把数据输入和数据输出短接在一起,四线就变三线了。
为什么混:从线数上看确实只少了一根,硬件上看起来就是合并。
怎么区分:帧格式和方向控制都变了。4 线是“地址加数据”,3 线是“命令加地址加数据”,地址位宽从 8 位变 13 位,数据位宽从 16 位变 8 位。更关键的是,3 线多出了一次每个读周期都要执行的方向切换,需要 FPGA 端用三态门配合。这是两套不同的实现,不是一个改一根线的活。
5.4 SCLK 跑得越快越好
易混点:手册写着最高 20MHz,那就直接拉到 20MHz,觉得快就是效率高。
为什么混:把“器件能承受的上限”和“当前操作能用的上限”混成了一件事。
怎么区分:分开算。写操作受时钟周期最小值限制,可以接近 20MHz。读操作还要额外满足输出数据稳定时间的要求,那个时间的最坏值是 28ns,对应的半周期必须大于它,20MHz 下半周期只有 25ns,不够。所以读寄存器时把 SCLK 降下来,是正确做法而不是保守。配置类操作本身耗时极短,降频的代价几乎为零。
5.5 写完寄存器就算配置成功
易混点:代码跑完不报错,就认为 ADC 已经按预期工作了。
为什么混:SPI 是单向主动的——FPGA 发什么就是什么,芯片不会主动告诉你“我收到了但我不认识这个地址”。
怎么区分:每个写周期结束后,用读操作把刚写的寄存器读回来比对。这一步在 4 线模式下成本很低,因为数据输出有独立的一根线;在 3 线模式下要额外注意三态切换的时机,但也不能省。读回值对不上,说明要么时序不对,要么地址段和数据段的边界错了,问题被限定在一个很小的范围内,比等整块板子跑不出数据再回头查要省事得多。
6. 什么时候用 4 线、什么时候用 3 线:几条真实的判断边界
这两种模式没有优劣之分,选哪个取决于你手上这块板子的约束。
管脚预算是最直接的判断项。一片 ADC 省一根线不算什么,但如果一块采集板上有八片、十六片,而且它们共用一组时钟和数据线,那省下来的就不只是一根线,而是布线空间和层数。反过来,如果板子上只有一片 ADC,管脚富余,那 4 线模式能省掉三态切换这一整块逻辑,出错概率立刻降一个量级。
调试阶段和量产阶段的选择也可能不同。首版打样时,我倾向于用 4 线模式,因为数据输入输出各走各的线,示波器一夹就能同时看到收发两个方向,出问题时信号是干净的。等配置流程稳定了、时序参数都验过了,再考虑为了布线收敛换成 3 线。这个顺序反过来做,等于在同时处理两个未知数。
| 判断项 | 4 线模式 | 3 线模式 |
|---|---|---|
| 管脚占用 | 4 根 | 3 根 |
| 帧格式 | 地址加数据 | 命令加地址加数据 |
| 方向切换 | 无 | 每个读周期一次 |
| 出错概率 | 低 | 中,三态时序是主要风险点 |
| 适合场景 | 首版打样、单片配置、调试期 | 多片共用总线、管脚紧张、配置流程已定型 |
还有一条容易被忽略的边界:SPI 只是配置通道,不是数据通道。ADC 的采样数据量大、连续性强,走的是并行或高速串行接口,SPI 那点带宽根本不够。有人一开始会想“既然 SPI 已经连上了,能不能顺便读采样值”,答案是不能,也不该这么设计。把配置和采集这两条路径在脑子里彻底分开,很多架构上的困惑会自然消失。
反过来说,什么情况下别用 SPI 配置?如果一颗 ADC 的寄存器需要在上百微秒内反复改,比如快速切换增益档位,那串行配置的延迟就会成为瓶颈,得看芯片有没有并行配置口或者引脚直接控制的模式。如果芯片本身提供了引脚绑定的默认配置,而你只需要最基础的工作模式,也可以完全不碰 SPI,靠上下拉电阻定死,省掉一整段上电配置逻辑。
血泪经验是这样的:早些年调一块多通道采集板,3 线模式的配置代码是从别的项目搬过来的,地址位宽没改,一直写不进去,读回来全是 0。当时怀疑过电源、怀疑过焊接、怀疑过时钟,最后把时序图打印出来拿荧光笔一位一位标,才发现命令段少算了一位。从那以后我每次碰新芯片,第一件事是把时序图打出来,把片选拉低的时刻、每个边沿采什么、地址多少位、数据多少位,全用笔标一遍,再动手写代码。这一步花十分钟,能省掉后面一整天。
这份系列资料的完整版把从 SPI 概念、4 线时序、3 线时序到 Verilog 实现串成了一条线,遇到具体芯片时翻对应那一篇对着看就行。希望帮到你。
本文还有配套的精品资源,点击获取