本帖最后由 正点原子运营 于 2023-2-20 10:52 编辑
第五十六章 串口IAP实验
1)实验平台:正点原子MiniPro STM32H750开发板
2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
6)MiniPro STM32H750技术交流QQ群:170313895
IAP,即在应用编程,通俗地说法就是“程序升级”。产品阶段设计完成后,在脱离实验室的调试环境下,如果想对产品做功能升级或BUG修复会十分麻烦,如果硬件支持,在出厂时预留一套升级固件的流程,就可以很好解决这个问题,IAP技术就是为此而生的。在之前的FLASH模拟EEPROM实验里面,我们学习了STM32H750的FLASH自编程,本章我们将结合FLASH自编程的知识,通过STM32H750的串口实现一个简单的IAP功能。
本章分为如下几个小节: 56.1 IAP简介 56.2 硬件设计 56.3 程序设计 56.4 下载验证
56.1 IAP简介IAP(In Application Programming)即在应用编程。在讲解STM32的启动模式时我们已经知道STM32可以通过设置MSP的方式从不同的地址启动:包括Flash地址、RAM地址等,在默认方式下,我们的嵌入式程序是以连续二进制的方式烧录到STM32的可寻址Flash区域上的。如果我们用的Flash容量大到可以存储两个或多个的完整程序,在保证每个程序完整的情况下,上电后的程序通过修改MSP的方式,就可以保证一个单片机上有多个有功能差异的嵌入式软件,这就是我们要讲解的IAP的设计思路。
IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它做如下操作: 1)检查是否需要对第二部分代码进行更新 2)如果不需要更新则转到4) 3)执行更新操作 4)跳转到第二部分代码执行
第一部分代码必须通过其它手段,如JTAG或ISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分IAP代码更新。
我们将第一个项目代码称之为Bootloader程序,第二个项目代码称之为APP程序,他们存放在STM32H750FLASH的不同地址范围,一般从最低地址区开始存放Bootloader,紧跟其后的就是APP程序(注意,如果FLASH容量足够,是可以设计很多APP程序的,本章我们只讨论一个APP程序的情况)。这样我们就是要实现2个程序:Bootloader和APP。
STM32H7的APP程序不仅可以放到FLASH里面运行,也可以放到SRAM里面运行,本章,我们将制作两个APP,一个用于FLASH运行,一个用于内部SRAM运行。
我们先来看看STM32H7正常的程序运行流程(为了方便说明IAP过程,我们先仅考虑代码全部存放在内部FLASH的情况),如图56.1.1所示: 图56.1.1 STM32H750正常运行流程图
STM32H7的内部闪存(FLASH)地址起始于0X0800 0000,一般情况下,程序文件就从此地址开始写入。此外STM32H750是基于Cortex-M7内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是0x08000004,当中断来临,STM32H750的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
在图56.1.1中,STM32H750在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的main函数,如图标号②所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生了中断),此时STM32H750强制将PC指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号⑤所示。
当加入IAP程序之后,程序运行流程如图56.1.2所示: 图56.1.2 加入IAP之后程序运行流程图
在图56.1.2所示流程中,STM32H750复位后,还是从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数,如图标号①所示,此部分同图56.1.1一样;在执行完IAP以后(即将新的APP代码写入STM32H750的FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,如图标号②和③所示,同样main函数为一个死循环,并且注意到此时STM32H750的FLASH,在不同位置上,共有两个中断向量表。
在main函数执行过程中,如果CPU得到一个中断请求,PC指针仍然会强制跳转到地址0X08000004中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回main函数继续运行,如图标号⑥所示。
通过以上两个过程的分析,我们知道IAP程序必须满足两个要求: 1) 新程序必须在IAP程序之后的某个偏移量为x的地址开始; 2) 必须将新程序的中断向量表相应的移动,移动的偏移量为x;
以上IAP过程是针对内部FLASH来说的,而我们的STM32H750实际上还有外扩QSPI FLASH,而且H750内部FLASH仅有一个扇区(128KB大小),因此有如下几个问题: 1, H750内部FLASH只有1个扇区,在擦除扇区的时候,会整个擦掉,IAP代码丢失。 2, QSPIFLASH的代码,必须在配置好QSPI接口参数以后,才能访问并运行。 3, QSPI接口并不支持读时写,因此写QSPI的代码,必须存放在其他地方。 4, H750启动后必须先执行内部FLASH代码,也就是必须从0X08000000启动。
结合以上4点问题,我们的解决办法如下: 1, IAP程序运行在DTCMSRAM里面,这样的话,它可以对FLASH和QSPI FLASH进行擦除和编程操作,而不会引起其他问题。 2, DTCM程序保存在QSPIFLASH里面,启动的时候由内部FLASH程序将DTCM程序从QSPIFLASH拷贝到DTCM,然后运行DTCM程序,这样掉电/复位时DTCM程序就不会丢失。 3, 内部FLASH程序只执行将DTCM程序从QSPIFLASH拷贝到DTCM的操作,因此在DTCM程序擦除内部FLASH的时候,并不影响代码的运行。而且,从之前的FLASH模拟EEPROM实验可以知道,前16K数据在擦除后,会自动恢复(人为操作),因此只要保证FLASH程序小于16K即可正常工作。
综上,具体的H750 IAP更新流程如图56.1.3所示: 图56.1.3 STM32H750 IAP更新流程图
上图中,DTCM SRAM的IAP程序来自QSPI FLASH,这两个IAP程序是一模一样的,因此,我们总共需要2个IAP程序: 1, DTCM IAP程序,保存在QSPI FLASH,上电时从QSPI拷贝到DTCM,执行QSPI FLASH和内部FLASH的APP程序更新过程。 2,内部FLASH IAP程序,保存在内部FLASH,上电时,将QSPI FLASH保存的DTCM IAP程序拷贝到DTCM,并运行DTCM IAP,执行后续的操作。
本章,我们有3个APP程序: 1,FLASH APP程序,即只运行在内部FLASH的APP程序。 2,SRAM APP程序,即只运行在内部SRAM的APP程序,其运行过程和图56.1.2相似,不过需要设置向量表的地址为SRAM的地址。 3,FLASH + QSPI FLASH程序,即通过分散加载,控制不同的代码存放在不同的FLASH上,包括内部FLASH和外部QSPI FLASH,其运行过程和图56.1.2相似,不过有2段程序运行地址:0X0800 0000和0X9000 0000。
1.APP程序起始地址设置方法 首先,针对内部FLASH的APP程序,我们随便打开一个之前的实例工程,点击Options for TargetàLinker选项卡,如图56.1.4所示: 图56.1.4 FLASH APP Linker选项卡设置
勾选Use Memory Layout from Target Dialog,这样就不会使用分散加载来分配程序存放地址了,而是使用Target选项卡的设置来分配,如图56.1.5所示: 图56.1.5 FLASH APP Target选项卡设置
默认的条件下,图中IROM1的起始地址(Start)一般为0X08000000,大小(Size)为0X20000,即从0X08000000开始的128K空间为我们的程序存储区。而图中,我们设置起始地址(Start)为0X08004000,即偏移量为0X4000(16K字节),因而,留给APP用的FLASH空间(Size)只有0X20000-0X2000=0X1C000(112K字节)大小了。设置好Start和Size,就完成APP程序的起始地址设置。
这里的16K字节,一般是需要大家根据Bootloader程序大小进行选择,但是对H750来说,我们只保留了前16K给IAP用(详见前面的说明),因此必须保证内部FLASH IAP占用的内部FLASH不能超过16K大小。实际上,我们存放在内部FLASH的IAP代码实际上只做了一个拷贝操作(将QSPI FLASH的IAP程序拷贝到DTCM去运行),因此占用的FLASH非常小,因此16K的内存空间是够用的。
这是针对FLASHAPP的起始地址设置,如果是SRAM APP,那么起始地址设置如图56.1.6所示(注意,需要先禁止分散加载,参见图:56.1.4): 图56.1.6 SRAM APP Target选项卡设置
这里我们将IROM1的起始地址(Start)定义为:0X24001000,大小为0X7F000(508K字节),即从地址0X24000000偏移0X1000开始,存放SRAM APP代码。这个分配关系大家可以根据自己的实际情况修改,不一定和我们这里的设置一模一样,不过也需要注意,保证偏移量为0X200的倍数(我们这里为0X1000)。IRAM1的起始地址(Start)定义为:0X30000000,大小为0X40000(256K字节),即APP内存数据都存放在0X30000000开始的,大小为256KB范围的内存空间内。
最后,针对FLASH + QSPI FLASH的APP,起始地址设置由分散加载文件(.sct)完成,针对任意一个H750工程,点击Options for TargetàLinker选项卡,取消勾选:Use MemoryLayout from Target Dialog,然后选择:app_qspi_code.scf,作为分散加载文件,如图56.1.7所示: 图56.1.7 FLASH+QSPI FLASH APP Linker选项卡设置
pp_qspi_code.scf这个分散加载文件和我们在8.2节介绍的分散加载文件几乎一模一样,只是FLASH起始地址和大小定义有变化,变化如下: - /*m_stmflash(STM32内部FLASH)域起始地址,偏移了0X4000*/
- #definem_stmflash_start 0X08004000
- /*m_stmflash(STM32内部FLASH)大小,偏移后H750可用大小112KB*/
- #definem_stmflash_size 0X1C000
- /*m_qspiflash(外扩QSPI FLASH)域起始地址,偏移了0X100000*/
- #definem_qspiflash_start 0X90100000
- /*m_qspiflash(外扩QSPI FLASH)大小,偏移后W25Q64可用大小7MB */
- #definem_qspiflash_size 0X700000
- /*m_stmsram(STM32内部RAM)域起始地址,定义在D1,AXISRAM */
- #definem_stmsram_start 0X24000000
- /*m_stmsram(STM32内部RAM)大小,AXISRAM共512KB */
- #definem_stmsram_size 0X80000
复制代码可以看到,我们定义APP使用的STM32内部FLASH起始地址为:0X08004000,大小为:0X1C000(112KB),留出了前16K给IAP使用;APP使用的外部QSPI FLASH起始地址为:0X90100000,大小为:0X700000(7MB),留出了前1M给IAP使用;SRAM的起始地址和大小没有变化。
关于APP起始地址的设置方法,我们就介绍到这里,大家可以根据自己项目的实际需求进行修改。
2.中断向量表的偏移量设置方法 前面11.2.1小节,我们介绍过SystemInit函数,在系统启动的时候,会首先调用SystemInit函数初始化时钟系统,同时SystemInit还完成了中断向量表的设置,我们可以打开SystemInit函数,看到函数体的结尾处有这样几行代码: - #ifdef VECT_TAB_SRAM
- SCB->VTOR = D1_AXISRAM_BASE | VECT_TAB_OFFSET;
- /* Vector Table Relocation in Internal D1 AXI-RAM */
- #else
- SCB->VTOR = FLASH_BANK1_BASE | VECT_TAB_OFFSET;
- /* Vector Table Relocation in Internal FLASH */
- #endif
复制代码从代码可以理解,VTOR寄存器存放的是中断向量表的起始地址。默认的情况VECT_TAB_SRAM是没有定义,所以执行SCB->VTOR =FLASH_BANK1_BASE | VECT_TAB_OFFSET;这条语句。VECT_TAB_OFFSET默认定义为0x00000000UL,即默认偏移量为:0。对于FLASH APP,我们设置为FLASH_BANK1_BASE+偏移量0x4000,所以我们可以在SystemInit函数里面修改SCB->VTOR的值。为了尽可能不修改系统级别文件,我们可以通过调用sys.c里面的sys_nvic_set_vector_table函数实现,该函数定义如下: - /**
- *@brief 设置中断向量表偏移地址
- *@param baseaddr: 基址
- *@param offset: 偏移量
- *@retval 无
- */
- voidsys_nvic_set_vector_table(uint32_t baseaddr, uint32_t offset)
- {
- /* 设置NVIC的向量表偏移寄存器,VTOR低9位保留,即[8:0]保留 */
- SCB->VTOR = baseaddr | (offset & (uint32_t)0xFFFFFE00);
- }
复制代码该函数用于设置中断向量偏移,baseaddr 为基地址(即APP程序首地址),Offset为偏移量,需要根据自己的实际情况进行设置。比如FLASH APP设置中断向量表偏移量为0x4000,调用情况如下: - /* 设置中断向量表偏移量为0X4000 */
- sys_nvic_set_vector_table(FLASH_BANK1_BASE,0x4000);
复制代码这是设置FLASH APP的情况,SRAM APP的情况可以参考触摸屏实验_SRAM APP版本,其具体的调用情况请看到main函数。
通过以上两个步骤的设置,我们就可以生成APP程序了,只要APP程序的FLASH和SRAM大小不超过我们的设置即可。不过MDK默认生成的文件是.hex文件,并不方便我们用作IAP更新,我们希望生成的文件是.bin文件,这样可以方便进行IAP升级(至于为什么,请大家自行百度HEX和BIN文件的区别!)。这里我们通过MDK自带的格式转换工具fromelf.exe,来实现.axf文件到.bin文件的转换。该工具在MDK的安装目录\ARM\ARMCC\bin文件夹里面。
fromelf.exe转换工具的语法格式为:fromelf [options]input_file。其中options有很多选项可以设置,详细使用请参考光盘《mdk如何生成bin文件.doc》。 本实验,我们可以通过在MDK点击Options forTargetàUser选项卡,在After Build/Rebuild一栏中,勾选Run #1,并写入:C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o ..\..\Output\atk_h750.bin ..\..\Output\atk_h750.axf,如图56.1.8所示: 图56.1.8 MDK生成.bin文件设置方法
通过这一步设置,我们就可以在MDK编译成功之后,调用fromelf.exe(注意,我的MDK是安装在C:\Keil_v5文件夹下,如果你是安装在其他目录,请根据你自己的目录修改fromelf.exe的路径),根据当前工程的atk_h750.axf,生成一个atk_h750.bin的文件。并存放在axf文件相同的目录下,即工程的Output文件夹里面。在得到.bin文件之后,我们只需要将这个bin文件传送给单片机,即可执行IAP升级。
56.2 硬件设计
1. 例程功能IAP部分,包括两份代码: 1)IAPBootloader V1.0_FLASH,用于下载到目标开发板,执行IAP BootLoader。 2)IAPBootloader V1.0_ITCM,用于生成具体的IAP Bootloader动作,实现IAP过程。
IAP Bootloader V1.0_ITCM工程生成.bin文件需要通过ATK-C2B软件转成数组形式,然后把数组拷贝到IAP BootloaderV1.0_FLASH的bl_itcm.c文件,替换里面的acApp2数组内容,然后编译IAP Bootloader V1.0_FLASH工程,并下载到STM32开发板上。
IAP Bootloader V1.0_FLASH工程实际上只是完成了将IAP Bootloader V1.0_ITCM拷贝到ITCM并运行的工作。
IAP Bootloader V1.0_ITCM工程生成的BIN文件转成数组后由IAPBootloader V1.0_FLASH工程存储到外部QSPI FLASH。
本实验开机的时候先显示提示信息,然后等待串口输入接收APP程序(无校验,一次性接收),在串口接收到APP程序之后,即可执行IAP。
1、对于SRAM APP,通过按下KEY0即可执行这个收到的SRAM APP程序。
2、对于FLASH APP,则需要按下KEY1按键,将串口接收到的APP程序存放到STM32的FLASH,之后会自动执行这个FLASH APP程序。
3、对于FLASH&QSPIAPP,则会生成两个.bin文件(ER_m_qspiflash和ER_m_stmflash),先接收ER_m_qspiflash固件,按KEY_UP按键,将串口接收到的ER_m_qspiflash存放到外部的QSPI FLASH(BY25Q128),然后再接收ER_m_stmflash,按KEY1将串口接收到的ER_m_qspiflash存放到STM32的内部FLASH,并执行这个FLASH&QSPI APP程序。
LED0闪烁,提示程序运行。
2. 硬件资源1)RGB灯 RED :LED0 - PB4 GREEN :LED1 - PE6 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)独立按键 :KEY0 - PA1、KEY1 - PA15、WK_UP - PA0 5)QSPI(PB2/PB6/PD11/PD12/PD13/PE2) 6)norflash(QSPI FLASH芯片,连接在QSPI上)
56.3 程序设计APP程序的生成步骤 设置APP程序的起始地址和存储空间大小 对于在FLASH里面运行的APP程序,我们只需要设置APP程序的起始地址,和存储空间大小即可。而对于在SRAM里面运行的APP程序,我们还需要设置SRAM的起始地址和大小。无论哪种APP程序,都需要确保APP程序的大小和所占SRAM大小不超过我们的设置范围。
1) 设置中断向量表偏移量 此步,通过调用sys_nvic_set_vector_table函数,实现对中断向量表偏移量的设置。这个偏移量的大小,其实就等于程序起始地址相对于0X08000000或者0X24000000的偏移。
2) 设置编译后运行fromelf.exe,生成.bin文件 通过在User选项卡,设置编译后调用fromelf.exe,根据.axf文件生成.bin文件,用于IAP更新。
以上3个步骤,就可以得到一个.bin的APP程序,通过Bootlader程序即可实现更新。对于FLASH + QSPI FLASH的APP,会生产2个.bin文件(放在atk_h750.bin文件夹里面,详见下文),需要分别发送并更新到对应的地址。
56.3.1 程序流程图
图56.3.1.1 串口IAP实验程序流程图
56.3.2 程序解析这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。本实验,我们总共需要5个程序(2个IAP,3个APP):
1、DTCM IAP Bootloader,保存在QSPI FLASH,运行在DTCM SRAM,完成串口数据接收并写入STM32内部FLASH和外部QSPI FLASH,实现IAP功能。本程序使用:实验24 QSPI实验,作为DTCM IAP Bootloader程序(起始地址为0X20000000)。
2、FLASH IAP Bootloader,用于将保存在QSPI FLASH的IAP Bootloader拷贝到DTCM SRAM里面,并执行DTCM IAP Bootloader。本程序使用:实验12 内存保护(MPU)实验,作为FLASH IAP Bootloader程序(起始地址为0X08000000)。 3、FLASH APP,仅使用STM32内部FLASH,大小为112KB。本程序使用:实验16 USMART调试实验,作为FLASH APP程序(起始地址为0X08004000)。
4、SRAM APP,使用STM32内部AXISRAM,大小为508KB。本程序使用:实验25 触摸屏实验,作为SRAM APP程序(起始地址为0X24001000)。
5、FLASH + QSPI FLASH APP,同时使用STM32内部FLASH和外部QSPI FLASH,内部FLASH大小为112KB,QSPI FLASH大小为7MB。本程序使用:实验15 RTC实验,作为FLASH + QSPI FLASH APP程序(内部FLASH起始地址为0X08004000,外部QSPI FLASH起始地址为:0X90100000)。
本章关于APP程序的生成和修改比较简单,我们就不细说,请大家结合光盘源码,以及56.1节的介绍,自行理解。本章程序解析小节仅针对Bootloader程序。
1. IAP Bootloader V1.0_ITCM程序首先,我们先复制:实验29 QSPI实验 源码,并重新命名该例程,该名字为:IAP Bootloader V1.0_DTCM,并复制:实验38 FLASH模拟EEPROM实验 的STMFLASH文件夹到本工程的HARDWARE文件夹下,打开本实验工程,并将STMFLASH文件夹内的stmflash.c加入到HARDWARE组下,同时将STMFLASH加入头文件包含路径,并去掉USMART组建。
然后,点击魔术棒→Linker选项卡,进行如图56.3.2.1所示的设置: 图56.3.2.1 DTCM IAP Bootloader程序Linker选项卡设置
我们勾选:Use Memory Layout from Target Dialog,使用Target选项卡的设置实现内存分配控制。如图56.3.2.2所示: 图56.3.2.2 DTCM IAP Bootloader程序Target选项卡设置
在User文件夹所在的文件夹下新建一个IAP的文件夹,并在该文件夹下新建iap.c和iap.h两个文件。然后在工程里面新建一个IAP的组,将iap.c加入到该组下面。
首先看到iap.h头文件的一些定义,具体如下: - typedef void (*iapfun)(void); /* 定义一个函数类型的参数 */
- /* 第一个应用程序起始地址(存放在内部FLASH),保留0X08000000~0X08003FFF
- 的空间为Bootloader使用(共16KB) */
- #define FLASH_APP1_ADDR 0x08004000
- /* 第一个应用程序QSPI起始地址(存放在QSPI FLASH),保留0X90000000~0X900FFFFF
- 的空间为Bootloader使用(共1MB) */
- #define QSPI_APP1_ADDR 0X90100000
- #define SRAM_APP1_ADDR 0X24001000 /* 第一个应用程序SRAM起始地址 */
复制代码这里有3个宏定义: FLASH_APP1_ADDR:定义了APP代码在STM32内部FLASH的起始地址。 QSPI_APP1_ADDR:定义了APP代码在外部QSPI FLASH的起始地址。 SRAM_APP1_ADDR:定义了APP代码在内部SRAM的起始地址。
这3个宏定义在IAP的main函数里面被用到,如果大家需要修改APP的起始地址,则需要根据实际情况修改这3个宏定义地址。
下面介绍iap.c文件,具体的代码如下: - iapfun jump2app;
- uint32_t g_iapbuf[512]; /* 2K字节缓存 */
- /**
- *@brief IAP写入APP BIN
- *@param appxaddr : 应用程序的起始地址
- *@param appbuf : 应用程序CODE
- *@param appsize : 应用程序大小(字节)
- *@retval 无
- */
- voidiap_write_appbin(uint32_t appxaddr, uint8_t *appbuf, uint32_t appsize)
- {
- uint32_t t;
- uint16_t i = 0;
- uint32_t temp;
- uint32_t fwaddr = appxaddr; /* 当前写入的地址 */
- uint8_t *dfu = appbuf;
- for (t = 0; t < appsize; t += 4)
- {
- temp = (uint32_t)dfu[3] << 24;
- temp |= (uint32_t)dfu[2] << 16;
- temp |= (uint32_t)dfu[1] << 8;
- temp |= (uint32_t)dfu[0];
- dfu += 4; /* 偏移4个字节 */
- g_iapbuf[i++] = temp;
- if (i == 512)
- {
- i = 0;
- stmflash_write(fwaddr, g_iapbuf, 512);
- fwaddr += 2048; /* 偏移2048 512*4=2048 */
- }
- }
- if (i)
- {
- stmflash_write(fwaddr, g_iapbuf, i); /* 将最后的一些内容字节写进去 */
- }
- }
- /**
- *@brief 跳转到应用程序段(执行APP)
- *@param appxaddr : 应用程序的起始地址
- *@retval 无
- */
- void iap_load_app(uint32_t appxaddr)
- {
- /* 检查栈顶地址是否合法.可以放在SRAM1+SRAM2共256KB(0x30000000)
- 检查栈顶地址是否合法.可以放在AXI,512KB(0x24000000) */
- if (((*(volatile uint32_t *)appxaddr) & 0x3FF00000) == 0x30000000
- || ((*(volatile uint32_t *)appxaddr) & 0x3FF00000) == 0x24000000)
- {
- /* 用户代码区第二个字为程序开始地址(复位地址) */
- jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4);
- /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
- MSR_MSP(*(volatile uint32_t *)appxaddr);
- /* 跳转到APP */
- jump2app();
- }
- }
复制代码该文件总共只有2个函数,其中,iap_write_appbin函数用于将存放在串口接收buf里面的APP程序写入到FLASH。iap_load_app函数,则用于跳转到APP程序运行,其参数appxaddr为APP程序的起始地址,程序先判断栈顶地址是否合法(因为FLASH APP和SRAM APP的栈顶地址不一样,所以2个都是合法的),在得到合法的栈顶地址后,通过MSR_MSP函数(该函数在sys.c文件)设置栈顶地址,最后通过一个虚拟的函数(jump2app)跳转到APP程序执行代码,实现IAPàAPP的跳转。
本实验,我们是通过串口接收APP程序的,我们将usart.c和usart.h做了稍微修改,在usart.h中,我们定义USART_REC_LEN为508K字节,也就是串口最大一次可以接收508K字节的数据,这也是本Bootloader程序所能接收的最大APP程序大小。 然后新增一个g_usart_rx_cnt的变量,用于记录接收到的文件大小,而g_usart_rx_sta不再使用。在usart.c里面,我们修改USART_UX_IRQHandler部分代码如下: - /* 接收缓冲, 最大USART_REC_LEN个字节
- * 起始地址为 : 0x24001000
- */
- uint8_t g_usart_rx_buf[USART_REC_LEN] __attribute__ ((at(0x24001000)));
- /* 接收的字节数 */
- uint32_t g_usart_rx_cnt = 0;
- UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
- /**
- *@brief 串口1中断服务函数
- *@param 无
- *@retval 无
- */
- voidUSART_UX_IRQHandler(void)
- {
- uint8_t rxdata;
- #if SYS_SUPPORT_OS /* 使用OS */
- OSIntEnter();
- #endif
- if ((__HAL_UART_GET_FLAG(&g_uart1_handle, UART_FLAG_RXNE) != RESET))
- {
- HAL_UART_Receive(&g_uart1_handle, &rxdata, 1, 1000);
- if (g_usart_rx_cnt < USART_REC_LEN)
- {
- g_usart_rx_buf[g_usart_rx_cnt] = rxdata;
- g_usart_rx_cnt++;
- }
- }
- HAL_UART_IRQHandler(&g_uart1_handle);
- #if SYS_SUPPORT_OS /* 使用OS */
- OSIntExit();
- #endif
- }
复制代码这里,我们指定g_usart_rx_buf的地址是从0x24001000开始,该地址也就是SRAM APP程序的起始地址!然后在USART1_IRQHandler函数里面,将串口发送过来的数据,全部接收到g_usart_rx_buf,并通过g_usart_rx_cnt计数。代码比较简单,我们就不多说了。
最后,我们介绍main.c的程序,具体如下: - int main(void)
- {
- uint8_t t;
- uint8_t key;
- uint32_t oldcount = 0; /* 老的串口接收数据值 */
- uint32_t applenth = 0; /* 接收到的app代码长度 */
- uint8_t clearflag = 0;
- uint16_t id = 0;
- sys_cache_enable(); /* 打开L1-Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
- /* 中断向量表放在ITCM RAM,无偏移 */
- sys_nvic_set_vector_table(D1_ITCMRAM_BASE, 0);
- delay_init(480); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- mpu_memory_protection(); /* 保护相关存储区域 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- key_init(); /* 初始化按键 */
- norflash_init(); /*NORFLASH(W25Q64)初始化 */
- id = norflash_read_id(); /* 读取FLASH ID */
- while ((id == 0) || (id == 0XFFFF)) /* 检测不到FLASH芯片 */
- {
- lcd_show_string(30, 150, 200, 16, 16, "FLASH CheckFailed!", RED);
- delay_ms(500);
- lcd_show_string(30, 150, 200, 16, 16, "PleaseCheck! ", RED);
- delay_ms(500);
- LED0_TOGGLE(); /* LED0闪烁 */
- }
- lcd_show_string(30, 50, 200, 16, 16, "STM32H7", RED);
- lcd_show_string(30, 70, 200, 16, 16, "IAPTEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "KEY_UP:#1Copy APP2QSPI!", RED);
- lcd_show_string(30, 130, 200, 16, 16, "KEY1:CopyAPP2FLASH & run", RED);
- lcd_show_string(30, 150, 200, 16, 16, "KEY0:RunSRAM APP", RED);
- while (1)
- {
- if (g_usart_rx_cnt)
- {
- /* 新周期内,没有收到任何数据,认为本次数据接收完成 */
- if (oldcount == g_usart_rx_cnt)
- {
- applenth = g_usart_rx_cnt;
- oldcount = 0;
- g_usart_rx_cnt = 0;
- printf("用户程序接收完成!\r\n");
- printf("代码长度:%dBytes\r\n", applenth);
- }
- else oldcount = g_usart_rx_cnt;
- }
- t++;
- delay_ms(10);
- if (t == 30)
- {
- LED0_TOGGLE();
- t = 0;
- if (clearflag)
- {
- clearflag--;
- if (clearflag == 0)
- {
- lcd_fill(30, 210, 240, 210 + 16, WHITE); /* 清除显示 */
- }
- }
- }
- key = key_scan(0);
- /* 注意:QSPI FLASH不做任何校验和检查,所以请确保数据正确!否则将运行出错 */
- if (key == WKUP_PRES) /* WKUP按下,更新固件到外部QSPI FLASH */
- {
- if (applenth)
- {
- printf("开始更新固件...\r\n");
- lcd_show_string(30, 210, 200, 16, 16, "Copying APP2QSPI...",BLUE);
- norflash_write((uint8_t *)g_usart_rx_buf,
- QSPI_APP1_ADDR - 0X90000000, applenth); /* 写入QSPI FLASH!*/
- lcd_show_string(30, 210, 200, 16, 16, "Copy APPSuccessed!",BLUE);
- printf("固件更新完成!\r\n");
- }
- else
- {
- printf("没有可以更新的固件!\r\n");
- lcd_show_string(30, 210, 200, 16, 16, "No APP!", BLUE);
- }
- clearflag = 7; /* 标志更新了显示,并且设置7*300ms后清除显示 */
- }
- /* KEY1按键按下,更新固件到内部FLASH,更新完后自动运行FLASH APP代码 */
- /* 注意:如果APP还有QSPI FLASH部分代码,则必须先更新QSPI FLASH */
- if (key == KEY1_PRES)
- {
- if (applenth)
- {
- printf("开始更新固件...\r\n");
- lcd_show_string(30, 210, 200, 16, 16,"CopyingAPP2FLASH...",BLUE);
- if (((*(volatile uint32_t *)(SRAM_APP1_ADDR + 4))
- & 0xFF000000) == 0x08000000) /* 判断是否为0X08XXXXXX */
- {
- /* 更新FLASH代码 */
- iap_write_appbin(FLASH_APP1_ADDR, g_usart_rx_buf, applenth);
- lcd_show_string(30, 210, 200, 16, 16, "Copy APP Successed!!",
- BLUE);
- printf("固件更新完成!\r\n");
- delay_ms(500);
- printf("开始执行FLASH用户代码!!\r\n\r\n");
- delay_ms(10);
- if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4))
- & 0xFF000000) == 0x08000000) /* 判断是否为0X08XXXXXX */
- {
- iap_load_app(FLASH_APP1_ADDR);/* 执行FLASH APP代码 */
- }
- else
- {
- printf("非FLASH应用程序,无法执行!\r\n");
- lcd_show_string(30, 210, 200, 16, 16, "Illegal FLASH APP!",
- BLUE);
- }
- }
- else
- {
- lcd_show_string(30, 210, 200, 16,16,"Illegal FLASH APP!",BLUE);
- printf("非FLASH应用程序!\r\n");
- }
- }
- else if (((*(volatile uint32_t *)(FLASH_APP1_ADDR + 4)) & 0xFF000000)
- == 0x08000000) /* 判断FLASH里面是否有APP,有的话执行 */
- {
- printf("开始执行FLASH用户代码!!\r\n\r\n");
- delay_ms(10);
- iap_load_app(FLASH_APP1_ADDR);/* 执行FLASH APP代码 */
- }
- else
- {
- printf("没有可以更新的固件!\r\n");
- lcd_show_string(30, 210, 200, 16, 16, "No APP!", BLUE);
- }
- clearflag = 7; /* 标志更新了显示,并且设置7*300ms后清除显示 */
- }
- if (key == KEY0_PRES) /* KEY0按下 */
- {
- printf("开始执行SRAM用户代码!!\r\n\r\n");
- delay_ms(10);
- if (((*(volatile uint32_t *)(SRAM_APP1_ADDR + 4)) & 0xFF000000)
- == 0x24000000) /* 判断是否为0X24XXXXXX */
- {
- iap_load_app(SRAM_APP1_ADDR);/* SRAM地址 */
- }
- else
- {
- printf("非SRAM应用程序,无法执行!\r\n");
- lcd_show_string(30, 210, 200, 16, 16, "Illegal SRAM APP!", BLUE);
- }
- clearflag = 7; /* 标志更新了显示,并且设置7*300ms后清除显示 */
- }
- }
- }
复制代码该段代码,实现了串口数据处理,以及IAP更新和跳转等各项操作。DTCM IAP Bootloader程序就设计完成了,一般要求bootloader程序越小越好(给APP省空间嘛),这里由于H750的DTCM比较大(128KB),而且存储在外部QSPI FLASH(预留1MB),所以是足够放代码的,但是在实际应用时,可以根据需要尽量精简代码来得到最小的IAP。
因为存放在RAM 的DTCM代码是无法掉电保存的,因此我们需要将DTCM IAP Bootloader编译生成.bin文件(二进制文件),然后把.bin转成c数组,添加到内部FLASH IAP Bootloader程序里面,由FLASH IAP Bootloader保存到外部QSPI FLASH,并在每次复位时执行将DTCM IAP程序拷贝到DTCM,并运行的操作,从而实现IAP功能。
生成bin文件的方法见56.1节最后部分的内容,我们设置好DTCM IAP Bootloader生成bin文件,然后编译,如图56.3.2.3所示: 图56.3.2.3 DTCM IAP Bootloader 编译结果
可以看到DTCM IAP Bootloader总共大小为58KB(DTCM大小为128KB),并且编译后生成了.bin文件(存放在Output文件夹)。
然后,我们使用正点原子提供的:ATK-C2B V2.1.exe 软件(路径:光盘à 6,软件资料à1,软件àC2B转换助手文件夹),将.bin文件转换成C语言数组,如图56.3.2.4所示: 图56.3.2.4 DTCM IAP Bootloader固件转换成c数组
这样acAPP2[]数组就是我们的DTCM IAP Bootloader固件(.bin文件转换来的),我们可以将它添加到FLASH IAP Bootloader里面,从而实现下载到外部QSPI FLASH,并由FLASH IAP Bootloader搬运到DTCM运行。
2. IAP Bootloader V1.0_FLASH程序接下来,我们看看内部FLASH IAP Bootloader程序,该程序由:内存保护(MPU)实验修改而来,我们先复制:实验12 内存保护(MPU)实验 源码,并重新命名该例程,改为:IAP Bootloader V1.0_FLASH,然后在USER文件夹内新建bl_dtcm.c,然后打开工程,将key.c删除,并添加bl_dtcm.c到User分组下,然后将前面DTCM IAP Bootloader转换来的acAPP2[]数组添加到:bl_dtcm.c,并定义bl_itcm_size变量,存放数组大小的值,代码如下: - /*
- 正点原子 C2B软件 V2.1
- 开源电子网:www.openedv.com
- 微信公众号:<span lang="EN-US" style=""><font face="Tahoma" style="" size="3">http://www.openedv.com/template/veikei_dz_sim_20131020_sea/images/wexin-hei.png</font></span>
- 嵌入式教育专家
- */
- unsigned char acApp2[] = {
- 0xF0,0x63,0x00,0x20,0x59,0x03,0x00,0x00,0xA5,
- /* 省略数组中间部分内容 */
- 0x00,0x01,0x00,0x17,0xEF,0x02,0x00,0x00,0x00};
- unsigned long bl_itcm_size=sizeof(acApp2);
- /*************************** End of file****************************/
复制代码其中acApp2存储固件,bl_dtcm_size表示固件大小,后续在main函数需要用到。
最后介绍main.c的代码,具体如下: - /* 例程说明:
- * 本例程用于将IAP Bootloader V1.0_ITCM工程生产的.c数组(由.bin文件生成),存放在
- * QSPI FLASH,然后把QSPI FLASH的这个代码加载到ITCM(0X0000 0000开始,64KB大小)里面,
- * 然后跳转到ITCM运行,执行:IAPBootloader V1.0_ITCM,完成BootLoader功能!
- */
- #define BOOTLOADER_RUN_ADDR 0x00000000 /* Bootloader运行地址,即ITCM首地址 */
- typedef void (*iapfun)(void); /* 定义一个函数类型的参数 */
- iapfun jump2app; /* 假函数,用于跳转 */
- /* 在bl_itcm.c里面定义,存放IAP Bootloader V1.0_ITCM固件数组 */
- extern const unsigned char acApp2[];
- /* 在bl_itcm.c里面定义,存放IAP Bootloader V1.0_ITCM固件大小 */
- extern uint32_t bl_itcm_size;
- /**
- *@brief 跳转到应用程序段
- *@param appxaddr : 用户代码起始地址
- *@retval 无
- */
- void iap_load_app(uint32_t appxaddr)
- {
- /* 检查栈顶地址是否合法.ITCM BootLoader的内存放在DTCM里面 */
- if (((*(volatile uint32_t *)appxaddr) & 0x2FF00000) == 0x20000000)
- {
- /* 用户代码区第二个字为程序开始地址(复位地址) */
- jump2app = (iapfun) * (volatile uint32_t *)(appxaddr + 4);
- /* 初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址) */
- MSR_MSP(*(volatile uint32_t *)appxaddr);
- jump2app(); /* 跳转到APP */
- }
- }
- int main(void)
- {
- uint8_t *pbr =BOOTLOADER_RUN_ADDR; /* 指针指向Bootloader运行首地址 */
- uint32_t i = 0;
- sys_cache_enable(); /* 打开L1-Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
- delay_init(480); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- led_init(); /* 初始化LED */
- mpu_memory_protection(); /* 保护相关存储区域 */
- printf("ITCMBootLoader size is:%d\r\n", bl_itcm_size);
- for (i = 0; i < bl_itcm_size; i++)
- {
- pbr = acApp2; /* 搬运数据到BOOTLOADER_RUN_ADDR */
- }
- if (((*(volatile uint32_t *)(BOOTLOADER_RUN_ADDR+ 4)) & 0xFF000000)
- == 0x00000000) /* 判断是否为0X00XXXXXX */
- {
- printf("RunITCM BootLoader...\r\n\r\n");
- delay_ms(10);
- iap_load_app(BOOTLOADER_RUN_ADDR); /* 运行ITCM BootLoader */
- }
- else
- {
- printf("ITCMBootLoader addr error!\r\n");
- }
- while (1)
- {
- printf("Error!\r\n");
- LED0_TOGGLE(); /* LED0闪烁 */
- delay_ms(500);
- }
- }
复制代码首先:BOOTLOADER_RUN_ADDR宏定义,定义了Bootloader的运行地址,也就是DTCM RAM的首地址:0x00000000,另外,此部分代码包含2个函数:
iap_load_app,用于跳转到APP程序运行,这里用于跳转到DTCM IAP Bootloader运行,其参数appxaddr为程序的起始地址,程序先判断栈顶地址是否合法(我们使用ITCM作为DTCM IAP Bootloader的SRAM),在得到合法的栈顶地址后,通过MSR_MSP函数(该函数在sys.c文件)设置栈顶地址,最后通过一个虚拟的函数(jump2app)跳转到APP程序执行代码,实现FLASH IAPàDTCM IAP的跳转。
main函数主要执行了将acApp2数组内容,拷贝到DTCM首地址(0x00000000),然后判断起始地址合法,则调用iap_load_app跳转到DTCM IAP Bootloader运行。
最后,编译FLASH IAP Bootloader工程,如图56.3.2.5所示: 图56.3.2.5 Bootloader 工程截图
从上图可以看出,FLASHIAP Bootloader大小为81K左右,整体来说比较大,但是实际上存放在内部FLASH的代码并不多,我们可以通过.map文件查看具体的分配情况,实际上,仅占用了13K字节的内部FLASH,其他都是存放在外部QSPI FLASH的。
APP代码我们在这里就不做介绍了,大家可以参考本例程提供的源代码,以及56.1节和56.3节相关内容进行学习了解。这里需要提醒大家的是,APP程序如果有多段存储地址(即用了内部FLASH也用了外部QSPI FLASH),则会有多个.bin文件,MDK会生成一个atk_h750.bin的文件夹,将每一段地址的bin文件放里面(没后缀,但是可以正常使用),如图56.3.2.6所示: 图56.3.2.6 多存储段APP程序生成多个.bin文件
在执行IAP更新的时候,必须分段更新:将ER_m_qspiflash存储到外部QSPI FLASH,将ER_m_stmflash存储到内部FLASH,最后再运行APP。
56.4 下载验证将程序下载到开发板后,可以看到LCD首先显示一些实验相关的信息,如图56.4.1所示: 图56.4.1 IAP程序界面
此时,我们可以通过串口,发送FLASH APP、SRAM APP、FLASH + QSPI FLASH APP到开发板,如图56.4.2所示: 图56.4.2 串口发送APP程序界面
首先找到开发板USB转串口的串口号,打开串口(我电脑是COM8),然后设置波特率为115200(图中标号1所示),此时,按复位可以看到串口提示:DTCM Bootloader大小为58K字节左右,并跳转到DTCM运行(图中标号2所示)。
然后,点击打开文件按钮(图中标号3所示),找到APP程序生成的.bin文件(注意:文件类型得选择所有文件!默认是只打开txt文件的),最后点击发送文件(图中标号4所示),将.bin文件发送给STM32开发板,发送完成后,XCOM会提示文件发送完毕(图中标号5所示)。
开发板在收到APP程序之后,我们就可以通过KEY0/KEY1运行这个APP程序了(如果是FLASH APP,则通过KEY1将其存入对应FLASH区域,并运行,如果是FLASH + QSPI FLASH,则需要分两次更新APP:按KEY_UP将ER_m_qspiflash APP存储到外部QSPI FLASH,按KEY1将ER_m_stmflash APP存储到内部FLASH,并运行APP)。 |