一、背景在嵌入式操作系统中,引导装载程序在操作系统的内核运行之前运行。可以初始化硬件设备,建立内存空间映射,使系统的软硬件环境达到合适的状态,为最终调用操作系统内核准备正确的环境。在嵌入式系统中,通常没有BIOS这样的固件程序。
二、实现思路bootloader实际上是一个启动程序,在芯片启动时首先执行。可以用来初始化一些硬件,初始化完成后再跳转到相应的应用。
我们可以把内存分成两个区域,一个是引导程序区(0x0800 0000-0x0800 2000),大小为8K字节,另一个是应用程序区(0x0800 2000-0x0801 0000)。
芯片上电时,先运行启动程序,然后跳转到应用区执行应用程序。
三、程序跳转bootloader的主要功能之一就是先跳转程序。在STM32中,只要把要跳转的地址直接写入PC寄存器,就可以跳转到相应的地址。
如何实现?
当我们实现一个函数时,这个函数最终会占用一个内存,它的函数名代表了这个内存的起始地址。当我们调用这个函数时,单片机会把这个段
存储器的第一个地址(对应于函数名的地址)被载入PC寄存器,从而跳转到该代码执行。然后我们也可以用这个原理定义一个函数指针,把这个指针指向我们。
想跳转到地址,然后调用这个函数,就可以实现程序的跳转。
代码如下:
#define APP_ADDR0x08002000 //应用的第一个地址定义typedef void(* APP _ FUNC)();//函数指针类型定义APP _ FUNC jump 2 APP;//定义一个函数指针jump 2 APP=(APP _ FUNC)(APP _ ADDR 4);//将jump2app()赋给函数指针;//调用函数指针实现程序跳转。上面的代码实现了我们想要的跳转功能,但是为什么要跳转到地址(APP_ADDR 4)而不是APP_ADDR呢?
首先,我们需要了解主控芯片的启动过程。以STM32为例。芯片上电时,将从存储器地址位0x0800 0000(由启动模式决定)加载顶地址(4字节),从地址位0x0800 0004加载程序复位地址(4字节),然后跳转到相应的复位地址执行。
所以在上面的程序会中,jump2app的函数指针的地址是(APP_ADDR 4)。当这个函数指针被调用时,芯片内核会自动跳转到这个指针所指向的内存地址,这个地址就是应用的复位地址。
四、加载堆栈地址的实际操作会发现上面的程序可能有问题。因为我们还缺少一个栈地址的加载过程,也就是芯片上电的第一个动作。这里我们需要用到一点编译知识:
_ _ ASM void MSR _ MSP(uint 32 _ t addr){ MSR MSP,r0 BX R14;}__asm void MSR_MSP(uint32_t addr)是MDK的嵌入式汇编形式。MSR MSP,r0表示将r0寄存器中的值加载到MSP(主栈寄存器,复位时默认使用)寄存器中,r0保存参数值,即addr的值。
BX r14跳转到存储在连接寄存器中的地址,也就是说,它退出函数并跳转到函数调用地址。
完整的过程如下:
#define APP_ADDR0x08002000 //应用的第一个地址定义typedef void(* APP _ FUNC)();//函数指针类型定义/* * @ brief * @ param * @ retval */_ _ ASM void MSR _ MSP(uint 32 _ taddr){ MSR MSP,r0bxr14}/* * * * @ brief * @ param * @ retval */void run _ app(uint 32 _ t app _ addr){ uint 32 _ t reset _ addr=0;APP _ FUNC jump 2 APP;NVIC _禁用IRQ(sy stick _ IRQn);NVIC _禁用IRQ(LPUART _ IRQ);if((*(uint 32 _ t *)app _ addr)0x 2 FFE 000)==0x 2000000){MSR _ MSP(app _ addr);reset _ addr=*(uint 32 _ t *)(app _ addr 4);jump 2 APP=(APP _ FUNC)reset _ addr;jump 2 app();} else { printf(找不到应用程序!\ n );}}五、编译设置我们需要在设置界面把默认值(0x800000)改成我们的应用地址(0x8002000)。
六、中断向量表重映射完成了上述工作,但实际测试发现程序仍然可以无法正常运行。原因是我们没有重新映射中断向量表。按比例制图?你什么时候做过这份工作?让让我们看一看:
的。的文件有以下代码:
;复位处理程序例程reset _ handler proc export reset _ handler[weak]import _ _ main import system Init ldrr 0,=system init blxr0 ldrr0,=_ _ main bxr0endp这段代码表示程序在执行main函数之前会先执行系统init函数。看看下面这个函数:
/* * * * @简单设置微控制器系统。* @ param None * @ retval None */void system init(void){/*!CR |=(uint 32 _ t)0x 00000100u;/*!CFGR=(uint 32 _ t)0x 88 ff 400 Cu;/*!CR=(uint 32 _ t)0x Fe F6 fff 6 u;/*!CRRCR=(uint 32 _ t)0x fffffffeu;/*!CR=(uint 32 _ t)0x fffbffffu;/*!CFGR=(uint 32 _ t)0x ff 02 ffffu;/*!CIER=0x 00000000 u;# ifdef VECT _ TAB _ SRAM sc B- VTOR=SRAM _ BASE | VECT _ TAB _ OFFSET;# else sc b-VTOR=FLASH _ BASE | VECT _ TAB _ OFFSET;# endif}从上面的代码可以看出,这个函数主要是初始化时钟和中断初始化,同时也中断了向量表的映射,也就是最后一段代码。
看看FLASH_BASE和VECT_TAB_OFFSET的定义:
这里默认的映射地址是FLASH的初始地址,改成我们程序的初始地址就行了:SCB-VTOR=0x08002000。
编译、运行和下载。
七、摘要程序跳转完成,这对于bootloader来说已经完成了一大半。剩下的就是根据自己的需求完善相应的功能。比如我的在线升级功能,需要bootloader中的固件接收和检查功能。这里需要特别注意的一点是,在跳转程序之前,最好关闭你使用的所有中断,否则跳转后的程序没有相应的中断处理函数,可能会使程序再次进入无限循环。审计福冈江
标签:程序地址函数