W25Q64 將 8M 的容量分為 128 個塊(Block),每個塊大小為 64K 字節(jié),每個塊又分為 16個扇區(qū)(Sector),每個扇區(qū) 4K 個字節(jié)。 W25Q64 的最少擦除單位為一個扇區(qū),也就是每次必須擦除 4K 個字節(jié)。操作需要給 W25Q64 開辟一個至少 4K 的緩存區(qū),對 SRAM 要求比較高,要求芯片必須有 4K 以上 SRAM 才能很好的操作。
W25Q64支持SPI總線操作模式0(0,0)和3(1,1)。模式0和模式3之間的主要區(qū)別在于,當SPI總線主處于備用狀態(tài)且數(shù)據(jù)沒有傳輸?shù)酱虚W存時,CLK信號的正常狀態(tài)。對于模式0,CLK信號通常在/CS的下降和上升邊緣較低。對于模式3,CLK信號通常在/CS的下降和上升邊緣較高。
W25Q64 的擦寫周期多達 10W 次,具有 20 年的數(shù)據(jù)保存期限,支持電壓為 2.7~3.6V,W25Q64 支持標準的 SPI,還支持雙輸出/四輸出的 SPI,最大 SPI 時鐘可以到 80Mhz(雙輸出時相當于 160Mhz,四輸出時相當于 320M)。
W25Q64讀寫用的是SPI協(xié)議,想要讀取數(shù)據(jù)時可以通過發(fā)送一個無效數(shù)據(jù)去接收數(shù)據(jù)。W25Q64 的讀寫通過發(fā)送指令來完成。
每次寫數(shù)據(jù)之前都要進行寫使能。W25Q64寫入數(shù)據(jù)是把地址上的“1”變?yōu)椤?”,所以要擦除要寫的區(qū)域讓要寫入的地址都為“1”,然后在進行寫入。
Write Enable指令將狀態(tài)寄存器中的Write Enable Latch (WEL)位設(shè)置為1。每一頁程序、扇區(qū)擦除、塊擦除、芯片擦除和寫入狀態(tài)寄存器指令前必須設(shè)置好WEL位。允許寫入指令輸入驅(qū)動/ CS低,轉(zhuǎn)移指令碼06h進入數(shù)據(jù)輸入(DI)銷CLK的前沿,然后開車/ CS高。
Write Disable指令將狀態(tài)寄存器中的Write Enable Latch (WEL)位重置為0。寫入禁用指令是通過驅(qū)動/CS低,將指令代碼04h轉(zhuǎn)換到DI引腳,然后驅(qū)動/CS高來輸入的。注意,WEL位在通電后,并在寫狀態(tài)寄存器、頁面程序、扇區(qū)擦除、塊擦除和芯片擦除指令完成后自動復位。
讀取狀態(tài)寄存器指令允許讀取8位狀態(tài)寄存器。將指令壓低/CS,將狀態(tài)寄存器-1指令05h和狀態(tài)寄存器-2指令35h移至CLK上升沿DI引腳,即可進入指令。然后將狀態(tài)寄存器位移到CLK下降邊緣的DO pin上,最重要的位(MSB)先移出。狀態(tài)寄存器位包括BUSY位、WEL位、BP2-BP0位、TB位、SEC位、SRP0位、SRP1位和QE位。
讀取狀態(tài)寄存器指令可以在任何時候使用,甚至在程序、擦除或?qū)懭霠顟B(tài)寄存器周期正在進行時也可以使用。這允許檢查繁忙狀態(tài)位,以確定周期何時完成,以及設(shè)備是否可以接受另一條指令。狀態(tài)寄存器可以連續(xù)讀取,指令是通過提高/CS值來完成的。
寫狀態(tài)寄存器指令允許寫狀態(tài)寄存器。允許寫入指令之前必須被執(zhí)行的設(shè)備接受寫狀態(tài)寄存器指令(狀態(tài)寄存器位WEL必須等于1)。寫啟用后,輸入的指令驅(qū)動/ CS低,發(fā)送指令碼 01 h ,然后寫狀態(tài)寄存器的數(shù)據(jù)字節(jié)。
只能寫入非易失性狀態(tài)寄存器位SRP0、SEC、TB、BP2、BP1、BP0(狀態(tài)寄存器-1的第7、5、4、3、2位)和QE、SRP1(狀態(tài)寄存器-2的第9和8位)。所有其他狀態(tài)寄存器位位置都是只讀的,不受寫狀態(tài)寄存器指令的影響。
/CS引腳必須在鎖定的第8位或第16位數(shù)據(jù)之后驅(qū)動高。如果不這樣做,寫狀態(tài)寄存器指令將不會執(zhí)行。如果/CS在第8個時鐘之后驅(qū)動高(與25X系列兼容),QE和SRP1位將被清除為0。當/CS被驅(qū)動高后,自動定時寫狀態(tài)寄存器周期將開始,持續(xù)時間為t - W。在寫狀態(tài)寄存器周期進行時,仍然可以訪問讀狀態(tài)寄存器指令來檢查忙位的狀態(tài)。繁忙位在寫狀態(tài)寄存器周期中為1,在周期結(jié)束并準備再次接受其他指令時為0。寫入寄存器周期結(jié)束后,狀態(tài)寄存器中的寫入使能鎖存器(WEL)位將被清除為0。
寫入狀態(tài)寄存器指令允許塊保護位(SEC、TB、BP2、BP1和BP0)被設(shè)置為保護所有、一部分或沒有內(nèi)存不被擦除和程序指令。受保護區(qū)域變?yōu)橹蛔x(請參閱狀態(tài)寄存器內(nèi)存保護表和描述)。寫入狀態(tài)寄存器指令還允許設(shè)置狀態(tài)寄存器保護位(SRP0, SRP1)。這些位與寫入保護(/WP) pin、鎖定或OTP功能一起使用,以禁用對狀態(tài)寄存器的寫入。
讀取數(shù)據(jù)指令允許從內(nèi)存中再按順序讀取一個數(shù)據(jù)字節(jié)。指令通過將/CS引腳壓低,然后將指令代碼03h和一個 24位地址(A23-A0) 轉(zhuǎn)換到DI引腳來啟動。代碼和地址位被鎖定在CLK引腳的上升邊緣。接收到地址后,首先在CLK下降沿的DO pin上以最有效位(MSB)將所尋址內(nèi)存位置的數(shù)據(jù)字節(jié)移出。每個字節(jié)的數(shù)據(jù)移出后,地址會自動增加到下一個更高的地址,從而允許連續(xù)的數(shù)據(jù)流。這意味著只要時鐘繼續(xù)運行,就可以用一條指令訪問整個內(nèi)存。指令是通過提高/CS值來完成的。
如果在擦除、程序或?qū)懼芷?BUSY=1)的過程中發(fā)出讀數(shù)據(jù)指令,則該指令將被忽略,并且不會對當前周期產(chǎn)生任何影響。
頁寫指令允許在以前擦除( FFh )內(nèi)存位置上編程的數(shù)據(jù)從一個字節(jié)到256字節(jié)(一頁)。允許寫入指令之前,必須執(zhí)行設(shè)備將接受頁面程序指令(狀態(tài)寄存器位逢= 1)。指令是由驅(qū)動/ CS銷低然后轉(zhuǎn)移 指令代碼02h ,后跟一個 24位地址(A23-A0) 和至少一個數(shù)據(jù)字節(jié),殘障保險銷。當數(shù)據(jù)被發(fā)送到設(shè)備時,在指令的整個長度內(nèi),/CS引腳必須保持在較低的位置。
如果要對整個256字節(jié)的頁面進行編程,最后一個地址字節(jié)(8個最不重要的地址位)應該設(shè)置為0。如果最后一個地址字節(jié)不為零,并且時鐘的數(shù)量超過剩余的頁長,則尋址將換行到頁的開頭。在某些情況下,可以對少于256字節(jié)(部分頁面)進行編程,而不會對同一頁面中的其他字節(jié)產(chǎn)生任何影響。執(zhí)行部分分頁程序的一個條件是時鐘的數(shù)量不能超過剩余的分頁長度。如果發(fā)送到設(shè)備的字節(jié)數(shù)超過256,則尋址將自動換行到頁面的開頭并覆蓋之前發(fā)送的數(shù)據(jù)。
與寫和擦除指令一樣,/CS引腳必須在鎖住最后一個字節(jié)的第8位之后驅(qū)動到高位。如果不這樣做,頁面程序指令將不會執(zhí)行。當/CS驅(qū)動高后,自動計時的頁面程序指令將在tpp期間開始(參見AC特性)。當頁面程序周期正在進行時,仍然可以訪問Read Status寄存器指令來檢查忙位的狀態(tài)。在頁面程序周期中,忙碌位是1,當周期結(jié)束,設(shè)備準備再次接受其他指令時,忙碌位變?yōu)?。當頁面程序周期完成后,狀態(tài)寄存器中的寫使能鎖存器(WEL)位被清除為0。如果所尋址的頁由塊Protect (BP2、BP1和BP0)位保護,則不會執(zhí)行頁程序指令。
扇區(qū)擦除指令(20h)、32K塊擦除指令(52h)、64K塊擦除指令(D8h)、均可通過發(fā)送扇區(qū)或者塊首地址進行擦除數(shù)據(jù)。整片擦除指令(C7h/60h)可擦除整片8M的數(shù)據(jù)。
#include "spi.h"
#include "delay.h"
#include "w25q64.h"
void W25Q64_Init()
{
Spi1_Init();
}
//寫使能
//在頁編程、擦除(扇區(qū)、塊、全片)、寫狀態(tài)寄存器前,需要寫使能
void W25Q64_WriteEnable()
{
F_CS_L();
Spi1_RevSendByte(0x06);
F_CS_H();
}
//注意:
//在頁編程、擦除(扇區(qū)、塊、全片)、寫狀態(tài)寄存器等操作完成后,WEL位會自動清零
//因此,該函數(shù)也可以不使用
void W25Q64_WriteDisable()
{
F_CS_L();
Spi1_RevSendByte(0x04);
F_CS_H();
}
//讀狀態(tài)寄存器1
u8 W25Q64_ReadStatusReg1()
{
u8 temp = 0;
F_CS_L();
Spi1_RevSendByte(0x05);
temp = Spi1_RevSendByte(0xff);
F_CS_H();
return temp;
}
//判斷忙標記
void W25Q64_WaitBusy()
{
u8 temp = 0;
do{
temp = W25Q64_ReadStatusReg1();
temp &= 0x01;
}while(temp);
}
//寫狀態(tài)寄存器1,用于對存儲區(qū)間進行寫保護
void W25Q64_WriteStatusReg1(u8 status)
{
W25Q64_WriteEnable();
F_CS_L();
Spi1_RevSendByte(0x01);
status < <=2;
Spi1_RevSendByte(status);
F_CS_H();
W25Q64_WaitBusy();
}
//讀數(shù)據(jù)
void W25Q64_ReadBytes(u32 add,u8 *buf,u32 size)
{
u32 i=0;
F_CS_L();
Spi1_RevSendByte(0x03);
Spi1_RevSendByte((u8)(add >?>16));
Spi1_RevSendByte((u8)(add >?>8));
Spi1_RevSendByte((u8)(add >?>0));
for(i=0;i< size;i++)
buf[i]=Spi1_RevSendByte(0xff);
F_CS_H();
}
//頁編程
//條件:要寫的空間,事先要擦除過
void W25Q64_PageProgram(u32 add,u8 *buf,u16 size)
{
u16 i = 0;
W25Q64_WriteEnable();
F_CS_L();
Spi1_RevSendByte(0X02);
Spi1_RevSendByte((u8)(add >?>16));
Spi1_RevSendByte((u8)(add >?>8));
Spi1_RevSendByte((u8)(add >?>0));
for(i=0;i< size;i++)
{
Spi1_RevSendByte(*buf++);
}
F_CS_H();
W25Q64_WaitBusy();
}
//扇區(qū)擦除
void W25Q64_SectorErase(u32 add)
{
W25Q64_WriteEnable();
F_CS_L();
Spi1_RevSendByte(0X20);
Spi1_RevSendByte((u8)(add >?>16));
Spi1_RevSendByte((u8)(add >?>8));
Spi1_RevSendByte((u8)(add >?>0));
F_CS_H();
W25Q64_WaitBusy();
}
//64K塊擦除
void W25Q64_BlockErase64K(u32 add)
{
W25Q64_WriteEnable();
F_CS_L();
Spi1_RevSendByte(0XD8);
Spi1_RevSendByte((u8)(add >?>16));
Spi1_RevSendByte((u8)(add >?>8));
Spi1_RevSendByte((u8)(add >?>0));
F_CS_H();
W25Q64_WaitBusy();
}
//全片擦除
void W25Q64_ChipErase()
{
W25Q64_WriteEnable();
F_CS_L();
Spi1_RevSendByte(0Xc7);
F_CS_H();
W25Q64_WaitBusy();
}
跟AT24C02一樣,如果要對W25Q64寫入大量數(shù)據(jù),通過調(diào)用頁寫函數(shù)就會很不方便,所以可以借用AT24C02連續(xù)寫的思想,編寫W25Q64扇區(qū)寫和連續(xù)寫的函數(shù)。
//由于擦除的最小單位是扇區(qū)(4K),而寫一次的單位是頁(256字節(jié)),兩者存在大小上的不匹配,
//這里擴充寫操作,也將范圍擴充到4K。這就要求在寫操作時要能實現(xiàn)換頁。
//另外:擦除涉及到對已存儲數(shù)據(jù)的破壞,所以實際處理時,需考慮數(shù)據(jù)保護。
//為實現(xiàn)方便,這里假定所操作的4K已經(jīng)被擦除過。
//設(shè)計不檢查空間擦除的操作函數(shù)
//條件:要寫的空間已經(jīng)擦除過
void W25Q64_SectorWrite(u32 add,u8 *buf,u32 size)
{
u16 CanWriteBytes = 0;
while(1)
{
CanWriteBytes = 256 - add%256; //計算當前頁可寫入的字節(jié)數(shù)
if(CanWriteBytes >= size)
{
W25Q64_PageProgram(add,buf,size);
break;
}
else
{
W25Q64_PageProgram(add,buf,CanWriteBytes);
add += CanWriteBytes;
buf += CanWriteBytes;
size -= CanWriteBytes;
}
}
}
//連續(xù)寫操作
//所操作的空間可以沒有擦除過,如果沒有擦除過,則擦除
//擦除前,需對數(shù)據(jù)進行保護
u8 W25Q64_BUF[4096] = {0xff};
void W25Q64_ContinueWrite(u32 add,u8 *buf,u32 size)
{
u16 CanWriteBytes = 0; //計算當前扇區(qū)能寫的字節(jié)數(shù)
u32 SectorAdd = 0; //當前扇區(qū)的首地址
u16 i = 0;
while(1)
{
SectorAdd = (add/4096)*4096; //計算出當前扇區(qū)的首地址
W25Q64_ReadBytes(SectorAdd,W25Q64_BUF,4096); //讀取當前扇區(qū)內(nèi)容
//判斷要寫的那部分扇區(qū)空間是否擦除過
CanWriteBytes = 4096 - add%4096;
if(CanWriteBytes >= size)
CanWriteBytes = size; //如果可寫空間比要寫的內(nèi)容多,則只判斷size個字節(jié)即可
for(i=0;i< CanWriteBytes;i++)
{
if(W25Q64_BUF[add%4096 + i] != 0xff) //沒有擦除
break;
}
if(i< CanWriteBytes) //說明要寫的那部分扇區(qū),存在沒有擦除過的空間
//1. 擦除
W25Q64_SectorErase(SectorAdd); //擦除整個扇區(qū)
//用要寫入的數(shù)據(jù),填充W25Q64_BUF
for(i=0;i< CanWriteBytes;i++)
{
W25Q64_BUF[add%4096 + i] = buf[i];
}
W25Q64_SectorWrite(SectorAdd,W25Q64_BUF,4096);
if(CanWriteBytes == size)
break;
add += CanWriteBytes;
buf += CanWriteBytes;
size -= CanWriteBytes;
}
}
讀寫過程中,如果想讓一些數(shù)據(jù)不丟失,W25Q64可以通過硬件進行內(nèi)存保護,但是硬件保護是整片保護的,操作起來不靈活。所以采用指令進行內(nèi)存保護,可以通過寫狀態(tài)寄存器1對特定區(qū)域進行內(nèi)存保護。
W25Q64可擦寫次數(shù)只有10萬多次,所以寫函數(shù)同樣不推薦放入while循環(huán)中。
主文件
#include "stm32f4xx.h"
#include "led.h"
#include "usart.h"
#include "delay.h"
#include "stdio.h"
#include "W25Q64.h"
int main()
{
u8 buf[]="Hello world!Hello world!Hello world!rn";
u8 rec[50] = {0};
NVIC_SetPriorityGrouping(5); //4層嵌套,4個響應優(yōu)先級
Usart1_Init(9600);
AT24C02_Init();
W25Q64_Init();
W25Q64_ContinueWrite(4090,buf,sizeof(buf));
while(1)
{
W25Q64_ReadBytes(4090,rec,sizeof(buf));
printf("%s",rec);
}
}
最后,編寫測試函數(shù),將程序燒入開發(fā)板中,讀出的數(shù)據(jù)和寫入的數(shù)據(jù)完全一致,SPI讀寫W25Q64成功。
評論
查看更多