[STM32F1] 基于STM32F103的W25Q128 SPI Flash使用指南

[复制链接]
229|0
磨砂 发表于 2025-8-15 07:37 | 显示全部楼层 |阅读模式
一、芯片概述与特性
基本参数
​容量​:128M-bit(16MB),分为256个块(Block),每块64KB;每块含16个扇区(Sector),每扇区4KB。
​通信协议​:支持标准SPI、Dual SPI、Quad SPI模式(最高时钟133MHz)。
​寿命与存储​:10万次擦写寿命,数据保存20年。
关键操作限制
​擦除单位​:最小擦除单位为扇区(4KB),写入前必须先擦除。
​写入单位​:单次最多写入256字节(一页)。
​写使能要求​:所有写操作(含擦除)前需发送0x06写使能命令。
封装




二、硬件连接(STM32F103与W25Q128)







关键点:

片选信号(/CS)需通过GPIO控制,其他SPI引脚复用为Alternate Function Push-Pull模式。
SPI模式必须配置为 Mode 0(CPOL=0, CPHA=0) 或 Mode 3(CPOL=1, CPHA=1) 。
三、SPI配置(标准库 vs HAL库)
1. 标准库配置(以SPI1为例)
void SPI1_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    SPI_InitTypeDef SPI_InitStruct;

    // 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE);
    // 配置SPI引脚(PA4)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;         //SPI_NSS
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA,GPIO_Pin_4);

    // 配置SPI引脚(PA5/7)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    // 配置SPI引脚(PA6)
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;                   //SPI_MISO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // 配置SPI参数
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;       // Mode 0
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;        // 软件控制NSS
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; // 低速调试
    SPI_Init(SPI1, &SPI_InitStruct);
    SPI_Cmd(SPI1, ENABLE);
}



2. HAL库配置
SPI_HandleTypeDef hspi1;

void MX_SPI1_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;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
  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();
  }
}






优化建议:

调试初期使用低速模式(如SPI_BaudRatePrescaler_256),稳定后提升至SPI_BaudRatePrescaler_4(最高18MHz)。
DMA传输可提升连续读写效率,减少CPU占用。
四、关键操作代码实现(HAL库实现)
1. SPI读写函数(HAL库)
uint8_t SPI_ReadWriteByte(uint8_t TxData) {
    uint8_t RxData;
    HAL_SPI_TransmitReceive(&hspi1, &TxData, &RxData, 1, 1000); // 阻塞模式
    // 或使用DMA:HAL_SPI_TransmitReceive_DMA(...)
    return RxData;
}


2. 片选信号
#define W25QXX_CS_L()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define W25QXX_CS_H()   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)


3. 读取状态寄存器
uint8_t W25QXX_ReadSR(void)
{
    uint8_t byte=0;
    W25QXX_CS_L();                    // 片选拉低
    W25Q128_ReadWriteByte(0x05);      // 发送读取状态寄存器命令
    byte=W25Q128_ReadWriteByte(0Xff); // 读取一个字节
    W25QXX_CS_H();                    // 片选拉高
    return byte;
}



4. 线路忙检测
uint8_t W25QXX_Wait_Busy(void)
{
    uint32_t tickstart = HAL_GetTick();
    while( (W25QXX_ReadSR() & 0x01) == 1);
    {
        /* 超时 */
        if((HAL_GetTick() - tickstart) > 1000)
        {
            return 0;
        }
    }
    return 1;
}



5. 写使能
uint8_t W25QXX_Write_Enable(void)
{
    W25QXX_CS_L();            // 片选拉低
    SPI_ReadWriteByte(0x06);  // 发送写使能命令
    W25QXX_CS_H();            // 片选拉高
    return W25QXX_Wait_Busy();
}



6. 扇区擦除与全片擦除
// 擦除指定扇区(4KB)
void W25QXX_Erase_Sector(uint32_t SectorAddr) {
    W25QXX_Write_Enable();
    W25QXX_CS_L();
    SPI_ReadWriteByte(0x20);  // 扇区擦除命令
    SPI_ReadWriteByte((SectorAddr >> 16) & 0xFF);
    SPI_ReadWriteByte((SectorAddr >> 8) & 0xFF);
    SPI_ReadWriteByte(SectorAddr & 0xFF);
    W25QXX_CS_H();
    W25QXX_Wait_Busy();  // 等待约100ms
}

// 全片擦除(慎用!耗时约20秒)
void W25QXX_Erase_Chip(void) {
    W25QXX_Write_Enable();
    W25QXX_CS_L();
    SPI_ReadWriteByte(0xC7);  // 0x60也可用
    W25QXX_CS_H();
    W25QXX_Wait_Busy();
}





7. 读取芯片ID(验证通信)
uint16_t W25QXX_ReadID(void) {
    uint16_t id = 0;
    W25QXX_CS_L();            // 片选拉低
    SPI_ReadWriteByte(0x90);  // 发送读ID命令
    SPI_ReadWriteByte(0x00);  // 3个空字节
    SPI_ReadWriteByte(0x00);
    SPI_ReadWriteByte(0x00);
    id |= SPI_ReadWriteByte(0xFF) << 8; // 高字节
    id |= SPI_ReadWriteByte(0xFF);      // 低字节
    W25QXX_CS_H();  // 片选拉高
    return id;       // 正常值:0xEF17(W25Q128)
}




8. 唯一序列号应用
void W25QXX_ReadUniqueID(uint8_t UID[8]) {
    W25QXX_CS_L();
    SPI_ReadWriteByte(0x4B);  // 读64位唯一ID命令
    SPI_ReadWriteByte(0x00);  // 4个空字节
    SPI_ReadWriteByte(0x00);
    SPI_ReadWriteByte(0x00);
    SPI_ReadWriteByte(0x00);
    for(uint8_t i=0; i<8; i++) UID = SPI_ReadWriteByte(0xFF);
    W25QXX_CS_H();
}  // 适用于设备加密




9. 数据读取(任意地址)
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t Size) {
    W25QXX_CS_L();
    SPI_ReadWriteByte(0x03);  // 读命令
    SPI_ReadWriteByte((ReadAddr >> 16) & 0xFF); // 24位地址(高位在前)
    SPI_ReadWriteByte((ReadAddr >> 8) & 0xFF);
    SPI_ReadWriteByte(ReadAddr & 0xFF);
    for(uint16_t i=0; i<Size; i++)
        pBuffer = SPI_ReadWriteByte(0xFF); // 循环读取
    W25QXX_CS_H();
}




10. 数据写入(需先擦除扇区)
uint8_t W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t Size) {
    W25QXX_Write_Enable();  // 发送0x06写使能命令
    W25QXX_CS_L();
    SPI_ReadWriteByte(0x02);  // 页编程命令
    SPI_ReadWriteByte((WriteAddr >> 16) & 0xFF);
    SPI_ReadWriteByte((WriteAddr >> 8) & 0xFF);
    SPI_ReadWriteByte(WriteAddr & 0xFF);
    for(uint16_t i=0; i<Size; i++)
        SPI_ReadWriteByte(pBuffer); // 逐字节写入
    W25QXX_CS_H();

    return W25QXX_Wait_Busy();
}



关键限制:

擦除单位:扇区(4KB),写前必须擦除(使用W25QXX_Erase_Sector())。
写入单位:单次最多256字节(一页),跨页需拆分。
五、使用示例
int main(void) {
    HAL_Init();
    SystemClock_Config();
    SPI1_Init();

    // 验证通信
    uint16_t flash_id = W25QXX_ReadID();
    if(flash_id != 0xEF17) {
        // 错误处理
    }

    // 数据读写示例
    uint8_t write_buf[256] = {0x01, 0x02, 0x03...};
    uint8_t read_buf[256];

    W25QXX_EraseSector(0); // 擦除扇区0
    W25QXX_WritePage(write_buf, 0x0000, 256); // 写入第一页
    W25QXX_ReadData(read_buf, 0x0000, 256);   // 读取数据

    while(1) {
        // 主循环
    }
}




使用以下代码验证:

printf("\r\n SPI-W25Qxx Example \r\n\r\n");
uint8_t ID[4];          //设备ID缓存数组
/*-Step1- 验证设备ID  ************************************************Step1*/
BSP_W25Qx_Read_ID(ID);
//第一位厂商ID固定0xEF,第二位设备ID根据容量不同,具体为:W25Q16为0x14、32为0x15、40为0x12、64为0x16、80为0x13、128为0x17
printf(" W25Qxx ID is : ");
for(int i=0;i<2;i++)
{
    printf("0x%02X ",ID);
}
printf("\r\n");







六、调试技巧与常见问题
1. 典型问题排查




2. 性能优化
Quad SPI模式:若MCU支持,将DO/DI改为4线通信(速度提升4倍)。
缓存管理:避免频繁擦除,使用环形缓冲区或磨损均衡算法(延长Flash寿命)。
DMA传输:连续读写时采用DMA(如HAL库的HAL_SPI_TransmitReceive_DMA)。
七、高级应用场景
通过移植FAT32文件系统挂载w25q128:
实现底层扇区读写接口(disk_read/disk_write)。
使用预分配策略减少擦写次数(擦一次写多页)。
实测速度:读271KB/s,写68KB/s(STM32F103 + SPI DMA)。
​唯一ID应用​:读取64位唯一序列号(命令0x4B)用于设备加密。
————————————————
版权声明:本文为CSDN博主「谱写秋天」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42222415/article/details/150205862

您需要登录后才可以回帖 登录 | 注册

本版积分规则

121

主题

4363

帖子

3

粉丝
快速回复 在线客服 返回列表 返回顶部