欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > W25Qxx

W25Qxx

2025/4/10 2:19:55 来源:https://blog.csdn.net/m0_57585228/article/details/147030765  浏览:    关键词:W25Qxx

概述

FLASH

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

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

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

芯片型号

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

型号容量ID最大地址(最小为0x0)
W25Q162MByte(16Mbit)0xEF140x1FFFFF
W25Q324MByte(32Mbit)0xEF150x3FFFFF
W25Q648MByte(64Mbit)0xEF160x7FFFFF
W25Q12816MByte(128Mbit)0xEF170xFFFFFF
W25Q25632MByte(256Mbit)0xEF180x01FFFFFF
W25Q51264MByte(512Mbit)0xEF190x03FFFFFF

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);                         //必须为0zj1 = 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]);elseW25Qxx_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) 根据芯片设置 是这里的数是 2 x 2^x 2x (eg: W25Q64 是64Mbit=8MByte log ⁡ 2 ( 8 × 1024 × 1024 ) + 1 = 23 + 1 = 24 \log_{2}{(8\times 1024\times 1024)} +1 =23+1=24 log2(8×1024×1024)+1=23+1=24 ) 注意最后要+1 来解决最后字节问题

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

其余默认即可

image-20250318232740991

注意GPIO中要选择最高模式

命令

HAL库API

QSPI命令结构体
类型名称功能
uint32_tInstruction命令(8bits)
uint32_tAddress地址(32bits)
uint32_tAlternateBytes备用字节(32bits)
uint32_tAddressSizeQSPI_ADDRESS_8_BITSQSPI_ADDRESS_32_BITS
uint32_tAlternateBytesSize交换字节大小(未用到)
uint32_tDummyCycles空周期(0-31)
uint32_tInstructionMode指令模式(QSPI_INSTRUCTION_NONE等)
uint32_tAddressMode地址模式(QSPI_ADDRESS_NONE等)
uint32_tAlternateByteMode字节交换模式(QSPI_ALTERNATE_BYTES_NONE
uint32_tDataMode数据模式(QSPI_DATA_NONE等)
uint32_tNbData数据长度(32bits)(0表示不确定的长度直到内存结束)
uint32_tDdrModeDDR模式(一般关闭即可QSPI_DDR_MODE_DISABLE)
uint32_tDdrHoldHalfCycleddr保持周期(一般QSPI_DDR_HHC_ANALOG_DELAY)
uint32_tSIOOMode是否单独命令(一般每次都发命令QSPI_SIOO_INST_EVERY_CMD)

QSPI内存映射结构体

类型名称功能
uint32_tTimeOutPeriodFIIFO满等待数(16bits)
uint32_tTimeOutActivation是否使用超时(QSPI_TIMEOUT_COUNTER_DISABLE)
命令发送
类型名称功能
QSPI_HandleTypeDefhqspi句柄
QSPI_CommandTypeDefcmd控制结构体
uint32_tTimeout超时
HAL_StatusTypeDef返回状态
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi, QSPI_CommandTypeDef *cmd, uint32_t Timeout)
获取数据
类型名称功能
QSPI_HandleTypeDefhqspi句柄
uint8_tpData数据缓冲区
uint32_tTimeout超时
HAL_StatusTypeDef返回状态
HAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout)
进入内存映射
类型名称功能
QSPI_HandleTypeDefhqspi句柄
QSPI_CommandTypeDefcmd命令结构体
QSPI_MemoryMappedTypeDefcfg内存映射结构体
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_CMDCmdhandler.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不支持DDRs_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:预分频 1FIFO 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
#endifuint16_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_CMDCmdhandler.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_SSPIuint32_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);                         // 必须为0zj1 = 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_QSPIuint8_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_SSPIW25Qxx_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_QSPIW25Qxx_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_SSPIW25Qxx_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_QSPIif (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); // 允许写入#endifW25Qxx_Wait_Free();
}/*** @brief 读取寄存器的状态* @retval 状态* @author:HZ12138* @date: 2022-07-03 20:49:18*/
uint8_t W25Qxx_Read_StatusReg(uint8_t num)
{
#ifdef W25Qxx_SSPIuint8_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_QSPIuint8_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_SSPIAddress &= 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_QSPIW25Qxx_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_QSPIW25Qxx_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_SSPIW25Qxx_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_QSPIW25Qxx_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不支持DDRs_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);
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词