W25Qxx 芯片的驱动方法
W25Qxx 芯片是一种 SPI 接口的 Flash 芯片,其内部存储空间为 若干 M 字节,支持 16 位地址读写。 常用的型号有 W25Q16(2M 字节)、W25Q32(4M 字节)、W25Q64(8M 字节)等。W25Qxx 芯片支持多种指令,包括读指令、写指令、擦除指令等,可以通过 SPI 接口发送指令来操作芯片。
一.W25Qxx 芯片的硬件连接
以下是 W25Qxx 芯片测试用的引脚定义,实际使用过程中根据实际硬件进行修改。

二.W25Qxx 芯片的驱动方法
初始化 W25Qxx 芯片,判断芯片是否正常.
- 通过读取 W25Qxx 芯片的 ID,判断是否正确.
- 不同厂家在读取 ID时,会有差异, 一般我们直接读取
#define W25X_JedecDeviceID 0x9F这个地址的值,这个值的三个字节分别表示: 制造商ID、设备ID高字节和设备ID低字节. - 判断 ID 是否正确,如果正确则返回 0,否则返回错误码. ID的第三个字节表示 Flash 容量大小,不同的容量大小对应的 Flash 容量大小不同,我们如果使用 16M 的 Flash 容量大小,因此第三字节的值为 0x17. 现请列举所有的 W25Qxx 芯片的 第三字节的值
c#define W25X_JedecDeviceID 0x9F #define W25X_ReleasePowerDown 0xAB uint16_t W25QXX_ReadID(void) { uint16_t flashID = 0; // uint8_t data[3]; W25QXX_CS(0); // 使能器件 // 通过读取 W25X_ManufactDeviceID 这种方式,不能厂家返回的值并不一样.所以需要使用 W25X_JedecDeviceID // W25QXX_WriteByte(W25X_ManufactDeviceID); // 发送读取ID命令 // W25QXX_WriteByte(0x00); // 发送24位地址 // W25QXX_WriteByte(0x00); // W25QXX_WriteByte(0x00); W25QXX_WriteByte(W25X_JedecDeviceID); // 发送读取ID命令 W25QXX_ReadByte(); // 制造商ID W25QXX_ReadByte(); // 设备ID高字节 flashID = W25QXX_ReadByte(); // 设备ID低字节 W25QXX_CS(1); // 取消片选 // printf("Flash ID: 0x%02X 0x%02X 0x%02X\r\n", data[0], data[1], data[2]); return flashID; } /** * @brief 唤醒W25QXX * @param 无 * @retval 无 */ void W25QXX_WAKEUP(void) { W25QXX_CS(0); // 使能器件 W25QXX_WriteByte(W25X_ReleasePowerDown); // 发送唤醒命令 W25QXX_CS(1); // 取消片选 HAL_Delay(1); // 等待TRES1 } void W25QXX_Init(void) { uint8_t retry = 0; W25QXX_CS(1); // 取消片选 W25QXX_WAKEUP(); // 唤醒 for (retry = 0; retry < 3; retry++) // 尝试3次 { uint16_t flashID = W25QXX_ReadID(); if (flashID != 0) // W25Q128的ID { // printf("Flash ID: 0x%02X \r\n", flashID); // printf("W25QXX INIT SUCCESS! \r\n"); break; } } if (retry == 3) // 检测失败 { // 可以在这里添加错误处理代码 printf("试了3次,还是找不到设备"); } }
2. 其它相关的操作函数包括:
```c
/* 函数声明 */
void W25QXX_Init(void);
void W25QXX_CS(uint8_t level);
uint8_t W25QXX_ReadByte(void);
void W25QXX_WriteByte(uint8_t txData);
uint16_t W25QXX_ReadID(void);
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void W25QXX_Write_Page(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void W25QXX_Write_NoCheck(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void W25QXX_Erase_Sector(uint32_t SectorAddr);
void W25QXX_Erase_Block32K(uint32_t BlockAddr);
void W25QXX_Erase_Block64K(uint32_t BlockAddr);
void W25QXX_Erase_Chip(void);
void W25QXX_Wait_Busy(void);
void W25QXX_WriteEnable(void);
void W25QXX_WriteDisable(void);
uint8_t W25QXX_ReadStatusReg1(void);
uint8_t W25QXX_ReadStatusReg2(void);
uint8_t W25QXX_ReadStatusReg3(void);
void W25QXX_WriteStatusReg(uint8_t status);
void W25QXX_PowerDown(void);
void W25QXX_WAKEUP(void);三. W25Qxx 芯片的核心概念
W25Q64共有8M字节的存储空间,W25Q64的存储结构由页、扇区和块三级组织构成:
- 页(Page):每页大小为256字节,是写入的最小单位。
- 扇区(Sector):每个扇区大小为4 KB(包含16页)。),是擦除的最小单位。
- 块(Block):每个块大小为64 KB(包含16个扇区)。),是较大规模擦除操作的单位。
- 具体数量计算如下: 总容量8 MByte(8,388,608字节)可分解为:
页总数:8,388,608字节 ÷ 256字节/页 = 32,768页。
扇区总数:8,388,608字节 ÷ 4,096字节/扇区 = 2,048扇区(或通过页数计算:32,768页 ÷ 16页/扇区)。
块总数:8,388,608字节 ÷ 65,536字节/块 = 128块(或通过扇区数计算:2,048扇区 ÷ 16扇区/块)。
四.擦除和写入的规则
- 最小的写入单位: 字节。你可以写入单个或多个字节。
- 最小的擦除单位: 扇区。这是最关键的限制!W25Q64 不支持以字节为单位擦除。
- 标准扇区 (Sector): 4KB。这是最常用的擦除单位。
- 块 (Block): 32KB 或 64KB。用于大范围擦除,效率更高。
- 整片 (Chip): 擦除整个芯片。
- 操作顺序:
- 如果目标地址所在扇区的数据已经是全0xFF,则可以直接写入。
- 如果目标地址所在扇区存有非0xFF的数据,则必须:
- 将整个扇区(例如4KB)的数据读出来,暂存到MCU的RAM中。
- 擦除整个扇区,可能会耗时较长。
- 在RAM中修改需要更新的数据。
- 将修改后的整个扇区数据(4KB)重新写入该扇区。
