STM32 SPI 模式0/3 实战:W25Q64 Flash 读写时序分析 3 步法

📅 2026/7/11 8:28:33 👁️ 阅读次数 📝 编程学习
STM32 SPI 模式0/3 实战:W25Q64 Flash 读写时序分析 3 步法

STM32 SPI 模式0/3 实战:W25Q64 Flash 读写时序分析 3 步法

当第一次拿到 W25Q64 Flash 芯片的数据手册时,面对密密麻麻的时序图,很多嵌入式开发者都会感到无从下手。SPI 通信看似简单,但模式配置错误会导致数据读写失败。本文将分享一个经过实战验证的三步分析法,帮助开发者快速确定 SPI 模式并完成 STM32 HAL 库配置。

1. 理解 SPI 模式的核心参数

SPI 通信模式由两个关键参数决定:CPOL(时钟极性)和 CPHA(时钟相位)。这两个参数的组合形成了四种 SPI 模式:

模式CPOLCPHA空闲时钟电平数据采样边沿
000低电平上升沿
101低电平下降沿
210高电平下降沿
311高电平上升沿

在 W25Q64 的数据手册中,明确说明支持模式 0 和模式 3。这意味着:

  • 模式 0:SCLK 空闲时为低电平,数据在上升沿采样
  • 模式 3:SCLK 空闲时为高电平,数据在上升沿采样

提示:虽然 W25Q64 支持两种模式,但在实际项目中建议统一使用模式 0,因为这是大多数 SPI 设备的默认配置。

2. 时序图逆向分析法

当芯片手册没有明确说明支持的 SPI 模式时,可以通过时序图逆向分析。以下是三步分析法:

2.1 确定时钟空闲状态

观察时序图中 SCLK 线在没有数据传输时的电平状态:

示例时序片段: _____ SCLK __| |______ (空闲时为低电平)

如果空闲时为低电平,CPOL=0;如果为高电平,CPOL=1。

2.2 识别数据采样边沿

找到数据有效窗口(数据稳定的区域),观察对应的时钟边沿:

数据采样示例: _____ SCLK __| |______ ^ ^ | | 上升沿 下降沿 DATA ___XXXXX______ (X表示数据有效)

如果数据在上升沿被采样,CPHA=0;如果在下降沿被采样,CPHA=1。

2.3 验证 W25Q64 读时序

以 W25Q64 的读数据时序为例:

  1. 指令阶段:0x03 指令通过 MOSI 发送
  2. 地址阶段:24 位地址通过 MOSI 发送
  3. 数据阶段:数据通过 MISO 返回

关键观察点:

  • SCLK 空闲时为低电平(CPOL=0)
  • 数据在上升沿保持稳定(CPHA=0)

因此确定使用模式 0(CPOL=0, CPHA=0)。

3. STM32 HAL 库配置实战

基于上述分析,以下是 STM32CubeIDE 中的配置示例:

3.1 SPI 外设初始化

SPI_HandleTypeDef hspi1; void SPI_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0 hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } }

3.2 W25Q64 读函数实现

#define W25Q64_CMD_READ_DATA 0x03 void W25Q64_Read(uint32_t addr, uint8_t *pData, uint16_t size) { uint8_t cmd[4] = { W25Q64_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF }; // 选择芯片 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET); // 发送读指令和地址 HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); // 接收数据 HAL_SPI_Receive(&hspi1, pData, size, HAL_MAX_DELAY); // 取消选择芯片 HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET); }

注意:实际应用中应添加超时处理和错误检查,这里为简化示例省略了这些代码。

3.3 常见问题排查

当 SPI 通信不正常时,可以按照以下步骤检查:

  1. 逻辑分析仪验证

    • 确认 SCLK 频率是否符合预期
    • 检查 MOSI/MISO 数据与时钟边沿的对应关系
    • 验证 CS 信号是否正确控制
  2. 硬件检查

    • 确保所有 SPI 引脚连接正确
    • 检查上拉/下拉电阻配置
    • 验证电源稳定性
  3. 软件调试

    • 检查 SPI 外设时钟是否使能
    • 确认 GPIO 引脚模式配置正确(复用功能)
    • 验证 DMA 配置(如果使用)

4. 进阶技巧与优化

4.1 提升读写速度

通过以下方式可以优化 W25Q64 的读写性能:

  • 提高 SPI 时钟频率:W25Q64 最高支持 104MHz
  • 使用 DMA 传输:减少 CPU 开销
  • 批量读写操作:利用芯片的页编程特性

4.2 多设备共享 SPI 总线

当多个 SPI 设备共享总线时,需要注意:

  1. 每个设备必须有独立的 CS 信号
  2. 不同设备可能支持不同的 SPI 模式
  3. 切换设备时需要重新配置 SPI 参数
// 切换设备示例 void SPI_SelectDevice(SPI_Device device) { switch(device) { case DEVICE_FLASH: hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; break; case DEVICE_SENSOR: hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; break; } HAL_SPI_Init(&hspi1); }

4.3 低功耗优化

对于电池供电设备:

  • 在不使用时关闭 SPI 外设时钟
  • 降低 SPI 时钟频率
  • 利用 W25Q64 的深度睡眠模式

通过这套三步分析法,我在多个项目中成功配置了不同厂商的 SPI 设备。记住,理解时序图的关键是观察时钟与数据的对应关系,而不是死记硬背协议细节。