ESP32 OTA远程升级功能解析

张开发
2026/4/13 5:31:20 15 分钟阅读

分享文章

ESP32 OTA远程升级功能解析
大家好我是嵌入式学习菌一名在上海嘉定打拼的嵌入式开发工程师。2023 年 7 月硕士毕业现于嘉定上市公司深耕 ESP32 嵌入式软件开发日常穿梭在代码调试与项目落地之间是典型的沪漂技术打工人。我会在这里分享一线嵌入式技术干货、代码拆解与优化方案坚持每周输出不定期分享学习资料愿与每一位嵌入式同行共同进步、少踩坑。今天给大家拆解 ESP32 设备 OTA 升级的核心灵魂代码 ——tm_fun_ota OTA 核心线程函数全程结合底层原理、逐段解析逻辑同时指出代码亮点、核心缺陷并贴合精准错误码区分 标准化进度上报实际需求给出可落地的优化方案适合做 ESP32 OTA 开发、调试的同行参考新手也能快速看懂核心逻辑。新增核心执行规范本次优化核心1. 升级进度上报规范开始下载step1progress0下载中每 10KB 上报step2实时进度下载完成仅升级成功上报step3progress1002. 精准错误码规范固件 CRC 校验失败 →-1下载失败超时 / Flash 写入失败 →-2仪表固件升级失败 →-3全流程升级成功 →0tm_fun_ota 作为 ESP32 设备 OTA 升级的核心工作线程并非简单的固件下载而是实现了固件接收、Flash 写入、CRC16 完整性校验、双分区切换、ESP32 自身 / WiFi 固件与外部仪表固件双升级的完整闭环是整个 OTA 功能的核心载体其稳定性直接决定了设备升级的成功率也是嵌入式开发中最容易出现异常、需要重点优化的模块。一、函数核心定位底层基础必看作为独立的 FreeRTOS 线程其基础配置与核心职责的底层逻辑的清晰是理解后续代码的关键线程配置优先级 osPriorityNormal2栈大小 4096确保 OTA 升级过程中不被低优先级线程抢占避免数据丢失核心职责全流程覆盖从环形 FIFO 缓存中读取网络接收的固件数据实现 “网络接收” 与 “Flash 写入” 的解耦提升升级稳定性按 1KB 分包写入 ESP32 Flash 的 OTA 备用分区避免单次写入过大导致的 Flash 损坏固件写入完成后通过 CRC16 校验确认固件完整性防止固件损坏导致设备升级后无法启动新增标准化上报升级进度实现全流程可视化精准区分两种升级类型ESP32 自身 WiFi 固件升级、外部仪表固件升级执行不同的后续逻辑全面处理异常场景数据超时、Flash 写入失败、CRC 校验失败并进行资源清理新增按 - 1/-2/-3 精准上报错误码快速定位故障上报升级结果成功 / 失败更新系统版本信息确保设备与后台数据同步依赖模块ESP32 OTA 双分区机制、自定义 Flash 读写接口、环形 FIFO 缓存、系统数据存储 DS_Sys缺一不可。二、逐段代码深度拆解结合底层逻辑 新增进度 / 错误码1. 线程初始化启动阶段c运行uint16_t i 0, Pla1 0, Pla2 0, ota_pla 0, len 0; char conten[80] {0}, URL[250] {0}; uint8_t hash[16]{0}; uint32_t reclen_printcnt 0; uint8_t successFlag 0; // 升级成功标志0失败1成功 UTIL_LOG_W(ota, start ota process, firmware size:%lu URL:%s, otaData.OTASize, otaData.URL); otaData.OtaRunFlag 1; // 标记OTA线程正在运行​核心作用定义局部变量、打印 OTA 启动日志固件大小、升级 URL标记 OTA 线程运行状态避免线程重复启动关键变量successFlag 是升级结果的核心判断依据仅在固件校验成功、切换分区完成后置 1后续用于结果上报的判断。2. 内存申请与线程资源管控c运行/* 申请环形缓存20KB固件缓存 1KB读写缓存 */ otaData.fifobuf malloc_Malloc(OTA_BUF_MAXLEN); if(otaData.fifobuf NULL) goto OTA_EXIT; otaData.fifo_optbuf malloc_Malloc(1024); if(otaData.fifo_optbuf NULL) goto OTA_EXIT; /* 暂停非必要线程注释保证OTA带宽 */ //e4g_enter_sendrecv(); //eiot_thread_suspend();​缓存设计亮点20KB 环形 FIFO 用于存储网络接收的固件数据解决网络接收与 Flash 写入速率不匹配的问题避免数据溢出1KB 读写缓存用于单次 Flash 写入符合 ESP32 Flash 的读写特性单次写入不宜过大资源管控内存申请失败直接跳至退出分支OTA_EXIT无冗余逻辑避免内存泄漏注释部分为暂停非必要线程目的是保证 OTA 升级的网络带宽和 CPU 资源实际项目中可根据需求启用。3. ESP32 OTA 双分区获取底层核心机制c运行const esp_partition_t *configured esp_ota_get_boot_partition(); // 下次启动分区 const esp_partition_t *running esp_ota_get_running_partition(); // 当前运行分区 update_partition esp_ota_get_next_update_partition(NULL); // 待写入的OTA备用分区 assert(update_partition ! NULL); otaData.error_exitflag 0; // 清除错误标志 otaExitStep 0; // 初始化退出码用于错误码分类​这是 ESP32 OTA 升级的底层核心必须吃透ESP32 OTA 双分区A/B 分区机制设备默认包含两个 APP 分区工厂分区 / OTA_0/OTA_1当前运行的是 A 分区时OTA 升级会将新固件写入 B 分区备用分区升级成功后切换启动分区重启后运行 B 分区若升级失败仍可启动原 A 分区避免设备变砖代码逻辑获取当前运行分区、下次启动分区、OTA 备用分区通过 assert 确保备用分区存在避免后续写入操作异常清除错误标志初始化退出码为后续精准错误码上报做准备。4. 主循环固件数据读取与 Flash 写入核心业务逻辑 进度上报线程通过死循环 while (1) 持续处理固件数据按 “首包 → 中间包 → 尾包” 分三种场景处理确保固件写入的完整性​同步实现标准化进度上报​1首包数据处理第一次写入 开始上报c运行if (otaData.FirstPackData 0) { otaData.FirstPackData 1; // ✅ 新增上报开始下载 step1 progress0 sungrow_api_pub_ota_progress(1, 0); ota_pla 0; // 注释了HTTP头解析直接全量写入 otaData.first_pack_datalen 1024-ota_pla; // 自定义Flash写入偏移0写入首包数据 err Flash_WriteSomeBytes(update_partition,0,otaData.fifo_optbufota_pla, otaData.first_pack_datalen); if (err ! ESP_OK) { otaExitStep 4; // 标记下载/写入失败 goto OTA_EXIT; } otaData.DataLen 长度; // 累计已写入长度 }​关键细节原代码中 HTTP 头解析逻辑被注释固件数据直接从偏移 0 开始写入备用分区适合无 HTTP 头的固件传输场景​新增优化​首包写入触发step1进度上报标记下载启动异常处理自定义 Flash_WriteSomeBytes 接口写入首包数据写入失败标记otaExitStep4对应错误码-2。2中间包数据处理循环写入 实时进度c运行else if(otaData.FirstPackData 1) { otaData.ErrTimeCnt 0; // 收到数据清空超时计数器 err Flash_WriteSomeBytes(update_partition,otaData.DataLen,otaData.fifo_optbuf, 1024); otaData.DataLen 1024; // 每10KB打印并上报一次进度 if(otaData.DataLen - reclen_printcnt (10 * 1024)) { reclen_printcnt otaData.DataLen; uint32_t per otaData.DataLen * 100 / otaData.FWSize; UTIL_LOG_W(ota, rec len:%lu (%lu%%), otaData.DataLen, per); // ✅ 新增上报下载中 step2 实时进度 sungrow_api_pub_ota_progress(2, per); } }​核心逻辑每次写入 1KB 固件数据按 otaData.DataLen 累计偏移量确保写入地址不重复、不遗漏​新增优化​每 10KB 上报一次step2实时进度后台可可视化监控优化点收到有效数据后清空超时计数器避免误判超时3尾包数据处理不足 1KB 剩余数据c运行else if(otaData.FirstPackData 1 otaData.FWSize - otaData.DataLen 1024 otaData.FWSize - otaData.DataLen otaData.fifobuf_vallen) { // 写入最后一段数据 err Flash_WriteSomeBytes(update_partition,otaData.DataLen,otaData.fifo_optbuf, 剩余长度); if (err ! ESP_OK) { otaExitStep 4; // 标记下载失败 goto OTA_EXIT; } otaData.DataLen 剩余长度; }​关键设计精准判断尾包数据固件总长度与已写入长度差小于 1KB确保最后一段不足 1KB 的数据完整写入避免固件缺失导致升级失败​新增优化​尾包写入失败统一标记下载失败错误码-25. 固件完整性校验CRC16 校验关键防错环节 错误码 - 1c运行if(otaData.DataLen otaData.FWSize otaData.FWSize ! 0) { // 从Flash读取全部固件逐包计算CRC16 uint16_t crc16 0xFFFF; for(i0;iota_1k_cnt;i) { esp_partition_read(partition_ptr,i*1024,otaData.fifo_optbuf,1024); crc16 CalCrc16File(otaData.fifo_optbuf, 1024,crc16); } // 对比服务器下发的CRC值 if (crc16 otaData.crc) { // 校验成功保存版本号、固件长度到Flash DS_Sys.VersionService[0] ...; DS_Sys.FWSize otaData.DataLen; otaData.OverFlag 1; } else { // ❌ 校验失败错误码-1 UTIL_LOG_E(ota, download firmware check fail); otaExitStep 2; otaData.error_exitflag 1; } }​校验逻辑固件写入完成后从 Flash 回读全部数据逐 1KB 计算 CRC16 校验和与服务器下发的 otaData.crc 对比确认固件未损坏​与需求关联​删除重复上报仅标记退出码校验失败对应​错误码 - 1​注意点校验成功后将版本号、固件长度写入系统存储 DS_Sys确保设备重启后能识别最新固件版本。6. 升级成功两种升级模式区分业务核心 完成进度上报c运行if (otaData.OverFlag 1) { if(DS_Sys.FWNewFlag 0xaa55) { // ✅ WIFI固件升级ESP32自身切换启动分区 重启 err esp_ota_set_boot_partition(update_partition); sungrow_api_pub_Ota_reply(0); // 上报成功码0 // ✅ 新增仅成功上报下载完成 step3 progress100 sungrow_api_pub_ota_progress(3, 100); successFlag 1; util_delay_time_Restart(1000); // 1秒后重启 } else if(DS_Sys.FWNewFlag 0xaa66) { // ✅ 仪表固件升级外部设备仅初始化不重启 Meter_Update_Init(...); sungrow_api_pub_Ota_reply(0); // 上报成功码0 // ✅ 新增仅成功上报下载完成 step3 progress100 sungrow_api_pub_ota_progress(3, 100); successFlag 1; } otaExitStep 3; goto OTA_EXIT; }​这是代码的核心业务亮点支持双设备升级逻辑区分清晰表格标志值升级类型核心行为适用场景0xaa55ESP32 WIFI 固件切换 OTA 备用分区 → 上报成功 → 重启设备自身固件更新0xaa66外部仪表固件仅初始化固件 → 不重启、不切换分区外接仪表固件升级​新增优化​仅升级成功时上报step3100% 进度杜绝失败误报成功统一上报0。7. 异常处理超时 强制错误退出 错误码 - 2c运行/* 超时错误40秒无数据2ms*20000 */ if(otaData.ErrTimeCnt 20*1000) { UTIL_LOG_E(ota, download firmware timeout exit); otaExitStep 4; // 下载失败对应错误码-2 goto OTA_EXIT; } /* 强制错误缓存满/写入失败 */ if(otaData.error_exitflag) { goto OTA_EXIT; } osDelay(2); // 线程延时释放CPU​超时处理每 2ms 计数一次累计 40 秒无数据则判定为超时标记otaExitStep4对应​错误码 - 2​强制错误若标记 otaData.error_exitflag如写入失败、缓存溢出立即终止 OTA线程优化osDelay (2) 释放 CPU 资源避免线程占用过高影响设备其他功能运行。8. 退出分支资源清理 结果上报闭环关键 全量错误码c运行OTA_EXIT: // 精准错误码上报核心修复 if(successFlag 0) { if(otaExitStep 2) { // 校验失败 → -1 sungrow_api_pub_Ota_reply(-1); } else if(otaExitStep 4) { // 下载/超时/写入失败 → -2 sungrow_api_pub_Ota_reply(-2); } else if(otaExitStep 5 DS_Sys.FWNewFlag 0xaa66) { // 仪表升级失败 → -3 sungrow_api_pub_Ota_reply(-3); } } ota_exit(); // 释放内存、关闭Socket tm_Thread_Exit(); // 终止线程​资源清理调用 ota_exit () 释放之前申请的 20KB 环形缓存、1KB 读写缓存关闭网络 Socket避免内存泄漏和资源浪费​核心修复​统一退出分支上报​彻底解决重复上报​支持-1/-2/-3全场景精准错误码仪表升级失败新增-3上报后台可感知三、核心变量速查调试必备 新增错误码变量表格变量核心作用调试重点otaData.DataLen累计已写入 Flash 的固件长度对比 otaData.FWSize判断是否写入完成otaData.FWSize目标固件总长度校验固件完整性的核心依据otaData.crc服务器下发的固件 CRC16 校验值与本地计算的 CRC 对比判断校验结果otaExitStep退出原因编码2 校验失败 / 3 成功 / 4 超时 / 5 仪表失败快速定位 OTA 失败原因 错误码匹配successFlag升级成功标志0 失败1 成功结果上报的核心判断依据DS_Sys.FWNewFlag升级类型区分标志区分 ESP32 / 仪表升级排查业务逻辑四、代码核心缺陷结合错误码 进度需求结合用户精准错误码 标准化进度的核心需求当前代码存在 5 个关键缺陷​**错误码混淆最严重**​校验失败、数据超时、Flash 写入失败、网络异常全部上报错误码 - 1无法区分具体失败原因不符合-2/-3需求​仪表升级失败无上报​退出分支仅判断 0xaa550xaa66 升级失败无通知​重复上报问题​CRC 校验失败时已直接上报 - 1退出分支又可能再次上报​进度上报异常​无标准化进度失败场景会强制上报 100%后台显示错误​细节错误​超时时间注释错误、自定义 Flash 写入存在风险。五、贴合需求的优化方案最小修改 进度 错误码全覆盖用户核心需求​错误码精准区分 标准化进度上报​最小修改方案进度上报标准化首包写入step10%每 10KBstep2实时进度仅成功step3100%删除 https_ota_pro 中强制进度上报代码错误码精准区分校验失败 (otaExitStep2) →-1超时 / 写入失败 (otaExitStep4) →-2仪表升级失败 (otaExitStep5) →-3成功 →0​解决重复上报​删除校验失败处直接上报统一在退出分支上报​细节修复​修正超时注释建议使用官方 OTA 接口六、总结核心重点提炼tm_fun_ota 函数整体逻辑严谨覆盖了 ESP32 OTA 升级的全流程支持双设备升级环形缓存的设计提升了升级稳定性CRC 校验有效防止固件损坏是一个可落地的 OTA 核心线程。本次新增标准化进度上报和 **-1/-2/-3 精准错误码 ** 后彻底解决了原代码的核心痛点✅ 进度可视化开始 / 下载中 / 完成全流程上报 ✅ 故障可定位校验 / 下载 / 仪表失败精准区分 ✅ 无重复上报统一退出分支处理 ✅ 全类型覆盖ESP32 仪表升级均支持结果上报对于嵌入式开发者而言OTA 升级是设备迭代的核心功能吃透这个函数的逻辑的底层原理不仅能快速排查升级异常更能掌握 ESP32 Flash 操作、FreeRTOS 线程管控、双分区机制等核心知识点对后续 ESP32 相关开发大有裨益。我愿用当下的踏实耕耘换往后半生的从容自在期待我们都能挣脱忙碌的枷锁迎来属于自己的 “打工人自由”。关注我一起在代码与技术里稳步前行慢慢靠近想要的生活

更多文章