在有一些應用中,我們可能需要大一些容量的存儲單元,而實現的形式多種多樣,在這一篇中我們將來討論怎么使用BY25QXXX系列NOR FLASH存儲器的問題。
1、功能概述
??在開始實現BY25QXXX系列NOR FLASH存儲器的驅動之前,我們需要先了解一下它的基本情況。
1.1、QSPI接口
??QSPI接口,是QueuedSPI的縮寫。和之前談到的SPI一樣都是出自Motorola。QSPI在SPI基礎上做了一些增強,且向下兼容SPI。QSPI相對SPI最顯著的差異就是增加了發送接收數據隊列,Queued的稱呼就是這么來的。這樣做的好處就是,無需每次數據傳輸都需要CPU參與,可以降低CPU的資源占用。
??QSPI采用6先模式,同樣也可以按Standard SPI、Dual SPI方式工作。我們引用STM32H7上一張QSPI接口與Flash的連接圖來展示其連線方式。
??QSPI接口可以在以下三種模式下工作:間接模式,使用 QSPI 寄存器執行全部操作;狀態輪詢模式,周期性讀取外部 Flash 狀態寄存器,而且標志位置 1 時會產生中斷(如擦除或燒寫完成,會產生中斷);內存映射模式,外部 Flash 映射到微控制器地址空間,從而系統將其視作內部存儲器。我們在這里考慮BY25QXXX系列NOR FLASH存儲器的驅動問題其實就是以間接模式訪問的情況。
1.2、BY25Q基本特點
??BY25QXXX系列NOR FLASH存儲器支持標準SPI模式、雙線SPI模式、四線SPI模式。其封裝級引腳定義如下:
??這些引腳中,片選信號CS和始終信號SCLK在各種模式下是沒有區別的。而SO(IO1)引腳在標準SPI模式下用作串行輸出,在雙線模式和四線模式下則是IO1。SI(IO0)引腳在標準SPI模式下用作串行輸入,在雙線模式和四線模式下則是IO0。WP(IO2)引腳在標準和雙線模式下為寫保護,在四線模式下為IO2。HOLD(IO3)引腳在標準和雙線模式下為HOLD,在四線模式下為IO3。
1.3、操作指令
??BY25QXXX系列NOR FLASH存儲器在間接訪問模式下,主要有四類指令:配置與狀態指令、讀指令、ID和安全指令、編程和擦除指令。
配置與狀態指令,用于配置操作方式及獲取工作狀態,主要包括使能及狀態操作,具體指令如下所示:
??讀指令,用于讀取數據。讀取數據支持在標準模式下、雙線模式下、四線模式下進行操作,具體的指令如下所示:
??ID和安全指令,用于讀取或配置一些特定操作,如獲取制造商編號以及設備編號等,具體的指令如下所示:
??編程和擦除指令,用以實現對扇區、塊以及整片的擦除以及指定的區域的編程等功能,具體的指令如下所示:
??對于BY25QXXX系列NOR FLASH存儲器,不管是讀寫操作還是其它操作在指令階段都是標準的SPI操作方式。
2、驅動設計與實現
??我們已經大致了解了BY25QXXX系列NOR FLASH存儲器操作方式及指令,接下來我們就來考慮實現以間接模式訪問它的驅動問題。
2.1、對象定義
??我們依舊是基于對象的模式來考慮這一問題,所以我們首先需要定義BY25QXXX系列NOR FLASH存儲器的對象類型。我們先來分析一下,作為對象BY25QXXX系列NOR FLASH存儲器都有哪些必要的屬性和操作。
??先說一說屬性問題,對于BY25QXXX系列NOR FLASH存儲器對象來說可以標識器身份和狀態的無非是ID和狀態寄存器,而ID有包括制造商ID、設備ID、JEDEC ID和uniqueID等,我們可以將其作為對象的屬性以標識不同的對象,當這些屬性并不是必須的。
??再來看一看操作問題,對于BY25QXXX系列NOR FLASH存儲器,它的操作指令有很多,但我們通過分析他們的時序不難發現所有的指令都可歸納為:命令發送、數據發送、數據接收等內容。不同的指令包括不同的組合,所以我們只需要將命令發送、數據發送、數據接收作為對象的操作,通過組合就可以實現全部的操作指令。還有一點需要考慮的是,在寫??數據或者擦除是需要等待是否完成,所以我們額外添加一個就緒檢測操作。通過上述分析我們可以定義BY25QXXX系列NOR FLASH存儲器對象類型如下:
/*定義BY25QXX對象類型 */
typedef struct BY25QObject{
uint8_t status[3];
uint8_t mfrID[2];
uint8_t jedecID[3];
uint8_t uniqueID[8];
void (*Write)(BY25QCommandConfigType config,uint8_t *wDatas); //寫數據操作指針
void (*Read)(BY25QCommandConfigType config,uint8_t *rDatas); //讀數據操作指針
void (*Command)(BY25QCommandConfigType config); //下發無數據操作命令
void (*Ready)(void); //檢查Flash是否處于BUSY
}BY25QObjectType;
??定義了對象類型后,我們便可以基于它得到對象變量,但對象變量必須實例化才可使用,我們我們來考慮BY25QXXX系列NOR FLASH存儲器對象的初始化問題。
/*實現BY25Q初始化配置*/
void BY25QInitialization(BY25QObjectType *by250q, /*BY250Q存儲器對象*/
BY25QWriteType write, /*寫函數指針*/
BY25QReadType read, /*讀函數指針*/
BY25QCommandType command, /*命令下發函數指針*/
BY25QReadyType ready /*就緒檢測函數指針*/
)
{
if((by250q==NULL)||(write==NULL)||(read==NULL)||(command==NULL)||(ready==NULL))
{
return;
}
by250q->Write=write;
by250q->Read=read;
by250q->Command=command;
by250q->Ready=ready;
GetBy25qxxID(by250q);
ReadStatusRegister(by250q);
}
??在這一初始化函數中,我們主要是配置了用于讀寫操作的函數指針,并讀取了設備的各類ID以及狀態寄存器的值。如果有其他需要在初始化是完成的工作也可以在此函數中實現。
2.2、對象操作
??我們得到了對象類型,而且也可以為對象變量實現初始化配置。接下來我們看一看對象需要實現哪些操作。由于BY25QXXX系列NOR FLASH存儲器的操作指令有很多,我們這里只是先幾個必要的操作函數。
2.2.1、寫使能
??寫使能操作需要在寫入數據之前完成,不僅是寫存儲區域是需要操作,在寫寄存器之前也需要先進行此操作。該操作只有一個0x06指令寫入,存儲器就會自己完成相應操作,并反應到狀態寄存器上。其操作時序圖如下:
??此操作只占用標準SPI接口,事實上全部的命令都是如此。根據前述的描述及時序圖,我們可以編寫“寫使能”的操作函數如下:
/* 寫使能 */
static void WriteEnable(BY25QObjectType *by250q)
{
BY25QCommandConfigType config;
config.Instruction=WRITE_ENABLE; // 寫使能指令0x06
config.DummyCycles=0; // 空指令周期數
config.AddressMode=0;
config.Address=0;
config.DataMode=0;
config.NbData=0;
by250q->Command(config);
}
2.2.2、讀取數據
??從BY25QXXX系列NOR FLASH存儲器讀取數據是必不可少的操作,而且有多個操作指令,這里我們實現Quad快速讀指令。讀取數據時,發送指令和地址都使用單線操作,獲取數據則使用四線操作。其時序圖如下:
??根據前述的描述及時序圖,我們可以編寫“讀取數據”的操作函數如下:
/*讀取數據*/
static void QuadFastRead(BY25QObjectType *by250q,uint32_t readAddress,uint8_t *readBuffer,uint32_t readSize)
{
BY25QCommandConfigType config;
config.Instruction=0xEB; // 讀ID指令0xEB
config.DummyCycles=6; // 空指令周期數
config.AddressMode=3;
config.Address=readAddress;
config.DataMode=3;
config.NbData=readSize;
by250q->Read(config,readBuffer);
}
2.2.3、擦除數據
??擦除和編程就其本質是一樣的。對于BY25QXXX系列NOR FLASH存儲器,其擦除指令有扇區擦除、塊擦除和整片擦除四種,我們這里實現常用的扇區擦除。只需要發送擦除指令和扇區首地址即可。其操作時序圖如下:
??根據前述的描述及時序圖,我們可以編寫“擦除數據”的操作函數如下:
/*擦除指定的扇區,扇區大小4KB*/
static void SectorErase(BY25QObjectType *by250q,uint32_t eraseAddress)
{
BY25QCommandConfigType config;
config.Instruction=0x20; // 讀ID指令0x90
config.DummyCycles=0; // 空指令周期數
config.AddressMode=1;
config.Address=eraseAddress;
config.DataMode=0;
config.NbData=0;
by250q->Command(config);
}
2.2.4、編程數據
??向存儲器中寫數據又稱之為編程數據,而BY25QXXX系列NOR FLASH存儲器有三種編程指令:頁編程、Quad頁編程以及快速頁編程。這里我們實現Quad頁編程。在編程時,發送指令和地址采用單線模式,發送數據則采用四線模式。其指令操作時序圖如下:
??根據前述的描述及時序圖,我們可以編寫“編程數據”的操作函數如下:
/*寫數據*/
static void QuadPageProgram(BY25QObjectType *by250q,uint32_t writeAddress,uint8_t *writeBuffer,uint32_t writeSize)
{
BY25QCommandConfigType config;
config.Instruction=0x32; // 讀ID指令0x32
config.DummyCycles=0; // 空指令周期數
config.AddressMode=1;
config.Address=writeAddress;
config.DataMode=3;
config.NbData=writeSize;
by250q->Write(config,writeBuffer);
}
3、驅動的使用
??前述我們已經完成了BY25QXXX系列NOR FLASH存儲器驅動的設計與實現。接下來我們需要具體使用這一驅動程序來操作BY25QXXX系列NOR FLASH存儲器,以便驗證驅動程序的正確性。
3.1、聲明并初始化對象
??我們先聲明一個BY25QXXX系列NOR FLASH存儲器對象變量,然后的操作都是基于這一對象變量來進行的。
BY25QObjectType by250q;
??如我們前面所述,對象變量必須要初始化才能使用。所以我們先來使用前面定義的初始化函數BY25QInitialization對這個對象變量進行初始化。這個初始化函數擁有多個輸入變量如下:
BY25QObjectType *by250q, /*BY250Q存儲器對象*/
BY25QWriteType write, /*寫函數指針*/
BY25QReadType read, /*讀函數指針*/
BY25QCommandType command, /*命令下發函數指針*/
BY25QReadyType ready /*就緒檢測函數指針*/
??在這些參數中,第一個參數就是我們要初始化的隊形變量。后面的4個參數則是需要定義的操作函數指針。與具體的應用有關,需要我們在特定的應用場景中定義并作為參數傳遞給對象變量初始化函數。在這里,我們使用的是STM32H750的硬件平臺和ST的HAL庫函數,具體的實現如下:
/*讀操作*/
static void ReadActionForBY25Q(BY25QCommandConfigType config,uint8_t *readBuffer)
{
QSPI_CommandTypeDef sCommand;
uint32_t addressMode[4]={QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINES,QSPI_ADDRESS_4_LINES};
uint32_t dataMode[4]={QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINES,QSPI_DATA_4_LINES};
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressSize = QSPI_ADDRESS_24_BITS;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
sCommand.Instruction = config.Instruction;
sCommand.DummyCycles= config.DummyCycles;
sCommand.AddressMode = addressMode[config.AddressMode];
sCommand.Address = config.Address;
sCommand.DataMode = dataMode[config.DataMode];
sCommand.NbData = config.NbData;
if (HAL_QSPI_Command(&hqspi, &sCommand,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
if (HAL_QSPI_Receive(&hqspi, readBuffer,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
/*寫操作*/
static void WriteActionForBY25Q(BY25QCommandConfigType config,uint8_t *writeBuffer)
{
QSPI_CommandTypeDef sCommand;
uint32_t addressMode[4]={QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINES,QSPI_ADDRESS_4_LINES};
uint32_t dataMode[4]={QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINES,QSPI_DATA_4_LINES};
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressSize = QSPI_ADDRESS_24_BITS;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
sCommand.Instruction = config.Instruction;
sCommand.DummyCycles= config.DummyCycles;
sCommand.AddressMode = addressMode[config.AddressMode];
sCommand.Address = config.Address;
sCommand.DataMode = dataMode[config.DataMode];
sCommand.NbData = config.NbData;
if (HAL_QSPI_Command(&hqspi, &sCommand,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
if (HAL_QSPI_Transmit(&hqspi, writeBuffer,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
/*配置命令*/
static void ConfigCommandForBY25Q(BY25QCommandConfigType config)
{
QSPI_CommandTypeDef sCommand;
uint32_t addressMode[4]={QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINES,QSPI_ADDRESS_4_LINES};
uint32_t dataMode[4]={QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINES,QSPI_DATA_4_LINES};
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressSize = QSPI_ADDRESS_24_BITS;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
sCommand.Instruction = config.Instruction;
sCommand.DummyCycles= config.DummyCycles;
sCommand.AddressMode = addressMode[config.AddressMode];
sCommand.Address = config.Address;
sCommand.DataMode = dataMode[config.DataMode];
sCommand.NbData = config.NbData;
if (HAL_QSPI_Command(&hqspi, &sCommand,HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
/* 檢測存儲器是否就緒 */
static void QSPI_AutoPollingMemReady(void)
{
QSPI_CommandTypeDef sCommand;
QSPI_AutoPollingTypeDef sConfig;
/* Configure automatic polling mode to wait for memory ready ------ */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressSize = QSPI_ADDRESS_24_BITS;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
sCommand.Instruction = 0x05;
sCommand.AddressMode = QSPI_ADDRESS_NONE;
sCommand.DataMode = QSPI_DATA_1_LINE;
sCommand.DummyCycles = 0;
sConfig.Match = 0x00;
sConfig.Mask = 0x01;
sConfig.MatchMode = QSPI_MATCH_MODE_AND;
sConfig.StatusBytesSize = 1;
sConfig.Interval = 0x10;
sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
Error_Handler();
}
}
??有了這些參數后,我們就可以使用這些參數來初始化BY25QXXX系列NOR FLASH存儲器的對象變量了。
/*實現BY25Q初始化配置*/
BY25QInitialization(&by250q, /*BY250Q存儲器對象*/
WriteActionForBY25Q, /*寫函數指針*/
ReadActionForBY25Q, /*讀函數指針*/
ConfigCommandForBY25Q, /*命令下發函數指針*/
QSPI_AutoPollingMemReady /*就緒檢測函數指針*/
);
3.2、基于對象進行操作
??我們設計這樣一個操作場景,我們更具一個變量的值來讀寫BY25QXXX系列NOR FLASH存儲器。當我們為指定的變量賦值為1時,我們從指定的地址讀取一定數量的數據出來,并復位變量。當我們為指定的變量賦值為2時,我們擦除指定的地址所在的扇區,然后在指定地址寫入一定數量的數據,并復位變量。
/*程序存儲器測試*/
void FlashOperation(void)
{
switch(swBY25Q)
{
case 1:
{
ReadDataFromBy25q(&by250q,QSPI_MEM_ADDRESS,readBuffer,READ_LENGTH);
swBY25Q=0;
break;
}
case 2:
{
pTimes++;
for(int i=0;iq(&by250q,QSPI_MEM_ADDRESS);
WriteDataToBy25q(&by250q,QSPI_MEM_ADDRESS,writeBuffer,256);
WriteDataToBy25q(&by250q,QSPI_MEM_ADDRESS+256,&writeBuffer[256],WRITE_LENGTH-256);
swBY25Q=0;
break;
}
default:
{
swBY25Q=0;
break;
}
}
}
??我們同過在先修改變量swBY25Q的值,先修改為2以寫入100個字節的數據;然后將變量賦值為1以讀取先前寫入的100個數據用以驗證是否正確。測試結果發現,寫入的數據和讀出的數據是完全一致的,說明的們設計的驅動程序是正確的。
4、應用總結
??在這一篇中,我們設計并實現了BY25QXXX系列NOR FLASH存儲器在間接操作模式下的驅動程序。后續我們也同過簡單的讀寫操作實例驗證了驅動成需的正確性。至此,BY25QXXX系列NOR FLASH存儲器驅動程序的設計工作就完成了。
??在使用BY25QXXX系列NOR FLASH存儲器驅動程序時需要注意,狀態寄存的QE位非常重要。在使用Quad SPI時,該位必須置“1”,否則以Quad SPI模式寫數據是不會成功的。而在SPI和Dual SPI方式時,該位最好置“0”,這樣WP和HOLD操作才能有效。
??在使用BY25QXXX系列NOR FLASH存儲器驅動程序時需要注意,在使用QSPI接口時,需要盡可能將對應的GPIO速度配置的快一點,否則可能會不能操作。
評論
查看更多