1. 硬件连接与SPI基础配置

在开始驱动GD25Q32之前,首先要确保硬件连接正确。STM32F407的SPI1接口与GD25Q32的连接方式如下:

  • PA4(SPI1_NSS) → CS (片选信号)
  • PA5(SPI1_SCK) → CLK (时钟线)
  • PA6(SPI1_MISO) → DO/IO1 (主机输入从机输出)
  • PA7(SPI1_MOSI) → DI/IO0 (主机输出从机输入)

这里有个容易踩坑的地方:虽然STM32F407的硬件SPI有NSS引脚,但实际使用中我推荐用软件控制片选信号。这样做有两个好处:一是可以灵活控制时序,二是避免硬件NSS在某些情况下的自动控制问题。

SPI模式配置是另一个关键点。GD25Q32支持模式0和模式3,我实测下来模式3更稳定。对应的配置参数是:

  • 时钟极性(CPOL)=1(空闲时高电平)
  • 时钟相位(CPHA)=1(第二个边沿采样)

在STM32CubeMX中配置时,记得把SPI的"Hardware NSS"设为"Disable",否则硬件会自动控制NSS引脚,导致操作失败。

2. GD25Q32芯片特性与存储结构

GD25Q32是一款32Mbit(4MB)容量的SPI Flash,采用标准的SPI接口协议。它的存储结构是按扇区(sector)和块(block)组织的:

  • 1个扇区=4KB
  • 1个块=16个扇区=64KB
  • 全芯片共128个扇区,8个块

这种结构直接影响我们的擦除操作。比如要写入数据,必须先擦除整个扇区(不能只擦除部分),这是Flash存储的特性决定的。我遇到过数据写入失败的情况,后来发现就是因为没有先擦除整个扇区。

芯片的工作电压范围是2.7V-3.6V,最大时钟频率104MHz。但在STM32F407上,实测超过50MHz就容易出现数据错误,建议先用低速测试,稳定后再逐步提高频率。

3. 关键指令实现与调试技巧

3.1 读ID指令(0x90)

这是验证硬件连接是否正常的第一步。完整的读ID流程:

  1. 拉低CS
  2. 发送0x90指令
  3. 发送3字节地址(通常全0)
  4. 读取2字节ID(制造商ID+设备ID)
  5. 拉高CS

调试时常见问题:

  • 如果读到的ID是0xFF,检查硬件连接和SPI模式
  • 如果ID不正确但非全FF,可能是时钟相位配置错误

3.2 写使能指令(0x06)

任何写入或擦除操作前都必须先发送写使能指令。这个指令很简单:

void GD25Q32_WriteEnable(void) {
    CS_LOW;
    SPI_WriteByte(0x06);
    CS_HIGH;
}

但容易忽略的是,写使能状态会在以下情况自动清除:

  • 写入操作完成
  • 芯片断电
  • 发生写保护错误

3.3 忙状态检测

所有写入和擦除操作都需要检测忙状态。通过读状态寄存器1(指令0x05)的BUSY位来判断:

void GD25Q32_WaitBusy(void) {
    uint8_t status;
    do {
        CS_LOW;
        SPI_WriteByte(0x05);
        status = SPI_WriteByte(0xFF);
        CS_HIGH;
    } while(status & 0x01);
}

实测发现扇区擦除约需60-400ms,页编程约需1-5ms。在代码中最好加入超时判断,避免死等。

4. 存储操作实战代码

4.1 扇区擦除(0x20)

擦除是写入的前提,注意地址要对齐到4KB边界:

void GD25Q32_SectorErase(uint32_t addr) {
    addr &= 0xFFF000; // 对齐到4KB边界
    
    GD25Q32_WriteEnable();
    GD25Q32_WaitBusy();
    
    CS_LOW;
    SPI_WriteByte(0x20);
    SPI_WriteByte((addr >> 16) & 0xFF);
    SPI_WriteByte((addr >> 8) & 0xFF);
    SPI_WriteByte(addr & 0xFF);
    CS_HIGH;
    
    GD25Q32_WaitBusy();
}

4.2 页编程(0x02)

GD25Q32支持页编程(256字节/页),但可以连续写入跨页:

void GD25Q32_PageProgram(uint8_t *data, uint32_t addr, uint16_t len) {
    GD25Q32_WriteEnable();
    GD25Q32_WaitBusy();
    
    CS_LOW;
    SPI_WriteByte(0x02);
    SPI_WriteByte((addr >> 16) & 0xFF);
    SPI_WriteByte((addr >> 8) & 0xFF);
    SPI_WriteByte(addr & 0xFF);
    
    while(len--) {
        SPI_WriteByte(*data++);
    }
    
    CS_HIGH;
    GD25Q32_WaitBusy();
}

4.3 数据读取(0x03)

读取操作相对简单,没有擦除和忙状态的要求:

void GD25Q32_ReadData(uint8_t *buf, uint32_t addr, uint16_t len) {
    CS_LOW;
    SPI_WriteByte(0x03);
    SPI_WriteByte((addr >> 16) & 0xFF);
    SPI_WriteByte((addr >> 8) & 0xFF);
    SPI_WriteByte(addr & 0xFF);
    
    while(len--) {
        *buf++ = SPI_WriteByte(0xFF);
    }
    
    CS_HIGH;
}

5. 工程优化与常见问题

5.1 SPI时钟配置

STM32F407的SPI时钟来自APB2总线(最高84MHz)。建议分频配置:

  • 初始化测试阶段:SPI_BaudRatePrescaler_8(10.5MHz)
  • 稳定运行阶段:SPI_BaudRatePrescaler_2(42MHz)

5.2 写入性能优化

Flash的写入有两大瓶颈:擦除时间和页编程时间。优化建议:

  1. 采用双缓冲机制:当A缓冲区正在写入时,B缓冲区准备数据
  2. 批量写入:尽量攒够一定数据量再写入,减少擦除次数
  3. 非必要不擦除:如果数据可以覆盖写入(如日志),可以跳过擦除

5.3 常见问题排查

  1. 写入后读回数据不正确

    • 检查是否先执行了擦除
    • 确认写使能指令已发送
    • 检查电源稳定性(Flash对电压敏感)
  2. SPI通信不稳定

    • 缩短信号线长度(最好<10cm)
    • 加上拉电阻(10kΩ)
    • 降低时钟频率测试
  3. 芯片无响应

    • 检查VCC电压(2.7-3.6V)
    • 确认CS信号正常(低电平有效)
    • 尝试重新上电复位

6. 完整驱动框架实现

为了便于复用,我将驱动分为三个层次:

  1. 硬件抽象层(SPI)
// spi.h
typedef struct {
    void (*Init)(void);
    uint8_t (*Transfer)(uint8_t data);
    void (*CS_Enable)(void);
    void (*CS_Disable)(void);
} SPI_Driver;

extern SPI_Driver SPI1_Driver;
  1. Flash指令层
// flash_cmd.h
typedef enum {
    FLASH_CMD_WRITE_ENABLE = 0x06,
    FLASH_CMD_PAGE_PROGRAM = 0x02,
    FLASH_CMD_SECTOR_ERASE = 0x20,
    FLASH_CMD_READ_DATA = 0x03,
    FLASH_CMD_READ_ID = 0x90
} Flash_Command;
  1. 应用接口层
// storage.h
bool Storage_Init(void);
bool Storage_Write(uint32_t addr, uint8_t *data, uint32_t len);
bool Storage_Read(uint32_t addr, uint8_t *data, uint32_t len);
uint32_t Storage_GetSize(void);

这种架构方便移植到不同平台,只需实现底层的SPI驱动即可。我在实际项目中用这套框架成功移植到了STM32F1、F4和H7多个系列。

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐