【第二十三章 IAP】
第二十三章 IAP
目录
第二十三章 IAP
1 IAP 简介
2 例程设计
2.1 IAP_Bootloader
3 下载验证
3.1 IAP_Bootloader
IAP,即在应用编程。通俗地说法就是“程序升级”。产品阶段设计完成后,在脱离实验室的调试环境下,如果想对产品做功能升级或 BUG 修复会十分麻烦,如果硬件支持,在出厂时预留一套升级固件的流程,就可以很好解决这个问题,IAP 技术就是为此而生的。在之前的FLASH 模拟 EEPROM 实验里面,我们学习了 W55MH32的 FLASH 自编程,本章我们将结合 FLASH 自编程的知识,通过 W55MH32的串口实现一个简单的 IAP 功能。
本章分为如下几个小节:
1 IAP 简介
2 例程设计
3 下载验证
1 IAP 简介
IAP(In Application Programming)即在应用编程。在讲解 W55MH32 的启动模式时我们已经知道 W55MH32 可以通过设置 MSP 的方式从不同的地址启动:包括 Flash 地址、RAM 地址等,在默认方式下,我们的嵌入式程序是以连续二进制的方式烧录到 W55MH32 的可寻址 Flash 区域上的。如果我们用的 Flash 容量大到可以存储两个或多个的完整程序,在保证每个程序完整的情况下,上电后的程序通过修改 MSP 的方式,就可以保证一个单片机上有多个有功能差异的嵌入式软件,这就是我们要讲解的 IAP 的设计思路。
IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级,由于用户可以自定义通讯方式和自定义加密,使得 IAP 在使用上非常灵活。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个程序检查有无升级需求,并通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它做如下操作:
1)检查是否需要对第二部分代码进行更新
2)如果不需要更新则转到 4)
3)执行更新操作
4)跳转到第二部分代码执行
一部分代码必须通过其它手段,如 JTAG、ISP 等方式烧录,常常是烧录后就不再进行更改;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP 代码更新。
我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 W55MH32FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序(注意,如果 FLASH 容量足够,是可以设计很多 APP 程序的,本章我们只讨论一个 APP 程序的情况)。这样我们就是要实现 2 个程序:Bootloader 和 APP。W55MH32的 APP 程序不仅可以放到 FLASH 里面运行,也可以放到 SRAM 里面运行,本章,我们将制作两个 APP,一个用于 FLASH 运行,一个用于内部 SRAM 运行。我们先来看看 W55MH32正常的程序运行流程(为了方便说明 IAP 过程,我们先仅考虑代码全部存放在内部 FLASH 的情况),如图所示:
W55MH3 正常运行流程图
W55MH32的内部闪存(FLASH)地址起始于 0X0800 0000,一般情况下,程序文件就从此地址开始写入。此外 W55MH32是基于 Cortex-M3 内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是 0x08000004,当中断来临,W55MH32的内部硬件机制亦会自动将 PC 指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
W55MH32在复位后,先从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的 main 函数,如图标号②所示;而我们的 main 函数一般都是一个死循环,在 main 函数执行过程中,如果收到中断请求(发生了中断),此时 W55MH32强制将 PC 指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回 main 函数执行,如图标号⑤所示。
当加入 IAP 程序之后,程序运行流程如图所示:
加入 IAP 之后程序运行流程图
W55MH32复位后,还是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示,此部分同图 22.1.1 一样;在执行完 IAP 以后(即将新的 APP 代码写入W55MH32的 FLASH,灰底部分。新程序的复位中断向量起始地址为 0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样 main 函数为一个死循环,并且注意到此时 W55MH32的 FLASH,在不同位置上,共有两个中断向量表。
在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍然会强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。
通过以上两个过程的分析,我们知道 IAP 程序必须满足两个要求:
1) 新程序必须在 IAP 程序之后的某个偏移量为 x 的地址开始;
2) 必须将新程序的中断向量表相应的移动,移动的偏移量为 x;
对 W55MH32系列来说,闪存编程一次可以写入 16 位(半字)。闪存擦除操作可以按页面擦除或完全擦除(全擦除)。全擦除不影响信息块。根据类别的不同,Flash 有如下区别:
- 小容量产品主存储块最大为 4K×64 位,每个存储块划分为 32 个 1K 字节的页。
- 中容量产品主存储块最大为 16K×64 位,每个存储块划分为 128 个 1K 字节的页。
- 大容量产品主存储块最大为 64K×64 位,每个存储块划分为 256 个 2K 字节的页。
- 互联型产品主存储块最大为 32K×64 位,每个存储块划分为 128 个 2K 字节的页
使用时我们需要根据自己的芯片型号来选择,设计 IAP 程序时需要严格避免不同的程序占用相同 Flash 扇区的情形。本章,我们有 2 个 APP 程序:
1,FLASH APP 程序,即只运行在内部 FLASH 的 APP 程序。
2,SRAM APP 程序,即只运行在内部 SRAM 的 APP 程序。
1.APP 程序起始地址设置方法
APP 我们使用以前的例程即可,不过需要对程序进行修改,默认的条件下,图中 IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为0x40000,即从 0X08000000 开始的 256K 空间为我们的程序存储区。
FLASH APP Target 选项卡设置
我们设置起始地址(Start)为 0X08010000,即偏移量为 0x10000(64K 字节,即留给 BootLoader 的空间),因而,留给 APP 用的 FLASH 空间(Size)为 0x40000-0x10000=0x30000(192K 字节)大小了。设置好 Start 和 Size,就完成 APP 程序的起始地址设置。IRAM 是内存的地址,APP 可以独占这些内存,我们不需要修改。
注意:需要确保 APP 起始地址在 Bootloader 程序结束位置之后,并且偏移量为 0X200 的倍数即可这是针对 FLASH APP 的起始地址设置,如果是 SRAM APP,那么起始地址设置如图所示:
SRAM APP Target 选项卡设置
这里我们将 IROM1 的起始地址(Start)定义为:0X20001000,大小为 0XB000(44K 字节),即从地址 0X20000000 偏移 0X1000 开始,存放 SRAM APP 代码。这个分配关系大家可以根据自己的实际情况修改,由于 W55MH32只有一个 48K 的片内 SRAM,存放程序的位置与变量的加载位置不能重复,所以我们需要设置 IRAM1 中的地址到 SRAM 程序空间之外。
关于 APP 起始地址的设置方法,我们就介绍到这里,大家可以根据自己项目的实际需求进行修改。
2.中断向量表的偏移量设置方法
VTOR 寄存器存放的是中断向量表的起始地址。默认的情况它由 BOOT 的启动模式决定,对于来说就是指向 0x0800 0000 这个位置,也就是从默认的启动位置加载中断向量等信息,不过 ST 允许重定向这个位置,这样就可以从 Flash 区域的任意位置启动我们的代码了。
我们可以通过调用 sys.c 里面的 sys_nvic_set_vector_table 函数实现,通过以上两个步骤的设置,我们就可以生成 APP 程序了,只要 APP 程序的 FLASH 和SRAM 大小不超过我们的设置即可。不过 MDK 默认生成的文件是.hex 文件,并不方便我们用作 IAP 更新,我们希望生成的文件是.bin 文件,这样可以方便进行 IAP 升级(至于为什么,请大家自行百度 HEX 和 BIN 文件的区别!)。这里我们通过 MDK 自带的格式转换工具fromelf.exe ,如果 安 装 在 C 盘的默认路径,它的位置是C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe,来实现.axf 文件到.bin 文件的转换。该工具在 MDK的安装目录\ARM\ARMCC\bin 文件夹里面。
fromelf.exe 转换工具的语法格式为:fromelf [options] input_file。其中 options 有很多选项可以设置,详细使用请参考光盘《mdk 如何生成 bin 文件.doc》。
我 们可 以通过 在 MDK 点 击 Options for Target→User 选 项卡 ,在 After Build/Rebuild 一栏中,勾选 Run #1,我们推荐使用相对地址,在勾选的同一行后的输入框并写入命令行:fromelf --bin -o ..\..\Output\@L.bin ..\..\Output\%L,如图 所示:
设置生成编译结果文件名
MDK 生成.bin 文件设置方法
通过这一步设置,我们就可以在 MDK 编译成功之后,调用 fromelf.exe,..\..\Output\%L 表示当前编译的链接文件(..\是相对路径,表示上级目录,编译器默认从工程文件*.uvprojx 开始查找,根据我的工程文件 Output 的位置就能明白路径的含义),指令--bin –o ..\..\Output\@L.bin表示在 Output 目录下生成一个.bin 文件,@L 在 Keil 的下表示 Output 选项卡下的 Name of Executable 后面的字符串,即在 Output 文件夹下生成一个 atk_APP.bin 文件。在得到.bin 文件之后,我们只需要将这个 bin 文件传送给单片机,即可执行 IAP 升级。
最后来看看 APP 程序的生成步骤:
1) 设置 APP 程序的起始地址和存储空间大小
对于在 FLASH 里面运行的 APP 程序,我们只需要设置 APP 程序的起始地址,和存储空间大小即可。而对于在 SRAM 里面运行的 APP 程序,我们还需要设置 SRAM 的起始地址和大小。无论哪种 APP 程序,都需要确保 APP 程序的大小和所占 SRAM 大小不超过我们的设置范围。
2) 设置中断向量表偏移量
此步,通过调用 sys_nvic_set_vector_table 函数,实现对中断向量表偏移量的设置。这个偏移量的大小,其实就等于程序起始地址相对于 0X08000000 或者 0X20000000 的偏移。
3) 设置编译后运行 fromelf.exe,生成.bin 文件
通过在 User 选项卡,设置编译后调用 fromelf.exe,根据.axf 文件生成.bin 文件,用于 IAP更新。 以上 3 个步骤,就可以得到一个.bin 的 APP 程序,通过 Bootlader 程序即可实现更新。
2 例程设计
2.1 IAP_Bootloader
该例程实现了一个基础的W55MH32AP引导加载程序,核心亮点包括中断驱动数据接收、Flash操作安全性校验及灵活的调试输出。适用于需远程固件更新(FOTA)或工厂批量烧录的场景。后续可通过协议增强、安全加固和错误恢复进一步提升可靠性,同时需注意中断向量表重映射与内存管理等关键细节。
1. 系统功能概述
该程序是一个基于W55MH32的IAP引导加载程序(Bootloader),支持通过串口接收用户应用程序(APP)的二进制文件,并写入Flash存储。主要功能包括:
串口通信:接收用户程序数据(最大32KB)。
固件更新:通过按键触发将接收到的数据写入Flash指定地址。
应用跳转:执行已烧录的Flash用户程序。
调试输出:通过USART1打印系统信息及操作日志。
2. 核心模块设计
(1)系统初始化:
时钟配置:通过RCC_GetClocksFreq获取系统时钟频率,并通过串口输出SYSCLK、HCLK等参数,验证时钟配置正确性。
外设初始化:延时模块:delay_init()初始化系统时钟,用于后续延时操作。
按键模块:KEY_Init()初始化按键输入,检测用户操作(KEY1:固件更新,KEY2:跳转APP)。
中断配置:NVIC_Configuration()使能USART1接收中断,实现非阻塞数据接收。
(2)串口通信硬件配置:USART1使用PA9(TX)和PA10(RX),波特率115200,8N1格式。接收缓冲区USART_RX_BUF通过__attribute__((at(0X20001000)))指定到SRAM地0x20001000,容32KB。
(3)中断驱动接收:在USART1_IRQHandler中,通过USART_IT_RXNE中断接收数据,逐字节存入缓冲区,并更新接收计数器USART_RX_CNT。主循环通过检测USART_RX_CNT是否稳定(oldcount对比)判断数据接收完成。
(4)IAP逻辑:
固件更新:按键触发:按下KEY1后,检查接收缓冲区长度applenth。
合法性校验:验证应用程序起始地址是否为Flash区域(0x08XXXXXX),防止写入非法地址。
Flash操作:调用IAP_Write_Appbin将缓冲区数据写入Flash目标地址(FLASH_APP1_ADDR)。
应用跳转:按下KEY2后,检查目标地址合法性,调用IAP_Load_App跳转执行用户程序。
关键实现:IAP_Load_App需重置堆栈指针并跳转到用户程序复位中断向量,通常包含以下操作:
typedef void (*pFunction)(void);
pFunction Jump_To_App;
uint32_t JumpAddress = *(__IO uint32_t*)(FLASH_APP1_ADDR + 4); // 用户程序复位向量
Jump_To_App = (pFunction)JumpAddress;
__set_MSP(*(__IO uint32_t*)FLASH_APP1_ADDR); // 重置主堆栈指针
Jump_To_App(); // 跳转执行
(5)调试输出:
- 重定向机制:通过fputc将printf输出重定向至USART1,支持格式化日志打印。
- 关键信息:
- 系统启动时输出时钟配置信息。
- 操作提示(如“KEY1:Copy To FLASH”)。
- 固件更新进度(如“Copy APP Successed!!”)。
3. 关键代码分析
(1)数据接收管理
接收状态检测:
if (USART_RX_CNT) {if (oldcount == USART_RX_CNT) { // 数据接收完成applenth = USART_RX_CNT;USART_RX_CNT = 0; // 清空计数器printf("User program reception complete!\r\n");} else {oldcount = USART_RX_CNT; // 更新旧值,继续等待}
}
通过对比oldcount与USART_RX_CNT判断数据是否接收完毕,避免固定超时等待。
(2)Flash操作校验
地址合法性检查:
if (((*(vu32 *)(0X20001000 + 4)) & 0xFF000000) == 0x08000000) {// 合法Flash地址,执行写入
} else {printf("Illegal FLASH APP!\n");
}
检查用户程序复位向量的高8位是否为0x08,确保目标地址在Flash范围内(W55MH32 Flash起始地址为)。
3 下载验证
3.1 IAP_Bootloader
发送文件,按下KEY1后,程序检查接收缓冲区数据:
若数据合法(复位向量地址为0x08XXXXXX),开始写入Flash:
若数据非法(非Flash地址),打印错误信息:
若未接收到数据,提示:
按下KEY2后,程序检查目标地址(FLASH_APP1_ADDR)
若地址合法(复位向量地址为0x08XXXXXX),跳转执行用户程序: