/* 「重启不断电」(2026-09-27 用户定:**只管计划内重启**,VBUS 也保持)。只进 IDF 目标。 ---- SR-6 改成了什么 -------------------------------------------------------- 原文:ESP32 复位 / 上电期间五路全关,靠上下拉保证,不靠固件。现在分两类: · **非计划复位**(上电、崩溃、看门狗、按 EN、esptool)—— 仍然全关,仍然靠下拉; · **计划内重启**(OTA 换固件、自证超时回滚 —— 都经 `warm_restart()`)—— 五根 EFUSE_EN 用 gpio_hold 锁在重启前的电平,新固件起来**接管**:不重新上电、不等 PG; PCA9555 不随 ESP 复位,VBUS 照留,按键 / EDL 清零(bringup.h 的 `adopt`)。 代价:从 esp_restart 到新固件 t_safety 起来 ≈1~2 s,SR-5 / SW-8 不判(eFuse 自己的硬件保护还在)。 ---- 三道闸 ---------------------------------------------------------------- 1. **RTC 里的标记**(RTC_NOINIT,跨软件复位保留,上电是垃圾):魔数 + 掩码 + 反码。 只有「复位原因 = ESP_RST_SW」且标记对得上才接管;**读一次就作废**。 ⚠️ gpio_hold 本身不分复位原因:崩溃 / 看门狗时锁照样还在 —— 所以不接管的路 `rail_en_init_keep()` 也**无条件放开 hold**,那一刻才真的落到低。 2. **目标固件认不认**:镜像头后面放一个能力标记(`.rodata_custom_desc`,紧跟 esp_app_desc_t)。 推一个老固件(比如 0.1.0-c2)再锁 EN,它起来会照常写低、却写不到锁住的线上 —— **五路从此关不掉**。所以目标不认就不锁,照老样子全断重启。 3. **线上核对**:起来后先读 EN 脚的真电平(上一条命配的是 INPUT_OUTPUT,锁住的输入缓冲是开的)。 标记说开着、线上却是低 ⇒ hold 没生效,那一路不接管,交给按意图恢复(错开 + 等 PG)。 ---- 最坏情况 -------------------------------------------------------------- 新固件在接管之前就崩了:这次复位原因是 PANIC,不接管 ⇒ 全关。多穿过的是一次启动(≈1 s)。 */ #include #include "driver/gpio.h" #include "esp_attr.h" #include "esp_log.h" #include "esp_ota_ops.h" #include "phonerail.h" #include "rail_cfg.h" #include "warm_esp.h" static const char *TAG = "warm"; #define WARM_MAGIC 0x5752414Du /* "WRAM" */ typedef struct { uint32_t magic; uint8_t mask; uint8_t nmask; /* ~mask:RTC 内存上电是随机值,魔数碰巧对上也过不了这一道 */ } warm_mark_t; static RTC_NOINIT_ATTR warm_mark_t s_mark; /* ---- 能力标记 ------------------------------------------------------------- 镜像里的位置:镜像头 24 B + 第一个段头 8 B + esp_app_desc_t 256 B = **288**。 IDF 的链接脚本保证 `.rodata_custom_desc` 紧跟 `.rodata_desc`(sections.ld.in)。 rack 后端上传时也可以读它(firmware.py)。 */ #define CAPS_OFFSET (24 + 8 + sizeof(esp_app_desc_t)) #define CAPS_TAG "PRCAPS1" #define CAP_WARM (1u << 0) /* 认「重启不断电」的接管 */ typedef struct { char tag[8]; uint32_t caps; } caps_t; const caps_t g_phonerail_caps __attribute__((section(".rodata_custom_desc"), used)) = { .tag = CAPS_TAG, .caps = CAP_WARM, }; bool warm_target_ok(const esp_partition_t *part) { caps_t c; if (part == NULL || esp_partition_read(part, CAPS_OFFSET, &c, sizeof(c)) != ESP_OK) { return false; } return memcmp(c.tag, CAPS_TAG, sizeof(c.tag)) == 0 && (c.caps & CAP_WARM) != 0; } uint8_t warm_take(esp_reset_reason_t why) { const bool valid = s_mark.magic == WARM_MAGIC && (uint8_t)~s_mark.mask == s_mark.nmask; const uint8_t all = (uint8_t)((1u << PHONERAIL_RAIL_COUNT) - 1u); const uint8_t before = valid ? (uint8_t)(s_mark.mask & all) : 0u; uint8_t m = (why == ESP_RST_SW) ? before : 0u; memset(&s_mark, 0, sizeof(s_mark)); /* 一次性:下一次(哪怕是软件复位)不再算数 */ /* 此刻 EN 线上的真电平(没有 hold 时是下拉的 0;上一条命锁过的才可能是 1)。 非计划复位时这里是 1 的那几路,bringup 第一步放开 hold 之后才落到低 —— 日志里留一笔。 */ uint8_t pads = 0; for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { if (gpio_get_level(rail_cfg(rail)->efuse_en) != 0) { pads |= (uint8_t)(1u << (rail - 1)); } } if (pads != 0 && !(valid && why == ESP_RST_SW)) { ESP_LOGW(TAG, "非计划复位(原因 %d)但 EN 线上还锁着 0x%02x:马上放开、全关(SR-6)", (int)why, (unsigned)pads); } if (!valid) { return 0; } if (why != ESP_RST_SW) { ESP_LOGW(TAG, "有接管标记但复位原因是 %d(不是软件复位)—— 按非计划复位全关", (int)why); return 0; } for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { const uint8_t bit = (uint8_t)(1u << (rail - 1)); if ((m & bit) == 0) { continue; } const int pin = rail_cfg(rail)->efuse_en; if (gpio_get_level(pin) == 0) { ESP_LOGE(TAG, "CH%d:标记说开着,EN 线上却是低(hold 没生效?)—— 不接管,按意图重开", rail); m &= (uint8_t)~bit; } } ESP_LOGI(TAG, "计划内重启:接管 0x%02x(重启前开着 0x%02x),不重新上电", (unsigned)m, (unsigned)before); return m; } void warm_restart(bool rollback) { const esp_partition_t *target = rollback ? esp_ota_get_next_update_partition(NULL) : esp_ota_get_boot_partition(); if (warm_target_ok(target)) { const uint8_t m = rail_efuse_mask(); s_mark = (warm_mark_t){ .magic = WARM_MAGIC, .mask = m, .nmask = (uint8_t)~m }; (void)rail_en_hold_all(); /* ⚠️ 从这一句起 t_safety 写不动 EN —— 下一句就是重启 */ ESP_LOGW(TAG, "%s:锁住 EN(开着 0x%02x),手机不断电", rollback ? "回滚" : "换固件", (unsigned)m); } else { ESP_LOGW(TAG, "%s:目标固件(%s)不认接管 —— 五路全断一次", rollback ? "回滚" : "换固件", target ? target->label : "?"); } if (rollback) { esp_ota_mark_app_invalid_rollback_and_reboot(); /* 成功不返回 */ ESP_LOGE(TAG, "回滚失败(没有可回的旧版?),就地重启"); } esp_restart(); }