下载算法下载到芯片的核心思想
通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。
算法程序中擦除操作执行流程
擦除操作大致流程:
- 加载算法到芯片RAM。
- 执行初始化函数Init。
- 执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
- 执行Uinit函数。
- 操作完毕。
算法程序中编程操作执行流程
编程操作大致流程:
- 针对MDK生成的axf可执行文件做Init初始化,这个axf文件是指的大家自己创建应用程序生成的。
- 查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
- 加载算法到RAM。
- 执行Init函数。
- 加载用户到RAM缓冲。
- 执行Program Page页编程函数。
- 执行Uninit函数。
- 操作完毕
算法程序中校验操作执行流程
校验操作大致流程:
- 校验要用到MDK生成的axf可执行文件。校验就是axf文件中下载到芯片的程序和实际下载的程序读出来做比较。
- 查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
-
加载算法到RAM。
-
执行Init函数。
-
查看校验算法是否存在
-
如果有,加载应用程序到RAM并执行校验。
-
如果没有,计算CRC,将芯片中读取出来的数据和RAM中加载应用计算输出的CRC值做比较。
-
执行Uninit函数。
-
替换BKPT(BreakPoint断点指令)为 B. 死循环指令。
-
执行RecoverySupportStop,恢复支持停止。
-
执行DebugCoreStop,调试内核停止。
-
- 运行应用:
- 执行失败。
- 执行成功,再执行硬件复位。
- 操作完毕,停止调试端口
创建MDK下载算法通用流程
第1步,使用MDK提供好的程序模板
位于路径: D:\Users\daiver\AppData\Local\Arm\Packs\Keil\STM32H7xx_DFP\2.8.0_bak_0\CMSIS\Flash\STM32H747I_eval_QSPI
D:\Users\daiver\AppData\Local\Arm\Packs\ARM\CMSIS\5.7.0\Device_Template_Flash
第2步,修改工程名
MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根据自己的需要做修改。比如修改为MyDevice.uvprojx。
第3步,修改使用的器件
在MDK的Option选项里面设置使用的器件。
第4步,修改输出算法文件的名字
这个名字是方便用户查看的,比如设置为daiver_h7,那么输出的算法文件就是daiver_h7.flm。
第5步,修改编程算法文件FlashPrg.c
第6步,修改配置文件FlashDev.c
模板工程里面提供简单的配置说明:
第7步,保证生成的算法文件中RO和RW段的独立性,即与地址无关
C和汇编的配置都勾选上:
如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:
(1)加载以响应运行事件。
(2)在不同情况下使用其他例程的不同组合加载到内存中。
(3)在执行期间映射到不同的地址。
使用Read-Write position independence同理,表示的可读可写数据段。
第8步,将程序可执行文件axf修改为flm格式
第9步,分散加载设置
–diag_suppress L6305用于屏蔽L6503类型警告信息。
设置了分散加载后,此处的配置就不再起作用了:
QSPI Flash的MDK下载算法制作
第1步,制作前重要提示
这两点非常重要:
- 程序里面不要开启任何中断,全部查询方式。
- HAL库里面各种时间基准相关的API全部处理掉。简单省事些,我们这里是直接注释,采用死等即可。无需做超时等待,因为超时后,已经意味着操作失败了,跟死等没有区别。
第2步,准备一个工程模板
第3步,修改HAL库
这一步比较重要,主要修改了以下三个文件:
第4步,时钟初始化
已经用不到滴答定时器了,直接在bsp.c文件里面对滴答初始化函数做重定向:
然后就是HSE外置晶振的配置,大家根据自己的板子实际外挂晶振大小,修改stm32h7xx_hal_conf.h文件中HSE_VALUE大小,实际晶振多大,这里就修改为多大:
#if !defined (HSE_VALUE)
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
最后修改PLL:
第5步,配置文件FlashDev.c的实现
配置如下:
struct FlashDevice const FlashDevice = {
FLASH_DRV_VERS, /* 驱动版本,勿修改,这个是MDK定的 */
H7x_BNANK2_QSPI_W25Q256", /* 算法名,添加算法到MDK安装目录会显示此名字 */
EXTSPI, /* 设备类型 */
0x90000000, /* Flash起始地址 */
32 * 1024 * 1024, /* Flash大小,32MB */
4 * 1024, /* 编程页大小 */
0, /* 保留,必须为0 */
0xFF, /* 擦除后的数值 */
1000, /* 页编程等待时间 */
6000, /* 扇区擦除等待时间 */
64 * 1024, 0x000000, /* 扇区大小,扇区地址 */
SECTOR_END
};
第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校验。
第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下载算法使用方法
编译本章教程配套的例子,生成的算法文件位于此路径下:
下载算法存放位置
生成算法文件后,需要大家将其存到MDK安装目录,有两个位置可以存放,任选其一,推荐第2种:
第1种:存放到MDK的STM32H7软包安装目录里面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软包版本不同,数值2.6.0不同)。
第2种:MDK的安装目录 \ARM\Flash里面。
下载配置
注意这里一定要够大,否则会提示算法文件无法加载:
我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。推荐使用AXI SRAM(地址0x24000000),因为这块RAM空间足够大。
如果要下载程序到QSPI Flash里面,需要做如下配置:
调试配置
注意这里一定要够大,否则会提示算法文件无法加载:
我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。
如果要做调试下载,需要做如下配置:
验证算法文件是否可以正常使用