嵌入式 MCU

SPI 面试手册

复习 SPI 同步全双工模型、CPOL/CPHA、片选时序、STM32 HAL 收发和常见故障排查。

难度:进阶

核心速记卡

概念一句话定义关键限制
SCK由主机产生的同步时钟不得超过从设备上限,还要满足板级信号质量和 AC 时序
MOSI / MISO主机输出/从机输入和主机输入/从机输出常见四线 SPI 收发在同一组时钟下同时进行
CS / NSS选中通信对象并界定一次 transaction多数设备低有效,命令、地址、dummy 和数据常需在同一有效窗口内完成
全双工移位每产生一个时钟,主从双方各移出和移入 1 bit只想读数据时,主机也必须继续产生时钟
Dummy 数据主机为了产生读取时钟而发送的无意义数据0x00 还是 0xFF、需要几个 dummy 由设备协议决定
CPOL / CPHACPOL 决定 SCK 空闲电平,CPHA 决定在第几个边沿采样主从必须一致,并保证数据在采样边沿前稳定
位序与帧宽决定 MSB/LSB 先发以及每个硬件数据帧的 bit 数必须与设备手册一致,16 bit 模式还要关注缓冲区类型和字节序
无统一 ACKSPI 本身没有统一的设备地址和 ACK/NACKHAL_OK 不代表从设备理解了命令,要通过 ID、状态位或读回验证
设备协议命令、寄存器地址、读写位、dummy 和自增规则由外设自定义不能把某一芯片的寄存器读写流程当成 SPI 通用标准

基础问答

Q: SPI 的四根常见信号线分别做什么?

答: SCK 由主机产生时钟,MOSI 从主机传向从机,MISO 从从机传向主机,CS/NSS 选中当前从设备并界定一次交易。多从设备通常共享 SCK、MOSI 和 MISO,每个设备使用独立 CS。

Q: SPI 为什么是同步全双工通信?

答: “同步”是因为双方都根据主机提供的 SCK 移位和采样;“全双工”是因为 MOSI 和 MISO 分离,每个时钟都能同时从两个方向交换 1 bit。发送与接收 API 是软件关注点的区分,不改变底层同时移位的事实。

Q: SPI 读数据时为什么还要发送 dummy byte?

答: 从设备只会在 SCK 推动下把 MISO 数据移出,而主机产生时钟的常见方式是继续写入发送数据寄存器。所以纯读阶段也会发送 0x000xFF 等 dummy 数据,用来换取同样数量的接收时钟。

Q: CS 除了选择从设备,还有什么作用?

答: 对很多设备,CS 的有效窗口同时是一次 transaction 的帧边界。命令、地址、dummy 和数据常必须在同一次 CS 拉低期间连续完成;中途拉高 CS 可能使从设备结束并重置当前命令解析。

Q: CPOL 和 CPHA 分别决定什么?

答: CPOL 决定 SCK 空闲时是低还是高;CPHA 决定接收方在 CS 有效后的第一个还是第二个时钟边沿采样。CPOL/CPHA 组合形成 Mode 0–3,主从配错时常出现 bit 偏移、命令不识别或读数据不稳定。

Q: SPI 为什么没有通用的“读寄存器”格式?

答: SPI 只定义了片选、时钟和位交换方式,不统一规定 opcode、寄存器地址、读写位、dummy 周期或自增规则。驱动必须根据具体从设备手册组织 transaction,不能套用其他芯片的命令格式。

Q: SPI 没有 ACK,怎么判断从设备真的正常工作?

答: 主机 SPI 外设完成移位不等于从设备已响应,即使对端没上电也可能得到 HAL_OK。工程上通常读取固定 ID、检查状态位、写后读回,或使用设备协议提供的 CRC/错误标志来建立可信证据。

深入追问

Q: 如何用移位寄存器解释 SPI 全双工?

答: 主机和从机各有发送/接收移位通路。每个 SCK 周期,双方都移出 1 bit 给对方,同时移入对方的 1 bit;8 个时钟后,两个方向各交换一个字节。因此命令阶段收到的字节可能只是无效值或上一状态,有效读数据则出现在后续 dummy 阶段。

追问: 只发送时为什么也可能产生 OVR? 答: 四线全双工中,每发出一帧也会同时接收一帧。如果控制器或驱动没有正确读取/清理接收通路,后续数据可以覆盖旧数据并置位 OVR;具体清除顺序要看所用 STM32 系列手册。

Q: 如何不靠死记理解 SPI Mode 0–3?

答: 先用 CPOL 确定 SCK 的空闲电平,从空闲电平出发的跳变是第一个边沿,返回空闲电平是第二个边沿。CPHA=0 在第一个边沿采样,CPHA=1 在第二个边沿采样;发送方应在另一个边沿更新数据,给采样留出稳定时间。

追问: Mode 0 和 Mode 3 都是上升沿采样,可以通用吗? 答: 不可以。它们的 SCK 空闲电平不同,CS 生效后的第一个跳变方向和数据准备时机也不同,必须完整匹配 CPOL 和 CPHA。

Q: SPI 硬件数据帧和设备 transaction 有什么区别?

答: 硬件数据帧是 SPI 外设一次移位的位宽,例如 8 bit 或 16 bit;transaction 是设备协议定义的完整交易,通常由 CS 有效窗口包围的多个命令、地址、dummy 和数据帧组成。SPI 配成 8 bit 并不代表一次交易只能发 1 字节。

追问: 为什么分别调用“发命令”和“收数据”有时会失败? 答: 问题通常不在 API 分成两次,而在两次调用之间是否错误释放了 CS、插入了不允许的停顿,或没有用发送/dummy 产生读时钟。要以设备 transaction 时序为准。

Q: 多个从设备共享 SPI 总线时有哪些风险?

答: 必须保证同一时刻只有一个 CS 有效,未选中设备要将 MISO 置为高阻,否则可能出现多个输出互相冲突。不同设备还可能要求不同 Mode、分频和位序,切换配置前必须确认 SPI 空闲;多任务环境还要用互斥量保护“配置 + CS + 传输”这个完整交易。

追问: 每个从设备都必须有独立 CS 吗? 答: 常见星形连接通常如此,但支持 Daisy Chain 或特定带内部寻址机制的设备可以例外。这是设备特性,不是所有 SPI 从机的通用能力。

Q: SPI 最高速度由什么决定?

答: 上限不只是 MCU 分频器能配多快,还受从设备最大 SCK、时钟到 MISO 有效延迟、主机采样时机、CS 建立/保持时间、PCB/飞线信号完整性和软件搬运能力限制。可靠速率应通过数据手册时序预算、真实波形和稳定性测试确认。

追问: 为什么降低 SCK 后正常,不能立即断定只是布线问题? 答: 降速同时放宽了从设备输出延迟、建立/保持时间、CS 到首时钟延迟和信号完整性裕量。它只证明问题与时序裕量有关,还需逐项看波形和 AC timing。

STM32 HAL 高频点

Q: HAL_SPI_Transmit()Receive()TransmitReceive() 怎么选?

答: 只关心 MOSI 写入时可用 Transmit(),只保留 MISO 数据时可用 Receive(),需明确控制每个发送字节与接收字节对应关系时用 TransmitReceive() 最直观。主机接收仍需要时钟,Receive() 内部如何产生 dummy 要以所用 STM32 HAL 实现为准。

Q: 为什么 SPI 外设驱动常用 GPIO 手动控制 CS?

答: 不同从设备对 CS 有效电平、建立/保持时间、命令间是否保持低电平等要求差异很大。用 GPIO 可以将“CS 拉低 → 完整 transaction → CS 拉高”封装得更清楚,也方便管理多从设备;硬件 NSS 是否适用要看具体 MCU 和外设时序。

Q: SPI 阻塞、中断和 DMA 模式怎么取舍?

答: 初次调试和少量寄存器读写优先用阻塞模式,因为 CS、命令和返回值的时序最直观。中断适合中小数据量且不希望 CPU 等待的传输;DMA 适合显示刷图、Flash 连续读写等大块数据。切换到非阻塞方式后,还要管理回调、CS、缓冲区和总线并发。

Q: SPI 异步或 DMA 传输最容易犯什么错?

答: 非阻塞 API 返回只表示已启动,不是已传完;CS 应在完成回调后再释放,TX/RX 缓冲区在此前不能是失效栈变量、被重用或改写。全双工 DMA 通常还要同时配置 TX 和 RX DMA,带 D-Cache 的 MCU 需处理缓存一致性。

Q: HAL 返回 HAL_BUSYHAL_ERROR 或出现 OVR 怎么查?

答: 先读取 HAL_SPI_GetState()HAL_SPI_GetError(),确认上一次 IT/DMA 是否真正完成、回调和 IRQ 是否执行,以及是否在 busy 期间又启动新 transaction。OVR 表示新接收数据到达时旧数据尚未取走,具体清标志顺序和恢复方式必须以对应 STM32 参考手册为准。

if (status != HAL_OK) {
uint32_t state = HAL_SPI_GetState(&hspi1);
uint32_t error = HAL_SPI_GetError(&hspi1);
printf("SPI state=%lu, error=0x%08lX\r\n", state, error);
}

工程场景题

场景:MISO 读回一直是 0xFF0x00

分析思路: 先用波形确认从设备供电、共地、CS 和 SCK,再看 MOSI 命令是否与手册一致。MISO 始终为高可能是线路浮空或未选中,始终为低可能是接线/短路或从设备未正常驱动。硬件正常后再核对 Mode、位序、读写位和 dummy 数量,优先读固定 ID 寄存器。

面试表达: SPI 没有 ACK,固定值不能只靠 HAL 返回值判断,必须通过 CS/SCK/MOSI/MISO 四路波形逐层定位。

场景:读数据整体错 1 bit 或错 1 字节

分析思路: 错 1 bit 优先核对 CPOL/CPHA 和实际采样边沿,检查 MISO 是否在采样点前稳定;错 1 字节则将 TX/RX 按同一组时钟对齐,区分命令阶段的无效回读、dummy 阶段和有效数据。然后确认设备是否还有 pipeline 延迟或多个 dummy clock 要求。

面试表达: bit 错位优先看采样边沿,字节错位优先看全双工对齐、dummy 和有效返回位置。

场景:低速正常,提高 SCK 后偶发错误

分析思路: 先确认没有超过从设备最大 SCK,再根据数据手册检查时钟到 MISO 有效延迟、建立/保持时间和 CS 到首个时钟的要求。用示波器观察过冲、振铃和采样边沿处的 MISO 稳定性,逐档分频定位边界;必要时缩短走线、改善回流或在驱动端加合适的串联阻尼。

面试表达: 降速有效只证明时序裕量相关,还要用 AC timing 和波形区分设备延迟、CS 时序与信号完整性问题。

场景:单设备正常,多设备或多任务后数据混乱

分析思路: 先确认所有 CS 默认无效且每次只选中一个设备,未选中设备确实释放 MISO。再检查各设备的 Mode、分频和位序,切换配置时要等总线空闲。RTOS 中用同一个互斥量保护配置、CS 和完整传输;IT/DMA 要在回调后才释放 CS 和总线所有权。

面试表达: SPI 总线互斥的单位应该是完整 transaction,不能只保护单次 HAL 函数调用。

易错点

  • 误区: SPI 读操作时主机不需要发任何东西。 正解: 主机必须产生 SCK 才能把从设备数据移出,常见做法是同时发送 dummy 数据。

  • 误区: CS 只是一个普通使能信号,两次 API 之间拉高不影响命令。 正解: 对很多设备,CS 同时定义 transaction 边界,中途释放会结束当前命令。

  • 误区: Mode 0–3 只要有一个采样边沿相同就能互换。 正解: CPOL 和 CPHA 必须整体匹配,空闲电平、第一个跳变和数据准备时机都会变化。

  • 误区: HAL_OK 说明从设备已正确执行命令。 正解: 它只说明本机 SPI 传输流程没有报错,从设备是否响应要靠 ID、状态位或读回验证。

  • 误区: SPI 配置为 8 bit,一次交易就只能传 1 字节。 正解: 8 bit 是硬件数据帧宽度,一次 CS transaction 可以连续传输多个数据帧。

  • 误区: DMA API 返回后就可以拉高 CS 并复用缓冲区。 正解: API 返回只代表已启动,要等完成回调后再结束 transaction 并复用缓冲区。

  • 误区: SPI 只要低速能通,任意提高分频后的频率都可靠。 正解: 可靠速率受从设备 AC timing、采样时机、片选时序和板级信号完整性共同限制。

面试口述模板

如果面试官让你“讲一下 SPI”,可以按这段组织:

SPI 是一种由主机提供时钟的同步串行接口,常见四线是 SCK、MOSI、MISO 和 CS。主从双方在同一组时钟下同时移出和移入数据,所以读数据时主机也要发 dummy 来产生时钟。CPOL 决定时钟空闲电平,CPHA 决定第几个边沿采样,主从必须匹配。CS 不仅选择设备,还常界定命令、地址、dummy 和数据组成的完整 transaction。SPI 本身不规定设备地址、ACK 和寄存器命令格式,所以驱动必须查具体设备手册,并用读 ID、状态位或读回判断从设备是否真正响应。排查时我会从供电和 CS 开始,再逐路检查 SCK、MOSI、MISO、Mode、dummy 与有效数据位置。

一句话总结

SPI 面试的核心是能用“CS 界定对象和交易,SCK 推动主从全双工移位”解释 dummy、CPOL/CPHA、字节错位和逻辑分析仪波形。

官方参考资料