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