升级功能对于所有的嵌入式产品都是非常重要的。尤其是当产品量产/销售阶段,已经没有条件让厂家对产品升级,因此升级方式的设计必须防呆防错以及稳定。
乐鑫ESP32作为蓝牙WIFI合一的物联网芯片,开发成产品后势必会使用OTA方式进行升级。本文档对ESP32的Flash分区配置情况以及SDK提供的OTA Demo进行简要分析,并以此作为未来产品OTA流程设计的参考。
本文档只分析SDKOTA Demo的升级流程,不涉及过于具体的Flash操作,具体问题在实际开发过程中再做处理。
1:ESP32 Flash空间分区情况
1.1ESP32 Flash空间分区配置
目前使用的ESP-WROOM-32集成4MB SPI Flash。在编译esp32程序时,通过make menuconfig -> PartitionTable 可以有三种分区选择:工厂程序(无OTA分区)/工厂程序(双OTA分区)/用户自定义分区。
menuconfig中的配置只是修改配置文件中的宏,实际上ESP32 SDK对应Flash分区配置的源码路径是:
\esp-idf-v3.0-rc1\components\partition_table
该路径下有文件partitions_singleapp/partitions_singleapp_coredump/partitions_two_ota/partitions_two_ota_coredump都是用来对Flash分区进行配置的。
这里以最为复杂的partitions_two_ota_coredump为例,再参考部分代码,可以得知使用partitions_two_ota_coredump配置分区时4M SPI Flash的分区情况:
分区用处需要特殊说明的是:
<1>core dump分区:core dump分区用于查找系统崩溃时的软件错误,简单的说就是软件崩溃的时候会刷写调试信息到Flash保存(掉电不丢失)以便于开发者分析。关于这个分区的使用有博客:《使用ESP32 的调试工具 coredump》
https://blog.****.net/tidyjiang/article/details/72123346
可见core dump分区在实际开发产品时比较有用,需要保留。
<2>Factory App分区:出厂时的主程序存储区
<3>OTA data/OTA_0/OTA_1与OTA升级功能相关,后面再做叙述。
1.2 自定义ESP32 Flash分区配置
对于产品开发而言,需要进行Flash分区自定义配置以满足需求。简单的说,至少core dump分区,OTA相关分区,以及掉电需要保存的用户数据区都是必要的。
现在在1.1节 partitions_two_ota_coredump的基础上进行自定义Flash分区配置,在core dump分区后面加上128K user data分区用于保存用户数据,方法如下:
<1>makemenuconfig -> Partition Table -> Custom partition table CSV
选择后会要求输入自定义的分区配置csv文件名,例如填入partition.csv。
<2>从路径\esp-idf-v3.0-rc1\components\partition_table下拷贝一个分区配置文件,使用写字板修改,例如添加128K user data分区:
<3>将新编辑好的partition.csv分区配置文件拷到用户的App目录下,make即可。
<4>烧录编译好的固件后,从调试信息看,Flash分区已经变更完成。用户需要掉电保存的数据可以通过nvs api(nvs_open_from_partition)写入到user data区。(暂时未测试)
2:ESP32 SDK OTA Demo升级流程
ESP32SDK路径\esp-idf-v3.0-rc1\examples\system\ota是官方提供的OTA Demo,并且有OTA升级流程图(使用说明):
但是很显然这对于嵌入式开发而言没有太大参考意义。
ESP32SDK OTA Demo部分代码目前未经调试测试,因此不保证分析完全正确,有谬误再做更改。
2.1 ESP32 SDK OTA Demo升级策略
升级策略分为两个部分讨论:1:升级目标固件的获取与烧录;2:OTA升级策略。后者是关键。
2.1.1升级目标固件的获取与烧录
升级固件的获取与写入非常简单:ESP32连接HTTP服务器;发送请求Get升级固件;每次读取1KB固件数据;写入Flash。嵌入式设备的升级流程基本都是类似的。
期间出现异常的处理是停止升级,指针原地跳转。
2.1.2 OTA升级策略
OTA升级策略是关键的。欠缺考虑的升级策略会坑死开发人员,更因为终端用户非专业人士,缺乏防呆防错的升级策略经常会使产品升级失败变砖,投诉与赔偿在所难免(手动笑脸)。
刨去繁琐的代码,简述ESP32SDK OTA Demo的升级策略如下:ESP32 SPI Flash内有与升级相关的(至少)四个分区:OTA data、Factory App、OTA_0、OTA_1。其中FactoryApp内存有出厂时的默认固件。
首次进行OTA升级时,OTA Demo向OTA_0分区烧录目标固件,并在烧录完成后,更新OTA data分区数据并重启。系统重启时获取OTA data分区数据进行计算,决定此后加载OTA_0分区的固件执行(而不是默认的Factory App分区内的固件),从而实现升级。
同理,若某次升级后ESP32已经在执行OTA_0内的固件,此时再升级时OTA Demo就会向OTA_1分区写入目标固件。再次启动后,执行OTA_1分区实现升级。以此类推。
升级的目标固件始终在OTA_0 OTA_1两个分区之间交互烧录,不会影响到出厂时的Factory App固件。
2.2 ESP32 SDK OTA Demo API简略说明
根据2.1.2对OTA Demo升级策略的描述,这里罗列一下相关的重要函数接口,但不做仔细分析。具体问题待开发过程中出现时再做处理:
<1>源码路径:\esp-idf-v3.0-rc1\examples\system\ota\main\ota_example_main.c
esp_ota_get_boot_partition:
esp_ota_get_running_partition:
获取当前系统执行的固件所在的Flash分区(两者区别暂时未理解)
esp_ota_get_next_update_partition:
获取当前系统下一个(紧邻当前使用的OTA_X分区)可用于烧录升级固件的Flash分区
esp_ota_begin& esp_ota_write & esp_ota_end:
向可用的Flash分区(一般是OTA_X分区)刷入升级目标固件
esp_ota_set_boot_partition:
升级完成更新OTA data区数据,重启时根据OTA data区数据到Flash分区加载执行目标(新)固件
<2>源码路径:\esp-idf-v3.0-rc1\components\bootloader\subproject\main\ bootloader_start.c
load_partition_table:加载Flash分区表(从分区表找到OTA data区地址)
get_selected_boot_partition:获取Flash启动分区(计算OTA data区数据得到)
load_boot_imageunpack_load_app:从Flash启动分区加载解压固件并执行
2.3 优点与可能的问题
从ESP32 SDKOTA Demo升级策略看,应该是比较稳妥的,无论升级期间出现任何异常,只要OTA data区数据未被修改,设备还可以加载原有的固件执行。
目前看来可能需要考虑的地方有:
<1>是否存在可能,OTA data数据指向了一个升级失败的区,导致设备加载损坏的固件;
<2>因为OTA需要三个升级相关区,因此固件大小被限制在小于SPI Flash Size/3
<3>获取升级目标固件还应当加入防错/重传/校验的机制;出现异常时也应当有相应处理。
<4>留出后台控制接口,用于修改OTA data区,便于远程控制程序运行。
3:ESP32 SDK OTA升级功能设计
TBD