目录
前言
主要是技术实现,完成一个完整的bootloadr编写+App下载+芯片加密的流程,原理不深究,如果对FLASH内存划分了解充分的可以跳转到第四步。
一、存储器映射
市面上绝大部分32位单片机都遵循以下结构进行内存划分(每个开发板的内存划分结构相同,但是内存大小不同,需要参照自己的用户手册进行开发)
最主要看的是三块地方Code RAM 和 Peripher als分别是用户代码区 运存和外设寄存器
1、外设寄存器
顾名思义就是存放外设寄存器的空间,与IAP无关,只是顺便了解
2、运存
使用的存储器是随机存取存储器,读写速度快,容量小,掉电丢失,除了只读变量和常量,其他变量都存在这里面。在判断某块内存是否有程序时会使用到。此开发板的运存大小是64K,所以大小从0x2000 0000 划分到 0x2001 0000
3、用户代码区
用户代码区又被细分为以下几块,进行IAP开发需要重点了解下面的内存划分。
1、Main memory
主存,也就是大家最熟悉的FLASH大小,我使用的开发板FLASH大小是384K,所以主存就从0x0800 0000划分到 0x0806 0000 正好384K,后续的IAP开发中也是在这一块内存上做文章。
2、Informatin block
信息块,存放系统信息,不算在384K的FLASH大小中,其中System memory存放系统的Bootloader,也就是系统帮你写好的IAP程序,方便你在没有J-Link或ST-Link等下载器的时候通过串口/IIC接口/SPI接口等等下载程序。但是不支持自定义,当我们想要自己更新程序或者板内下载多个程序的时候就需要编写自己的Bootloader了。
还有一个需要了解的就是UID,每个开发板都有自己唯一的UID,可以用来实现芯片加密,这样程序被盗版到其他板子上就无法运行。这个工作可以在进行IAP开发时顺便完成。
二、IAP内存划分
需要注意,上面介绍的是存储器映射,实际代码下载的地方是Main memory
要实现IAP就需要在一块主存中同时下Bootloader和APP,Bootloater用于检测更新标志,并进行更新或者进行跳转,具体实现在下一篇,这篇先对主存进行内存划分。
在单片机的入门学习中没有进行主存划分,所以主存映像如下图所示
进行IAP开发,最简单的主存划分如下,每块的大小都可以自定义,一般Bootloader大小是20K~100K,用户参数1K。在高级的开发中还会用到外部存储器,如SD卡或EEPROM但原理都相同。
本文中主存划分如下图所示
#define BOOTLOADER_ADDR 0x08000000 //32K BLOCK0
#define APP_ADDR 0x08008000 //160K BLOCK1~BLOCK5
#define APP_BUFFER_ADDR 0x08030000 //160K BLOCK6~BLOCK10
#define APP_ARG_ADDR 0x08058000 //2K BLOCK11
了解完IAP需要的主存划分,接下来就是本文的重点,怎么让Bootloader和APP下载到指定的主存空间上。
三、配置FLASH下载设置
bootloader和APP是两个不同的工程,配置不同,下载时先下载Bootloader后下载APP,后续的下载就只需要下载APP。
1、Bootloader下载设置
2、APP下载设置
如果选择了Erase Full Chip之前的Bootloader就会被擦除
APP需要额外设置向量表偏移,在system_xx32xx.c中的SystemInit设置,VECT_TAB_OFFSET就是从0x0800 0000 开始的偏移量,偏移到APP的起始地址即可。
本文偏移0x8000,即偏移到0x0800 8000,就是APP的起始地址,这里需要注意的是需要按0x200对齐。就是说偏移量必须是0x200的倍数
设置完上面的设置后就可以将Bootloadr和APP下载到同一块主存的不同区域了。
四、单片机启动流程
可以参照这篇文章STM32启动流程详解,这里主要需要知道的是配置成从主存中启动会初始化系统并执行Bootloader,Bootloader解密和更新判断后需要跳转到APP,要跳转到APP就需要访问APP程序的(中断)向量表,找到APP的初始化函数入口地址和主函数入口地址。这个地址存放在Reset_Handler(复位中断服务程序)中。
而Reset_Handler存放在向量表的第二字,因为地址是32位对齐,所以Reset_Handler的地址是APP_ADDR + 4,由此可以得出,执行APP_ADDR + 4这里的代码就会进入APP的复位中断服务函数,继而执行SystemInit和main这两个函数。这就是跳转到APP的原理,接下来代码实现跳转
五、Bootloader编写
这里给出一个最简单的Bootloader模板,更新标志是APP设置的,APP通过串口/IIC/SPI等接口收到更新包时写入主存中的APP缓冲区。接收完毕后复位就会执行Bootloader,Bootloader检测是否需要更新,需要就把缓冲区的数据搬运到APP同时进行数据校验。不需要更新就跳转到APP,具体的实现下一篇介绍。
uint8_t uid[12] = {0};
int main(void)
{
FLASH_Read(UID_ADDR,(uint16_t *)uid,6);
/* Reset of all peripherals, Initializes the Systick. */
HAL_Init();
/* System clock configuration */
APP_SystemClockConfig();
//定时器2初始化
MU_Tim2_Init();
//解密
MU_Decrypt();
//检测更新标志
if(IAP_UpdateFlag_Read() == APP_UPDATEFLAG)
{
//清标志位
IAP_UpdateFlag_Clear();
//搬运数据
//...
}
//没有就跳转主程序
else
{
IapLoadApp(APP_ADDR);
}
while(1);
}
跳转函数的实现,if(((*(uint32_t*)AppAddr) & 0x2FFE0000) == 0x20000000)用于判断栈空间是否在RAM中被开辟。本文的RAM大小是0x10000,所以需要 & 0x2FFE0000判断是否越界,如果越界就代表栈没有分配内存即该地址不存在向量表,进而得出该地址没有APP。
__asm void MSR_MSP(uint32_t addr)
{
MSR MSP, r0
BX r14
}
typedef void (*IapFun)(void); // 声明一个函数指针,用于跳转到绝对地址执行程序
IapFun JumpToApp;
void IapLoadApp(uint32_t AppAddr)
{
if(((*(uint32_t*)AppAddr) & 0x2FFE0000) == 0x20000000)
{
JumpToApp = (IapFun)*(uint32_t*)(AppAddr+4);
MSR_MSP(*(__IO uint32_t*)AppAddr);
__set_PRIMASK(1);
JumpToApp();
}
//该地址没有APP
else
{
//...
}
}
原理挺复杂,只需要知道AppAddr+4就是APP的main函数的入口地址,如果上面的下载设置没错就会执行APP的main函数。此过程没有复位,所以在Bootloader中的初始化会保存。在APP中就不需要再次初始化。
六、芯片加密
在进行IAP开发中可以顺便完成芯片加密,关于芯片加密的具体实现上一篇有详细介绍,这里介绍一下下载流程。首先将加密程序下载到FLASH中,最普通的下载方式即可,加密程序执行完后生成加密序列号,保存到一个不会被擦除的空间如上面划分的用户参数区、备份寄存器、外部存储器等。执行一次后加密程序的任务就完成了。然后就可以下载Bootloader和APP。将原本的加密程序覆盖。
总结
IAP实现起来不是很难,但是需要了解的知识很多,包括存储器映射、单片机内存划分、FLASH读写操作、单片机启动流程、程序的内存分配(向量表的存放位置)和一点点的汇编代码知识等,掌握了这些再回头看IAP也不过如此。