本文主要介绍BootLoader程序升级的原理和一些代码的分析,试图以通俗易懂的语言描述BootLoader升级的主要要点。
BootLoader升级原理概述
当我第一次接触该文章时,有一个称为IAP(在应用程序编程中)的概念。通常,它是通过现有程序(我们称为BootLoader程序)升级另一个程序(用户程序)。升级方式很多,可以通过串行,USB,SPI等接口进行升级。实际上,我们将需要升级的芯片分为两个区域,临时称为区域A和区域B。
A区域主要存放BootLoader程序,B区域主要存放用户程序,也就是我们希望升级或修改的程序。
一般情况下,为了升级流程的方便,我们会把A区域布置在芯片flash(有人喜欢称ROM,就是存放代码的区域)的起始位置,也就是0x0开始的位置,至于A区域在哪里结束,这需要看你的BootLoader程序有多大了,它能占用多少的代码量了。比如你的BootLoader程序编译完后有2.5KB左右的大小,那么你可以计算一下:
2.5K = 2.5 X 1024 = 2560B = A00(H)
也就是说,你的这段代码如果从零地址开始存放的话,他会在0xA00的位置结束,0xA00之后的区域你便可以用来存放需要升级的用户代码了。但有时我们并不会紧接着0xA01的位置开始放置用户代码,而是会留出一定的空间,比如从0xB00处开始存放代码,这主要是因为BootLoader程序在flash中存放时不一定会紧挨着存放,有时代码段之间会有空闲区域,这一点可以参考之前的文章 IAR编译器如何节省代码占用的flash空间?然后,我们通过在BootLoader程序中设置相关参数(应用程序起始位置等),使应用程序升级时按照我们设置的位置存放在B区域,从而完成升级。这一点我会在后面的代码详解中介绍。
我们可以通过下面的图解来理解bootloader程序升级时的区域占用情况。
需要了解的关键点
进行BootLoader程序编写之前,我们需要了解并熟悉以下几个关键点:
文件传输协议。因为升级时需要上位机软件配合下位机的BootLoader程序进行应用程序代码的传输,因此文件传输协议至关重要,笔者推荐使用Xmodem 1K协议。 这个协议的好处便是上位机可以自动打包数据,每一包数据含有1k字节的代码,传输效率很高,传输时间很短。
2. 芯片空间map。 做BootLoader升级相关的项目,肯定离不开对芯片空间的了解,需要对自己所用芯片的RAM、ROM以及向量表(如果有的话)等占用情况有比较深入的了解。
3. 跳转函数。这个是程序从BootLoader程序跳转到应用程序运行的关键。笔者在做项目时曾经在这一块浪费了不少的时间。文末笔者会提供实用的跳转函数。
升级代码解析
其实前面说的再多,脱离了代码都是纸上谈兵。下面通过一个实际的BootLoader升级例子,结合笔者自己编写的BootLoader代码,对这一过程进行解析。
代码主要包括
main.c
BootLoader.c
xmodem-1k.c
跳转函数
其中,
main.c主要是升级执行初始化及升级完成后初始化升级环境及跳转代码实现的部分。
BootLoader.c部分主要是升级流程的代码控制。
xmodem-1k.c主要是文件传输协议的代码实现。
至于其他部分的代码,比如串口相关以及时钟相关的代码,每种芯片的编程方式都不尽相同,因此笔者不详细介绍这部分,该部分代码大家可以从需要升级的应用程序中直接移植即可。
main.c
int main (void)
{
SystemCoreClockUpdate(); // 时钟初始化
WatchDog_Initial(); // 看门狗初始化
vBootLoader(&vScene_Init,&vScene_Renew); // BootLoader主程序
}
在vBootloader()这个函数中用到了两个函数指针,分别指向初始化函数vScene_Init()和环境重置函数vScene_Renew()。初始化函数很好理解,在运行程序之前,先对芯片时钟、管脚等初始化,或者有些参数需要初始化,这个根据自己的代码情况进行选择。
那么环境重置函数什么意思呢?这主要是为了和需要升级的应用程序的运行想配合,因为我们的bootloader程序的相关配置有时候并不一定会和应用程序的配置完全一致,如果运行完BootLoader之后,没有把BootLoader程序的相关配置关闭掉或者恢复到默认值,运行到应用程序之后,还可能会执行BootLoader程序的配置,这样会出现问题。举个栗子,在BootLoader程序中用中断喂狗,跳转到应用程序之前,没有关闭喂狗中断,如果在应用程序中没有配置相关喂狗中断的程序,那么应用程序仍然会按照bootloader的配置执行中断喂狗,这样会导致应用程序中的喂狗失效,因为中断喂狗是很准时的,往往起不到喂狗的效果,有时会影响程序的复位操作。因此,环境重置函数说白了,就是把bootloader用到的配置关掉。笔者建议把用到的所有的东西全部关闭(包括但不限于串口、时钟、看门狗、IO等),因为在应用程序中会根据自己的应用程序配置相关的代码。
BootLoader.c
/**********************************************************
* BootLoader流程控制函数
** 参 数: pfunSenceInitCallBack 初始化芯片指针函数
** pfunSenceRenewCallBack 重新初始化代码环境指针函数
** 返回值: 无
**********************************************************/
void vBootLoader(void(* pfunSenceInitCallBack)(void), void (* pfunSenceRenewCallBack)(void))
{
uint8_t ucMessage = 0;
unsigned int sp;
unsigned int pc;
uint16_t bootflag_read;
sp = APP_START_Flash;
pc = sp + 4;
pfunSenceInitCallBack(); //初始化函数指针,具体函数怎么写这里不再赘述
while(1)
{
wdt_feed();
do{
ucMessage = u8UpdateMode(); // 此函数为升级主函数
if (UPDATE_OK == ucMessage) /* 升级成功 */
{
memcpy(erase_pg_buf, bootflag_OK, sizeof(bootflag_OK));
update_bootflag();
break;
}
else if (UPDATE_NO == ucMessage) /* 没有升级 */
{
break;
}
else /* 升级错误 */
{
memcpy(erase_pg_buf, bootflag_ERROR, sizeof(bootflag_ERROR));
update_bootflag();
break;
}
} while (1);
bootflag_read = *( volatile uint16_t *)(BOOT_FLAG_ADDR); /* 读取存放在bootflag地址的值 */
if (u8UserCodeEffect() == USERCODE_OK) // 代码判断
{
if (bootflag_read == 0xAA55)
{
pfunSenceRenewCallBack(); // 环境重置函数
vControlSwitch(sp,pc); // 跳转函数
}
}
}
}
上面是bootloader程序擦写完flash之后判断是否升级成功以及执行跳转函数的代码。流程主要是,升级主函数u8UpdateMode()(下面是详细代码)中进行数据接收校验以及flash擦写工作,如果擦写成功,该函数返回0(UPDATE-OK),擦写失败,该函数返回1(UPDATE-ERROR),没有擦写操作,该函数返回2(UPDATE-NO)。在这个函数中根据相关返回标志进行处理。处理完去读取flash中存放BootLoader标志的地方的数据,如果使我们希望的数据,我们就执行跳转函数,让程序从BootLoader跳转到应用程序中,如果标志不正确,说明升级过程出了问题,我们就不跳转,一直运行在BootLoader程序中。当然,在跳转之前需要执行我们之前提到的环境重置函数pfunSenceRenewCallBack()。
// 升级主函数
static uint8_t u8UpdateMode(void)
{
uint8_t ret;
if (iap_prepare_sector(APP_START_SECTOR, APP_END_SECTOR) == CMD_SUCCESS) // 准备扇区
{
if (iap_erase_sector(APP_START_SECTOR, APP_END_SECTOR) == CMD_SUCCESS) // 擦除扇区
{
ret = u8Xmodem1kClient(ProgramFlash, (uint16_t)BOOT_DELAYTIME_C, (uint16_t)BOOT_WAITTIME_UPDATE);// 编程指针+X-Modem协议识别
if (0 == ret)
{
return UPDATE_OK;// 返回标志
}
if (2 == ret)
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』