电子产业一站式赋能平台

PCB联盟网

搜索
查看: 39|回复: 0
收起左侧

一文读懂Bootloader:从原理到OTA应用

[复制链接]

313

主题

313

帖子

2878

积分

三级会员

Rank: 3Rank: 3

积分
2878
发表于 昨天 14:41 | 显示全部楼层 |阅读模式
关注公众号,回复“入门资料”获取单片机入门到高级开挂教程
开发板带你入门,我们带你飞

文 | 无际(微信:2777492857)
全文约4976字,阅读大约需要 10 分钟
在2016年,我在做一个网关项目时,需要实现远程固件升级功能,也就是大家常听到的OTA。
           
当时,我对此一窍不通,Bootloader、IAP、OTA都是啥玩意?全部都是我的知识盲区。
           
公司也没人教,我只能硬着头皮去学习,记得之前对接的是云智易,整个项目前前后后搞了一年,才勉强做稳定,其实还有一些BUG,APP获取子设备信息不稳定,平台的协议没设计好,链路交互不够精简。
           
这个项目其中有个功能就是OTA,当时我啥也不懂,厚着脸皮,这里问问,那里搜搜,又从他们平台技术支持的嫖一点,在他爱搭不理的状态下,慢慢磨出来的经验。
           
一、Bootloader到底是个啥玩意儿?
简单来说,Bootloader就是单片机上电或复位后,在你的主应用程序(也就是你辛辛苦苦写的业务逻辑代码)运行之前,先跑起来的一小段特殊程序。
           
你可以把它想象成电脑开机时的BIOS/UEFI。电脑按下电源键,不是直接就进Windows或MacOS了对吧?得先有个东西检查下硬件,做点初始化,然后再去加载操作系统。
           
单片机的Bootloader干的也是类似的事情,只不过规模小得多,目标也更专一:初始化必要的硬件,然后决定是启动主应用程序,还是进入某种特殊模式(比如,准备接收新的固件)。
           
它通常被烧录在单片机内部或者外部Flash存储器的一个特定区域,这块区域往往还带有写保护,防止被应用程序不小心破坏掉。毕竟,如果连Bootloader都挂了,那设备很可能就真的“启动不能”了,到时候返厂维修,那成本可就上去了。   
           
二、为啥我们需要Bootloader?直接跑应用它不香吗?
           
直接跑应用当然行,很多简单的项目就是这么干的。代码编译、烧录、运行,一条龙。但问题来了,如果你的产品已经卖出去了,部署到了天南海北,这时候发现一个bug,或者想加个新功能,咋办?
           
总不能让用户把设备拆开,拿出J-Link/ST-Link,吭哧吭哧连上电脑,重新烧录吧?这用户体验也太差了,而且很多场景下根本不现实。
           
这时候,Bootloader的价值就体现出来了。它最重要的使命之一,就是实现固件的远程更新,也就是我们常说的IAP(In-Application Programming,在应用编程)或者更时髦的OTA(Over-The-Air,空中升级)。
           
有了Bootloader,我们就可以通过预留的通信接口(比如串口UART, USB, CAN, 以太网, 甚至无线方式如蓝牙, Wi-Fi, LoRa, NB-IoT等),给设备发送新的应用程序固件。Bootloader负责接收这些固件数据,把它写入到应用程序的存储区域,然后重新启动,加载新的程序。
           
总结一下Bootloader的核心优势:
1.固件更新能力:无需物理接触和专用烧录工具,即可更新设备功能或修复bug,极大降低维护成本。
2.灵活性:产品可以先发布,后续通过软件升级迭代。
3.可靠性(如果设计得好):可以设计成即使应用程序损坏,也能通过Bootloader进行恢复。   
           
所以你看,对于需要持续维护和升级的产品来说,Bootloader不是可有可无的点缀,而是实实在在的刚需。
           
三、Bootloader的工作原理
           
咱们来深入一点,看看Bootloader内部是怎么运作的。
           
1. 启动流程:
?单片机上电或复位。
?CPU根据内部设定,从一个固定的地址(通常是Flash的起始地址,也就是复位向量表所在位置)取出第一条指令。
?如果Bootloader被放置在这个起始地址,那么CPU就开始执行Bootloader的代码。
           
2. 硬件初始化:
           
?Bootloader不需要像主应用程序那样初始化所有的外设。它只需要初始化最基本的硬件,满足它的核心功能需求即可。
           
?这通常包括:
?时钟系统:让CPU和外设跑起来。
?GPIO:可能需要用来检测某个引脚状态,判断是否进入升级模式,或者控制LED指示状态。   
?通信接口:用于接收新固件的接口,比如UART, SPI, I2C, CAN, USB等。
           
3. 决策逻辑:跑应用还是搞升级?
           
这是Bootloader的一个关键环节。初始化完成后,它需要决定接下来干什么。常见的判断方式有:
           
?检测特定GPIO引脚:比如,上电时长按某个按钮,Bootloader检测到这个引脚是低电平,就进入升级模式。
           
?检查共享内存区域的标志位:应用程序可以在某个不易失存储(如Flash的特定扇区)写入一个标志,请求下次启动时进入升级模式。Bootloader启动时读取这个标志。
           
?通信接口等待:启动后,在一定时间内(比如1-2秒)监听某个通信接口,如果收到了特定的“进入升级”指令,就进入升级模式;否则超时后就去启动应用程序。
           
?检查应用程序有效性:计算应用程序区域的校验和(Checksum/CRC),如果校验和错误或者程序区域为空,可能强制进入升级模式。
           
这个决策过程,有点像Bootloader每天都要面对的灵魂拷问:“今天,我是该跳转到应用,还是留下来等等更新?”
           
4. 跳转到应用程序:   
           
如果决定运行主应用程序,Bootloader需要做几件重要的事情(以ARM Cortex-M为例):
           
?设置主堆栈指针 (MSP):应用程序有自己的堆栈,Bootloader需要从应用程序的向量表(通常位于应用程序Flash区域的起始处)的第一个字读取MSP的初始值,并设置到CPU的MSP寄存器。
           
?获取复位处理函数地址:应用程序的入口点不是main函数,而是复位处理函数(Reset_Handler)。这个函数的地址通常存储在应用程序向量表的第二个字。
           
?执行跳转:获取到地址后,通过函数指针或者汇编指令,直接跳转到应用程序的Reset_Handler执行。
           
注意:如果应用程序使用了中断,还需要考虑中断向量表的重定向问题。通常通过修改NVIC的VTOR(Vector Table Offset Register)寄存器,将中断向量表的基地址指向应用程序的向量表。这一步可以在Bootloader跳转前完成,也可以由应用程序自己初始化时完成。
           
5. 进入升级模式 (IAP过程):
           
如果决定进入升级模式,Bootloader会:
           
?初始化通信接口:准备接收数据。
           
?遵循预定义的通信协议:与上位机(发送固件的工具或服务器)进行交互。协议可能很简单(比如,握手->发数据块->应答->结束),也可能比较复杂(带加密、分包、重传机制)。   
           
?接收固件数据:通常按块(Packet/Frame)接收。
           
?数据校验:每接收一块数据,都要进行校验(如CRC校验),确保数据在传输过程中没有损坏。
           
?擦除Flash:在写入新数据之前,必须先擦除应用程序占用的Flash扇区。Flash存储器的特性是写之前必须先擦除,而且通常是按扇区(Sector)或块(Block)擦除的。
           
?写入Flash:将校验通过的数据块写入到Flash的对应位置。
           
?状态反馈:向上位机报告接收和写入的状态(成功、失败、重传请求等)。
           
?完成与验证:全部数据接收并写入完成后,可能会进行一次整体的固件校验。然后向上位机发送完成信号。
           
?复位:升级完成后,通常会触发一次软件复位,让CPU重新从头开始执行。这次,Bootloader检查发现无需升级(或者升级标志已被清除),就会跳转到刚刚写入的新应用程序。
           
四、Bootloader, IAP, OTA,别再傻傻分不清
               
这三个词经常一起出现,关系紧密但含义不同:
           
?Bootloader:是那段程序本身,它提供了加载应用程序和执行固件更新的基础能力。
           
?IAP (In-Application Programming):是一种技术或过程,指的是在设备运行状态下(通常是在Bootloader的引导下,或者有时甚至是应用程序自己调用特定代码)对自身的程序存储器进行擦写,以达到更新固件的目的。
           
?OTA (Over-The-Air):是一种固件交付方式,特指通过无线通信(Wi-Fi, Bluetooth, 蜂窝网络等)将新的固件包发送到设备。设备接收到OTA包后,通常会利用其IAP能力来完成实际的烧录更新。
           
所以,可以说:OTA是实现远程固件更新的一种高级方式,它依赖于设备的IAP能力,而IAP能力的实现往往离不开一个健壮的Bootloader。
           
五、动手写一个简单的Bootloader?
           
想自己动手写一个Bootloader?完全可以!虽然细节不少,但核心思路是清晰的。你需要关注以下几个方面:
           
1. 内存规划
你需要精确规划Flash的内存布局。哪部分给Bootloader,哪部分给应用程序,可能还需要一块用于Bootloader和应用程序通信的共享区域(比如存放升级标志)。  
               
这通常通过修改编译器的链接脚本来实现。你需要告诉链接器,Bootloader的代码和数据放在哪里,应用程序的代码和数据从哪个地址开始放。
           
例如,我们可以在Keil里面设置APP程序从哪个地址开始放。

eqgetu1i0qc64018888020.png

eqgetu1i0qc64018888020.png

通过上图设置,告诉编译器,我们的应用程序代码是从0x800C800这个地址开始,而Bootloader的,则是从0x8000000开始。

qw41ggghpmz64018888120.png

qw41ggghpmz64018888120.png

           
2. 通信协议设计
           
拿我们无际单片机项目6举例,这个项目实现了OTA升级,设备通过WiFi和4G连接我们开发的云平台。   

1i2imh2g1nk64018888220.png

1i2imh2g1nk64018888220.png

           
WiFi和4G之间通过MQTT,而单片机到WiFi和4G模块之前通过串口通讯。

uehku5zhwev64018888320.jpg

uehku5zhwev64018888320.jpg

               
这样,就需要设计一个串口协议。
           

12p0ozyrk0w64018888421.png

12p0ozyrk0w64018888421.png

           

2zkkgvlbdd264018888521.png

2zkkgvlbdd264018888521.png

               

1m053wlcut364018888621.png

1m053wlcut364018888621.png

           
例如,定义几个命令:
?查询服务器是否有新的固件版本(0x21,0x22)
?MCU申请获取OTA升级固件内容(0x24,0x25)
           
           
3. 核心逻辑实现
Bootloader的main函数:
?初始化时钟、GPIO、通信接口。
?检查是否需要进入升级模式(CheckIfUpdateRequest())。
?如果需要升级,调用 EnterUpdateMode()。
?如果不需要升级,调用 JumpToApplication()。
           
跳转函数的实现(C语言示例,ARM Cortex-M):   

  • #define APP_ADDRESS    (uint32_t)0x800C800 // 假设应用程序起始地址typedef void (*pFunction)(void); // 定义函数指针类型void JumpToApplication(void){    uint32_t jumpAddress;    pFunction applicationEntry;    // 检查应用程序起始地址是否有效 (至少包含向量表)    if (((*(volatile uint32_t*)APP_ADDRESS) & 0x2FFE0000) == 0x20000000)     {        // 1. 获取应用程序的堆栈顶地址 (向量表第一个字)        uint32_t appStack = *(volatile uint32_t*)APP_ADDRESS;        // 2. 设置主堆栈指针 MSP        __set_MSP(appStack);        // 3. 获取应用程序的复位处理函数地址 (向量表第二个字)        jumpAddress = *(volatile uint32_t*)(APP_ADDRESS + 4);        applicationEntry = (pFunction)jumpAddress;        // 4. 跳转到应用程序的复位处理函数        applicationEntry();    }    else    {       // 应用程序无效,可以在这里处理,比如闪烁LED报错       while(1)        {       }    }}
    升级模式的实现 (EnterUpdateMode()):
    ?循环等待接收命令。
    ?根据命令处理:擦除Flash、接收数据、校验数据、写入Flash。
    ?使用芯片厂商提供的Flash驱动库来执行擦写操作。注意处理好擦写的时序和错误。
               
    应用程序的注意事项:
    ?应用程序的链接脚本也要相应修改,确保它被链接到正确的地址(APP_ADDRESS)。
    ?如果使用了中断,应用程序需要在初始化时设置NVIC的VTOR寄存器指向自己的向量表。

    znl4bc3ox5s64018888721.png

    znl4bc3ox5s64018888721.png

               
    六、从IAP走向OTA
               
    实现了基本的IAP Bootloader,除了IAP的基础,OTA还需要考虑:
                   
    1. 无线通信栈
               
    需要在Bootloader(或者一个专门的、可更新的下载器应用)中集成无线通信协议栈(如Wi-Fi, BLE)。这对Bootloader的大小和复杂度提出了更高的要求。有时会采用两阶段Bootloader,第一阶段极简,负责加载第二阶段(带通信能力)或应用程序。
               
    2. 安全性
               
    这是OTA的重中之重!
               
    ?固件签名与验签:防止恶意固件被刷入。服务器用私钥对固件签名,设备用公钥验证签名,确认固件来源可靠且未被篡改。
    ?传输加密:防止固件在传输过程中被窃听或篡改,可以使用TLS/DTLS等安全传输协议。
    ?安全存储:密钥的安全存储也是个挑战。
               
    3. 健壮性
               
    ?断点续传:无线环境不稳定,传输中断是常事。需要机制能从中断的地方继续传输。
    ?电源管理:升级过程中断电怎么办?需要保证设备不会变砖。常用的方法是“双备份区(Dual-Bank)”或者使用日志/状态记录来确保更新的原子性(要么完全成功,要么回滚到旧版本)。
    ?版本管理与回滚:如果新版本有问题,需要有机制能回滚到上一个稳定版本。
                   
    4. 后端支持
               
    需要有服务器来存储固件、管理设备、推送更新任务,比如我们无际单片机的项目,通过自研的云平台,配合上自己设计的协议,就能打造一套完整的物联网OTA系统。
               
    七、Bootloader开发的“坑”
               
    开发Bootloader时,要特别小心,因为一旦出错,后果可能很严重。几个常见的“坑”和建议:
               
    ?大小限制:Bootloader占用的空间越小越好,给应用程序留出更多空间。代码要精简,避免引入不必要的库。
               
    ?稳定性压倒一切:Bootloader是设备的“最后防线”,必须极其稳定。充分测试各种边界条件和异常情况(如通信错误、Flash擦写失败)。
               
    ?别忘了看门狗(Watchdog):在Bootloader执行擦写Flash等耗时操作时,一定要记得“喂狗”,防止看门狗超时导致复位,让设备陷入无限重启的循环。
               
    ?日志与调试:Bootloader调试起来比应用程序困难,因为它运行在系统最早期。可以利用一个UART输出最关键的日志信息,或者通过调试接口(JTAG/SWD)单步跟踪。但要小心,连接调试器可能会影响某些硬件的正常初始化。
               
    ?保护机制:Flash的写保护功能一定要用起来,保护Bootloader自身不被意外修改。   
               
    ?版本意识:Bootloader和应用程序都需要有明确的版本号,方便管理和问题追溯。升级时可能需要进行版本兼容性检查。
               
               
    好了,关于Bootloader的原理、应用到OTA的演进,我们就聊到这里。希望这篇有点啰嗦但力求实在的文章,能帮你揭开Bootloader的神秘面纱。它虽然只是一段“开场前的小程序”,但却肩负着让设备具备“进化”能力的关键使命。从简单的串口IAP到复杂的安全OTA,Bootloader技术的发展也见证了嵌入式系统功能的不断强大。
               
    掌握Bootloader,不仅能让你在项目开发中更加游刃有余,也是衡量一个单片机工程师能力的重要标尺之一。
                   
    end

    arpz2b32q2y64018888821.jpg

    arpz2b32q2y64018888821.jpg


    下面是更多无际原创的个人成长经历、行业经验、技术干货。
    1.电子工程师是怎样的成长之路?10年5000字总结
    2.如何快速看懂别人的代码和思维
    3.单片机开发项目全局变量太多怎么管理?
    4.C语言开发单片机为什么大多数都采用全局变量的形式
    5.单片机怎么实现模块化编程?实用程度让人发指!
    6.c语言回调函数的使用及实际作用详解

    7.手把手教你c语言队列实现代码,通俗易懂超详细!

    8.c语言指针用法详解,通俗易懂超详细!
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表