![[实践]ESP32自定义分区表优化 -- 释放FLASH空间以增强OTA与双模连接能力](http://pic.xiahunao.cn/yaotu/[实践]ESP32自定义分区表优化 -- 释放FLASH空间以增强OTA与双模连接能力)
1. 为什么需要自定义分区表ESP32作为一款广泛应用于物联网设备的芯片其内置的FLASH存储空间往往成为开发中的瓶颈。我最近在开发一个智能家居网关项目时就遇到了FLASH空间不足的问题——当同时启用Wi-Fi、蓝牙和HTTP OTA功能时默认的4MB FLASH空间竟然不够用了乐鑫官方提供的默认分区表将FLASH划分为三个主要区域factory分区用于存放出厂固件、ota_0和ota_1分区用于OTA升级。这种设计本意是好的factory分区作为安全网万一OTA升级失败还能回退。但在实际项目中我发现这个设计有点浪费——既然ota_0和ota_1本身就可以互相作为备份为什么还要保留factory分区呢更让人头疼的是随着功能增加固件体积越来越大。以我的项目为例基础固件1.2MBWi-Fi协议栈约300KB蓝牙协议栈约250KBHTTP OTA功能约150KB应用程序代码约800KB这样算下来总大小已经接近3MB而默认的ota分区每个只有1.5MB左右根本装不下完整功能。这时候自定义分区表就成了必选项。2. 理解ESP32的分区表机制在动手修改之前我们需要先搞清楚ESP32的分区表是如何工作的。分区表本质上是一个存储空间分配方案告诉芯片哪些地址范围用来存放什么内容。标准的4MB FLASH分区表大致如下分区名类型子类型偏移地址大小说明nvsdatanvs0x90000x5000非易失性存储otadatadataota0xe0000x2000OTA数据记录phy_initdataphy0x100000x1000RF校准数据factoryappfactory0x200001.5M出厂固件可删除ota_0appota_00x1700001.5MOTA分区0ota_1appota_10x2c00001.5MOTA分区1看到问题了吗factory分区占用了1.5MB空间但实际上我们可以完全依赖ota_0和ota_1来实现安全的OTA升级流程。我的做法是删除factory分区将释放出的1.5MB空间平均分配给ota_0和ota_1每个OTA分区扩大到约2.25MB这样修改后我们的固件就有足够的空间容纳所有功能模块了。不过要注意这种修改会影响OTA升级策略我们需要确保首次烧录时直接烧录到ota_0分区升级时在ota_0和ota_1之间交替写入通过otadata分区记录当前活动分区3. 实战创建自定义分区表现在让我们一步步实现这个优化方案。我使用的是ESP-IDF v4.4开发环境其他版本也大同小异。3.1 准备分区表文件首先在项目根目录下创建或修改partitions.csv文件。这是我的配置示例# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000, otadata, data, ota, 0xe000, 0x2000, phy_init, data, phy, 0x10000, 0x1000, ota_0, app, ota_0, 0x20000, 0x220000, ota_1, app, ota_1, 0x240000,0x220000,关键修改点完全移除了factory分区ota_0从原来的0x170000提前到0x20000两个OTA分区大小都扩大到0x220000约2.125MB注意偏移地址必须按4KB对齐大小也建议是4KB的整数倍这是FLASH擦除块的基本单位。3.2 配置工程使用自定义分区表在项目的sdkconfig文件或通过idf.py menuconfig进行以下配置进入Partition Table配置菜单选择Custom partition table CSV输入你的CSV文件路径如partitions.csv确保Offset of partition table保持默认值0x80003.3 验证分区表有效性编译前建议先用以下命令验证分区表python $IDF_PATH/components/partition_table/gen_esp32part.py partitions.csv partitions.bin这会生成二进制分区表文件同时会输出解析结果。仔细检查所有分区是否有重叠OTA分区大小是否满足需求关键分区如otadata是否保留4. OTA升级流程适配修改分区表后我们的OTA流程也需要相应调整。以下是关键修改点4.1 首次烧录策略不再烧录factory分区而是直接烧录到ota_0esptool.py --chip esp32 --port /dev/ttyUSB0 \ --baud 921600 write_flash \ 0x1000 bootloader/bootloader.bin \ 0x8000 partition_table/partition-table.bin \ 0x20000 ota_data_initial.bin \ 0x21000 your_app.bin注意ota_data_initial.bin是一个初始化otadata分区的特殊文件应用固件烧录到0x20000ota_0的起始地址4.2 HTTP OTA服务端适配服务端需要知道设备现在使用的是哪个OTA分区。可以在HTTP请求中添加headeresp_http_client_set_header(client, X-ESP32-Current-OTA, ota_0);服务端可以根据这个信息决定下一步动作如果当前是ota_0就下载到ota_1如果当前是ota_1就下载到ota_04.3 客户端升级逻辑在客户端代码中需要正确处理分区切换void launch_new_firmware(void) { const esp_partition_t *running esp_ota_get_running_partition(); esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, ota_state) ESP_OK) { if (ota_state ESP_OTA_IMG_PENDING_VERIFY) { // 新固件验证通过确认升级 esp_ota_mark_app_valid_cancel_rollback(); } } const esp_partition_t *next esp_ota_get_next_update_partition(NULL); printf(准备从%s切换到%s\n, running-label, next-label); }5. 双模连接的性能优化解决了存储空间问题后Wi-Fi和蓝牙共存时的性能优化也很关键。以下是我总结的几个实用技巧5.1 内存分配策略// 优先为Wi-Fi分配连续内存 heap_caps_malloc_prefer_size(1024, MALLOC_CAP_DEFAULT|MALLOC_CAP_8BIT, MALLOC_CAP_SPIRAM); // 蓝牙协议栈使用内部内存 esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); esp_bt_controller_init(bt_cfg);5.2 任务优先级调整// Wi-Fi任务优先级应高于蓝牙 esp_wifi_set_default_wifi_sta_handlers(); esp_wifi_set_default_wifi_ap_handlers(); // 调整蓝牙任务优先级 xTaskCreatePinnedToCore(bt_task, bt_task, 4096, NULL, 5, NULL, 0);5.3 射频时间分配在sdkconfig中调整CONFIG_ESP32_WIFI_SOFTAP_BEACON_INTERVAL100 CONFIG_ESP32_WIFI_STA_DISCONNECTED_PM_ENABLEn CONFIG_BTDM_CTRL_BLE_MAX_CONN3这些配置可以平衡Wi-Fi和蓝牙的射频占用时间减少冲突。6. 常见问题与解决方案在实际项目中我遇到过不少坑这里分享几个典型问题的解决方法6.1 OTA升级失败回退当新固件启动失败时ESP32会自动回退到上一个版本。为了确保这个过程可靠在app_main()开始时添加验证代码esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, ota_state) ESP_OK) { if (ota_state ESP_OTA_IMG_PENDING_VERIFY) { // 运行基本功能测试 if (!hardware_self_test()) { esp_ota_mark_app_invalid_rollback_and_reboot(); } } }设置合理的看门狗超时esp_task_wdt_config_t twdt_config { .timeout_ms 10000, .idle_core_mask (1 portNUM_PROCESSORS) - 1, }; esp_task_wdt_init(twdt_config, true);6.2 FLASH磨损均衡频繁OTA会加速FLASH磨损。可以通过以下方式延长寿命交替使用两个OTA分区const esp_partition_t *next esp_ota_get_next_update_partition(NULL); // 确保不连续写入同一分区 if (strcmp(next-label, last_used_partition) 0) { next (strcmp(next-label, ota_0) 0) ? esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL) : esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL); }启用FLASH缓存// 在sdkconfig中设置 CONFIG_SPI_FLASH_CACHE_WRAP_ENABLEy CONFIG_SPI_FLASH_USE_ROM_IMPLy6.3 双模连接稳定性问题Wi-Fi和蓝牙同时工作时可能出现连接不稳定。我的解决方案是实现动态带宽分配// 当蓝牙传输量大时降低Wi-Fi速率 if (bt_traffic threshold) { esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, WIFI_BW_HT20); } else { esp_wifi_set_bandwidth(ESP_IF_WIFI_STA, WIFI_BW_HT40); }使用时间分片// 每100ms切换一次优先级 xTaskCreate(time_slicing_task, time_slice, 2048, NULL, 3, NULL); void time_slicing_task(void *arg) { while (1) { vTaskDelay(100 / portTICK_PERIOD_MS); if (wifi_active) { esp_wifi_set_ps(WIFI_PS_MIN_MODEM); esp_bt_controller_disable(); } else { esp_wifi_set_ps(WIFI_PS_MAX_MODEM); esp_bt_controller_enable(ESP_BT_MODE_BLE); } wifi_active !wifi_active; } }经过这些优化后我的智能家居网关项目终于可以稳定运行所有功能模块同时支持可靠的OTA升级。整个过程虽然踩了不少坑但收获的经验非常宝贵。如果你也遇到类似问题不妨试试这个方案。记住在修改分区表前一定要备份重要数据测试时建议先用开发板验证确认无误后再应用到量产设备。