第3章:SPI协议基础:SPI四线制详解、时钟极性与相位、主从模式通信流程
好,咱们进入SPI的世界。说实话,在嵌入式开发里,SPI是我用得最多的接口之一。它简单、高效,而且几乎每个MCU都标配。今天我就把SPI的底裤给你扒干净。
3.1 SPI四线制:到底哪四根线?
SPI全称是Serial Peripheral Interface,串行外设接口。它由摩托罗拉公司发明,后来成了事实标准。标准的SPI是四线制,这四根线各司其职,缺一不可。
| 信号线 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SCLK | Serial Clock | 主→从 | 时钟信号,由主机产生 |
| MOSI | Master Out Slave In | 主→从 | 主机发送数据给从机 |
| MISO | Master In Slave Out | 从→主 | 从机发送数据给主机 |
| CS/SS | Chip Select / Slave Select | 主→从 | 片选信号,低电平有效 |
这里有个细节我想强调一下:SPI是同步通信,所有数据交换都依赖SCLK时钟。没有时钟,数据就传不了。这和UART那种异步方式完全不同。
关键点:SPI通信时,主机和从机之间形成一个移位寄存器环。主机每发出一个时钟脉冲,双方同时移出一位数据,同时移入一位数据。所以一次时钟脉冲,数据是双向流动的。
我在项目中遇到过一个问题:有个同事把MOSI和MISO接反了,结果调试了一整天。嗯,这种低级错误其实挺常见的。你接线的时候一定要仔细看数据手册,别想当然。
3.2 时钟极性与相位:SPI最绕的地方
说到SPI,很多人最头疼的就是时钟极性和相位。说白了,就是决定数据在时钟的哪个边沿采样。SPI协议定义了四种模式,由CPOL和CPHA两个参数决定。
3.2.1 CPOL:时钟极性
CPOL决定SCLK空闲时的电平状态:
- CPOL = 0:空闲时SCLK为低电平
- CPOL = 1:空闲时SCLK为高电平
3.2.2 CPHA:时钟相位
CPHA决定数据在哪个边沿被采样:
- CPHA = 0:在第一个边沿采样(上升沿或下降沿,取决于CPOL)
- CPHA = 1:在第二个边沿采样
组合起来就是四种模式:
| 模式 | CPOL | CPHA | 空闲时钟 | 采样边沿 |
|---|---|---|---|---|
| 模式0 | 0 | 0 | 低电平 | 上升沿 |
| 模式1 | 0 | 1 | 低电平 | 下降沿 |
| 模式2 | 1 | 0 | 高电平 | 下降沿 |
| 模式3 | 1 | 1 | 高电平 | 上升沿 |
我的经验:LoRa模块(比如SX1278)通常工作在SPI模式0。但不同厂家的模块可能不一样。我建议你拿到模块后,先看数据手册的SPI时序图,确认是哪种模式。我曾经因为没看手册,默认用模式0去驱动一个模式3的传感器,结果读回来的数据全是乱的。
为什么会这样?因为主从双方的采样边沿必须一致。主机在上升沿发送数据,从机却在下降沿采样,那肯定对不上。你想想看,这就像两个人跳舞,一个踩鼓点,一个踩镲片,能跳好吗?
3.3 主从模式通信流程
SPI通信是典型的主从模式。一个主机可以挂多个从机,但同一时刻只能和一个从机通信。流程其实很简单:
- 主机拉低CS:选中目标从机
- 主机产生时钟:SCLK开始输出脉冲
- 数据交换:每个时钟周期,主机通过MOSI发一位,从机通过MISO回一位
- 主机拉高CS:结束通信,释放从机
这里有个重要的概念:SPI是全双工。什么意思?就是发送和接收可以同时进行。你发一个字节的同时,也能收到一个字节。这和I2C那种半双工不一样。
实战代码:下面是一个用STM32 HAL库驱动SPI的示例,读取LoRa模块的寄存器值。
// SPI读取一个字节
uint8_t SPI_ReadByte(uint8_t reg) {
uint8_t tx_data, rx_data;
// 拉低CS,选中从机
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
// 发送寄存器地址(读操作,最高位置1)
tx_data = reg | 0x80;
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, HAL_MAX_DELAY);
// 发送一个空字节,同时接收数据
tx_data = 0x00;
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, HAL_MAX_DELAY);
// 拉高CS,释放从机
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
return rx_data;
}
避坑指南:我曾经在调试一个LoRa网关时,发现读取的寄存器值总是0xFF。查了半天,原来是CS引脚没有正确初始化,一直处于高阻态。从机没被选中,MISO引脚自然就是高电平。所以,CS引脚的GPIO配置一定要检查,尤其是推挽输出和上拉电阻的设置。
3.4 SPI通信的时序细节
咱们再深入一点。SPI通信的时序有几个关键参数:
- SCLK频率:决定了通信速度。LoRa模块通常支持几MHz到十几MHz。但要注意,线长和寄生电容会影响最高频率。
- CS建立时间:CS拉低后,需要等待一段时间才能开始发时钟。这个时间一般在数据手册里有。
- CS保持时间:最后一个时钟结束后,CS需要保持低电平一段时间再拉高。
我个人习惯在写驱动时,把CS的拉低和拉高操作单独封装成函数。这样调试的时候,可以方便地在CS动作前后加延时或者打印信息。
嗯,这里还要提一句:SPI的从机不能主动发起通信。它只能被动响应。所以如果你需要从机主动上报数据,就得靠主机轮询,或者用额外的中断引脚。
3.5 多从机连接方式
当你有多个SPI从机时,有两种连接方式:
- 独立CS方式:每个从机一个CS引脚,主机用多个GPIO分别控制。这是最常用的方式。
- 菊花链方式:从机的数据输出接到下一个从机的数据输入,所有从机共享一个CS。这种方式用得少,但可以节省GPIO。
我建议初学者用独立CS方式。菊花链虽然省引脚,但调试起来很麻烦。而且一旦中间某个从机出问题,整条链都废了。
小技巧:如果你MCU的GPIO不够用,可以用一个3-8译码器(比如74HC138)来扩展CS引脚。三个GPIO就能控制八个从机。我在一个项目里就这么干过,效果不错。
3.6 总结一下
SPI协议其实不复杂。四根线,四种模式,主从通信。你只要记住:
- 接线别搞反(MOSI/MISO)
- 模式要匹配(CPOL/CPHA)
- CS要控制好(拉低选中,拉高释放)
做到这三点,SPI通信基本不会出大问题。下一章咱们会讲SPI在LoRa模块中的具体应用,包括寄存器读写、FIFO操作等。到时候我会拿实际项目中的代码来演示。
好,今天就到这儿。有什么问题,咱们下节课再聊。