一、简介
在实际工程项目应用时,人工烧录升级程序耗时长,效率低,接下来介绍一种利用程序来升级程序的在线升级方案。
在线升级程序可分为两个部分:APP程序、BOOT程序。
boot程序类似TI的bootloader,实现上电引导功能,一般处于不会被擦除的FLASH段。需要具备以下几项功能:
①通信功能:进行流程交互、获取升级数据;
②FLASH的擦除、编程、校验功能;
③程序跳转功能:跳转到app程序。
app程序是真正的用户程序,也是升级的目标。需要具备以下几项功能:
①用户逻辑实现;
②程序跳转功能:跳转到boot程序,进行升级。
二、DSP上电流程
dsp上电后会从0x3FFFC0处开始运行,此处存放跳转到0x3FF16A的指令,执行TI的bootloader,执行完毕会跳转到BEGIN(0x80000),cmd文件可看到此处放置了codestart。
codestart位于F2837xD_CodeStartBranch.asm中,将跳转到_c_int00。
_c_int00在boot28.asm中,主要进行以下几部分工作:
(1)初始化系统堆栈
(2)进行全局变量的初始化(.cinit段)
(3)全局对象构造(.pinit段,仅c++需要)
(4)跳转到main()
(5)等待main函数return,执行exit()
三、FLASH API使用
参考TI官方API使用手册,主要了解下图部分。
TMS320F28377xD系列FLASH存储映射如下图:
四、方案介绍
1、方案一:app与boot为两份程序
该方案app与boot为独立的两份程序。举例,假设将FLASH分割成两部分,0x80000为boot起始地址,0x84000为app的起始地址,那么0x80000将存放boot的codestart,跳转到boot的main(),进行相关逻辑判断,跳转到0x84000处,此处存放app的codestart,最终执行app的main()。
调试时,如何确保在仿真boot或app时,不会擦除另一份程序,接下来将介绍如何在一个芯片上同时烧录两份代码。如下图所示,可以在工程配置里配置仿真烧录时,仅对那些扇区进行擦除操作,确保不会擦除到另一份代码的区域。
2、方案二:app与boot为同一份程序
该方案app与boot为同一份程序,或者说在一份程序里。那么就需要仿写codestart/_c_int00,因为正常流程codestart --> _c_int00 --> main并不是我们期望的,我们期望的是boot_codestart --> boot_c_int00 --> bootMain --> app_codestart --> app_c_int00 --> main,我们需要仿写出属于boot的codestart,跳转到boot的_c_int00,在其中进行boot的全局变量初始化等动作。
3、方案对比
接下来将从下面几方面对两个方案进行比较:
(1)cmd文件配置
首先,先来了解一下cmd的配置,CMD文件分为两部分:MEMORY、SECTIONS。MEMORY中又将内存分为Data、Program两大类,每一类内再细分不同的内存区域;SECTIONS中则是不同的段名,并定义这些段分配到MEMORY中的哪一个内存区域。默认的段名有以下几种:
①.cinit : 用于存储程序中使用的全局变量、静态变量等的初始化值;
②.pinit : 全局构造器(C++语言用)程序列表;
③.text : 编译后生成的二进制指令代码段,即所有可执行的代码和常量;
④.econst : 包含由far const关键字修饰的字符串常量和初始化的全局变量和静态变量的初始化列表;
⑤.switch : 包含switch声明的列表。
⑥.ebss:程序上电后,.cinit段中数据会复制并存储与.ebss中
接下来,我们从cmd文件配置来讲两个方案的不同点。针对MEMORY而言,两种方案都是一致的,规划好如何将内存资源分配给boot、app即可;但对于SECTIONS而言,由于第一个方案app与boot是独立的程序,所以仅需要在这部分将对应的段分配到属于自己的内存即可,但是第二个方案中,app与boot是在同一份代码中,所以它们是共用一个段的。
这会导致一个问题:假设原来boot程序中有个变量a(地址是0xC000),当app部分程序变动时,新增了别的变量,这些变量存放在同一个段中,这时该段中的变量相当于重新洗牌,变量的位置将发生改变,变量a的地址可能变成0xC020,但是boot程序是不变的,boot程序中调用变量a时,是从地址0xC000处取值,而不是0xC020,其他字段同理,这将导致boot程序是异常运行的。
所以,第二个方案需要将各个段名中属于boot程序的单独提取出来,定义新的段名。当写好boot程序(app程序仅有一个空的main函数)后编译,生成debug文件夹下的.map文件,就可以从.map文件中看到各个段中有什么是属于boot程序的。
假如编译后的.map文件中有如下图的.cinit段:
可以通过下面配置固定好boot的cinit段,其中固定地址是因为可能存在多个段分配到同一个内存区,这时每个段的先后也是未知的,同样存在地址变动的情况,所以要固定地址;START是为了定义一个全局变量,此变量存储boot_cinit段的起始地址,作为指针用于boot_c_int00中进行全局变量初始化,系统默认段名.cinit在链接时会自动定义cinit的全局变量。
boot_cinit :
{
F2837xD_struct.obj (.cinit:_TRIP_SEL)
F2837xD_struct.obj (.cinit:_EPWM)
} > 固定地址或内存区名 PAGE = 0 START(boot_cinit)
(2)仿真调试
仿真调试时,由于方案一是独立的两份程序,所以只能单独仿真调试某一份,跳转过程是无法仿真调试的,但是方案二是一份程序,所以是能通过仿真调试仿真跳转流程的,易于排查问题。
(3)重复功能代码利用率
如果boot与app走的是同一份通信协议,或者某些模块功能时同样的,在方案一中,这部分代码在app与boot中同时存在,占用多一倍内存;但是在方案二中,这部分代码是可以只在boot程序中,app只需调用即可,减少冗余代码。