4、SPI总线详解:SPI四种模式、多从机配置、全双工通信
SPI,全称是Serial Peripheral Interface,串行外设接口。这玩意儿在嵌入式系统里太常见了。说白了,它就是让主控芯片跟各种外设——比如传感器、ADC、显示屏、存储芯片——之间快速交换数据的一条专用通道。
我刚开始做嵌入式那会儿,总觉得SPI比I2C复杂。后来发现,其实它的逻辑非常直白。今天咱们就把SPI的四种模式、多从机怎么接、全双工到底怎么玩,一次性讲透。
4.1 SPI的物理层:四根线搞定一切
SPI通信只需要四根线。嗯,严格来说,最少三根也能跑,但标准配置是四根:
- SCLK(串行时钟):由主机产生,控制数据传输的节奏
- MOSI(主机输出,从机输入):主机发数据给从机
- MISO(主机输入,从机输出):从机发数据给主机
- SS/CS(从机选择):片选信号,低电平有效
你想想看,这四根线分工非常明确。时钟线负责打拍子,两根数据线各走各的方向,片选线决定跟谁说话。这就是全双工的基础——发送和接收可以同时进行。
核心要点:SPI是同步、全双工、主从式的串行通信协议。没有地址的概念,靠片选线区分设备。
4.2 SPI的四种模式:时钟极性CPOL和时钟相位CPHA
很多初学者在这里栽跟头。我当年也是,调一个SPI Flash死活读不出数据,折腾了两天才发现是模式没配对。
SPI的四种模式,由两个参数决定:
- CPOL(Clock Polarity,时钟极性):空闲时时钟线的电平。CPOL=0表示空闲低电平,CPOL=1表示空闲高电平。
- CPHA(Clock Phase,时钟相位):数据在时钟的哪个边沿采样。CPHA=0表示第一个边沿采样,CPHA=1表示第二个边沿采样。
组合起来就是四种模式:
| 模式 | CPOL | CPHA | 空闲时钟电平 | 数据采样边沿 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 上升沿(第一个边沿) |
| 模式1 | 0 | 1 | 低电平 | 下降沿(第二个边沿) |
| 模式2 | 1 | 0 | 高电平 | 下降沿(第一个边沿) |
| 模式3 | 1 | 1 | 高电平 | 上升沿(第二个边沿) |
我个人习惯,遇到一个新的SPI器件,第一件事就是去翻数据手册,找到时序图。图上会明确标出CPOL和CPHA的值,或者直接告诉你支持模式0和模式3。千万别猜,猜错了就是白调。
避坑指南:我曾经在一个项目里,把温湿度传感器的SPI模式配成了模式2,结果读回来的温度值忽高忽低。后来用示波器一看,数据采样边沿完全对不上。从那以后,我每次初始化SPI之前,都会先确认从机支持的CPOL和CPHA。
4.3 多从机配置:片选的艺术
一个SPI总线上可以挂多个从机。怎么区分它们?靠片选线。
有两种常见的接法:
- 独立片选:每个从机一根独立的CS线。主机通过拉低某个从机的CS来选中它。这是最常用的方式。
- 菊花链:所有从机共用一根CS线,数据从一个从机传到下一个。这种方式用得少,因为延迟大,调试也麻烦。
我建议,除非你特别清楚自己在做什么,否则就用独立片选。简单、可靠、好调试。
多从机配置时,有几个细节要注意:
- 所有从机的SCLK、MOSI、MISO可以并联在一起
- MISO线需要特别注意——未选中的从机必须把MISO置为高阻态,否则多个从机同时驱动MISO会打架
- 片选信号在传输过程中不能抖动,否则从机可能误判
警告:多从机共用一个SPI总线时,如果某个从机的MISO不是高阻态输出,一定要加一个三态缓冲器或者用GPIO模拟片选。否则,轻则数据错误,重则烧毁IO口。我亲眼见过一个同事因为没注意这个,把STM32的MISO引脚烧了。
4.4 全双工通信:同时收发
SPI最牛的地方就是全双工。什么意思?主机发数据的同时,从机也在发数据。两根数据线各走各的,互不干扰。
具体流程是这样的:
- 主机拉低CS,选中从机
- 主机在SCLK上产生时钟脉冲
- 在每个时钟周期,主机通过MOSI发送一位数据,同时从机通过MISO发送一位数据
- 8个时钟周期后,主机和从机各完成一个字节的交换
- 传输结束,主机拉高CS
你看,一个时钟周期内,数据是双向流动的。这就是全双工的精髓。
实际编程中,SPI的收发通常是成对出现的。比如你要读一个传感器的数据,你得先发一个命令字节过去,然后才能读到数据。这个过程中,主机发的命令和从机回的数据是同时进行的。
// 伪代码示例:SPI全双工读写
uint8_t spi_transfer(uint8_t tx_data) {
// 发送一个字节,同时接收一个字节
SPI_DR = tx_data; // 写入发送数据寄存器
while(!(SPI_SR & TX_EMPTY)); // 等待发送完成
while(!(SPI_SR & RX_FULL)); // 等待接收完成
return SPI_DR; // 读取接收数据寄存器
}
// 读取传感器数据的典型流程
uint8_t cmd = 0x03; // 读命令
uint8_t dummy = 0x00;
uint8_t result;
CS_LOW(); // 选中从机
result = spi_transfer(cmd); // 发命令,同时收到一个无效字节
result = spi_transfer(dummy); // 发任意字节,同时收到有效数据
CS_HIGH(); // 释放从机
嗯,这里要注意:很多SPI外设要求主机在发送命令后,继续发送空字节(比如0x00或0xFF)来产生时钟,从机才会把数据送出来。这个细节容易忽略。
4.5 知识体系总览
下面这张图,把SPI的核心知识点串起来了。你可以把它当作一个快速索引,遇到问题回来翻一翻。
4.6 实战经验总结
最后,分享几个我这些年积累下来的经验:
- 先看数据手册的时序图:别急着写代码。把CPOL、CPHA、时钟频率、数据格式(MSB还是LSB first)都确认清楚。
- 用示波器或逻辑分析仪验证:我每次调SPI,都会先抓一下波形。看看片选有没有拉低,时钟频率对不对,数据是不是在正确的边沿采样。这一步能省下大量调试时间。
- 注意时钟频率:SPI的时钟频率不是越高越好。从机有最高频率限制,而且走线长了频率太高信号会失真。我一般先用手册推荐的最大频率的一半开始调。
- 片选信号要干净:CS线上的毛刺会导致从机误触发。如果发现通信不稳定,先查CS的波形。
小技巧:如果你手头没有逻辑分析仪,可以用GPIO模拟SPI来调试。虽然慢,但每个时序细节都在你的掌控之中。我早期做项目时,经常用这种方法来验证从机的响应是否正确。
SPI总线,说白了就是一根时钟线加两根数据线再加一根片选线。理解了四种模式的本质,掌握了多从机的接法,搞清楚了全双工的工作方式,你就能驾驭绝大多数SPI外设。剩下的,就是多动手、多看波形。