三、MCU中BootLoader实现的方式
要先将BL的代码通过下载器烧录到MCU中,然后再通过CAN总线将接收到的app代码写入到指定的Flash扇区中,校验接收的app代码,如果完全正确,则将程序指针指向app的入口地址,完成了BL向app的转换。在此过程中要解决下面几个问题:
1)BL代码的CMD文件如何编写,即BL阶段,MCU内存如何分配。
2)对应的app的CMD文件中,如何分配MCU内存。
3)app的编译输出文件.out用什么格式传送给MCU。
4)接收app代码的协议和Flash写入机制
5)如何指向app的入口地址
1)BL的CMD文件一般分成两部分
上图中,CMD文件是在以前编写的F28034.CMD文件基础上改写的,其实是F28035内存的分配方式
从上图中,可以看出来,BL与app的Flash存储区域是分开的。而且app明显要比BL区域大的多。当将BL下载到MCU中时,要同时将所有的Flash扇区擦除。
接收的app代码做为CAN传送的数据,需要一个缓冲区。为了避免数据溢出,在内存选择了一段速度快且容量大的区域做为CAN数据缓冲区。
可以看到,将RAML0-3这段连续的区域合并起来使用,容量是8K×16bit当做数据缓冲。如果app用到CLA外设,会占用RAML1-L3的一部分空间,这样会和BL的内存分配产生冲突吗?
并不会。因为app被引导进入自己的_c_int00后,会重新按照app本身的CMD分配方式分配RAML0-3代码或数据的存放。
这样就将BL的CMD编写完成。
2)app的存储分配
可知,app的Flash的分配与BL的Flash分配完全一致,而RAMM0-1、RAML0-3则完全不一样,但这不影响app的正常运行。
app程序可以先独立开发,即不通过BL下载到MCU中。app编译完成后,用TI的XDS100V3下载器烧录到MCU中,只要避开FlashA扇区的使用即可。
3)app的编译输出文件.out用什么格式传送给MCU。
当app编译完成后,会输出一个.out文件,但我们不可能用CAN传送这种格式的文件内容。在TI官方的BootROM中,关于串口或CAN的引导方式中,提到了一种app代码文件的传送方式。将.out文件转换成数据流结构。具体请参考TI的文档SPRUGO0A.PDF的第22页。
这里提到用hex2000.exe工具将.out转换成流数据文件,然后再通过CAN总线传送到MCU。MCU收到后,就按照该文件指定的方式,将app代码写入到指定的Flash扇区。
其实就是将.out转换成ASCII-Hex格式
转换完成
一共生成了5个块.cinit、.text、codestart、ramfuncs、Cla1Prog。
这个后缀为.a00的文件可以用记事本打开。
根据SPRUGO0A.PDF这篇文档的说明,将此文件处理了一下,方便观察。
可以看到hex2000将.out文件转换成几个块状的数据结构,这几个块就是要写入到Flash对应扇区的数据。最上面有该app的入口地址(0x003E F293) 。还可以查看编译后生成的对应.map文件。
入口地址确实是0x003E F293。然后再对比一下第一个段的地址和大小
从上面可以知道,只要按照生成的.a00文件的内容,依次将收到的app代码写入到指定的Flash地址中,然后在BL中将程序指针指向.a00的入口地址,就能实现BL启动app的目地。
4)接收app代码的协议和Flash写入机制
接收app代码的协议是根据自己的习惯,自行制定的,没有一定之规。
作者的方法是:
- 上位机先将.a00文件转换成数据流结构,然后上位机会以.a00文件中每个块做为一个段,以段为单位向MCU发送段的内容。
- 先传送此段的大小(长度)和地址。
- 当MCU收到段长度和段地址后,会发送一个确认信号给上位机。上位机收到确认信号后,再传送此段的内容。这个段的长度、地址和内容经过CAN接收,全部缓存在MCU的内存RAML0-3中,此内存区域容量为8K×16bit,所以每个段的大小不能超过此数值,否则会出现溢出。目前,作者编写的MCU程序代码的每段大小还没有超过此数值的。
- 一个段传送完成后,MCU一次性将RAML0-3中的内容写入到段指定的Flash地址中。然后再进行下一个段的传送。重复2、3的步骤
- 当读到某个段的大小为0时,表示所有段都传送完毕,接着传送app的入口地址,要将这个入口地址也保存在Flash里,通过前面BL和app的CMD存储区域的划分,可以知道,有一个FlashB扇区既没用于BL也没用于aap。此区域就是用来存储app入口地址的。
- 进行app代码的校验。校验成功后,通过看门狗复位,MCU重启,BL在规定时间内收不到PC端上位机发出的握手信号,则自动进入app的程序空间。
5)BL是如何指向app的入口地址的
首先,上位机已经正确将app的入口地址传送给MCU,MCU将入口地址存放在FlashB扇区。声明一个32bit的无符号整型变量,如下:
volatile Uint32 ProgramEntry = 0;
然后,读出FlashB中存储的入口地址,并声明的变量指向入口地址。
当BL在规定的时间内,没有接收到上位机的握手信号,则执行下面的语句。
(*((void(*)(void))ProgramEntry))();
-
(void(*)(void))
:这是一个类型转换,它将ProgramEntry
转换为一个指向返回void
类型且不接受任何参数的函数的指针。换句话说,它将ProgramEntry
视为一个函数指针,该函数不返回任何值,也不接收任何参数。 *(...)
:这是间接引用操作符,它用于调用函数指针指向的函数-
(*((void(*)(void))ProgramEntry))();
:将上述所有部分组合起来,这行代码首先将ProgramEntry
转换为一个函数指针,然后调用这个函数。
用上面的方法就可以将程序指针指向ProgramEntry
变量存储的入口地址。(待续)