完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
本章节为大家讲解MDK下载算法制作方法。
目录
第80章 STM32H7的QSPI 总线应用之QSPI Flash的MDK下载算法制作
80.1 初学者重要提示
80.2 MDK下载算法基础知识
80.2.1 程序能够通过下载算法下载到芯片的核心思想
80.2.2 算法程序中擦除操作执行流程
80.2.3 算法程序中编程操作执行流程
80.2.4 算法程序中校验操作执行流程
80.3 创建MDK下载算法通用流程
80.3.1 第1步,使用MDK提供好的程序模板
80.3.2 第2步,修改工程名
80.3.3 第3步,修改使用的器件
80.3.4 第4步,修改输出算法文件的名字
80.3.5 第5步,修改编程算法文件FlashPrg.c
80.3.6 第6步,修改配置文件FlashDev.c
80.3.7 第7步,保证生成的算法文件中RO和RW段的独立性,即与地址无关
80.3.8 第8步,将程序可执行文件axf修改为flm格式
80.3.9 第9步,分散加载设置
80.4 QSPI Flash的MDK下载算法制作
80.4.1 第1步,制作前重要提示
80.4.2 第2步,准备一个工程模板
80.4.3 第3步,修改HAL库
80.4.4 第4步,时钟初始化
80.4.5 第5步,配置文件FlashDev.c的实现
80.4.6 第6步,编程文件FlashPrg.c的实现
80.4.7 第7步,修改QSPI Flash驱动文件(引脚,命令等)
80.5 QSPI Flash的MDK下载算法使用方法
80.5.1 下载算法存放位置
80.5.2 下载配置
80.5.3 调试配置
80.5.4 验证算法文件是否可以正常使用
80.6 实验例程说明
80.7 总结
Flash编程算法是一种用于擦除应用程序或将应用程序下载到Flash的程序代码。MDK本身支持的各种器件都自带下载算法,存放在MDK各种器件的软件包里面,以STM32H7为例,算法存放在\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软件包版本不同,数值2.6.0不同),但不支持的需要我们自己制作,本章教程为此而生。
认识到这点很重要:通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。
擦除操作大致流程:
编程操作大致流程:
校验操作大致流程:
下面是MDK给的一种大致操作流程,不限制必须采用这种方法,自己创建也可以的。
位于路径:\Keil\ARM\Pack\ARM\CMSIS\version\Device\_Template_Flash。
效果如下:
MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根据自己的需要做修改。比如修改为MyDevice.uvprojx。
在MDK的Option选项里面设置使用的器件。
这个名字是方便用户查看的,比如设置为stm32h7,那么输出的算法文件就是stm32h7.flm。
注:MDK这里设置的名字与下面位置识别出来的算法名无关:
这个名字是在FlashDev.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
}
模板工程里面提供简单的配置说明:
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选项里面会识别出这个名字。
C和汇编的配置都勾选上:
汇编:
如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:
(1)加载以响应运行事件。
(2)在不同情况下使用其他例程的不同组合加载到内存中。
(3)在执行期间映射到不同的地址。
使用Read-Write position independence同理,表示的可读可写数据段。
通过下面的命令就可以将生成的axf可执行文件修改为flm。
我们这里的分散加载文件直接使用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类型警告信息。
特别注意,设置了分散加载后,此处的配置就不再起作用了:
下面将QSPI Flash算法制作过程中的几个关键点为大家做个说明。
这两点非常重要:
推荐大家直接使用我们本章工程准备好的模板即可,如果大家自己制作,注意一点,请使用当前最新的HAL库。
这一步比较重要,主要修改了以下三个文件:
主要是修改了HAL库时间基准相关的几个API,并注释掉了一批无关的API。具体修改内容,大家可以找个比较软件,对比修改后的这个文件和CubeH7软件包V1.8.0(软件包里面的HAL库版本是V1.9.0)的差异即可。
我们已经用不到滴答定时器了,直接在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;
}
配置如下:
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会反馈到这个地方:
下面将文件中实现的几个函数为大家做个说明:
/*
*********************************************************************************************************
* 函 数 名: 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;
}
初始化完毕后将其设置为内存映射模式。
擦除,编程和校验函数后都会调用此函数。
/*
*********************************************************************************************************
* 函 数 名: 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);
}
复位初始化这里,直接将其设置为内存映射模式。
如果大家配置勾选了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);
}
如果大家配置勾选了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
* 功能说明: 页编程
* 形 参: 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校验。
最后一步就是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 /* 整片擦除 */
编译本章教程配套的例子,生成的算法文件位于此路径下:
生成算法文件后,需要大家将其存到MDK安装目录,有两个位置可以存放,任选其一,推荐第2种:
注意这里一定要够大,否则会提示算法文件无法加载:
我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。推荐使用AXI SRAM(地址0x24000000),因为这块RAM空间足够大。
如果要下载程序到QSPI Flash里面,需要做如下配置:
注意这里一定要够大,否则会提示算法文件无法加载:
我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。
如果要做调试下载,需要做如下配置:
为了验证算法文件是否可以正常使用,大家可以运行本教程第82章或者83章配套的例子。
本章配套例子:V7-060_QSPI Flash的MDK下载算法制作。
编译后,算法文件会存到此路径下:
本章节就为大家讲解这么多,为了熟练掌握,大家可以尝试自己实现一个Flash下载算法。