1. SPI基础与STM32硬件SPI配置
SPI(Serial Peripheral Interface)是一种高速全双工同步串行通信协议,在嵌入式系统中广泛应用。STM32F1系列芯片内置了硬件SPI外设,最高支持18MHz时钟频率(系统时钟72MHz时)。硬件SPI相比软件模拟SPI有三个显著优势:一是数据传输由硬件自动完成,不占用CPU资源;二是支持更高的通信速率;三是时序精度更高。
配置STM32F1硬件SPI需要关注几个关键参数:
- 时钟极性(CPOL):决定SCK空闲状态电平
- 时钟相位(CPHA):决定数据采样边沿
- 数据帧格式:8位或16位
- 波特率预分频:决定通信速率
具体配置代码如下(以SPI2为例):
void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); // 配置SPI引脚为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // SPI参数配置 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_High; // 空闲时SCK为高 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个边沿采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // 软件控制片选 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI2, &SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); // 使能SPI }实际项目中我曾遇到过SPI通信不稳定的问题,后来发现是GPIO速度配置过低导致。将GPIO_Speed从10MHz提高到50MHz后问题解决。这也提醒我们,高速SPI通信时GPIO速度配置很关键。
2. 模拟SPI的原理与实现
当硬件SPI资源不足或需要特殊时序时,模拟SPI是很好的替代方案。模拟SPI通过GPIO引脚模拟时钟、数据线时序,具有高度灵活性。我曾在一个项目中需要驱动三种不同SPI设备,它们的时序要求各异,最终就是用模拟SPI实现的。
模拟SPI的核心是时序控制,需要根据设备要求的模式(0-3)来编写读写函数。以下是模式0的典型实现:
// 定义GPIO操作宏 #define SPI_SCK_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_13) #define SPI_SCK_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_13) #define SPI_MOSI_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_15) #define SPI_MOSI_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_15) #define SPI_MISO_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) uint8_t SPI_ReadWriteByte(uint8_t TxData) { uint8_t RxData = 0; for(uint8_t i=0; i<8; i++) { SPI_SCK_LOW(); // 下降沿准备数据 // 发送数据 if(TxData & 0x80) SPI_MOSI_HIGH(); else SPI_MOSI_LOW(); TxData <<= 1; SPI_SCK_HIGH(); // 上升沿采样数据 // 接收数据 RxData <<= 1; if(SPI_MISO_READ()) RxData |= 0x01; } return RxData; }模拟SPI需要注意三点:一是GPIO初始化要正确配置输入输出方向;二是时序要严格符合设备要求;三是通信速率受CPU速度限制。在STM32F103上,模拟SPI最高速率约1MHz,适合低速设备。
3. W25Q64 FLASH芯片驱动开发
W25Q64是Winbond推出的64Mbit SPI FLASH,广泛应用于数据存储。其特点包括:
- 支持标准SPI和双线SPI模式
- 每页256字节,每扇区4KB,每块64KB
- 写操作前必须先擦除(只能1变0)
- 典型页编程时间1.5ms,扇区擦除时间50ms
驱动开发首先要实现基本读写功能。读操作相对简单,写操作需要遵循"使能-写入-等待"的流程:
// 读取芯片ID uint16_t W25Q_ReadID(void) { uint16_t id = 0; W25Q_CS_LOW(); SPI_ReadWriteByte(0x90); // 读ID指令 SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); id = SPI_ReadWriteByte(0xFF) << 8; id |= SPI_ReadWriteByte(0xFF); W25Q_CS_HIGH(); return id; } // 页编程 void W25Q_PageWrite(uint32_t addr, uint8_t *buf, uint16_t len) { W25Q_WriteEnable(); // 写使能 W25Q_CS_LOW(); SPI_ReadWriteByte(0x02); // 页编程指令 SPI_ReadWriteByte(addr >> 16); SPI_ReadWriteByte(addr >> 8); SPI_ReadWriteByte(addr); for(uint16_t i=0; i<len; i++) { SPI_ReadWriteByte(buf[i]); } W25Q_CS_HIGH(); W25Q_WaitBusy(); // 等待写入完成 }实际使用中要注意三点:一是写操作前必须确保区域已擦除;二是跨页写入需要分多次操作;三是重要数据建议添加CRC校验。我曾遇到过数据丢失的情况,后来发现是未正确等待写操作完成就断电导致的。
4. 硬件SPI与模拟SPI的对比与选择
硬件SPI和模拟SPI各有优缺点,选择时需要综合考虑项目需求:
| 特性 | 硬件SPI | 模拟SPI |
|---|---|---|
| 速度 | 高(可达18MHz) | 低(通常<1MHz) |
| CPU占用 | 低(DMA更佳) | 高(全程占用CPU) |
| 灵活性 | 固定时序 | 可自定义时序 |
| 引脚占用 | 固定引脚 | 任意GPIO |
| 多设备支持 | 需多个SPI外设 | 可共用同一组GPIO |
| 开发难度 | 需理解寄存器配置 | 时序控制较复杂 |
选择建议:
- 高速场景(>1MHz)优先选硬件SPI
- 多设备共用总线选硬件SPI+DMA
- 特殊时序要求(如非标准模式)选模拟SPI
- SPI引脚与其他功能冲突时选模拟SPI
在资源允许的情况下,我通常这样搭配使用:主设备用硬件SPI确保性能,特殊设备用模拟SPI实现兼容。例如同时驱动W25Q64(硬件SPI)和OLED屏(模拟SPI)。