通信协议之软件模拟SPI读写W25Qxx(HAL库)

        这两天学习了软件模拟SPI,于是用软件模拟SPI来操作了一下W25Q128的flash。

前言:

        W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景。
存储介质:Nor Flash(闪存)
时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)
存储容量(24位地址):
    W25Q40:      4Mbit / 512KByte
    W25Q80:      8Mbit / 1MByte
    W25Q16:      16Mbit / 2MByte
    W25Q32:      32Mbit / 4MByte
    W25Q64:      64Mbit / 8MByte
    W25Q128:  128Mbit / 16MByte
    W25Q256:  256Mbit / 32MByte

        这里HOLD、WP引脚分别是保持引脚和写保护引脚。当管脚电平为低电平时有效,如WP为低电平时,W25QXX为写保护。不能通过SPI对W25QXX进行数据写操作。这里电路设计将HOLD与写保护置为高电平失能。

一、读取ID号

        通过DataSheet可以看到,该系列的flash有特定的厂商编号以及设备ID号,可以看到W25Q128的制造厂商ID为 0xEF,设备ID根据使用的通信协议不一样返回的设备ID也不一致,如使用标准SPI通信,返回的16进制数据为0x4018,而使用Quad SPI 返回的0x6018。

        JEDEC ID 即为查询厂商和设备ID,发送0x9F,首先会返回一个字节,这个字节的数据为厂商ID。之后继续交换数据,再发送一个数据后,会返回设备ID的高8位,再进行第三次交换,会接收到设备ID的低8位。

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArrary, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(i = 0; i < Count; i++  )
	{
		DataArrary[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

对代码进行测试,通过串口打印,可以看到上位机上显示的数据厂商ID为0xEF,设备ID为0x4018,与Datasheet一致。

二、写使能

        

   写入使能指令(如图5所示)将状态寄存器中的写入使能锁存(WEL)位设置为1。在进行每次页面编程、四路页面编程、扇区擦除、块擦除、芯片擦除、写入状态寄存器以及擦除/编程安全寄存器指令之前,必须先设置WEL位。写入使能指令的执行方式是:首先将/CS信号拉低,然后在CLK时钟信号的上升沿将指令代码“06h”移入数据输入(DI)引脚,之后将/CS信号拉高。

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}

三、等待BUSY

        BUSY是状态寄存器(S0)中的一个只读位,当设备正在执行页面编程、四路页面编程、扇区擦除、块擦除、芯片擦除、写入状态寄存器或擦除/编程安全寄存器指令时,该位会被设置为1状态。在此期间,设备将忽略除读取状态寄存器和擦除/编程暂停指令以外的所有进一步指令(请参阅交流特性中的tW、tPP、tSE、tBE和tCE)。当程序、擦除或写入状态/安全寄存器指令完成后,BUSY位将被清除为0状态,表示设备已准备好接收进一步的指令。

void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//BUSY寄存器为1代表在忙 0跳出循环
	Timeout = 1000000;
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

四、页编程

最小写的单位为页!!!

最小写的单位为页!!!

最小写的单位为页!!!

        页面编程指令允许在一个之前已被擦除(FFh)的内存位置编程从1个字节到256个字节(一个页面)的数据。在设备接受页面编程指令之前,必须执行写入使能指令(状态寄存器位WEL=1)。该指令通过拉低/CS引脚,然后向DI引脚移入指令代码“02h”,紧接着是一个24位地址(A23-A0)以及至少一个数据字节来启动。在向设备发送数据时,/CS引脚必须保持低电平,直到整个指令完成。页面编程指令序列如图29所示。

        如果要编程整个256字节的页面,则最后一个地址字节(8个最低有效地址位)应设置为0。如果最后一个地址字节不为0,并且时钟数超过了剩余页面长度,则寻址将回绕到页面的开头。在某些情况下,可以在不影响同一页面内其他字节的情况下编程少于256字节(部分页面)。执行部分页面编程的一个条件是时钟数不能超过剩余页面长度。如果向设备发送超过256字节的数据,则寻址将回绕到页面的开头并覆盖之前发送的数据。

        与写入和擦除指令一样,在最后一个字节的第8位被锁存后,必须将/CS引脚拉高。如果不这样做,页面编程指令将不会执行。/CS被拉高后,自定时页面编程指令将开始执行,持续时间为tpp(请参阅交流特性)。在页面编程周期进行期间,仍然可以访问读取状态寄存器指令以检查BUSY位的状态。在页面编程周期期间,BUSY位为1,当周期完成且设备准备好再次接受其他指令时,BUSY位变为0。页面编程周期完成后,状态寄存器中的写入使能锁存(WEL)位将被清除为0。如果所寻址的页面受到块保护(CMP、SEC、TB、BP2、BP1和BP0)位或单独块/扇区锁的保护,则页面编程指令将不会执行。

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArrary, uint16_t Count)
{
	uint16_t i;
	W25Q64_WriteEnable();
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(i = 0;i<Count;i++)
	{
		MySPI_SwapByte(DataArrary[i]);
	}
	MySPI_Stop();
	W25Q64_WaitBusy();
}

五、扇擦除

最小擦除的单位为扇区!

最小擦除的单位为扇区!

最小擦除的单位为扇区!

扇区(Sector)
定义与功能:扇区是W25Q128存储结构中的一个基本单元,用于数据的擦除和读取操作。每个扇区都有唯一的地址,可以单独进行擦写和擦除操作。
大小与数量:
操作特性:W25Q128的最小擦除单位为1个扇区,即每次必须擦除4KB的数据。这种设计使得在需要局部更新数据时,可以仅擦除和重写受影响的扇区,从而减少对整个存储空间的影响。


块(Block)
定义与功能:块是由多个扇区组成的更大单元,用于更大范围的数据擦除操作。在W25Q128中,块的概念主要用于擦除操作的分组。
大小与数量:每个块包含16个扇区,因此每个块的大小为64KB(即4KB * 16)。W25Q128总共有256个块,即16MB / 64KB = 256。
操作特性:虽然块不是最小的擦除单位(扇区才是),但在进行大规模数据删除时,可以选择按块进行擦除,以提高效率。


页(Page)
定义与功能:页是W25Q128中用于写入操作的最小单位。每个扇区被进一步划分为多个页,每页的大小为256字节。
大小与数量:由于每个扇区包含4KB的数据,因此每个扇区有4KB / 256B = 16个页。整个芯片总共有65536个页(即4096个扇区 * 16个页/扇区)。
操作特性:写入操作通常以页为单位进行,即每次写入的数据量不可超过一页的大小。这种设计有助于在写入数据时保持数据的完整性和一致性。

W25Q128有256个块,每个块包括16个扇区,每个扇区有16页。页的大小为256B,所以每个扇区的大小为256*16=4096即4K数据。而一个块等于16个扇区。块的大小为16*4k=64K。

扇区擦除指令将指定扇区(4KB)内的所有内存设置为全1(FFh)的擦除状态。在设备接受扇区擦除指令之前,必须执行写使能指令(状态寄存器中的WEL位必须等于1)。指令的启动通过将/CS引脚拉低,然后发送指令码“20h”,后跟一个24位扇区地址(A23-A0)来完成。扇区擦除指令序列如图31a和31b所示。

        在最后一个字节的第八位被锁存后,必须将/CS引脚拉高。如果未执行此操作,扇区擦除指令将不会执行。/CS引脚拉高后,自定时扇区擦除指令将开始执行,持续时间为tSE(请参阅交流特性)。在扇区擦除周期进行中,仍然可以访问读取状态寄存器指令来检查BUSY位的状态。BUSY位在扇区擦除周期期间为1,当周期完成且设备准备好再次接受其他指令时,BUSY位变为0。扇区擦除周期完成后,状态寄存器中的写使能锁存器(WEL)位将被清除为0。如果寻址的页面受到块保护位(CMP、SEC、TB、BP2、BP1和BP0)或单个块/扇区锁的保护,则扇区擦除指令将不会执行。

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

六、读取数据

“读数据”指令允许从内存中顺序读取一个或多个数据字节。指令的启动通过将/CS引脚拉低,然后向DI引脚发送指令码“03h”后跟一个24位地址(A23-A0)来完成。代码和地址位在CLK引脚的上升沿被锁存。接收到地址后,被寻址的内存位置的数据字节将在CLK的下降沿从DO引脚移出,首先是最高有效位(MSB)。每移出一个数据字节后,地址会自动递增到下一个更高的地址,从而允许数据连续流出。这意味着,只要时钟继续,整个内存都可以通过单个指令进行访问。指令的完成通过将/CS拉高来实现。

读数据指令序列如图14所示。如果在擦除、编程或写入周期(BUSY=1)正在进行时发出读数据指令,则该指令将被忽略,并且不会对当前周期产生任何影响。读数据指令允许的时钟速率从直流(D.C.)到最大fR(请参阅交流电气特性)。

请注意,“读数据”(03h)指令仅在标准SPI模式下受支持。

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArrary, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);//32位右移16位,只剩下低八位
	MySPI_SwapByte(Address >> 8);//32位右移8位,只处理中间八位
	MySPI_SwapByte(Address);//发送低八位
	for(i = 0; i < Count; i++  )
	{
		DataArrary[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

七、代码测试

        对以上代码进行测试,读取芯片的厂商ID、设备ID以及对芯片写入和读取数据,结果如下所示。可以看到,代码测试无误。

附件:

W25Q64.c

#include "w25q64.h"
#include "main.h"
#include "MySPI.h"
#include "W25Q64.h"
#include "W25Q64_INS.h"

void W25Q64_Init(void)
{
	MySPI_Init();
}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);		
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}

void W25Q64_WriteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}


void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//BUSY寄存器为1代表在忙 0跳出循环
	Timeout = 1000000;
	while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArrary, uint16_t Count)
{
	uint16_t i;
	W25Q64_WriteEnable();
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	for(i = 0;i<Count;i++)
	{
		MySPI_SwapByte(DataArrary[i]);
	}
	MySPI_Stop();
	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);
	MySPI_SwapByte(Address);
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArrary, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);//32位右移16位,只剩下低八位
	MySPI_SwapByte(Address >> 8);//32位右移8位,只处理中间八位
	MySPI_SwapByte(Address);//发送低八位
	for(i = 0; i < Count; i++  )
	{
		DataArrary[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

W25Q64.h

#ifndef __W25Q64_H_
#define __W25Q64_H_
#include <stdint.h>
void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArrary, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArrary, uint32_t Count);
#endif

W25Q64_INS.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#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_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#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

#define W25Q64_DUMMY_BYTE							0xFF

#endif

软件模拟SPI读写W25Q128(HAL库)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值