1. SPI通信协议概述
SPI(Serial Peripheral Interface)是一种同步串行通信协议,由Motorola在1980年代提出。作为嵌入式系统中最常用的通信接口之一,它以全双工、高速、简单的硬件实现著称。典型的SPI系统包含一个主设备(Master)和一个或多个从设备(Slave),通过四根信号线完成数据交换:
- SCLK(Serial Clock):时钟信号线,由主设备控制
- MOSI(Master Out Slave In):主设备输出,从设备输入
- MISO(Master In Slave Out):主设备输入,从设备输出
- SS/CS(Slave Select/Chip Select):从设备选择信号
实际应用中,SPI的硬件连接可能因厂商而异,有些文档会使用SCK(Serial Clock)、SDI(Serial Data In)、SDO(Serial Data Out)等不同命名方式,但通信原理相同。
2. SPI工作原理深度解析
2.1 时钟极性(CPOL)与相位(CPHA)
SPI通信的核心在于时钟同步,其工作模式由CPOL(Clock Polarity)和CPHA(Clock Phase)两个参数决定:
| 模式 | CPOL | CPHA | 时钟空闲状态 | 数据采样边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
以模式0为例:
- 时钟空闲时为低电平
- 数据在上升沿被采样
- 数据在下降沿发生变化
不同外设可能支持不同模式,使用前必须查阅器件手册确认。我曾遇到过因模式设置错误导致传感器数据全为0xFF的情况,排查了整整一天才发现是CPHA配置问题。
2.2 数据传输时序
SPI是全双工通信,主从设备同时收发数据。以8位数据传输为例:
- 主设备拉低对应从设备的SS线
- 主设备生成时钟信号
- 每个时钟周期:
- 主设备通过MOSI发送1位数据
- 从设备通过MISO返回1位数据
- 传输完成后,主设备拉高SS线
数据传输通常采用MSB(最高有效位)优先,但某些器件支持LSB优先模式,这需要在初始化时配置。
3. SPI硬件接口实现
3.1 典型连接方式
标准SPI连接示意图:
code复制主设备 从设备
SCLK ----------- SCLK
MOSI ----------- MOSI
MISO ----------- MISO
SS ----------- SS
多从设备连接方案:
- 独立SS线方式:每个从设备使用独立的SS线
- 菊花链方式:从设备串联,数据依次传递
3.2 电平转换考虑
当主从设备使用不同电压时(如3.3V与5V系统互联),必须加入电平转换电路。常用方案:
- 电阻分压(单向通信)
- 专用电平转换芯片(如TXB0108)
- 光耦隔离(工业环境)
4. SPI软件实现要点
4.1 寄存器配置示例(STM32)
c复制// SPI1初始化示例(模式0, 8位数据, MSB优先)
void SPI1_Init(void)
{
SPI_HandleTypeDef hspi1;
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_256;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi1);
}
4.2 数据传输函数
c复制// SPI发送接收函数
uint8_t SPI_Transfer(uint8_t data)
{
uint8_t rx_data;
HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1, HAL_MAX_DELAY);
return rx_data;
}
5. SPI性能优化技巧
5.1 时钟频率选择
SPI时钟频率计算公式:
code复制实际速率 = 主时钟频率 / 预分频系数
选择原则:
- 从设备支持的最大速率
- 线路长度和干扰情况
- 系统实时性需求
我曾测试过不同线长下的最大可靠速率:10cm内可达10MHz,50cm降至1MHz以下。长距离传输建议降低速率并加入终端电阻。
5.2 批量传输优化
对于大数据量传输:
- 使用DMA减少CPU开销
- 合理设置FIFO阈值
- 采用中断而非轮询方式
6. 常见问题排查
6.1 无数据返回
排查步骤:
- 确认SS信号有效
- 检查时钟信号是否正常
- 验证模式(CPOL/CPHA)设置
- 测量MISO线电平变化
6.2 数据错位
可能原因:
- 时钟极性/相位错误
- 位序(MSB/LSB)配置错误
- 时钟频率过高导致建立时间不足
7. SPI与其他接口对比
| 特性 | SPI | I2C | UART |
|---|---|---|---|
| 通信方式 | 同步 | 同步 | 异步 |
| 数据线数量 | 4+ | 2 | 2 |
| 最大速率 | 50MHz+ | 3.4MHz | 1Mbps |
| 寻址方式 | 硬件SS | 软件地址 | 点对点 |
| 硬件复杂度 | 简单 | 中等 | 简单 |
选择建议:
- 高速、短距离:SPI
- 多设备、中速:I2C
- 远距离、简单:UART
8. 进阶应用技巧
8.1 虚拟SPI实现
当硬件SPI资源不足时,可用GPIO模拟:
c复制void Soft_SPI_Write(uint8_t data)
{
for(int i=0; i<8; i++) {
CLK_LOW();
if(data & 0x80) MOSI_HIGH();
else MOSI_LOW();
delay_us(1);
CLK_HIGH();
delay_us(1);
data <<= 1;
}
}
8.2 噪声抑制措施
工业环境建议:
- 使用双绞线或屏蔽线
- 加入RC滤波(典型值:100Ω+100pF)
- 在SS线上拉电阻(10kΩ)
- 缩短走线长度
9. 典型应用场景
9.1 存储器接口
SPI Flash读写流程:
- 发送写使能指令(0x06)
- 发送页编程指令(0x02)+地址
- 写入数据
- 等待写完成
9.2 传感器数据采集
以BME280环境传感器为例:
c复制// 读取温度数据
void BME280_ReadTemp(void)
{
uint8_t cmd[4] = {0xFA, 0xFB, 0xFC, 0};
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, cmd, rx_data, 4, 100);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
int32_t temp_raw = (rx_data[1]<<12)|(rx_data[2]<<4)|(rx_data[3]>>4);
// 进行温度补偿计算...
}
10. 调试工具推荐
- 逻辑分析仪:Saleae Logic Pro 8
- 支持SPI协议解码
- 可同时捕获多路信号
- 示波器:Rigol DS1104Z
- 观察信号质量
- 测量建立/保持时间
- 万用表:测量线路通断和电平
调试技巧:
- 先单独测试时钟信号
- 使用已知好的从设备验证主设备
- 逐步提高时钟频率测试稳定性