一、总述
W25X16、W25X32 和 W25X64 分别有 8192、16384 和 32768 可编程页,每页 256 字节用“页编程指令”每次就可以编程 256 个字节。用“扇区 (sector) 擦除指令”每次可以擦除 16 页,用“块(block)擦除指令”每次可以擦除 256 页,用“整片擦除指令”即可以擦除整个芯片。W25X16、W25X32 和 W25X64 分别有 512、1024 和 2048 个可擦除“扇区”或32、64 和 128 个可擦除“块”。
W25X16、W25X32 和 W25X64 支持标准的 SPI接口,传输速率最大 75MHz。
2:芯片选择引脚 CS;
3:串行数据输出引脚 DO;
串行 FLASH 系列存储器系列:
W25X16: 16M 比特 (bit) /2M 字节 (byte)
W25X32: 32M 比特 (bit) /4M 字节 (byte)
W25X64: 64M 比特(bit) /8M字节 (byte)
统一的 4K 字节扇区 (Sectors) 和 64K 字节块区(Blocks)。一页有256B,共有16页;一个扇区有4K(4096)=256Bx16(页),有16扇区;一个块有64K(65535)=4Kx16(扇区),有64块。
二、指令表
通过发送和接收指令实现相应功能
三、功能介绍
1.读ID指令(90H)
制造/器件ID和 JEDEC ID的执行过程及时序图是一样的。
“读制造/器件号”指令不同于“释放掉电/器件ID指令”,“读制造/器件号”指令读出的数据包含JEDEC标准制造号和特殊器件ID号。
先把/CS引脚拉低,然后把指令90h通过引脚DIO送到芯片,然后接着把24位地址000000h送到芯片,然后芯片会先后把“生产D”和“器件D”通过DO引脚在CLK的上升沿发送出去。如果把24位地址写为000001h,ID号的发送顺序会颠倒,即先发“器件 ID”后发“生产ID”。ID号都是8位数据。
2.写使能(06H)
“写使能”指令将会使“状态寄存器”WEL位置位。在执行每个“页编程”“扇区擦除”“块区擦除”“芯片擦除”和“写状态寄存器”命令之前,都要先置位WEL。ICS 引脚先拉低之后,“写使能”指令代码06h 从 DI引脚输入,在CLK上升沿采集,然后再拉高/CS 引脚。
3.读状态寄存器(05H)
当/CS拉低之后,开始把05h 从DIO 引脚送到芯片,在CLK的上升沿数据被芯片采集,当芯片认出采集到的数据时05h时,芯片就会把“状态寄存器”的值从DO引脚输出,数据在CLK的下降沿输出,高位在前。
“读状态寄存器”指令在任何时候都可以用,甚至在编程、擦除和写状态寄存器的过程中也可以用,这样,就可以从状态寄存器的BUSY位判断编程、擦除和写状态寄存器周期有没有结束,从而让我们知道芯片是否可以接收下一条指令了。如果/CS不被拉高,状态寄存器的值将一直从DO引脚输出。ICS拉高之后,读指令结束。
4.读数据(03H)
这个时序要注意:如果使用硬件sPI功能,一般需要主机先发送一个字节数据后才能接收到flash发送出来的数据,实际上是为了给flash提供发送的时钟(因为时钟只能由主机产生)。
“读数据”指令允许读出一个字节或一个以上的字节被读出。先把/CS引脚拉低,然后把03h通过DIO引脚送到芯片,之后再送入24位的地址,这些数据在CLK的上升沿被芯片采集。芯片接收完24位地址之后,就会把相应地址的数据在CLK引脚的下降沿从DO引脚送出去,高位在前。当读完这个地址的数据之后,地址自动增加,然后通过DO引脚把下一个地址的数据送出去,形成一个数据流。也就是说,只要时钟在工作,通过一条读指令,就可以把整个芯片存储区的数据读出来。把/CS 引脚拉高,“读数据”指令结束。当芯片在执行编程、擦除和读状态寄存器指令的周期内,“读数据”指令不起作用。
5.页写(02H)
执行“页编程”指令之前,需要先执行“写使能”指令,而且要求待写入的区域位都为1,也就是需要先把待写入的区域擦除。先把/CS 引脚拉低,然后把代码02h通过DIO引脚送到芯片,然后再把24位地址送到芯片,然后接着送要写的字节到芯片。在写完数据之后,把/CS引脚拉高。
写完一页(256个字节)之后,必须把地址改为0,不然的话,如果时钟还在继续,地址将自动变为页的开始地址。在某些时候,需要写入的字节不足256个字节的话,其它写入的字节都是无意义的。如果写入的字节大于了256个字节,多余的字节将会加上无用的字节覆盖刚刚写入的的256个字节。所以需要保证写入的字节小于等于256个字节。
在指令执行过程中,用“读状态寄存器”可以发现BUSY位为1,当指令执行完毕,BUSY位自动变为0。如果需要写入的地址处于“写保护”状态,“页编程”指令无效。
6.整块芯片擦除(07H)
“芯片擦除”指令将会使整个芯片的存储区位都变为1,即字节都变位FFh。在执行“芯片擦除”指令之前需要先执行“写使能”指令。
先把/CS引脚拉低,然后再把指令代码C7h通过DIO引脚送到芯片,然后拉高/CS 引脚。如果没有及时拉高/CS 引脚,指令无效。在“芯片擦除”指令执行周期内,可以执行“读状态寄存器”指令访问BUSY位,这时BUSY位为1,当“芯片擦除”指令执行完毕,BUSY变为0,WEL位也变为0。任何一个块区处于保护状态(BP2\BPIBP)),指令都会失效。
四、代码实现
初始化代码这里就不介绍了,注意初始化SPI和GPIO哦,引脚一定要配置正确哦
#define W25Q32_FLASH_STATUS_WIP (1UL << 0)
#define W25Q32_FLASH_STATUS_WEL (1UL << 1)
//函数功能:SPI发送接收数据
static uint8_t W25Q32_FlashSendBytes(uint8_t byte)
{
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_TXE));
SPI_SendData(SPI1, byte);
while (RESET == SPI_GetFlagStatus(SPI1, SPI_FLAG_RXNE));
SPI_ReceiveData(SSPI1);
}
//函数功能:读JEDEC_ID 先读ID确保FLASH可以正常使用
uint32_t W25Q32_FlashReadID(void)
{
uint32_t temp0 = 0, temp1 = 0, temp2 = 0;
W25Q32_FLASH_CS_LOW();//片选拉低
W25Q32_FlashSendBytes(0X9F);
temp0=W25Q32_FlashSendBytes(0XFF)<<16;
temp1=W25Q32_FlashSendBytes(0XFF)<<8;
temp2=W25Q32_FlashSendBytes(0XFF);
W25Q32_FLASH_CS_HIGH();
return (temp0|temp1|temp2);
}
//函数功能:写使能 部分功能执行前需要先执行写使能
void W25Q32_FlashWriteEnable(void)
{
W25Q32_FLASH_CS_LOW();
W25Q32_FlashSendBytes(0X06);
W25Q32_FLASH_CS_HIGH();
}
//函数功能:读状态
uint32_t W25Q32_FlashReadStatus(void)
{
uint8_t status = 0;
W25Q32_FLASH_CS_LOW();
W25Q32_FlashSendBytes(0X05);
status = W25Q32_FlashSendBytes(0XFF);
W25Q32_FLASH_CS_HIGH();
return status;
}
//函数功能:读数据(接收数据)
int W25Q32_FlashReadData(uint8_t *buffer, uint32_t addr, uint32_t size)
{
int count = 0;
if (addr >= MAX_SIZE || NULL == buffer || size < 1)
{
return 1;
}
memset(buffer, 0, size);
W25Q32_FLASH_CS_LOW();
W25Q32_FlashSendBytes(0X03);
W25Q32_FlashSendBytes((addr >> 16) & 0xFF);
W25Q32_FlashSendBytes((addr >> 8) & 0xFF);
W25Q32_FlashSendBytes(addr & 0xFF);
for (count = 0; count < size; count++)
{
buffer[count] = W25Q32_FlashSendBytes(0XFF);
}
W25Q32_FLASH_CS_HIGH();
return 0;
}
//函数功能:页写
int W25Q32_FlashPageWrite(uint8_t *buffer, uint32_t addr, uint32_t size)//页写
{
int count = 0;
if (addr >= MAX_SIZE || NULL == buffer || size < 1)//MAX_SIZE=4M
{
return 1;
}
W25Q32_FlashWriteEnable();
if (!(W25Q32_FLASH_STATUS_WEL & W25Q32_FlashReadStatus()))
{
return OPERATE_FAILED;
}
W25Q32_FLASH_CS_LOW();
W25Q32_FlashSendBytes(0X02);
W25Q32_FlashSendBytes((addr >> 16) & 0xFF);
W25Q32_FlashSendBytes((addr >> 8) & 0xFF);
W25Q32_FlashSendBytes(addr & 0xFF);
for (count = 0; count < size; count++)
{
W25Q32_FlashSendBytes(buffer[count]);
}
W25Q32_FLASH_CS_HIGH();
while (W25Q32_FlashReadStatus() & W25Q32_FLASH_STATUS_WIP);//读忙位
return count;
}
//函数功能:跨页写
int W25Q32_Flash_WriteData(uint8_t* pBuff, uint32_t Addr, uint32_t Num)//发送数据
{
uint8_t addr = 0;
uint8_t temp = 0;
uint8_t count = 0;
uint8_t numpage = 0;
uint8_t numsingle = 0;
addr = Addr % PAGE_SIZE; //PAGE_SIZE=256
count = PAGE_SIZE - addr;//当地址从中间开始时,保存前面不满一页的数据
numpage = Num / PAGE_SIZE;
numsingle = Num % PAGE_SIZE;
if (0 == addr) //位置从一页(256)的0地址开始
{
if (0 == numpage)//数量不满一页(256)
{
W25Q32_FlashPageWrite(pBuff, Addr, Num);
}
else //数量超过一页
{
while(numpage--)
{
W25Q32_FlashPageWrite(pBuff, Addr, PAGE_SIZE);//写满页的数据
Addr += PAGE_SIZE;
pBuff += PAGE_SIZE;
}
W25Q32_FlashPageWrite(pBuff, Addr, numsingle); //写最后未满一页的数据
}
}
else
{
if (0 == numpage)
{
if (numsingle > count)//数量过多需要跨页写
{
temp = numsingle - count;
W25Q32_FlashPageWrite(pBuff, Addr, count);
Addr += count;
pBuff += count;
W25Q32_FlashPageWrite(pBuff, Addr, temp);
}
else
{
W25Q32_FlashPageWrite(pBuff, Addr, Num);
}
}
else
{
Num -= count;
numpage = Num / PAGE_SIZE;
numsingle = Num % PAGE_SIZE;
W25Q32_FlashPageWrite(pBuff, Addr, count);
Addr += count;
pBuff += count;
while(numpage--)
{
W25Q32_FlashPageWrite(pBuff, Addr, PAGE_SIZE);//满页的数据
Addr += PAGE_SIZE;
pBuff += PAGE_SIZE;
}
if (0 != numsingle)
{
W25Q32_FlashPageWrite(pBuff, Addr, numsingle);//写最后未满一页的数据
}
}
}
}
//函数功能:整个芯片擦除 写数据前需要先进行芯片擦除,否则无法写入。
int W25Q32_FlashChipErase(void)
{
W25Q32_FlashWriteEnable();
if (!(W25Q32_FLASH_STATUS_WEL & W25Q32_FlashReadStatus()))
{
return 2;
}
W25Q32_FLASH_CS_LOW();
W25Q32_FlashSendBytes(0XC7);
W25Q32_FLASH_CS_HIGH();
while (W25Q32_FlashReadStatus() & W25Q32_FLASH_STATUS_WIP);
return 0;
}