fix(ota): 修复OtaCoordinator互斥锁死锁和SPI appPing首字节丢失

- OtaCoordinator lock/unlock: 移除共享 m_locked 标志,改为传参传递 take
  结果,消除多任务并发 lock() 超时覆盖持锁状态导致 mutex 永久死锁的问题
- SPI appPing: 发送帧填充至 16 字节防止首字节丢失,增大轮询间隔至 10ms,
  增大接收缓冲区避免字节偏移解析失败
- SPI init: 先创建 mutex 再使用 LockGuard,消除竞态窗口
- 压制 WiFi 框架 INFO 日志 (pp/net80211/wifi/wifi_init),减少启动打印
- 降级 7 条调试日志为 DBG 级别(UART0 初始化、冷启动缓存清理、互斥锁/重连
  任务/回调/netif 创建)
- GD32 版本号读取增加 5 次重试上限和 OTA 繁忙时跳过逻辑
- 移除 forwardFirmware 中已过时注释
This commit is contained in:
小龙虾
2026-06-17 11:52:26 +08:00
parent 98b5cc1ba1
commit 2586582e57
8 changed files with 214 additions and 178 deletions
+2 -2
View File
@@ -8,7 +8,7 @@
#define FW_VERSION_STRING "0.3.12"
// 编译时间
#define BUILD_TIME "2026-06-17 02:12:38"
#define BUILD_TIME "2026-06-17 03:09:19"
// Git 信息
#define GIT_HASH "5b2c5f8"
#define GIT_HASH "98b5cc1"
+1 -1
View File
@@ -196,7 +196,7 @@ esp_err_t DebugLog::uartInit()
}
m_uartInitialized = true;
ESP_LOGI(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
ESP_LOGD(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE);
return ESP_OK;
+129 -119
View File
@@ -23,28 +23,26 @@ OtaCoordinator& OtaCoordinator::instance()
// ============================================================
// 线程安全辅助
// ============================================================
void OtaCoordinator::lock()
bool OtaCoordinator::lock()
{
// ★ 修复:FreeRTOS mutex (xSemaphoreCreateMutex) 只能由 take 的 task give。
// 超时后必须在 unlock() 中跳过 give,避免 xTaskPriorityDisinherit assert。
// 此处假设 OtaCoordinator 内部不嵌套 lock(全局已确认:无嵌套调用)。
m_locked = false;
if (m_mutex) {
m_locked = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
if (!m_locked) {
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
(unsigned)pdTICKS_TO_MS(kLockTimeout));
}
// ★ 修复: 返回 take 结果,由调用方传回 unlock()。
// 旧实现用成员 bool m_locked 记录 take 结果,多任务并发 lock() 时
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
if (!m_mutex) return false;
bool ok = (xSemaphoreTake(m_mutex, kLockTimeout) == pdTRUE);
if (!ok) {
DEBUG_WARN(TAG, "lock() 超时 (%u ms),本次状态变更被跳过",
(unsigned)pdTICKS_TO_MS(kLockTimeout));
}
return ok;
}
void OtaCoordinator::unlock()
void OtaCoordinator::unlock(bool handle)
{
// ★ 修复:仅在 lock() 成功 take 时才 give。
// 错误的 task 给同一 mutex give 会触发 xTaskPriorityDisinherit assert → PANIC。
if (m_locked && m_mutex) {
// ★ 修复: 仅在对应的 lock() 成功 take 时才 give。
if (handle && m_mutex) {
xSemaphoreGive(m_mutex);
m_locked = false;
}
}
@@ -119,51 +117,51 @@ void OtaCoordinator::init()
// ============================================================
esp_err_t OtaCoordinator::beginGd32Storage(uint32_t total_size)
{
lock();
bool h = lock();
// ★ 先做卡死自愈检查:若之前忙状态卡死,自动转 ERROR 继续后续流程
watchdogCheckLocked();
if (h) watchdogCheckLocked();
// 系统忙检查:仅拒绝真正"在传输"的活跃状态
// STORED / COMPLETE 是"待中继/已中继"的非活跃状态 → 允许新存储覆盖
// ★ 修复:之前把所有非 IDLE 都当忙,导致"Flash 缓存里有固件但没在传输"时无法上传新固件
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
if (h && (m_sysState == OTA_SYS_ESP32_RECEIVING ||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
m_sysState == OTA_SYS_ESP32_REBOOTING ||
m_sysState == OTA_SYS_GD32_STORING ||
m_sysState == OTA_SYS_GD32_RELAYING) {
m_sysState == OTA_SYS_GD32_RELAYING)) {
DEBUG_WARN(TAG, "系统忙 (state=%d),拒绝 GD32 存储请求", (int)m_sysState);
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
// 如果之前已有存储但未中继(STORED)或刚中继完(COMPLETE),先清理再开始新存储
if (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE) {
if (h && (m_sysState == OTA_SYS_GD32_STORED || m_sysState == OTA_SYS_GD32_COMPLETE)) {
DEBUG_INFO(TAG, "清理旧 GD32 存储状态,开始新存储 (size=%" PRIu32 ")", total_size);
OtaRelay::metaClear();
}
m_sysState = OTA_SYS_GD32_STORING;
m_gd32StorageDone = false;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
unlock();
return ESP_OK;
if (h) {
m_sysState = OTA_SYS_GD32_STORING;
m_gd32StorageDone = false;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录忙状态起始时刻
DEBUG_INFO(TAG, "GD32 固件存储开始: %" PRIu32 " 字节, 系统状态 → GD32_STORING", total_size);
}
unlock(h);
return h ? ESP_OK : ESP_ERR_INVALID_STATE;
}
void OtaCoordinator::completeGd32Storage()
{
lock();
if (m_sysState == OTA_SYS_GD32_STORING) {
bool h = lock();
if (h && m_sysState == OTA_SYS_GD32_STORING) {
m_sysState = OTA_SYS_GD32_STORED;
m_gd32StorageDone = true;
m_busySinceTick = 0; // ★ STORED 是非忙状态
DEBUG_INFO(TAG, "GD32 固件存储完成, 系统状态 → GD32_STORED");
} else {
} else if (h) {
DEBUG_WARN(TAG, "completeGd32Storage 在非预期状态: %d", (int)m_sysState);
}
unlock();
unlock(h);
}
// ============================================================
@@ -171,59 +169,60 @@ void OtaCoordinator::completeGd32Storage()
// ============================================================
esp_err_t OtaCoordinator::triggerGd32Relay()
{
lock();
bool h = lock();
// ★ 卡死自愈检查
watchdogCheckLocked();
if (h) watchdogCheckLocked();
// 校验:必须处于"已存储"或"已中继完成"状态
// ★ 修复:COMPLETE(旧固件已成功推给 GD32,NVS 元数据已被 doFinishAndVerify 清掉,
// OtaRelay 状态为 DONE)也能触发——但需要先回滚到 STORED 以保证 m_busySinceTick 一致。
// 通常用户应先上传新固件再触发中继,COMPLETE 时元数据已被清,metaLoad 会返回 stored=0
// 并在 188 行被拒绝,自然不会走到这里。
if (m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
if (h && m_sysState != OTA_SYS_GD32_STORED && m_sysState != OTA_SYS_IDLE) {
if (m_sysState == OTA_SYS_GD32_STORING) {
DEBUG_WARN(TAG, "GD32 正在存储中,拒绝触发中继");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
if (m_sysState == OTA_SYS_GD32_RELAYING) {
DEBUG_WARN(TAG, "GD32 中继已在进行中");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
m_sysState == OTA_SYS_ESP32_FINALIZING ||
m_sysState == OTA_SYS_ESP32_REBOOTING) {
DEBUG_WARN(TAG, "ESP32 OTA 进行中,拒绝中继触发");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
}
// 验证固件是否确实已存储(通过 NVS 元数据)
ota_meta_t meta;
if (OtaRelay::metaLoad(&meta) != ESP_OK ||
if (h && (OtaRelay::metaLoad(&meta) != ESP_OK ||
meta.total_size == 0 ||
meta.stored_size < meta.total_size) {
meta.stored_size < meta.total_size)) {
DEBUG_WARN(TAG, "GD32 固件未完整存储,拒绝触发中继");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
// 检查 OtaRelay 是否空闲
if (OtaRelay::getState() != RELAY_IDLE &&
if (h && OtaRelay::getState() != RELAY_IDLE &&
OtaRelay::getState() != RELAY_DONE &&
OtaRelay::getState() != RELAY_ERROR) {
DEBUG_WARN(TAG, "OtaRelay 正忙 (state=%d),拒绝触发", (int)OtaRelay::getState());
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
// 向 OtaRelay task 发送启动消息
if (!OtaRelay::dispatchQueue) {
if (h && !OtaRelay::dispatchQueue) {
DEBUG_ERR(TAG, "OtaRelay dispatchQueue 未就绪");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
if (!h) {
unlock(h);
return ESP_ERR_INVALID_STATE;
}
@@ -237,39 +236,41 @@ esp_err_t OtaCoordinator::triggerGd32Relay()
DEBUG_ERR(TAG, "向 OtaRelay 发送启动消息失败");
m_sysState = OTA_SYS_GD32_STORED;
m_busySinceTick = 0; // 回退到 STORED(非忙)
unlock();
unlock(h);
return ESP_FAIL;
}
unlock();
unlock(h);
return ESP_OK;
}
void OtaCoordinator::notifyRelayStarted()
{
lock();
if (m_sysState != OTA_SYS_GD32_RELAYING) {
bool h = lock();
if (h && m_sysState != OTA_SYS_GD32_RELAYING) {
m_sysState = OTA_SYS_GD32_RELAYING;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
DEBUG_INFO(TAG, "中继已开始, 系统状态 → GD32_RELAYING");
}
unlock();
unlock(h);
}
void OtaCoordinator::notifyRelayComplete(bool success)
{
lock();
if (success) {
m_sysState = OTA_SYS_GD32_COMPLETE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ COMPLETE 非忙
DEBUG_INFO(TAG, "GD32 中继完成, 系统状态 → GD32_COMPLETE");
} else {
m_sysState = OTA_SYS_ERROR;
m_busySinceTick = 0; // ★ ERROR 非忙(可被后续请求恢复)
DEBUG_ERR(TAG, "GD32 中继失败, 系统状态 → ERROR");
bool h = lock();
if (h) {
if (success) {
m_sysState = OTA_SYS_GD32_COMPLETE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ COMPLETE 非忙
DEBUG_INFO(TAG, "GD32 中继完成, 系统状态 → GD32_COMPLETE");
} else {
m_sysState = OTA_SYS_ERROR;
m_busySinceTick = 0; // ★ ERROR 非忙(可被后续请求恢复)
DEBUG_ERR(TAG, "GD32 中继失败, 系统状态 → ERROR");
}
}
unlock();
unlock(h);
}
// ============================================================
@@ -277,60 +278,65 @@ void OtaCoordinator::notifyRelayComplete(bool success)
// ============================================================
esp_err_t OtaCoordinator::beginEsp32Ota(uint32_t total_size)
{
lock();
bool h = lock();
// ★ 卡死自愈检查
watchdogCheckLocked();
if (h) watchdogCheckLocked();
// 系统忙检查
// ★ 修复:STORING/RELAYING 都拒绝(防止中继中改 gd32_ota 分区被 ESP32 OTA 误触发)。
// STORED/COMPLETE 允许——它们是非活跃状态,ESP32 OTA 与 gd32_ota 分区无冲突。
if (m_sysState == OTA_SYS_GD32_STORING ||
m_sysState == OTA_SYS_GD32_RELAYING) {
if (h && (m_sysState == OTA_SYS_GD32_STORING ||
m_sysState == OTA_SYS_GD32_RELAYING)) {
DEBUG_WARN(TAG, "GD32 正在 %s,拒绝 ESP32 OTA",
m_sysState == OTA_SYS_GD32_STORING ? "存储" : "中继");
unlock();
unlock(h);
return ESP_ERR_INVALID_STATE;
}
// 委托 Esp32OtaCore 处理分区/互斥逻辑
esp_err_t err = Esp32OtaCore::instance().begin(total_size);
if (err == ESP_OK) {
m_sysState = OTA_SYS_ESP32_RECEIVING;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
} else {
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
esp_err_t err = ESP_ERR_INVALID_STATE;
if (h) {
// 委托 Esp32OtaCore 处理分区/互斥逻辑
err = Esp32OtaCore::instance().begin(total_size);
if (err == ESP_OK) {
m_sysState = OTA_SYS_ESP32_RECEIVING;
m_busySinceTick = xTaskGetTickCount(); // ★ 记录起始时刻
DEBUG_INFO(TAG, "ESP32 OTA 开始: %" PRIu32 " 字节, 系统状态 → ESP32_RECEIVING", total_size);
} else {
DEBUG_WARN(TAG, "ESP32 OTA begin 失败: %s", esp_err_to_name(err));
}
}
unlock();
unlock(h);
return err;
}
void OtaCoordinator::notifyEsp32ReceiveComplete()
{
lock();
if (m_sysState == OTA_SYS_ESP32_RECEIVING) {
bool h = lock();
if (h && m_sysState == OTA_SYS_ESP32_RECEIVING) {
m_sysState = OTA_SYS_ESP32_FINALIZING;
DEBUG_INFO(TAG, "ESP32 固件接收完毕, 系统状态 → ESP32_FINALIZING");
}
unlock();
unlock(h);
}
void OtaCoordinator::notifyEsp32FinalizeComplete()
{
lock();
m_sysState = OTA_SYS_ESP32_REBOOTING;
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
unlock();
bool h = lock();
if (h) {
m_sysState = OTA_SYS_ESP32_REBOOTING;
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成, 系统状态 → ESP32_REBOOTING");
}
unlock(h);
}
void OtaCoordinator::notifyEsp32Rebooting()
{
lock();
m_sysState = OTA_SYS_ESP32_REBOOTING;
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
unlock();
bool h = lock();
if (h) {
m_sysState = OTA_SYS_ESP32_REBOOTING;
DEBUG_INFO(TAG, "ESP32 即将重启, 系统状态 → ESP32_REBOOTING");
}
unlock(h);
}
// ============================================================
@@ -338,23 +344,25 @@ void OtaCoordinator::notifyEsp32Rebooting()
// ============================================================
void OtaCoordinator::abortCurrent()
{
lock();
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
bool h = lock();
if (h) {
DEBUG_INFO(TAG, "中止当前 OTA 操作 (state=%d)", (int)m_sysState);
if (m_sysState == OTA_SYS_GD32_STORING) {
OtaRelay::metaClear();
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
OtaRelay::instance().reset();
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
Esp32OtaCore::instance().abort();
if (m_sysState == OTA_SYS_GD32_STORING) {
OtaRelay::metaClear();
} else if (m_sysState == OTA_SYS_GD32_RELAYING) {
OtaRelay::instance().reset();
} else if (m_sysState == OTA_SYS_ESP32_RECEIVING ||
m_sysState == OTA_SYS_ESP32_FINALIZING) {
Esp32OtaCore::instance().abort();
}
m_sysState = OTA_SYS_IDLE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ 离开忙状态
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
}
m_sysState = OTA_SYS_IDLE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ 离开忙状态
DEBUG_INFO(TAG, "已中止, 系统状态 → IDLE");
unlock();
unlock(h);
}
// ============================================================
@@ -364,9 +372,9 @@ ota_system_state_t OtaCoordinator::getSystemState() const
{
// 对于纯读取,短暂加锁保证一致性
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
self->lock();
bool h = self->lock();
ota_system_state_t s = m_sysState;
self->unlock();
self->unlock(h);
return s;
}
@@ -416,9 +424,9 @@ bool OtaCoordinator::getGd32AppAlive() const
bool OtaCoordinator::isGd32StorageDone() const
{
OtaCoordinator* self = const_cast<OtaCoordinator*>(this);
self->lock();
bool h = self->lock();
bool done = m_gd32StorageDone;
self->unlock();
self->unlock(h);
return done;
}
@@ -427,10 +435,12 @@ bool OtaCoordinator::isGd32StorageDone() const
// ============================================================
void OtaCoordinator::reset()
{
lock();
m_sysState = OTA_SYS_IDLE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ 复位
DEBUG_INFO(TAG, "协调器已完全复位");
unlock();
bool h = lock();
if (h) {
m_sysState = OTA_SYS_IDLE;
m_gd32StorageDone = false;
m_busySinceTick = 0; // ★ 复位
DEBUG_INFO(TAG, "协调器已完全复位");
}
unlock(h);
}
+6 -3
View File
@@ -174,8 +174,12 @@ private:
OtaCoordinator& operator=(const OtaCoordinator&) = delete;
// 线程安全辅助
void lock();
void unlock();
// ★ lock() 返回是否成功 take;unlock(handle) 据此决定是否 give。
// 旧实现用共享 bool m_locked 记录 take 结果,多任务并发 lock() 时
// 超时方会把 m_locked 置 false,覆盖持锁方的 true,导致 unlock() 跳过 give,
// mutex 永久死锁 → 后续 take 超时触发 vTaskPriorityDisinherit assert → PANIC。
bool lock();
void unlock(bool handle);
// ★ 卡死自愈检查:若忙状态停留超过 watchdog 超时,强制转为 ERROR。
// 用于在中继 task 因任何原因(崩溃/SPI 死锁/看门狗复位残留)未能回调
@@ -194,7 +198,6 @@ private:
// 互斥锁
SemaphoreHandle_t m_mutex = nullptr;
bool m_locked = false; // ★ 记录最近一次 lock() 是否成功 take;unlock() 据此跳过错误 give
static constexpr TickType_t kLockTimeout = pdMS_TO_TICKS(100);
// ★ 忙状态 watchdog 超时。GD32 中继理论最大耗时 ≈ 60s(全局超时) + 15s(APP存活轮询) + 1s(bootloader) ≈ 76s,
-6
View File
@@ -420,8 +420,6 @@ ota_result_t OtaRelay::forwardFirmware()
}
// 全局超时计算
// ★ 收敛到 1 min:30.8 KB / 30 KB·s ≈ 1 s 数据 + 握手/认证/等待 ≈ 2~3 s,
// 1 min 已是数十倍安全余量。旧 10 min 会在中继异常时把协调器锁住极久。
constexpr uint32_t kMinGlobalTimeoutMs = 20U * 1000U; // 20 s 最小超时
constexpr uint32_t kMaxGlobalTimeoutMs = 60U * 1000U; // 60 s 硬上限
uint32_t dynamic_ms = kMinGlobalTimeoutMs + (meta.total_size / 1024U) * 50U; // 50 ms / KB
@@ -444,10 +442,6 @@ ota_result_t OtaRelay::forwardFirmware()
if (res != OTA_RESULT_OK) goto cleanup;
// 步骤2: SIZE_NEG(内部完成大小协商 → TRANSFERRING)
// ★ 此处检查在 SIZE_NEG handler 执行之前。从日志看握手刚完成(~0.6 s)就触发超限,
// 真实根因:handleSizeNeg 是同步阻塞的——它本身就会跑完整个 doSendFirmwareSize,
// 真正的"步骤2 超限"应在 handler 之后检查。修复:把"前已超限"检查全部删除,
// 改为在 processEvent 之后用 deadline 校验返回值。handler 内部已经有自己的超时。
res = processEvent(RELAY_EVT_START, nullptr, 0);
if (res != OTA_RESULT_OK) goto cleanup;
+32 -9
View File
@@ -7,6 +7,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_http_server.h"
@@ -1164,21 +1165,37 @@ static esp_err_t system_status_handler(httpd_req_t *req)
gd32_version_t pingVer = {};
OtaRelay::getGd32Version(&pingVer);
/* 若尚无 GD32 版本信息且 OTA 中继空闲,尝试一次 SPI PING 获取 */
if (pingVer.app_major == 0 &&
/* 若尚无 GD32 版本信息且 OTA 中继空闲,尝试 SPI PING 获取(最多 5 次) */
// ★ 修复:OTA 活跃期间(存储/中继/ESP32 OTA)跳过 appPing(),
// 避免 HTTP 任务长期持有 SPI mutex 与 relay 任务争用,
// 导致优先级继承异常 → vTaskPriorityDisinheritAfterTimeout assert。
ota_system_state_t curSysState = OtaCoordinator::instance().getSystemState();
bool ota_busy = (curSysState == OTA_SYS_GD32_STORING ||
curSysState == OTA_SYS_GD32_RELAYING ||
curSysState == OTA_SYS_ESP32_RECEIVING ||
curSysState == OTA_SYS_ESP32_FINALIZING ||
curSysState == OTA_SYS_ESP32_REBOOTING);
if (!ota_busy &&
pingVer.app_major == 0 &&
pingVer.boot_major == 0 &&
OtaRelay::getState() == RELAY_IDLE) {
static uint32_t last_ping_ms = 0;
static uint8_t ping_attempts = 0;
uint32_t now_ms = (uint32_t)(esp_timer_get_time() / 1000);
if (now_ms - last_ping_ms > 5000) { /* 5 秒节流 */
if (ping_attempts < 5 && now_ms - last_ping_ms > 5000) { /* 5 秒节流 */
last_ping_ms = now_ms;
++ping_attempts;
uint32_t uptime = 0;
gd32_version_t ver = {};
if (SpiMaster::instance().appPing(500, &uptime, &ver) == ESP_OK) {
OtaRelay::setGd32AppAlive(true);
OtaRelay::setGd32Version(ver);
ping_attempts = 0; /* 成功后重置,允许后续更新 */
} else {
OtaRelay::setGd32AppAlive(false);
if (ping_attempts >= 5) {
DEBUG_WARN("STATUS", "GD32 版本号读取失败,已达最大重试次数 (5),停止尝试");
}
}
}
}
@@ -1502,7 +1519,7 @@ bool WifiOta::earlyInit()
nvs_erase_all(nvs_handle);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
DEBUG_INFO("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
DEBUG_DBG("WIFI", "冷启动: 已清除 WiFi NVS 缓存 (nvs.net80211)");
} else {
DEBUG_INFO("WIFI", "冷启动: 无 WiFi NVS 缓存分区,跳过清除");
}
@@ -1512,7 +1529,7 @@ bool WifiOta::earlyInit()
nvs_erase_all(nvs_handle);
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
DEBUG_INFO("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
DEBUG_DBG("WIFI", "冷启动: 已清除 PHY 校准缓存,将重新校准");
}
}
@@ -1522,7 +1539,7 @@ bool WifiOta::earlyInit()
if (mtx) {
// 通过临时获取再赋值(私有成员在同类内可访问)
WifiOta::instance().m_stateMutex = mtx;
DEBUG_INFO("WIFI", "状态互斥锁已创建");
DEBUG_DBG("WIFI", "状态互斥锁已创建");
} else {
DEBUG_ERR("WIFI", "创建状态互斥锁失败");
}
@@ -1542,14 +1559,14 @@ bool WifiOta::earlyInit()
if (ret != pdPASS) {
DEBUG_ERR("WIFI", "创建重连任务失败");
} else {
DEBUG_INFO("WIFI", "WiFi 重连任务已创建");
DEBUG_DBG("WIFI", "WiFi 重连任务已创建");
}
}
// 注册 WiFi/IP 事件回调(必须在 esp_wifi_init 之前)
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &WifiOta::wifiEventHandler, NULL));
DEBUG_INFO("WIFI", "WiFi/IP 事件回调已注册");
DEBUG_DBG("WIFI", "WiFi/IP 事件回调已注册");
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif == NULL) {
@@ -1557,12 +1574,18 @@ bool WifiOta::earlyInit()
if (sta_netif == NULL) {
DEBUG_ERR("WIFI", "创建 STA netif 失败");
} else {
DEBUG_INFO("WIFI", "STA netif 已创建");
DEBUG_DBG("WIFI", "STA netif 已创建");
}
} else {
DEBUG_INFO("WIFI", "STA netif 已存在,跳过创建");
}
// ★ 压制 WiFi 框架内部 INFO 日志(pp/net80211/wifi/wifi_init 等 tag 大量启动打印)
esp_log_level_set("wifi", ESP_LOG_WARN);
esp_log_level_set("wifi_init", ESP_LOG_WARN);
esp_log_level_set("pp", ESP_LOG_WARN);
esp_log_level_set("net80211", ESP_LOG_WARN);
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
DEBUG_INFO("WIFI", "WiFi driver 初始化成功");
+22 -11
View File
@@ -285,13 +285,18 @@ bool SpiMaster::otaFindFrame(const uint8_t *buf, size_t buf_len,
*/
esp_err_t SpiMaster::init()
{
LockGuard lock(m_mutex);
// 创建 SPI 总线互斥锁(必须在 SPI 设备初始化之前)
m_mutex = xSemaphoreCreateMutex();
if (m_mutex == NULL) {
DEBUG_ERR("SPI", "SPI 互斥锁创建失败");
return ESP_ERR_NO_MEM;
// ★ 修复:先创建 mutex,再使用 LockGuard。
// 旧代码在 LockGuard 构造时 m_mutex 仍为 NULL,
// 虽然 LockGuard 会跳过 NULL take/give,但在极端情况下
// 其他任务可能在 mutex 创建后立即尝试 take,产生竞态。
if (!m_mutex) {
m_mutex = xSemaphoreCreateMutex();
if (m_mutex == NULL) {
DEBUG_ERR("SPI", "SPI 互斥锁创建失败");
return ESP_ERR_NO_MEM;
}
}
LockGuard lock(m_mutex);
DEBUG_INFO("SPI", "=== 初始化 SPI2 总线 ===");
@@ -560,7 +565,6 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
{
LockGuard lock(m_mutex);
constexpr uint8_t kAppPingType = 0xA1U;
constexpr size_t kAppPingPollBytes = 32U;
uint8_t tx_frame[8] = {0};
size_t tx_len = 0U;
@@ -574,7 +578,14 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
while (xTaskGetTickCount() <= deadline) {
++attempt;
esp_err_t err = transferClocked(tx_frame, tx_len, NULL, 0U);
/* 发送 PING 帧,填充至 16 字节:
* GD32 SPI 从机可能在 CS 断言时丢失首字节(EXTI/SPI 中断竞态),
* 填充确保即使首字节丢失,后续仍有完整 0xA5 帧头可供扫描定位。 */
uint8_t tx_padded[16];
memset(tx_padded, 0xFF, sizeof(tx_padded));
memcpy(tx_padded, tx_frame, tx_len); /* [0..4]=PING帧, [5..15]=0xFF */
esp_err_t err = transferClocked(tx_padded, sizeof(tx_padded), NULL, 0U);
if (err != ESP_OK) {
DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s",
(unsigned long)attempt, esp_err_to_name(err));
@@ -583,10 +594,10 @@ esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec, gd32_ver
}
/* 给 GD32 充足时间处理 CS 释放中断并准备响应帧 */
vTaskDelay(pdMS_TO_TICKS(3));
vTaskDelay(pdMS_TO_TICKS(10));
uint8_t rx_raw[kAppPingPollBytes];
uint8_t dummy[kAppPingPollBytes];
uint8_t rx_raw[48]; /* 增大缓冲区,吸收 SPI 字节偏移 */
uint8_t dummy[48];
memset(dummy, 0xFF, sizeof(dummy));
memset(rx_raw, 0xFF, sizeof(rx_raw));