SPI(Serial Peripheral Interface)作为嵌入式系统中最常用的同步串行通信协议之一,其设计哲学体现了"简单即美"的工程理念。我在多个工业级项目中实测发现,SPI的通信速率可以轻松达到10MHz以上,这是I2C协议难以企及的。但高速度也带来了严格的时序要求,这也是许多开发者初次接触SPI时容易踩坑的地方。
SPI总线由四条关键信号线构成:
特别注意:不同厂商的MCU文档可能使用不同的信号线命名,例如NXP的LPC系列常将FSS称为SSEL,而ST的STM32系列则多用NSS表示。
SPI最显著的特点是全双工通信能力。这意味着在单个时钟周期内,主机和从机可以同时收发数据。我在调试STM32F4系列时发现,这种特性使得SPI的理论吞吐量是I2C的两倍。但实际工程中,我们往往只利用其半双工特性,特别是在连接SPI Flash等存储设备时。
SPI通信质量很大程度上取决于时钟配置的正确性。CPOL(Clock Polarity)和CPHA(Clock Phase)这两个参数决定了数据采样和保持的时机:
| 模式 | CPOL | CPHA | 时钟空闲状态 | 数据采样边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
在STM32项目中,Mode 0和Mode 3是最常用的配置。我曾在智能家居网关开发中遇到一个典型问题:当主从设备分别采用不同模式时,会出现数据错位。后来通过逻辑分析仪捕获波形发现,从机设备(温湿度传感器)要求Mode 1,而主机默认配置为Mode 0,导致采样边沿错位。
下图展示了一个典型的SPI Mode 0通信波形(基于STM32F103实测):
code复制SCK : _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾
MOSI : D7 D6 D5 D4 D3 D2 D1 D0
MISO : D7 D6 D5 D4 D3 D2 D1 D0
FSS : ‾‾‾\_______________________/‾‾‾
在工业自动化项目中,我总结出一个重要经验:SCK的空闲状态必须与CPOL严格一致。曾经有个案例,CPOL设为1但硬件上拉电阻导致实际空闲状态为0,结果从设备无法正确识别时钟起始边沿。
以STM32F407为例,配置SPI主机时需要关注以下寄存器:
一个可靠的初始化流程应该是:
关键细节:SPI时钟分频系数需要根据系统时钟和所需波特率计算。例如72MHz系统时钟下,要得到1.125MHz SPI时钟,分频值应为64(72/64=1.125)。
从机模式下的稳定性问题是工程实践中的难点。通过多个项目验证,我总结出以下DMA配置要点:
在智能电表项目中,我们遇到首字节丢失问题。最终解决方案是:
以下是一个经过验证的主从通信代码框架:
c复制// 主机发送函数
void SPI_MasterTransmit(uint8_t *pData, uint16_t Size) {
GPIO_ResetBits(GPIOA, GPIO_Pin_4); // FSS拉低
for(uint16_t i=0; i<Size; i++) {
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
SPI_I2S_SendData(SPI1, pData[i]);
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
uint8_t received = SPI_I2S_ReceiveData(SPI1);
UART_SendByte(received); // 通过UART输出
}
GPIO_SetBits(GPIOA, GPIO_Pin_4); // FSS拉高
}
// 从机DMA配置
void SPI_SlaveDMA_Init(void) {
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel0);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)TxBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel0, &DMA_InitStructure);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 首字节错误 | DMA启动延迟 | 预装DMA或提前使能 |
| 数据错位 | 主从模式不匹配 | 检查CPOL/CPHA配置 |
| 通信不稳定 | 布线过长/干扰 | 缩短走线,加终端电阻 |
| 从机无响应 | FSS极性错误 | 确认片选信号有效电平 |
| DMA传输不完整 | 缓冲区溢出 | 检查DMA缓冲区大小 |
在最近的一个电机控制项目中,我们发现SPI通信在电机启动时会出现偶发错误。通过示波器捕获发现,电机启动瞬间电源有200mV的跌落。最终通过以下措施解决:
SPI Flash器件(如Winbond W25Q系列)之所以能在高频率下稳定工作,源于其专为SPI优化的硬件架构:
我在开发TF卡读写器时做过对比测试:
这种差异提醒我们,在设计自定义SPI从设备时,应该尽量采用硬件状态机而非软件响应,以提升通信可靠性。