1. SPI协议基础:时钟极性与相位
SPI协议的核心难点在于理解时钟极性(CPOL)和时钟相位(CPHA)这两个关键参数。我第一次接触SPI时,花了整整三天才搞明白Mode 0和Mode 3的区别。简单来说,CPOL决定了时钟空闲时的电平状态,而CPHA决定了数据在时钟的哪个边沿被采样。
- CPOL=0:时钟空闲时为低电平
- CPOL=1:时钟空闲时为高电平
- CPHA=0:数据在第一个时钟边沿采样
- CPHA=1:数据在第二个时钟边沿采样
实际项目中,最常遇到的问题是主从设备模式不匹配。比如用STM32作为主机连接SPI Flash时,如果模式设置错误,读回来的数据全是0xFF。这时候需要反复检查设备手册中的时序图,确认从设备支持的模式。
2. 四种工作模式详解
2.1 Mode 0:CPOL=0, CPHA=0
这是最常见的模式,W25Q系列Flash、ADXL345加速度计等器件都默认使用此模式。特点如下:
- 时钟空闲时为低电平
- 数据在上升沿采样,下降沿切换
- 主机在下降沿更新MOSI数据,从机在上升沿捕获
// STM32 HAL库配置示例 hspi1.Instance = SPI1; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPha = SPI_PHASE_1EDGE;2.2 Mode 1:CPOL=0, CPHA=1
某些RFID读卡器会采用这种模式。实测发现NRF24L01模块虽然文档写支持Mode 0,但实际通信需要Mode 1:
- 时钟空闲仍为低电平
- 数据在下降沿采样,上升沿切换
- 主机在上升沿更新数据
2.3 Mode 2:CPOL=1, CPHA=0
这种模式比较少见,但在一些老款传感器中会遇到。我曾在一个温湿度传感器项目中被这个模式坑过:
- 时钟空闲时为高电平
- 数据在下降沿采样
- 数据稳定时间要求更严格
2.4 Mode 3:CPOL=1, CPHA=1
某些特定型号的ADC模块会使用此模式。特点是:
- 时钟空闲高电平
- 数据在上升沿采样
- 主机在下降沿更新数据
提示:使用逻辑分析仪抓取SPI波形时,建议先设置CPOL=0观察起始边沿,再确认CPHA
3. 实战:配置SPI Flash存储器
以W25Q128JV Flash芯片为例,演示完整配置流程:
3.1 硬件连接检查
首先确认硬件连接:
- SCK → PA5
- MISO → PA6
- MOSI → PA7
- CS → PA4(软件控制)
3.2 STM32CubeMX配置
- 选择SPI1模式为Full-Duplex Master
- 设置Prescaler为2分频(16MHz/2=8MHz)
- 选择CPOL=Low, CPHA=1Edge
- 开启DMA通道(可选)
// 生成代码后检查结构体 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;3.3 读写函数实现
// 写使能函数 void W25Q_WriteEnable(void) { CS_LOW(); HAL_SPI_Transmit(&hspi1, (uint8_t[]){0x06}, 1, 100); CS_HIGH(); } // 页编程函数 void W25Q_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { W25Q_WriteEnable(); CS_LOW(); uint8_t cmd[4] = {0x02, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF}; HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Transmit(&hspi1, data, len, 1000); CS_HIGH(); }4. 常见问题排查指南
4.1 通信失败检查步骤
- 确认电源稳定:用示波器检查3.3V电源纹波
- 检查时钟信号:SCK线是否有正常波形
- 验证模式设置:逻辑分析仪捕获时序对照手册
- 测试CS信号:确保片选信号有效拉低
- 检查上拉电阻:长距离通信需要加1kΩ上拉
4.2 典型错误案例
案例1:某次使用Mode 0读取MPU9250数据,始终返回0xFF。最终发现陀螺仪实际需要Mode 3,修改CPOL和CPHA后正常。
案例2:SPI总线挂载多个设备时,发现数据冲突。原因是CS信号切换不及时,增加1us延时后解决。
案例3:高速通信(>10MHz)时数据出错。通过缩短走线长度、添加端接电阻改善信号完整性。
5. 进阶技巧与优化建议
5.1 DMA传输优化
对于大数据量传输,建议使用DMA:
// 启用DMA传输 HAL_SPI_Transmit_DMA(&hspi1, txBuf, length); HAL_SPI_Receive_DMA(&hspi1, rxBuf, length); // 回调函数处理完成事件 void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { // 传输完成处理 }5.2 时钟配置技巧
不同STM32系列的时钟树配置差异:
- F1系列最大SPI时钟为PCLK/2
- F4系列可达42MHz
- H7系列支持最高150MHz
5.3 多从机管理方案
推荐两种实现方式:
独立CS方案:每个从设备独占一个GPIO
- 优点:时序控制简单
- 缺点:占用IO资源
复用CS方案:使用译码器(如74HC138)
- 优点:节省IO
- 缺点:增加硬件复杂度
最后分享一个实用技巧:在PCB布局时,SPI信号线要尽量等长,避免信号偏移。曾经有个项目因为SCK走线比MOSI长2cm,导致10MHz以上通信不稳定。