今天梳理SPI总线的核心内容,嵌入式开发里很常用的同步串行通信协议。
SPI全称串行外设接口,是全双工、同步的串行通信协议,典型一主多从架构,由SCLK时钟线、MOSI、MISO、CS片选线四根线组成。MOSI负责主机发送、从机接收,MISO负责从机发送、主机接收,CS是片选信号,主机拉低对应从机的CS引脚即可选中通信,速率覆盖1MHz到上百MHz,像OLED、加速度传感器这类外设大多支持SPI驱动。
SPI一共有四种工作模式,由CPOL时钟极性和CPHA时钟相位组合决定。CPOL决定空闲状态下SCLK的电平,CPOL=0时空闲为低电平,CPOL=1时空闲为高电平;CPHA决定接收方在第几个时钟边沿采样数据,CPHA=0在第一个边沿采样,CPHA=1在第二个边沿采样。
四种模式具体对应:
-
CPOL=0,CPHA=0:空闲时钟低电平,上升沿采样数据
-
CPOL=0,CPHA=1:空闲时钟低电平,下降沿采样数据
-
CPOL=1,CPHA=0:空闲时钟高电平,下降沿采样数据
-
CPOL=1,CPHA=1:空闲时钟高电平,上升沿采样数据
// SPI模式配置示例
void spi_set_mode(uint8_t cpol, uint8_t cpha)
{
if (cpol) SPI->CR |= SPI_CR_CPOL;
else SPI->CR &= ~SPI_CR_CPOL;
if (cpha) SPI->CR |= SPI_CR_CPHA;
else SPI->CR &= ~SPI_CR_CPHA;
}
SPI通信有个核心特点:写即是读,读即是写。主机发送数据的同时,移位寄存器会同步接收从机返回的数据;读取数据时,主机需要发送Dummy占位字节(通常0xFF或0x00)来提供时钟,从机才会把数据移位输出。
写操作流程:主机先发送寄存器地址,再发送要写入的数据即可。
读操作流程:主机先发送寄存器地址,接着发送占位字节,从机就会在时钟驱动下返回对应寄存器的数据。
// SPI读写一个字节示例
uint8_t spi_rw_byte(uint8_t data)
{
while (!(SPI->SR & SPI_SR_TXE));
SPI->DR = data;
while (!(SPI->SR & SPI_SR_RXNE));
return SPI->DR;
}
常用的SPI从机设备ADXL345是三轴加速度传感器,它的分辨率为13位,加速度量程可达±16g,1g对应9.8m/s²。换算下来每个刻度值为 32g / (2^13 - 1) ≈ 0.0039g,读取到的原始值乘以这个系数就是实际加速度,比如x轴原始值123,对应加速度就是123 * 0.0039g。
最后对比下I2C和SPI的核心区别:
-
线数:I2C仅需SCL+SDA两根线,SPI需要SCLK+MOSI+MISO+CS至少四根线
-
通信方式:I2C是半双工,SPI是全双工
-
速率:I2C常用速率100KHz~3.4MHz,SPI普遍1MHz~100MHz,速度优势明显
-
拓扑:I2C支持多主多从,SPI为一主多从架构
-
数据顺序:两者均为高位先行
都是嵌入式底层通信的基础内容,把模式和读写时序理清楚,驱动外设基本就不会踩坑。