天天看點

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  在有一些應用中,我們可能需要大一些容量的存儲單元,而實作的形式多種多樣,在這一篇中我們将來讨論怎麼使用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的連接配接圖來展示其連線方式。

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  QSPI接口可以在以下三種模式下工作:間接模式,使用 QSPI 寄存器執行全部操作;狀态輪詢模式,周期性讀取外部 Flash 狀态寄存器,而且标志位置 1 時會産生中斷(如擦除或燒寫完成,會産生中斷);記憶體映射模式,外部 Flash 映射到微控制器位址空間,進而系統将其視作内部存儲器。我們在這裡考慮BY25QXXX系列NOR FLASH存儲器的驅動問題其實就是以間接模式通路的情況。

1.2、BY25Q基本特點

  BY25QXXX系列NOR FLASH存儲器支援标準SPI模式、雙線SPI模式、四線SPI模式。其封裝級引腳定義如下:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  這些引腳中,片選信号CS和始終信号SCLK在各種模式下是沒有差別的。而SO(IO1)引腳在标準SPI模式下用作串行輸出,在雙線模式和四線模式下則是IO1。SI(IO0)引腳在标準SPI模式下用作串行輸入,在雙線模式和四線模式下則是IO0。WP(IO2)引腳在标準和雙線模式下為寫保護,在四線模式下為IO2。HOLD(IO3)引腳在标準和雙線模式下為HOLD,在四線模式下為IO3。

1.3、操作指令

  BY25QXXX系列NOR FLASH存儲器在間接通路模式下,主要有四類指令:配置與狀态指令、讀指令、ID和安全指令、程式設計和擦除指令。。

配置與狀态指令,用于配置操作方式及擷取工作狀态,主要包括使能及狀态操作,具體指令如下所示:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  讀指令,用于讀取資料。讀取資料支援在标準模式下、雙線模式下、四線模式下進行操作,具體的指令如下所示:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  ID和安全指令,用于讀取或配置一些特定操作,如擷取制造商編号以及裝置編号等,具體的指令如下所示:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  程式設計和擦除指令,用以實作對扇區、塊以及整片的擦除以及指定的區域的程式設計等功能,具體的指令如下所示:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  對于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指令寫入,存儲器就會自己完成相應操作,并反應到狀态寄存器上。其操作時序圖如下:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  此操作隻占用标準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快速讀指令。讀取資料時,發送指令和位址都使用單線操作,擷取資料則使用四線操作。其時序圖如下:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  根據前述的描述及時序圖,我們可以編寫“讀取資料”的操作函數如下:

/*讀取資料*/
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存儲器,其擦除指令有扇區擦除、塊擦除和整片擦除四種,我們這裡實作常用的扇區擦除。隻需要發送擦除指令和扇區首位址即可。其操作時序圖如下:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  根據前述的描述及時序圖,我們可以編寫“擦除資料”的操作函數如下:

/*擦除指定的扇區,扇區大小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頁程式設計。在程式設計時,發送指令和位址采用單線模式,發送資料則采用四線模式。其指令操作時序圖如下:

外設驅動庫開發筆記49:BY25Qxx存儲器驅動

  根據前述的描述及時序圖,我們可以編寫“程式設計資料”的操作函數如下:

/*寫資料*/
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;i<WRITE_LENGTH;i++)
            {
                writeBuffer[i]=i+pTimes;
            }
            
            EraseSectorForBy25q(&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速度配置的快一點,否則可能會不能操作。

歡迎關注:

當然,如果您想及時了解我的部落格更新,不妨點選下方的【關注我】按鈕。