1. STM32硬件SPI主从通信项目概述
在嵌入式系统开发中,SPI(Serial Peripheral Interface)总线因其高速、全双工、同步通信的特性,成为芯片间通信的首选方案之一。我最近完成了一个基于STM32的硬件SPI主从通信项目,实现了两块STM32开发板之间的稳定数据传输。这个方案相比软件模拟SPI(俗称"模拟SPI")有着明显的性能优势,实测传输速率可达18Mbps(在STM32F4系列上),且CPU占用率极低。
硬件SPI与模拟SPI的关键区别:硬件SPI由专用外设实现,时序严格符合规范;而模拟SPI是通过GPIO翻转实现的,速率和稳定性都较差。在需要可靠通信的场景,硬件SPI是更专业的选择。
项目中使用STM32F427作为主机,STM32F407作为从机,通过4线SPI接口(MOSI、MISO、SCK、NSS)连接。这种配置在工业控制、传感器数据采集等场景非常常见,比如读取SPI Flash(如W25Q系列)、与数字传感器(如ICM-42688)通信等。
2. 硬件设计与电路连接
2.1 硬件选型与接口定义
我选择STM32F4系列是因为其SPI外设功能完整,支持主从模式切换,且内置硬件NSS信号管理。具体引脚分配如下:
| 信号线 | 主机引脚 | 从机引脚 | 备注 |
|---|---|---|---|
| SCK | PA5 | PA5 | 时钟线,主机输出 |
| MOSI | PA7 | PA7 | 主机输出,从机输入 |
| MISO | PA6 | PA6 | 主机输入,从机输出 |
| NSS | PA4 | PA4 | 片选信号,低有效 |
注意:不同STM32型号的SPI引脚可能不同,需查阅对应芯片的"Alternate Function Mapping"表格确认。例如STM32F1系列的SPI1_SCK可能在PA5或PB3。
2.2 电路连接要点
实际接线时需注意:
- 使用短线连接(建议<10cm),避免信号反射
- 若距离较远,应在SCK线上串联22Ω电阻抑制振铃
- 在3.3V系统中,若从机是5V器件,需加电平转换芯片(如TXB0108)
- 每组SPI总线应至少保留一个测试点,方便逻辑分析仪连接
常见错误示例:
// 错误:未启用GPIO时钟直接配置引脚 RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // 必须先启用GPIO时钟! RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;3. SPI外设配置详解
3.1 主机配置流程
使用STM32CubeMX生成初始化代码是最便捷的方式,但理解底层寄存器配置很重要:
// SPI1主机初始化示例(寄存器版) void SPI1_Master_Init(void) { // 1. 启用时钟 RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // 2. 配置GPIO GPIOA->MODER &= ~(GPIO_MODER_MODER5 | GPIO_MODER_MODER7 | GPIO_MODER_MODER4); GPIOA->MODER |= (2 << GPIO_MODER_MODER5_Pos) | (2 << GPIO_MODER_MODER7_Pos) | (2 << GPIO_MODER_MODER4_Pos); GPIOA->AFR[0] |= (5 << GPIO_AFRL_AFSEL5_Pos) | (5 << GPIO_AFRL_AFSEL7_Pos) | (5 << GPIO_AFRL_AFSEL4_Pos); // 3. SPI参数配置 SPI1->CR1 = SPI_CR1_MSTR | // 主机模式 SPI_CR1_BR_0 | // 波特率预分频=4 (SCK=42MHz/4=10.5MHz) SPI_CR1_SSM | // 软件管理NSS SPI_CR1_SSI | // 内部NSS信号高 SPI_CR1_SPE; // 启用SPI SPI1->CR2 = SPI_CR2_DS_0 | SPI_CR2_DS_1 | SPI_CR2_DS_2; // 8位数据格式 }关键参数解析:
SPI_CR1_BR:设置SCK时钟频率,计算公式为f_SCK = f_PCLK / (2^(BR+1))SPI_CR1_CPOL和SPI_CR1_CPHA:决定时钟极性(0=空闲时低电平)和相位(0=第一个边沿采样)
3.2 从机特殊配置
从机配置与主机主要区别在于:
- 必须清除
SPI_CR1_MSTR位 - NSS引脚应配置为输入模式
- 建议启用硬件NSS模式(
SPI_CR1_SSM=0)
// SPI1从机初始化关键代码 SPI1->CR1 = SPI_CR1_SPE; // 从机模式(MSTR=0) SPI1->CR2 |= SPI_CR2_RXNEIE; // 启用接收中断4. 通信协议设计与实现
4.1 数据帧格式设计
在实际项目中,简单的字节传输往往不够。我设计了一个包含帧头、长度、数据和CRC的协议:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 帧头 | 2 | 固定0xAA55 |
| 命令字 | 1 | 区分不同功能 |
| 数据长度 | 1 | 后续数据段的长度 |
| 数据 | N | 有效载荷 |
| CRC16 | 2 | 对前面所有字段的校验(多项式0x1021) |
主机发送函数示例:
void SPI_SendPacket(uint8_t cmd, uint8_t* data, uint8_t len) { uint16_t crc = CRC16_Calculate(0xFFFF, &cmd, 1); crc = CRC16_Calculate(crc, data, len); SPI_CS_Low(); // 拉低NSS SPI_Write(0xAA); // 帧头 SPI_Write(0x55); SPI_Write(cmd); SPI_Write(len); for(int i=0; i<len; i++) { SPI_Write(data[i]); } SPI_Write(crc >> 8); SPI_Write(crc & 0xFF); SPI_CS_High(); // 释放NSS }4.2 从机中断处理
从机应采用中断方式接收数据,避免轮询造成的资源浪费:
// SPI1中断服务程序 void SPI1_IRQHandler(void) { if(SPI1->SR & SPI_SR_RXNE) { uint8_t data = SPI1->DR; // 读取数据会自动清除RXNE标志 static uint8_t state = 0; static uint8_t buffer[256]; static uint8_t index = 0; switch(state) { case 0: // 等待帧头第一个字节 if(data == 0xAA) state = 1; break; case 1: // 等待帧头第二个字节 if(data == 0x55) state = 2; else state = 0; break; // ...其他状态处理 } } }5. 性能优化技巧
5.1 使用DMA提升效率
对于大数据量传输(如图像、音频),应启用DMA:
// SPI TX DMA配置(以DMA2 Stream3为例) DMA2_Stream3->CR = DMA_SxCR_CHSEL_0 | // 通道3 DMA_SxCR_MINC | // 内存地址递增 DMA_SxCR_DIR_0 | // 内存到外设 DMA_SxCR_TCIE; // 传输完成中断 DMA2_Stream3->PAR = (uint32_t)&SPI1->DR; DMA2_Stream3->M0AR = (uint32_t)tx_buffer; DMA2_Stream3->NDTR = data_len; DMA2_Stream3->CR |= DMA_SxCR_EN; // 启用DMA SPI1->CR2 |= SPI_CR2_TXDMAEN; // 启用SPI TX DMA5.2 时钟配置建议
为了获得最高速率:
- 确保APB2时钟(SPI1/4)或APB1时钟(SPI2/3)运行在最大频率
- 设置
SPI_CR1_BR为0(不分频) - 检查从机支持的最大SCK频率(如W25Q256JV支持104MHz)
实测数据:在STM32F427(180MHz)上,SPI1配置为
BR=0时,SCK=45MHz(APB2=90MHz),实际持续传输速率可达35Mbps。
6. 常见问题排查
6.1 通信失败检查清单
无任何信号:
- 检查SPI和GPIO时钟是否启用
- 确认
SPI_CR1_SPE位已设置 - 测量NSS信号是否正常
有SCK但无数据:
- 检查MOSI/MISO引脚配置
- 确认主从机的CPOL/CPHA设置一致
- 检查从机的
SPI_CR1_SPE位
数据错位或CRC错误:
- 降低SCK频率测试
- 检查PCB走线是否过长
- 确认主从机的数据位宽一致(
SPI_CR2_DS)
6.2 逻辑分析仪调试技巧
使用Saleae Logic或PulseView时:
- 设置采样率至少为SCK频率的4倍
- 添加SPI协议解码器,配置正确的时钟极性和相位
- 触发条件设为NSS下降沿
- 检查第一个数据字节是否在NSS有效后的第一个SCK边沿出现
典型错误波形分析:
- SCK上有振铃:增加串联电阻(22-100Ω)
- 数据线交叉干扰:缩短走线或降低速率
- NSS信号抖动:检查软件中是否有其他GPIO操作干扰
7. 进阶应用:多从机管理
7.1 硬件NSS方案
对于多个从机,传统方法是使用多个GPIO作为片选:
// 选择从机1 GPIOB->ODR &= ~GPIO_ODR_OD0; // CS1低 SPI_Transmit(data, len); GPIOB->ODR |= GPIO_ODR_OD0; // CS1高 // 选择从机2 GPIOB->ODR &= ~GPIO_ODR_OD1; // CS2低 SPI_Transmit(data, len); GPIOB->ODR |= GPIO_ODR_OD1; // CS2高7.2 菊花链(Daisy Chain)连接
某些SPI器件支持菊花链模式,将所有从机的MISO-MOSI串联:
主机MOSI → 从机1 MOSI 从机1 MISO → 从机2 MOSI 从机2 MISO → 主机MISO这种模式下,数据会依次通过所有从机,适合LED驱动(如WS2812)、数字电位器等应用。