历史上的今天
返回首页

历史上的今天

今天是:2024年12月11日(星期三)

正在发生

2019年12月11日 | 基于ARM Cortex-M0+内核的bootloader程序升级原理及代码解析

2019-12-11 来源:eefocus

本文主要讲述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程序编写之前,我们需要了解并熟悉以下几个关键点: 

  1. 文件传输协议。因为升级时需要上位机软件配合下位机的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)

            {

                return UPDATE_NO;

            }


        }

   }

   return UPDATE_ERROR;

}


主函数代码不难理解,进来之后先准备相应的扇区,然后擦除(FLash内部值置全FF),然后启动X-Modem协议接收数据,数据接收完成启动写flash函数ProgramFlash()进行代码烧写,这一部分在下一节X-Modem-1K.c中讲。不同的片子的烧写流程不同,这个得看芯片手册,有的需要准备扇区,有的不需要,但是大多数流程都保留了先擦除后烧写的内容。


注意:擦除和烧写之前需要看技术手册搞明白芯片是支持区块擦除还是支持页擦除。


X-modem-1k.c

关于X-Modem协议是什么,大家可以自行去百度,这里也不再赘述。 

先看代码,比较长:


/**********************************************************

** Xmodem1k协议传输程序

** 参  数:    pfunPktHandle,: Xmodem1k协议传输所需函数结构体指针

**          u16ShortDly:    轮询发送C字符的时间间隔

**          u8LongDly:      等待传输开始超时时限

** 返回值: 传输结果: 0--成功,1--升级失败(错误或取消升级),2--没有升级

**********************************************************/

uint8_t u8Xmodem1kClient(pFunPKTHAND pfunPktHandle, uint16_t  u16ShortDly, uint16_t u8LongDly)

{

    uint32_t u32ByteCnt   = 0;                                          /* 位计数器  计数一包的第几个字节数据   */

    uint8_t  u8TimeoutCnt = 0;                                          /* 超时次数                             */

    uint8_t  u8DataerrCnt = 0;                                          /* 数据错误次数                       */

    uint8_t  u8PktIndex   = 1;                                          /* 包序号期望值                       */


    uint8_t  u8STATE = STAT_IDLE_C;                                     /* 状态变量                             */

    uint8_t  u8Data;                                                    /* 存放接收数据及发送命令              */

    volatile uint16_t u16PktLen;                                        /* 包中有效数据的长度                */

    uint8_t  u8Message;


    sysTimerClr(1);


    while (1)

    {

        wdt_feed();


        switch (u8STATE)

        {

        case STAT_IDLE_C:                                               /* 轮询发C状态                  */

             if (sysTimerGet(1) >= u8LongDly )

             {

                u8STATE = STAT_TIMEOUT_C;                               /* 等待开始超时,跳到结束状态   */

             } 

推荐阅读

史海拾趣

Holy Stone公司的发展小趣事

近年来,Holy Stone不断加大在技术研发和产品创新方面的投入。公司致力于研发适用于节能产品应用所需的被动元件,帮助客户提升产品能源转换效率。在全球汽车电子化发展的背景下,Holy Stone正式跨入车用电子供应链,以提供优质元件的能力协助汽车电子达到最佳数位化。这些技术创新不仅推动了公司产品的升级换代,还为公司开拓了新的市场领域和增长点。

以上五个故事展示了Holy Stone公司在电子行业中的发展历程和成就,每个故事都体现了公司在不同方面的努力和成果。

FRIWO公司的发展小趣事

作为全球化的技术型企业,FRIWO在全球范围内设立了研发、生产和营销部门,以满足不同市场的需求。FRIWO的电源解决方案业务部(FPS)专注于为客户量身订做电源解决方案,其客户群遍布医疗科技、IT通讯、家用电器、移动工具以及工业应用等多个领域。为了更好地服务本地客户,FRIWO在不同地区设立了分支机构,确保能够快速响应客户需求并提供专业的技术支持和服务。这种全球化布局与本地化服务的策略,使得FRIWO在激烈的市场竞争中保持了强劲的竞争力。

Dialog公司的发展小趣事

近年来,Dialog公司通过一系列收购活动实现了快速扩张。例如,Dialog公司收购了Adesto公司,进一步拓展了工业物联网市场。这次收购为Dialog公司带来了一系列新的智能楼宇自动化工业解决方案,为其现有的制造自动化产品提供了非常好的补充。此外,收购Adesto还为Dialog公司带来了近5,000家客户公司,这些公司中大部分对Dialog公司来说是新客户。

Freqtech Ohg公司的发展小趣事

在电子行业的激烈竞争中,Freqtech Ohg公司深知技术创新的重要性。公司投入大量研发资源,专注于高频电子技术的研发。经过数年的努力,Freqtech成功开发出一款高性能的高频滤波器,该产品在信号传输效率和稳定性方面远超同类竞品。这一技术创新不仅为公司赢得了国内外多个重要客户,还使得Freqtech在高频电子领域迅速崛起,成为行业内的佼佼者。

Cornell Dubilier公司的发展小趣事

在1936年至1962年期间,Cornell Dubilier公司在汉密尔顿工业园运营,专门制造电子零件和组件。然而,在这一时期,公司在生产过程中使用了含有多氯联苯(PCB)和其他有害物质的材料,并将其直接倾倒在现场土壤中。这一行为导致了严重的环境污染问题,使得该场地在后续被众多商业企业占据后,仍面临着环境保护的挑战。

ACE [ACE Technology Co., Ltd.]公司的发展小趣事

随着时间的推移,Cornell Dubilier公司不断致力于技术创新和产品升级。在电力电容领域,公司取得了显著的成果,其产品广泛应用于飞机、激光医学技术、替代能源、电焊机及发电机等多个领域。这些技术的突破和应用,不仅提升了公司的市场竞争力,也为电子行业的发展做出了贡献。

问答坊 | AI 解惑

红外学习型万能解码遥控开关及调光调速器

有需要请联系我  免费的  截止时间2009.6.28(上班了,就没时间了) [ 本帖最后由 sjl105105 于 2009-6-26 12:31 编辑 ]…

查看全部问答>

在绘制原理图时如何设置标题

在protel99绘制原理图中,要求设置标题。为什么在disign菜单option中设置标题,为什么显示不出来呢…

查看全部问答>

要下载一个设备的SDK,大家帮我看看应该下哪个

CE上开发MC3000的程序,我去MC3000下他的SDK,没有搞清楚应该下载下面的哪个. 1:Platform SDK for MC3000 v1.1 with Windows CE 5.0 Core 2:Platform SDK for MC3000 v1.1 with Windows CE 5.0 Professional 不知道这两个有什么区别. 另外还有一 ...…

查看全部问答>

74HC244内部的三态门是如何实现缓冲和驱动的?

我在用proteus做LPC2132的串口通信仿真时,外接一个虚拟终端和示波器,用示波器来查看数据发送的波形 当把示波器和虚拟终端接在一起然后连到Txd0 Rxd0上时,虚拟终端上面不能显示LPC2132发过来的数据,但是示波器可以看到LPC2132串口数据发送的波 ...…

查看全部问答>

LabVIEW关于定时的研究

1.如果想实现定周期While循环时,需要用Wait(ms).vi而不是Wait Until Next ms Multiple.vi2.Wait(ms).vi在与代码并行时可以保证整个运行时间为设定值,这个功能一般都会以为是Wait Until Next ms Multiple.vi的特性3.Wait Until Next ms Multipl ...…

查看全部问答>

FPGA设计中的异步信号处理

        近来一直在学习在fpga如何进行异步信号的处理,有一些心得,开个贴,把自己的一些体会贴出来,跟大家分享,也期待可以得到高手的指点。       目录如下:  &n ...…

查看全部问答>

IAR中如何在执行main()之间关闭看门狗

IAR中如何在执行main()之间关闭看门狗?论坛上有人问,但是说的不具体。不知道怎么弄。会的告诉一下,谢谢! …

查看全部问答>

基础的LED灯和数码管同时点亮的问题

如题,我在练习的时候遇到了一个可能比较基本的问题:不通过定时器,只写一个delay函数来实现LED灯的闪烁和数码管的点亮。 我自己用这种方式写的(8个LED闪烁,点亮2个数码管),无法实现上述功能。LED能闪烁,但是两位数码管中的第一位不能稳定显 ...…

查看全部问答>

1602取字模软件及其设置

看到论坛里面有用1602显示汉字的,很好奇,也整弄了下 找了个区模的软件,能很好的取模,共享给大家 第一步:先新建一个 x*y  的模; 第二步:自己一个点点自己点; 第三步:使字模左右翻转下; 第四部:设置(看图片) 第五部: ...…

查看全部问答>