天天看點

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

第81章       STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

81.1 初學者重要提示

81.2 STM32CubeProg簡介

80.3 STM32CubeProg下載下傳算法基礎知識

80.4 建立STM32CubeProg下載下傳算法通用流程

80.4 QSPI Flash的STM32CubeProg下載下傳算法制作

80.5 QSPI Flash的STM32CubeProg下載下傳算法使用方法

80.6 實驗例程說明

80.7 總結

  1.   QSPI Flash的相關知識點可以看第78章和79章。
  2.   QSPI Flash下載下傳算法檔案直接采用HAL庫制作,友善大家自己修改。
  3.   STM32CubeProg下載下傳算法制作和MDK下載下傳算法制作基本是一樣
  4.   本教程的第68章USB DFU和第69章序列槽IAP章節為大家介紹過STM32CubeProg的用法。STM32CubeProg下載下傳http://www.armbbs.cn/forum.php?mod=viewthread&tid=97370 。

STM32CubeProg,此軟體實作了之前的 DfuSe,STLINK 小軟體和 Flashloader 三合一,并且支援外部 EEPROM,NOR Flash,SPI Flash,NAND Flash 等燒寫,也支援 OTA 程式設計。

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.3 STM32CubeProg下載下傳算法基礎知識

STM32CubeProg下載下傳算法是一種用于擦除應用程式或将應用程式下載下傳到Flash的程式代碼。ST自家的晶片都自帶下載下傳算法,存放在STM32CubeProg安裝目錄裡面,但不支援的需要我們自己制作,本章教程為此而生。

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

認識到這點很重要:通過IDE開發環境建立一批與位址資訊無關的算法檔案,實作的功能主要有初始化,擦除,程式設計,讀取,校驗等,然後STM32CubeProg下載下傳階段,會将算法檔案加載到晶片的内部RAM裡面,然後STM32CubeProg通過與這個算法檔案的互動,實作程式下載下傳,資料讀取等操作。

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

注:下面是MDK的算法執行流程,STM32CubeProg是類似的。

擦除操作大緻流程:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作
  •   加載算法到晶片RAM。
  •   執行初始化函數Init。
  •   執行擦除操作,根據使用者配置,這裡可以選擇整個晶片擦除或者扇區擦除。
  •   執行Uinit函數。
  •   操作完畢。

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

注:下面是MDK的算法執行流程,STM32CubeProg是類似的(沒有Unint函數)。

程式設計操作大緻流程:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作
  •   針對IDE生成的axf(elf)可執行檔案做Init初始化,這個axf(elf)檔案是指的大家自己建立應用程式生成的。
  •   檢視Flash算法是否在FLM檔案。如果沒有在,操作失敗。如果在:
    •   加載算法到RAM。
    •   執行Init函數。
    •   加載使用者到RAM緩沖。
    •   執行Program Page頁程式設計函數。
    •   執行Uninit函數。

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

校驗操作大緻流程:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作
  •   校驗要用到IDE生成的axf(elf)可執行檔案。校驗就是axf(elf)檔案中下載下傳到晶片的程式和實際下載下傳的程式讀出來做比較。
    •   檢視校驗算法是否存在
  •   如果有,加載應用程式到RAM并執行校驗。
  •   如果沒有,執行計算CRC,将晶片中讀取資料出來和RAM中加載應用計算輸出的CRC值做比較。
    •   替換BKPT(BreakPoint斷點指令)為 B. 死循環指令。
    •   執行RecoverySupportStop,回複支援停止。
    •   執行DebugCoreStop,調試核心停止。
  •   運作應用:
    •   執行失敗
    •   執行成功,再執行硬體複位。
  •   操作完畢,停止調試端口。

81.4 建立STM32CubeProg下載下傳算法通用流程

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

81.4.1 第1步,使用STM32CubeProg提供好的程式模闆

位于路徑:STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

以M25P64為例(注,其餘步驟就以這個為例子進行說明):

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.4.2 第2步,修改工程名

STM32CubeProg提供的工程模闆原始名字是M25P64_STM3210E-EVAL.uvproj,大家可以根據自己的需要做修改。比如修改為MyDevice.uvproj(MDK4的字尾)或者MyDevice.uvprojx(MDK5的字尾)。

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

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

注:STM32CubeProg軟體裡面别的檔案名并不采用這個,而是由使用者在檔案Dev_Inf.c裡面定義的:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.4.5 第5步,修改使用的庫檔案和頭檔案路徑

根據大家使用的主要晶片,添加相應的庫檔案和頭檔案路徑。

81.4.6 第5步,修改程式設計算法檔案Loader_Src.c

模闆工程裡面提供的是M25P64,部分代碼如下:

/**
  * Description :
  * Initilize the MCU Clock, the GPIO Pins corresponding to the
  * device and initilize the FSMC with the chosen configuration 
  * Inputs    :
  *      None
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types of device 
  */
int Init (void)
{  
  SystemInit();
  sFLASH_Init();
  return 1;
}

        
/**
  * Description :
  * Read data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer        : Address where to get the data to write
  * outputs   :
  *      R0             : "1"             : Operation succeeded
  *               "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Read (uint32_t Address, uint32_t Size, uint8_t* buffer)
{ 
  sFLASH_ReadBuffer(buffer, Address, Size);
  return 1;
} 

    
/**
  * Description :
  * Write data from the device 
  * Inputs    :
  *      Address       : Write location
  *      Size          : Length in bytes  
  *      buffer        : Address where to get the data to write
  * outputs   :
  *      R0           : "1"             : Operation succeeded
  *                     "0"             : Operation failure
  * Note: Mandatory for all types except SRAM and PSRAM    
  */
int Write (uint32_t Address, uint32_t Size, uint8_t* buffer)
{
  sFLASH_WriteBuffer(buffer, Address, Size);
  return 1;
} 


/**
  * Description :
  * Erase a full sector in the device
  * Inputs    :
  *     None
  * outputs   :
  *     R0             : "1" : Operation succeeded
  *              "0" : Operation failure
  * Note: Not Mandatory for SRAM PSRAM and NOR_FLASH
  */
int MassErase (void)
{  
  sFLASH_EraseBulk();
  return 1;    
}      

81.4.7 第6步,修改配置檔案Dev_Inf.c

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

#if defined (__ICCARM__)
__root struct StorageInfo const StorageInfo  =  {
#else
struct StorageInfo const StorageInfo  =  {
#endif
   "M25P64_STM3210E-EVAL",           // Device Name + version number
   SPI_FLASH,                       // Device Type
   0x00000000,                     // Device Start Address
   0x00800000,                      // Device Size in Bytes (8MBytes/64Mbits)
   0x00000100,                      // Programming Page Size 16Bytes
   0xFF,                            // Initial Content of Erased Memory
// Specify Size and Address of Sectors (view example below)
   0x00000080, 0x00010000,          // Sector Num : 128 ,Sector Size: 64KBytes 
   0x00000000, 0x00000000,
};      

注:名字M25P64_STM3210E-EVAL就是我們第4步所說的。STM32CubeProg會識别出這個名字。

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

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

彙編:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

(1)加載以響應運作事件。

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

(3)在執行期間映射到不同的位址。

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

81.4.9 第8步,将程式可執行檔案axf修改為stldr格式

通過下面的指令就可以将生成的axf可執行檔案修改為stldr。

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.4.10   第9步,分散加載設定

我們這裡的分散加載檔案直接使用MDK模闆工程裡提供好的即可。

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

分散加載檔案中的内容如下:

FLASH_LOADER 0x20000004 PI   ; FlashLoader Functions
{
  PrgCode +0           ; Code
  {
    * (+RO)
  }
  PrgData +0           ; Data
  {
    * (+RW,+ZI)
  }
}

DEVICE_INFO +0               ; Device Info
{
  DevInfo +0           ; Info structure
  {
    dev_inf.o
  }
}      

這裡要修改下Flash算法加載位址,将0x20000004修改為STM32H7的RAM位址,任何RAM塊位址均可,隻要夠存儲Flash算法。推薦設定為AXI SRAM位址0x24000004,因為空間夠大,有512KB。

--diag_suppress L6305用于屏蔽L6503類型警告資訊。

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.5 QSPI Flash的STM32CubeProg下載下傳算法制作

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

81.5.1 第1步,制作前重要提示

這兩點非常重要:

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

81.5.2 第2步,準備一個工程模闆

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.5.3 第3步,修改HAL庫

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

81.5.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;
}      

81.5.5 第5步,配置檔案Dev_Inf.c的實作

配置如下:

#if defined (__ICCARM__)
__root struct StorageInfo const StorageInfo  =  {
#else
struct StorageInfo const StorageInfo =  {
#endif
    "ARMFLY_STM32H7x_QSPI_W25Q256", /* 算法名,添加算法到STM32CubeProg安裝目錄會顯示此名字 */
    NOR_FLASH,                      /* 裝置類型 */
    0x90000000,                     /* Flash起始位址 */
    32 * 1024 * 1024,               /* Flash大小,32MB */
    4*1024,                         /* 程式設計頁大小 */
    0xFF,                           /* 擦除後的數值 */
    512 , 64 * 1024,                /* 塊個數和塊大小 */
    0x00000000, 0x00000000,
};      

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.5.6 第6步,程式設計檔案Loader_Src.c的實作

下面将變成檔案中實作的幾個函數為大家做個說明:

  •   初始化函數Init
/*
*********************************************************************************************************
*    函 數 名: Init
*    功能說明: Flash程式設計初始化
*    形    參: 無
*    返 回 值: 0 表示失敗, 1表示成功
*********************************************************************************************************
*/
int Init(void)
{   
    int result = 1;
 
    /* 系統初始化 */
    SystemInit(); 

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

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

    return 1;
}      

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

  •   整個晶片擦除函數MassErase

整個晶片的擦除實作如下:

/*
*********************************************************************************************************
*    函 數 名: MassErase
*    功能說明: 整個晶片擦除
*    形    參: 無
*    返 回 值: 1 表示成功,0表示失敗
*********************************************************************************************************
*/
int MassErase(void)
{
    int result = 1;
    
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result == 1)
    {
        return 0;
    }
                                             
    result = QSPI_EraseChip(); 
    if (result == 1)
    {
        return 0;
    }    

    /* 記憶體映射 */    
    result = QSPI_MemoryMapped(); 
    if (result == 1)
    {
        return 0;
    }
    
    return result;          
}      
  •   扇區擦除函數SectorErase

扇區擦除實作:

/*
*********************************************************************************************************
*    函 數 名: SectorErase
*    功能說明: EraseStartAddress 擦除起始位址
*             EraseEndAddress   擦除結束位址
*    形    參: adr 擦除位址
*    返 回 值: 1 表示成功,0表示失敗
*********************************************************************************************************
*/
int SectorErase (uint32_t EraseStartAddress ,uint32_t EraseEndAddress)
{
    uint32_t BlockAddr, result;
    
    EraseStartAddress -= QSPI_FLASH_MEM_ADDR;
    EraseEndAddress -= QSPI_FLASH_MEM_ADDR;
    EraseStartAddress = EraseStartAddress -  EraseStartAddress % 0x10000; /* 64KB首位址 */
    
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result == 1)
    {
        return 0;        
    }

    while (EraseEndAddress >= EraseStartAddress)
    {
        /* 防止超出256MB空間 */
        BlockAddr = EraseStartAddress & 0x0FFFFFFF;

        result = QSPI_EraseSector(BlockAddr);    
        if (result == 1)
        {
            QSPI_MemoryMapped(); 
            return 0;        
        }

        EraseStartAddress += 0x10000;
    }

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

    return 1; 
}      

這裡要注意兩點:

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

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

  •   頁程式設計函數Write

頁程式設計函數實作如下:

int Write(uint32_t Address, uint32_t Size, uint8_t* buffer)
{ 
    int result = 0;
    uint32_t end_addr, current_size, current_addr;
    

    if (Address < QSPI_FLASH_MEM_ADDR || Address >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
    {
        return 0;
    }
    
    Address -= QSPI_FLASH_MEM_ADDR;

    /* 計算寫入位址和頁末的位元組大小 */
    current_size = QSPI_PAGE_SIZE - (Address % QSPI_PAGE_SIZE);

    /* 檢測頁剩餘空間是否夠用 */
    if (current_size > Size)
    {
        current_size = Size;
    }
    
    /* W25Q256初始化 */
    result = bsp_InitQSPI_W25Q256();
    if (result == 1)
    {
        return 0;
    }

    current_addr = Address;
    end_addr = Address + Size;

    do{
        if (QSPI_WriteBuffer(buffer, current_addr, current_size) == 1)
        {
            QSPI_MemoryMapped();  
            return 0;  
        }

        /* 更新位址 */
        current_addr += current_size;
        buffer += current_size;
        current_size = ((current_addr + QSPI_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : QSPI_PAGE_SIZE;
        
    }while(current_addr < end_addr);

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

    return (1);  
}      

這裡注意兩點:

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

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

  •   讀取和校驗函數

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

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

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

81.5.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    /* 整片擦除 */      

81.6 QSPI Flash的STM32CubeProg下載下傳算法使用方法

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

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.6.1 下載下傳算法存放位置

生成此檔案後,需要大家将其存放到STM32CubeProg安裝目錄路徑:

\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.6.2 STM32CubeProg下載下傳配置

我們這裡以STLINK連接配接開發闆為例進行說明(USB DFU或者序列槽方式不支援下載下傳外部Flash)。

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

點選Connect後效果如下:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

在這裡選擇我們制作的下載下傳算法:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

任意加載個hex或者bin檔案,并按照如下配置,然後點選Start Programming

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

下載下傳完成後的效果如下:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

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

為了驗證算法檔案是否可以正常使用,大家可以運作本教程第82章或者83章配套的例子。也可以使用STM32CubeProg直接讀取:

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.7 實驗例程說明

本章配套例子:V7-061_QSPI Flash的STM32CubeProg下載下傳算法制作。

【STM32H7教程】第81章 STM32H7的QSPI 總線應用之QSPI Flash的STM32CubeProg下載下傳算法制作

81.8 總結

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

微信公衆号:armfly_com

安富萊論壇:www.armbbs.cn

安富萊淘寶:https://armfly.taobao.com