概述
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
芯片支持的工作电压 2.7V 到 3.6V,正常工作时电流小于5mA,掉电时低于1uA
注意
1.W25Qxx的封装和通信协议有许多种,本文以最常见的SOT-8封装和SPI通信为例
2.FLASH因为物理限制(相比EEPROM省成本),只能将1变成0,所以需要在写入数据前进行擦除操作
W25Qxx的数据组成
下图是数据手册的截图(W25Q64)
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)
地址的构成如下图
标准SPI
SPI配置
因为是存储芯片的驱动,往往需要较大的传输速度以支持快速读取数据
所以使用硬件SPI进行通信
配置内容
1.全双工主机模式
2.软件触发NSS(片选)
3.数据长度8bit
4.先发送MSB(高位)
5.分频后的时钟要小于80MHz
6.时钟极性(CPOL)为低电平有效,时钟相位(CPHA)为第一个边沿
7.不启用CRC校验
8.不开启DMA和中断(DMA传输的以后再说)
片选信号
使用软件触发需要设置另外的GPIO
必须设置为推挽浮空输出,最高等级(其他设置会出现问题)
HAL配置
硬件SPI
GPIO
基础命令
命令介绍
这部分介绍一下W25Qxx的命令构成,这是数据手册的截图
使用的是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
数据手册的截图
流程
- 片选选中(低电平)
- 发送读取ID命令(0x90)
- 发送2个字节(Byte)的占位符(任意数据即可)
- 发送0x00
- 接收2个字节的数据(也需要发送2个字节的占位符)
- 片选释放(高电平)
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);
}
写使/失能
数据手册的截图
写使能是0x06,写失能是0x04
调用之后即可一直保持失能/使能状态
默认状态是不允许写入数据(失能),这是为了保护数据,避免误操作
因此每次上电后想要写入数据需要使能写入
建议每次写入完成后 将写入失能
为了便于使用,将写入使能和失能放入同一个函数中
流程
- 片选选中(低电平)
- 根据输入选择命令发送
- 片选释放(高电平)
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的截图,用到时再细说各个位的功能
读状态寄存器1的命令 和时序
流程
- 片选选中(低电平)
- 发送命令并接收数据
- 片选释放(高电平)
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的擦除最小单位为扇区
流程
- 片选选中(低电平)
- 发送命令
- 发送地址(只要是扇区内的任意地址即可)
- 片选释放(高电平)
注意:
- 地址长度需要根据不同芯片来定(24bit或32bit)
- 地址是扇区内的任意一个地址即可
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) //等待写完// ;
}
读取数据
读取数据的命令
流程
- 片选选中(低电平)
- 发送命令
- 发送起始地址
- 接收数据
- 片选释放(高电平)
注意
- 地址为起始地址
- 地址会自增
- 可以跨页,区块,扇区
设计为输入起始地址,将数据放到的数字的地址,长度
将读取的数据放入指定的位置
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();
}
按页写入数据
本函数是为了之后的数据写入做的准备函数
流程
- 片选选中(低电平)
- 发送命令
- 发送地址
- 发送数据
- 片选释放(高电平)
注意:
- 本命令不可以跨页使用,如果发送的数据长度小于本页剩余长度,则会从本页最开头覆盖数据
- 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");}
}
扇区单位写入数据
上文说过,芯片擦除数据的最小单位为扇区
因此进行数据写入时,会将整个扇区的数据都给擦除,我们需要建立一个缓冲区来存储数据,才能精确更改几个数据
流程:
- 读取这个扇区原来的数据到缓冲区
- 根据需要写入数据到缓冲区
- 擦除这个扇区的数据
- 将缓冲区的数据写回芯片
用到了一些位操作,建议配合前文的地址构成理解
读取和写入的地址计算和上文的读取扇区数据一样, (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部分,第一个扇区,中间扇区,最后一个扇区
- 第一个扇区的数据长度不定,数据长度为(本扇区结束地址-起始地址)
- 中间扇区数据长度确定,数据长度为4096
- 最后一个扇区数据长度不定,数据长度为(结束地址-本扇区起始地址)
- 只需要根据计数以此推进输入数据再调用写入扇区的函数即可
这个是第一个扇区的数据写入
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配置
开启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即可
其余默认即可
注意GPIO中要选择最高模式
命令
HAL库API
QSPI命令结构体
类型 | 名称 | 功能 |
---|---|---|
uint32_t | Instruction | 命令(8bits) |
uint32_t | Address | 地址(32bits) |
uint32_t | AlternateBytes | 备用字节(32bits) |
uint32_t | AddressSize | QSPI_ADDRESS_8_BITS 到QSPI_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_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);
}