1. 程式人生 > 實用技巧 >【STM32H7教程】第80章 STM32H7的QSPI 匯流排應用之QSPI Flash的MDK下載算法制作

【STM32H7教程】第80章 STM32H7的QSPI 匯流排應用之QSPI Flash的MDK下載算法制作

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

第80章 STM32H7的QSPI 匯流排應用之QSPI Flash的MDK下載算法制作

本章節為大家講解MDK下載算法制作方法。

80.1 初學者重要提示

80.2 MDK下載演算法基礎知識

80.3 建立MDK下載演算法通用流程

80.4 QSPI Flash的MDK下載算法制作

80.5 QSPI Flash的MDK下載演算法使用方法

80.6 實驗例程說明

80.7 總結

80.1 初學者重要提示

  1. QSPI Flash的相關知識點可以看第78章和79章。
  2. QSPI Flash下載演算法檔案直接採用HAL庫製作,方便大家自己修改。

80.2 MDK下載演算法基礎知識

Flash程式設計演算法是一種用於擦除應用程式或將應用程式下載到Flash的程式程式碼。MDK本身支援的各種器件都自帶下載演算法,存放在MDK各種器件的軟體包裡面,以STM32H7為例,演算法存放在\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(軟體包版本不同,數值2.6.0不同),但不支援的需要我們自己製作,本章教程為此而生。

80.2.1 程式能夠通過下載演算法下載到晶片的核心思想

認識到這點很重要:通過MDK建立一批與地址資訊無關的函式,實現的功能主要有初始化,擦除,程式設計,讀取,校驗等,然後MDK除錯下載階段,會將演算法檔案載入到晶片的內部RAM裡面(載入地址可以通過MDK設定),然後MDK通過與這個演算法檔案的互動,實現程式下載,除錯階段資料讀取等操作。

80.2.2 演算法程式中擦除操作執行流程

擦除操作大致流程:

  • 載入演算法到晶片RAM。
  • 執行初始化函式Init。
  • 執行擦除操作,根據使用者的MDK配置,這裡可以選擇整個晶片擦除或者扇區擦除。
  • 執行Uinit函式。
  • 操作完畢。

80.2.3 演算法程式中程式設計操作執行流程

程式設計操作大致流程:

  • 針對MDK生成的axf可執行檔案做Init初始化,這個axf檔案是指的大家自己建立應用程式生成的。
  • 檢視Flash演算法是否在FLM檔案。如果沒有在,操作失敗。如果在:
    • 載入演算法到RAM。
    • 執行Init函式。
    • 載入使用者到RAM緩衝。
    • 執行Program Page頁程式設計函式。
    • 執行Uninit函式。
  • 操作完畢。

80.2.4 演算法程式中校驗操作執行流程

校驗操作大致流程:

  • 校驗要用到MDK生成的axf可執行檔案。校驗就是axf檔案中下載到晶片的程式和實際下載的程式讀出來做比較。
  • 檢視Flash演算法是否在FLM檔案。如果沒有在,操作失敗。如果在:
    • 載入演算法到RAM。
    • 執行Init函式。
    • 檢視校驗演算法是否存在
      • 如果有,載入應用程式到RAM並執行校驗。
      • 如果沒有,計算CRC,將晶片中讀取出來的資料和RAM中載入應用計算輸出的CRC值做比較。
    • 執行Uninit函式。
    • 替換BKPT(BreakPoint斷點指令)為 B. 死迴圈指令。
    • 執行RecoverySupportStop,恢復支援停止。
    • 執行DebugCoreStop,除錯核心停止。
  • 執行應用:
    • 執行失敗。
    • 執行成功,再執行硬體復位。
  • 操作完畢,停止除錯埠。

80.3 建立MDK下載演算法通用流程

下面是MDK給的一種大致操作流程,不限制必須採用這種方法,自己建立也可以的。

80.3.1 第1步,使用MDK提供好的程式模板

位於路徑:\Keil\ARM\Pack\ARM\CMSIS\version\Device\_Template_Flash。

效果如下:

80.3.2 第2步,修改工程名

MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根據自己的需要做修改。比如修改為MyDevice.uvprojx。

80.3.3 第3步,修改使用的器件

在MDK的Option選項裡面設定使用的器件。

80.3.4 第4步,修改輸出演算法檔案的名字

這個名字是方便使用者檢視的,比如設定為stm32h7,那麼輸出的演算法檔案就是stm32h7.flm。

注:MDK這裡設定的名字與下面位置識別出來的演算法名無關:

這個名字是在FlashDev.c裡面定義的。

80.3.5 第5步,修改程式設計演算法檔案FlashPrg.c

模板工程裡面僅提供了介面函式,內容需要使用者自己填。

/* 
   Mandatory Flash Programming Functions (Called by FlashOS):
                int Init        (unsigned long adr,   // Initialize Flash
                                 unsigned long clk,
                                 unsigned long fnc);
                int UnInit      (unsigned long fnc);  // De-initialize Flash
                int EraseSector (unsigned long adr);  // Erase Sector Function
                int ProgramPage (unsigned long adr,   // Program Page Function
                                 unsigned long sz,
                                 unsigned char *buf);

   Optional  Flash Programming Functions (Called by FlashOS):
                int BlankCheck  (unsigned long adr,   // Blank Check
                                 unsigned long sz,
                                 unsigned char pat);
                int EraseChip   (void);               // Erase complete Device
      unsigned long Verify      (unsigned long adr,   // Verify Function
                                 unsigned long sz,
                                 unsigned char *buf);

       - BlanckCheck  is necessary if Flash space is not mapped into CPU memory space
       - Verify       is necessary if Flash space is not mapped into CPU memory space
       - if EraseChip is not provided than EraseSector for all sectors is called
*/

/*
 *  Initialize Flash Programming Functions
 *    Parameter:      adr:  Device Base Address
 *                    clk:  Clock Frequency (Hz)
 *                    fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
 *    Return Value:   0 - OK,  1 - Failed
 */

int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  De-Initialize Flash Programming Functions
 *    Parameter:      fnc:  Function Code (1 - Erase, 2 - Program, 3 - Verify)
 *    Return Value:   0 - OK,  1 - Failed
 */

int UnInit (unsigned long fnc) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Erase complete Flash Memory
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseChip (void) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Erase Sector in Flash Memory
 *    Parameter:      adr:  Sector Address
 *    Return Value:   0 - OK,  1 - Failed
 */

int EraseSector (unsigned long adr) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}


/*
 *  Program Page in Flash Memory
 *    Parameter:      adr:  Page Start Address
 *                    sz:   Page Size
 *                    buf:  Page Data
 *    Return Value:   0 - OK,  1 - Failed
 */

int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {

  /* Add your Code */
  return (0);                                  // Finished without Errors
}

80.3.6 第6步,修改配置檔案FlashDev.c

模板工程裡面提供簡單的配置說明:

struct FlashDevice const FlashDevice  =  {
   FLASH_DRV_VERS,             // Driver Version, do not modify!
   "New Device 256kB Flash",   // Device Name 
   ONCHIP,                     // Device Type
   0x00000000,                 // Device Start Address
   0x00040000,                 // Device Size in Bytes (256kB)
   1024,                       // Programming Page Size
   0,                          // Reserved, must be 0
   0xFF,                       // Initial Content of Erased Memory
   100,                        // Program Page Timeout 100 mSec
   3000,                       // Erase Sector Timeout 3000 mSec

// Specify Size and Address of Sectors
   0x002000, 0x000000,         // Sector Size  8kB (8 Sectors)
   0x010000, 0x010000,         // Sector Size 64kB (2 Sectors) 
   0x002000, 0x030000,         // Sector Size  8kB (8 Sectors)
   SECTOR_END
};

注:名字New Device 256kB Flash就是我們第4步所說的。MDK的Option選項裡面會識別出這個名字。

80.3.7 第7步,保證生成的演算法檔案中RO和RW段的獨立性,即與地址無關

C和彙編的配置都勾選上:

彙編:

如果程式的所有隻讀段都與位置無關,則該程式為只讀位置無關(ROPI, Read-only position independence)。ROPI段通常是位置無關程式碼(PIC,position-independent code),但可以是隻讀資料,也可以是PIC和只讀資料的組合。選擇“ ROPI”選項,可以避免使用者不得不將程式碼載入到記憶體中的特定位置。這對於以下例程特別有用:

(1)載入以響應執行事件。

(2)在不同情況下使用其他例程的不同組合載入到記憶體中。

(3)在執行期間對映到不同的地址。

使用Read-Write position independence同理,表示的可讀可寫資料段。

80.3.8 第8步,將程式可執行檔案axf修改為flm格式

通過下面的命令就可以將生成的axf可執行檔案修改為flm。

80.3.9 第9步,分散載入設定

我們這裡的分散載入檔案直接使用MDK模板工程裡提供好的即可,無需任何修改。

分散載入檔案中的內容如下:

; Linker Control File (scatter-loading)
;

PRG 0 PI               ; Programming Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW,+ZI)
  }
}

DSCR +0                ; Device Description
{
  DevDscr +0
  {
    FlashDev.o
  }
}

--diag_suppress L6305用於遮蔽L6503型別警告資訊。

特別注意,設定了分散載入後,此處的配置就不再起作用了:

80.4 QSPI Flash的MDK下載算法制作

下面將QSPI Flash算法制作過程中的幾個關鍵點為大家做個說明。

80.4.1 第1步,製作前重要提示

這兩點非常重要:

  • 程式裡面不要開啟任何中斷,全部查詢方式。
  • HAL庫裡面各種時間基準相關的API全部處理掉。簡單省事些,我們這裡是直接註釋,採用死等即可。無需做超時等待,因為超時後,已經意味著操作失敗了,跟死等沒有區別。

80.4.2 第2步,準備一個工程模板

推薦大家直接使用我們本章工程準備好的模板即可,如果大家自己製作,注意一點,請使用當前最新的HAL庫。

80.4.3 第3步,修改HAL庫

這一步比較重要,主要修改了以下三個檔案:

主要是修改了HAL庫時間基準相關的幾個API,並註釋掉了一批無關的API。具體修改內容,大家可以找個比較軟體,對比修改後的這個檔案和CubeH7軟體包V1.8.0(軟體包裡面的HAL庫版本是V1.9.0)的差異即可。

80.4.4 第4步,時鐘初始化

我們已經用不到滴答定時器了,直接在bsp.c檔案裡面對滴答初始化函式做重定向:

/*
*********************************************************************************************************
*    函 數 名: HAL_InitTick
*    功能說明: 重定向,不使用
*    形    參: TickPriority
*    返 回 值: 無
*********************************************************************************************************
*/
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    return HAL_OK;
}

然後就是HSE外接晶振的配置,大家根據自己的板子實際外掛晶振大小,修改stm32h7xx_hal_conf.h檔案中HSE_VALUE大小,實際晶振多大,這裡就修改為多大:

#if !defined  (HSE_VALUE) 
#define HSE_VALUE    ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */

最後修改PLL:

/*
*********************************************************************************************************
*    函 數 名: SystemClock_Config
*    功能說明: 初始化系統時鐘
*                System Clock source            = PLL (HSE)
*                SYSCLK(Hz)                     = 400000000 (CPU Clock)
*               HCLK(Hz)                       = 200000000 (AXI and AHBs Clock)
*                AHB Prescaler                  = 2
*                D1 APB3 Prescaler              = 2 (APB3 Clock  100MHz)
*                D2 APB1 Prescaler              = 2 (APB1 Clock  100MHz)
*                D2 APB2 Prescaler              = 2 (APB2 Clock  100MHz)
*                D3 APB4 Prescaler              = 2 (APB4 Clock  100MHz)
*                HSE Frequency(Hz)              = 25000000
*               PLL_M                          = 5
*                PLL_N                          = 160
*                PLL_P                          = 2
*                PLL_Q                          = 4
*                PLL_R                          = 2
*                VDD(V)                         = 3.3
*                Flash Latency(WS)              = 4
*    形    參: 無
*    返 回 值: 1 表示失敗,0 表示成功
*********************************************************************************************************
*/
int SystemClock_Config(void)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    HAL_StatusTypeDef ret = HAL_OK;

    /* 鎖住SCU(Supply configuration update) */
    MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0);

    /* 
      1、晶片內部的LDO穩壓器輸出的電壓範圍,可選VOS1,VOS2和VOS3,不同範圍對應不同的Flash讀速度,
         詳情看參考手冊的Table 12的表格。
      2、這裡選擇使用VOS1,電壓範圍1.15V - 1.26V。
    */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

    /* 使能HSE,並選擇HSE作為PLL時鐘源 */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
    RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
        
    RCC_OscInitStruct.PLL.PLLM = 5;
    RCC_OscInitStruct.PLL.PLLN = 160;
    RCC_OscInitStruct.PLL.PLLP = 2;
    RCC_OscInitStruct.PLL.PLLR = 2;
    RCC_OscInitStruct.PLL.PLLQ = 4;        
        
    RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
    RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;    
    ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
    if(ret != HAL_OK)
    {
        return 1;        
    }

    /* 
       選擇PLL的輸出作為系統時鐘
       配置RCC_CLOCKTYPE_SYSCLK系統時鐘
       配置RCC_CLOCKTYPE_HCLK 時鐘,對應AHB1,AHB2,AHB3和AHB4匯流排
       配置RCC_CLOCKTYPE_PCLK1時鐘,對應APB1匯流排
       配置RCC_CLOCKTYPE_PCLK2時鐘,對應APB2匯流排
       配置RCC_CLOCKTYPE_D1PCLK1時鐘,對應APB3匯流排
       配置RCC_CLOCKTYPE_D3PCLK1時鐘,對應APB4匯流排     
    */
    RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 | \
                                 RCC_CLOCKTYPE_PCLK2  | RCC_CLOCKTYPE_D3PCLK1);

    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;  
    RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2; 
    RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2; 
    RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2; 
    
    /* 此函式會更新SystemCoreClock,並重新配置HAL_InitTick */
    ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
    if(ret != HAL_OK)
    {
        return 1;
    }

    /*
      使用IO的高速模式,要使能IO補償,即呼叫下面三個函式 
      (1)使能CSI clock
      (2)使能SYSCFG clock
      (3)使能I/O補償單元, 設定SYSCFG_CCCSR暫存器的bit0
    */
    __HAL_RCC_CSI_ENABLE() ;

    __HAL_RCC_SYSCFG_CLK_ENABLE() ;

    HAL_EnableCompensationCell();

    __HAL_RCC_D2SRAM1_CLK_ENABLE();
    __HAL_RCC_D2SRAM2_CLK_ENABLE();
    __HAL_RCC_D2SRAM3_CLK_ENABLE();
    
    return 0;
}

80.4.5 第5步,配置檔案FlashDev.c的實現

配置如下:

struct FlashDevice const FlashDevice  =  {
    FLASH_DRV_VERS,                   /* 驅動版本,勿修改,這個是MDK定的 */
    "ARMFLY_STM32H7x_QSPI_W25Q256",   /* 演算法名,新增演算法到MDK安裝目錄會顯示此名字 */
    EXTSPI,                           /* 裝置型別 */
    0x90000000,                       /* Flash起始地址 */
    32 * 1024 * 1024,                 /* Flash大小,32MB */
    4 * 1024,                         /* 程式設計頁大小 */
    0,                                /* 保留,必須為0 */
    0xFF,                             /* 擦除後的數值 */
    1000,                             /* 頁程式設計等待時間 */
    6000,                             /* 扇區擦除等待時間 */
    64 * 1024, 0x000000,              /* 扇區大小,扇區地址 */
    SECTOR_END    
};

註釋已經比較詳細,大家根據自己的需要做修改即可。注意一點,演算法名ARMFLY_STM32H7x_QSPI_W25Q256會反饋到這個地方:

80.4.6 第6步,程式設計檔案FlashPrg.c的實現

下面將檔案中實現的幾個函式為大家做個說明:

  • 初始化函式Init
/*
*********************************************************************************************************
*    函 數 名: Init
*    功能說明: Flash程式設計初始化
*    形    參: adr Flash基地址,晶片首地址。
*             clk 時鐘頻率
*             fnc 函式程式碼,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失敗
*********************************************************************************************************
*/
int Init (unsigned long adr, unsigned long clk, unsigned long fnc) 
{
    int result = 0;
 
    /* 系統初始化 */
    SystemInit(); 

    /* 時鐘初始化 */
    result = SystemClock_Config();
    if (result  != 0)
    {
        return 1;        
    }

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 記憶體對映 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }

    return 0;
}

初始化完畢後將其設定為記憶體對映模式。

  • 復位初始化函式Uinit

擦除,程式設計和校驗函式後都會呼叫此函式。

/*
*********************************************************************************************************
*    函 數 名: UnInit
*    功能說明: 復位初始化
*    形    參: fnc 函式程式碼,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失敗
*********************************************************************************************************
*/
int UnInit (unsigned long fnc) 
{ 
    int result = 0;

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 記憶體對映 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);
}

復位初始化這裡,直接將其設定為記憶體對映模式。

  • 整個晶片擦除函式EraseChip

如果大家配置勾選了MDK Option選項中此處的配置,會呼叫的整個晶片擦除:

實際應用中不推薦大家勾選這裡,因為整個晶片擦除太耽誤時間,比如32MB QSPI Flash整個晶片擦除需要300秒左右。

另外,如果大家的演算法工程裡面沒有新增此函式,MDK會呼叫扇區擦除函式來實現,直到所有扇區擦除完畢。

/*
*********************************************************************************************************
*    函 數 名: UnInit
*    功能說明: 復位初始化
*    形    參: fnc 函式程式碼,1 - Erase, 2 - Program, 3 - Verify
*    返 回 值: 0 表示成功, 1表示失敗
*********************************************************************************************************
*/
int UnInit (unsigned long fnc) 
{ 
    int result = 0;

    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 記憶體對映 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);
}
  • 扇區擦除函式EraseSector

如果大家配置勾選了MDK Option選項中此處的配置,會呼叫扇區擦除:

/*
*********************************************************************************************************
*    函 數 名: EraseSector
*    功能說明: 扇區擦除
*    形    參: adr 擦除地址
*    返 回 值: 無
*********************************************************************************************************
*/
int EraseSector (unsigned long adr) 
{    
    int result = 0;

    /* 地址要在操作的晶片範圍內 */
    if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
    {
        return 1;
    }
    
    adr -= QSPI_FLASH_MEM_ADDR;
    
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
    
    /* 扇區擦除 */
    result = QSPI_EraseSector(adr);  
    if (result != 0)
    {
        return 1;
    }    

    /* 記憶體對映 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return 0;   
}

這裡要注意兩點:

(1) 程式裡面的操作adr -= QSPI_FLASH_MEM_ADDR,實際傳遞進來的地址是帶了首地址的,即0x90000000。

(2) 這裡執行的擦除大小要前面FlashDev.c檔案中配置的扇區大小一致,這裡是執行的64KB為扇區進行擦除。

  • 頁程式設計函式ProgramPage

頁程式設計函式實現如下:

/*
*********************************************************************************************************
*    函 數 名: ProgramPage
*    功能說明: 頁程式設計
*    形    參: adr 頁起始地址
*             sz  頁大小
*             buf 要寫入的資料地址
*    返 回 值: 無
*********************************************************************************************************
*/
int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) 
{
    int size;
    int result = 0;

    /* 地址要在操作的晶片範圍內 */    
    if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
    {
        return 1;
    }
   
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result != 0)
    {
        return 1;
    }
        
    adr -= QSPI_FLASH_MEM_ADDR;
    size =  sz;
    
    /* 頁程式設計 */
    while(size > 0)
    {
        if (QSPI_WriteBuffer(buf, adr, 256) == 1)
        {
            QSPI_MemoryMapped(); 
            
            return 1;   
        }
        size -= 256;
        adr += 256;
        buf += 256;
    }
    
    /* 記憶體對映 */    
    result = QSPI_MemoryMapped(); 
    if (result != 0)
    {
        return 1;
    }
    
    return (0);                      
}

這裡注意兩點:

(1) W25Q256的頁大小是256位元組,前面FlashDev.c中將頁程式設計大小設定為4096位元組,所以此程式要做處理。

(2) 程式裡面的操作adr -= QSPI_FLASH_MEM_ADDR,實際傳遞進來的地址是帶了首地址的,即0x90000000。

  • 讀取和校驗函式

我們程式中未做讀取和校驗函式。

(1) 如果程式中未做讀取函式,那麼MDK會以匯流排方式進行讀取,這也是為什麼每個函式執行完畢都設定為記憶體對映模式的原因。

(2) 如果程式中未做校驗函式,那麼MDK會讀取資料做CRC校驗。

80.4.7 第7步,修改QSPI Flash驅動檔案(引腳,命令等)

最後一步就是QSPI Flash(W25Q256)的驅動修改,大家可以根據自己的需求做修改。使用的引腳定義在檔案bsp_qspi_w25q256.c(做了條件編譯,包含了H7-TOOL和STM32-V7板子):

/* 
    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
        
    H7-TOOL開發板接線

    PG6/QUADSPI_BK1_NCS     AF10
    PB2/QUADSPI_CLK         AF9
    PD11/QUADSPI_BK1_IO0    AF10
    PD12/QUADSPI_BK1_IO1    AF10
    PF7/QUADSPI_BK1_IO2     AF9
    PD13/QUADSPI_BK1_IO3    AF9
*/

/* QSPI引腳和時鐘相關配置巨集定義 */
#if 0
#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_GPIOB_CLK_ENABLE()
#define QSPI_BK1_D0_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_CLK_ENABLE()
#define QSPI_BK1_D1_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_CLK_ENABLE()
#define QSPI_BK1_D2_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOF_CLK_ENABLE()
#define QSPI_BK1_D3_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOD_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_CS_GPIO_AF                 GPIO_AF10_QUADSPI

#define QSPI_CLK_PIN                    GPIO_PIN_2
#define QSPI_CLK_GPIO_PORT              GPIOB
#define QSPI_CLK_GPIO_AF                GPIO_AF9_QUADSPI

#define QSPI_BK1_D0_PIN                 GPIO_PIN_11
#define QSPI_BK1_D0_GPIO_PORT           GPIOD
#define QSPI_BK1_D0_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D1_PIN                 GPIO_PIN_12
#define QSPI_BK1_D1_GPIO_PORT           GPIOD
#define QSPI_BK1_D1_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D2_PIN                 GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT           GPIOF
#define QSPI_BK1_D2_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D3_PIN                 GPIO_PIN_13
#define QSPI_BK1_D3_GPIO_PORT           GPIOD
#define QSPI_BK1_D3_GPIO_AF             GPIO_AF9_QUADSPI
#else
#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_CS_GPIO_AF                 GPIO_AF10_QUADSPI

#define QSPI_CLK_PIN                    GPIO_PIN_10
#define QSPI_CLK_GPIO_PORT              GPIOF
#define QSPI_CLK_GPIO_AF                GPIO_AF9_QUADSPI

#define QSPI_BK1_D0_PIN                 GPIO_PIN_8
#define QSPI_BK1_D0_GPIO_PORT           GPIOF
#define QSPI_BK1_D0_GPIO_AF             GPIO_AF10_QUADSPI

#define QSPI_BK1_D1_PIN                 GPIO_PIN_9
#define QSPI_BK1_D1_GPIO_PORT           GPIOF
#define QSPI_BK1_D1_GPIO_AF             GPIO_AF10_QUADSPI

#define QSPI_BK1_D2_PIN                 GPIO_PIN_7
#define QSPI_BK1_D2_GPIO_PORT           GPIOF
#define QSPI_BK1_D2_GPIO_AF             GPIO_AF9_QUADSPI

#define QSPI_BK1_D3_PIN                 GPIO_PIN_6
#define QSPI_BK1_D3_GPIO_PORT           GPIOF
#define QSPI_BK1_D3_GPIO_AF             GPIO_AF9_QUADSPI
#endif

硬體設定了之後,剩下就是QSPI Flash相關的幾個配置,在檔案bsp_qspi_w25q256.h:

主要是下面這幾個:

#define QSPI_FLASH_MEM_ADDR         0x90000000

/* 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 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線快速讀取命令 */

#define BLOCK_ERASE_64K_4_BYTE_ADDR_CMD         0xDC    /* 4位元組地址,64K扇區 */

#define BULK_ERASE_CMD                          0xC7    /* 整片擦除 */

80.5 QSPI Flash的MDK下載演算法使用方法

編譯本章教程配套的例子,生成的演算法檔案位於此路徑下:

80.5.1 下載演算法存放位置

生成演算法檔案後,需要大家將其存到MDK安裝目錄,有兩個位置可以存放,任選其一,推薦第2種:

  • 第1種:存放到MDK的STM32H7軟包安裝目錄裡面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(軟包版本不同,數值2.6.0不同)。
  • 第2種:MDK的安裝目錄 \ARM\Flash裡面。

80.5.2 下載配置

注意這裡一定要夠大,否則會提示演算法檔案無法載入:

我們這裡是將其加到DTCM中,即首地址為0x20000000,大家也可以儲存到任意其它RAM地址,只要空間還夠載入演算法檔案即可。推薦使用AXI SRAM(地址0x24000000),因為這塊RAM空間足夠大。

如果要下載程式到QSPI Flash裡面,需要做如下配置:

80.5.3 除錯配置

注意這裡一定要夠大,否則會提示演算法檔案無法載入:

我們這裡是將其加到DTCM中,即首地址為0x20000000,大家也可以儲存到任意其它RAM地址,只要空間還夠載入演算法檔案即可。

如果要做除錯下載,需要做如下配置:

80.5.4 驗證演算法檔案是否可以正常使用

為了驗證演算法檔案是否可以正常使用,大家可以執行本教程第82章或者83章配套的例子。

80.6 實驗例程說明

本章配套例子:V7-060_QSPI Flash的MDK下載算法制作。

編譯後,演算法檔案會存到此路徑下:

80.7 總結

本章節就為大家講解這麼多,為了熟練掌握,大家可以嘗試自己實現一個Flash下載演算法。