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:
@@ -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"
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 初始化成功");
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user