1. 汇编文件正文的第一句
include bl_config.inc
包含bl_config.inc,这个文件是什么,从哪里来,有什么作用?再看bootloader工程Options---User---Run User Programs Before Build/Rebuild内的用户命令(见图2-2)又是什么?
图2-2
所有的一切,要从keil MDK的汇编器说起,在启动代码中要用到配置文件bl_config.h中定义的一些配置选项,但因为MDK汇编器不能通过C预处理器运行汇编代码,所以bl_config.h中的相关内容需要 转化为汇编格式并包含到MDK的启动代码中。这需要手动运行C预编译器进行格式转化。图2-2中红色部分圈出的内容正是为了完成这个转换。在点击Build/Rebuild编译按钮之后,会先运行图2-2指定的命令,再进行编译。先来分析一下这条命令:
armcc --device DLM -o bl_config.inc -E bl_config.c
这条命令的作用是将bl_config.c(包含bl_config.h文件)进行而且仅进行预编译处理,并生成bl_config.inc文件。
armcc是Keil MDK提供的C编译工具,语法为:
armcc [Options] file1 file2 ... file n
介绍一下这里用到的Options选项:
--device<dev>:设置目标的设备类型,DLM为Luminary的设备标识。
-I<directory> :目录列表
-E :仅执行预处理
-o<file> :指定输出文件的名字
2. 看一下目标板上电后启动代码的运行流程
上电后程序先到Flash地址0x00处装载堆栈地址,这跟以前接触过的处理器不同,以前0x00处都是放置的复位处理代码,但Cortex M3内核却不是,0x00处是放置的堆栈地址,而不是跳转指令。
堆栈设置完成后,跳转到Reset处理程序处,调用处理器初始化函数ProcessorInit,该函数将bootloader从Flash拷贝到SRAM,将.bss区用零填充并将向量表重映射到SRAM开始处。
之后跳转到Reset_Handler_In_SRAM函数,在该函数中,如果用户提供了底层硬件初始化函数(在bl_config.h中使能),则调用这个函数。然后调用CheckForceUpdate函数,检查是否有升级请求。如果没有升级请求,跳转到CallApplication函数,在该函数中,将向量表重映射到应用程序开始处(这里为地址0x1000),装载用户程序堆栈地址,跳转到用户程序的Reset服务函数。
如果调用CheckForceUpdate函数检测到有升级请求,则配置以太网,跳转到升级程序UpdateBOOTP处执行。
3. 如何在用户程序中调用升级程序
用户程序存在于Flash地址0x1000处,bootloader存放于Flash地址0x00处,并且用户程序在执行的时候已经将向量表重映射到了Flash地址0x1000处了,那么应用程序是如何调用位于bootloader中的升级程序呢?
再看bootloader启动代码的中断向量表,在Flash地址的0x2C中存放的是CPU SVC异常服务跳转地址:
dcd UpdateHandler ; Offset 2C: SVCall handler
而bootloader正是用这个异常来处理升级请求的。那么,应用程序只要执行该地址处的跳转指令,就能进行一次程序升级,在应用程序中的swupdate.c中,使用了如下C代码来执行位于Flash地址0x2C内的跳转程序:
(*((void (*)(void))(*(unsigned long *)0x2c)))();
对C语言还没有入门的同学可能会比较的头痛,这像谜一样的语句是如何执行位于bootloader的SVC异常服务例程呢?还是分解一下吧:
(*(unsigned long *)0x2c):将0x2C强制转化为unsigned long类型指针,并指向该地址所在的数据;
void (*)(void) :函数指针,指针名为空,该函数参数为空,返回值为空
(void (*)(void))(*(unsigned long *)0x2c):将Flash地址0x2C中的内容强制转化为函数指针,该函数参数为空,返回值为空
(*((void (*)(void))(*(unsigned long *)0x2c)))();:调用函数,即开始从启动代码中的UpdateHandler标号处开始执行。