W25Q64(模拟SPI)读写数据的简单应用

文章目录

  • 一、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.引脚说明

在这里插入图片描述

引脚序号引脚名称引脚功能
1CS片选引脚,用于选择芯片进行通信。当该引脚被拉低时,表示选中W25Q64芯片。
2DO数据输出引脚,用于将数据从W25Q64芯片传输到主控芯片。
3WP写保护引脚,用于控制W25Q64芯片是否进入写保护状态。当WP引脚被拉低时,芯片进入写保护状态,禁止对芯片进行写操作。
4GND地引脚,用于接地。
5DI数据输入引脚,用于将数据从主控芯片传输到W25Q64芯片。
6CLK时钟引脚,用于同步数据传输的时钟信号。
7HOLDHOLD引脚用于暂停SPI通信,以便于主控芯片在需要时保持W25Q64芯片的状态。当HOLD引脚被拉低时,芯片会暂停当前的SPI通信操作,并保持之前的状态。当HOLD引脚恢复高电平时,SPI通信可以继续。
8VCC电源引脚,用于提供芯片的电源电压: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块=16
4096(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,表示容量,默认为0x17
    SPI_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 需要熟悉其相关的指令集、通信协议和时序要求,合理地设计读写操作流程,并注意处理特殊情况,比如数据跨页写入时的处理等。感谢你的观看,谢谢!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/252433.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在排序数组中查找元素的第一个和最后一个位置(Java详解)

一、题目描述 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&#xff0c;返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。 示…

Android开发——组合函数、注解与连接Android设备

1、JetPack Compose、组合函数与注解和文本修改 1、JetPack Compose&#xff1a;Jetpack Compose 是由 Google 推出的用于构建 Android 用户界面的现代化工具包。它是一个声明式的 UI 工具包&#xff0c;用于简化 Android 应用程序的用户界面设计和开发。Jetpack Compose 采用…

02.Git常用基本操作

一、基本配置 &#xff08;1&#xff09;打开Git Bash &#xff08;2&#xff09;配置姓名和邮箱 git config --global user.name "Your Name" git config --global user.email "Your email" 因为Git是分布式版本控制工具&#xff0c;所以每个用户都需要…

02-分组查询group by和having的使用

分组查询 MySQL中默认是对整张表的数据进行操作即整张表为一组, 如果想对每一组的数据进行操作,这个时候我们需要使用分组查询 分组函数的执行顺序: 先根据where条件筛选数据,然后对查询到的数据进行分组,最后也可以采用having关键字过滤取得正确的数据 group by子句 在一条…

【STM32】STM32学习笔记-EXTI外部中断(11)

00. 目录 文章目录 00. 目录01. 中断系统02. 中断执行流程03. STM32中断04. NVIC基本结构05. NVIC优先级分组06. EXTI简介07. EXTI基本结构08. AFIO复用IO口09. EXTI框图10. 计数器模块11. 旋转编码器简介12. 附录 01. 中断系统 中断&#xff1a;在主程序运行过程中&#xff0…

easy贪吃蛇

之前承诺给出一个贪吃蛇项目。 1.EasyX库认知 有关EasyX库的相关信息&#xff0c;您可以看一下官方的文档&#xff1a;EasyX官方文档。 这里我做几点总结&#xff1a; EasyX库就和名字一样&#xff0c;可以让用户调用一些简单的函数来绘制图像和几何图形利用EasyX库可以制作…

ES6 面试题 | 15.精选 ES6 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

知识付费平台选择指南:如何找到最适合你的学习平台?

在当今的知识付费市场中&#xff0c;用户面临的选择越来越多&#xff0c;如何从众多知识付费平台中正确选择属于自己的平台呢&#xff1f;下面&#xff0c;我们将为您介绍我有才知识付费平台相比同行的优势&#xff0c;帮助您做出明智的选择。 一、创新的技术架构&#xff0c;…

3.3【窗口】窗口的几何形状(二,窗口属性)

写在前面 应用程序使用窗口来显示内容。一些属性决定了窗口及其内容的大小和位置。其他属性决定了窗口内容的外观和解释。 了解窗口属性引用的两个坐标系非常重要。如果你知道你正在使用的坐标系,那么为你的窗口属性选择设置值会容易得多,并且会更有意义。 一,显示相关属…

【CMU 15-445】Lecture 11: Joins Algorithms 学习笔记

Joins Algorithms Nested Loop JoinNaive Nested Loop JoinBLock Nested Loop JoinIndex Nested Loop Join Sort-Merge JoinHash JoinBasic Hash JoinPartitioned Hash Join Conclusion 本节课主要介绍的是数据库系统中的一些Join算法 Nested Loop Join Naive Nested Loop Joi…

华媒舍:打造出色科普文章的10步路透社发稿手册

1.科普文章的编写是一项让科技知识更为浅显易懂重要工作。下面我们就详细介绍路透社的发稿手册&#xff0c;带来了好用的流程&#xff0c;协助作者从零开始创作出色的科普文章。 2.明确主题风格在创作科普文章以前&#xff0c;首先要明确要介绍的主题风格。选择一个有趣且适合…

verilog语法进阶,时钟原语

概述&#xff1a; 内容 1. 时钟缓冲 2. 输入时钟缓冲 3. ODDR2作为输出时钟缓冲 1. 输入时钟缓冲 BUFGP verilog c代码&#xff0c;clk作为触发器的边沿触发&#xff0c;会自动将clk综合成时钟信号。 module primitive1(input clk,input a,output reg y); always (posed…

【C++11特性篇】探究【右值引用(移动语义)】是如何大大提高效率?——对比【拷贝构造&左值引用】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C11系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.【左值&左值引用】和【右值&a…

Leetcode 删除有序数组中的重复项

给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成 示例 1&#xff1a; 输入&…

超声波清洗机哪个好?怎么样挑选清洗力比较强超声波清洗机

大部分的年轻人&#xff0c;现在因为各种原因&#xff0c;戴眼镜的人群不再是局限于学生&#xff0c;很多打工族或者是老人也戴上了眼镜&#xff0c;近视眼或者是老花眼都有。戴眼镜的人群越来越多&#xff0c;会有很多人忽视眼镜清洗这个事情&#xff0c;眼镜长时间不清洗的话…

多层记忆增强外观-运动对齐框架用于视频异常检测 论文阅读

MULTI-LEVEL MEMORY-AUGMENTED APPEARANCE-MOTION CORRESPONDENCE FRAMEWORK FOR VIDEO ANOMALY DETECTION 论文阅读 摘要1.介绍2.方法2.1外观和运动对其建模2.2.记忆引导抑制模块2.3. Training Loss2.4. Anomaly Detection 3.实验与结果4.结论 论文标题&#xff1a;MULTI-LEVE…

云渲染技术下的虚拟现实:技术探索与革新思考

虚拟现实&#xff08;含增强现实、混合现实&#xff09;是新一代信息技术的重要前沿方向&#xff0c;是数字经济的重大前瞻领域&#xff0c;将深刻改变人类的生产生活方式&#xff0c;产业发展战略窗口期已然形成。但是虚拟现实想要深入改变影响我们的生活&#xff0c;以下技术…

PyQt6 QToolBar工具栏控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计44条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

【QT】时间日期与定时器

目录 1.时间日期相关的类 2.日期时间数据与字符串之间的转换 2.1 时间、日期编辑器属性设置 2.2 日期时间数据的获取与转换为字符串 2.3 字符串转换为日期时间 3.QCaIendarWidget日历组件 3.1基本属性 3.2 公共函数 3.3 信号 4.实例程序演示时间日期与定时器的使用 …

Linux c++开发-06-使用Linux API 进行文件的读写

先简单的介绍一下open,read,write 先用open接口去打开文件&#xff0c;flag表示打开文件的权限不同。 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);示例 结果&#xff1a;