物联网
如今,几乎所有可联网的电子设备都支持远程升级(OTA)功能,OTA 一是让电子设备能够支持更多的功能,二是能够修复一些应用程序中的漏洞。
前文中,我们在 2nd Bootloader 中实现了类似 ISP 的下载的功能,上位机如果基于 Ymodem 协议,通过 2nd Bootloader 下载应用程序到 Flash 中,其实就可以实现简单的 OTA 功能,但是,这样的 OTA 却是不安全的:无法保证升级过程中,若出现意外情况,打断升级后,微控制器还能够执行原有的应用程序。健全的 OTA,需要保证微控制器即使在升级失败之后,也依然能够按照升级之前的应用程序继续工作。
设计
升级应用程序这个需求其实通过前文实现的 ISP 就能实现,而 OTA 升级失败后,依然能够执行升级前的应用程序,才是 OTA 升级的难点。
OTA 为什么会升级失败?有如下可能:
OTA 升级过程中,意外断开网络,固件包接收不完整。
OTA 升级过程中,设备突然断电,固件包接收不完整。
OTA 升级过程中,设备突然断电,接收到的固件包未写入到 Flash 中。
如果要保证 OTA 升级失败后,原有的应用程序还能够正常运行,就需要让接收到的固件包,不能直接覆盖到原有的固件中。因此,我们需要把 Flash 空间一分为二,分别是执行区域和备份区域,执行区域存储的是当前微控制器可执行的固件,2nd Bootloader 会引导微控制器跳转到这块区域执行应用程序,而备份区域就是存放将要下载的新固件,如图1所示:
图1 Flash 空间划分
还需要将 OTA 的过程分为两个大的步骤:
固件下载过程
固件更新过程
固件下载过程中,微控制器 会把接收到的固件保存到备份区域,如图2所示。
图2 固件包存储在备份区域
固件更新过程中,微控制器 会把备份区域的固件复制到执行区域中如图3所示。
图3 固件包转移到执行区域
由于固件包存储在备份区域,固件下载过程中再怎么失败,也影响不到执行区域的固件,所以应用程序能够正常运行。
然后问题来了,当备份区域的固件下载好后,该怎么转移到执行区域上呢?其流程如图4所示:
图4 备份区域内容复制到执行区域
是不是很简单,但如果在固件更新的过程中断电,固件复制到一半怎么办?
因此,我们需要在 Flash 中保存一些信息,让微控制器复位后,在执行应用程序前,都先检查下固件是否已经复制完毕。
我们在执行区域和备份区域的末尾分别上写一些内容,来验证执行区域内容的完整性,存放这些内容的区域可称为信息块,如图5所示。
图5 在各区域末尾添加信息块存储区
信息块的大小至少是 Flash 的最小擦除单位,以保证擦除信息块时不影响其它内容。大多数常见的 QSPI Flash 的最小擦除单位大小是 4KB,片内 Flash 是 1KB,因此这个信息块的大小可按照 4KB 大小设计,执行区域和备份区域各一个,共计 8KB 大小。
信息块中包含以下信息:
固件版本,用于判断备份区域的固件是否为新的固件,是否执行固件更新操作
固件大小,需要复制备份区域多少内容到执行区域
信息块内容的校验值,建议使用是 CRC 校验,而不建议使用 BCC 校验,目的是证明这个信息块的内容可信,保证信息块的完整性
为什么说不建议使用 BCC 校验呢?一般来说,Flash 擦除信息后,读取的数据一般是 0xFF,如果使用 BCC 校验,其校验值只能是 0x00 或 0xFF,如果是 0xFF,则又与擦除后的 Flash 中的数据相同,起不到校验的效果,而 0x00 又容易碰撞到别的数据。
一般来说,上位机发送过来的文件中不包含版本号信息,那如何进行版本号管理呢?可在微控制器中自行管理:当新的固件下载好后,其版本号按照如图6方式管理:
图6 版本号管理
当 2nd Bootloader 进入到 ISP 模式后,会执行固件下载的过程,从外界接收新的固件,随后复位微控制器,进入执行应用程序的模式。
固件下载过程的流程图如图7 所示:
图7 固件下载过程
当 2nd Bootloader 进入到执行应用程序的模式后,并不会直接运行应用程序,而是执行固件更新过程,将备份区域的新固件复制到执行区域,待固件更新过程完毕后,才会执行应用程序。
事实上,每次 2nd Bootloader 进入到执行应用程序的模式后,都会执行固件更新过程,固件更新包括了检查是否下载了新固件和是否已经完成复制新固件到执行区域两部分内容,如果没有新的固件包在备份区域的话,固件更新程序就会提前结束,直接执行应用程序。
固件更新过程如图8所示:
图8 固件更新过程
过程分析
为了保证 OTA 过程的安全,我们需要保证备份区域和执行区域至少有一个固件是完整的,这样,当备份区域的固件破坏时,我们能够保证执行区域的固件还能用,而执行区域的固件损坏时,还能从备份区域恢复过来。
那怎么确定某区域的固件是完整的呢?
通过图7和图8可知,不管是下载固件到备份区域,还是复制备份区域固件到执行区域,只要有写固件的操作,都会先将对应区域的信息块进行擦除,直到写固件完成,才会把新的信息块写入到指定区域。因此,我们可以通过信息块是否完整来判断该区域的固件是否完整,信息块的校验值是验证信息块完整性的保证。
不管是人事档案,学生档案,还是党员档案,在档案袋上都会贴有密封条,并且盖有部门的红章,这个密封条就是证明档案内容完整性的保证。Flash 中的固件,就相当于档案袋里面的内容,信息块就相当于档案袋上的封条,版本号相当于封条上的日期,校验值相当于封条上的红章,擦除信息块的过程,相当于给 Flash 进行撕封条的操作,而写入信息块的过程,就相当于给 Flash进行贴封条的过程,通过这个封条,就能够证明 Flash 中的内容是完整的。
有了“封条”证明固件的完整性,还需要确定什么时候才能拆“封条”:
拆封条的原则就是:保证两个区域至少有一个固件是完整的。因此,在擦除其中一个区域的信息块时,需要查看另一个区域的信息块是否完整,只有另一个区域的信息块是完整的,才能够擦除本区域信息块的内容。
在固件下载的过程中,如果发现执行区域的信息块不完整,那就暂时还不能擦除本区域的信息块,需先执行固件更新过程,将备份区域的固件写入到执行区域,在写入执行区域的信息块后,才能继续擦除备份区域的信息块。一般来说,执行区域的信息块如果不完整,则说明该设备在上次固件更新过程中,意外掉电,固件更新失败。
在固件更新的过程中,如果备份区域的信息块不完整,则说明固件还没有下载成功,自然是不能继续进行固件更新的,就得跳过固件更新的过程。如果备份区域的固件版本小于(一般不会小于)或等于执行区域的固件版本,则说明备份区域的固件和执行区域的固件一样,没有必要固件更新,也要跳过固件更新的过程。只有备份区域的信息块完整,且固件版本大于执行区域时,或者执行区域没有完整的信息块时,才可以擦除执行区域的信息块,对执行区域的内容进行写操作。
有一种情况,执行区域和备份区域是都没有完整的固件的,那就是产品硬件生产完成后,还没有烧录程序的时候。
当产品硬件生产完成后,微控制器内部只有一个 2nd Bootloader,进入到固件下载的过程时发现,执行区域没有完整的信息块,再跳转到执行固件更新过程,但固件更新过程发现备份区域也没有完整的信息块,2nd Bootloader 傻眼了,咋哪都没有完整固件呢?
因此,在固件下载过程中,若发现执行区域和备份区域都没有完整信息块时,还是得允许向备份区域写固件。
当意外出现在任何没有操作 Flash 的时候,再次上电后,不影响应用程序的正常执行,不影响固件更新,也不影响固件下载的过程;当意外出现在对备份区域的 Flash 操作时,执行区域的固件还能够正常执行,也不影响重新执行固件下载的过程。当意外出现在对执行区域的 Flash 操作时,再次上电后,重新进行固件更新,应用程序还是能够正常运行。所以,这个完整的 OTA 过程,处处安全。
测试
根据本文讲述的过程,实现带 OTA 功能的 2nd Bootloader,进行验证。
下载新的固件前效果,如图9所示:
图9 下载新固件前的应用程序
下载固件过程中中断下载,通过观察应用程序打印的时间可知,微控制器会继续执行更新固件前的应用程序,如图10所示:
图10 Ymodem 升级失败后的应用程序
当固件下载成功后,在进入应用程序前,立刻按下复位按键,产生一次固件更新失败的事件,随后释放复位按键,观察通过应用程序打印的时间可以发现,新的固件在再次复位之后,依然进行了更新,如图11所示:
图11 固件更新失败后,再次复位程序
结语
本文主要针对 OTA 升级的流程进行了讲解,把 OTA 升级的过程拆分为固件下载和固件更新两个过程,每个过程都想办法保证设备复位后,执行区域总有可用固件,保障 OTA 过程中如果发生意外,不至于让设备变 “砖”。
OTA 升级的方法不仅仅只有串口一种,我们还可以使用 I2C 接口,SPI 接口升级固件,如果支持 USB Device,那么 微控制器微控制器可以模拟成为一个 U 盘,插在电脑上,将二进制文件放到这个 U 盘中更新固件,如果支持 USB Host,则可以将固件放到 U 盘中,微控制器读取 U 盘中的数据更新固件,或者将 U 盘换成 SD 卡…… 实现 OTA 功能的方法极多。
当 OTA 的方法越来越多时,2nd Bootloader 也随之变得臃肿起来。其实,我们也可以把 OTA 的大部分内容做到应用程序中,仅将备份区域的应用程序转移到执行区域这个核心功能保留到 2nd Bootloader 中,并且提供对备份区域 Flash 擦除和写操作的 API 即可,应用程序中的 OTA 功能,将会把下载到的新固件通过 2nd Bootloader 提供的 API 写入到备份区域,随后复位 MCU,让 2nd Bootloader 将新固件再转移到执行区域即可。
为了减少下载新固件所需的流量,其固件包可能进行了压缩处理,而解压过程可以放在上位机中,让上位机发送给微控制器的固件就是经过解压后的固件包;或者可以放在应用程序中,在应用程序中进行解压后写入到 Flash 中,或者放在 2nd Bootloader 中,从备份区域放到执行区域时再进行解压;第三种方法虽然可以减少备份区域占用的空间,但需要将压缩算法确定好,永久不会再进行改动——2nd Bootloader 虽然灵活,但就像微控制器厂商出厂微控制器前,将 1st Bootloader 固化到微控制器中那样,2nd Bootloader 只能在产品厂商将产品出厂前写入到微控制器中,产品一旦售出,就不像应用程序可以通过 OTA 升级了,除非进行产品召回,否则就没有修改的可能,因此,放在 2nd Bootloader 中的代码,一定要谨慎对待。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !