完整教程下載下傳位址: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 總結
- QSPI Flash的相關知識點可以看第78章和79章。
- QSPI Flash下載下傳算法檔案直接采用HAL庫制作,友善大家自己修改。
- STM32CubeProg下載下傳算法制作和MDK下載下傳算法制作基本是一樣
- 本教程的第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 程式設計。

81.3 STM32CubeProg下載下傳算法基礎知識
STM32CubeProg下載下傳算法是一種用于擦除應用程式或将應用程式下載下傳到Flash的程式代碼。ST自家的晶片都自帶下載下傳算法,存放在STM32CubeProg安裝目錄裡面,但不支援的需要我們自己制作,本章教程為此而生。
81.3.1 程式能夠通過下載下傳算法下載下傳到晶片的核心思想
認識到這點很重要:通過IDE開發環境建立一批與位址資訊無關的算法檔案,實作的功能主要有初始化,擦除,程式設計,讀取,校驗等,然後STM32CubeProg下載下傳階段,會将算法檔案加載到晶片的内部RAM裡面,然後STM32CubeProg通過與這個算法檔案的互動,實作程式下載下傳,資料讀取等操作。
81.3.2 算法程式中擦除操作執行流程
注:下面是MDK的算法執行流程,STM32CubeProg是類似的。
擦除操作大緻流程:
- 加載算法到晶片RAM。
- 執行初始化函數Init。
- 執行擦除操作,根據使用者配置,這裡可以選擇整個晶片擦除或者扇區擦除。
- 執行Uinit函數。
- 操作完畢。
81.3.3 算法程式中程式設計操作執行流程
注:下面是MDK的算法執行流程,STM32CubeProg是類似的(沒有Unint函數)。
程式設計操作大緻流程:
- 針對IDE生成的axf(elf)可執行檔案做Init初始化,這個axf(elf)檔案是指的大家自己建立應用程式生成的。
- 檢視Flash算法是否在FLM檔案。如果沒有在,操作失敗。如果在:
- 加載算法到RAM。
- 執行Init函數。
- 加載使用者到RAM緩沖。
- 執行Program Page頁程式設計函數。
- 執行Uninit函數。
81.3.4 算法程式中校驗操作執行流程
校驗操作大緻流程:
- 校驗要用到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
以M25P64為例(注,其餘步驟就以這個為例子進行說明):
81.4.2 第2步,修改工程名
STM32CubeProg提供的工程模闆原始名字是M25P64_STM3210E-EVAL.uvproj,大家可以根據自己的需要做修改。比如修改為MyDevice.uvproj(MDK4的字尾)或者MyDevice.uvprojx(MDK5的字尾)。
81.4.3 第3步,修改使用的器件
在MDK的Option選項裡面設定使用的器件。
81.4.4 第4步,修改輸出算法檔案的名字
這個名字是友善使用者檢視的,比如設定為stm32h7,那麼輸出的算法檔案就是stm32h7.stldr。
注:STM32CubeProg軟體裡面别的檔案名并不采用這個,而是由使用者在檔案Dev_Inf.c裡面定義的:
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和彙編的配置都勾選上:
彙編:
如果程式的所有隻讀段都與位置無關,則該程式為隻讀位置無關(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。
81.4.10 第9步,分散加載設定
我們這裡的分散加載檔案直接使用MDK模闆工程裡提供好的即可。
分散加載檔案中的内容如下:
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類型警告資訊。
特别注意,設定了分散加載後,此處的配置就不再起作用了:
81.5 QSPI Flash的STM32CubeProg下載下傳算法制作
下面将QSPI Flash算法制作過程中的幾個關鍵點為大家做個說明。
81.5.1 第1步,制作前重要提示
這兩點非常重要:
- 程式裡面不要開啟任何中斷,全部查詢方式。
- HAL庫裡面各種時間基準相關的API全部處理掉。簡單省事些,我們這裡是直接注釋,采用死等即可。無需做逾時等待,因為逾時後,已經意味着操作失敗了,跟死等沒有差別。
81.5.2 第2步,準備一個工程模闆
推薦大家直接使用我們本章工程準備好的模闆即可,如果大家自己制作,注意一點,請使用目前最新的HAL庫。
81.5.3 第3步,修改HAL庫
這一步比較重要,主要修改了以下三個檔案:
主要是修改了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會回報到這個地方:
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下載下傳算法使用方法
編譯本章教程配套的例子,生成的算法檔案位于此路徑下:
81.6.1 下載下傳算法存放位置
生成此檔案後,需要大家将其存放到STM32CubeProg安裝目錄路徑:
\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader
81.6.2 STM32CubeProg下載下傳配置
我們這裡以STLINK連接配接開發闆為例進行說明(USB DFU或者序列槽方式不支援下載下傳外部Flash)。
點選Connect後效果如下:
在這裡選擇我們制作的下載下傳算法:
任意加載個hex或者bin檔案,并按照如下配置,然後點選Start Programming
下載下傳完成後的效果如下:
81.6.3 驗證算法檔案是否可以正常使用
為了驗證算法檔案是否可以正常使用,大家可以運作本教程第82章或者83章配套的例子。也可以使用STM32CubeProg直接讀取:
81.7 實驗例程說明
本章配套例子:V7-061_QSPI Flash的STM32CubeProg下載下傳算法制作。
81.8 總結
本章節就為大家講解這麼多,為了熟練掌握,大家可以嘗試自己實作一個Flash下載下傳算法。
微信公衆号:armfly_com
安富萊論壇:www.armbbs.cn
安富萊淘寶:https://armfly.taobao.com