别再死记硬背SPI时序了!用STM32标准库驱动W25Q64,我画了张图让你秒懂四种模式
SPI时序可视化实战:用STM32标准库驱动W25Q64的四种模式解析
在嵌入式开发中,SPI通信协议因其高速、全双工的特性被广泛应用,但初学者往往对SPI的四种工作模式(CPOL/CPHA组合)感到困惑。本文将带你通过可视化时序图和STM32标准库代码,彻底掌握SPI模式选择的精髓。
1. SPI通信基础与四种模式本质
SPI(Serial Peripheral Interface)是一种同步串行通信协议,通过四根线实现全双工数据传输:
- SCK:串行时钟,由主机控制
- MOSI:主机输出从机输入
- MISO:主机输入从机输出
- SS:从机选择(低电平有效)
SPI的四种工作模式由CPOL(Clock Polarity)和CPHA(Clock Phase)两个参数决定:
| 模式 | CPOL | CPHA | 空闲时钟极性 | 数据采样边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
关键理解:CPHA=0表示数据在第一个时钟边沿采样,CPHA=1则表示在第二个时钟边沿采样。这个"第几个边沿"的概念是理解四种模式差异的核心。
2. 时序图动态解析
为了直观理解四种模式的区别,我们绘制了动态时序对比图(想象为GIF动画):
模式0时序特点
- 空闲时SCK为低电平(CPOL=0)
- SS下降沿后立即在MOSI上准备第一位数据
- 数据在SCK上升沿被采样(CPHA=0)
- 数据在SCK下降沿切换
// 模式0的GPIO模拟时序代码片段 void SPI_Mode0_WriteBit(uint8_t bit) { GPIO_WriteBit(MOSI_PORT, MOSI_PIN, bit); // 准备数据 Delay_us(1); // 保持稳定 GPIO_SetBits(SCK_PORT, SCK_PIN); // 上升沿采样 Delay_us(1); GPIO_ResetBits(SCK_PORT, SCK_PIN); // 下降沿切换 Delay_us(1); }模式3时序特点
- 空闲时SCK为高电平(CPOL=1)
- SS下降沿后MOSI保持直到第一个SCK边沿
- 数据在SCK下降沿准备
- 数据在SCK上升沿采样(CPHA=1)
// 模式3的GPIO模拟时序代码片段 void SPI_Mode3_WriteBit(uint8_t bit) { GPIO_SetBits(SCK_PORT, SCK_PIN); // 保持高电平 GPIO_WriteBit(MOSI_PORT, MOSI_PIN, bit); // 准备数据 Delay_us(1); GPIO_ResetBits(SCK_PORT, SCK_PIN); // 下降沿切换 Delay_us(1); GPIO_SetBits(SCK_PORT, SCK_PIN); // 上升沿采样 Delay_us(1); }3. W25Q64闪存芯片特性
W25Q64是Winbond公司生产的64Mbit串行Flash存储器,关键特性:
- 支持标准SPI和双线/四线模式
- 80MHz时钟频率
- 分为128个块(64KB/块),每块16个扇区(4KB/扇区)
- 页编程大小256字节
重要操作限制:
- 写入前必须先擦除(只能1→0,不能0→1)
- 最小擦除单位是扇区(4KB)
- 连续写入不能跨页(最多256字节)
- 操作后需等待忙状态结束
4. STM32标准库驱动实现
4.1 硬件连接配置
典型连接方式:
- PA4 → W25Q64 CS
- PA5 → SCK
- PA6 → MISO
- PA7 → MOSI
- 3.3V供电
初始化代码:
void SPI_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK, MOSI, CS 推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // MISO 上拉输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS高电平 GPIO_ResetBits(GPIOA, GPIO_Pin_5); // SCK低电平 }4.2 四种模式切换实战
通过修改GPIO时序实现模式切换:
uint8_t SPI_TransferByte(uint8_t byte, uint8_t mode) { uint8_t i, received = 0; for(i = 0; i < 8; i++) { // 根据模式设置时钟初始状态 if(mode == 0 || mode == 1) { GPIO_ResetBits(SCK_PORT, SCK_PIN); // CPOL=0 } else { GPIO_SetBits(SCK_PORT, SCK_PIN); // CPOL=1 } // 输出数据位 GPIO_WriteBit(MOSI_PORT, MOSI_PIN, (byte & (0x80 >> i)) ? Bit_SET : Bit_RESET); Delay_us(1); // 模式0/3:上升沿采样 if(mode == 0 || mode == 3) { GPIO_SetBits(SCK_PORT, SCK_PIN); Delay_us(1); received |= (GPIO_ReadInputDataBit(MISO_PORT, MISO_PIN) << (7 - i)); GPIO_ResetBits(SCK_PORT, SCK_PIN); } // 模式1/2:下降沿采样 else { GPIO_ResetBits(SCK_PORT, SCK_PIN); Delay_us(1); received |= (GPIO_ReadInputDataBit(MISO_PORT, MISO_PIN) << (7 - i)); GPIO_SetBits(SCK_PORT, SCK_PIN); } Delay_us(1); } return received; }4.3 W25Q64完整驱动实现
关键操作函数示例:
写使能指令
void W25Q64_WriteEnable(void) { GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS拉低 SPI_TransferByte(0x06, 0); // 写使能指令,使用模式0 GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS拉高 }页编程函数
void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { W25Q64_WriteEnable(); GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS拉低 SPI_TransferByte(0x02, 0); // 页编程指令 SPI_TransferByte((addr >> 16) & 0xFF, 0); // 地址高位 SPI_TransferByte((addr >> 8) & 0xFF, 0); // 地址中位 SPI_TransferByte(addr & 0xFF, 0); // 地址低位 for(uint16_t i = 0; i < len; i++) { SPI_TransferByte(data[i], 0); } GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS拉高 W25Q64_WaitBusy(); // 等待操作完成 }扇区擦除
void W25Q64_SectorErase(uint32_t addr) { W25Q64_WriteEnable(); GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS拉低 SPI_TransferByte(0x20, 0); // 4KB扇区擦除指令 SPI_TransferByte((addr >> 16) & 0xFF, 0); SPI_TransferByte((addr >> 8) & 0xFF, 0); SPI_TransferByte(addr & 0xFF, 0); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS拉高 W25Q64_WaitBusy(); }5. 调试技巧与常见问题
示波器观测技巧:
- 触发设置:使用SS下降沿触发
- 时间基准:设置为1us/div观察细节,或10us/div观察完整字节
- 测量SPI时钟频率是否符合预期
常见问题排查:
无响应:
- 检查电源和接线
- 确认CS信号有效
- 验证时钟极性设置
数据错误:
- 检查SPI模式是否匹配
- 确认时序延迟是否足够
- 测量信号质量(振铃、过冲)
写入失败:
- 确保先执行擦除操作
- 检查写使能指令是否成功
- 等待忙状态结束
性能优化建议:
- 根据W25Q64规格书,最高支持80MHz时钟
- 在STM32F103上,GPIO模拟SPI约可达2-3MHz
- 对于高速需求,建议使用硬件SPI外设
通过实际项目验证,模式0和模式3是W25Q64最常用的工作模式。在调试不同模式时,建议先用示波器捕获完整时序,对照理论波形分析差异,这种"修改代码-观察波形"的实践方法能有效加深对SPI协议的理解。
