在有一些應(yīng)用中,我們可能需要大一些容量的存儲單元,而實(shí)現(xiàn)的形式多種多樣,在這一篇中我們將來討論怎么使用BY25QXXX系列NOR FLASH存儲器的問題。
1、功能概述
??在開始實(shí)現(xiàn)BY25QXXX系列NOR FLASH存儲器的驅(qū)動之前,我們需要先了解一下它的基本情況。
1.1、QSPI接口
??QSPI接口,是QueuedSPI的縮寫。和之前談到的SPI一樣都是出自Motorola。QSPI在SPI基礎(chǔ)上做了一些增強(qiáng),且向下兼容SPI。QSPI相對SPI最顯著的差異就是增加了發(fā)送接收數(shù)據(jù)隊(duì)列,Queued的稱呼就是這么來的。這樣做的好處就是,無需每次數(shù)據(jù)傳輸都需要CPU參與,可以降低CPU的資源占用。
??QSPI采用6先模式,同樣也可以按Standard SPI、Dual SPI方式工作。我們引用STM32H7上一張QSPI接口與Flash的連接圖來展示其連線方式。
??QSPI接口可以在以下三種模式下工作:間接模式,使用 QSPI 寄存器執(zhí)行全部操作;狀態(tài)輪詢模式,周期性讀取外部 Flash 狀態(tài)寄存器,而且標(biāo)志位置 1 時會產(chǎn)生中斷(如擦除或燒寫完成,會產(chǎn)生中斷);內(nèi)存映射模式,外部 Flash 映射到微控制器地址空間,從而系統(tǒng)將其視作內(nèi)部存儲器。我們在這里考慮BY25QXXX系列NOR FLASH存儲器的驅(qū)動問題其實(shí)就是以間接模式訪問的情況。
1.2、BY25Q基本特點(diǎn)
??BY25QXXX系列NOR FLASH存儲器支持標(biāo)準(zhǔn)SPI模式、雙線SPI模式、四線SPI模式。其封裝級引腳定義如下:
??這些引腳中,片選信號CS和始終信號SCLK在各種模式下是沒有區(qū)別的。而SO(IO1)引腳在標(biāo)準(zhǔn)SPI模式下用作串行輸出,在雙線模式和四線模式下則是IO1。SI(IO0)引腳在標(biāo)準(zhǔn)SPI模式下用作串行輸入,在雙線模式和四線模式下則是IO0。WP(IO2)引腳在標(biāo)準(zhǔn)和雙線模式下為寫保護(hù),在四線模式下為IO2。HOLD(IO3)引腳在標(biāo)準(zhǔn)和雙線模式下為HOLD,在四線模式下為IO3。
1.3、操作指令
??BY25QXXX系列NOR FLASH存儲器在間接訪問模式下,主要有四類指令:配置與狀態(tài)指令、讀指令、ID和安全指令、編程和擦除指令。
配置與狀態(tài)指令,用于配置操作方式及獲取工作狀態(tài),主要包括使能及狀態(tài)操作,具體指令如下所示:
??讀指令,用于讀取數(shù)據(jù)。讀取數(shù)據(jù)支持在標(biāo)準(zhǔn)模式下、雙線模式下、四線模式下進(jìn)行操作,具體的指令如下所示:
??ID和安全指令,用于讀取或配置一些特定操作,如獲取制造商編號以及設(shè)備編號等,具體的指令如下所示:
??編程和擦除指令,用以實(shí)現(xiàn)對扇區(qū)、塊以及整片的擦除以及指定的區(qū)域的編程等功能,具體的指令如下所示:
??對于BY25QXXX系列NOR FLASH存儲器,不管是讀寫操作還是其它操作在指令階段都是標(biāo)準(zhǔn)的SPI操作方式。
2、驅(qū)動設(shè)計(jì)與實(shí)現(xiàn)
??我們已經(jīng)大致了解了BY25QXXX系列NOR FLASH存儲器操作方式及指令,接下來我們就來考慮實(shí)現(xiàn)以間接模式訪問它的驅(qū)動問題。
2.1、對象定義
??我們依舊是基于對象的模式來考慮這一問題,所以我們首先需要定義BY25QXXX系列NOR FLASH存儲器的對象類型。我們先來分析一下,作為對象BY25QXXX系列NOR FLASH存儲器都有哪些必要的屬性和操作。
??先說一說屬性問題,對于BY25QXXX系列NOR FLASH存儲器對象來說可以標(biāo)識器身份和狀態(tài)的無非是ID和狀態(tài)寄存器,而ID有包括制造商ID、設(shè)備ID、JEDEC ID和uniqueID等,我們可以將其作為對象的屬性以標(biāo)識不同的對象,當(dāng)這些屬性并不是必須的。
??再來看一看操作問題,對于BY25QXXX系列NOR FLASH存儲器,它的操作指令有很多,但我們通過分析他們的時序不難發(fā)現(xiàn)所有的指令都可歸納為:命令發(fā)送、數(shù)據(jù)發(fā)送、數(shù)據(jù)接收等內(nèi)容。不同的指令包括不同的組合,所以我們只需要將命令發(fā)送、數(shù)據(jù)發(fā)送、數(shù)據(jù)接收作為對象的操作,通過組合就可以實(shí)現(xiàn)全部的操作指令。還有一點(diǎn)需要考慮的是,在寫??數(shù)據(jù)或者擦除是需要等待是否完成,所以我們額外添加一個就緒檢測操作。通過上述分析我們可以定義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); //寫數(shù)據(jù)操作指針
void (*Read)(BY25QCommandConfigType config,uint8_t *rDatas); //讀數(shù)據(jù)操作指針
void (*Command)(BY25QCommandConfigType config); //下發(fā)無數(shù)據(jù)操作命令
void (*Ready)(void); //檢查Flash是否處于BUSY
}BY25QObjectType;
??定義了對象類型后,我們便可以基于它得到對象變量,但對象變量必須實(shí)例化才可使用,我們我們來考慮BY25QXXX系列NOR FLASH存儲器對象的初始化問題。
/*實(shí)現(xiàn)BY25Q初始化配置*/
void BY25QInitialization(BY25QObjectType *by250q, /*BY250Q存儲器對象*/
BY25QWriteType write, /*寫函數(shù)指針*/
BY25QReadType read, /*讀函數(shù)指針*/
BY25QCommandType command, /*命令下發(fā)函數(shù)指針*/
BY25QReadyType ready /*就緒檢測函數(shù)指針*/
)
{
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);
}
??在這一初始化函數(shù)中,我們主要是配置了用于讀寫操作的函數(shù)指針,并讀取了設(shè)備的各類ID以及狀態(tài)寄存器的值。如果有其他需要在初始化是完成的工作也可以在此函數(shù)中實(shí)現(xiàn)。
2.2、對象操作
??我們得到了對象類型,而且也可以為對象變量實(shí)現(xiàn)初始化配置。接下來我們看一看對象需要實(shí)現(xiàn)哪些操作。由于BY25QXXX系列NOR FLASH存儲器的操作指令有很多,我們這里只是先幾個必要的操作函數(shù)。
2.2.1、寫使能
??寫使能操作需要在寫入數(shù)據(jù)之前完成,不僅是寫存儲區(qū)域是需要操作,在寫寄存器之前也需要先進(jìn)行此操作。該操作只有一個0x06指令寫入,存儲器就會自己完成相應(yīng)操作,并反應(yīng)到狀態(tài)寄存器上。其操作時序圖如下:
??此操作只占用標(biāo)準(zhǔn)SPI接口,事實(shí)上全部的命令都是如此。根據(jù)前述的描述及時序圖,我們可以編寫“寫使能”的操作函數(shù)如下:
/* 寫使能 */
static void WriteEnable(BY25QObjectType *by250q)
{
BY25QCommandConfigType config;
config.Instruction=WRITE_ENABLE; // 寫使能指令0x06
config.DummyCycles=0; // 空指令周期數(shù)
config.AddressMode=0;
config.Address=0;
config.DataMode=0;
config.NbData=0;
by250q->Command(config);
}
2.2.2、讀取數(shù)據(jù)
??從BY25QXXX系列NOR FLASH存儲器讀取數(shù)據(jù)是必不可少的操作,而且有多個操作指令,這里我們實(shí)現(xiàn)Quad快速讀指令。讀取數(shù)據(jù)時,發(fā)送指令和地址都使用單線操作,獲取數(shù)據(jù)則使用四線操作。其時序圖如下:
??根據(jù)前述的描述及時序圖,我們可以編寫“讀取數(shù)據(jù)”的操作函數(shù)如下:
/*讀取數(shù)據(jù)*/
static void QuadFastRead(BY25QObjectType *by250q,uint32_t readAddress,uint8_t *readBuffer,uint32_t readSize)
{
BY25QCommandConfigType config;
config.Instruction=0xEB; // 讀ID指令0xEB
config.DummyCycles=6; // 空指令周期數(shù)
config.AddressMode=3;
config.Address=readAddress;
config.DataMode=3;
config.NbData=readSize;
by250q->Read(config,readBuffer);
}
2.2.3、擦除數(shù)據(jù)
??擦除和編程就其本質(zhì)是一樣的。對于BY25QXXX系列NOR FLASH存儲器,其擦除指令有扇區(qū)擦除、塊擦除和整片擦除四種,我們這里實(shí)現(xiàn)常用的扇區(qū)擦除。只需要發(fā)送擦除指令和扇區(qū)首地址即可。其操作時序圖如下:
??根據(jù)前述的描述及時序圖,我們可以編寫“擦除數(shù)據(jù)”的操作函數(shù)如下:
/*擦除指定的扇區(qū),扇區(qū)大小4KB*/
static void SectorErase(BY25QObjectType *by250q,uint32_t eraseAddress)
{
BY25QCommandConfigType config;
config.Instruction=0x20; // 讀ID指令0x90
config.DummyCycles=0; // 空指令周期數(shù)
config.AddressMode=1;
config.Address=eraseAddress;
config.DataMode=0;
config.NbData=0;
by250q->Command(config);
}
2.2.4、編程數(shù)據(jù)
??向存儲器中寫數(shù)據(jù)又稱之為編程數(shù)據(jù),而BY25QXXX系列NOR FLASH存儲器有三種編程指令:頁編程、Quad頁編程以及快速頁編程。這里我們實(shí)現(xiàn)Quad頁編程。在編程時,發(fā)送指令和地址采用單線模式,發(fā)送數(shù)據(jù)則采用四線模式。其指令操作時序圖如下:
??根據(jù)前述的描述及時序圖,我們可以編寫“編程數(shù)據(jù)”的操作函數(shù)如下:
/*寫數(shù)據(jù)*/
static void QuadPageProgram(BY25QObjectType *by250q,uint32_t writeAddress,uint8_t *writeBuffer,uint32_t writeSize)
{
BY25QCommandConfigType config;
config.Instruction=0x32; // 讀ID指令0x32
config.DummyCycles=0; // 空指令周期數(shù)
config.AddressMode=1;
config.Address=writeAddress;
config.DataMode=3;
config.NbData=writeSize;
by250q->Write(config,writeBuffer);
}
3、驅(qū)動的使用
??前述我們已經(jīng)完成了BY25QXXX系列NOR FLASH存儲器驅(qū)動的設(shè)計(jì)與實(shí)現(xiàn)。接下來我們需要具體使用這一驅(qū)動程序來操作BY25QXXX系列NOR FLASH存儲器,以便驗(yàn)證驅(qū)動程序的正確性。
3.1、聲明并初始化對象
??我們先聲明一個BY25QXXX系列NOR FLASH存儲器對象變量,然后的操作都是基于這一對象變量來進(jìn)行的。
BY25QObjectType by250q;
??如我們前面所述,對象變量必須要初始化才能使用。所以我們先來使用前面定義的初始化函數(shù)BY25QInitialization對這個對象變量進(jìn)行初始化。這個初始化函數(shù)擁有多個輸入變量如下:
BY25QObjectType *by250q, /*BY250Q存儲器對象*/
BY25QWriteType write, /*寫函數(shù)指針*/
BY25QReadType read, /*讀函數(shù)指針*/
BY25QCommandType command, /*命令下發(fā)函數(shù)指針*/
BY25QReadyType ready /*就緒檢測函數(shù)指針*/
??在這些參數(shù)中,第一個參數(shù)就是我們要初始化的隊(duì)形變量。后面的4個參數(shù)則是需要定義的操作函數(shù)指針。與具體的應(yīng)用有關(guān),需要我們在特定的應(yīng)用場景中定義并作為參數(shù)傳遞給對象變量初始化函數(shù)。在這里,我們使用的是STM32H750的硬件平臺和ST的HAL庫函數(shù),具體的實(shí)現(xiàn)如下:
/*讀操作*/
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();
}
}
??有了這些參數(shù)后,我們就可以使用這些參數(shù)來初始化BY25QXXX系列NOR FLASH存儲器的對象變量了。
/*實(shí)現(xiàn)BY25Q初始化配置*/
BY25QInitialization(&by250q, /*BY250Q存儲器對象*/
WriteActionForBY25Q, /*寫函數(shù)指針*/
ReadActionForBY25Q, /*讀函數(shù)指針*/
ConfigCommandForBY25Q, /*命令下發(fā)函數(shù)指針*/
QSPI_AutoPollingMemReady /*就緒檢測函數(shù)指針*/
);
3.2、基于對象進(jìn)行操作
??我們設(shè)計(jì)這樣一個操作場景,我們更具一個變量的值來讀寫B(tài)Y25QXXX系列NOR FLASH存儲器。當(dāng)我們?yōu)橹付ǖ淖兞抠x值為1時,我們從指定的地址讀取一定數(shù)量的數(shù)據(jù)出來,并復(fù)位變量。當(dāng)我們?yōu)橹付ǖ淖兞抠x值為2時,我們擦除指定的地址所在的扇區(qū),然后在指定地址寫入一定數(shù)量的數(shù)據(jù),并復(fù)位變量。
/*程序存儲器測試*/
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個字節(jié)的數(shù)據(jù);然后將變量賦值為1以讀取先前寫入的100個數(shù)據(jù)用以驗(yàn)證是否正確。測試結(jié)果發(fā)現(xiàn),寫入的數(shù)據(jù)和讀出的數(shù)據(jù)是完全一致的,說明的們設(shè)計(jì)的驅(qū)動程序是正確的。
4、應(yīng)用總結(jié)
??在這一篇中,我們設(shè)計(jì)并實(shí)現(xiàn)了BY25QXXX系列NOR FLASH存儲器在間接操作模式下的驅(qū)動程序。后續(xù)我們也同過簡單的讀寫操作實(shí)例驗(yàn)證了驅(qū)動成需的正確性。至此,BY25QXXX系列NOR FLASH存儲器驅(qū)動程序的設(shè)計(jì)工作就完成了。
??在使用BY25QXXX系列NOR FLASH存儲器驅(qū)動程序時需要注意,狀態(tài)寄存的QE位非常重要。在使用Quad SPI時,該位必須置“1”,否則以Quad SPI模式寫數(shù)據(jù)是不會成功的。而在SPI和Dual SPI方式時,該位最好置“0”,這樣WP和HOLD操作才能有效。
??在使用BY25QXXX系列NOR FLASH存儲器驅(qū)動程序時需要注意,在使用QSPI接口時,需要盡可能將對應(yīng)的GPIO速度配置的快一點(diǎn),否則可能會不能操作。
評論
查看更多