ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ESP32分区级应用平台:像手机一样切换固件的实战指南

ESP32分区级应用平台:像手机一样切换固件的实战指南 上电之后串口终端打印出一个菜单1号槽 LED_Blink2号槽 温湿度采集3号槽 WebServer_Demo。你没看错这不是 Linux是那颗 ESP32。输入 1回车单片机重启几秒后 LED 按新的逻辑闪烁下次想换功能再按一下复位菜单又回来了。整个过程不插 USB、不重新烧录芯片里像是装了好几个应用想用哪个启动哪个。这就是这个小项目的核心在 ESP32 上做一个“分区级小型应用平台”让开发板像手机一样“安装应用”。这个项目解决的是我自己折腾 ESP32 时最大的痛点每次换功能都要连电脑、改代码、编译、烧录调试一个多功能板子时尤其崩溃。做出来之后我可以在现场用串口菜单或手机网页直接切换应用甚至能通过浏览器往空槽里传一个新的固件包这在别人看来就像在“装 App”实际原理是 ESP-IDF 的 OTA 分区机制加一个常驻 Launcher。今天把完整设计和踩坑记录都写出来适合有一定 Arduino/ESP32 基础、想进一步理解分区与 OTA 机制的开发爱好者也适合所有嫌烧录太麻烦的 DIY 玩家。1. 这个项目的动机与边界给 ESP32 装上“App Store”1.1 反复烧录的痛点做过三五个项目的人都懂ESP32 是很流行的开发板自带 WiFi 和蓝牙Flash 空间通常是 4MB 起步性能也够跑不少 IoT 应用。但我过去很长一段时间都把它当“高级单片机”用每次改功能都要重新编译、连接 USB、手动烧录。这在一个项目从零到完成的过程中还算能接受可一旦你手上同时维护好几个小项目比如一个做 LED 氛围灯、一个做温湿度传感器、一个做网页服务器你会发现一个很尴尬的情况功能都没有坏但你想在同一个板子上切换它们只能一遍遍重新烧录。刚开始我的应对办法是多买几块板子一个项目用一块。但板子多了以后找起来麻烦而且每块板子还得单独配电源、外壳桌面全是线。后来我做了一个网络摄像头项目发现 ESP32 本身就有 OTA 升级能力可以通过网络更新固件于是我开始想既然网络都能更新固件了那么能不能更“暴力”一点——把 Flash 里划分出好几个槽位每个槽位放一个完整可启动的固件再做一个引导菜单开机让你选跑哪一个。本质上手机“安装 App”是把应用安装到外部存储器然后由操作系统调度运行。ESP32 因为资源限制做不到真正意义上的多进程并发但我也不需要它那么复杂。我需要的只是“多个固件轮流跑切换尽量不要插线”这就完全可以用分区方案解决。1.2 平台化设计的边界这不是操作系统但能解决 80% 的折腾在动手之前我先把期望拉回地面。手机上的 App 是可以同时运行、后台切换的而我要在 ESP32 上做的其实更接近“多系统引导”或者用嵌入式行业的话说是多镜像分区管理。每个“应用”本质上是一个独立的 ESP32 固件镜像里面可以包含 Arduino 代码、ESP-IDF 应用、MicroPython 固件等等只要它能被引导并启动就可以作为“应用”被安装。这个方案有几个明显的约束一个时刻只能跑一个应用切换必须重启没有后台运行。每个应用的大小受分区大小限制我这次示例里每个槽位给 512KB大多数小项目都够用但跑带完整 Web UI 的大工程会有点紧。应用没有沙箱概念它可以直接读写全部外设、甚至直接通过 esp_partition 读写其他分区安全性靠自觉。这些约束决定了它的定位适合做开发调试、现场演示、多教室项目的“口袋多合一”不适合做严肃的产品级动态更新。我心里很清楚这个方案就像给开发板装了一个“简易抽屉”它的价值不是技术上的突破而是把开发中的切换成本降到最低。1.3 为什么选 ESP32而不是 STM32 或树莓派 Pico好多人在评论区会问能不能用 STM32 实现能但要别扭得多。STM32 的 Flash 也可以分区也可以用 IAP 方式跳转引导多个固件但 STM32 没有自带 WiFi/BLE你要想从网页或手机传一个新应用进去还得外接网络模块或者自己写 USB 协议。ESP32 天生就带 WiFi 和蓝牙让“应用分发”这件事变得非常顺手。还有一个很重要的点是 ESP-IDF 已经提供了比较完整的 OTA 基础设施。它内置了 otadata 分区用来记录“下次从哪个槽启动”也有 esp_ota_set_boot_partition、esp_image_verify 这些 API我不用自己从零造轮子。这比我早年在 STM32 上自己写 Flash 擦写跳转函数省了太多功夫。STM32 的优势是实时性、丰富的外设和低功耗场景但在做“小型应用平台”这个玩法上ESP32 的开发体验确实更合适。1.4 和真正的 App Store 差在哪里如果把这个项目介绍给不懂硬件的朋友最准确的类比其实是不是 Android 的 App Store更像电脑上的“双系统引导器”。你在开机时选择一个系统进入只是这里每个“系统”都很轻量一共 4 个槽位加起来也就 2MB 左右。和真正的应用商店相比我还没有做在线商店页面、版本管理、签名机制。现在能做到的是开机进入 Launcher 菜单。在菜单里选定某个应用启动。通过 Web 页面上传新的应用镜像到指定空槽位。校验镜像合法性非法的镜像不会被启动。这就已经解决了“减少折腾”的核心问题。后面如果你想做得更完整可以继续加网络下载、远程更新甚至做一个简单的云端签名校验我在文章最后会补充一些扩展思路。2. 底层机制拆解分区表、启动链路与镜像校验2.1 Flash 分区表是什么为什么它决定整个方案ESP32 的 Flash 不是一整块乱用的而是通过一张“分区表”来划分区域。分区表里每行定义一个分区的名字、类型、子类型、起始偏移地址和大小。默认情况下一套常见的 Arduino 分区表大概是这样的分区名类型子类型说明nvsdatanvs存储密钥、校准数据、NVS 变量otadatadataota记录当前/下次 OTA 启动槽app0appfactory默认出厂应用烧录时的默认入口app1appota_0OTA 应用槽 0spiffsdataspiffs文件系统存储bootloader 上电后会读取 otadata 分区根据里面的启动信息决定加载 factory 还是某个 ota 槽。这才是整个方案的“地基”如果把应用槽命名为 ota_0、ota_1、ota_2、ota_3那么标准 bootloader 就能直接识别并引导它们不需要改 bootloader 源码。这里补充一个概念otadata 是 ESP-IDF OTA 机制里的“指针记录”它只有 8KB不存应用内容只存“下次该启动谁”的索引。你可以通过esp_ota_mark_app_valid_cancel_rollback等函数控制回滚也可以直接用esp_ota_set_boot_partition指定下次启动的分区。我做的 Launcher 大量依赖这些 API。2.2 我给 4MB Flash 做的分配方案为了照顾最常见的 4MB Flash 开发板我把分区表设计成如下样子# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x5000 otadata, data, ota, 0xE000, 0x2000 phy_init, data, phy, 0xF000, 0x1000 factory, app, factory, 0x10000, 0xC0000 ota_0, app, ota_0, 0xD0000, 0x80000 ota_1, app, ota_1, 0x150000, 0x80000 ota_2, app, ota_2, 0x1D0000, 0x80000 ota_3, app, ota_3, 0x250000, 0x80000 spiffs, data, spiffs, 0x2D0000, 0x130000简单算一下空间factory 分区也就是 Launcher占用 0x10000 到 0xD0000大小 0xC0000等于 768KB足够放一个带 Web 上传和 BLE 传输的引导程序。四个 ota 槽位每个 512KB从 0xD0000 一直排到 0x2D0000一共 2MB正好可以装下四五个小型应用。最后从 0x2D0000 到 0x400000 留了约 1.2MB 给 spiffs 文件系统应用运行时可以把配置、图标或日志写进去。选择 512KB 一个槽位是我实际测试后的权衡一个带 WiFi 的 Arduino 应用编译产物通常在 200KB 到 400KB 之间512KB 比较安全如果扩大槽位Launcher 空间就会变小或者总槽位数减少。对于 4MB Flash 的开发板这个比例是我试下来比较均衡的。如果你手头是 8MB / 16MB Flash可以把每个槽位扩大到 1MB 甚至 1.5MB分区表结构完全不用变只需调整偏移即可。2.3 启动链路上电之后到底发生了什么很多初学者第一次看到分区表会疑惑bootloader 怎么知道先跑哪个实际流程比想象中简单芯片上电后ROM 里固化的启动代码跳到 0x1000 地址的一级 bootloader。一级 bootloader 读取分区表并根据 otadata 里的信息决定加载哪个 app 分区。如果 otadata 无效或为空默认加载 factory 分区。factory 分区就是我们的 Launcher它扫描所有 ota_0 到 ota_3 槽位把状态和菜单打印出来。用户选择某个应用后Launcher 调用esp_ota_set_boot_partition把目标槽记录到 otadata然后主动esp_restart。再次上电bootloader 发现 otadata 指向 ota_1于是直接启动 ota_1 里的应用。这套链路唯一的“机关”就是 Launcher 在每次选择后都要写 otadata 并重启因为 ESP32 的 bootloader 一般不支持运行时跨分区跳转。虽然能做到不重启直接用app_main加载但会破坏应用的堆栈和初始化假设非常容易遇到玄学问题。所以我的方案是“宁可重启几十毫秒也不要在内存里硬跳”。2.4 镜像校验防止把半截文件写进 Flash如果你曾经用手机下载安装包一定碰到过“空间不足”或者“下载失败”的提示系统不会让你安装一个残缺的 APK。在 ESP32 上这个“检查完整性”的角色由esp_image_verify承担。它会在启动前检查分区头部、段表、App Descriptor甚至计算哈希确认这个镜像格式正确、内容完整。我在 Web 上传功能里每次写完整个镜像后都会调用esp_image_verify只有校验通过才更新菜单里的状态。这样即使传输中途断网或者写入时突然断电也不会在重启时走进一个坏分区导致“砖机”。你可能会觉得多此一举但我就因为测试时断电把某个槽写成了半截后来每次启动那个槽都会 crash排查了很久才发现是镜像校验没做。3. 代码实操从零写出一个可用的 Launcher3.1 开发环境准备与自定义分区表生效我用的是 Arduino 官方 esp32 支持包的组合因为社区资料最多坑相对少。开发环境可以选 Arduino IDE 也可以选 PlatformIO关键步骤是让编译系统使用我们自定义的分区表。在 Arduino IDE 里只要把上面那段分区表存成一个partitions.csv文件然后在Tools → Partition Scheme里选择Custom Partition Table并确认Flash Size选的是4MB (Default)或对应你开发板的实际大小。如果你用 PlatformIO直接在platformio.ini里加一行board_build.partitions partitions.csv很多第一次做这个项目的人都会忘记切换分区方案结果烧进去以后发现 Flash 布局还是默认的Launcher 里扫描不到任何 ota 槽位。这一点我在文章后面还会重点提。Launcher 本身建议用普通 Arduino 工程编译不需要魔改任何库。核心依赖是esp_partition.h、esp_ota.h以及一个 Web Server 库。我推荐ESPAsyncWebServer因为它支持异步接收请求体配合我们的“边收边写 Flash”策略最方便。3.2 实现应用列表与菜单选择Launcher 最重要的任务是扫描分区并让用户选择启动哪个应用。下面是精简版的 Arduino 代码#include esp_partition.h #include esp_ota.h const char* slot_names[4] { ota_0, ota_1, ota_2, ota_3 }; void printAppMenu() { Serial.println(\n ESP32 App Launcher ); for (int i 0; i 4; i) { const esp_partition_t* part esp_partition_find_first( ESP_PARTITION_TYPE_APP, static_castesp_partition_subtype_t(ESP_PARTITION_SUBTYPE_APP_OTA_MIN i), NULL); if (!part) { Serial.printf(%d: Invalid slot\n, i); continue; } // 简单判断分区里有没有合法镜像 bool is_valid esp_image_verify(ESP_IMAGE_VERIFY, NULL, part) ESP_OK; Serial.printf(%d: %-12s size0x%X %s\n, i, slot_names[i], part-size, is_valid ? [ok] : [empty]); } Serial.println(Input slot number to boot, U to upload via Web); } esp_partition_subtype_t slotToSubtype(int slot) { return static_castesp_partition_subtype_t(ESP_PARTITION_SUBTYPE_APP_OTA_MIN slot); }这里有两个地方需要解释。第一ESP_PARTITION_SUBTYPE_APP_OTA_MIN i是枚举 ota_0、ota_1、ota_2、ota_3 的标准做法它要求分区表里的子类型必须连续并且从ota_0开始。我最初偷懒直接写死字符串ota_1然后用esp_partition_find_first查也可以但不如这种索引方式干净。第二esp_image_verify不仅用来校验上传后的文件也可以用来判断一个槽位当前是否已经有可启动的镜像。如果分区还没写过或者写坏了这个函数会返回错误菜单里就显示[empty]这样你就不会选定一个坏槽。选择启动的代码更简单void bootSlot(int slot) { esp_partition_subtype_t subtype slotToSubtype(slot); const esp_partition_t* target esp_partition_find_first( ESP_PARTITION_TYPE_APP, subtype, NULL); if (!target) { Serial.printf(Slot %d not found\n, slot); return; } esp_err_t err esp_ota_set_boot_partition(target); if (err ! ESP_OK) { Serial.printf(Set boot partition failed: %s\n, esp_err_to_name(err)); return; } Serial.println(Rebooting into selected app...); delay(200); esp_restart(); }esp_ota_set_boot_partition是 ESP-IDF 提供的标准接口它会写 otadata让 bootloader 下一次启动时加载目标分区。3.3 通过 Web 上传一个“应用安装包”菜单只是第一步真正让这个平台变得好玩的是能通过 WiFi 往空槽里装新应用。我在这里选了 ESPAsyncWebServer因为普通 WebServer 在处理大文件上容易导致内存紧张而异步库可以在回调里把数据分块写进 Flash。核心逻辑是注册一个PUT请求接收方根据 URL 参数slot判断目标槽位然后把请求体里的二进制内容按 4KB 的粒度擦写分区。#include ESPAsyncWebServer.h #include esp_partition.h AsyncWebServer server(80); void setupWebUpload() { server.onRequestBody([](AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { if (!request-hasParam(slot)) { request-send(400, text/plain, Missing slot); return; } int slot request-getParam(slot)-value().toInt(); if (slot 0 || slot 3) { request-send(400, text/plain, Bad slot); return; } esp_partition_subtype_t subtype slotToSubtype(slot); const esp_partition_t* part esp_partition_find_first( ESP_PARTITION_TYPE_APP, subtype, NULL); if (!part) { request-send(400, text/plain, Partition not found); return; } // 每次第一个分片到达时先整体擦除目标分区 if (index 0) { esp_partition_erase_range(part, 0, part-size); Serial.printf(Erasing slot %d, size0x%X\n, slot, part-size); } // 按 4KB 扇区边界擦除然后写入 if (index % 0x1000 0) { esp_partition_erase_range(part, index, 0x1000); } esp_partition_write(part, index, data, len); Serial.printf(Written %u bytes at offset %u / %u\n, len, index, total); // 最后一个分片到达做整体校验 if (index len total) { bool ok esp_image_verify(ESP_IMAGE_VERIFY, NULL, part) ESP_OK; if (ok) { request-send(200, text/plain, OK); } else { request-send(400, text/plain, Invalid image); } } }); server.on(/, HTTP_GET, [](AsyncWebServerRequest* request) { request-send(200, text/html, h2ESP32 App Upload/h2 form idf Slot: select nameslot option value00/optionoption value11/option option value22/optionoption value33/option /selectbr input typefile namebin idbinbr button typebutton onclickupload()Install/button /form script function upload(){ let fdocument.getElementById(bin).files[0]; let sdocument.querySelector([nameslot]).value; fetch(/upload?slots,{method:PUT,body:f}).then(rr.text().then(talert(t))).catch(ealert(ERR e)); } /script); }); server.begin(); }这套流程的实际体验是电脑连上 ESP32 发射的热点打开192.168.4.1选一个槽选择一个app.bin点上传几秒钟后提示OK。然后回到串口菜单选择那个槽应用就“装”上了。需要提醒的是ESP32 的 Flash 擦写是有磨损寿命的我为了测试反复擦写了同一个槽几百次目前还没有出问题但量产场景必须考虑寿命问题。在做实验时建议尽量把常用应用放固定槽减少频繁整片擦写。3.4 BLE 上传的思路除了 Web还可以通过蓝牙 BLE 上传因为 ESP32 自带经典蓝牙和低功耗蓝牙。我在早期版本里用 BLE 做过一次把 BLE 建立一个 Write characteristic手机端用 nRF Connect 这类工具连接后选择固件文件通过 Write Long 分块写入。BLE 的好处是不需要电脑也没有路由器手机就能操作适合现场演示。坏处是 MTU 通常很小速度比 WiFi 慢不少而且要处理长包分段、粘包等问题代码量会多不少。如果你的应用场景主要是“电池供电的野外设备”可以考虑 BLE如果只是想省事Web 比 BLE 实用得多。3.5 让应用能“返回桌面”手机 App 一般都有返回按键但是 ESP32 应用自己跑起来之后如果不做一个通行的返回机制你只能复位物理按键——而复位后 bootloader 还是会根据 otadata 启动上次选中的应用回不到 Launcher。解决办法是给每个应用加一个“返回 Launcher”函数。下面是标准代码直接放到应用的setup()或按键回调里#include esp_partition.h #include esp_ota.h void backToLauncher() { Serial.println(Returning to Launcher...); const esp_partition_t* launcher esp_partition_find_first( ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); if (launcher) { esp_ota_set_boot_partition(launcher); } delay(200); esp_restart(); }我在所有测试应用里习惯用一个按钮接在某个 GPIO 上长按 2 秒调用这个函数。这样 Launcher → 应用 → Launcher 的循环就跑通了整个平台的操作逻辑和手机桌面几乎一致。4. 实测记录装两个 App 并用菜单切换4.1 准备测试应用为了验证平台我准备了两个小应用。第一个是传统的 LED 闪烁没什么特殊之处但结构上能说明问题。我加了backToLauncher把 GPIO2 接一个按键按下去就返回 Launcher。编译后生成的二进制文件叫LED_Blink.ino.bin大小约 180KB。第二个是我在项目热词里经常提到的 WebServer Demo启动后连上局域网提供一些 JSON 接口并控制一个 GPIO 点灯。这个应用编译出来接近 280KB放在 512KB 槽位里很宽裕。4.2 烧录 Launcher 并上传应用第一步把自定义partitions.csv和 Launcher 工程一起编译、烧录。注意烧录后首次启动四个应用槽都是空的菜单会显示[empty]。第二步启动 Web 上传服务。Launcher 上电后启动 WiFi AP热点名是ESP32-AppLauncher电脑连接后访问192.168.4.1选择 slot 0 和LED_Blink.ino.bin点击安装。上传过程中串口会输出分片写入日志几秒后页面提示OK。第三步把第二个应用装到 slot 1。这里有一个小细节同一台电脑如果之前连的是路由器需要断开再连回 AP或者让 Launcher 同时连接已有 WiFi 并且你与开发板在同一局域网内。为了简单我直接开了 AP 模式。4.3 启动切换实测上传完成后复位 Launcher串口输出大概是 ESP32 App Launcher 0: ota_0 size0x80000 [ok] 1: ota_1 size0x80000 [ok] 2: ota_2 size0x80000 [empty] 3: ota_3 size0x80000 [empty] Input slot number to boot, U to upload via Web输入0开发板重启LED 开始闪烁。进入应用后我按下 GPIO2 键串口打印 “Returning to Launcher…” 并重启菜单再次出现输入1再重启进入 WebServer Demo手机浏览器访问 ESP32 的 IP 就能看到测试页面。这次实测让我最满意的一点是从“决定切换功能”到“进入另一个应用”整个过程只需要一个串口输入或一次按键不碰 USB不碰 IDE也不重新编译。4.4 实测过程中的资源占用Launcher 本身包含菜单、OTA、Web Server、分区扫描编译后大约 260KB放在 768KB 的 factory 分区里绰绰有余。运行内存方面Launcher 启动后空闲堆大约 170KB 到 190KB处理 4KB 一包的上传不会出现内存吃紧。实际上如果你愿意把 Web Server 换成极简铁板烧式的原生 TCP 服务器Launcher 还能再瘦身。但考虑到开发效率和可维护性我都尽量使用成熟库这比无谓地压空间更重要。5. 避坑指南这个方案最容易翻车的几个地方5.1 自定义分区表没有生效这是新手最容易踩的坑。你在 Arduino IDE 里存了自定义 CSV也填好了partitions.csv路径但烧录后菜单里依然扫描不到任何 ota 槽或者启动地址错误直接无法开机。解决办法有两步。第一步检查 Tools 菜单里的Partition Scheme是否选中了Custom Partition Table如果没有Arduino 会继续用默认分区表。第二步区分“Bootloader Partition Table App”三种 bin烧录时不要只烧 App开机会跑飞。最稳妥的方法是先用 Arduino IDE 的Sketch → Export Compiled Binary导出 bin再用esptool.py或者支持“合并烧录”的 GUI 工具把 bootloader、partition table、Launcher 一起写入指定地址。如果你对烧录方式不熟建议看一遍官方的烧录地址表避免分区表地址写错。5.2 Web 上传中断导致镜像损坏我在最初版本里没有做esp_image_verify结果测试时中途拔掉了网线或者网页上传到一半浏览器自动断开槽位就写了一半。之后启动到那个槽位bootloader 会提示“invalid app image”然后继续回退到 Launcher也就是说不会直接变砖但它会反复启动失败看起来像卡死。解决办法就是文章里写的上传完成后必须调用esp_image_verify。同时我还养成了一个习惯上传前在上位机端先记录原始二进制的 MD5上传后再从 Flash 读出来算一次 MD5两边对比。虽然esp_image_verify已经能证明“可以启动”但 MD5 能更直观地确认字节完全一致。在关键应用上我强烈建议两者都做。5.3 Flash 擦写顺序与寿命问题ESP32 的 Flash 擦写不是按字节擦的而是按扇区通常 4KB擦。我在上传代码里是先从 offset 0 开始按 4KB 粒度一边擦一边写。这里有个细节esp_partition_erase_range的起始地址和长度必须是 4KB 对齐否则会返回错误。如果你传的 bin 长度不是 4KB 的整数倍最后几个字节也是安全的因为写入不要求按扇区对齐只要分区边界对齐就行。寿命问题也要多说两句。Flash 的擦除次数通常是 1 万到 10 万次具体取决于芯片。我为了测试分区表、上传功能两个星期里擦了同一个槽可能有三四百次目前还能跑但这个方向只能用于开发调试。如果做成产品我建议把常用功能做成只读把动态更新的槽位控制在少数几次更新以内或者改用 8MB / 16MB 的 Flash并在上层做版本限制。5.4 常见问题速查表症状可能原因排查与修复启动后只跑 Launcher应用菜单全是 empty分区表没换成自定义方案确认 Tools → Partition Scheme Custom Partition Table上传后提示 Invalid image文件不是完整 app bin或传输被中断重新导出 bin换一条稳定的 WiFi增加 MD5 校验选择了某个 slot启动后反复重启该槽镜像损坏或分区地址越界用esp_image_verify检查确认 bin 大小不超过分区容量Web 上传大文件时 OOM把整个文件读进了内存改成分片写入不要用普通 WebServer 一次性读取 body应用跑起来后没有返回菜单的方法应用里没加返回 Launcher 的逻辑在应用里调用esp_ota_set_boot_partition(factory)后重启WiFi 上传速度太慢AP 模式信道干扰或者 MTU 设置不合适切换到 STA 模式并和电脑在同一个局域网或者用以太网扩展模块 LAN8720把上传服务架在有线网络上5.5 可以继续扩展的方向这个平台做完以后我自己又往里加了几样东西一个是局域网 OTALauncher 主动连接家里的路由器不是开 AP这样手机和电脑都不用切换网络体验更顺另一个是把 Launcher 的串口菜单做成 Web 页面既能看状态也能在网页上直接点选启动哪个应用。如果你研究网络拓扑还可以把 WiFi Mesh 的思路加进来多个 ESP32 节点都刷同一套 Launcher通过 Mesh 网络分发应用镜像这样就连每台设备单独连接路由器都不用了。对于分布在多个房间的设备这套玩法特别省事。另外如果有项目必须用有线网络LAN8720 以太网模块搭配 ESP32 也很常见Launcher 里可以加一个有线优先的判定逻辑局域网内做远程批量更新会稳定很多。我的几点体会做完这个项目再回头看最初的问题“ESP32 能不能像手机一样安装应用”我的答案是能但要让它的“应用”跑在独立的 Flash 分区里而不是像手机那样动态加载到内存。分区级方案换来了稳定和隔离某个应用崩溃了拉闸复位Launcher 还在上传写坏了某个 slot只影响那个槽不影响其他应用。代价是切换必须重启而且不能用系统调用去共享外设但对于绝大多数单机 IoT 项目这已经够了。我目前把常玩的几个小项目都烧进了同一块板子现场演示的时候不用带笔记本只带一块板子和手机。你需要什么功能就开机选对应的“应用”。这个思路放在开发调试阶段尤其好用你甚至可以把它当成一个简易的“固件备份管理工具”把好的版本留在某个槽里随时切回去。最后想提醒一句折腾归折腾量产产品别这么玩Flash 寿命和引导安全性都需要更严肃的设计。如果你也做了类似的 ESP32 应用平台欢迎告诉我你踩过的最离谱的坑。
返回列表