固件更新
OTA升级机制允许设备在正常运行时接收新固件,并将新固件写入当前未运行的应用程序分区,在校验固件有效后,切换至新固件运行。OTA升级的基本步骤如图所示。
从图可以看出,OTA升级的基本步骤如下:
- 云服务器向设备推送OTA升级信息
- 设备对云服务器身份进行验证,从受信任的云服务器中下载固件
- 根据下载固件中的版本信息,设备决定是否进行升级,如果决定升级,则请求固件,并将固件写入Flash后对其进行校验,校验成功后切换至新的固件运行
分区表概述
ESP-IDF的分区表是指在用户层面将Flash不同位置、范围划分为特定功能的描述文件。以ESP-IDF中partition_table组件下的partitions_two_ota.csv文件为例:
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x4000,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
factory, app, factory, , 1M,
ota_0, app, ota_0, , 1M,
ota_1, app, ota_1, , 1M,
从上面的例子可以看到,分区表每个条目都是由Name、Type、SubType、Offset、Size、Flags构成的。
- Name字符用于标识名称,不应超过16B
- Type字段既可以指定app或者data,也可以直接使用0-254(0x00~0xFE),主要用于标记存储的内容是应用固件还是数据
- SubType字段的长度为8bit,具体标记内容与Type字段有关
- 当Type定义为app时,SubType字段可以指定为factory(0x00)、ota_0(0x10)、… 、ota_15(0x1F)或者test(0x20)。
- 当Type定义为data时,SubType字段可以指定为ota(0x00)、phy(0x01)、nvs(0x02)、nvs_keys(0x04)或者其他组件特定的子类型。
- Offset与Size字段用于划分一个特定的区域
- Flags字段用于标记是否加密
该示例分区表中的Offset未填写任何数值,但依旧是有效的分区表,这是因为分区表首个条目的位置是确定的,所以可以通过前一个条目的Size字段计算出后续条目的地址。如果分区表划分的每个条目地址不是连续的,此时就需要通过Offset来标记每个条目的起始地址。为了方便理解,可以参考下图。
从上图可以看出,分区表首个条目的起始地址为0x9000,即partitions_two_ota.csv中Name为nvs条目的Offset字段是0x9000,该条目的大小为0x4000,根据之前介绍的计算规则,下一个条目的Offset为0xd000,依次计算,最后一个ota_1条目的Offset为0x210000。
partitions_two_ota.csv分区表划分了6个区域:3个数据分区nvs、otadata、phy_init分别用于存储nvs数据、OTA升级数据、PHY初始化数据;3个应用分区分别存储了3个不同的应用固件。从OTA升级的基本步骤可知,要进行OTA升级,至少需要包括两个OTA升级应用分区-Type(app),SubType(ota_0/ota_1),同时也包含一个可选的应用分区,出厂应用分区-Type(app)、SubType(factory)。
-
OTA升级数据分区用于存储当前所选的OTA升级应用分区的信息,第一次OTA升级后,OTA升级数据分区数据将被更新,指定下一次启动哪个OTA升级应用分区。OTA升级数据分区的大小需要设定为0x2000,用于防止写入时电源故障引发问题,两个扇区单独擦除、写入匹配数据,若存在不一致,则用计数器字段将判定哪个扇区为最新数据。
-
应用分区用于存储固件,出场应用分区是默认的应用分区,如果不存在OTA升级数据分区或者OTA升级数据分区无效,则优先使用出厂应用分区的固件(如果存在),其次使用OTA升级数据分区的固件。OTA升级永远都不会更新出厂应用分区中的内容。
固件启动流程
从下图可以看出,Flash被分为了8个区域,0x00地址存放了Bootloader(启动加载程序),0x8000存放了分区表,从0x9000开始便是分区表所划分的区域。在乐鑫科技的芯片程序设计中,Bootloader被称为二级引导程序,其主要作用为增加Flash分区的灵活性,并且方便实现Flash加密、安全引导和OTA升级等功能。二级引导程序默认从Flash的0x8000偏移地址处加载分区表,分区表的大小为0x1000,二级引导程序会从分区表中寻找出厂应用分区和OTA升级数据分区,并通过查询OTA升级数据分区来确定引导哪个分区。
ESP32-C3从上电到运行app_main()函数的过程可以分为如下3个步骤:
-
由一级引导程序进行引导。一级引导程序被固化在了ESP32-C3内部的ROM中,芯片复位后,CPU会立即开始运行,判断启动模式并执行相关操作,从Flash的0x0偏移地址处将二级引导程序加载到RAM中。
-
由二级引导程序进行引导。二级引导程序将首先从Flash中加载分区表,然后查询OTA升级数据分区,并选择某个应用分区的固件进行加载。当处理完所有数据后,二级引导程序将校验固件的完整性,并从二进制固件文件的头部寻找入口地址,跳转到该地址处运行。应用分区的固件拥有一些状态,这些状态会影响固件的启动。这些状态保存在OTA升级数据分区,在ESP-IDF中由一组枚举变量(esp_ota_img_states_t)定义。
-
应用程序启动阶段。二级引导程序跳转后便是应用固件的启动阶段,该阶段包含了从应用程序开始执行到app_main()函数创建运行前的所有过程。可分为三个部分:
- 硬件和基本端口的初始化。
- 软件服务和FreeRTOS系统的初始化。
- 运行主任务并调用app_main()函数
OTA升级原理概述
物联网设备进行OTA升级的第一步是获取新固件。获取新固件的方式有很多,使用Wi-Fi获取是其中较为简单、方便的一种方式。物联网设备可以通过Wi-Fi连接到路由器,进而连接到OTA升级服务器,通过一些应用层协议(如HTTP、FTP)下载固件。