1. 程式人生 > 實用技巧 >【STM32H7教程】第79章 STM32H7的QSPI匯流排應用之驅動W25QXX(支援查詢和MDMA)

【STM32H7教程】第79章 STM32H7的QSPI匯流排應用之驅動W25QXX(支援查詢和MDMA)

完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第79章 STM32H7的QSPI匯流排應用之驅動W25QXX(支援查詢和MDMA)

本章節為大家講解標準QSPI接線方式驅動W25QXX,實現了查詢和MDMA兩種方式。

79.1 初學者重要提示

79.2 W25QXX硬體設計

79.3 W25QXX關鍵知識點整理(重要)

79.4 W25QXX驅動設計

79.5 W25QXX板級支援包(bsp_spi_flash.c)

79.6 W25QXX驅動移植和使用

79.7 使用例程設計框架

79.8 實驗例程說明(MDK)

79.9 實驗例程說明(IAR)

79.10 總結

79.1 初學者重要提示

1、 學習本章節前,務必優先學習第79章。

2、 W25Q256JV屬於NOR型Flash儲存晶片。

3、 W25Q256JV手冊下載地址:連結 (這是一個超連結),當前章節配套例子的Doc檔案件裡面也有存放。

4、 本章第3小節整理的知識點比較重要,務必要了解下,特別是頁程式設計和頁回捲。

5、 對QSPI Flash W25Q256JV的不同接線方式(1線,2線或者4線,這裡的線是指的資料線),程式設計命令是不同的。

6、 W25Q256JV最高支援133MHz。

7、 STM32H7驅動QSPI Flash的4線DMA模式,讀速度48MB/S左右。

http://www.armbbs.cn/forum.php?mod=viewthread&tid=91616

8、 記憶體對映模式下,最後一個位元組無法正常讀取的解決辦法:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96726

9、 本章配套例子的DMA是採用效能最強的MDMA。

79.2 W25QXX硬體設計

STM32H7驅動W25Q256JV的硬體設計如下:

關於這個原理圖,要了解到以下幾個知識:

  • V7開發板實際外接的晶片是W25Q256JV。
  • CS片選最好接上拉電阻,防止意外操作。
  • W25Q256的WP引腳用於防寫,低電平有效性,當前是將其作為4方式的IO2。
  • HOLD引腳也是低電平有效,當前是將其接到高電平。此引腳的作用是CS片選低電平時,DO引腳輸出高阻,忽略CLK和DI引腳上的訊號。當前是將其作為4線方式的IO3。

79.3 W25QXX關鍵知識點整理(重要)

驅動W25QXX前要先了解下這個晶片的相關資訊。

79.3.1 W25QXX基礎資訊

  • W25Q256JV是32MB(256Mbit)。
  • W25Q256JV支援標準SPI(單線SPI),用到引腳CLK、CS,DI和DO引腳。

支援兩線SPI,用到引腳CLK、CS、IO0、IO1

支援四線SPI,用到引腳CLK、CS、IO0、IO1,IO2、IO3

(注:這裡幾線的意思是幾個資料線)。

  • W25Q256JV支援的最高時鐘是133MHz。
  • 每個扇區最少支援10萬次擦寫,可以儲存20年資料。
  • 頁大小是256位元組,支援頁程式設計,也就是一次編寫256個位元組,也可以一個一個編寫。
  • 支援4KB為單位的扇區擦除,也可以32KB或者64KB為單位的擦除。

整體框圖如下:

W25Q256JV:

  • 有512個Block,每個Block大小64KB。
  • 每個Block有16個Sector,每個Sector大小4KB。
  • 每個Sector有16個Page,每個Page大小是256位元組。

79.3.2 W25QXX命令

使用W25Q256的接線方式不同,使用的命令也有所不同,使用的時候務必要注意,當前我們使用的QSPI,即4線SPI,並且用的4位元組地址模式,使用的命令如下:

當前主要用到如下幾個命令:

#define WRITE_ENABLE_CMD      0x06         /* 寫使能指令 */  
#define READ_ID_CMD2          0x9F         /* 讀取ID命令 */  
#define READ_STATUS_REG_CMD   0x05         /* 讀取狀態命令 */ 
#define BULK_ERASE_CMD        0xC7         /* 整個晶片擦除命令 */ 
#define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇區擦除指令, 4KB */
#define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4線快速寫入命令 */
#define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4線快速讀取命令 */

79.3.3 W25QXX頁程式設計和頁回捲

SPI Flash僅支援頁程式設計(頁大小256位元組),所有其它大批量資料的寫入都是以頁為單位。這裡注意所說的頁程式設計含義,頁程式設計分為以下三步(虛擬碼):

bsp_spiWrite1(0x02);                               ----------第1步傳送頁程式設計命令        
bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步傳送地址   
bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
bsp_spiWrite1(_uiWriteAddr & 0xFF);               

    for (i = 0; i < _usSize; i++)
    {
        bsp_spiWrite1(*_pBuf++);   ----------第3步寫資料,此時就可以連續寫入資料了,
                                             不需要再重新設定地址,地址會自增。這樣可以大大加快寫入速度。   
    }

頁程式設計的含義恰恰就體現在第3步了,如果使用者設定的“起始地址+資料長度”所確定的地址範圍超過了此起始地址所在的頁,地址自增不會超過頁範圍,而是重新回到了此頁的首地進行編寫。這一點要特別的注意。如果使用者不需要使用地址自增效果,那麼直接指定地址進行編寫即可。可以任意指定地址進行編寫,編寫前一定要進行擦除。

比如下面就是頁內操作(使用前已經進行了扇區擦除,每次擦除最少擦除一個扇區4KB):

uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
  • 從250地址開始寫入10個位元組資料 PageWrite(tempbuf, 250, 10);(因為一旦寫入超過地址255,就會從0地址開始重新寫)。
  • 向地址20寫入1個位元組資料:PageWrite(&temp1, 20, 1);
  • 向地址30寫入1個位元組資料:PageWrite(&temp2, 30, 1);
  • 向地址510寫入1個位元組資料:PageWrite(&temp3, 510, 1) (這裡已經是寫到下一頁了)

下面是將從0地址到511地址讀取出來的512個位元組資料,一行32位元組。

79.3.4 W25QXX扇區擦除

SPI Flash的擦除支援扇區擦除(4KB),塊擦除(32KB或者64KB)以及整個晶片擦除。對於扇區擦除和塊擦除,使用的時候要注意一點,一般情況下,只需使用者給出扇區或者塊的首地址即可。

如果給的不是扇區或者塊的首地址也沒有關係的,只要此地址是在扇區或者塊的範圍內,此扇區或者塊也可以被正確擦除。不過建議使用時給首地址,方便管理。

79.3.5 W25QXX規格引數

這裡我們主要了解擦寫耗時和支援的時鐘速度,下面是擦寫時間引數:


  • 頁程式設計時間:典型值0.4ms,最大值3ms。
  • 扇區擦除時間(4KB):典型值50ms,最大值400ms。
  • 塊擦除時間(32KB):典型值120ms,最大值1600ms。
  • 塊擦除時間(64KB):典型值150ms,最大值2000ms。
  • 整個晶片擦除時間:典型值80s,最大值400s。

支援的速度引數如下:

可以看到最高支援的讀時鐘(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。

79.4 W25QXX驅動設計

W25QXX的程式驅動框架設計如下:

有了這個框圖,程式設計就比較好理解了。

79.4.1 第1步:QSPI匯流排配置

QSPI匯流排配置如下:

/*
*********************************************************************************************************
*    函 數 名: bsp_InitQSPI_W25Q256
*    功能說明: QSPI Flash硬體初始化,配置基本引數
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitQSPI_W25Q256(void)
{
    /* 復位QSPI */
    QSPIHandle.Instance = QUADSPI;
    if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 設定時鐘速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
    QSPIHandle.Init.ClockPrescaler  = 1;  
    
    /* 設定FIFO閥值,範圍1 - 32 */
    QSPIHandle.Init.FifoThreshold   = 32; 
    
    /* 
        QUADSPI在FLASH驅動訊號後過半個CLK週期才對FLASH驅動的資料取樣。
        在外部訊號延遲時,這有利於推遲資料取樣。
    */
    QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE; 
    
    /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
    //QSPI_FLASH_SIZE - 1; 需要擴大一倍,否則記憶體對映方位最後1個地址時,會異常。
    QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE; 
    
    /* 命令之間的CS片選至少保持2個時鐘週期的高電平 */
    QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
    
    /*
       MODE0: 表示片選訊號空閒期間,CLK時鐘訊號是低電平
       MODE3: 表示片選訊號空閒期間,CLK時鐘訊號是高電平
    */
    QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
    
    /* QSPI有兩個BANK,這裡使用的BANK1 */
    QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;
    
    /* V7開發板僅使用了BANK1,這裡是禁止雙BANK */
    QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;

    /* 初始化配置QSPI */
    if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    
}

QSPI這部分配置,要特別注意Flash大小的設定,這裡做了特別處理,本來是應該填入QSPI_FLASH_SIZE-1,而我們實際上填入的是QSPI_FLASH_SIZE,主要是因為記憶體對映模式下,最後一個位元組訪問有問題。

79.4.2 第2步:QSPI匯流排的查詢和MDMA方式設定

本章提供了QSPI Flash的查詢和MDMA兩種方式的例子,驅動的區別是呼叫的API不同,查詢方式呼叫的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。

79.4.3 第3步:W25QXX的讀取實現

注:這裡以查詢方式的API進行說明,DMA方式是一樣的。

W25QXX的讀取功能是傳送的4線快速讀取指令0xEC,設定任意地址都可以讀取資料,只要不超過晶片容量即可(如果採用的DMA方式,限制每次最大讀取65536位元組)。

/*
*********************************************************************************************************
*    函 數 名: QSPI_ReadBuffer
*    功能說明: 連續讀取若干位元組,位元組個數不能超出晶片容量。
*    形    參: _pBuf : 資料來源緩衝區。
*              _uiReadAddr :起始地址。
*              _usSize :資料個數, 可以大於PAGE_SIZE, 但是不能超出晶片總容量。
*    返 回 值: 無
*********************************************************************************************************
*/
void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
{
    
    QSPI_CommandTypeDef sCommand = {0};
    
    
    /* 基本配置 */
    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1線方式傳送指令 */
    sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 無交替位元組 */
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支援DDR */
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,資料輸出延遲 */
    sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次傳輸要發指令 */    
 
    /* 讀取資料 */
    sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4線快速讀取命令 */
    sCommand.DummyCycles = 6;                    /* 空週期 */
    sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4線地址 */
    sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4線資料 */ 
    sCommand.NbData      = _uiSize;              /* 讀取的資料大小 */ 
    sCommand.Address     = _uiReadAddr;          /* 讀取資料的起始地址 */ 
    
    if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }

    /* 讀取 */
    if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    
}

此時函式使用的指令0xEC對應的W25Q256JV手冊說明,注意紅色方框位置:

左上角的1-4-4就是指令階段使用1個IO,地址階段使用4個IO,資料階段也是使用4個IO,並且採用的4位元組地址方式,反映到程式裡面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.4 第4步:W25QXX的程式設計實現

注:這裡以查詢方式的API進行說明,DMA方式是一樣的。

下面實現了一個頁程式設計函式:

/*
*********************************************************************************************************
*    函 數 名: QSPI_WriteBuffer
*    功能說明: 頁程式設計,頁大小256位元組,任意頁都可以寫入
*    形    參: _pBuf : 資料來源緩衝區;
*              _uiWriteAddr :目標區域首地址,即頁首地址,比如0, 256, 512等。
*              _usWriteSize :資料個數,不能超過頁面大小,範圍1 - 256。
*    返 回 值: 1:成功, 0:失敗
*********************************************************************************************************
*/
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
{
    QSPI_CommandTypeDef sCommand={0};

    /* 寫使能 */
    QSPI_WriteEnable(&QSPIHandle);    
    
    /* 基本配置 */
    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1線方式傳送指令 */
    sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 無交替位元組 */
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支援DDR */
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,資料輸出延遲 */
    sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 僅傳送一次命令 */    
    
    /* 寫序列配置 */
    sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4線快速寫入命令 */
    sCommand.DummyCycles = 0;                    /* 不需要空週期 */
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4線地址方式 */
    sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4線資料方式 */
    sCommand.NbData      = _usWriteSize;         /* 寫資料大小 */   
    sCommand.Address     = _uiWriteAddr;         /* 寫入地址 */
    
    if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    {
        //return 0;
        Error_Handler(__FILE__, __LINE__);
    }
    
    /* 啟動傳輸 */
    if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    {
        //return 0;
        Error_Handler(__FILE__, __LINE__);
        
    }
    
    QSPI_AutoPollingMemReady(&QSPIHandle);    
    
    return 1;
}

此時函式使用的指令0x34對應的W25Q256JV手冊說明,注意紅色方框位置:

左上角的1-4-4就是指令階段使用1個IO,地址階段使用4個IO,資料階段也是使用4個IO,並且採用的4位元組地址方式,反映到程式裡面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.5 第5步:W25QXX的扇區擦除實現

注:這裡以查詢方式的API進行說明,DMA方式是一樣的。

通過傳送“扇區擦除命令+扇區地址”即可完成相應扇區的擦除,擦除的扇區大小是4KB。

/*
*********************************************************************************************************
*    函 數 名: QSPI_EraseSector
*    功能說明: 擦除指定的扇區,扇區大小4KB
*    形    參: _uiSectorAddr : 扇區地址,以4KB為單位的地址,比如0,4096, 8192等,
*    返 回 值: 無
*********************************************************************************************************
*/
void QSPI_EraseSector(uint32_t _uiSectorAddr)
{
    QSPI_CommandTypeDef sCommand={0};

    /* 寫使能 */
    QSPI_WriteEnable(&QSPIHandle);    

    /* 基本配置 */
    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1線方式傳送指令 */
    sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 無交替位元組 */
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支援DDR */
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,資料輸出延遲 */
    sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次傳輸都發指令 */    
    
    /* 擦除配置 */
    sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇區擦除命令,扇區大小4KB*/       
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址傳送是1線方式 */       
    sCommand.Address     = _uiSectorAddr;        /* 扇區首地址,保證是4KB整數倍 */    
    sCommand.DataMode    = QSPI_DATA_NONE;       /* 無需傳送資料 */  
    sCommand.DummyCycles = 0;                    /* 無需空週期 */  

    if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    QSPI_AutoPollingMemReady(&QSPIHandle);    
}

此時函式使用的指令0x21對應的W25Q256JV手冊說明,注意紅色方框位置:

左上角的1-1-1就是指令階段使用1個IO,地址階段使用1個IO,資料階段也是使用1個IO,並且採用的4位元組地址方式,反映到程式裡面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;

sCommand.AddressMode = QSPI_ADDRESS_1_LINES;

sCommand.AddressSize = QSPI_ADDRESS_32_BITS;

sCommand.DataMode = QSPI_DATA_1_LINES;

79.4.6 第6步:W25QXX的整個晶片擦除實現

注:這裡以查詢方式的API進行說明,DMA方式是一樣的。

整個晶片的擦除可以通過擦除各個扇區來實現,也可以呼叫專門的整個晶片擦除指令實現。下面實現方法是傳送整個晶片擦除命令實現:

/*
*********************************************************************************************************
*    函 數 名: QSPI_EraseChip
*    功能說明: 整個晶片擦除
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void QSPI_EraseChip(void)
{
    QSPI_CommandTypeDef sCommand={0};

    /* 寫使能 */
    QSPI_WriteEnable(&QSPIHandle);    

    /* 基本配置 */
    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1線方式傳送指令 */
    sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 無交替位元組 */
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支援DDR */
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,資料輸出延遲 */
    sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次傳輸都發指令 */    
    
    /* 擦除配置 */
    sCommand.Instruction = BULK_ERASE_CMD;       /* 整個晶片擦除命令*/       
    sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址傳送是1線方式 */       
    sCommand.Address     = 0;                    /* 地址 */    
    sCommand.DataMode    = QSPI_DATA_NONE;       /* 無需傳送資料 */  
    sCommand.DummyCycles = 0;                    /* 無需空週期 */  

    if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    QSPI_AutoPollingMemReady(&QSPIHandle);    
}

此時函式使用的指令0xC7對應的W25Q256JV手冊說明,注意紅色方框位置:

左上角的1-1-1就是指令階段使用1個IO,地址階段使用1個IO,資料階段也是使用1個IO,並且採用的4位元組地址方式,反應到程式裡面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_1_LINES;

擦除用不到資料階段,sCommand.DataMode = QSPI_DATA_NONE即可。

79.4.7 第7步:W25QXX記憶體對映實現

注:這裡以查詢方式的API進行說明,DMA方式是一樣的。

通過記憶體對映模式,就可以像使用內部Flash一樣使用W25QXX,程式碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: QSPI_MemoryMapped
*    功能說明: QSPI記憶體對映,地址 0x90000000
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void QSPI_MemoryMapped(void)
{
    QSPI_CommandTypeDef s_command = {0};
    QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};

    /* 基本配置 */
    s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1線方式傳送指令 */ 
    s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
    s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 無交替位元組 */
    s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支援DDR */
    s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,資料輸出延遲 */
    s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次傳輸都發指令 */
    
    /* 全部採用4線 */
    s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速讀取命令 */
    s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4個地址線 */
    s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4個數據線 */
    s_command.DummyCycles = 6;                                    /* 空週期 */

    /* 關閉溢位計數 */
    s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    s_mem_mapped_cfg.TimeOutPeriod = 0;

    if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
    {
       Error_Handler(__FILE__, __LINE__);
    }
}

此時函式使用的指令0xEC對應的W25Q256JV手冊說明,注意紅色方框位置:

左上角的1-4-4就是指令階段使用1個IO,地址階段使用4個IO,資料階段也是使用4個IO,採用的4位元組地址方式,反應到程式裡面就是:

sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
sCommand.DataMode = QSPI_DATA_4_LINES;

79.4.8 第8步:使用MDMA方式要注意Cache問題

如果使用MDMA方式的話,可以使用TCM RAM,此時不用考慮Cache問題。如果使用的是其它RAM空間,要考慮Cache問題。因為MDMA和CPU同時訪問DMA緩衝造成的資料一致性問題,將這塊空間關閉讀Cache和寫Cache,比如使用的AXI SRAM,這樣可以方便大家做測試,測試通過後,再根據需要開啟Cache。

/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x24000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);

79.5 W25QXX板級支援包(bsp_qspi_w25q256.c)

W25QXX驅動檔案bsp_qspi_w25q256.c主要實現瞭如下幾個API供使用者呼叫:

  • QSPI_ReadBuffer
  • QSPI_WriteBuffer
  • QSPI_EraseSector
  • QSPI_EraseChip
  • QSPI_MemoryMapped

79.5.1 函式QSPI_ReadBuffer

函式原型:

void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);

函式描述:

此函式主要用於從QSPI Flash讀取資料,支援任意大小,任意地址,不超過晶片容量即可(如果使用DMA方式,每次最大65536位元組)。

函式引數:

  • 第1個引數用於儲存從QSPI Flash讀取的資料。
  • 第2個引數是讀取地址,不可以超過晶片容量。
  • 第3個引數是讀取的資料大小,讀取範圍不可以超過晶片容量。

79.5.2 函式QSPI_WriteBuffer

函式原型:

uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);

函式描述:

頁程式設計,頁大小256位元組,任意頁都可以寫入。注意使用前,務必保證相應頁已經做了擦除操作。

函式引數:

  • 第1個引數是源資料緩衝區。
  • 第2個引數是目標區域首地址,即頁首地址,比如0, 256, 512等。
  • 第3個引數是資料個數,不能超過頁面大小,範圍1 – 256,單位位元組個數。
  • 返回值,返回1表示成功,返回0表示失敗。

79.5.3 函式QSPI_EraseSector

函式原型:

void QSPI_EraseSector(uint32_t _uiSectorAddr)

函式描述:

此函式主要用於扇區擦除,一個扇區大小是4KB。

函式引數:

  • 第1個引數是扇區地址,比如擦除扇區0,此處填0x0000,擦除扇區1,此處填0x1000,擦除扇區2,此處填0x2000,以此類推。

79.5.4 函式QSPI_EraseChip

函式原型:

void QSPI_EraseChip(void)

函式描述:

此函式主要用於整個晶片擦除。

79.5.5 函式QSPI_MemoryMapped

函式原型:

void QSPI_MemoryMapped(void)

函式描述:

呼叫了此函式就可以像使用內部Flash一樣使用外部Flash。

79.6 W25QXX驅動移植和使用

W25QXX移植步驟如下:

  • 第1步:複製bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目錄,並新增到工程裡面。
  • 第2步:根據使用的QSPI引腳,時鐘等,修改bsp_qspi_w25q256.c檔案開頭的巨集定義。
/* 
    STM32-V7開發板接線

    PG6/QUADSPI_BK1_NCS AF10
    PF10/QUADSPI_CLK    AF9
    PF8/QUADSPI_BK1_IO0 AF10
    PF9/QUADSPI_BK1_IO1 AF10
    PF7/QUADSPI_BK1_IO2 AF9
    PF6/QUADSPI_BK1_IO3 AF9

    W25Q256JV有512塊,每塊有16個扇區,每個扇區Sector有16頁,每頁有256位元組,共計32MB
*/

/* QSPI引腳和時鐘相關配置巨集定義 */
#define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()
#define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
#define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()
#define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()

#define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()
#define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()
#define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()

#define QSPI_CS_PIN                    GPIO_PIN_6
#define QSPI_CS_GPIO_PORT              GPIOG

#define QSPI_CLK_PIN                   GPIO_PIN_10
#define QSPI_CLK_GPIO_PORT             GPIOF

#define QSPI_BK1_D0_PIN                GPIO_PIN_8
#define QSPI_BK1_D0_GPIO_PORT          GPIOF

#define QSPI_BK1_D1_PIN                GPIO_PIN_9
#define QSPI_BK1_D1_GPIO_PORT          GPIOF

#define QSPI_BK1_D2_PIN                GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT          GPIOF

#define QSPI_BK1_D3_PIN                GPIO_PIN_6
#define QSPI_BK1_D3_GPIO_PORT          GPIOF
  • 根據使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h標頭檔案
/* W25Q256JV基本資訊 */
#define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
#define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇區大小,4KB */
#define QSPI_PAGE_SIZE      256                        /* 頁大小,256位元組 */
#define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */
#define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/

/* W25Q256JV相關命令 */
#define WRITE_ENABLE_CMD      0x06         /* 寫使能指令 */  
#define READ_ID_CMD2          0x9F         /* 讀取ID命令 */  
#define READ_STATUS_REG_CMD   0x05         /* 讀取狀態命令 */ 
#define BULK_ERASE_CMD        0xC7         /* 整個晶片擦除命令 */ 
#define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇區擦除指令, 4KB */
#define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4線快速寫入命令 */
#define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4線快速讀取命令 */
  • 第4步:如果使用MDMA方式的話,可以使用TCM RAM,此時不用考慮Cache問題。如果使用的是其它RAM空間,要考慮Cache問題。因為MDMA和CPU同時訪問DMA緩衝造成的資料一致性問題,將這塊空間關閉讀Cache和寫Cache,比如使用的AXI SRAM:
/* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x24000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
  • 第5步:初始化QSPI。
/* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
bsp_InitQSPI_W25Q256();  /* 配置SPI匯流排 */
  • 第6步:QSPI Flash驅動主要用到HAL庫的SPI驅動檔案,簡單省事些可以新增所有HAL庫C原始檔進來。
  • 第7步:應用方法看本章節配套例子即可。

79.7 實驗例程設計框架

通過程式設計框架,讓大家先對配套例程有一個全面的認識,然後再理解細節,本次實驗例程的設計框架如下:

第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

第2階段,進入main函式:

  • 第1部分,硬體初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
  • 第2部分,應用程式設計部分,實現QSPI Flash的查詢和MDMA方式操作。

79.8 實驗例程說明(MDK)

配套例子:

V7-029_QSPI讀寫例程,四線DMA方式,讀每秒48MB(V1.1)

V7-059_QSPI讀寫例程,查詢方式

實驗目的:

  1. 學習QSPI Flash的讀寫測試例程

實驗操作:

  1. 支援以下7個功能,使用者通過電腦端串列埠軟體傳送數字1-6給開發板即可
  2. printf("請選擇操作命令:\r\n");
  3. printf("【1 - 讀QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  4. printf("【2 - 寫QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  5. printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
  6. printf("【4 - 讀整個序列Flash, 測試讀速度】\r\n");
  7. printf("【Z - 讀取前1K,地址自動減少】\r\n");
  8. printf("【X - 讀取後1K,地址自動增加】\r\n");
  9. printf("【Y - 擦除整個序列Flash,整片32MB擦除大概300秒左右】\r\n");
  10. printf("其他任意鍵 - 顯示命令提示\r\n");

上電後串列埠列印的資訊:

波特率 115200,資料位 8,奇偶校驗位無,停止位 1。

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發板使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化串列埠 */
    bsp_InitExtIO();     /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
    bsp_InitQSPI_W25Q256();  /* 配置SPI匯流排 */}

MPU配置和Cache配置:

資料Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴充套件IO區。

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

#if 0
       /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

 #else
    /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif    
    
    /* 配置FMC擴充套件IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

每10ms呼叫一次按鍵處理:

按鍵處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函式每隔10ms被Systick中斷呼叫1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函式。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}

主功能:

主程式實現如下操作:

  • 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
  • 支援以下7個功能,使用者通過電腦端串列埠軟體傳送命令給開發板即可
  • printf("【1 - 讀QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  • printf("【2 - 寫QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  • printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
  • printf("【4 - 讀整個序列Flash, 測試讀速度】\r\n");
  • printf("【Z - 讀取前1K,地址自動減少】\r\n");
  • printf("【X - 讀取後1K,地址自動增加】\r\n");
  • printf("【Y - 擦除整個序列Flash,整片32MB擦除大概300秒左右】\r\n");
  • printf("其他任意鍵 - 顯示命令提示\r\n");
/*
*********************************************************************************************************
*    函 數 名: DemoSpiFlash
*    功能說明: QSPI讀寫例程
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiFlash(void)
{
    uint8_t cmd;
    uint32_t uiReadPageNo = 0, id;

    /* 檢測序列Flash OK */
    id = QSPI_ReadID();
    printf("檢測到序列Flash, ID = %08X, 型號: WM25Q256JV\r\n", id);
    printf(" 容量 : 32M位元組, 扇區大小 : 4096位元組, 頁大小:256位元組\r\n");

    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重灌的定時器,軟體定時器0 */
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷軟體定時器0是否超時 */
        if(bsp_CheckTimer(0))
        {
            /* 每隔200ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        if (comGetChar(COM1, &cmd))    /* 從串列埠讀入一個字元(非阻塞方式) */
        {
            switch (cmd)
            {
                case '1':
                    printf("\r\n【1 - 讀QSPI Flash, 地址:0x%X ,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
                    sfReadTest();    /* 讀序列Flash資料,並打印出來資料內容 */
                    break;

                case '2':
                    printf("\r\n【2 - 寫QSPFlash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
                    sfWriteTest();    /* 寫序列Flash資料,並列印寫入速度 */
                    break;

                case '3':
                    printf("\r\n【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
                    sfWriteAll(0x55);/* 擦除序列Flash資料,實際上就是寫入全0xFF */
                    break;

                case '4':
                    printf("\r\n【4 - 讀整個QSPI Flash, %dM位元組】\r\n", QSPI_FLASH_SIZES/(1024*1024));
                    sfTestReadSpeed(); /* 讀整個序列Flash資料,測試速度 */
                    break;
                
                case 'y':
                case 'Y':
                    printf("\r\n【Y - 擦除整個QSPI Flash】\r\n");
                    printf("整個Flash擦除完畢大概需要300秒左右,請耐心等待");
                    sfErase();        /* 擦除序列Flash資料,實際上就是寫入全0xFF */
                    break;

                case 'z':
                case 'Z': /* 讀取前1K */
                    if (uiReadPageNo > 0)
                    {
                        uiReadPageNo--;
                    }
                    else
                    {
                        printf("已經是最前\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                case 'x':
                case 'X': /* 讀取後1K */
                    if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
                    {
                        uiReadPageNo++;
                    }
                    else
                    {
                        printf("已經是最後\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                default:
                    sfDispMenu();    /* 無效命令,重新列印命令提示 */
                    break;
            }
        }
    }
}

79.9 實驗例程說明(IAR)

配套例子:

V7-029_QSPI讀寫例程,四線DMA方式,讀每秒48MB(V1.1)

V7-059_QSPI讀寫例程,查詢方式

實驗目的:

  1. 學習QSPI Flash的讀寫測試例程

實驗操作:

  1. 支援以下7個功能,使用者通過電腦端串列埠軟體傳送數字1-6給開發板即可
  2. printf("請選擇操作命令:\r\n");
  3. printf("【1 - 讀QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  4. printf("【2 - 寫QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  5. printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
  6. printf("【4 - 讀整個序列Flash, 測試讀速度】\r\n");
  7. printf("【Z - 讀取前1K,地址自動減少】\r\n");
  8. printf("【X - 讀取後1K,地址自動增加】\r\n");
  9. printf("【Y - 擦除整個序列Flash,整片32MB擦除大概300秒左右】\r\n");
  10. printf("其他任意鍵 - 顯示命令提示\r\n");

上電後串列埠列印的資訊:

波特率 115200,資料位 8,奇偶校驗位無,停止位 1。

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發板使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化串列埠 */
    bsp_InitExtIO();     /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
    bsp_InitQSPI_W25Q256();  /* 配置SPI匯流排 */}

MPU配置和Cache配置:

資料Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴充套件IO區。

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

#if 0
       /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

 #else
    /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif    
    
    /* 配置FMC擴充套件IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

每10ms呼叫一次按鍵處理:

按鍵處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函式每隔10ms被Systick中斷呼叫1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函式。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}

主功能:

主程式實現如下操作:

  • 啟動一個自動重灌軟體定時器,每100ms翻轉一次LED2。
  • 支援以下7個功能,使用者通過電腦端串列埠軟體傳送命令給開發板即可
  • printf("【1 - 讀QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  • printf("【2 - 寫QSPI Flash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
  • printf("【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
  • printf("【4 - 讀整個序列Flash, 測試讀速度】\r\n");
  • printf("【Z - 讀取前1K,地址自動減少】\r\n");
  • printf("【X - 讀取後1K,地址自動增加】\r\n");
  • printf("【Y - 擦除整個序列Flash,整片32MB擦除大概300秒左右】\r\n");
  • printf("其他任意鍵 - 顯示命令提示\r\n");
/*
*********************************************************************************************************
*    函 數 名: DemoSpiFlash
*    功能說明: QSPI讀寫例程
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiFlash(void)
{
    uint8_t cmd;
    uint32_t uiReadPageNo = 0, id;

    /* 檢測序列Flash OK */
    id = QSPI_ReadID();
    printf("檢測到序列Flash, ID = %08X, 型號: WM25Q256JV\r\n", id);
    printf(" 容量 : 32M位元組, 扇區大小 : 4096位元組, 頁大小:256位元組\r\n");

    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 200); /* 啟動1個200ms的自動重灌的定時器,軟體定時器0 */
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷軟體定時器0是否超時 */
        if(bsp_CheckTimer(0))
        {
            /* 每隔200ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        if (comGetChar(COM1, &cmd))    /* 從串列埠讀入一個字元(非阻塞方式) */
        {
            switch (cmd)
            {
                case '1':
                    printf("\r\n【1 - 讀QSPI Flash, 地址:0x%X ,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
                    sfReadTest();    /* 讀序列Flash資料,並打印出來資料內容 */
                    break;

                case '2':
                    printf("\r\n【2 - 寫QSPFlash, 地址:0x%X,長度:%d位元組】\r\n", TEST_ADDR, TEST_SIZE);
                    sfWriteTest();    /* 寫序列Flash資料,並列印寫入速度 */
                    break;

                case '3':
                    printf("\r\n【3 - 寫QSPI Flash前10KB空間, 全0x55】\r\n");
                    sfWriteAll(0x55);/* 擦除序列Flash資料,實際上就是寫入全0xFF */
                    break;

                case '4':
                    printf("\r\n【4 - 讀整個QSPI Flash, %dM位元組】\r\n", QSPI_FLASH_SIZES/(1024*1024));
                    sfTestReadSpeed(); /* 讀整個序列Flash資料,測試速度 */
                    break;
                
                case 'y':
                case 'Y':
                    printf("\r\n【Y - 擦除整個QSPI Flash】\r\n");
                    printf("整個Flash擦除完畢大概需要300秒左右,請耐心等待");
                    sfErase();        /* 擦除序列Flash資料,實際上就是寫入全0xFF */
                    break;

                case 'z':
                case 'Z': /* 讀取前1K */
                    if (uiReadPageNo > 0)
                    {
                        uiReadPageNo--;
                    }
                    else
                    {
                        printf("已經是最前\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                case 'x':
                case 'X': /* 讀取後1K */
                    if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
                    {
                        uiReadPageNo++;
                    }
                    else
                    {
                        printf("已經是最後\r\n");
                    }
                    sfViewData(uiReadPageNo * 1024);
                    break;

                default:
                    sfDispMenu();    /* 無效命令,重新列印命令提示 */
                    break;
            }
        }
    }
}

79.10 總結

本章節就為大家講解這麼多,實際應用中根據需要選擇DMA和查詢方式。