1. SPI协议基础解析
SPI(Serial Peripheral Interface)作为一种高速全双工同步串行通信协议,在嵌入式系统中扮演着重要角色。我第一次接触SPI是在开发一个传感器数据采集系统时,当时需要以10MHz的速率读取加速度计数据。相比I2C协议,SPI的简洁性和高速特性让我印象深刻。
1.1 物理层架构
标准SPI采用4线制设计:
- SCK(Serial Clock):主设备产生的同步时钟信号,频率可达几十MHz。我在STM32F4系列上实测最高可达42MHz
- MOSI(Master Out Slave In):主设备发送数据线。注意布线时要远离高频干扰源
- MISO(Master In Slave Out):从设备返回数据线。当从设备不发送数据时需保持高阻态
- CS/SS(Chip Select/Slave Select):片选信号(低电平有效)。多从机系统需要为每个设备分配独立CS引脚
实际应用中我曾遇到三线制SPI设备(如某些OLED屏),此时MISO和MOSI合并为单数据线,通信效率会降低约40%
1.2 电气特性
SPI采用推挽输出结构,这与I2C的开漏输出形成鲜明对比。推挽输出的优势在于:
- 无需外接上拉电阻(I2C通常需要4.7kΩ上拉)
- 边沿更陡峭(实测上升时间可缩短至3ns)
- 抗干扰能力更强(在电机控制场景中表现优异)
但需要注意电平匹配问题,特别是3.3V主控与5V外设通信时,建议使用电平转换芯片如TXB0104。
2. 时序参数详解
2.1 时钟模式组合
CPOL(Clock Polarity)和CPHA(Clock Phase)的组合构成SPI的四种工作模式:
| 模式 | CPOL | CPHA | 采样边沿 | 适用场景 |
|---|---|---|---|---|
| 0 | 0 | 0 | 上升沿 | 多数传感器(如BMP280) |
| 1 | 0 | 1 | 下降沿 | 某些Flash存储器 |
| 2 | 1 | 0 | 下降沿 | 特定RF模块 |
| 3 | 1 | 1 | 上升沿 | 部分ADC芯片 |
我在调试MPU9250时发现,其SPI模式必须配置为模式3,否则读取的陀螺仪数据会出现周期性错误。
2.2 时序关键参数
-
建立时间(tSU):数据在采样边沿前必须稳定的最小时间
- 典型值:5ns(高速SPI Flash)
- 计算公式:tSU > 1/(2*fSCK) + PCB延迟
-
保持时间(tH):数据在采样边沿后必须保持稳定的最小时间
- 典型值:3ns(多数MCU)
- 实测发现STM32的保持时间裕量较大
-
时钟偏差(Skew):
c复制// 测量代码示例(使用GPIO和定时器) start_timer(); GPIO_Set(); // 触发SCK上升沿 while(!GPIO_Read()); // 等待MOSI变化 skew = read_timer();
3. 硬件实现要点
3.1 MCU外设配置
以STM32CubeMX配置为例:
- 选择SPI模式(全双工/半双工)
- 设置预分频器(PCLK/2, /4, /8...)
- 配置数据宽度(8bit/16bit)
- 设置MSB/LSB优先
- 使能硬件NSS(可选)
c复制// 典型初始化代码
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_8;
HAL_SPI_Init(&hspi1);
3.2 PCB布局规范
- 等长布线:SCK与数据线长度差控制在±5mm内
- 阻抗匹配:50Ω单端阻抗(FR4板材,线宽0.3mm)
- 避免直角走线:采用45°或圆弧转角
- 地平面:保持完整地平面,关键信号下方不要分割
我曾遇到SPI速率超过20MHz时通信失败的问题,最终发现是MOSI走线比SCK长了15mm,调整后问题解决。
4. 软件优化技巧
4.1 DMA传输配置
高速SPI(>10MHz)建议使用DMA:
c复制// STM32 DMA配置示例
hdma_spi1_tx.Instance = DMA2_Stream3;
hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
HAL_DMA_Init(&hdma_spi1_tx);
__HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);
HAL_SPI_Transmit_DMA(&hspi1, txData, sizeof(txData));
4.2 中断处理优化
- 使用TXE(发送缓冲区空)和RXNE(接收缓冲区非空)中断
- 双缓冲技术:准备下一帧数据时不影响当前传输
- 错误处理:
- 超时检测(典型值:3个SCK周期)
- CRC校验(部分SPI外设支持)
5. 典型问题排查
5.1 通信失败排查步骤
- 检查电源:测量VDD电压(3.3V±5%)
- 验证时钟:用示波器观察SCK波形
- 检查片选:CS信号是否正常拉低
- 测试单字节传输:先尝试最简单的单字节读写
- 确认模式:CPOL/CPHA是否匹配
5.2 常见异常现象
-
数据错位:
- 原因:CPHA配置错误
- 解决:调整采样边沿
-
高频失真:
- 原因:走线阻抗不匹配
- 解决:缩短走线或端接电阻
-
从机无响应:
- 检查CS信号负载能力(建议驱动电流>4mA)
- 验证从机供电时序(某些器件要求电源先于CS有效)
6. 性能优化实践
6.1 速率提升方案
- 时钟源选择:使用PLL输出作为SPI时钟源
- 减少分频系数:在允许范围内选择最小分频
- 优化IO速度:将GPIO设置为最高速模式
c复制
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
6.2 多从机系统设计
-
菊花链拓扑:
- 优点:节省CS引脚
- 缺点:延迟累积(每级增加约10ns)
-
独立CS设计:
- 使用74HC138等译码器扩展CS
- 注意CS切换时的时序间隔(典型值>50ns)
我在工业控制器项目中采用CPLD管理32个SPI从设备,通过寄存器映射实现CS自动切换,将访问延迟控制在100ns以内。
7. 特殊应用场景
7.1 单线半双工模式
某些存储器(如AT45DB系列)支持:
- 共用SI/SO线
- 通过方向控制位切换
- 速率降低至标准模式的60%
7.2 QSPI扩展
四线SPI(Quad-SPI)特点:
- 数据线扩展到4条(IO0-IO3)
- 支持DDR(双倍数据率)
- 典型应用:外扩Flash(如W25Q256)
- 配置示例:
c复制hqspi.Init.ClockPrescaler = 2; // 84MHz/2=42MHz hqspi.Init.FifoThreshold = 4; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
经过多个项目的实践验证,SPI协议的稳定性很大程度上取决于时序配置和物理层设计的合理性。建议在正式开发前先用逻辑分析仪捕获通信波形,确保各项参数符合器件手册要求。对于关键应用,可以考虑在代码中加入CRC校验和超时重试机制。