1. STM32G4 SPI通信基础解析
作为一名嵌入式开发工程师,我经常需要在STM32系列MCU上实现SPI通信。STM32G4系列的SPI外设功能强大但配置复杂,今天我就结合实战经验,详细解析SPI的硬件接口和工作原理。
SPI(Serial Peripheral Interface)是一种高速、全双工的同步串行通信接口,在嵌入式系统中广泛应用。与I2C相比,SPI没有复杂的地址机制和应答信号,通信速率更高,但需要更多的硬件连线。
1.1 SPI硬件接口详解
STM32G4的SPI接口包含4个标准信号线:
- MOSI(Master Out Slave In):主设备数据输出,从设备数据输入
- MISO(Master In Slave Out):主设备数据输入,从设备数据输出
- SCK(Serial Clock):由主设备产生的同步时钟
- NSS(Slave Select):从设备选择信号(低电平有效)
实际应用中,NSS引脚在某些简单场景可以省略,主从设备通过硬件连线直接绑定。但在多从机系统中,NSS是必需的。

在硬件设计时需要注意:
- 主从设备的MOSI和MISO需要交叉连接
- SCK由主设备产生,所有从设备共享同一时钟
- 在多从机系统中,每个从机需要独立的NSS线
- 长距离通信时建议加入终端电阻匹配阻抗
1.2 SPI时钟极性与相位配置
SPI通信的核心是时钟同步,时钟的极性和相位配置直接影响数据采样时机。这两个参数通过CPOL和CPHA位配置:
-
CPOL(Clock Polarity):控制SCK空闲状态
- 0:SCK空闲时为低电平
- 1:SCK空闲时为高电平
-
CPHA(Clock Phase):控制数据采样边沿
- 0:在第一个时钟边沿采样数据
- 1:在第二个时钟边沿采样数据
这两个参数的组合形成四种工作模式:
| 模式 | CPOL | CPHA | 采样时机 |
|---|---|---|---|
| 0 | 0 | 0 | 上升沿采样 |
| 1 | 0 | 1 | 下降沿采样 |
| 2 | 1 | 0 | 下降沿采样 |
| 3 | 1 | 1 | 上升沿采样 |

关键点:主从设备必须使用相同的时钟模式,否则无法正常通信。建议在硬件设计阶段就确定好通信模式。
2. STM32G4 SPI工作模式深度解析
2.1 NSS引脚工作模式
NSS(从机选择)引脚有三种工作模式,通过SSM和SSOE位配置:
-
软件模式(SSM=1)
- NSS引脚功能由软件控制
- 通过SPIx_CR1寄存器的SSI位设置电平
- 适用于简单的主从通信场景
-
硬件输入模式(SSM=0, SSOE=0)
- NSS作为输入引脚
- 当NSS被拉低时,设备自动切换为从模式
- 用于多主机冲突检测
-
硬件输出模式(SSM=0, SSOE=1)
- NSS作为输出引脚
- 主机模式下自动控制从机选择
- 支持脉冲模式(每个字节传输后自动拉高)

在实际项目中,我通常这样选择:
- 单主机单从机:软件模式最简单
- 一主多从:硬件输出模式最方便
- 多主机系统:必须使用硬件输入模式
2.2 SPI通信拓扑结构
STM32G4支持多种SPI通信拓扑,适应不同应用场景:
- 4线全双工模式
- MOSI和MISO同时工作
- 最高通信效率
- 需要4根信号线

- 2线/3线半双工模式
- 共用数据线
- 节省IO资源
- 通信效率减半

- 4线单向通信
- 只读或只写模式
- 适用于传感器等单向数据流设备

- 一主多从系统
- 共享SCK、MOSI、MISO
- 每个从机独立NSS
- 软件控制从机选择

- 多主机系统
- 需要硬件输入模式
- 支持总线冲突检测
- 需要软件仲裁机制

3. STM32G4 SPI实战配置
3.1 SPI初始化配置步骤
以标准外设库为例,配置SPI的典型流程如下:
c复制// 1. 使能SPI时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
// 2. 配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; // SCK/MISO/MOSI
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置SPI参数
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
// 4. 使能SPI
SPI_Cmd(SPI1, ENABLE);
关键参数说明:
SPI_BaudRatePrescaler:设置SPI时钟分频,影响通信速率SPI_FirstBit:数据传输顺序(MSB/LSB first)SPI_CRCPolynomial:CRC多项式,用于硬件CRC校验
3.2 SPI数据收发实现
STM32G4的SPI数据收发通过DR(Data Register)实现,这是一个8/16位的FIFO队列:
c复制// 发送数据
void SPI_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPIx, Data);
}
// 接收数据
uint16_t SPI_ReceiveData(SPI_TypeDef* SPIx)
{
while(SPI_I2S_GetFlagStatus(SPIx, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPIx);
}
注意:STM32G4的SPI DR寄存器是双缓冲的,写入数据会进入发送缓冲区,读取数据是从接收缓冲区获取。这种设计允许连续传输而不需要等待前一个字节完全发送完成。
4. SPI通信实战经验与问题排查
4.1 SPI通信常见问题
在实际项目中,我遇到过以下典型问题:
-
数据错位或丢失
- 原因:主从设备时钟模式不匹配
- 解决:检查CPOL/CPHA设置是否一致
-
从机无响应
- 原因:NSS信号配置错误
- 解决:确认从机选择信号有效,检查硬件连接
-
通信速率不稳定
- 原因:时钟分频设置不当或线缆过长
- 解决:调整预分频值,缩短通信距离
-
DMA传输数据错乱
- 原因:缓冲区未对齐或长度设置错误
- 解决:确保DMA缓冲区地址和长度符合要求
4.2 SPI性能优化技巧
-
使用DMA提升效率
- 对于大数据量传输,DMA可以显著降低CPU开销
- 配置示例:
c复制DMA_InitTypeDef DMA_InitStructure; 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 = bufferSize; 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_Channel3, &DMA_InitStructure); -
合理设置时钟分频
- 根据从设备最大支持速率选择分频值
- 公式:SPI时钟 = APB时钟 / 预分频值
-
利用硬件NSS简化控制
- 硬件NSS模式可以减少软件开销
- 特别适合一主多从系统
-
注意电气特性
- 高速SPI需要良好的PCB布局
- 长距离传输建议使用差分信号
5. 高级应用:多主机SPI系统实现
在多主机SPI系统中,总线冲突是主要挑战。STM32G4提供了硬件支持:
-
硬件冲突检测
- 当NSS被拉低时,主机自动转为从机
- 通过SPI_SR寄存器的MODF位检测冲突
-
软件仲裁机制
- 实现简单的优先级或令牌环算法
- 冲突后随机退避重试
配置示例:
c复制// 多主机模式初始化
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;
SPI_InitStructure.SPI_SSM = 0;
SPI_InitStructure.SPI_SSOE = 0; // NSS作为输入
SPI_Init(SPI1, &SPI_InitStructure);
// 冲突检测中断
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_ERR, ENABLE);
NVIC_EnableIRQ(SPI1_IRQn);
在中断服务程序中处理冲突:
c复制void SPI1_IRQHandler(void)
{
if(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_MODF) != RESET)
{
// 清除标志
SPI_I2S_ClearITPendingBit(SPI1, SPI_I2S_IT_MODF);
// 重新初始化SPI为主机
SPI_Cmd(SPI1, DISABLE);
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
// 实现退避算法
Delay_ms(randomBackoffTime());
}
}
通过这种机制,可以实现可靠的SPI多主机通信系统。在实际项目中,我发现合理的退避时间设置对系统稳定性至关重要。