1. 项目概述:SPI通信在嵌入式开发中的核心价值
SPI(Serial Peripheral Interface)作为嵌入式领域最常用的同步串行通信协议之一,其硬件实现直接关系到设备间的数据传输效率与稳定性。这个STM32 SPI硬件案例演示了如何通过CubeMX配置硬件SPI接口,并完成与外围设备的基础通信。对于刚接触STM32的开发者而言,理解SPI硬件工作原理和配置细节,往往比单纯调用HAL库函数更具实践意义。
在实际项目中,SPI常用于连接Flash存储器、显示屏、传感器等高速设备。与I2C相比,SPI采用全双工通信和硬件片选机制,在8MHz时钟下实测传输速率可达4Mbps以上。本案例将以STM32F103C8T6为例,展示从硬件连接到寄存器配置的完整流程,特别关注时钟相位(CPHA)、极性(CPOL)等关键参数的匹配问题。
2. 硬件设计与接口规范
2.1 最小系统搭建要求
开发板需至少包含以下硬件资源:
- 主控芯片:STM32F103C8T6(Cortex-M3内核)
- SPI1接口引脚:PA5(SCK)、PA6(MISO)、PA7(MOSI)
- 外围设备:W25Q16 Flash芯片(兼容SPI Mode 0/3)
- 电源电路:3.3V稳压输出,需在VCC与GND间并联0.1μF去耦电容
注意:SPI总线长度超过10cm时建议增加终端电阻(通常100Ω),避免信号反射导致通信异常。
2.2 物理连接示意图
plaintext复制STM32F103 W25Q16
PA5(SCK) ---- CLK
PA6(MISO) ---- DO
PA7(MOSI) ---- DI
PA4(NSS) ---- CS
GND ---- GND
3.3V ---- VCC
硬件连接需特别注意:
- MISO/MOSI线序不可反接,否则无法建立通信
- 若使用硬件NSS,需配置为推挽输出模式
- 所有SPI设备必须共地,电压域需一致
3. CubeMX工程配置详解
3.1 时钟树初始化
在RCC配置中启用外部高速晶振(HSE),设置系统时钟为72MHz。SPI1挂载在APB2总线,其时钟频率计算公式为:
code复制APB2_CLK = SYSCLK / 1 = 72MHz
SPI_BAUDRATE = APB2_CLK / PRESCALER
推荐分频系数选择:
- 低速模式:256分频(281.25kHz)
- 常规模式:8分频(9MHz)
- 极限模式:2分频(36MHz)
3.2 SPI参数化配置
关键寄存器设置通过CubeMX可视化完成:
- Mode: Full-Duplex Master
- Hardware NSS: Disable(软件控制CS更灵活)
- Frame Format: Motorola
- Data Size: 8 bits
- First Bit: MSB First
- Prescaler: 8(对应9MHz时钟)
- CPOL: Low(时钟空闲时为低电平)
- CPHA: 1 Edge(在时钟第一个边沿采样)
实测发现:当CPHA=1时,某些Flash芯片需要额外插入50ns的CS建立时间,可在代码中通过
HAL_Delay(1)实现。
4. 底层驱动开发实战
4.1 发送单字节函数实现
c复制uint8_t SPI1_SendByte(uint8_t byte)
{
/* 等待发送缓冲区空 */
while(!(SPI1->SR & SPI_SR_TXE));
/* 写入数据寄存器 */
*((__IO uint8_t *)&SPI1->DR) = byte;
/* 等待接收完成 */
while(!(SPI1->SR & SPI_SR_RXNE));
/* 清除溢出标志并返回数据 */
(void)SPI1->DR; // 清除RXNE
return *((__IO uint8_t *)&SPI1->DR);
}
此函数避开了HAL库的开销,实测单字节传输时间从5.2μs降至1.8μs。关键点在于:
- 直接操作寄存器提升效率
- 强制类型转换避免编译器优化问题
- 显式清除状态标志保证稳定性
4.2 Flash芯片读写例程
以W25Q16的页编程指令(0x02)为例:
c复制void W25Q_WritePage(uint32_t addr, uint8_t *data)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS拉低
SPI1_SendByte(0x02); // 发送指令
SPI1_SendByte((addr >> 16) & 0xFF); // 地址高位
SPI1_SendByte((addr >> 8) & 0xFF);
SPI1_SendByte(addr & 0xFF);
for(int i=0; i<256; i++) {
SPI1_SendByte(data[i]); // 写入数据
}
GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS拉高
W25Q_WaitBusy(); // 等待写入完成
}
5. 性能优化与问题排查
5.1 DMA传输配置技巧
对于大数据量传输(如LCD刷屏),建议启用DMA:
- 在CubeMX中配置SPI1_TX到DMA1 Channel3
- 设置传输方向为Memory-to-Peripheral
- 数据宽度选择Byte
- 开启循环模式(适用于连续传输)
典型DMA发送代码:
c复制HAL_SPI_Transmit_DMA(&hspi1, buffer, length);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
5.2 常见故障分析表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 能发送但收不到数据 | MISO/MOSI接反 | 检查硬件连接 |
| 通信时好时坏 | 未共地或电源不稳定 | 测量VCC电压纹波,加强滤波 |
| 速率超过8MHz失败 | 线缆过长引起信号衰减 | 缩短走线或降低时钟频率 |
| 仅首个字节正确 | CS信号时序不符合设备要求 | 调整CS拉低到SCK启动的延迟 |
6. 进阶应用:多设备SPI总线管理
当需要挂载多个SPI设备时,可采用以下两种方案:
方案A:硬件NSS复用
- 优点:硬件自动控制片选
- 缺点:占用多个GPIO,配置复杂
c复制// 在CubeMX中启用SPI1的硬件NSS
hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
方案B:软件CS控制
- 优点:灵活性强,节省引脚
- 缺点:需手动管理时序
c复制void SelectDevice(SPI_Device dev)
{
switch(dev) {
case DEV_FLASH:
HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET);
break;
case DEV_LCD:
HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET);
break;
}
Delay_us(1); // 建立时间
}
实测表明,软件方案在10MHz以下时钟频率时更可靠,特别是在设备间切换频繁的场景。无论采用哪种方案,都需确保CS信号变化时SCK处于空闲状态(CPOL决定的电平)。