1. SPI通信协议基础解析
SPI(Serial Peripheral Interface)是一种同步串行通信协议,由Motorola在1980年代提出。作为嵌入式领域最常用的短距离通信标准之一,它以全双工、高速传输的特性著称。典型的SPI系统包含一个主设备(Master)和多个从设备(Slave),通过四线制实现数据交换。
在实际项目中,我经常用SPI连接传感器、存储芯片和显示模块。相比I2C,SPI没有复杂的地址机制和应答信号,时钟频率可以轻松达到几十MHz。但它的缺点也很明显——没有流控机制,且需要更多硬件引脚。下面这张表对比了SPI与常见串行协议的差异:
| 特性 | SPI | I2C | UART |
|---|---|---|---|
| 通信方式 | 同步 | 同步 | 异步 |
| 数据线数量 | 4线 | 2线 | 2线 |
| 最大速率 | 50MHz+ | 3.4MHz | 115200bps |
| 拓扑结构 | 点对多点 | 多主多从 | 点对点 |
| 硬件流控 | 不支持 | 不支持 | 支持 |
提示:选择通信协议时,如果设备距离小于1米且需要高速传输,SPI通常是首选方案。但需要控制引脚数量时,I2C可能更合适。
2. SPI硬件电路深度剖析
2.1 四线制信号定义
标准的SPI接口包含四条关键信号线:
- SCLK(Serial Clock):主设备产生的同步时钟,频率决定传输速率
- MOSI(Master Out Slave In):主设备发送数据线
- MISO(Master In Slave Out):从设备返回数据线
- SS/CS(Slave Select):从设备片选信号(低电平有效)
在我的一个气象站项目中,曾用SPI连接BME280温湿度传感器。电路设计时特别注意了走线长度匹配——当SCLK超过10MHz时,信号线长度差应控制在5cm以内,否则会出现时序错乱。以下是推荐的PCB布局要点:
- SCLK走线尽可能短且直
- MOSI/MISO平行走线,保持等长
- 每个SS信号单独拉线,避免共用
- 在信号末端放置33Ω电阻进行阻抗匹配
2.2 多从设备连接方案
SPI支持两种多设备连接方式:
- 独立片选型:每个从设备有独立的SS线
- 优点:通信互不干扰
- 缺点:占用主设备IO口多
- 菊花链型:设备串联,数据依次传递
- 优点:仅需一个SS线
- 缺点:延迟累积,软件处理复杂
曾在一个工业控制器中使用过菊花链方案连接多个MAX7219 LED驱动芯片。关键是要确保:
- 所有设备的CPOL/CPHA模式一致
- 数据移位速率匹配最慢的设备
- 每次传输的时钟周期数是数据位数的整数倍
3. SPI协议工作时序详解
3.1 时钟极性与时相
SPI有四种工作模式,由CPOL(Clock Polarity)和CPHA(Clock Phase)组合决定:
| 模式 | CPOL | CPHA | 时钟空闲状态 | 数据采样边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
注意:主从设备必须配置相同模式。例如,SD卡通常使用模式0,而FLASH芯片多用模式3。
3.2 完整通信流程示例
以读取W25Q128 Flash芯片ID为例:
- 拉低CS信号激活设备
- 发送0x9F指令(8个时钟周期)
- 继续输出24个时钟周期接收3字节ID
- 拉高CS结束传输
用逻辑分析仪捕获的波形显示,实际传输中存在约50ns的建立时间(从CS拉低到第一个时钟边沿)。因此软件实现时需要添加延时:
c复制void read_flash_id() {
CS_LOW();
delay_us(1); // 等待建立时间
spi_transfer(0x9F);
id[0] = spi_transfer(0xFF);
id[1] = spi_transfer(0xFF);
id[2] = spi_transfer(0xFF);
CS_HIGH();
}
4. 常见问题排查手册
4.1 无数据返回故障
现象:主设备能发送但MISO始终为高电平
- 检查清单:
- 确认从设备供电正常(实测电压非空载值)
- 用示波器观察CS信号是否真正拉低
- 核对SPI模式是否匹配
- 测量MISO线是否对地短路
去年调试一块定制板时,发现MISO信号被PCB上的过孔损坏。最终用飞线直接连接解决,教训是高速信号线要避免90°拐角。
4.2 数据错位问题
现象:接收到的数据位序颠倒
- 解决方案:
- 确认设备使用的字节序(MSB/LSB)
- 检查SPI控制器是否支持位序配置
- 在软件层添加位反转处理
c复制// 位反转函数示例
uint8_t reverse_bits(uint8_t b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
4.3 时钟干扰处理
当SPI线缆较长时(>30cm),可能出现:
- 时钟边沿振铃
- 数据采样不稳定
可采取的措施:
- 降低时钟频率(至少减半)
- 在信号线上串联100Ω电阻
- 使用双绞线或屏蔽线
- 在接收端添加50pF对地电容
5. 进阶优化技巧
5.1 DMA加速传输
对于STM32等MCU,使用DMA可以释放CPU资源。配置要点:
- 设置DMA为循环模式
- 使能SPI的TX/RX DMA请求
- 注意缓冲区对齐(4字节边界)
c复制// STM32Cube HAL示例
hspi1.hdmatx->XferCpltCallback = spi_tx_done_cb;
HAL_SPI_Transmit_DMA(&hspi1, tx_buf, length);
5.2 软件模拟SPI
当硬件SPI不够用时,可用GPIO模拟:
- 精确控制时序(nop延时)
- 支持任意引脚配置
- 灵活调整速率
c复制void soft_spi_write(uint8_t data) {
for(int i=0; i<8; i++) {
MOSI = (data >> (7-i)) & 0x01;
SCLK = 1;
delay_ns(50);
SCLK = 0;
delay_ns(50);
}
}
5.3 信号完整性测试
使用示波器进行关键测量:
- 建立/保持时间(Setup/Hold)
- 数据应在时钟边沿前稳定至少5ns
- 上升/下降时间
- 20MHz时钟应<10ns
- 过冲幅度
- 不应超过电源电压的20%
建议保存正常波形作为参考模板,后续调试时可快速对比。