Bootloader(1):初步认识Bootloader概念(什么是Bootloader)
前言
回想起刚入门单片机,在学校使用单片机参加各种电子竞赛。
代码跑起来的流程可简单了。
只需要拷贝一份demo工程(可能只实现GPIO点亮几颗LED的功能)。
然后在这个工程上面加应用功能逻辑、编译,烧录,最后断开仿真器,代码就能直接跑起来了。
简单来说,就是仅只使用一个工程,然后在上面实现我想要的应用功能就可以了。
这样的整套流程,可是搞了好几年。
后来毕业了,在一个小公司实习,首次真正见识到了Bootloader。
当时感觉好牛。
它一个项目下面,有2个工程,一个工程叫App、一个工程叫Boot。
看到Boot的Main函数,它跑了一些东西之后,会执行跳转,这个跳转代码好强,执行它就会跳到App工程里面(当时看这跳转代码,可是好久都没理解)。
在当时,我脑袋瓜子对Boot的问题可是太多了。
Boot的大概作用,我百度一下倒是能懂,可以用于升级嘛。
但是,这两个工程它们是怎么衔接起来的?跟我以前接触的不一样啊?以前都是一个工程一烧录,就跑起来了。
好像还有一级Boot、二级Boot?这个听起来更牛批了!搞那么多Boot干嘛?!
听说Bootloader会初始化必要的硬件外设,啥是必要的?为啥不能在App初始化?
当时太菜,怎么看都看不懂。
......
又好几年过去。
积累了各种MCU的知识后。
终于搞清楚了这Bootloader基本的来龙去脉。
认识Bootloader需了解的知识
一个工程里面,代码多了去了:有函数,有局部变量,有全局变量,等等。
我们所谓的刷代码刷代码,到底刷的是啥?又是刷到MCU的哪里去?
如果这些都不知道,还谈啥boot谈啥升级呢。
ROM(PFlash)、RAM概念
首先需要认识的是ROM(这里我们主要是认识ROM)和RAM:
ROM是断电数据不丢失的、只读的
RAM是断电后数据会丢失、可读可写的
但是,仅了解到这个层面还不够。
我们还需要深入去了解,在我们实际写的代码中,ROM对应哪些部分,RAM又对应哪些部分
大家平时写的代码中,有函数、有局部变量、有全局变量、有常量。
其中
1、函数、常量
函数是用来实现各种逻辑的代码。
常量是不可变化的只读变量。
因此函数和常量都放在ROM里面。
2、局部变量、全局变量
这2种变量都是代码运行过程中可以修改的、且下电会丢失的。
因此局部变量和全局变量放在RAM里面。
MCU地址概念
在对ROM和RAM有了形象的认识后,我们就要看看它们在芯片里面具体又是怎么样的。
接下来我们就要去翻一翻芯片手册了。
我们简单一点,把芯片理解成一个容器,因此,在这个容器里面一定有地方放我们上面说到的ROM和RAM。
于是我们翻到芯片手册关于地址的描述(以英飞凌TC275为例,Tc275它是3核芯片,但我们不用管它几个核,我们只看它的ROM和RAM就行。):
ROM的地址共2个区域(大小共4M):
PFlash1:0x8000 0000 ~ 0x801F FFFF
PFlash2:0x8020 0000 ~ 0x803F FFFF
RAM地址的区域就多了一些了(虽然分开的区域多,但空间大小是远小于ROM空间的)
大家不用管它叫什么PSPR、DSPR啥的,反正它就是RAM。
具体RAM地址举例如下:
好了, 现在稍微深入点了解了ROM和RAM之后。
我们来看看刷代码是怎么个事。
比如我们这颗TC275芯片。
对于工程里面的函数、常量这些东西
刷代码就是把函数、常量这些东西刷进入了MCU的ROM所在的地址里面。
画图举例如下:
对于工程里面的全局变量,
刷代码就是把全局变量的地址,指定到MCU的某个RAM地址上面。
比如定义一个全局变量:uint16 u16TestVal,它占用的空间是2个Byte。
假设我们把这个u16TestVal变量指定到0x5000 0000 ~ 0x5000 0001这个地址上面,那么这个地址存的值就是u16TestVal的值。
画图举例如下:
(我们这里只是简单举个ROM和RAM的例子,比如还有局部变量等等,就不展开讨论了)
初步认识Bootloader原理
到这里,你大概了解了刷代码是刷了啥东西之后,你已经具备足够的知识去初步认识Bootloader的实现了。
接下来,我们再简单一点。
第一步:
假设你手上有一个代码工程,并且只有一个main函数,先不用想这么复杂,mcu上电后,不用管main之前还跑了别的什么东西,就假设main函数之前没有东西了。
你的代码只实现了一个东西,就是在main里面拉一下GPIO点个灯(LED1)。
我们刚刚说了,函数放哪?放ROM嘛对吧。
因此,我们不妨直接把这个工程(即这个函数的起始位置)放到芯片手册里面ROM开始的地址:0x80000000(至于具体怎么放的,我们先不管)。
第二步:
你把刚刚那个工程拷贝一份,然后改一下main函数的实现内容(甚至你不改也行)。
这个工程就改成点亮另一个LED灯吧(LED2)。
同样的,函数放ROM里面嘛。
对于这个工程2的起始地址(即这个main函数的起始地址),放到第1个工程的后面,我们给第1个工程一些预留位置(因为不可能一个工程就点个灯吧,后续要加功能的)
工程2起始地址就放到0x80200000吧。
好了。经过了第一步、第二步的操作。
你现在MCU的ROM里面成功放了2个工程进去。
同时,我们把MCU上电后跑起来的起始地址设为0x80000000,即工程1的main起始地址。
那么问题又来了,这两个工程怎么衔接起来?
这就需要靠下面这句跳转代码了:
((void (*)(void))(0x80200000))();
我们把这段代码放到工程1点亮LED1代码的后面。
于是,MCU上电,代码跑起来后,工程1在点完LED1就会直接跳到工程2的main函数的起始地址,然后跑工程2的代码去点亮LED2了。
说明:这里得再说一下,实际工程的起始地址肯定不是main的,main的前面还会跑别的东西。我们这里只是简化理解。
但是,main前面的东西也一样是放在ROM里,所以我们现在不需要纠结main前面到底有啥。
到这里,很关键的一步已经完成了,你的MCU已经能跑2个工程了,已经有点Bootloader的雏形了。
接下来,我们再进一步。
比如我们简简单单,实现一个平衡小车的功能。
主要的逻辑功能放哪呢?
由于工程2的起始地址比工程1的靠后。
所以我们放到工程2里面,工程1我们后续有大用。
于是,在工程2里面就有了各种应用功能逻辑代码。
比如读取陀螺仪的方向转角代码啦,PID算法代码啦,等等。
你好好地搞着平衡车的功能,突然有一天,天塌了。
烧录器被人偷了!没法烧录代码了!平衡小车功能还没优化完呢!
好在很久以前,你从网上ctrl-c、ctrl-v,移植了下面这样功能的代码到工程1里面,并且在烧录器没被偷之前就刷写进MCU了。
你移植到工程1的这代码干的事情也不多,主要是下面这些:
1、擦除指定ROM地址数据
把数据写入指定ROM地址之前,我们需要先把MCU这段ROM地址上的数据擦除。
比如我们现在需要刷写工程2到MCU里面,那么我们就需要从ROM地址0x80200000开始擦除,至于擦除到哪里结束,则取决于这个工程2使用的ROM大小了。
又至于它是怎么擦除的,实际代码就是调用一个函数接口,这个函数是MCU厂家的软件包就有这个接口,因此不需要担心。
2、接收升级包
比如你编译工程2之后,会生成一个.hex文件。
这个.hex文件就是升级包。
然后,我们电脑会有一个上位机(类似于串口工具那样的上位机),它可以载入这个.hex文件,然后通过UART(或者CAN)发送给MCU。
至于MCU如何接收整个数据包的过程,我们现在先不管。
3、把升级包写入指定ROM
在擦除完成之后,就MCU就把接收到的升级包刷写至指定的ROM里面。
至于它是怎么写入的,也是类似于擦除那样,MCU厂家的软件包就有这个函数调用接口,直接调用就行,只需要把数据本身、数据大小和写入地址传进函数里面,它就会去写入了。
到这里,我们整理一下:
工程2,实现各种想要的应用功能代码,用来搞功能逻辑的,比如你的平衡小车功能。
工程1,不搞应用逻辑功能,作用是通过UART(或CAN)直接把工程2的代码刷写进MCU。
好了,MCU里面有了工程1在,你的烧录器就算被偷了,也能通过UART(或者CAN)直接把工程2的代码刷写进去:
1、擦除工程2在MCU中所在的ROM地址
2、接收工程2的hex文件
3、把接收到的工程2的hex文件数据写入指定ROM地址
这个工程1的作用,就是执行这3个关键步骤去升级工程2。
朋友们,这个工程1,不就是所以谓的Bootloader嘛。
有了工程1在,我们的MCU不就具备升级功能了嘛。
我们这里接收升级包的方式是通过UART(或者CAN)。
但完全可以改成别的方式。
比如通过以太网从云端远程接收数据包,然后进行升级。
这不就是所谓的OTA(Over The Air)了,你的手机、车、电脑不就是用这种方式升级的嘛!
结语
我们这里是简化了很多东西去讲Bootloader的大致的流程。
很多细节的东西我们都暂且忽略了,接下来就是一点点填坑,把这些细节一点点补上了。
比如:
main前面有什么东西,干啥用的?
工程的起始地址具体要怎么放到指定的ROM地址上?
Bootloader工程和App工程它们用的RAM是一起用的还是分开用的?
Bootloader跑起来的时候,怎么知道当前是上电流程还是升级流程?(如果是上电流程就要直接跳App工程,如果是升级流程就要停留在Bootloader里面跑升级功能)
一级Boot、二级Boot又是怎么回事?
升级包接收到流程具体是怎么样的?
App能不能倒反天罡,去升级Boot?
hex和bin有啥关系,好像两者都能用于升级?
等等......
真正要搞起来,细节可太多了。