1. SPI通信协议基础认知
SPI(Serial Peripheral Interface)作为一种同步串行通信协议,在嵌入式系统中扮演着重要角色。我第一次接触SPI是在调试传感器模块时,当时为了读取陀螺仪数据,不得不深入理解这个看似简单却暗藏玄机的通信方式。SPI以其全双工、高速传输的特性,在存储器、传感器、显示屏等外设连接中广泛应用。
与UART和I2C相比,SPI最大的特点是没有复杂的地址机制和标准帧格式。它采用主从架构,通常由一个主设备(如MCU)控制一个或多个从设备。实际项目中,我遇到过最多同时控制三个从设备的场景,通过片选信号(CS)轮流激活不同设备。这种灵活性使得SPI在需要高速数据传输的场景中优势明显,比如我在驱动OLED屏时,SPI的刷新速率明显优于I2C。
注意:SPI虽然协议简单,但不同厂商的芯片实现可能存在差异,特别是时钟极性和相位的组合,这往往是调试时最容易出错的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SPI硬件架构与信号解析
2.1 四线制基础结构
标准的SPI接口包含四条关键信号线:
- SCLK(Serial Clock):时钟信号,由主设备产生
- MOSI(Master Out Slave In):主设备输出,从设备输入
- MISO(Master In Slave Out):主设备输入,从设备输出
- SS/CS(Slave Select/Chip Select):从设备选择信号
在调试STM32与Flash存储器的通信时,我发现硬件设计有个细节容易被忽视:当系统中只有一个从设备时,CS引脚可以固定接低电平;但多设备情况下,必须为每个从设备分配独立的CS线。曾经有个项目因为CS线处理不当,导致设备间数据冲突,浪费了两天排查时间。
2.2 时钟模式详解
SPI的时钟配置涉及两个关键参数:
- CPOL(Clock Polarity):时钟空闲状态
- 0:空闲时为低电平
- 1:空闲时为高电平
- CPHA(Clock Phase):数据采样边沿
- 0:在第一个时钟边沿采样
- 1:在第二个时钟边沿采样
这组合出四种工作模式,不同设备可能要求不同模式。例如,SD卡在SPI模式下通常使用模式0,而某些传感器可能要求模式3。我在使用ICM20948惯性测量单元时,就曾因为模式设置错误导致读数全为0xFF。
3. SPI通信时序深度剖析
3.1 典型数据传输过程
以一个完整的8位数据传输为例:
- 主设备拉低对应从设备的CS信号
- 主设备生成时钟信号(根据CPOL/CPHA)
- 每个时钟周期,主从设备同时交换1位数据
- 传输完成后,主设备拉高CS信号
在逻辑分析仪上观察SPI波形时,我发现数据有效性总是相对于时钟边沿的。例如在模式0下,MOSI数据在时钟上升沿前必须稳定,而MISO数据则在下降沿后被从设备输出。这个细节对时序要求严格的设计尤为重要。
3.2 多从设备连接方案
当系统需要连接多个SPI设备时,有三种典型方案:
- 独立CS线方案:每个设备有独立的CS线,主设备轮流激活
- 优点:各设备完全隔离
- 缺点:占用较多GPIO
- 菊花链方案:设备串联,数据依次传递
- 优点:节省CS线
- 缺点:所有设备必须支持该模式
- 软件片选方案:通过其他GPIO模拟CS信号
- 优点:灵活
- 缺点:增加软件复杂度
我在驱动多个WS2811 LED控制器时,就采用了菊花链方案,通过级联方式减少了布线复杂度。但需要注意,这种方案要求所有设备时钟速率兼容。
4. SPI实例代码解析
4.1 STM32硬件SPI配置
以下是使用STM32CubeMX配置SPI1的典型代码片段:
c复制/* SPI1 init function */
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
关键参数说明:
- BaudRatePrescaler:决定SPI时钟频率,系统时钟72MHz时,分频4得到18MHz
- FirstBit:大多数设备采用MSB优先传输
- NSS:软件控制片选更灵活
4.2 SPI数据收发示例
读写SPI Flash的典型操作:
c复制#define SPI_FLASH_CS_LOW() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET)
#define SPI_FLASH_CS_HIGH() HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET)
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{
uint8_t ret;
HAL_SPI_TransmitReceive(&hspi1, &byte, &ret, 1, HAL_MAX_DELAY);
return ret;
}
void SPI_FLASH_ReadData(uint32_t addr, uint8_t *pBuffer, uint32_t size)
{
SPI_FLASH_CS_LOW();
SPI_FLASH_SendByte(0x03); // Read command
SPI_FLASH_SendByte((addr >> 16) & 0xFF);
SPI_FLASH_SendByte((addr >> 8) & 0xFF);
SPI_FLASH_SendByte(addr & 0xFF);
while(size--) {
*pBuffer++ = SPI_FLASH_SendByte(0xFF);
}
SPI_FLASH_CS_HIGH();
}
经验:SPI Flash操作时,地址通常采用24位格式,分三次发送。读操作后需要发送伪数据(0xFF)来产生时钟信号。
5. SPI实战问题排查指南
5.1 常见故障现象与对策
-
无任何响应
- 检查CS信号是否有效
- 确认时钟信号是否产生
- 验证电源和地线连接
-
收到全0xFF或全0x00
- 检查时钟极性/相位设置
- 确认MISO线连接正确
- 检查从设备是否进入正确模式
-
数据错位或丢失
- 降低时钟频率测试
- 检查信号线长度和干扰
- 确认MSB/LSB设置匹配
5.2 逻辑分析仪调试技巧
使用Saleae逻辑分析仪时,我总结出以下SPI调试要点:
- 设置正确的采样率(至少4倍于SPI时钟)
- 添加SPI协议解码器,配置匹配的时钟模式
- 同时捕获CS信号,便于区分传输帧
- 关注数据与时钟边沿的对应关系
曾经有个项目,SPI通信间歇性失败,通过逻辑分析仪发现是CS信号抖动导致的。最终在软件中添加了CS信号稳定延时解决了问题。
6. SPI性能优化实践
6.1 时钟速率选择策略
SPI速率并非越高越好,需考虑:
- 从设备支持的最大速率
- 线路长度和布线质量
- 系统实时性要求
我的经验法则是:从设备标称最大速率的80%开始测试,逐步提高直到出现错误,然后回退20%作为工作频率。例如,某Flash标称50MHz,实际稳定工作在40MHz。
6.2 DMA传输应用
对于大数据量传输,使用DMA可以显著降低CPU负载:
c复制HAL_SPI_Transmit_DMA(&hspi1, txBuffer, length);
HAL_SPI_Receive_DMA(&hspi1, rxBuffer, length);
需要注意:
- 确保缓冲区地址和长度对齐
- 处理DMA传输完成中断
- 避免在传输过程中修改缓冲区
在驱动SPI TFT屏时,使用DMA使刷新率从15fps提升到了45fps,同时CPU占用率从70%降至15%。
7. 特殊SPI应用场景
7.1 模拟SPI实现
当硬件SPI资源不足时,可以用GPIO模拟:
c复制void SoftSPI_WriteByte(uint8_t byte)
{
for(int i=0; i<8; i++) {
SOFT_SPI_CLK_LOW();
if(byte & 0x80) SOFT_SPI_MOSI_HIGH();
else SOFT_SPI_MOSI_LOW();
SOFT_SPI_CLK_HIGH();
byte <<= 1;
}
}
模拟SPI的优缺点:
- 优点:不占用硬件资源,时序完全可控
- 缺点:速度慢(通常<1MHz),占用CPU资源
7.2 SPI与其他协议转换
有些场景需要协议转换,如:
- SPI转UART:用于远距离通信
- SPI转I2C:连接仅支持I2C的设备
- SPI转USB:与PC通信
我曾使用CP2130芯片实现SPI转USB功能,方便上位机直接访问SPI设备。关键点在于正确配置转换芯片的寄存器,匹配两端协议参数。
