FPGA 入门 46:SPI 读取 ADC 入门——主控状态机与采样
更新时间:2026-09-12。本文是
fpga/入门层第 48 篇。前置:状态机入门、计数器与时钟使能、时钟与复位;应用:定点数、FIR 滤波;协议深入:进阶 低速接口 UART/SPI/I2C。
本文要回答的问题
- ADC 的采样、量化、编码三个步骤在芯片内部怎么发生?分辨率、采样率、参考电压、ENOB 各是什么意思?
- SPI 为什么能成为最常用的传感器接口?它和 I2C/UART 在时序、寻址、吞吐上怎么取舍?
- CPOL/CPHA 四个模式在波形上的精确区别是什么?为什么同样叫「上升沿采样」,mode0 和 mode3 的实现代码不一样?
- 一次 ADC 转换的完整时间预算:CS 建立、命令位、流水线延迟(本次命令读上次结果)、转换时间,最大采样率怎么算?
- 单端/差分输入、通道选择、单极性/双极性码、左对齐/右对齐这些配置如何反映在命令字和数据整理代码里?
- 多从设备、菊花链、四线/三线 SPI 在板级和状态机上如何处理?
- 采样数据从比特到电压再到滤波显示的完整链路怎么验证?参考电压噪声、输入阻抗、混叠在入门阶段要注意什么?
读完本文,你应当能拿到任意一颗 SPI ADC 的数据手册,独立画出帧时序、写出主控状态机、建行为模型仿真,并在上板后用 ILA 对拍四线波形确认采样正确。
一、ADC 与 SPI 基础
ADC:code ≈ Vin / Vref × 2^N (12 位、3.3V → 1 LSB ≈ 0.806 mV)
SPI 四线(主机=FPGA):
SCLK 串行时钟(主产生)
MOSI 主→从:发通道/配置
MISO 从→主:读转换结果
CS_N 片选(低有效),一帧拉低二、四种 SPI 模式(按手册选)
| 模式 | CPOL | CPHA | 空闲电平 | 采样沿 |
|---|---|---|---|---|
| 0(ADC 常见) | 0 | 0 | 低 | 上升沿 |
| 1 | 0 | 1 | 低 | 下降沿 |
| 2 | 1 | 0 | 高 | 下降沿 |
| 3(ADC 常见) | 1 | 1 | 高 | 上升沿 |
CPOL/CPHA 配错会在错误边沿锁存 MISO、数据整体错位——头号坑。
三、典型帧(以手册时序图为准)
CS_N 拉低
→ MOSI 发 起始位+通道/配置;MISO 在规定采样沿移出 [前导0][N 位结果]
→ 收满 N 位,CS_N 拉高,等待转换
(可能“本次发命令、读的是上次结果”;位数/对齐/单次连续逐芯片不同)四、主控状态机骨架(单时钟域 + tick)
logic [7:0] div; logic tick;
always_ff @(posedge clk)
if(rst) begin div<=0; tick<=0; end
else begin div <= (div==DIV-1)?0:div+1; tick <= (div==DIV-1); end
always_ff @(posedge clk) begin
if (rst) begin state<=IDLE; cs_n<=1; bcnt<=0; rx<=0; end
else case (state)
IDLE: if (start) begin cs_n<=0; bcnt<=0; state<=SHIFT; end
SHIFT: if (tick) begin
mosi <= cfg[NBITS-1-bcnt]; // MSB 先发
rx <= {rx[NBITS-2:0], miso}; // 规定沿采样
bcnt <= bcnt+1;
if (bcnt==NBITS-1) state<=DONE;
end
DONE: begin cs_n<=1; data<=rx; valid<=1; state<=IDLE; end
endcase
end
// 输出并行 data + valid;采样率由“每 N ms 给一个 start”决定五、对接与验证
换算:Vin ≈ code × Vref / 2^N(注意对齐/差分符号位)
下游:数码管/UART/VGA 显示,或送 FIR(入门 46)
仿真:ADC 模型在 MISO 回放已知码,核对并行 data 与 valid
上板:电位器给 0/半量程/满量程,ILA 抓四线对拍时序图六、ADC 基础:采样、量化、参考电压
ADC 把连续模拟电压变成离散码,分三步:采样保持(在一个时间点冻结电压)、量化(和若干比较电平比较得到最接近的等级)、编码(输出二进制补码或原码)。两个核心指标:分辨率 N 位给出 2^N 个等级,12 位即 4096 级;采样率 每秒多少次转换,由内部转换时间和接口读取速度共同限制。还有一个必须知道的现实指标 ENOB(有效位数):由于热噪声、积分/微分非线性(INL/DNL)、参考电压抖动,标称 12 位的 ADC 在高频下实际有效精度可能只有 10 位出头,数据手册的 ENOB/SINAD 曲线比标称位数更可信。
参考电压 Vref 决定满量程:输出码约等于 Vin/Vref×2^N(单极性),它同时是量化的尺子,尺子本身的噪声和温漂直接变成结果误差。所以 Vref 引脚通常要并联一个 0.1μF 陶瓷电容和一个较大的储能电容,布线上远离开关电源;用 VDD 当 Vref 是最省事但精度最差的选择(电源纹波直接调制数据)。差分输入时公式变成 code≈(V+−V−)/Vref×2^N,共模噪声被抵消,工业现场几乎都用差分。入门电位器实验可以单极性,接真实传感器应尽早理解差分和仪表放大。
采样定理只说「采样率大于两倍带宽」,实际系统还要在 ADC 前加抗混叠模拟低通(截止频率略高于信号带宽),否则高于奈奎斯特的噪声会折叠进基带且无法用数字滤波去除。数字侧的 FIR 滤波(见 FIR 滤波入门)处理的是已经混叠完的数据,救不回来。
七、SPI 协议为什么适合这件事
SPI 是主从结构的同步串行接口:时钟完全由主机产生,没有应答、没有设备地址(靠独立 CS 选片),数据全双工流动——MOSI 和 MISO 在同一帧里同时移位,这是它和 UART/I2C 最大的结构区别。由此带来三个特性:①协议极简,几乎任何芯片都能实现,FPGA 侧一个移位寄存器加位计数器就是主机;②没有固定速率,SCLK 频率由主机在从机允许范围内任意选,低速传感器 1MHz、高速 ADC/Flash 可达几十 MHz;③帧格式由从机定义(多少位、先发谁、空闲电平),没有字节级标准,每换一颗芯片都必须重读手册。和 I2C 比,SPI 用线多(每多一个从机多一根 CS)但吞吐高、协议开销小;和 UART 比,SPI 有时钟同步所以不用约定波特率、不会有采样误差。SPI 的这些特点决定了它是传感器、Flash、ADC、显示屏模块最常见的接口(协议全景在进阶 低速接口)。
八、四种模式的精确波形
用一张时序心智图理解 CPOL/CPHA:CPOL 决定 SCLK 空闲电平。CPHA 决定在时钟周期的「第一沿」还是「第二沿」采样:第一沿对 mode0 是上升沿(空闲低后的第一个沿),对 mode2 是下降沿(空闲高后的第一个沿)。
- mode0(CPOL=0,CPHA=0):空闲低;主机在下降沿更新 MOSI,从机在上升沿采样;MISO 同理在上升沿被主机读入。最常见。
- mode1(0,1):空闲低;第二沿(下降沿)采样。
- mode2(1,0):空闲高;第一沿(下降沿)采样。
- mode3(1,1):空闲高;第二沿(上升沿)采样。和 mode0 的采样沿同为上升沿,但数据建立的时间窗在时钟半周期的另一侧。
实现建议:在系统时钟域用「相位计数器」生成 SCLK,并严格定义两个 tick——drive_tick(更新输出)和 sample_tick(采 MISO),两者间隔半个 SCLK 周期。mode 切换只是这两个 tick 的相位交换,不要为每个模式写一套代码。mode0/mode3 都在上升沿读 MISO,但区别在 CS 拉低后第一位的建立时间:mode0 的第一位在 CS 后就要就绪(有些芯片要求 CS 到首个 SCLK 至少若干 ns),mode3 的第一位有一个空闲高电平做缓冲。读数据错位且每位都错,优先怀疑相位;读出来数据差一位(数值大约翻倍或减半),优先怀疑帧对齐(前导位、MSB/LSB)。
九、时间预算:采样率不是只看 SCLK
很多新手以为 SCLK 跑 20MHz 就能每秒读 20M/16≈1.25M 个样本,实际要算完整周期:一次读取 = CS 建立时间 + 16(或 N)个 SCLK 周期 + CS 拉高保持时间 + 芯片转换时间。以「命令和结果同帧、流水线式」ADC 为例:CS 建立约几十 ns,16 位在 10MHz 下是 1.6μs,片间 CS 高电平至少一个转换时钟,转换本身可能再要 1~2μs,实际吞吐约 300~400kSPS。多通道轮询时还要乘通道数并加通道切换的建立时间。更高采样率的 ADC 改用并行/LVDS/串行 JESD204 接口,不再是 SPI 范畴。
连续采样模式(CS 常低、用帧同步脉冲)能省 CS 开销,但要确认芯片支持且板上没有其他从机共用总线。流水线语义「本次命令、读上次结果」几乎是多通道 ADC 的通用设计,FPGA 侧要在通道号和返回数据之间做一拍/一帧的标签对齐,否则通道 0 的数据标到通道 1 上——这类错误读数完全合理但系统性偏移,最难发现。testbench 要专门覆盖「换通道第一帧」这个边界。
十、命令字、对齐与数据整理
不同 ADC 的帧格式差异都集中在三处。命令字:起始位(通常 1)、单端/差分区段位、通道号、可编程增益、休眠位等,逐位对照手册拼一个常量或参数化 ROM。数据对齐:12 位结果可能左对齐(放在 16 位帧的高 12 位,低位补零,等于数值乘 16)或右对齐(高位补零),也可能前面带一个前导 0 和一个 NULL 位;整理代码就是按手册切片再移位,建议在代码里用显式切片加注释标明页码。码制:单极性一般是无符号原码(0 到满量程),双极性可能是二补码或偏移二进制码(最高位反相即补码),送入有符号滤波链路前必须统一(定点处理见 定点数基础)。
写主控时推荐的参数化:时钟分频、帧位数、命令位数、采样沿选择、CS 建立/保持拍数都做成 parameter,同一个模块用不同参数支持不同芯片;通道调度做成小 ROM 表(通道号、命令字、标签),采样周期由定时器发起。
十一、多从机、三线制与板级事项
一主多从的标准接法是 SCLK/MOSI/MISO 共线、每从机一根 CS;MISO 要求从机在未选中时高阻输出,否则两个芯片同时驱动会打架。总线电容和多个从机输入电容限制 SCLK 上限,速率上不去先看布线和从机数量。菊花链是另一种拓扑:所有芯片共用 CS,MISO 串过每个从机,数据像移位寄存器一样传递,适合批量配置(如 DAC 阵列)但读回复杂。三线 SPI 把 MOSI/MISO 合成一根双向 SIO,半开关方向切换需要状态机精确控制,能省一根 IO。
板级注意:SPI 是板级短距接口,没有差分阻抗要求,但 SCLK 是常开翻转的信号,走线远离模拟输入和参考电压;地回流路径要完整;CS 线上若有较大电容会延缓下降沿导致建立时间变化。FPGA 侧管脚电平必须和 ADC 的 I/O 电压(常是 VDD/VIO)同 Bank 匹配(见 外设地图)。
十二、完整验证链路
仿真层:写一个 ADC 行为模型——接收命令字、按 CPHA 在正确沿驱动 MISO、返回带通道标签的可辨识码(如通道号加简单递增序列),检查主机解出的帧和标签;再做参数化测试覆盖 mode0/mode3、不同分频、CS 时序边界。回环自检:板上若有 SPI DAC 或 Flash,可用同一主控思路写读 Flash JEDEC ID 验证时序。上板层:电位器给三档电压(地、约一半、参考电压),原始码应分别接近 0、约 2048、约 4095;差值反映偏移和增益误差,记录校准。再用信号源给已知正弦,ILA 采一批样本导出,Python 做 FFT,看频谱是否干净、噪声底对应多少 ENOB、有没有 50Hz 工频干扰(混叠/布线问题)。数据链路:换算后的定点值接 FIR 滤波 去噪、送 UART/VGA 显示,完成「模拟世界到数字处理」的第一段完整闭环。
排查读数错误的决策树:全部读零或全一查 CS、电源、MISO 方向;数据像移位错版查 CPHA/采样沿;数值翻倍减半查对齐;通道串标查流水线语义;读数随机跳查 Vref 去耦、接地、采样率是否超出规格;只有高速时出错查 SCLK 上限和布线。把这个决策树写进项目 README,换 ADC 时能省一整天。
十三、从 SPI 读到系统设计
SPI ADC 虽是入门接口,但它包含了 FPGA 对接外部芯片的全部要素:时序参数化状态机、命令协议解析、跨时钟语义(片上转换时钟与系统时钟)、数据对齐和码制转换、物理层验证。掌握这一套后,再接 SPI DAC、SPI Flash(见 比特流固化)、SPI 屏就是换手册的工作;再进一步学 I2C(地址、应答、开漏)和高速并行/JESD204 接口时,也能复用「手册时序图 → 相位 tick → 状态机 → 行为模型 → ILA 对拍」这条标准路径。传感器接口设计的核心能力从来不是背协议,而是能把任何一张时序图准确翻译成单时钟域的寄存器行为。再给一个读手册的实操技巧:第一次接触一颗 ADC,先不看文字,直接找手册里的「Serial Interface Timing Diagram」,对着图自己列出一张表——CS 拉低后第几个时钟沿发命令、第几个沿开始出数据、数据 MSB 还是 LSB、几个等待位、一帧多少位、最高时钟多少、CS 间隔最小多少纳秒。列完再回头读文字核对,你会发现理解速度快很多,因为 SPI 类器件的差异 90% 就浓缩在这张表里。带着这张表写状态机,参数化后就是一个可以复用到 Flash、DAC、传感器的通用 SPI 主控。接口设计能力就是这样从一张张时序图的翻译练习里长出来的。最后把 SPI ADC 放回整个信号链看:传感器把物理量变成电压、ADC 把电压变成码、FPGA 主控把码变成可处理的数据,这三跳中任何一跳的错误假设都会污染后面所有算法。所以接口工程师的价值不只是写出移位状态机,还要能和硬件确认 Vref 和输入阻抗、和算法确认码制和定标、和测试确认采样率和抗混叠滤波。把这些跨边界的问题在设计评审时问全,SPI 读数据这件简单的事,就真正变成了可靠系统的第一环。最后提醒一个容易忽视的硬件细节:SPI 走线虽然低速,但 SCLK 在 10~20MHz 时已经是方波,长飞线和面包板上的寄生电容会让边沿变圆,从机能看到的有效建立时间缩短,表现为低速正常、高速读错。排查时不要只怀疑代码,用示波器看从机管脚处(不是 FPGA 管脚处)的 SCLK 和 MISO 实际波形,很多「协议 bug」其实是信号完整性问题。板级接口调试永远要同时带着软件和物理两副眼镜。最后补一个 ADC 选型的入门视角:不要只看标称位数和采样率,先看需求——直流慢信号看重 INL/DNL 和参考电压温漂,交流信号看 ENOB、信噪比和输入带宽,多通道看通道间串扰和建立时间,电池产品看功耗和待机模式。同样 12 位 ADC,不同定位的实际有效精度和价格可以差十倍。把接口协议读通只是第一步,能根据系统指标选对芯片,才是接口工程师的价值所在。最后提醒一个和数字逻辑直接相关的点:ADC 采样结果的更新时刻(转换完成)很多芯片用一根 DRDY/DOUT/READY 线通知主控,它和 SCLK 通常是异步关系,进入 FPGA 后必须当作异步输入做两级同步,不能直接当时钟或直接控制状态机跳变。很多「偶发多读一次」的 bug 就源于把 DRDY 当成了完美对齐的脉冲。协议状态机、跨域处理、码制定标、模拟前端,四件事都闭环,SPI ADC 这个小系统才算真正做完。最后给一个扩展练习:给 ADC 读取模块加一个简单的滑动平均(8 或 16 点),再用 定点数基础 的规则把平均值转成毫伏数值,通过 UART 周期上报。这个练习串起了接口、CDC、定点算法和通信,做完它,你就拥有了一个完整的「模拟世界到数字系统」数据采集通道,之后接 PID 控制、接 FIR 滤波、上 VGA 画波形,都是在这条通道上叠加功能。最后给一个常见工程坑:多通道轮询时,很多实现把「命令、等待、读数」放在一个固定大状态机里,切换通道要改一堆状态。更干净的结构是把通道序列做成一张小 ROM 表(通道号、命令字、标签),主状态机只负责「取描述符、发帧、存结果、贴标签」,增加通道只改表不动状态机。这种「机制与策略分离」的写法在所有序列式接口控制器里都适用,也是从「能读一颗芯片」到「能写通用主控」的标志。最后提醒一个协议层面的常识:SPI 规范只定义了四线和时钟相位的大致框架,具体命令字、帧长、流水线语义全部由从机器件自定义,所以不存在「读遍 SPI 芯片的通用驱动」这种东西(通用的只有时序引擎)。面试或工作中被问到不熟的 SPI 器件,正确反应不是背时序,而是现场翻手册的时序图和命令表,按本文的翻译步骤实现。这个「读手册做接口」的能力,比记住任何一颗具体芯片都重要。最后总结 SPI ADC 模块的最小自检清单:命令字与手册逐位一致;CPOL/CPHA 用模式表确认且 SCLK 不超上限;CS 建立保持时间满足;DRDY 已两级同步;通道标签与数据对齐;码制(单/双极性)和定标在文档中写明;仿真有行为模型、上板有零中满三点电压测试。逐条打勾后,这个简单的传感器接口就具备了进入可靠系统的资格。最后把 SPI ADC 与职业能力对应一下:能把状态机写对只算入门;能讲清 CPOL/CPHA 为什么这样选、DRDY 为什么要同步、码制和定标如何与算法对接、PCB 走线如何影响高速读数,才算真正掌握了这个接口。这些问题没有一个靠背诵能回答,全部要在「手册、波形、代码、示波器」四者对照中才能吃透——而这种四对照的习惯,正是做任何硬件接口都通用的核心能力。把 SPI ADC 做扎实之后,再学 I2C、UART、CAN 等接口,你会发现差异主要在物理层帧格式,方法学完全一致:读手册时序、列出帧表、翻译成单时钟域状态机、写行为模型仿真、ILA 对拍、最后做物理层确认。接口学习不是积累协议数量,而是掌握这套可迁移的翻译流程。带着流程去读任何新接口的数据手册,你都会先找时序图、再列帧表、最后写状态机——这正是从「初学者」到「接口工程师」的关键转变。当这个流程成为你的肌肉记忆,面对任何陌生串行或并行接口,第一反应都不会是找现成驱动,而是打开手册的时序章节,把物理世界的协议翻译成你熟悉的单时钟域寄存器行为。
十四、从行为模型到系统联调的完整测试台
接口模块最值得投入的验证资产是一个忠实的从机行为模型。对 SPI ADC 来说,模型要包含的不是「理想移位寄存器」一个进程,而是一组可配置的真实特性:命令字解析后按通道返回预设码值、转换期间 BUSY 拉低并维持可编程的拍数、数据输出带流水线等待位、片选无效时 MISO 高阻。再加上一组故障注入开关——偶尔提前半个周期改变 MISO、在帧中间拉高 CS、返回超范围码值——就能在仿真里提前演练主机会如何反应。写模型的过程本身就是第二次精读手册的过程:凡是你无法在模型里实现的行为,多半也是主控里没想清楚的假设。很多接口 bug 在仿真阶段之所以抓不到,恰恰因为 testbench 里的从机比真实芯片「乖」太多。
测试台分三层组织。第一层是协议自检:主控发出的 SCLK 频率、CPOL/CPHA、CS 建立保持时间用监视器进程自动测量,超出参数表允许范围立即报错,而不是靠肉眼看波形。第二层是数据闭环:模型返回递增码和已知通道标签,检查主控收到的数据和顺序在几千次随机间隔的读取中零错位、零丢帧,CS 在两次读取之间是否真的拉高了最短间隔也要断言。第三层才是系统联调:把主控接上滑动平均和 UART 上报,用真实速率跑几分钟仿真(或直接上板),统计吞吐是否满足采样率预算。三层测试都通过,这个接口模块才具备和其他子系统集成的条件;带着只验证过「能读出数」的模块进联调,问题会以十倍代价在集成阶段爆发。
十五、吞吐量预算与低速接口的时序收敛
SPI 常被贴上「低速、随便写都能收敛」的标签,但真正决定设计成败的是吞吐量预算而不是 SCLK 名义频率。先从需求端算:需要的有效采样率乘以一帧总位数(命令位、等待位、数据位、通道切换开销、CS 间隔)才是最低 SCLK,再留 30% 到 50% 裕量给软件调度和多通道轮询。多通道时这个乘法很容易被忽略——四个通道轮询意味着每通道有效速率降为四分之一,而 ADC 的吞吐通常是按单通道标定的。如果预算算下来 SCLK 要逼近器件上限,就要重新审视通道数、每帧位数和有没有必要持续转换,而不是硬超手册频率,后者在温度变化时会以难以解释的偶发读错出现。
即使 SCLK 只有十几兆,时序约束也不能省略。SCLK 是由系统时钟分频生成的,必须通过 clock 相关约束告诉工具它和主时钟的周期关系;输出的 MOSI、CS 要设置合理的输出延迟,输入的 MISO 要按 PCB 走线和从机输出延迟设置输入约束,否则工具要么按过紧的默认值报一堆假违例,要么干脆不检查。另一个常见隐患是 SCLK 分频逻辑本身:用组合逻辑从计数器译码产生门控时钟会引入毛刺,正确做法是让 SCLK 作为触发器输出的使能型信号(在系统时钟沿翻转),物理上的 SCLK 来自寄存器而不是与门。记住「低速接口照样需要完整约束」这一条,能避免大量「仿真没问题、换温度就读错」的板级悬案。再补一句心态上的提醒:接口类工作的成就感不如算法炫,但它处在系统与物理世界的边界上,任何时序和电气上的想当然都会被真实芯片直接惩罚。愿意逐纳秒抠时序、逐毫米看走线的工程师并不多,把这种笨功夫做扎实,恰恰是接口方向最稳固、也最难被替代的职业壁垒。
常见坑对照
| 坑 | 现象 | 建议 |
|---|---|---|
| CPOL/CPHA 错 | 数据错位/跳变 | 严格按手册采样沿 |
| SCLK 太快 | 读不准/无数据 | 不超手册上限 |
| CS 建立时间不足 | 首帧错 | CS 提前拉低 |
| MSB/LSB、前导零 | 数值偏移 | 按帧格式对齐 |
| 通道/命令发错 | 读错通道 | 核对命令位 |
| 另起 SCLK 时钟域 | CDC/时序问题 | 用 tick 时钟使能 |
| Vref/地不稳 | 噪声/码跳 | 参考电压去耦 |
相关与延伸
基础:状态机三段式、计数器与时钟使能、时钟与复位。 后续:定点数、FIR 滤波;协议见进阶 UART/SPI/I2C 协议与实现。
一句话总结
FPGA 读 SPI ADC 就是做一个 SPI 主机:用 SCLK/MOSI/MISO/CS_N 四线,在手册规定的 CPOL/CPHA 模式和采样沿上把通道命令发出、把 N 位结果逐位移回。ADC 把电压量化为码(code≈Vin/Vref×2^N,12 位 3.3V 时 1LSB≈0.8mV)。四种 SPI 模式由空闲电平 CPOL 和采样沿 CPHA 决定,ADC 常用 mode0/mode3,配错会在错误沿采样导致整体错位,是头号坑。一次帧通常是:拉低 CS→SCLK 上 MOSI 发命令、MISO 移出结果(可能含前导零或为上次转换值)→收满拉高 CS,位数/对齐/命令逐芯片不同、以手册为准。实现推荐单一系统时钟域 + 分频 tick 使能 + 三段式状态机(IDLE→SHIFT 位计数同时收发→DONE 出 data+valid),勿另造 SCLK 时钟域。验证先在 TB 用 ADC 模型回放已知比特核对解串,再上板用电位器给 0/半/满量程、ILA 抓四线对拍;读不依次查 CPHA、SCLK 频率、CS 建立、MSB 与前导位对齐、通道命令和 Vref。
上一篇:48 VGA 显示入门(行场同步/像素时钟) | 下一篇:50 RTL 版本管理(Git/tcl/可复现工程) | FPGA 系列目录