概述

FLASH

FLASH是一种是非易失性存储器,即掉电后不会丢失数据,这和RAM(随机存储器)不同。

FLASH比起同作用的EEPROM有价格低的优点

FLASH的擦除操作是以扇区为单位的(比起EEPROM来说操作较为不方便)

芯片型号

本文介绍的W25Qxx系列芯片有以下型号

型号 容量 ID 最大地址(最小为0x0)
W25Q16 2MByte(16Mbit) 0xEF14 0x1FFFFF
W25Q32 4MByte(32Mbit) 0xEF15 0x3FFFFF
W25Q64 8MByte(64Mbit) 0xEF16 0x7FFFFF
W25Q128 16MByte(128Mbit) 0xEF17 0xFFFFFF
W25Q256 32MByte(256Mbit) 0xEF18 0x01FFFFFF
W25Q512 64MByte(512Mbit) 0xEF19 0x03FFFFFF

W25Q16、W25Q32、W25Q64、W25Q128的地址为3字节(3x8=24bit)

W25Q256和W25Q512的地址为4字节(4x8=32bit)

本文以W25Q64为例,代码已经做了兼容处理,可以兼容所有种类芯片

电气连接

常规SPI协议的W25Qxx

image-20250223144644288

芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于5mA,掉电时低于1uA

注意

1.W25Qxx的封装和通信协议有许多种,本文以最常见的SOT-8封装和SPI通信为例

2.FLASH因为物理限制(相比EEPROM省成本),只能将1变成0,所以需要在写入数据前进行擦除操作

W25Qxx的数据组成

下图是数据手册的截图(W25Q64)

image-20250223145357671

8个数据位(bit)组成1个字节(Byte)

256个字节(Byte)组成1页(Page),1页(Page)的数据为256Byte

16页(Page)组成1个扇区(Sector),1扇区(Sector)的数据为4KByte(16*256B)

16个扇区(Sector)组成1个区块(Block),1个区块(Block)数据为64KByte(16*4KB)

不同的芯片有不同的区块(Block)数量,W25Q64有128个区块(Block).总容量为8MByte(128*64KByte)

地址的构成如下图

image-20250223145511876

标准SPI

SPI配置

因为是存储芯片的驱动,往往需要较大的传输速度以支持快速读取数据

所以使用硬件SPI进行通信

配置内容

1.全双工主机模式

2.软件触发NSS(片选)

3.数据长度8bit

4.先发送MSB(高位)

5.分频后的时钟要小于80MHz

6.时钟极性(CPOL)为低电平有效,时钟相位(CPHA)为第一个边沿

7.不启用CRC校验

8.不开启DMA和中断(DMA传输的以后再说)

片选信号

使用软件触发需要设置另外的GPIO

必须设置为推挽浮空输出,最高等级(其他设置会出现问题)

HAL配置

硬件SPI

image-20250223145735205

GPIO

image-20250223151232702

基础命令

命令介绍

这部分介绍一下W25Qxx的命令构成,这是数据手册的截图

image-20250223151727050

使用的是SPI协议,每个通信过程都需要完成一次双向数据传输

也就是写入命令时会接收到数据,想接收数据时需要写入命令

上图的命令有两种,一种是只需要发送的,一种是即需要发送也需要接收的

兼容性处理

利用一个宏定义和预编译命令兼容不同芯片的地址长度

需要在头文件里定义,如

#define W25Q16
#define W25Q32
#define W25Q64
#define W25Q128
#define W25Q256
#define W25Q512

C文件(W25Qxx.c)

#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

片选和SPI读写

这部分是SPI的基础驱动,包括片选信号的软件发生和调用HAL库完成读写

SPI的句柄在其他文件里定义,在这个文件中使用需要加这句话

extern SPI_HandleTypeDef hspi1;

头文件(W25Qxx.h)

extern SPI_HandleTypeDef hspi1;
#define W25Qxx_SPI_Handle hspi1
#define W25Qxx_CS_GPIOx GPIOA
#define W25Qxx_CS_PIN GPIO_PIN_4

C文件(W25Qxx.c)

/**
 * @brief 设置片选(CS)为低电平选中
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_CS_Low(void)
{
    HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_RESET);
}
/**
 * @brief 设置片选(CS)为高电平未选中
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_CS_Hight(void)
{
    HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_SET);
}
/**
 * @brief 通过SPI发送接收数据(阻塞)(SPI是移位发送,接收时要发送数据,发送时也会收到数据)
 * @param TxData:发送的数据
 * @retval 接收的数据
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint8_t W25Qxx_SPI_RW_Byte(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&W25Qxx_SPI_Handle, &TxData, &Rxdata, 1, 1000);
    return Rxdata;
}

调用HAL库的GPIO写入SPI阻塞传输函数即可

读取ID

数据手册的截图

image-20250223152212579

image-20250223152300356

流程

  1. 片选选中(低电平)
  2. 发送读取ID命令(0x90)
  3. 发送2个字节(Byte)的占位符(任意数据即可)
  4. 发送0x00
  5. 接收2个字节的数据(也需要发送2个字节的占位符)
  6. 片选释放(高电平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_ID (0x90)             //读ID
/**
 * @brief 读取芯片的ID
 * @param 无
 * @retval 芯片的ID
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint16_t W25Qxx_Read_ID(void)
{
    uint32_t zj1, zj2;
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ID);                //发送命令
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       //占位符
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       //占位符
    W25Qxx_SPI_RW_Byte(0x00);                         //必须为0
    zj1 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //发送占位符读取数据
    zj2 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //发送占位符读取数据
    W25Qxx_CS_Hight();
    return ((zj1 << 8) | zj2);
}

写使/失能

数据手册的截图

image-20250223152350903

image-20250223152429065

image-20250223152448311

写使能是0x06,写失能是0x04

调用之后即可一直保持失能/使能状态

默认状态是不允许写入数据(失能),这是为了保护数据,避免误操作

因此每次上电后想要写入数据需要使能写入

建议每次写入完成后 将写入失能

为了便于使用,将写入使能和失能放入同一个函数中

流程

  1. 片选选中(低电平)
  2. 根据输入选择命令发送
  3. 片选释放(高电平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_Write_Enable (0x06)   //写功能打开
#define W25Qxx_CMD_Write_Disable (0x04)  //写功能关闭
/**
 * @brief 写保护
 * @param Functional:
 * @arg 1:     允许写入
 * @arg 0:     不允许写入
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Protect(uint8_t Functional)
{
    W25Qxx_CS_Low();
    if (Functional == 0)
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Disable);//不允许写入
    else if (Functional == 1)
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Enable);//允许写入
    W25Qxx_CS_Hight();
}

读寄存器1

数据手册的关于寄存器1的截图,用到时再细说各个位的功能

image-20250223153638550

读状态寄存器1的命令 和时序

image-20250223153720929

image-20250223153739647

流程

  1. 片选选中(低电平)
  2. 发送命令并接收数据
  3. 片选释放(高电平)

C文件(W25Qxx.c)

#define W25Qxx_CMD_ReadStatusReg1 (0x05) //读取状态寄存器
/**
 * @brief 读取寄存器1的状态
 * @param 无
 * @retval 状态
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint8_t W25Qxx_Read_StatusReg1(void)
{
    uint8_t zj = 0;
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg1);   //发送命令
    zj = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); //接收数据
    W25Qxx_CS_Hight();
    return zj;
}

等待写入完成

读取状态寄存器1的最低位

为1则写忙碌

为0则为空闲

利用一个死循环来确保已经写入已经完成

/**
 * @brief 等待写入/擦除完成
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Wait_Free(void)
{
    while (W25Qxx_Read_StatusReg1() & 0x01) //等待写完
        ;
}

扇区擦除

上文提到过,FLASH的擦除最小单位为扇区

image-20250223153837852

image-20250223153856297

流程

  1. 片选选中(低电平)
  2. 发送命令
  3. 发送地址(只要是扇区内的任意地址即可)
  4. 片选释放(高电平)

注意:

  1. 地址长度需要根据不同芯片来定(24bit或32bit)
  2. 地址是扇区内的任意一个地址即可

C文件(W25Qxx.c)

/**
 * @brief 扇区擦除
 * @param Address:要擦除的扇区内的任意地址
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Sector_Erase(uint32_t Address)
{
    Address &= 0xFFFFF000;
    W25Qxx_Write_Protect(1); //允许写入

    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Sector_Erase); //命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址
    W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  //地址
    W25Qxx_CS_Hight();

    W25Qxx_Write_Protect(0); //关闭写入
    // while (W25Qxx_Read_StatusReg1() & 0x01) //等待写完
    //     ;
}

读取数据

读取数据的命令

image-20250223153926926

image-20250223154039993

流程

  1. 片选选中(低电平)
  2. 发送命令
  3. 发送起始地址
  4. 接收数据
  5. 片选释放(高电平)

注意

  1. 地址为起始地址
  2. 地址会自增
  3. 可以跨页,区块,扇区

设计为输入起始地址,将数据放到的数字的地址,长度

将读取的数据放入指定的位置

C文件(W25Qxx.c)

#define W25Qxx_CMD_Read_Data (0x03)      //读取数据
/**
 * @brief 读取数据
 * @param Address:要读取的地址
 * @param Buf:将数据放入的数组地址
 * @param Len:读取的字节数
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Read_Data); //命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址
    W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  //地址
    for (int i = 0; i < Len; i++)
        Buf[i] = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);
    W25Qxx_CS_Hight();
}

按页写入数据

本函数是为了之后的数据写入做的准备函数

image-20250223154105119

image-20250223154125754

流程

  1. 片选选中(低电平)
  2. 发送命令
  3. 发送地址
  4. 发送数据
  5. 片选释放(高电平)

注意:

  1. 本命令不可以跨页使用,如果发送的数据长度小于本页剩余长度,则会从本页最开头覆盖数据
  2. FLASH只能从数据只能1->0,写入之前需要确保数据为0xFF,避免出现问题

设计:

输入要写入的地址和缓冲区及长度,即可写入指定页的全部数据

内部已经做了处理,保证写入的数据从页的头部开始

本函数是以后工具函数

C文件(W25Qxx.c)

#define W25Qxx_CMD_Write_Page (0x02)     //写页
/**
 * @brief 按页写入数据
 * @param Address:要写入的地址的首地址(必须首地址)
 * @param Buf:将数据的数组地址
 * @param Len:写入的字节数
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxx_Write_Protect(1); //允许写入
    W25Qxx_Wait_Free();
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Page); //命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); //如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); //地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  //地址
    W25Qxx_SPI_RW_Byte((Address & 0x00000000) >> 0);  //地址(确保是首地址)
    for (int i = 0; i < 256; i++)
    {
        if (i < Len)
            W25Qxx_SPI_RW_Byte(Buf[i]);
        else
            W25Qxx_SPI_RW_Byte(0xFF);
    }
    W25Qxx_CS_Hight();
    W25Qxx_Write_Protect(0); //关闭写入
}

拓展命令

读取扇区数据

之前说过重定向printf到串口,本部分将用到这个

本函数的功能为:读取整个扇区的数据并通过串口发送到上位机显示

思路就是调用之前读数据命令,并将其放入一个缓冲区内,再进行发送(可以用DMA)

for (uint32_t i = 0; i < 16; i++)
        W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

上面这段代码即为核心

进行一个循环,遍历整个扇区内的页(0x0-0xF)

获取区块和扇区编号,加上每页的编号 获取地址(Address & 0xFFFFF000) | (i << 8)

将缓冲区的数组首地址送入,固定数据长度256,即可读取

C文件(W25Qxx.c)

uint8_t W25Qxx_Buf[16][256] = {0};
/**
 * @brief 打印出整个扇区的数据
 * @param Address:扇区内的任意地址
 * @return 无
 * @author HZ12138
 * @date 2022-07-04 10:13:03
 */
void W25Qxx_Print_Sector(uint32_t Address)
{
    for (uint32_t i = 0; i < 16; i++)
        W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

    for (int i = 0; i < 16; i++)
    {
        printf("%06X:", (Address & 0xFFFFF000) | (i << 8));
        for (int j = 0; j < 256; j++)
        {
            printf("%02X ", W25Qxx_Buf[i][j]);
        }
        printf("\r\n");
    }
}

扇区单位写入数据

上文说过,芯片擦除数据的最小单位为扇区

因此进行数据写入时,会将整个扇区的数据都给擦除,我们需要建立一个缓冲区来存储数据,才能精确更改几个数据

流程:

  1. 读取这个扇区原来的数据到缓冲区
  2. 根据需要写入数据到缓冲区
  3. 擦除这个扇区的数据
  4. 将缓冲区的数据写回芯片

用到了一些位操作,建议配合前文的地址构成理解

读取和写入的地址计算和上文的读取扇区数据一样, (Add_Bef & 0xFFFFF000) | (i << 8)

这边重点看缓冲区的地址计算,即这段

 for (uint32_t i = Add_Bef; i < Add_Aft; i++) //向缓冲区写入数据
    {
        W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];
        Num++;
    }

我们计算一下数据写完后的地址(长度与初始地址求和即可)

然后进行一个循环,遍历这段地址

取出页编号(i & 0x00000F00) >> 8)

取出页内的数据编号(i & 0x000000FF)

向缓冲区内写入即可

另外仔细看一下这个缓冲区

uint8_t W25Qxx_Buf[16][256] = {0};

第一维代表扇区内的页,第二维代表页内的数据编号

另外可以选择覆盖内容直接写入

C文件(W25Qxx.c)

/**
 * @brief 向扇区写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 * @param cover: 0覆盖 1不覆盖
 * @return 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len, uint8_t cover)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;
    if (cover)
    {
        for (uint32_t i = 0; i < 16; i++) // 读取原来数据到缓冲区
            W25Qxx_Read_Data((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

        for (uint32_t i = Add_Bef; i < Add_Aft; i++) // 向缓冲区写入数据
        {
            W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];
            Num++;
        }

        W25Qxx_Sector_Erase(Add_Bef); // 清空这个扇区
        W25Qxx_Wait_Free();

        for (uint32_t i = 0; i < 16; i++) // 向FLASH写入缓冲区内的数据
        {
            W25Qxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);
            W25Qxx_Wait_Free();
        }
    }
    else
    {
        W25Qxx_Sector_Erase(Add_Bef); // 清空这个扇区
        W25Qxx_Wait_Free();

        Num = 0;
        for (int i = 0; i < 16; i++)
        {
            W25Qxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), &Buf[Num], 256);
            Num += 256;
        }
    }
}

写入任意长度数据

这个函数即为写入函数最终的成品,可以无视扇区限制写入数据

思路:将要写入的数据根据扇区的种类分为3部分,第一个扇区,中间扇区,最后一个扇区

  1. 第一个扇区的数据长度不定,数据长度为(本扇区结束地址-起始地址)
  2. 中间扇区数据长度确定,数据长度为4096
  3. 最后一个扇区数据长度不定,数据长度为(结束地址-本扇区起始地址)
  4. 只需要根据计数以此推进输入数据再调用写入扇区的函数即可

这个是第一个扇区的数据写入

W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef), 1);

将起始地址的区块扇区编号提取出来再加1个扇区的地址长度0x1000,结果为下一个扇区的起始地址,与数据写入的起始地址做差即可得到数据长度

中间扇区部分为固定的长度4096

只需要进行一个循环,从起始扇区的下一个扇区开始,到倒数第二个扇区,更新输入数组的开始地址,更新计数值即可

for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;
         i < (Add_Aft & 0xFFFFF000); i += 0x00001000)
    {
        W25Qxx_Write_Sector(i, &Buf[Num], 4096, 1);
        Num += 4096;
    }

最后一个扇区的长度不定

W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000), 1);

数据长度为结束地址减区本扇区的起始地址:

Add_Aft - (Add_Aft & 0xFFFFF000)

C文件(W25Qxx.c)

/**
 * @brief 写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 * @return 无
 * @date 2022-07-04 21:50:38
 */
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;
    W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef), 1);
    Num = ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef);
    for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;
         i < (Add_Aft & 0xFFFFF000); i += 0x00001000)
    {
        W25Qxx_Write_Sector(i, &Buf[Num], 4096, 1);
        Num += 4096;
    }
    W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000), 1);
}

QSPI

QSPI一般用于可以映射的芯片,用外置FLASH存储代码文件

例如STM32H750等

设映射到地址Addr 当芯片读取Addr 的代码时就会自动调用QSPI总线访问FLASH用来扩大存储空间

QSPI配置

image-20250318230521015

开启4线SPI模式

时钟根据情况设置 一般W25qxx时钟要小于120MHz

先进先出缓冲区(Fifo Threshold) 设为 4

采样位置(Sample Shifting Half Cycle) 设为 半时钟采样( Sample Shifting Half Cycle)

存储大小(Flash Size) 根据芯片设置 是这里的数是2x2^x2x (eg: W25Q64 是64Mbit=8MByte log⁡2(8×1024×1024)+1=23+1=24\log_{2}{(8\times 1024\times 1024)} +1 =23+1=24log2(8×1024×1024)+1=23+1=24 ) 注意最后要+1 来解决最后字节问题

片选高延迟 (Chip Select High Time ) 两次命令间的延迟周期 一般选2-5即可

其余默认即可

image-20250318232740991

注意GPIO中要选择最高模式

命令

HAL库API

QSPI命令结构体
类型 名称 功能
uint32_t Instruction 命令(8bits)
uint32_t Address 地址(32bits)
uint32_t AlternateBytes 备用字节(32bits)
uint32_t AddressSize QSPI_ADDRESS_8_BITSQSPI_ADDRESS_32_BITS
uint32_t AlternateBytesSize 交换字节大小(未用到)
uint32_t DummyCycles 空周期(0-31)
uint32_t InstructionMode 指令模式(QSPI_INSTRUCTION_NONE等)
uint32_t AddressMode 地址模式(QSPI_ADDRESS_NONE等)
uint32_t AlternateByteMode 字节交换模式(QSPI_ALTERNATE_BYTES_NONE
uint32_t DataMode 数据模式(QSPI_DATA_NONE等)
uint32_t NbData 数据长度(32bits)(0表示不确定的长度直到内存结束)
uint32_t DdrMode DDR模式(一般关闭即可QSPI_DDR_MODE_DISABLE)
uint32_t DdrHoldHalfCycle ddr保持周期(一般QSPI_DDR_HHC_ANALOG_DELAY)
uint32_t SIOOMode 是否单独命令(一般每次都发命令QSPI_SIOO_INST_EVERY_CMD)

QSPI内存映射结构体

类型 名称 功能
uint32_t TimeOutPeriod FIIFO满等待数(16bits)
uint32_t TimeOutActivation 是否使用超时(QSPI_TIMEOUT_COUNTER_DISABLE)
命令发送
类型 名称 功能
QSPI_HandleTypeDef hqspi 句柄
QSPI_CommandTypeDef cmd 控制结构体
uint32_t Timeout 超时
HAL_StatusTypeDef 返回 状态
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, uint32_t Timeout)
获取数据
类型 名称 功能
QSPI_HandleTypeDef hqspi 句柄
uint8_t pData 数据缓冲区
uint32_t Timeout 超时
HAL_StatusTypeDef 返回 状态
HAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout)
进入内存映射
类型 名称 功能
QSPI_HandleTypeDef hqspi 句柄
QSPI_CommandTypeDef cmd 命令结构体
QSPI_MemoryMappedTypeDef cfg 内存映射结构体
HAL_StatusTypeDef 返回 状态
HAL_StatusTypeDef HAL_QSPI_MemoryMapped(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, QSPI_MemoryMappedTypeDef *cfg)

宏定义

#define W25Qxx_CMD_Write_Enable (0x06)   // 写功能打开
#define W25Qxx_CMD_Write_Disable (0x04)  // 写功能关闭
#define W25Qxx_CMD_Device_ID (0x90)      // 读设备ID
#define W25Qxx_CMD_ReadStatusReg1 (0x05) // 读取状态寄存器1
#define W25Qxx_CMD_ReadStatusReg2 (0x35) // 读取状态寄存器2
#define W25Qxx_CMD_ReadStatusReg3 (0x35) // 读取状态寄存器3
#define W25Qxx_CMD_Placeholder (0xFF)    // 占位符
#define W25Qxx_CMD_Sector_Erase (0x20)   // 扇区擦除
#define W25Qxx_CMD_Read_Data (0x03)      // 读取数据
#define W25Qxx_CMD_Write_Page (0x02)     // 写页

#define W25Qxx_CMD_QSPI_Read (0x0B)  // QSPI读
#define W25Qxx_CMD_Enter_QSPI (0x38) // 进入QSPI模式
#define W25Qxx_CMD_ENABLE_RESET (0x66)
#define W25Qxx_CMD_RESET (0x99)
#define W25Qxx_CMD_QSPI_FAST_READ (0xEB)

uint8_t W25Qxx_Buf[16][256] = {0};

#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS

#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS

#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

发送命令

封装一个QSPI发送命令的函数,具体看注释

/**
 * @brief QSPI发送命令
 * @param cmd 命令
 * @param addr 地址
 * @param InstructionMode 指令模式
 * @param AddressMode 地址模式
 * @param AddressSize 地址长度
 * @param DataMode 数据模式
 * @param SIOOMode 仅发一次指令
 * @param dmcycle 空周期
 * @param NbData 数据长度
 * @param buf 读取缓冲区
 * @author HZ12138
 * @date 2025-03-19 09:15:00
 */
void W25Qxx_QSPI_CMD(uint8_t cmd, uint32_t addr, uint32_t InstructionMode, uint32_t AddressMode,
                     uint32_t AddressSize, uint32_t DataMode, uint32_t SIOOMode, uint8_t dmcycle, uint32_t NbData, uint8_t *buf)
{
    QSPI_CommandTypeDef Cmdhandler;

    Cmdhandler.Instruction = cmd;     // 指令
    Cmdhandler.Address = addr;        // 地址
    Cmdhandler.DummyCycles = dmcycle; // 设置空指令周期数

    Cmdhandler.InstructionMode = InstructionMode; // 指令模式
    Cmdhandler.AddressMode = AddressMode;         // 地址模式
    Cmdhandler.AddressSize = AddressSize;         // 地址长度
    Cmdhandler.DataMode = DataMode;               // 数据模式
    // QSPI_SIOO_INST_EVERY_CMD
    Cmdhandler.SIOOMode = SIOOMode;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
    Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;               // 关闭DDR模式
    Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;

    Cmdhandler.NbData = NbData; // 数据长度
    HAL_QSPI_Command(&W25Qxx_QSPI_Handle, &Cmdhandler, 5000);

    if (NbData != 0 && buf != NULL)
        HAL_QSPI_Receive(&W25Qxx_QSPI_Handle, buf, 5000);
}

读取ID

uint16_t W25Qxx_Read_ID(void)
{
    uint8_t temp[2];
    uint16_t deviceid;

    W25Qxx_QSPI_CMD(W25Qxx_CMD_Device_ID, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 2, temp);

    deviceid = (temp[0] << 8) | temp[1];
    return deviceid;
}

读取数据

void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxx_QSPI_CMD(W25Qxx_CMD_QSPI_Read, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 2, Len, Buf);

}

读取状态寄存器

uint8_t W25Qxx_Read_StatusReg(uint8_t num)
{
    uint8_t temp;
    switch (num)
    {
    case 1:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg1, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    case 2:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg2, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    case 3:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg3, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    default:
        break;
    }
    return temp;

}

等待写入完成

void W25Qxx_Wait_Free(void)
{
    while (W25Qxx_Read_StatusReg(1) & 0x01) // 等待写完
        ;
}

扇区擦除

void W25Qxx_Sector_Erase(uint32_t Address)
{
    W25Qxx_Write_Protect(1);

    W25Qxx_QSPI_CMD(W25Qxx_CMD_Sector_Erase, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);

    W25Qxx_Wait_Free();
    W25Qxx_Write_Protect(0);
}

复位

void W25Qxx_reset(void)
{
    W25Qxx_QSPI_CMD(W25Qxx_CMD_ENABLE_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
    HAL_Delay(5);
    W25Qxx_QSPI_CMD(W25Qxx_CMD_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
    HAL_Delay(10);
}

初始化

void W25Qxx_init(void)
{
    W25Qxx_reset();
    W25Qxx_QSPI_CMD(W25Qxx_CMD_Enter_QSPI, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
}

页写入

void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    W25Qxx_Write_Protect(1);
    W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Page, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_ONLY_FIRST_CMD, 0, Len, NULL);

    HAL_QSPI_Transmit(&W25Qxx_QSPI_Handle, Buf, 5000);

    W25Qxx_Wait_Free();
    W25Qxx_Write_Protect(0);
}

内存映射

调用函数后会将 0x90000000的内存映射到FLASH里

void W25Qxx_Enter_MemoryMappedMode(void)
{

    QSPI_CommandTypeDef s_command = {0};
    QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};

    s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;     // 1线方式发送指令
    s_command.AddressSize = W25Qxx_QSPI_ADDR_SIZE;           // 32位地址
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
    s_command.DdrMode = QSPI_DDR_MODE_DISABLE;               // W25Q256JV不支持DDR
    s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;  // DDR模式,数据输出延迟
    s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;           // 每次传输都发指令

    s_command.Instruction = W25Qxx_CMD_QSPI_FAST_READ; // 快速读取命令
    s_command.AddressMode = QSPI_ADDRESS_4_LINES;      // 4地址线
    s_command.DataMode = QSPI_DATA_4_LINES;            // 4数据线
    s_command.DummyCycles = 6;                         // 空周期

    s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    s_mem_mapped_cfg.TimeOutPeriod = 0;

    HAL_QSPI_MemoryMapped(&W25Qxx_QSPI_Handle, &s_command, &s_mem_mapped_cfg);
}

源码

W25Qxx.h

#ifndef _W25Qxx_H
#define _W25Qxx_H
#include "main.h"
#include "Print.h"
/*
需要:
SSPI:
    1.GPIO
        推挽浮空输出,最高等级
    2.硬件SPI
        全双工主机
        (默认设置)
        片选信号由软件触发
        数据长8bit
        先发高位MSB
        时钟分频后<80MHz
        上升沿时钟
        单周期触发
QSPI:
    预分频 1
    FIFO 4
    半时钟时采集
    片选延迟5周期
    其余默认
*/
#define W25Q64

// #define W25Qxx_SSPI // 使用标准SPI
#define W25Qxx_QSPI // 使用QSPI

#ifdef W25Qxx_SSPI
extern SPI_HandleTypeDef hspi2;
#define W25Qxx_SPI_Handle hspi2
#define W25Qxx_CS_GPIOx GPIOB
#define W25Qxx_CS_PIN GPIO_PIN_12
#endif

#ifdef W25Qxx_QSPI
extern QSPI_HandleTypeDef hqspi;
#define W25Qxx_QSPI_Handle hqspi
#endif

uint16_t W25Qxx_Read_ID(void);
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Write_Protect(uint8_t Functional);
void W25Qxx_Sector_Erase(uint32_t Address);
void W25Qxx_init(void);
void W25Qxx_reset(void);
void W25Qxx_Wait_Free(void);
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Print_Sector(uint32_t Address);
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len);
void W25Qxx_Enter_MemoryMappedMode(void);
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len);

#endif

W25Qxx.c

#include "W25Qxx.h"

#define W25Qxx_CMD_Write_Enable (0x06)   // 写功能打开
#define W25Qxx_CMD_Write_Disable (0x04)  // 写功能关闭
#define W25Qxx_CMD_Device_ID (0x90)      // 读设备ID
#define W25Qxx_CMD_ReadStatusReg1 (0x05) // 读取状态寄存器1
#define W25Qxx_CMD_ReadStatusReg2 (0x35) // 读取状态寄存器2
#define W25Qxx_CMD_ReadStatusReg3 (0x35) // 读取状态寄存器3
#define W25Qxx_CMD_Placeholder (0xFF)    // 占位符
#define W25Qxx_CMD_Sector_Erase (0x20)   // 扇区擦除
#define W25Qxx_CMD_Read_Data (0x03)      // 读取数据
#define W25Qxx_CMD_Write_Page (0x02)     // 写页

#define W25Qxx_CMD_QSPI_Read (0x0B)  // QSPI读
#define W25Qxx_CMD_Enter_QSPI (0x38) // 进入QSPI模式
#define W25Qxx_CMD_ENABLE_RESET (0x66)
#define W25Qxx_CMD_RESET (0x99)
#define W25Qxx_CMD_QSPI_FAST_READ (0xEB)

uint8_t W25Qxx_Buf[16][256] = {0};

#if defined(W25Q16)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS
#elif defined(W25Q32)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q64)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q128)
uint8_t W25Qxx_Address_Len = 24;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_24_BITS

#elif defined(W25Q256)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS

#elif defined(W25Q512)
uint8_t W25Qxx_Address_Len = 32;
#define W25Qxx_QSPI_ADDR_SIZE QSPI_ADDRESS_32_BITS

#else
#error "prese define: W25Q16 or W25Q32 or W25Q64 or W25Q128 or W25Q256 or W25Q512"
#endif

#ifdef W25Qxx_SSPI
/**
 * @brief 设置片选(CS)为低电平选中
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_CS_Low(void)
{
    HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_RESET);
}
/**
 * @brief 设置片选(CS)为高电平未选中
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_CS_Hight(void)
{
    HAL_GPIO_WritePin(W25Qxx_CS_GPIOx, W25Qxx_CS_PIN, GPIO_PIN_SET);
}
/**
 * @brief 通过SPI发送接收数据(阻塞)(SPI是移位发送,接收时要发送数据,发送时也会收到数据)
 * @param TxData:发送的数据
 * @retval 接收的数据
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint8_t W25Qxx_SPI_RW_Byte(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&W25Qxx_SPI_Handle, &TxData, &Rxdata, 1, 1000);
    return Rxdata;
}

/**
 * @brief 批量读SPI
 * @param RX_Buf:接收缓冲区
 * @param len:接收长度
 * @author HZ12138
 * @date 2025-02-23 22:58:29
 */
void W25Qxx_SPI_Read_Len(uint8_t *RX_Buf, uint32_t len)
{
    HAL_SPI_Receive(&W25Qxx_SPI_Handle, RX_Buf, len, 1000);
}
/**
 * @brief 批量写SPI
 * @param TX_Buf:发送缓冲区
 * @param len:发送长度
 * @author HZ12138
 * @date 2025-02-23 23:00:49
 */
void W25Qxx_SPI_Write_Len(uint8_t *TX_Buf, uint32_t len)
{
    HAL_SPI_Transmit(&W25Qxx_SPI_Handle, TX_Buf, len, 1000);
}
#endif

/**
 * @brief 向扇区写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 * @return 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Sector(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;

    for (uint32_t i = 0; i < 16; i++) // 读取原来数据到缓冲区
        W25Qxx_Read_Data((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

    for (uint32_t i = Add_Bef; i < Add_Aft; i++) // 向缓冲区写入数据
    {
        W25Qxx_Buf[(i & 0x00000F00) >> 8][i & 0x000000FF] = Buf[Num];
        Num++;
    }

    W25Qxx_Sector_Erase(Add_Bef); // 清空这个扇区
    W25Qxx_Wait_Free();

    for (uint32_t i = 0; i < 16; i++) // 向FLASH写入缓冲区内的数据
    {
        W25Qxx_Write_Page((Add_Bef & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);
        W25Qxx_Wait_Free();
    }
}
/**
 * @brief 打印出整个扇区的数据
 * @param Address:扇区内的任意地址
 * @return 无
 * @author HZ12138
 * @date 2022-07-04 10:13:03
 */
void W25Qxx_Print_Sector(uint32_t Address)
{
    for (uint32_t i = 0; i < 16; i++)
        W25Qxx_Read_Data((Address & 0xFFFFF000) | (i << 8), W25Qxx_Buf[i], 256);

    for (int i = 0; i < 16; i++)
    {
        printf("%06X:", (Address & 0xFFFFF000) | (i << 8));
        for (int j = 0; j < 256; j++)
        {
            printf("%02X ", W25Qxx_Buf[i][j]);
        }
        printf("\r\n");
    }
}
/**
 * @brief 写入数据
 * @param Address:要写入的地址
 * @param Buf:写入数据的数组地址
 * @param Len:长度
 * @return 无
 * @date 2022-07-04 21:50:38
 */
void W25Qxx_Write(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
    uint32_t Add_Bef = Address;
    uint32_t Add_Aft = Address + Len;
    uint32_t Num = 0;
    W25Qxx_Write_Sector(Add_Bef, Buf, ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef));
    Num = ((Add_Bef & 0xFFFFF000) + 0x00001000 - Add_Bef);
    for (uint32_t i = (Add_Bef & 0xFFFFF000) + 0x00001000;
         i < (Add_Aft & 0xFFFFF000); i += 0x00001000)
    {
        W25Qxx_Write_Sector(i, &Buf[Num], 4096);
        Num += 4096;
    }
    W25Qxx_Write_Sector((Add_Aft & 0xFFFFF000), &Buf[Num], Add_Aft - (Add_Aft & 0xFFFFF000));
}

#ifdef W25Qxx_QSPI
/**
 * @brief QSPI发送命令
 * @param cmd 命令
 * @param addr 地址
 * @param InstructionMode 指令模式
 * @param AddressMode 地址模式
 * @param AddressSize 地址长度
 * @param DataMode 数据模式
 * @param SIOOMode 仅发一次指令
 * @param dmcycle 空周期
 * @param NbData 数据长度
 * @param buf 读取缓冲区
 * @author HZ12138
 * @date 2025-03-19 09:15:00
 */
void W25Qxx_QSPI_CMD(uint8_t cmd, uint32_t addr, uint32_t InstructionMode, uint32_t AddressMode,
                     uint32_t AddressSize, uint32_t DataMode, uint32_t SIOOMode, uint8_t dmcycle, uint32_t NbData, uint8_t *buf)
{
    QSPI_CommandTypeDef Cmdhandler;
    Cmdhandler.Instruction = cmd;     // 指令
    Cmdhandler.Address = addr;        // 地址
    Cmdhandler.DummyCycles = dmcycle; // 设置空指令周期数

    Cmdhandler.InstructionMode = InstructionMode; // 指令模式
    Cmdhandler.AddressMode = AddressMode;         // 地址模式
    Cmdhandler.AddressSize = AddressSize;         // 地址长度
    Cmdhandler.DataMode = DataMode;               // 数据模式
    // QSPI_SIOO_INST_EVERY_CMD
    Cmdhandler.SIOOMode = SIOOMode;
    Cmdhandler.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
    Cmdhandler.DdrMode = QSPI_DDR_MODE_DISABLE;               // 关闭DDR模式
    Cmdhandler.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;

    Cmdhandler.NbData = NbData; // 数据长度
    HAL_QSPI_Command(&W25Qxx_QSPI_Handle, &Cmdhandler, 5000);

    if (NbData != 0 && buf != NULL)
        HAL_QSPI_Receive(&W25Qxx_QSPI_Handle, buf, 5000);
}
#endif

/**
 * @brief 读取芯片的ID
 * @param 无
 * @retval 芯片的ID
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint16_t W25Qxx_Read_ID(void)
{
#ifdef W25Qxx_SSPI
    uint32_t zj1, zj2;
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Device_ID);         // 发送命令
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       // 占位符
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);       // 占位符
    W25Qxx_SPI_RW_Byte(0x00);                         // 必须为0
    zj1 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 发送占位符读取数据
    zj2 = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 发送占位符读取数据
    W25Qxx_CS_Hight();
    return ((zj1 << 8) | zj2);
#endif
#ifdef W25Qxx_QSPI
    uint8_t temp[2];
    uint16_t deviceid;

    W25Qxx_QSPI_CMD(W25Qxx_CMD_Device_ID, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 2, temp);

    deviceid = (temp[0] << 8) | temp[1];
    return deviceid;
#endif
}
/**
 * @brief 读取数据
 * @param Address:要读取的地址
 * @param Buf:将数据放入的数组地址
 * @param Len:读取的字节数
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Read_Data(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
#ifdef W25Qxx_SSPI

    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Read_Data); // 命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址
    // for (int i = 0; i < Len; i++)
    //     Buf[i] = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder);
    W25Qxx_SPI_Read_Len(Buf, Len);
    W25Qxx_CS_Hight();
#endif

#ifdef W25Qxx_QSPI

    W25Qxx_QSPI_CMD(W25Qxx_CMD_QSPI_Read, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 2, Len, Buf);

#endif
}

/**
 * @brief 写保护
 * @param Functional:
 * @arg 1:     允许写入
 * @arg 0:     不允许写入
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Protect(uint8_t Functional)
{
#ifdef W25Qxx_SSPI
    W25Qxx_CS_Low();
    if (Functional == 0)
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Disable); // 不允许写入
    else if (Functional == 1)
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Enable); // 允许写入
    W25Qxx_CS_Hight();
#endif

#ifdef W25Qxx_QSPI

    if (Functional == 0)
        W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Disable, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL); // 不允许写入
    else if (Functional == 1)
        W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Enable, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL); // 允许写入

#endif
    W25Qxx_Wait_Free();
}

/**
 * @brief 读取寄存器的状态
 * @retval 状态
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
uint8_t W25Qxx_Read_StatusReg(uint8_t num)
{
#ifdef W25Qxx_SSPI
    uint8_t zj = 0;
    W25Qxx_CS_Low();

    switch (num)
    {
    case 1:
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg1);
        break;
    case 2:
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg2);
        break;
    case 3:
        W25Qxx_SPI_RW_Byte(W25Qxx_CMD_ReadStatusReg3);
        break;
    default:
        break;
    }
    zj = W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Placeholder); // 接收数据
    W25Qxx_CS_Hight();
    return zj;
#endif
#ifdef W25Qxx_QSPI
    uint8_t temp;
    switch (num)
    {
    case 1:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg1, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    case 2:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg2, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    case 3:
        W25Qxx_QSPI_CMD(W25Qxx_CMD_ReadStatusReg3, 0, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_EVERY_CMD, 0, 1, &temp);
        break;
    default:
        break;
    }
    return temp;
#endif
}
/**
 * @brief 等待写入/擦除完成
 * @param 无
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Wait_Free(void)
{
    while (W25Qxx_Read_StatusReg(1) & 0x01) // 等待写完
        ;
}

/**
 * @brief 扇区擦除
 * @param Address:要擦除的扇区内的任意地址
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Sector_Erase(uint32_t Address)
{
#ifdef W25Qxx_SSPI
    Address &= 0xFFFFF000;
    W25Qxx_Write_Protect(1); // 允许写入

    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Sector_Erase); // 命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x000000FF) >> 0);  // 地址
    W25Qxx_CS_Hight();

    W25Qxx_Write_Protect(0); // 关闭写入
    // while (W25Qxx_Read_StatusReg1() & 0x01) //等待写完
    //     ;
#endif
#ifdef W25Qxx_QSPI
    W25Qxx_Write_Protect(1);

    W25Qxx_QSPI_CMD(W25Qxx_CMD_Sector_Erase, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);

    W25Qxx_Wait_Free();
    W25Qxx_Write_Protect(0);
#endif
}

/**
 * @brief 复位
 * @author HZ12138
 * @date 2025-02-24 19:31:43
 */
void W25Qxx_reset(void)
{
    W25Qxx_QSPI_CMD(W25Qxx_CMD_ENABLE_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
    HAL_Delay(5);
    W25Qxx_QSPI_CMD(W25Qxx_CMD_RESET, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
    HAL_Delay(10);
}

/**
 * @brief 初始化
 * @author HZ12138
 * @date 2025-02-24 19:31:57
 */
void W25Qxx_init(void)
{
#ifdef W25Qxx_QSPI
    W25Qxx_reset();
    W25Qxx_QSPI_CMD(W25Qxx_CMD_Enter_QSPI, 0, QSPI_INSTRUCTION_1_LINE, QSPI_ADDRESS_NONE, QSPI_ADDRESS_8_BITS, QSPI_DATA_NONE, QSPI_SIOO_INST_EVERY_CMD, 0, 0, NULL);
#endif
}

/**
 * @brief 按页写入数据
 * @param Address:地址 256bit/页
 * @param Buf:将数据的数组地址
 * @param Len:写入的字节数
 * @retval 无
 * @author:HZ12138
 * @date: 2022-07-03 20:49:18
 */
void W25Qxx_Write_Page(uint32_t Address, uint8_t *Buf, uint32_t Len)
{
#ifdef W25Qxx_SSPI
    W25Qxx_Write_Protect(1); // 允许写入
    W25Qxx_Wait_Free();
    W25Qxx_CS_Low();
    W25Qxx_SPI_RW_Byte(W25Qxx_CMD_Write_Page); // 命令

    if (W25Qxx_Address_Len == 32)
        W25Qxx_SPI_RW_Byte((Address & 0xFF000000) >> 24); // 如果是32位的地址则发送

    W25Qxx_SPI_RW_Byte((Address & 0x00FF0000) >> 16); // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x0000FF00) >> 8);  // 地址
    W25Qxx_SPI_RW_Byte((Address & 0x00000000) >> 0);  // 地址(确保是首地址)

    W25Qxx_SPI_Write_Len(Buf, Len);

    W25Qxx_CS_Hight();
    W25Qxx_Write_Protect(0); // 关闭写入
#endif

#ifdef W25Qxx_QSPI
    W25Qxx_Write_Protect(1);
    W25Qxx_QSPI_CMD(W25Qxx_CMD_Write_Page, Address, QSPI_INSTRUCTION_4_LINES, QSPI_ADDRESS_4_LINES, W25Qxx_QSPI_ADDR_SIZE, QSPI_DATA_4_LINES, QSPI_SIOO_INST_ONLY_FIRST_CMD, 0, Len, NULL);

    HAL_QSPI_Transmit(&W25Qxx_QSPI_Handle, Buf, 5000);

    W25Qxx_Wait_Free();
    W25Qxx_Write_Protect(0);

#endif
}
/**
 * @brief 进入映射模式
 * @author HZ12138
 * @date 2025-02-25 23:31:05
 */
void W25Qxx_Enter_MemoryMappedMode(void)
{

    QSPI_CommandTypeDef s_command = {0};
    QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};

    s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;     // 1线方式发送指令
    s_command.AddressSize = W25Qxx_QSPI_ADDR_SIZE;           // 32位地址
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; // 无交替字节
    s_command.DdrMode = QSPI_DDR_MODE_DISABLE;               // W25Q256JV不支持DDR
    s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;  // DDR模式,数据输出延迟
    s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;           // 每次传输都发指令

    s_command.Instruction = W25Qxx_CMD_QSPI_FAST_READ; // 快速读取命令
    s_command.AddressMode = QSPI_ADDRESS_4_LINES;      // 4地址线
    s_command.DataMode = QSPI_DATA_4_LINES;            // 4数据线
    s_command.DummyCycles = 6;                         // 空周期

    s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    s_mem_mapped_cfg.TimeOutPeriod = 0;

    HAL_QSPI_MemoryMapped(&W25Qxx_QSPI_Handle, &s_command, &s_mem_mapped_cfg);
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐