From a59364e2391db4457c93eaf07d38119fd37a39e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=BE=99=E8=99=BE?= Date: Tue, 2 Jun 2026 03:25:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(ota):=20=E5=AE=8C=E5=96=84OTA=E4=BC=A0?= =?UTF-8?q?=E8=BE=93=E5=8D=8F=E8=AE=AE=E5=92=8C=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改finish ACK验证逻辑,不再接受structured ACK 0xBAC0作为FINISH成功信号 - 移除appRequestBootloader函数的timeout参数,改为内部实现重试机制 - 新增probeBootHandshake函数用于检测GD32是否已处于bootloader模式 - 调整数据包传输中expected_ack值从0xFFFFFFFFU改为0xF0F0F0F0U - 增加重试次数限制:poll轮询从5次增加到10次,重发次数从3次增加到10次 - 实现FINISH_OTA阶段的重试机制,支持WAIT帧处理和错误恢复 - 在startHandshake和forwardFirmware中添加bootloader探测和状态检查 - 优化APP存活确认失败时的错误处理,添加设备复位操作 - 统一各阶段错误处理逻辑,确保失败时正确清理状态并返回错误码 --- main/relay/ota_relay.cpp | 156 +++++++++++++++++++++++----------- main/transport/spi_master.cpp | 44 ++++++---- main/transport/spi_master.h | 4 +- 3 files changed, 136 insertions(+), 68 deletions(-) diff --git a/main/relay/ota_relay.cpp b/main/relay/ota_relay.cpp index 522f49b..0a60060 100644 --- a/main/relay/ota_relay.cpp +++ b/main/relay/ota_relay.cpp @@ -123,8 +123,8 @@ bool OtaRelay::isSizeSeq0Ack(const uint8_t *payload, size_t len) bool OtaRelay::isFinishPlainAck(const uint8_t *payload, size_t len) { if (!payload) return false; - if (len >= 2U && payload[0] == 0x0AU && payload[1] == 0xACU) return true; - if (len >= 7U && payload[0] == 0xBAU && payload[1] == 0xC0U) return true; + if (len == 2U && payload[0] == 0x0AU && payload[1] == 0xACU) return true; + /* structured ACK 0xBAC0 no longer accepted as FINISH success */ return false; } @@ -346,7 +346,7 @@ esp_err_t OtaRelay::flashReadChunk(uint32_t offset, uint8_t *data, size_t len) esp_err_t OtaRelay::requestGd32AppEnterBootloader() { DEBUG_INFO("OTA", "尝试通知 GD32 APP 进入 Bootloader OTA 窗口"); - esp_err_t err = SpiMaster::instance().appRequestBootloader(500); + esp_err_t err = SpiMaster::instance().appRequestBootloader(); if (err == ESP_OK) { /* 收到确认:可能来自 APP(即将跳转 BOOT)或 BOOT 本身(已在 BOOT)。 * 如果来自 APP,GD32 需要复位并重新初始化 SPI 外设,需等 ~1-2s; @@ -360,6 +360,28 @@ esp_err_t OtaRelay::requestGd32AppEnterBootloader() return err; } + +ota_result_t OtaRelay::probeBootHandshake() +{ + /* 0xB0 timeout: probe if GD32 is already in BOOT. + * Send one lightweight HANDSHAKE_REQ, accept only echo ACK 0x00 0xAC + * as "already in BOOT" signal; otherwise fail immediately. */ + DEBUG_INFO("OTA", "0xB0 timeout, probing if GD32 already in BOOT..."); + uint8_t payload[] = {0x00, 0xAC}; + uint8_t resp_type = 0U; + uint8_t resp_payload[64] = {0}; + size_t resp_len = 0U; + + esp_err_t err = SpiMaster::instance().otaTransfer(SPI_OTA_CMD_REQ, payload, sizeof(payload), + &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 500); + if (err == ESP_OK && resp_type == SPI_OTA_RSP_ACK && isHandshakeEchoAck(resp_payload, resp_len)) { + DEBUG_INFO("OTA", "BOOT probe success: GD32 already in BOOT, continue OTA"); + return OTA_RESULT_OK; + } + DEBUG_ERR("OTA", "BOOT probe failed: GD32 not in BOOT, abort (err=%d resp_type=0x%02X)", err, resp_type); + return OTA_RESULT_ERR_TIMEOUT; +} + ota_result_t OtaRelay::sendHandshakeReq() { DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ==="); @@ -642,7 +664,7 @@ ota_result_t OtaRelay::relayPacketsFromFlash() break; } - uint32_t expected_ack = (seq == total_packets - 1) ? 0xFFFFFFFFU : (seq + 1U); + uint32_t expected_ack = (seq == total_packets - 1) ? 0xF0F0F0F0U : (seq + 1U); /* 跳过上一阶段残留的、seq 不匹配的结构化 ACK。 * @@ -669,8 +691,8 @@ ota_result_t OtaRelay::relayPacketsFromFlash() * 超时/非ACK 退出时需要重发当前包(GD32 Flash 缓冲满时会发 WAIT 后排空, * ESP32 必须重发才能让 GD32 重新处理)。 */ bool got_expected = false; - for (uint32_t retry = 0; retry < 5; ++retry) { - DEBUG_WARN("OTA", "包 %u 收到非目标ACK(seq不匹配),继续轮询(%u/5)...", seq, retry + 1); + for (uint32_t retry = 0; retry < 10; ++retry) { + DEBUG_DBG("OTA", "包 %u 收到非目标ACK(seq不匹配),继续轮询(%u/5)...", seq, retry + 1); err = SpiMaster::instance().otaPollResponse(&resp_type, resp_payload, sizeof(resp_payload), &resp_len, 2000); if (err == ESP_OK && resp_type == SPI_OTA_RSP_WAIT) { err = waitUntilNotWait("DATA_PACKET", &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 100, 5); @@ -707,7 +729,7 @@ ota_result_t OtaRelay::relayPacketsFromFlash() if (parseStructuredError(resp_payload, resp_len, &err_seq, &err_code) && err_seq == seq) { DEBUG_WARN("OTA", "包 %u 收到结构化错误 err=0x%02X,准备重发", seq, err_code); relayState = RELAY_PACKET_ACK_RETRY; - if (++retry_count >= 3U) { + if (++retry_count >=10U) { DEBUG_ERR("OTA", "包 %u 重发次数超限", seq); result = (err_code == 0x04U) ? OTA_RESULT_ERR_CRC : OTA_RESULT_ERR_GD32; break; @@ -718,7 +740,7 @@ ota_result_t OtaRelay::relayPacketsFromFlash() /* 轮询耗尽仍未拿到期望 ACK -> 重发当前包 */ DEBUG_WARN("OTA", "包 %u 轮询耗尽未匹配,重发 (retry=%u/3)", seq, retry_count + 1); relayState = RELAY_PACKET_ACK_RETRY; - if (++retry_count >= 3U) { + if (++retry_count >= 10U) { DEBUG_ERR("OTA", "包 %u 重发次数超限", seq); result = OTA_RESULT_ERR_GD32; break; @@ -756,45 +778,71 @@ ota_result_t OtaRelay::finishOta() DEBUG_INFO("OTA", "=== 阶段4: 结束 OTA ==="); DEBUG_INFO("OTA", "发送 FINISH_OTA (0x0A 0xAC),等待 GD32 确认即将跳转 APP"); + uint8_t finish_payload[2] = {0x0A, 0xAC}; uint8_t resp_type = 0; uint8_t resp_payload[32] = {0}; size_t resp_len = 0; - uint8_t finish_payload[2] = {0x0A, 0xAC}; - relayState = RELAY_FINISH_SENT; - esp_err_t err = SpiMaster::instance().otaTransfer(SPI_OTA_CMD_DATA, finish_payload, sizeof(finish_payload), - &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000); - logRespPayload("FINISH_OTA", resp_type, resp_payload, resp_len); - err = pollUntilExpectedAckShape("FINISH_OTA", &resp_type, resp_payload, - sizeof(resp_payload), &resp_len, - 1000, 10, 8, OtaRelay::isFinishPlainAck); - if (err != ESP_OK || resp_type != SPI_OTA_RSP_ACK || - !isFinishPlainAck(resp_payload, resp_len)) { - DEBUG_ERR("OTA", "FINISH_OTA 失败, err=%d, resp_type=0x%02X, resp_len=%zu", err, resp_type, resp_len); + /* FINISH_OTA 重试机制:GD32 Flash 缓冲未排空时会回 WAIT, + * 需要重试直到收到正确的 ACK(0x0A 0xAC) 或超时。 */ + for (uint32_t retry = 0; retry < 10; ++retry) { + resp_type = 0; + resp_len = 0; + memset(resp_payload, 0, sizeof(resp_payload)); + + relayState = RELAY_FINISH_SENT; + esp_err_t err = SpiMaster::instance().otaTransfer(SPI_OTA_CMD_DATA, finish_payload, sizeof(finish_payload), + &resp_type, resp_payload, sizeof(resp_payload), &resp_len, 3000); + logRespPayload("FINISH_OTA", resp_type, resp_payload, resp_len); + if (err != ESP_OK) { + DEBUG_WARN("OTA", "FINISH_OTA 传输失败, err=%d, retry=%u/10", err, retry + 1); + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + + err = pollUntilExpectedAckShape("FINISH_OTA", &resp_type, resp_payload, + sizeof(resp_payload), &resp_len, + 1000, 10, 8, OtaRelay::isFinishPlainAck); + if (err != ESP_OK) { + DEBUG_WARN("OTA", "FINISH_OTA poll 失败, err=%d, retry=%u/10", err, retry + 1); + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + + if (resp_type == SPI_OTA_RSP_ACK && isFinishPlainAck(resp_payload, resp_len)) { + /* 成功收到 FINISH_OTA ACK */ + break; + } + + /* 检查是否为 WAIT 帧(GD32 Flash 缓冲未排空) */ + if (resp_type == SPI_OTA_RSP_WAIT) { + DEBUG_INFO("OTA", "FINISH_OTA 收到 WAIT, GD32 正在排空 Flash 缓冲, retry=%u/10", retry + 1); + vTaskDelay(pdMS_TO_TICKS(200)); + continue; + } + + /* 检查是否为错误帧 */ + uint32_t err_seq = 0; + uint8_t err_code = 0; + if (parseStructuredError(resp_payload, resp_len, &err_seq, &err_code)) { + DEBUG_ERR("OTA", "FINISH_OTA 收到错误帧 err=0x%02X, retry=%u/10", err_code, retry + 1); + vTaskDelay(pdMS_TO_TICKS(100)); + continue; + } + + DEBUG_WARN("OTA", "FINISH_OTA 响应异常, resp_type=0x%02X, resp_len=%zu, retry=%u/10", + resp_type, resp_len, retry + 1); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + if (resp_type != SPI_OTA_RSP_ACK || !isFinishPlainAck(resp_payload, resp_len)) { + DEBUG_ERR("OTA", "FINISH_OTA 失败, err=0, resp_type=0x%02X, resp_len=%zu", resp_type, resp_len); relayState = RELAY_ERROR; return OTA_RESULT_ERR_GD32; } - /* 检查是否为结构化错误帧(BOOT validateApplication 失败等) */ - uint32_t err_seq = 0; - uint8_t err_code = 0; - if (parseStructuredError(resp_payload, resp_len, &err_seq, &err_code)) { - DEBUG_ERR("OTA", "FINISH_OTA BOOT 返回错误: seq=0x%08lX err=0x%02X (%s)", - (unsigned long)err_seq, err_code, - err_code == 0x02U ? "FLASH_ERROR/validateApp失败" : - err_code == 0x04U ? "INVALID_PACKET" : - err_code == 0x03U ? "INVALID_KEY" : - "未知错误"); - relayState = RELAY_ERROR; - return OTA_RESULT_ERR_GD32; - } - - /* 正常明文 ACK: 0x0A 0xAC */ - if (!isFinishPlainAck(resp_payload, resp_len)) { - DEBUG_ERR("OTA", "FINISH_OTA 响应格式异常: len=%zu payload[0]=0x%02X", resp_len, resp_payload[0]); - relayState = RELAY_ERROR; - return OTA_RESULT_ERR_GD32; - } + /* isFinishPlainAck already verified: only 0x0A 0xAC (len==2) passes. + * Structured error frames (0xBA 0xC0 ...) are rejected above. */ relayState = RELAY_DONE; Protocol::sessionKeyClear(&m_sessionCtx); @@ -833,7 +881,9 @@ ota_result_t OtaRelay::finishOta() } if (!gd32AppAlive) { - DEBUG_WARN("OTA", "GD32 APP 存活确认超时,OTA 传输已完成但 APP 未启动"); + DEBUG_ERR("OTA", "GD32 APP alive confirm timeout, OTA failed"); + reset(); + return OTA_RESULT_ERR_GD32; } return OTA_RESULT_OK; @@ -847,21 +897,28 @@ ota_result_t OtaRelay::startHandshake() { ota_result_t res; - requestGd32AppEnterBootloader(); + esp_err_t boot_err = requestGd32AppEnterBootloader(); + if (boot_err != ESP_OK) { + res = probeBootHandshake(); + if (res != OTA_RESULT_OK) { + reset(); + return res; + } + } res = sendHandshakeReq(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = sendNonceAndAuthenticate(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } ota_meta_t meta; OtaRelay::metaLoad(&meta); res = sendFirmwareSize(meta.total_size); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = relayPacketsFromFlash(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = finishOta(); @@ -885,16 +942,16 @@ ota_result_t OtaRelay::forwardFirmware() requestGd32AppEnterBootloader(); ota_result_t res = sendHandshakeReq(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = sendNonceAndAuthenticate(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = sendFirmwareSize(meta.total_size); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = relayPacketsFromFlash(); - if (res != OTA_RESULT_OK) return res; + if (res != OTA_RESULT_OK) { reset(); return res; } res = finishOta(); @@ -968,3 +1025,4 @@ void OtaRelay::taskEntry(void *pvParameters) } } } + diff --git a/main/transport/spi_master.cpp b/main/transport/spi_master.cpp index b92bd2d..b003116 100644 --- a/main/transport/spi_master.cpp +++ b/main/transport/spi_master.cpp @@ -349,31 +349,41 @@ esp_err_t SpiMaster::otaPollResponse(uint8_t *resp_type, uint8_t *resp_payload, return ESP_ERR_TIMEOUT; } -esp_err_t SpiMaster::appRequestBootloader(uint32_t timeout_ms) +esp_err_t SpiMaster::appRequestBootloader() { DEBUG_INFO("SPI", "请求 GD32 APP 进入 Bootloader..."); - uint8_t cmd = SPI_APP_ENTER_BOOT_CMD; - esp_err_t ret = transferClocked(&cmd, 1, NULL, 0); - if (ret != ESP_OK) { - DEBUG_WARN("SPI", "发送 Bootloader 请求失败: %s", esp_err_to_name(ret)); - return ret; - } + constexpr uint32_t kMaxRetries = 3U; + constexpr uint32_t kPerRetryAckMs = 200U; + constexpr uint32_t kRetryGapMs = 50U; - TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms); + for (uint32_t retry = 0U; retry < kMaxRetries; ++retry) { + if (retry > 0U) { + DEBUG_WARN("SPI", "Bootloader 请求重发 (%lu/%lu)", (unsigned long)retry, (unsigned long)kMaxRetries); + vTaskDelay(pdMS_TO_TICKS(kRetryGapMs)); + } - while (xTaskGetTickCount() <= deadline) { - uint8_t rx = 0; - uint8_t dummy = 0xFF; - ret = transferClocked(&dummy, 1, &rx, 1); - if (ret == ESP_OK && rx == SPI_APP_ENTER_BOOT_ACK_OK) { - DEBUG_INFO("SPI", "GD32 APP 已确认进入 Bootloader"); - return ESP_OK; + uint8_t cmd = SPI_APP_ENTER_BOOT_CMD; + esp_err_t ret = transferClocked(&cmd, 1, NULL, 0); + if (ret != ESP_OK) { + DEBUG_WARN("SPI", "发送 Bootloader 请求失败: %s", esp_err_to_name(ret)); + continue; + } + + TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(kPerRetryAckMs); + while (xTaskGetTickCount() <= deadline) { + uint8_t rx = 0; + uint8_t dummy = 0xFF; + ret = transferClocked(&dummy, 1, &rx, 1); + if (ret == ESP_OK && rx == SPI_APP_ENTER_BOOT_ACK_OK) { + DEBUG_INFO("SPI", "GD32 APP 已确认进入 Bootloader (retry=%lu)", (unsigned long)retry); + return ESP_OK; + } + vTaskDelay(pdMS_TO_TICKS(10)); } - vTaskDelay(pdMS_TO_TICKS(10)); } - DEBUG_WARN("SPI", "GD32 APP 进入 Bootloader 确认超时"); + DEBUG_WARN("SPI", "GD32 APP 进入 Bootloader 确认超时 (retries exhausted)"); return ESP_ERR_TIMEOUT; } diff --git a/main/transport/spi_master.h b/main/transport/spi_master.h index c45b295..7d62934 100644 --- a/main/transport/spi_master.h +++ b/main/transport/spi_master.h @@ -94,7 +94,7 @@ public: size_t resp_max, size_t *resp_len, uint32_t timeout_ms); /** @brief 通知 GD32 DCU_APP 设置 OTA 标志并软复位进入 Bootloader。 */ - esp_err_t appRequestBootloader(uint32_t timeout_ms); + esp_err_t appRequestBootloader(); /** @brief 向 GD32 APP 发送存活探测命令,等待 APP 回复 ALIVE ACK。 */ esp_err_t appPing(uint32_t timeout_ms, uint32_t *uptime_sec); @@ -146,4 +146,4 @@ private: size_t *frame_offset); }; -#endif // SPI_MASTER_H \ No newline at end of file +#endif // SPI_MASTER_H