SPI 从机设计:跨时钟采样、移位时序、TX 预装载与模式处理
更新时间:2026-09-19。前置:SPI 主机 CPOL/CPHA、CDC 两级同步。写 SPI 主机时时钟、片选都由自己控制;做从机时 SCLK 和 SS# 是异步输入,设计难点从「发波形」变成「安全地跟随外来时钟」。
本文要回答的问题
- 从机用哪个时钟沿移位、哪个沿采样,模式 0/3 怎么配?
- 外来 SCLK 与本地 pclk 异步,数据怎么安全跨时钟域?
- 第一位为什么容易丢,TX 数据何时必须就位?
- SS# 中途撤销、背靠背传输、从机停时钟怎么处理?
一、从机视角的模式边沿
CPOL 定空闲电平、CPHA 定在哪种边沿采样。从机必须与主机一致:
| 模式 | 空闲 | 从机采样 MOSI边沿 | 从机更新 MISO边沿 |
|---|---|---|---|
| 0 | SCLK=0 | 上升沿 | 下降沿(或 SS# 拉低时放首 bit) |
| 3 | SCLK=1 | 下降沿 | 上升沿 |
关键差异:CPHA=0(mode0/2)时,第一位在 SS# 拉低那一刻就必须在 MISO 上,要「片选一选中就预装载首 bit」;CPHA=1 时主机给第一个沿后从机才推出首 bit,时序更宽松。这是从机最常出错处。
二、移位寄存器与时钟域
从机本质是一个「由外部 SCLK 拨动」的移位寄存器,但配置/状态寄存器在本地 pclk 域,存在两个域:
bash
SCLK 域(外部、可停):
SCLK ─沿► 移位寄存器(并入串出/串入并出),MOSI 移入,MISO 移出
SS#(异步)
│ 每收完 8/16 bit 产生 done/byte_ready
▼ 跨时钟域(两级同步 + 脉冲展宽)
pclk 域(本地):
控制/状态、RX FIFO、TX FIFO、中断- 接收:在采样沿把 MOSI 打入 SCLK 域移位寄存器;收满一字节后,把字节锁存并产生
byte_ready,再经 CDC 同步器(两级 FF + 脉冲展宽/握手)通知 pclk 域读走,或直接写进带独立写时钟的异步 FIFO(见 异步 FIFO)。 - 发送:pclk 域把下一字节预装进 TX holding;SCLK 域在需要时把它并入移位寄存器。因为 SCLK 可能很快,不能在传到位边界时才跨域取新字节,必须提前在片选有效/上一字节快结束时预装载。
- 生产做法是 SCLK 域与 pclk 域之间放双向异步 FIFO(RX:SCLK 写、pclk 读;TX:pclk 写、SCLK 读),天然解耦速率差并避免亚稳态。
三、片选 SS# 的作用
SS# 不只是「使能」,从机用它复位/对齐帧:
- SS# 下降沿:复位位计数器、移位寄存器对齐、mode0 时把首 bit 放到 MISO、清空半字节状态。
- SS# 必须经两级同步进 pclk,但在 SCLK 域做帧对齐时要直接用异步 SS# 与 SCLK 逻辑组合(并注意恢复/撤销时间,类似复位恢复检查)。
- SS# 中途提前撤销(传了 3 个 bit 就取消):要能复位位计数、丢弃残帧,而不是把半字节误判成完整字节。
- SS# 噪声/抖动:从机只认完整 8/16 位 + SS 结束,半帧不提交。
四、背靠背与主机比从机快
- 连续多字节时主机可能字节间几乎不抬 SS(或短暂抬),从机要在每收满一字节立即锁存、在移位间隙换 TX 字节;靠 TX FIFO 预取保证 MISO 不断档(否则下一字节首 bit 是旧数据/0)。
- 若本地处理慢、RX FIFO 将满,标准 SPI 没有流控(不像 I2C 能时钟拉伸,见 I2C 主机),从机只能靠足够 FIFO;溢出则置 overrun 标志并丢字节。
- 有些从机支持 BUSY/状态字节(如传感器):主机读回的第一个字节是状态,从机协议层安排即可。
五、半双工/单线与三态
- 标准全双工 MISO 只在 SS# 有效期间驱动,SS# 一高必须立刻释放 MISO 成高阻,否则多从机共 MISO 时总线冲突(见 三态总线)。
- 半双工/单线 SPI(如 Quad 的 IO)要按方向在不同相位切换三态,见 Quad/OSPI。
- SCLK 空闲期间保持,不能假设它连续翻转;所有 SCLK 域逻辑在时钟停住后要保持状态。
六、可综合骨架(概念)
verilog
// 模式0:上升沿采 MOSI;SS# 异步复位位计数/对齐
always @(posedge sclk or negedge ss_n) begin
if (!ss_n) begin bitcnt <= 3'd0; rx_shifter <= 8'd0; end
else begin
rx_shifter <= {rx_shifter[6:0], mosi}; // 上升沿移入
bitcnt <= bitcnt + 1'b1;
end
end
// 收满 8 位锁存(SCLK 域),随后跨域/写异步 FIFO
wire byte_done = ss_n ? 1'b0 : (bitcnt == 3'd7);
// mode0 首 bit:SS# 拉低即把 tx_shifter 最高位送上 MISO(在 SCLK 下降沿移位)七、常见的坑
- mode0 首 bit 没预装载:SS# 拉低后等第一个 SCLK 才准备,主机采到旧值/0。
- 外来 SCLK 直接当系统逻辑时钟不做跨域:亚稳态、数据偶发出错,要用同步/异步 FIFO。
- SS# 只当普通使能不复位位计数:上次半帧导致后续字节全部错位。
- SS# 撤销后还驱动 MISO:多从机 MISO 冲突,读到抖动值。
- TX 换字节太晚:背靠背传输中下字节首 bit 缺失/重复。
- 无流控导致 overrun:主机快、FIFO 浅又没处理,丢字节不报错。
- CPHA 边沿用反:mode3 仍按上升沿采样,整字节位错位。
- SCLK 停止/慢时钟假设:按连续时钟设计的握手在 SCLK 长停时死等。
- 把 SCLK 域脉冲直接打两拍当数据:脉冲可能比本地时钟窄而漏采,要用电平/握手/FIFO。
- 多字节帧长不固定:没按协议校验字节数,半帧被当有效数据。
- 极性/空闲态未判 SS:SCLK 上毛刺在 SS 无效时被当沿,需 SS 门控移位。
相关与延伸
主机侧见 SPI CPOL/CPHA;跨域处理见 CDC 两级同步、异步 FIFO;三态见 三态总线;多线见 Quad/OSPI。
总结
SPI 从机的核心矛盾是 SCLK/SS# 异步且随时可停:移位寄存器挂在外部 SCLK 域按模式边沿(mode0 上升沿采、下降沿发)移动,再用异步 FIFO 或握手把字节安全跨到本地 pclk 域;SS# 下降沿必须复位位计数并在 CPHA=0 时立即把首 bit 送上 MISO,SS# 一高立刻释放 MISO 三态,背靠背传输靠 TX FIFO 预取补位,且标准 SPI 没有流控只能靠 FIFO 深度抗 overrun——首 bit 不预载、不做跨域同步、SS 不复位和不释放 MISO 是四大坑。