bootloader的原理及实现过程详解

一、背景  在嵌入式操作系统中,bootloader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像bios那样的固件程序
二、实现思路 bootloader其实就是一段启动程序,它在芯片启动的时候首先被执行,它可以用来做一些硬件的初始化,当初始化完成之后跳转到对应的应用程序中去。
我们可以将内存分为两个区,一个是启动程序区(0x0800 0000 - 0x0800 2000 )大小为8k bytes,剩下的为应用程序区(0x0800 2000 - 0x0801 0000)。
芯片上电时先运行启动程序,然后跳转到应用程序区执行应用程序。
三、程序跳转 bootloader一个主要的功能就是首先程序的跳转。在stm32中只要将要跳转的地址直接写入pc寄存器,就可以跳转到对应的地址中去。
怎么实现呢?
当我们实现一个函数的时候,这个函数最终会占用一段内存,而它的函数名代表的就是这段内存的起始地址。当我们调用这个函数的时候,单片机会将这段
内存的首地址(函数名对应的地址)加载到pc寄存器中,从而跳转到这段代码来执行。那么我们也可以利用这个原理,定义一个函数指针,将这个指针指向我们
想要跳转的地址,然后调用这个函数,就可以实现程序的跳转了。
代码如下:
#define app_addr 0x08002000 //应用程序首地址定义 typedef void (*app_func)(); //函数指针类型定义app_func jump2app; //定义一个函数指针jump2app = ( 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(uint32_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_addr 0x08002000 //应用程序首地址定义 typedef void (*app_func)(); //函数指针类型定义/** * @brief * @param * @retval */__asm void msr_msp(uint32_t addr){ msr msp, r0 bx r14;}/** * @brief * @param * @retval */void run_app(uint32_t app_addr){ uint32_t reset_addr = 0; app_func jump2app; /* 跳转之前关闭相应的中断 */ nvic_disableirq(systick_irqn); nvic_disableirq(lpuart_irq); /* 栈顶地址是否合法(这里sram大小为8k) */ if(((*(uint32_t *)app_addr)&0x2fffe000) == 0x20000000) { /* 设置栈指针 */ msr_msp(app_addr); /* 获取复位地址 */ reset_addr = *(uint32_t *)(app_addr+4); jump2app = ( app_func )reset_addr; jump2app(); } else { printf(app not found!n); }} 五、编译设置 我们需要在设置界面将默认(0x8000000)改为我们的应用程序地址(0x8002000)
六、中断向量表重映射 完成了上面的工作,实际测试发现程序还是无法正确运行。原因是我们没有进行中断向量表的重映射。向量表映射?什么时候有做过这个工作,我们来看一下:
.s文件里有如下代码:
; reset handler routinereset_handler proc export reset_handler [weak] import __main import systeminit ldr r0, =systeminit blx r0 ldr r0, =__main bx r0 endp 这代码表示,程序在执行main函数之前,会先执行systeminit这个函数。下面看看这个函数:
/** * @brief setup the microcontroller system. * @param none * @retval none */void systeminit (void){/*!cr |= (uint32_t)0x00000100u; /*!cfgr &= (uint32_t) 0x88ff400cu; /*!cr &= (uint32_t)0xfef6fff6u; /*!crrcr &= (uint32_t)0xfffffffeu; /*!cr &= (uint32_t)0xfffbffffu; /*!cfgr &= (uint32_t)0xff02ffffu; /*!cier = 0x00000000u; /* configure the vector table location add offset address ------------------*/#ifdef vect_tab_sram scb->vtor = sram_base | vect_tab_offset; /* vector table relocation in internal sram */#else scb->vtor = flash_base | vect_tab_offset; /* vector table relocation in internal flash */#endif} 从上面的代码可以看到,这个函数主要是做了时钟的初始化和中断初始化,还有就是中断向量表的映射,就是最后那一段代码
再看看flash_base 和 vect_tab_offset的定义:
这里默认映射地址就是flash的初始地址,所以只要将其改成我们程序的起始地址就行了: scb->vtor = 0x08002000
编译,运行,下载.
 七、总结 程序跳转完成,对于bootloader来说也就完成了一大半。剩下的就是根据自己的需求去完善相应功能了,比如我的在线升级功能,就要在bootloader里做固件接收和校验的功能。这里有一点需要特别注意的是,跳转程序之前最好把你用到的中断都关了,不然跳转之后的程序没有对应的中断处理函数,那就又可能使得程序进入死循环中。


微软Surface Book拆解评测:可维修指数低 SSD可更换
真空干燥箱噪声问题分析
无人机有望成为火星探索的关键工具
康佳40周年转型以科技创新为驱动 科技康佳 连接美好
安全广播在CMMB终端的应用与实现
bootloader的原理及实现过程详解
Intel正式发布CascadeLake第二代可扩展Xeon至强服务器处理器
采用模拟PFC+模拟LLC的用交流输入AC/DC电源设计
华帝干态抑菌洗碗机V6高温净洗 为全家人的健康保驾护航
关于工业无人机的介绍和与应用
联咏敦泰带头,开启芯片业涨价第一枪
10W儿童数码相机电源IC介绍
美国释出3.5GHz CBRS频谱 商用服务或2017年出现
联想Z6 Pro 5G仅售3299,全球售价最低的5G手机?
新华社 | 厚植文化根基 共筑永续未来,德力西电气亮相“年鉴里的中国”文化市集
LUN映射出错导致文件系统共享冲突的服务器数据恢复案例
冷冻电镜在材料科学中崭露头角
为什么以HDV格式进行拍摄?
三星S8并非如此完美,看完三星S8这些缺点:让我更期待小米6
压力补偿密封件 101:汽车选择指南