文章目录
- 一、W25Q64是什么?
- 二、使用步骤
- 1.硬件
- 1.引脚说明
- 2.硬件连接
- 3.设备ID
- 4.内部框架
- 5.指令集
- 指令集1
- 指令集2
- 2.软件
- 1.W25Q64引脚定义代码如下(示例):
- 2.W25Q64初始化代码如下(示例):
- 3.W25Q64引脚配置代码如下(示例):
- 4.SPI读取数据代码如下(示例):
- 5.写使能/禁止写使能代码如下(示例):
- 6. W25Q64 判断忙状态代码如下(示例):
- 7. W25Q64 扇区/块/芯片擦除代码如下(示例):
- 1.W25Q64 扇区擦除
- 2.W25Q64 块擦除
- 3.W25Q64 芯片擦除
- 8.W25Q64页写操作代码如下(示例):
- 9.W25Q64读数据代码如下(示例):
- 10.W25Q64读取设备ID代码如下(示例):
- 11.W25Q64测试代码如下(示例):
- 12.W25Q64测试结果:
- 三、总结
一、W25Q64是什么?
W25Q64是一种常见的串行闪存存储器,由Winbond公司生产。它的容量为64兆比特(8兆字节),采用SPI接口进行通信。
W25Q64主要特点包括:
1.容量大:W25Q64具有64兆比特(8兆字节)的存储容量,可以存储大量数据。
2.串行接口:W25Q64使用SPI(Serial Peripheral Interface)接口进行通信,它由四条线组成:时钟线(SCK)、数据输入线(MOSI)、数据输出线(MISO)和片选线(CS)。支持SPI模式0和模式3。在SPI模式0中,数据在时钟的下降沿采样,在上升沿传输;而在SPI模式3中,数据在时钟的上升沿采样,在下降沿传输。这两种模式的时序图可以在W25Q64的数据手册中找到。
3.高速读写:W25Q64的串行闪存存储器支持高速读取和写入操作,可达到快速的数据传输速度。
4.扇区擦除:W25Q64的存储空间被划分为多个扇区,每个扇区大小为4KB。可以通过扇区擦除来更新存储的数据。
5.低功耗:W25Q64具有低功耗特性,在待机模式下能够降低功耗,延长电池寿命。
二、使用步骤
1.硬件
1.引脚说明
引脚序号 | 引脚名称 | 引脚功能 |
---|---|---|
1 | CS | 片选引脚,用于选择芯片进行通信。当该引脚被拉低时,表示选中W25Q64芯片。 |
2 | DO | 数据输出引脚,用于将数据从W25Q64芯片传输到主控芯片。 |
3 | WP | 写保护引脚,用于控制W25Q64芯片是否进入写保护状态。当WP引脚被拉低时,芯片进入写保护状态,禁止对芯片进行写操作。 |
4 | GND | 地引脚,用于接地。 |
5 | DI | 数据输入引脚,用于将数据从主控芯片传输到W25Q64芯片。 |
6 | CLK | 时钟引脚,用于同步数据传输的时钟信号。 |
7 | HOLD | HOLD引脚用于暂停SPI通信,以便于主控芯片在需要时保持W25Q64芯片的状态。当HOLD引脚被拉低时,芯片会暂停当前的SPI通信操作,并保持之前的状态。当HOLD引脚恢复高电平时,SPI通信可以继续。 |
8 | VCC | 电源引脚,用于提供芯片的电源电压:2.7V~3.6V。 |
2.硬件连接
从上面的引脚说明,我们不需要使用WP引脚和HOLD引脚的功能,可以将它们直接连接到VCC电源引脚上。这样做可以确保这两个引脚保持在高电平状态,以便芯片正常工作。
连接WP和HOLD引脚到VCC上的好处是简化了连接和布线过程,节省了两个单片机IO口资源。
3.设备ID
W25Q64的芯片ID为0xEF4017。这个ID是由该芯片制造商Winbond公司指定的,用于标识该芯片型号的唯一性。
W25Q64的芯片ID是由3个字节组成,其中第1个字节为制造商ID,固定为0xEF,代表Winbond公司;第2个字节和第3个字节表示设备类型和密度,固定为0x40和0x17,分别代表64Mbit(8MB)的串行闪存存储器。因此,W25Q64的完整芯片ID为0xEF4017。
4.内部框架
存储结构:W25Q64通常以块(Block)、扇区(Sector)和页(Page)的形式组织存储数据。W25Q64一共有128块,每个块包含16个扇区,每个扇区包含16个页,每页最多256字节。
由此可知W25Q64容量的计算方式如下:
以1页/256字节,1扇区包含16页为基础,可以得到1扇区=16256Byte)=4096(Byte)=4KB;
又因1块包含16个扇区,可以得到1块=164096(Byte)=65536(Byte)=64KB;
最后总共128块,128*65536(Byte)=8388608(Byte)=8MB,所以
W25Q64寻址空间:0x000000~0x7FFFFF。
5.指令集
指令集1
//W25Q64指令表1
#define W25Q64_Write_Enable 0x06
#define W25Q64_Write_Disable 0x04
#define W25Q64_Read_Status_register_1 0x05
#define W25Q64_Read_Status_register_2 0x35
#define W25Q64_Write_Status_register 0x01
#define W25Q64_Page_Program 0x02
#define W25Q64_Quad_Page_Program 0x32
#define W25Q64_Block_Erase_64KB 0xD8
#define W25Q64_Block_Erase_32KB 0x52
#define W25Q64_Sector_Erase_4KB 0x20
#define W25Q64_Chip_Erase 0xC7
#define W25Q64_Erase_Suspend 0x75
#define W25Q64_Erase_Resume 0x7A
#define W25Q64_Power_down 0xB9
#define W25Q64_High_Performance_Mode 0xA3
#define W25Q64_Continuous_Read_Mode_Reset 0xFF
#define W25Q64_Release_Power_Down_HPM_Device_ID 0xAB
#define W25Q64_Manufacturer_Device_ID 0x90
#define W25Q64_Read_Uuique_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
指令集2
//W25Q64指令集表2(读指令)
#define W25Q64_Read_Data 0x03
#define W25Q64_Fast_Read 0x0B
#define W25Q64_Fast_Read_Dual_Output 0x3B
#define W25Q64_Fast_Read_Dual_IO 0xBB
#define W25Q64_Fast_Read_Quad_Output 0x6B
#define W25Q64_Fast_Read_Quad_IO 0xEB
#define W25Q64_Octal_Word_Read_Quad_IO 0xE3
2.软件
1.W25Q64引脚定义代码如下(示例):
/* Defines ------------------------------------------------------------------*/
#define W25Q64_GPIO_RCC RCC_APB2Periph_GPIOA
#define W25Q64_GPIO_Port GPIOA
#define SPI_CS_Pin GPIO_Pin_6//CS
#define SPI_DO_Pin GPIO_Pin_5//MISO
#define SPI_SLK_Pin GPIO_Pin_4//CLK
#define SPI_DI_Pin GPIO_Pin_3//MOSI//根据实际的引脚修改
2.W25Q64初始化代码如下(示例):
当 CS 为高电平时,表示不选中该芯片,芯片将不会响应 SPI 总线上传来的数据。因此,为了确保在系统上电后不会意外触发芯片操作,一般会在初始化时将 CS 引脚设置为高电平状态。
/******************************************************************************** 函数名:User_W25Q64_Init* 描述 :W25Q64初始化* 输入 :void* 输出 :void* 调用 :初始化* 备注 :
*******************************************************************************/
void User_W25Q64_Init(void)
{W25Q64_GPIO_Init(); W25Q64_DATA_Init();
}/******************************************************************************** 函数名:W25Q64_GPIO_Init* 描述 :W25Q64引脚初始化* 输入 :void* 输出 :void* 调用 :初始化* 备注 :
*******************************************************************************/
void W25Q64_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(W25Q64_GPIO_RCC, ENABLE);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = SPI_CS_Pin | SPI_DI_Pin | SPI_SLK_Pin; GPIO_Init(W25Q64_GPIO_Port, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = SPI_DO_Pin; GPIO_Init(W25Q64_GPIO_Port, &GPIO_InitStructure);
}/******************************************************************************** 函数名:W25Q64_DATA_Init* 描述 :W25Q64数据初始化* 输入 :void* 输出 :void* 调用 :初始化* 备注 :
*******************************************************************************/
void W25Q64_DATA_Init(void)
{SPI_CS_HIGH();
}
3.W25Q64引脚配置代码如下(示例):
/******************************************************************************** 函数名:SPI_CS_HIGH* 描述 :CS输出高电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_CS_HIGH(void)
{GPIO_SetBits(W25Q64_GPIO_Port,SPI_CS_Pin);
}/******************************************************************************** 函数名:SPI_CS_LOW* 描述 :CS输出低电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_CS_LOW(void)
{GPIO_ResetBits(W25Q64_GPIO_Port,SPI_CS_Pin);
}/******************************************************************************** 函数名:SPI_SLK_HIGH* 描述 :SLK输出高电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_SLK_HIGH(void)
{GPIO_SetBits(W25Q64_GPIO_Port,SPI_SLK_Pin);
}/******************************************************************************** 函数名:SPI_SLK_LOW* 描述 :SLK输出低电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_SLK_LOW(void)
{GPIO_ResetBits(W25Q64_GPIO_Port,SPI_SLK_Pin);
}/******************************************************************************** 函数名:SPI_SLK_HIGH* 描述 :SI输出高电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_DI_HIGH(void)
{GPIO_SetBits(W25Q64_GPIO_Port,SPI_DI_Pin);
}/******************************************************************************** 函数名:SPI_DI_LOW* 描述 :SI输出低电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void SPI_DI_LOW(void)
{GPIO_ResetBits(W25Q64_GPIO_Port,SPI_DI_Pin);
}/******************************************************************************** 函数名:Read_DO_Level* 描述 :读取DO电平* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
uint8_t Read_DO_Level(void)
{return GPIO_ReadInputDataBit(W25Q64_GPIO_Port,SPI_DO_Pin);
}
4.SPI读取数据代码如下(示例):
我这里SPI读数据是采用了SPI模式0的方式,在这种模式下,数据在时钟的下降沿进行采样,并且在时钟的上升沿进行传输,最后将数据保存到Outdata变量中。
SPI模式0是指在时钟空闲时,时钟线为低电平,在读取数据时,数据从MISO引脚上升沿进行采样,这是W25Q64芯片的SPI通信方式之一。
/******************************************************************************** 函数名:SPI_SendByte* 描述 :SPI读取数据* 输入 :data* 输出 :Outdata* 调用 :内部调用* 备注 :通过发送一个字节的数据并同时接收一个字节的数据,实现了数据的读取操作。*******************************************************************************/
uint8_t SPI_SendByte(uint8_t data)
{uint8_t i, Outdata = 0x00; for (i = 0; i < 8; i ++){if(data & (0x80 >> i))//在下降沿,把数据移到MOSI总线上{SPI_DI_HIGH();}else{SPI_DI_LOW();} SPI_SLK_HIGH(); // 上升沿读取数据if (Read_DO_Level()){Outdata |= (0x80 >> i); //掩码提取数据}SPI_SLK_LOW(); // 下降沿} return Outdata;
}
5.写使能/禁止写使能代码如下(示例):
在进行写入操作之前,需要先调用写使能命令来允许写入操作,写入完成后再调用禁止写使能命令来禁止写入操作。如果不调用禁止写使能命令,则W25Q64芯片仍然处于写入状态,可能会导致数据错误或者芯片损坏。
写使能/禁止写使能的时序图在规格书上有,这里就不贴出来了。
/******************************************************************************** 函数名:W25Q64_WriteEnable* 描述 :W25Q64发送写使能命令* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void W25Q64_WriteEnable(void)
{SPI_CS_LOW();SPI_SendByte(W25Q64_Write_Enable);SPI_CS_HIGH();
}/******************************************************************************** 函数名:W25Q64_WriteDisable* 描述 :W25Q64发送禁止写使能命令* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void W25Q64_WriteDisable(void)
{SPI_CS_LOW();SPI_SendByte(W25Q64_Write_Disable);SPI_CS_HIGH();
}
6. W25Q64 判断忙状态代码如下(示例):
该函数的作用是等待 W25Q64 芯片的忙状态结束。在进行一些与 Flash 写入相关的操作之前,需要检查 W25Q64 芯片是否处于忙状态,以确保写入操作的正确性。该函数使用 SPI 协议与 W25Q64 通信,读取状态寄存器中的信息,并检查其中的位 0 是否为 0。如果位 0 为 0,表示忙状态已经结束,函数退出;否则,继续等待。如果等待时间超过了设定的 Timeout 时间,函数将会输出错误信息并退出。
/******************************************************************************** 函数名:W25Q64_WaitForBusyStatus* 描述 :等待 W25Q64 芯片的忙状态结束* 输入 :void* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void W25Q64_WaitForBusyStatus(void)
{uint16_t Timeout = 0xFFFF;SPI_CS_LOW();SPI_SendByte(W25Q64_Read_Status_register_1); while (Timeout > 0){uint8_t status = SPI_SendByte(W25Q64_DUMMY_BYTE);if ((status & 0x01) == 0)// 检查忙状态是否结束{break;} Timeout--;if (Timeout == 0){printf("W25Q64 ERROR \r\n");break;}} SPI_CS_HIGH();
}
7. W25Q64 扇区/块/芯片擦除代码如下(示例):
1.W25Q64 扇区擦除
W25Q64 芯片的扇区大小为 4KB
/******************************************************************************** 函数名:W25Q64_SectorErase* 描述 :扇区擦除函数* 输入 :Sector_Address * 输出 :void* 调用 :内部调用* 备注 :在执行写入操作前要进行擦除*******************************************************************************/
void W25Q64_SectorErase(uint32_t Sector_Address)
{W25Q64_WriteEnable();//W25Q64写使能 SPI_CS_LOW(); SPI_SendByte(W25Q64_Sector_Erase_4KB); SPI_SendByte(Sector_Address >> 16);//24位扇区地址SPI_SendByte(Sector_Address >> 8); SPI_SendByte(Sector_Address); SPI_CS_HIGH();W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 W25Q64_WriteDisable();//W25Q64禁止写使能
}
2.W25Q64 块擦除
W25Q64 芯片的块大小为 64KB
/******************************************************************************** 函数名:W25Q64_BlockErase* 描述 :块擦除函数* 输入 :Address * 输出 :void* 调用 :内部调用* 备注 :在执行写入操作前要进行擦除*******************************************************************************/
void W25Q64_BlockErase(uint32_t Block_Address)
{W25Q64_WriteEnable();//W25Q64写使能 SPI_CS_LOW();SPI_SendByte(W25Q64_Block_Erase_64KB);SPI_SendByte((Block_Address & 0xFF0000) >> 16);//24位扇区地址SPI_SendByte((Block_Address & 0xFF00) >> 8);SPI_SendByte(Block_Address & 0xFF);SPI_CS_HIGH(); W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 W25Q64_WriteDisable();//W25Q64禁止写使能
}
3.W25Q64 芯片擦除
W25Q64 芯片的芯片大小为 8MB
/******************************************************************************** 函数名:W25Q64_ChipErase* 描述 :芯片擦除函数* 输入 :void * 输出 :void* 调用 :内部调用* 备注 :在执行写入操作前要进行擦除*******************************************************************************/
void W25Q64_ChipErase(void)
{W25Q64_WriteEnable();//W25Q64写使能 SPI_CS_LOW();SPI_SendByte(W25Q64_Chip_Erase);SPI_CS_HIGH();W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 W25Q64_WriteDisable();//W25Q64禁止写使能
}
这些擦除函数之间的不同在于擦除的范围。扇区擦除函数擦除的是单个扇区(4KB)的数据;块擦除函数擦除的是单个块(64KB)的数据;而芯片擦除函数则会擦除整个芯片(8MB)的数据。根据实际需要,选择适当的擦除函数进行操作。
注意,如果是选用芯片擦除函数,W25Q64_WaitForBusyStatus();函数中的等待时间uint16_t Timeout = 0xFFFF;需更改为uint32_t Timeout = 0xFFFFFFFF;
8.W25Q64页写操作代码如下(示例):
当使用 W25Q64 芯片进行页写操作时,需要考虑到写入字节数大于 256 字节和不大于 256 字节的两种情况。
如果小于等于 256 字节,直接进行一次写操作;如果大于 256 字节,则先写入前 256 字节的数据,然后循环写入剩余数据,同时需要在每次写入之前等待上次写操作完成。
/******************************************************************************** 函数名:W25Q64_PageProgram* 描述 :W25Q64页写操作* 输入 :address 要写入的起始地址*data 要写入的数据缓冲区指针。dataSize 要写入的数据大小,单位为字节。* 输出 :void* 调用 :内部调用* 备注 :*******************************************************************************/
void W25Q64_PageWrite(uint32_t address, uint8_t *data, uint16_t dataSize)
{W25Q64_WriteEnable();//W25Q64写使能 SPI_CS_LOW();SPI_SendByte(W25Q64_Page_Program);SPI_SendByte((address & 0xFF0000) >> 16);SPI_SendByte((address & 0xFF00) >> 8);SPI_SendByte(address & 0xFF); if (dataSize <= 256){for (uint16_t i = 0; i < dataSize; i++){SPI_SendByte(data[i]);}}else{for (uint16_t i = 0; i < 256; i++){SPI_SendByte(data[i]);}dataSize -= 256;data += 256; // 写入剩余数据W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 address += 256; while (dataSize > 0){W25Q64_WriteEnable();//W25Q64写使能 SPI_CS_LOW();SPI_SendByte(W25Q64_Page_Program);SPI_SendByte((address & 0xFF0000) >> 16);SPI_SendByte((address & 0xFF00) >> 8);SPI_SendByte(address & 0xFF); uint16_t chunkSize = (dataSize > 256) ? 256 : dataSize;for (uint16_t i = 0; i < chunkSize; i++){SPI_SendByte(data[i]);} SPI_CS_HIGH();W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 dataSize -= chunkSize;data += chunkSize;address += chunkSize;}} SPI_CS_HIGH();W25Q64_WaitForBusyStatus();//等待 W25Q64 芯片的忙状态结束 W25Q64_WriteDisable();//W25Q64禁止写使能
}
9.W25Q64读数据代码如下(示例):
/******************************************************************************** 函数名:W25Q64_ReadData* 描述 :W25Q64读数据* 输入 :void* 输出 :void* 调用 :* 备注 :*******************************************************************************/
void W25Q64_ReadData(uint32_t address, uint8_t *data, uint16_t dataSize)
{uint16_t i;SPI_CS_LOW();SPI_SendByte(W25Q64_Read_Data);SPI_SendByte((address & 0xFF0000) >> 16);SPI_SendByte((address & 0xFF00) >> 8);SPI_SendByte(address & 0xFF); for (i = 0; i < dataSize; i++){data[i] = SPI_SendByte(W25Q64_DUMMY_BYTE);} SPI_CS_HIGH();
}
10.W25Q64读取设备ID代码如下(示例):
W25Q64的芯片ID为0xEF4017,用于标识该芯片型号的唯一性。
/******************************************************************************** 函数名:W25Q64_ReadID* 描述 :W25Q64读取设备ID* 输入 :*ID 存储ID的变量 * 输出 :void* 调用 :内部调用* 备注 :EF4017*******************************************************************************/
void W25Q64_ReadID(uint32_t *ID)
{SPI_CS_LOW();SPI_SendByte(W25Q64_JEDEC_ID); // 读ID号指令*ID = SPI_SendByte(W25Q64_DUMMY_BYTE); // 厂商ID,默认为0xEF*ID <<= 8;*ID |= SPI_SendByte(W25Q64_DUMMY_BYTE); // 设备ID,表示存储类型,默认为0x40*ID <<= 8;*ID |= SPI_SendByte(W25Q64_DUMMY_BYTE); // 设备ID,表示容量,默认为0x17SPI_CS_HIGH();
}
11.W25Q64测试代码如下(示例):
/******************************************************************************** 函数名:W25Q64_Test* 描述 :W25Q64测试函数* 输入 :void* 输出 :void* 调用 :测试* 备注 :*******************************************************************************/
void W25Q64_Test(void)
{uint8_t i,j; W25Q64_ReadID(&W25Q64_ID); OLED_ShowString(1, 1, "ID:");OLED_ShowString(2, 1, "W:");OLED_ShowString(3, 1, "R:"); OLED_ShowHexNum(1, 5, W25Q64_ID, 6); //printf("W25Q64_ID=%d\r\n",W25Q64_ID); W25Q64_SectorErase(0x000000);// 擦除扇区的起始地址W25Q64_PageWrite(0x000000, TestWrite, 4);// 写入数据 W25Q64_ReadData(0x000000, TestRead, 4); // 读取数据 for(i = 0;i < 4; i++){OLED_ShowHexNum(2, 3+i*3, TestWrite[i], 2);}for(j = 0;j < 4; j++){OLED_ShowHexNum(3, 3+j*3, TestRead[j], 2);}
}
12.W25Q64测试结果:
我们可以在OLED显示屏看到W25Q64的ID为EF4017,测试写入和读出的数据是一样的。
三、总结
总的来说,使用 W25Q64 需要熟悉其相关的指令集、通信协议和时序要求,合理地设计读写操作流程,并注意处理特殊情况,比如数据跨页写入时的处理等。感谢你的观看,谢谢!