feat(main): 将全局函数重构为单例类模式

- 将 debug_log 模块重构为 DebugLog 单例类,封装 UART、UDP 和环形缓冲区日志输出
- 将 health_monitor 模块重构为 HealthMonitor 单例类,统一健康状态管理和监控逻辑
- 更新 main.cpp 中的初始化调用,使用单例实例替代原有全局函数
- 添加健康监控任务作为第四个 FreeRTOS 任务,增强系统监控能力
- 更新固件版本从 0.2.11 到 0.3.0,反映架构重大变更
This commit is contained in:
小龙虾
2026-05-31 03:20:26 +08:00
parent 56f26fa0d1
commit 1586a81b97
16 changed files with 1501 additions and 2245 deletions
+14 -14
View File
@@ -1,6 +1,8 @@
/* /*
* ESP32-C3FH4 OTA 中继 - 程序入口 * ESP32-C3FH4 OTA 中继 - 程序入口
* 三个 FreeRTOS 任务:OTA Relay / WiFi Server / BLE GATT * 四个 FreeRTOS 任务:健康监控 / OTA Relay / WiFi Server / BLE GATT
*
* 使用各模块的单例类进行初始化和任务创建。
*/ */
#include <stdio.h> #include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
@@ -19,7 +21,7 @@
#include "monitor/debug_log.h" #include "monitor/debug_log.h"
/** /**
* @brief 程序主入口,初始化系统组件并创建各 FreeRTOS 任务。 * @brief 程序主入口,初始化系统组件并创建各 FreeRTOS 任务。
*/ */
extern "C" void app_main(void) extern "C" void app_main(void)
{ {
@@ -31,13 +33,12 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
// 初始化调试日志(UART0 先就绪,UDP socket 延迟到首次发送时创建) // 初始化调试日志(UART0 先就绪,UDP socket 延迟到首次发送时创建)
debug_log_init(); DebugLog::instance().init();
// ★ WiFi 提前初始化(调度器启动前) // ★ WiFi 提前初始化(调度器启动前)
// 包括:NVS → netif 创建 → event handler 注册 → WiFi driver init → 配置 → connect bool wifi_sta_ok = WifiOta::instance().earlyInit();
bool wifi_sta_ok = wifi_early_init();
health_monitor_init(); HealthMonitor::instance().init();
DEBUG_INFO("MAIN", "健康监控状态已初始化"); DEBUG_INFO("MAIN", "健康监控状态已初始化");
DEBUG_INFO("MAIN", "========================================"); DEBUG_INFO("MAIN", "========================================");
DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay v1.0 启动"); DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay v1.0 启动");
@@ -50,7 +51,7 @@ extern "C" void app_main(void)
// 健康监控任务(温度 + WiFi RSSI) // 健康监控任务(温度 + WiFi RSSI)
BaseType_t xRet; BaseType_t xRet;
xRet = xTaskCreatePinnedToCore( xRet = xTaskCreatePinnedToCore(
health_monitor_task, HealthMonitor::taskEntry,
"health_mon", "health_mon",
4096, 4096,
NULL, NULL,
@@ -64,10 +65,10 @@ extern "C" void app_main(void)
DEBUG_INFO("MAIN", "健康监控任务已创建"); DEBUG_INFO("MAIN", "健康监控任务已创建");
} }
protocol_init(); Protocol::instance().init();
DEBUG_INFO("MAIN", "协议层初始化完成"); DEBUG_INFO("MAIN", "协议层初始化完成");
esp_err_t ret = spi_master_init(); esp_err_t ret = SpiMaster::instance().init();
if (ret != ESP_OK) { if (ret != ESP_OK) {
DEBUG_ERR("MAIN", "SPI 总线初始化失败: %d", ret); DEBUG_ERR("MAIN", "SPI 总线初始化失败: %d", ret);
} else { } else {
@@ -76,7 +77,7 @@ extern "C" void app_main(void)
// OTA 中继任务(中高优先级) // OTA 中继任务(中高优先级)
xRet = xTaskCreatePinnedToCore( xRet = xTaskCreatePinnedToCore(
ota_relay_task, OtaRelay::taskEntry,
"ota_relay", "ota_relay",
8192, 8192,
NULL, NULL,
@@ -92,7 +93,7 @@ extern "C" void app_main(void)
// WiFi OTA 服务器任务 // WiFi OTA 服务器任务
xRet = xTaskCreatePinnedToCore( xRet = xTaskCreatePinnedToCore(
wifi_ota_task, WifiOta::taskEntry,
"wifi_ota", "wifi_ota",
8192, 8192,
NULL, NULL,
@@ -108,7 +109,7 @@ extern "C" void app_main(void)
// BLE OTA 任务 // BLE OTA 任务
xRet = xTaskCreatePinnedToCore( xRet = xTaskCreatePinnedToCore(
ble_ota_task, BleOta::taskEntry,
"ble_ota", "ble_ota",
8192, 8192,
NULL, NULL,
@@ -128,4 +129,3 @@ extern "C" void app_main(void)
DEBUG_LOG_IP, DEBUG_LOG_PORT); DEBUG_LOG_IP, DEBUG_LOG_PORT);
DEBUG_INFO("MAIN", "========================================"); DEBUG_INFO("MAIN", "========================================");
} }
+1 -1
View File
@@ -10,7 +10,7 @@
// ============================================================ // ============================================================
// 固件版本 // 固件版本
// ============================================================ // ============================================================
inline constexpr const char *FW_VERSION = "0.2.11"; inline constexpr const char *FW_VERSION = "0.3.0";
// ============================================================ // ============================================================
// WiFi 工作模式选择 // WiFi 工作模式选择
+79 -148
View File
@@ -1,5 +1,5 @@
/* /*
* debug_log.c - 调试日志输出实现 * debug_log.cpp - 调试日志输出实现
* *
* 日志会同时写入三处: * 日志会同时写入三处:
* 1. UART0 串口(本地串口调试) * 1. UART0 串口(本地串口调试)
@@ -11,7 +11,7 @@
* *
* 时序说明: * 时序说明:
* esp_netif_init() 创建 tcpip 线程后才可安全使用 socket * esp_netif_init() 创建 tcpip 线程后才可安全使用 socket
* 本模块在首次 debug_log_send() 时才创建 socket(延迟创建) * 本模块在首次 send() 时才创建 socket(延迟创建)
* WiFi 连接前 sendto() 会失败(无路由),日志仍可写入 UART 与 HTTP 日志缓冲 * WiFi 连接前 sendto() 会失败(无路由),日志仍可写入 UART 与 HTTP 日志缓冲
* WiFi 连接后 UDP 立即生效,无需额外操作 * WiFi 连接后 UDP 立即生效,无需额外操作
*/ */
@@ -30,60 +30,39 @@
static const char *TAG = "DEBUG_LOG"; static const char *TAG = "DEBUG_LOG";
/** @brief 当前调试日志过滤级别,运行时可动态调整。 */ // ============================================================
debug_log_level_t g_debug_log_level = DEBUG_LOG_INFO; // 单例
// ============================================================
/** @brief UDP 调试日志 socket 句柄,首次发送时延迟创建。 */ DebugLog& DebugLog::instance()
static int s_udp_sock = -1; {
/** @brief UDP 调试日志目标地址。 */ static DebugLog inst;
static struct sockaddr_in s_debug_addr; return inst;
/** @brief 日志模块初始化标志。 */ }
static bool s_initialized = false;
// ============================================================ // ============================================================
// 日志环形缓冲区(供 HTTP /logs 拉取) // 日志环形缓冲区(供 HTTP /logs 拉取)
// ============================================================ // ============================================================
#define LOG_RING_SLOTS 64 int DebugLog::ringCount()
#define LOG_RING_SIZE 256
static char s_log_ring[LOG_RING_SLOTS][LOG_RING_SIZE];
static int s_log_ring_lens[LOG_RING_SLOTS];
static int s_log_ring_head = 0; // 下次写入位置
static int s_log_ring_count = 0; // 已存条数(超过 LOG_RING_SLOTS 后不再增长,覆盖旧数据)
/** @brief 环形缓冲区互斥锁,保护多线程并发读写。 */
static SemaphoreHandle_t s_log_ring_mutex = NULL;
/**
* @brief 获取日志缓冲区中的条数
* @return 当前日志条数。
*/
int debug_log_ring_count(void)
{ {
if (!s_log_ring_mutex) return 0; if (!m_logRingMutex) return 0;
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY); xSemaphoreTake(m_logRingMutex, portMAX_DELAY);
int count = s_log_ring_count; int count = m_logRingCount;
xSemaphoreGive(s_log_ring_mutex); xSemaphoreGive(m_logRingMutex);
return count; return count;
} }
// 读取并清空日志缓冲区,返回 JSON 数组字符串 char *DebugLog::ringDrainJson(int *out_len)
// 调用者负责 free 返回值
/**
* @brief 读取并清空环形日志缓冲区,返回 JSON 数组字符串。
* @param out_len 输出 JSON 字符串长度(不含末尾 '\0')。
* @return 成功返回需由调用者 free 的字符串;无日志或失败返回 NULL。
*/
char *debug_log_ring_drain_json(int *out_len)
{ {
if (!s_log_ring_mutex) { if (!m_logRingMutex) {
*out_len = 0; *out_len = 0;
return NULL; return NULL;
} }
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY); xSemaphoreTake(m_logRingMutex, portMAX_DELAY);
int count = s_log_ring_count; int count = m_logRingCount;
if (count == 0) { if (count == 0) {
xSemaphoreGive(s_log_ring_mutex); xSemaphoreGive(m_logRingMutex);
*out_len = 0; *out_len = 0;
return NULL; return NULL;
} }
@@ -92,7 +71,7 @@ char *debug_log_ring_drain_json(int *out_len)
size_t buf_size = (size_t)count * 280 + 16; size_t buf_size = (size_t)count * 280 + 16;
char *json = (char *)malloc(buf_size); char *json = (char *)malloc(buf_size);
if (!json) { if (!json) {
xSemaphoreGive(s_log_ring_mutex); xSemaphoreGive(m_logRingMutex);
*out_len = 0; *out_len = 0;
return NULL; return NULL;
} }
@@ -101,17 +80,17 @@ char *debug_log_ring_drain_json(int *out_len)
json[pos++] = '['; json[pos++] = '[';
// 从最旧的开始读 // 从最旧的开始读
int start = (count < LOG_RING_SLOTS) ? 0 : s_log_ring_head; int start = (count < LOG_RING_SLOTS) ? 0 : m_logRingHead;
for (int i = 0; i < count && pos < (int)buf_size - 200; i++) { for (int i = 0; i < count && pos < (int)buf_size - 200; i++) {
int idx = (start + i) % LOG_RING_SLOTS; int idx = (start + i) % LOG_RING_SLOTS;
int len = s_log_ring_lens[idx]; int len = m_logRingLens[idx];
if (len <= 0) continue; if (len <= 0) continue;
if (i > 0) json[pos++] = ','; if (i > 0) json[pos++] = ',';
pos += snprintf(json + pos, buf_size - pos, "\""); pos += snprintf(json + pos, buf_size - pos, "\"");
for (int j = 0; j < len && pos < (int)buf_size - 10; j++) { for (int j = 0; j < len && pos < (int)buf_size - 10; j++) {
char c = s_log_ring[idx][j]; char c = m_logRing[idx][j];
if (c == '"' || c == '\\') { if (c == '"' || c == '\\') {
json[pos++] = '\\'; json[pos++] = '\\';
json[pos++] = c; json[pos++] = c;
@@ -129,109 +108,85 @@ char *debug_log_ring_drain_json(int *out_len)
json[pos] = '\0'; json[pos] = '\0';
// 清空缓冲区 // 清空缓冲区
s_log_ring_head = 0; m_logRingHead = 0;
/** @brief 环形缓冲区中已存储的日志条数。 */ m_logRingCount = 0;
s_log_ring_count = 0;
xSemaphoreGive(s_log_ring_mutex); xSemaphoreGive(m_logRingMutex);
*out_len = pos; *out_len = pos;
return json; return json;
} }
/** void DebugLog::ringPush(const char *data, int len)
* @brief 写入一条日志到环形缓冲区
* @param data
* @param len
*/
static void log_ring_push(const char *data, int len)
{ {
if (!s_log_ring_mutex || len <= 0) return; if (!m_logRingMutex || len <= 0) return;
xSemaphoreTake(s_log_ring_mutex, portMAX_DELAY); xSemaphoreTake(m_logRingMutex, portMAX_DELAY);
int copy_len = len; int copy_len = len;
if (copy_len >= LOG_RING_SIZE) copy_len = LOG_RING_SIZE - 1; if (copy_len >= LOG_RING_SIZE) copy_len = LOG_RING_SIZE - 1;
memcpy(s_log_ring[s_log_ring_head], data, copy_len); memcpy(m_logRing[m_logRingHead], data, copy_len);
s_log_ring[s_log_ring_head][copy_len] = '\0'; m_logRing[m_logRingHead][copy_len] = '\0';
s_log_ring_lens[s_log_ring_head] = copy_len; m_logRingLens[m_logRingHead] = copy_len;
/** @brief 环形缓冲区下一个写入位置。 */ m_logRingHead = (m_logRingHead + 1) % LOG_RING_SLOTS;
s_log_ring_head = (s_log_ring_head + 1) % LOG_RING_SLOTS; if (m_logRingCount < LOG_RING_SLOTS) m_logRingCount++;
if (s_log_ring_count < LOG_RING_SLOTS) s_log_ring_count++;
xSemaphoreGive(s_log_ring_mutex); xSemaphoreGive(m_logRingMutex);
} }
// UART0 状态
static bool s_uart_initialized = false;
/** @brief 调试串口使用的 UART 端口号。 */
static uart_port_t s_uart_port = UART_NUM_0;
// ============================================================ // ============================================================
// UART0 串口调试 // UART 调试日志
// ============================================================ // ============================================================
/** esp_err_t DebugLog::uartInit()
* @brief 初始化 UART0 调试串口,配置波特率和引脚映射。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t uart_debug_init(void)
{ {
if (s_uart_initialized) { if (m_uartInitialized) {
return ESP_OK; return ESP_OK;
} }
uart_config_t uart_config; const uart_port_t uart_port = UART_NUM_0;
memset(&uart_config, 0, sizeof(uart_config)); uart_config_t uart_config = {};
uart_config.baud_rate = UART_DEBUG_BAUD_RATE; uart_config.baud_rate = UART_DEBUG_BAUD_RATE;
uart_config.data_bits = UART_DATA_8_BITS; uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE; uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1; uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.source_clk = UART_SCLK_DEFAULT; uart_config.source_clk = UART_SCLK_DEFAULT;
uart_config.rx_flow_ctrl_thresh = 0;
esp_err_t ret = uart_driver_install(s_uart_port, 1024, 0, 0, NULL, 0); esp_err_t ret = uart_driver_install(uart_port, 1024, 0, 0, NULL, 0);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "UART0 驱动安装失败: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "UART0 驱动安装失败: %s", esp_err_to_name(ret));
return ret; return ret;
} }
ret = uart_param_config(s_uart_port, &uart_config); ret = uart_param_config(uart_port, &uart_config);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "UART0 参数配置失败: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "UART0 参数配置失败: %s", esp_err_to_name(ret));
return ret; return ret;
} }
ret = uart_set_pin(s_uart_port, UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, ret = uart_set_pin(uart_port, UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (ret != ESP_OK) { if (ret != ESP_OK) {
ESP_LOGE(TAG, "UART0 引脚配置失败: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "UART0 引脚配置失败: %s", esp_err_to_name(ret));
return ret; return ret;
} }
/** @brief UART0 初始化标志。 */ m_uartInitialized = true;
s_uart_initialized = true;
ESP_LOGI(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)", ESP_LOGI(TAG, "UART0 调试输出已初始化 (TX=GPIO%d, RX=GPIO%d, %d baud)",
UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE); UART_DEBUG_TX_GPIO, UART_DEBUG_RX_GPIO, UART_DEBUG_BAUD_RATE);
return ESP_OK; return ESP_OK;
} }
/** esp_err_t DebugLog::uartSend(const uint8_t *data, size_t len)
* @brief 通过 UART0 发送一段原始字节数据。
* @param data 待发送的数据指针。
* @param len 数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t uart_debug_send(const uint8_t *data, size_t len)
{ {
if (!s_uart_initialized) { if (!m_uartInitialized) {
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
int ret = uart_write_bytes(s_uart_port, data, len); int ret = uart_write_bytes(UART_NUM_0, data, len);
if (ret < 0) { if (ret < 0) {
return ESP_FAIL; return ESP_FAIL;
} }
@@ -240,50 +195,38 @@ esp_err_t uart_debug_send(const uint8_t *data, size_t len)
} }
// ============================================================ // ============================================================
// UDP 调试日志 // 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建)
// ============================================================ // ============================================================
esp_err_t DebugLog::init()
/**
* @brief 初始化(UART 立即可用,UDP socket 延迟到首次发送时创建)
* @return ESP_OK 成功,其他失败。
*/
esp_err_t debug_log_init(void)
{ {
if (s_initialized) { if (m_initialized) {
return ESP_OK; return ESP_OK;
} }
uart_debug_init(); uartInit();
/** @brief 环形缓冲区互斥锁,保护多线程并发读写。 */ m_logRingMutex = xSemaphoreCreateMutex();
s_log_ring_mutex = xSemaphoreCreateMutex();
// 目标地址:单播到上位机 // 目标地址:单播到上位机
memset(&s_debug_addr, 0, sizeof(s_debug_addr)); memset(&m_debugAddr, 0, sizeof(m_debugAddr));
s_debug_addr.sin_family = AF_INET; m_debugAddr.sin_family = AF_INET;
s_debug_addr.sin_port = htons(DEBUG_LOG_PORT); m_debugAddr.sin_port = htons(DEBUG_LOG_PORT);
s_debug_addr.sin_addr.s_addr = inet_addr(DEBUG_LOG_IP); m_debugAddr.sin_addr.s_addr = inet_addr(DEBUG_LOG_IP);
/** @brief 日志模块初始化标志。 */ m_initialized = true;
s_initialized = true;
ESP_LOGI(TAG, "调试日志已初始化,上位机 %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT); ESP_LOGI(TAG, "调试日志已初始化,上位机 %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
return ESP_OK; return ESP_OK;
} }
/** esp_err_t DebugLog::ensureUdpSocket()
* @brief 延迟创建 UDP socket(首次发送时调用)
* @return
*/
static esp_err_t ensure_udp_socket(void)
{ {
if (s_udp_sock >= 0) { if (m_udpSock >= 0) {
return ESP_OK; return ESP_OK;
} }
/** @brief UDP 调试日志 socket 句柄,首次发送时延迟创建。 */ m_udpSock = socket(AF_INET, SOCK_DGRAM, 0);
s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0); if (m_udpSock < 0) {
if (s_udp_sock < 0) {
ESP_LOGE(TAG, "创建 UDP socket 失败"); ESP_LOGE(TAG, "创建 UDP socket 失败");
return ESP_FAIL; return ESP_FAIL;
} }
@@ -292,32 +235,26 @@ static esp_err_t ensure_udp_socket(void)
return ESP_OK; return ESP_OK;
} }
/** esp_err_t DebugLog::send(const uint8_t *data, size_t len)
* @brief 发送原始数据(UDP 单播 + UART 双通道)
* @param data 日志数据指针。
* @param len 数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t debug_log_send(const uint8_t *data, size_t len)
{ {
esp_err_t udp_ret = ESP_FAIL; esp_err_t udp_ret = ESP_FAIL;
esp_err_t uart_ret = ESP_FAIL; esp_err_t uart_ret = ESP_FAIL;
// UART 通道 // UART 通道
if (s_uart_initialized) { if (m_uartInitialized) {
uart_ret = uart_debug_send(data, len); uart_ret = uartSend(data, len);
} }
// UDP 通道(WiFi 未连接时 sendto 失败,静默跳过) // UDP 通道(WiFi 未连接时 sendto 失败,静默跳过)
if (s_initialized) { if (m_initialized) {
if (s_udp_sock < 0) { if (m_udpSock < 0) {
ensure_udp_socket(); ensureUdpSocket();
} }
if (s_udp_sock >= 0) { if (m_udpSock >= 0) {
size_t send_len = len > 1024 ? 1024 : len; size_t send_len = len > 1024 ? 1024 : len;
int ret = sendto(s_udp_sock, data, send_len, 0, int ret = ::sendto(m_udpSock, data, send_len, 0,
(struct sockaddr *)&s_debug_addr, sizeof(s_debug_addr)); (struct sockaddr *)&m_debugAddr, sizeof(m_debugAddr));
if (ret >= 0) { if (ret >= 0) {
udp_ret = ESP_OK; udp_ret = ESP_OK;
} }
@@ -325,7 +262,7 @@ esp_err_t debug_log_send(const uint8_t *data, size_t len)
} }
// 同时写入环形缓冲区(供 HTTP /logs 拉取) // 同时写入环形缓冲区(供 HTTP /logs 拉取)
log_ring_push((const char *)data, (int)len); ringPush((const char *)data, (int)len);
if (udp_ret == ESP_OK || uart_ret == ESP_OK) { if (udp_ret == ESP_OK || uart_ret == ESP_OK) {
return ESP_OK; return ESP_OK;
@@ -333,19 +270,13 @@ esp_err_t debug_log_send(const uint8_t *data, size_t len)
return ESP_FAIL; return ESP_FAIL;
} }
/** void DebugLog::log(debug_log_level_t level, const char *tag, const char *fmt, ...)
* @brief 格式化日志输出
* @param level 日志级别。
* @param tag 日志标签。
* @param fmt 格式化字符串。
*/
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
{ {
if (!s_initialized) { if (!m_initialized) {
debug_log_init(); init();
} }
if (level > g_debug_log_level) { if (level > logLevel) {
return; return;
} }
@@ -369,5 +300,5 @@ void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
char log_buf[600]; char log_buf[600];
int total = snprintf(log_buf, sizeof(log_buf), "%s %s\n", prefix, msg); int total = snprintf(log_buf, sizeof(log_buf), "%s %s\n", prefix, msg);
debug_log_send((const uint8_t *)log_buf, total); send((const uint8_t *)log_buf, total);
} }
+58 -44
View File
@@ -1,4 +1,4 @@
/* /*
* debug_log.h - 调试日志输出 * debug_log.h - 调试日志输出
* *
* 当前日志会并行写入: * 当前日志会并行写入:
@@ -17,7 +17,11 @@
#include <stdint.h> #include <stdint.h>
#include <stdarg.h> #include <stdarg.h>
#include "esp_err.h" #include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "app_config.h" #include "app_config.h"
#include <sys/socket.h>
#include <netinet/in.h>
// 调试日志配置(DEBUG_LOG_IP/PORT/ENABLED 已在 app_config.h 中定义) // 调试日志配置(DEBUG_LOG_IP/PORT/ENABLED 已在 app_config.h 中定义)
@@ -36,57 +40,67 @@ typedef enum {
DEBUG_LOG_ALL = 5 // 全部 DEBUG_LOG_ALL = 5 // 全部
} debug_log_level_t; } debug_log_level_t;
// 当前日志级别(运行时可改)
extern debug_log_level_t g_debug_log_level;
/** /**
* @brief 初始化日志系统,包括 UART 输出、UDP 目标和内部缓冲区。 * @brief 调试日志类(单例)
* @return ESP_OK 成功,其他失败。 *
* 封装 UART0、UDP 和环形缓冲区三路日志输出通道,
* 所有内部状态(socket、缓冲区、互斥锁等)均为私有成员。
*/ */
esp_err_t debug_log_init(void); class DebugLog {
public:
static DebugLog& instance();
/** /** @brief 初始化日志系统,包括 UART 输出、UDP 目标和内部缓冲区。 */
* @brief 初始化 UART0 调试串口。 esp_err_t init();
* @return ESP_OK 成功,其他失败。
*/
esp_err_t uart_debug_init(void);
/** /** @brief 初始化 UART0 调试串口。 */
* @brief 通过 UART0 直接发送一段原始字节数据。 esp_err_t uartInit();
* @param data 待发送的数据指针。
* @param len 数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t uart_debug_send(const uint8_t *data, size_t len);
/** /** @brief 通过 UART0 直接发送一段原始字节数据。 */
* @brief 发送原始日志数据到底层输出通道。 esp_err_t uartSend(const uint8_t *data, size_t len);
* @param data 日志数据指针。
* @param len 数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t debug_log_send(const uint8_t *data, size_t len);
/** /** @brief 发送原始日志数据到底层输出通道。 */
* @brief 按日志级别与标签格式化输出一条日志。 esp_err_t send(const uint8_t *data, size_t len);
* @param level 日志级别。
* @param tag 日志标签。
* @param fmt 格式化字符串。
*/
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...);
/** /** @brief 按日志级别与标签格式化输出一条日志。 */
* @brief 获取当前环形日志缓冲区中的日志条数。 void log(debug_log_level_t level, const char *tag, const char *fmt, ...);
* @return 当前日志条数。
*/
int debug_log_ring_count(void);
// 读取并清空环形日志缓冲区,返回 JSON 数组字符串,需由调用者 free。 /** @brief 获取当前环形日志缓冲区中的日志条数。 */
char *debug_log_ring_drain_json(int *out_len); // 调用者 free int ringCount();
#define DEBUG_ERR(tag, fmt, ...) debug_log(DEBUG_LOG_ERR, tag, fmt, ##__VA_ARGS__) /** @brief 读取并清空环形日志缓冲区,返回 JSON 数组字符串(需调用者 free)。 */
#define DEBUG_WARN(tag, fmt, ...) debug_log(DEBUG_LOG_WARN, tag, fmt, ##__VA_ARGS__) char* ringDrainJson(int *out_len);
#define DEBUG_INFO(tag, fmt, ...) debug_log(DEBUG_LOG_INFO, tag, fmt, ##__VA_ARGS__)
#define DEBUG_DBG(tag, fmt, ...) debug_log(DEBUG_LOG_DBG, tag, fmt, ##__VA_ARGS__) /** @brief 当前日志级别(运行时可改)。 */
debug_log_level_t logLevel = DEBUG_LOG_INFO;
private:
DebugLog() = default;
~DebugLog() = default;
DebugLog(const DebugLog&) = delete;
DebugLog& operator=(const DebugLog&) = delete;
int m_udpSock = -1;
struct sockaddr_in m_debugAddr;
bool m_initialized = false;
bool m_uartInitialized = false;
static constexpr int LOG_RING_SLOTS = 64;
static constexpr int LOG_RING_SIZE = 256;
char m_logRing[LOG_RING_SLOTS][LOG_RING_SIZE];
int m_logRingLens[LOG_RING_SLOTS];
int m_logRingHead = 0;
int m_logRingCount = 0;
SemaphoreHandle_t m_logRingMutex = NULL;
esp_err_t ensureUdpSocket();
void ringPush(const char *data, int len);
};
// 便捷宏 — 直接调用单例方法
#define DEBUG_ERR(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_ERR, tag, fmt, ##__VA_ARGS__)
#define DEBUG_WARN(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_WARN, tag, fmt, ##__VA_ARGS__)
#define DEBUG_INFO(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_INFO, tag, fmt, ##__VA_ARGS__)
#define DEBUG_DBG(tag, fmt, ...) DebugLog::instance().log(DEBUG_LOG_DBG, tag, fmt, ##__VA_ARGS__)
#endif // DEBUG_LOG_H #endif // DEBUG_LOG_H
+91 -128
View File
@@ -1,6 +1,8 @@
/* /*
* health_monitor.cpp - 系统健康监控 * health_monitor.cpp - 系统健康监控
* 包含:健康状态管理 + WiFi RSSI 监控 + 温度监控 * 包含:健康状态管理 + WiFi RSSI 监控 + 温度监控
*
* 所有功能封装为 HealthMonitor 类方法。
*/ */
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@@ -16,207 +18,173 @@
#include "debug_log.h" #include "debug_log.h"
#include "server/wifi_ota.h" #include "server/wifi_ota.h"
/** @brief 全局健康状态,受互斥锁保护。 */ // ============================================================
health_state_t g_health_state = SYS_NORMAL; // 静态成员
// ============================================================
health_state_t HealthMonitor::healthState = SYS_NORMAL;
/** @brief 健康状态互斥锁。 */ // ============================================================
static SemaphoreHandle_t g_health_mutex = NULL; // 单例
/** @brief 内置温度传感器句柄。 */ // ============================================================
static temperature_sensor_handle_t s_temp_handle = NULL; HealthMonitor& HealthMonitor::instance()
/** @brief 最近一次采样的芯片温度(摄氏度)。 */
static float s_last_temperature = 0.0f;
/** @brief 最近一次健康监控采样得到的 WiFi RSSI。 */
static int8_t s_last_wifi_rssi = -127;
/** @brief 最近一次 WiFi RSSI 采样时间(毫秒)。 */
static int64_t s_last_wifi_rssi_ms = 0;
// ----------------------------------------
// 健康状态管理
/**
* @brief 初始化健康监控模块,创建互斥锁和基础状态。
*/
void health_monitor_init(void)
{ {
/** @brief 健康状态互斥锁。 */ static HealthMonitor inst;
g_health_mutex = xSemaphoreCreateMutex(); return inst;
if (g_health_mutex == NULL) { }
// ============================================================
// 初始化
// ============================================================
void HealthMonitor::init()
{
m_mutex = xSemaphoreCreateMutex();
if (m_mutex == NULL) {
DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败"); DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败");
} }
} }
/** // ============================================================
* @brief 获取当前系统健康状态。 // 获取当前健康状态
* @return 当前健康状态枚举值。 // ============================================================
*/ health_state_t HealthMonitor::getCurrent()
health_state_t health_get_current(void)
{ {
health_state_t state; health_state_t state;
if (g_health_mutex) { if (m_mutex) {
xSemaphoreTake(g_health_mutex, portMAX_DELAY); xSemaphoreTake(m_mutex, portMAX_DELAY);
} }
state = g_health_state; state = healthState;
if (g_health_mutex) { if (m_mutex) {
xSemaphoreGive(g_health_mutex); xSemaphoreGive(m_mutex);
} }
return state; return state;
} }
/** float HealthMonitor::getLastTemperature()
* @brief 返回最近一次采样到的芯片温度(摄氏度)。
* @return 最近一次温度采样值(摄氏度)。
*/
float health_get_last_temperature(void)
{ {
/** @brief 最近一次采样的芯片温度(摄氏度)。 */ return m_lastTemperature;
return s_last_temperature;
} }
/** bool HealthMonitor::getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms)
* @brief 获取最近一次采样到的 WiFi RSSI 及其年龄。
* @param out_rssi 输出 RSSI 指针,可为 NULL。
* @param out_age_ms 输出采样距今毫秒数指针,可为 NULL。
* @return 有有效采样返回 true,否则返回 false。
*/
bool health_get_last_wifi_rssi(int8_t *out_rssi, uint32_t *out_age_ms)
{ {
if (s_last_wifi_rssi_ms <= 0) { if (m_lastWifiRssiMs <= 0) {
return false; return false;
} }
int64_t now_ms = esp_timer_get_time() / 1000; int64_t now_ms = esp_timer_get_time() / 1000;
uint32_t age_ms = (now_ms > s_last_wifi_rssi_ms) ? (uint32_t)(now_ms - s_last_wifi_rssi_ms) : 0; uint32_t age_ms = (now_ms > m_lastWifiRssiMs) ? (uint32_t)(now_ms - m_lastWifiRssiMs) : 0;
if (out_rssi) *out_rssi = s_last_wifi_rssi; if (out_rssi) *out_rssi = m_lastWifiRssi;
if (out_age_ms) *out_age_ms = age_ms; if (out_age_ms) *out_age_ms = age_ms;
return true; return true;
} }
// ---------------------------------------- // ============================================================
// 温度监控(定时器回调) // 温度监控(定时器回调)
/** // ============================================================
* @brief 温度定时回调,采样并根据阈值更新健康状态。 void HealthMonitor::tempMonitorCallback(void *arg)
* @param arg
*/
static void temp_monitor_callback(void *arg)
{ {
auto& self = instance();
float temp = 0.0f; float temp = 0.0f;
esp_err_t err = temperature_sensor_get_celsius(s_temp_handle, &temp); esp_err_t err = temperature_sensor_get_celsius(self.m_tempHandle, &temp);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_WARN("TEMP", "读取温度失败: %s", esp_err_to_name(err)); DEBUG_WARN("TEMP", "读取温度失败: %s", esp_err_to_name(err));
return; return;
} }
/** @brief 最近一次采样的芯片温度(摄氏度)。 */ self.m_lastTemperature = temp;
s_last_temperature = temp;
if (temp >= TEMP_CRITICAL) { if (temp >= TEMP_CRITICAL) {
/** @brief 全局健康状态,受互斥锁保护。 */ healthState = SYS_SAFE_SHUTDOWN;
g_health_state = SYS_SAFE_SHUTDOWN;
DEBUG_ERR("TEMP", "!!! 危险 !!! 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL); DEBUG_ERR("TEMP", "!!! 危险 !!! 温度 %.1f°C >= %d°C,触发安全关机", temp, TEMP_CRITICAL);
DEBUG_ERR("TEMP", "健康状态变更为: SYS_SAFE_SHUTDOWN"); DEBUG_ERR("TEMP", "健康状态变更为: SYS_SAFE_SHUTDOWN");
} else if (temp >= TEMP_WARNING) { } else if (temp >= TEMP_WARNING) {
if (g_health_state < SYS_WARNING) { if (healthState < SYS_WARNING) {
/** @brief 全局健康状态,受互斥锁保护。 */ healthState = SYS_WARNING;
g_health_state = SYS_WARNING;
DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING); DEBUG_WARN("TEMP", "警告: 温度 %.1f°C >= %d°C", temp, TEMP_WARNING);
DEBUG_WARN("TEMP", "健康状态变更为: SYS_WARNING"); DEBUG_WARN("TEMP", "健康状态变更为: SYS_WARNING");
} }
} }
} }
// ---------------------------------------- // ============================================================
// 温度监控任务入口 // FreeRTOS 任务入口
/** // ============================================================
* @brief 启动温度监控定时器,周期性巡检系统健康。 void HealthMonitor::taskEntry(void *pvParameters)
* @param pvParameters FreeRTOS 任务参数,未使用。
*/
void health_monitor_task(void *pvParameters)
{ {
auto& self = instance();
DEBUG_INFO("MON", "=== 健康监控任务启动 ==="); DEBUG_INFO("MON", "=== 健康监控任务启动 ===");
temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80); temperature_sensor_config_t temp_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 80);
esp_err_t err = temperature_sensor_install(&temp_config, &s_temp_handle); esp_err_t err = temperature_sensor_install(&temp_config, &self.m_tempHandle);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR("MON", "温度传感器初始化失败: %s", esp_err_to_name(err)); DEBUG_ERR("MON", "温度传感器初始化失败: %s", esp_err_to_name(err));
} else { } else {
ESP_ERROR_CHECK(temperature_sensor_enable(s_temp_handle)); ESP_ERROR_CHECK(temperature_sensor_enable(self.m_tempHandle));
DEBUG_INFO("MON", "温度传感器已启用 (范围 20°C ~ 80°C)"); DEBUG_INFO("MON", "温度传感器已启用 (范围 20°C ~ 80°C)");
} }
// 定时器,每1秒读取一次温度 // 定时器,每1秒读取一次温度
esp_timer_handle_t temp_timer; esp_timer_handle_t temp_timer;
esp_timer_create_args_t timer_args = {}; esp_timer_create_args_t timer_args = {};
timer_args.callback = &temp_monitor_callback; timer_args.callback = &tempMonitorCallback;
timer_args.arg = nullptr; timer_args.arg = nullptr;
timer_args.name = "temp_monitor"; timer_args.name = "temp_monitor";
timer_args.dispatch_method = ESP_TIMER_TASK; timer_args.dispatch_method = ESP_TIMER_TASK;
timer_args.skip_unhandled_events = false;
esp_timer_create(&timer_args, &temp_timer);
esp_timer_start_periodic(temp_timer, 1 * 1000000); // 1秒
DEBUG_INFO("MON", "温度监控定时器已启动 (间隔 1 秒)"); if (esp_timer_create(&timer_args, &temp_timer) == ESP_OK) {
esp_timer_start_periodic(temp_timer, 1000000); // 1秒
DEBUG_INFO("MON", "温度监控定时器已启动 (1秒间隔)");
} else {
DEBUG_ERR("MON", "创建温度监控定时器失败");
}
// 保活循环:定时器由 esp_timer 任务驱动,此任务只需保持存活 // 周期性巡检
// 状态汇报已由 status_monitor_task 负责,此处只在异常时告警
while (1) { while (1) {
int8_t rssi = wifi_get_rssi();
if (wifi_is_connected() && rssi != -127) {
s_last_wifi_rssi = rssi;
s_last_wifi_rssi_ms = esp_timer_get_time() / 1000;
}
uint32_t free_heap = esp_get_free_heap_size(); uint32_t free_heap = esp_get_free_heap_size();
if (free_heap < HEAP_WARNING_MIN) { if (free_heap < HEAP_WARNING_MIN) {
DEBUG_WARN("MON", "空闲堆内存不足: %lu < %d", (unsigned long)free_heap, HEAP_WARNING_MIN); DEBUG_WARN("MON", "堆内存不足: %lu < %lu", free_heap, HEAP_WARNING_MIN);
} }
if (g_health_state >= SYS_CRITICAL) { if (healthState >= SYS_CRITICAL) {
DEBUG_ERR("MON", "健康状态危险: %d", g_health_state); DEBUG_ERR("MON", "健康状态危险: %d", healthState);
} }
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
} }
} }
// ---------------------------------------- // ============================================================
// 健康事件日志 // 健康事件日志
/** // ============================================================
* @brief 记录一条健康监控事件日志。 void HealthMonitor::logEvent(const char *event, int value)
* @param event 事件名称字符串。
* @param value 事件关联的数值。
*/
void health_log_event(const char *event, int value)
{ {
DEBUG_INFO("HEALTH", "[健康事件] %s: %d", event, value); DEBUG_INFO("HEALTH", "[健康事件] %s: %d", event, value);
} }
// ---------------------------------------- // ============================================================
// OTA 传输前的健康检查 // OTA 健康检查
/** // ============================================================
* @brief OTA 传输前执行健康检查,决定是否允许传输。 health_status_t HealthMonitor::checkBeforeOta()
* @return 健康检查结果,决定是否允许 OTA。
*/
health_status_t health_check_before_ota(void)
{ {
health_status_t status = HEALTH_MON_OK; health_status_t status = HEALTH_MON_OK;
if (g_health_state >= SYS_SAFE_SHUTDOWN) { if (healthState >= SYS_SAFE_SHUTDOWN) {
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_SAFE_SHUTDOWN,禁止传输"); DEBUG_WARN("HEALTH", "OTA 前检查: SYS_SAFE_SHUTDOWN,禁止传输");
return HEALTH_MON_SHUTDOWN; return HEALTH_MON_SHUTDOWN;
} else if (g_health_state >= SYS_CRITICAL) { } else if (healthState >= SYS_CRITICAL) {
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_CRITICAL,禁止传输"); DEBUG_WARN("HEALTH", "OTA 前检查: SYS_CRITICAL,禁止传输");
return HEALTH_MON_CRITICAL; return HEALTH_MON_CRITICAL;
} else if (g_health_state >= SYS_WARNING) { } else if (healthState >= SYS_WARNING) {
status = HEALTH_MON_WARNING; status = HEALTH_MON_WARNING;
DEBUG_WARN("HEALTH", "OTA 前检查: SYS_WARNING,继续传输但需注意"); DEBUG_WARN("HEALTH", "OTA 前检查: SYS_WARNING,继续传输但需注意");
} }
if (!wifi_is_connected()) { if (!WifiOta::isConnected()) {
DEBUG_WARN("HEALTH", "OTA 前检查: WiFi 未连接"); DEBUG_WARN("HEALTH", "OTA 前检查: WiFi 未连接");
return HEALTH_MON_CRITICAL; return HEALTH_MON_CRITICAL;
} }
int8_t last_rssi = -127; int8_t last_rssi = -127;
uint32_t age_ms = 0; uint32_t age_ms = 0;
if (health_get_last_wifi_rssi(&last_rssi, &age_ms)) { if (getLastWifiRssi(&last_rssi, &age_ms)) {
if (age_ms <= OTA_WIFI_RSSI_SAMPLE_MAX_AGE_MS) { if (age_ms <= OTA_WIFI_RSSI_SAMPLE_MAX_AGE_MS) {
if (last_rssi < OTA_WIFI_RSSI_CRITICAL) { if (last_rssi < OTA_WIFI_RSSI_CRITICAL) {
DEBUG_WARN("HEALTH", "OTA 前检查: 最近 RSSI %d < %d (样本年龄 %lu ms)", DEBUG_WARN("HEALTH", "OTA 前检查: 最近 RSSI %d < %d (样本年龄 %lu ms)",
@@ -239,21 +207,21 @@ health_status_t health_check_before_ota(void)
return status; return status;
} }
health_status_t health_check_before_ota_relay(void) health_status_t HealthMonitor::checkBeforeOtaRelay()
{ {
health_status_t status = HEALTH_MON_OK; health_status_t status = HEALTH_MON_OK;
if (g_health_state >= SYS_SAFE_SHUTDOWN) { if (healthState >= SYS_SAFE_SHUTDOWN) {
DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_SAFE_SHUTDOWN,禁止传输"); DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_SAFE_SHUTDOWN,禁止传输");
return HEALTH_MON_SHUTDOWN; return HEALTH_MON_SHUTDOWN;
} else if (g_health_state >= SYS_CRITICAL) { } else if (healthState >= SYS_CRITICAL) {
/* SYS_CRITICAL 可能是由 WiFi RSSI 过低导致的, /* SYS_CRITICAL 可能是由 WiFi RSSI 过低导致的,
* 但 SPI 中继阶段不依赖 WiFi,只检查温度/硬件等本地状态。 * 但 SPI 中继阶段不依赖 WiFi,只检查温度/硬件等本地状态。
* 温度达到 TEMP_CRITICAL(100°C) 时 g_health_state 会被设为 * 温度达到 TEMP_CRITICAL(100°C) 时 g_health_state 会被设为
* SYS_SAFE_SHUTDOWN,在上面的分支已经拦截。 * SYS_SAFE_SHUTDOWN,在上面的分支已经拦截。
* 所以 SYS_CRITICAL 在中继阶段只可能是 WiFi 导致的,允许继续。 */ * 所以 SYS_CRITICAL 在中继阶段只可能是 WiFi 导致的,允许继续。 */
DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_CRITICAL (WiFi 相关),SPI 中继允许继续"); DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_CRITICAL (WiFi 相关),SPI 中继允许继续");
} else if (g_health_state >= SYS_WARNING) { } else if (healthState >= SYS_WARNING) {
status = HEALTH_MON_WARNING; status = HEALTH_MON_WARNING;
DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_WARNING,继续传输"); DEBUG_WARN("HEALTH", "OTA 中继检查: SYS_WARNING,继续传输");
} }
@@ -269,34 +237,29 @@ health_status_t health_check_before_ota_relay(void)
return status; return status;
} }
/** // ============================================================
* @brief 根据 WiFi RSSI 更新健康状态并返回检查结果。 // WiFi RSSI 更新
* @param rssi 当前 WiFi 信号强度(dBm)。 // ============================================================
* @return 健康检查结果。 health_status_t HealthMonitor::updateFromWifiRssi(int8_t rssi)
*/
health_status_t health_update_from_wifi_rssi(int8_t rssi)
{ {
if (rssi < WIFI_RSSI_CRITICAL) { if (rssi < WIFI_RSSI_CRITICAL) {
/** @brief 全局健康状态,受互斥锁保护。 */ healthState = SYS_CRITICAL;
g_health_state = SYS_CRITICAL;
DEBUG_WARN("HEALTH", "WiFi RSSI 危险: %d dBm < %d dBm", rssi, WIFI_RSSI_CRITICAL); DEBUG_WARN("HEALTH", "WiFi RSSI 危险: %d dBm < %d dBm", rssi, WIFI_RSSI_CRITICAL);
health_log_event("WIFI危险", rssi); logEvent("WIFI危险", rssi);
return HEALTH_MON_CRITICAL; return HEALTH_MON_CRITICAL;
} else if (rssi < WIFI_RSSI_NORMAL) { } else if (rssi < WIFI_RSSI_NORMAL) {
if (g_health_state < SYS_WARNING) { if (healthState < SYS_WARNING) {
/** @brief 全局健康状态,受互斥锁保护。 */ healthState = SYS_WARNING;
g_health_state = SYS_WARNING;
DEBUG_WARN("HEALTH", "WiFi RSSI 警告: %d dBm < %d dBm", rssi, WIFI_RSSI_NORMAL); DEBUG_WARN("HEALTH", "WiFi RSSI 警告: %d dBm < %d dBm", rssi, WIFI_RSSI_NORMAL);
health_log_event("WIFI警告", rssi); logEvent("WIFI警告", rssi);
} }
return HEALTH_MON_WARNING; return HEALTH_MON_WARNING;
} }
if (g_health_state == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) { if (healthState == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) {
/** @brief 全局健康状态,受互斥锁保护。 */ healthState = SYS_NORMAL;
g_health_state = SYS_NORMAL;
DEBUG_INFO("HEALTH", "WiFi RSSI 恢复: %d dBm >= %d dBm", rssi, WIFI_RSSI_NORMAL); DEBUG_INFO("HEALTH", "WiFi RSSI 恢复: %d dBm >= %d dBm", rssi, WIFI_RSSI_NORMAL);
health_log_event("WIFI恢复", rssi); logEvent("WIFI恢复", rssi);
} }
return HEALTH_MON_OK; return HEALTH_MON_OK;
+50 -54
View File
@@ -1,9 +1,14 @@
/* /*
* health_monitor.h - 系统健康监控头文件 * health_monitor.h - 系统健康监控头文件
*
* HealthMonitor 类封装健康状态管理、温度监控和 WiFi RSSI 监控。
*/ */
#pragma once #pragma once
#include <stdint.h> #include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/temperature_sensor.h"
#include "app_config.h" #include "app_config.h"
// T9.3: 健康状态等级 // T9.3: 健康状态等级
@@ -21,69 +26,60 @@ typedef enum {
HEALTH_MON_SHUTDOWN = 3 HEALTH_MON_SHUTDOWN = 3
} health_status_t; } health_status_t;
// 外部共享状态(互斥锁保护)
extern health_state_t g_health_state;
// WiFi RSSI 阈值(来自 app_config.h) // WiFi RSSI 阈值(来自 app_config.h)
#define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING #define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING
#define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL #define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL
/** /**
* @brief 初始化健康监控模块,创建内部互斥锁与基础状态。 * @brief 系统健康监控类(单例)
*/
void health_monitor_init(void);
/**
* @brief 健康监控任务入口,负责启动温度定时检测并周期性巡检系统状态。
* @param pvParameters FreeRTOS 任务参数,未使用。
*/
void health_monitor_task(void *pvParameters);
/**
* @brief 在 OTA 传输开始前执行健康检查,返回当前是否允许传输。
* @return 健康检查结果,决定是否允许 OTA。
*/
health_status_t health_check_before_ota(void);
/**
* @brief 在 OTA 中继转发阶段执行健康检查,跳过 WiFi 相关检查。
* *
* SPI 中继阶段固件已存储在 ESP32 Flash 中,不依赖 WiFi 连接, * 封装健康状态管理、温度定时监控和 WiFi RSSI 监控。
* 因此跳过 WiFi 连接状态和 RSSI 检查,只检查温度等本地状态。
* @return 健康检查结果,决定是否允许 OTA。
*/ */
health_status_t health_check_before_ota_relay(void); class HealthMonitor {
public:
static HealthMonitor& instance();
/** /** @brief 初始化健康监控模块,创建内部互斥锁与基础状态。 */
* @brief 记录一条健康相关事件日志,便于故障追踪与状态回溯。 void init();
* @param event 事件名称字符串。
* @param value 事件关联的数值。
*/
void health_log_event(const char *event, int value);
/** /** @brief 健康监控 FreeRTOS 任务入口。 */
* @brief 获取当前系统健康状态。 static void taskEntry(void *pvParameters);
* @return 当前健康状态枚举值。
*/
health_state_t health_get_current(void);
/** /** @brief OTA 传输前执行健康检查。 */
* @brief 根据最新 WiFi RSSI 更新健康状态,并返回对应健康检查结果。 health_status_t checkBeforeOta();
* @param rssi 当前 WiFi 信号强度(dBm)。
* @return 健康检查结果。
*/
health_status_t health_update_from_wifi_rssi(int8_t rssi);
/** /** @brief OTA 中继转发阶段执行健康检查(跳过 WiFi 检查)。 */
* @brief 获取最近一次采样到的芯片温度(摄氏度)。 health_status_t checkBeforeOtaRelay();
* @return 最近一次温度采样值(摄氏度)。
*/
float health_get_last_temperature(void);
/** /** @brief 记录一条健康事件日志。 */
* @brief 获取最近一次健康监控采样得到的 WiFi RSSI。 void logEvent(const char *event, int value);
* @param out_rssi 输出 RSSI。
* @param out_age_ms 输出样本距今时长(毫秒)。 /** @brief 获取当前系统健康状态。 */
* @return true 有有效样本,false 当前无有效样本。 health_state_t getCurrent();
*/
bool health_get_last_wifi_rssi(int8_t *out_rssi, uint32_t *out_age_ms); /** @brief 根据 WiFi RSSI 更新健康状态。 */
health_status_t updateFromWifiRssi(int8_t rssi);
/** @brief 获取最近一次芯片温度。 */
float getLastTemperature();
/** @brief 获取最近一次 WiFi RSSI 及其年龄。 */
bool getLastWifiRssi(int8_t *out_rssi, uint32_t *out_age_ms);
/** @brief 全局健康状态(public,供外部读取)。 */
static health_state_t healthState;
private:
HealthMonitor() = default;
~HealthMonitor() = default;
HealthMonitor(const HealthMonitor&) = delete;
HealthMonitor& operator=(const HealthMonitor&) = delete;
SemaphoreHandle_t m_mutex = NULL;
temperature_sensor_handle_t m_tempHandle = NULL;
float m_lastTemperature = 0.0f;
int8_t m_lastWifiRssi = -127;
int64_t m_lastWifiRssiMs = 0;
static void tempMonitorCallback(void *arg);
};
+311 -543
View File
File diff suppressed because it is too large Load Diff
+103 -119
View File
@@ -1,6 +1,8 @@
/* /*
* ota_relay.h - OTA 中继核心状态机 * ota_relay.h - OTA 中继核心状态机
* T2.3, T3.1 - T3.7 * T2.3, T3.1 - T3.7
*
* OtaRelay 类封装 OTA 中继状态机、Flash 存储和元数据管理。
*/ */
#ifndef OTA_RELAY_H #ifndef OTA_RELAY_H
#define OTA_RELAY_H #define OTA_RELAY_H
@@ -8,151 +10,133 @@
#include <stdint.h> #include <stdint.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#include "transport/protocol.h"
#include "esp_partition.h"
// T4.3: OTA 元数据结构,用于 NVS 存储
#define OTA_META_NVS_NAMESPACE "ota_meta" #define OTA_META_NVS_NAMESPACE "ota_meta"
#define OTA_META_NVS_KEY "meta" #define OTA_META_NVS_KEY "meta"
typedef struct { typedef struct {
uint32_t total_size; // 固件总大小(字节) uint32_t total_size;
uint32_t stored_size; // 当前已存储大小(字节) uint32_t stored_size;
uint32_t last_seq; // 最后成功存储的序列号 uint32_t last_seq;
uint32_t chunk_count; // 预期总块数 uint32_t chunk_count;
uint8_t checksum[32]; // 固件 SHA256 校验(可选) uint8_t checksum[32];
} ota_meta_t; } ota_meta_t;
// OTA 中继状态(对应 GD32 四阶段协议)
typedef enum { typedef enum {
// 阶段0: 空闲
RELAY_IDLE = 0, RELAY_IDLE = 0,
RELAY_HANDSHAKE_IDLE,
// 阶段1: 安全握手 RELAY_HANDSHAKE_SENT,
RELAY_HANDSHAKE_IDLE, // 等待 HANDSHAKE_REQ RELAY_CHALLENGE_SENT,
RELAY_HANDSHAKE_SENT, // 已发送 0x00 AC,等待 GD32 响应 RELAY_CHALLENGE_RESPOND,
RELAY_CHALLENGE_SENT, // 已发送 nonce_server (16B),等待响应 RELAY_SESSION_KEY_SENT,
RELAY_CHALLENGE_RESPOND, // 已收到 GD32 握手数据包 RELAY_AUTH_COMPLETE,
RELAY_SESSION_KEY_SENT, // 已发送变换后的随机数,等待 0xBA C0 RELAY_SIZE_SENT,
RELAY_AUTH_COMPLETE, // 已收到 0xBA C0,安全通道建立完成 RELAY_SIZE_ACK,
RELAY_RECEIVING,
// 阶段2: 传输准备 RELAY_RELAY_ACTIVE,
RELAY_SIZE_SENT, // 已发送固件大小(4B LE) RELAY_PACKET_SENT,
RELAY_SIZE_ACK, // 已收到 0xBA C0 + seq RELAY_PACKET_ACK_OK,
RELAY_PACKET_ACK_RETRY,
// 阶段3: 数据传输 RELAY_RECEIVE_DONE,
RELAY_RECEIVING, // 接收并存储固件到 Flash RELAY_TRANSFER_DONE,
RELAY_RELAY_ACTIVE, // 存储完成,开始向 GD32 转发 RELAY_FINISH_SENT,
RELAY_PACKET_SENT, // 已发送 DATA_PACKET,等待 GD32 ACK RELAY_DONE,
RELAY_PACKET_ACK_OK, // ACK 正常,继续下一包 RELAY_APP_VERIFY,
RELAY_PACKET_ACK_RETRY, // ACK 要求重发,重新发送当前包
RELAY_RECEIVE_DONE, // 已发送 DATA_END,等待 TRANSFER_DONE
// 阶段4: 结束跳转
RELAY_TRANSFER_DONE, // 已收到 0xBA C0 + FF FF FF FF
RELAY_FINISH_SENT, // 已发送 0x0A AC,等待 GD32 确认
RELAY_DONE, // GD32 回复 0x0A AC,OTA 完成
RELAY_APP_VERIFY, // 等待 GD32 APP 启动并回复存活确认
// 错误状态
RELAY_ERROR = 0xFF RELAY_ERROR = 0xFF
} relay_state_t; } relay_state_t;
// OTA 结果码
typedef enum { typedef enum {
OTA_RESULT_OK = 0, OTA_RESULT_OK = 0,
OTA_RESULT_ERR_TIMEOUT = -1, // GD32 超时无响应 OTA_RESULT_ERR_TIMEOUT = -1,
OTA_RESULT_ERR_CRC = -2, // CRC 校验失败 OTA_RESULT_ERR_CRC = -2,
OTA_RESULT_ERR_AUTH = -3, // 认证失败 OTA_RESULT_ERR_AUTH = -3,
OTA_RESULT_ERR_STORAGE = -4, // Flash 存储错误 OTA_RESULT_ERR_STORAGE = -4,
OTA_RESULT_ERR_GD32 = -5, // GD32 返回错误 OTA_RESULT_ERR_GD32 = -5,
OTA_RESULT_ERR_HEALTH = -6, // 健康状态异常中止 OTA_RESULT_ERR_HEALTH = -6,
OTA_RESULT_ERR_USER_ABORT = -7 // 用户中止 OTA_RESULT_ERR_USER_ABORT = -7
} ota_result_t; } ota_result_t;
// 外部队列句柄(在 app_main 中创建) class OtaRelay {
extern QueueHandle_t g_ota_dispatch_queue; // 从 WiFi/BLE 到 OTA 中继 public:
extern QueueHandle_t g_ota_result_queue; // OTA 结果到 WiFi/BLE static OtaRelay& instance();
/** static void taskEntry(void *pvParameters);
* @brief OTA 中继任务入口,负责执行握手、认证、分包转发和结束流程。 ota_result_t startHandshake();
* @param pvParameters FreeRTOS 任务参数,未使用。 ota_result_t forwardFirmware();
*/ relay_state_t getState();
void ota_relay_task(void *pvParameters); void reset();
/** static esp_err_t flashWriteChunk(uint32_t offset, const uint8_t *data, size_t len);
* @brief 发起与 GD32 的握手流程,建立 OTA 传输前的安全会话。 static esp_err_t flashReadChunk(uint32_t offset, uint8_t *data, size_t len);
* @return 操作结果码。 static esp_err_t metaLoad(ota_meta_t *meta);
*/ static esp_err_t metaSave(const ota_meta_t *meta);
ota_result_t ota_relay_start_handshake(void); static esp_err_t metaUpdate(uint32_t stored_size, uint32_t last_seq);
static esp_err_t metaClear();
/** // 公开状态
* @brief 从 Flash 中读取已缓存固件并按协议转发给 GD32。 static relay_state_t relayState;
* @return 操作结果码。 static uint32_t relaySeq;
*/ static uint32_t relayTotalPackets;
ota_result_t ota_relay_forward_firmware(void); static volatile bool gd32AppAlive;
static QueueHandle_t dispatchQueue;
static QueueHandle_t resultQueue;
/** // 实例状态(public,供状态机方法访问)
* @brief 获取当前 OTA 中继状态机状态。 const esp_partition_t *m_gd32Partition = NULL;
* @return 当前中继状态枚举值。 session_ctx_t m_sessionCtx;
*/ uint8_t m_nonceServer[16] = {0};
relay_state_t ota_relay_get_state(void); uint8_t m_kSession[16] = {0};
uint8_t m_gd32UidRuntime[12] = {0};
/** static constexpr size_t GD32_UID_LEN = 12;
* @brief 重置 OTA 中继状态机,清理当前状态以便重新开始流程。 static constexpr size_t GD32_CHALLENGE_RESPONSE_LEN = 36;
*/
void ota_relay_reset(void);
/** ota_result_t sendHandshakeReq();
* @brief 向 OTA 缓存分区写入一段固件数据。 ota_result_t sendNonceAndAuthenticate();
* @param offset 分区内写入偏移。 ota_result_t sendFirmwareSize(uint32_t size);
* @param data 待写入的数据指针。 ota_result_t relayPacketsFromFlash();
* @param len 数据长度(字节)。 ota_result_t finishOta();
* @return ESP_OK 成功,其他失败。
*/
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len);
/** static int aes128EcbEncryptBlockLocal(const uint8_t *key, const uint8_t *in, uint8_t *out);
* @brief 从 OTA 缓存分区读取一段固件数据。 static void logRespPayload(const char *stage, uint8_t resp_type, const uint8_t *payload, size_t len);
* @param offset 分区内读取偏移。 esp_err_t waitUntilNotWait(const char *stage, uint8_t *resp_type,
* @param data 读取缓冲区指针。 uint8_t *resp_payload, size_t resp_max,
* @param len 读取长度(字节)。 size_t *resp_len, uint32_t timeout_ms,
* @return ESP_OK 成功,其他失败。 uint32_t max_wait_rounds);
*/
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len);
/** static bool isStructuredAckWithSeq(const uint8_t *payload, size_t len, uint32_t expected_seq);
* @brief 从持久化文件中加载 OTA 元数据。 static bool parseStructuredError(const uint8_t *payload, size_t len, uint32_t *seq_out, uint8_t *err_out);
* @param meta 输出参数,加载后的元数据结构体。 static bool isHandshakeEchoAck(const uint8_t *payload, size_t len);
* @return ESP_OK 成功,其他失败。 static bool isChallengePayloadAck(const uint8_t *payload, size_t len);
*/ static bool isConfirmPlainAck(const uint8_t *payload, size_t len);
esp_err_t meta_load(ota_meta_t *meta); static bool isSizeSeq0Ack(const uint8_t *payload, size_t len);
static bool isFinishPlainAck(const uint8_t *payload, size_t len);
static bool isTransferDoneAck(const uint8_t *payload, size_t len);
static bool isStructuredAckWithPayload(const uint8_t *payload, size_t len);
/** static esp_err_t pollUntilExpectedAckShape(const char *stage,
* @brief 将 OTA 元数据保存到持久化文件。 uint8_t *resp_type,
* @param meta 待保存的元数据结构体。 uint8_t *resp_payload,
* @return ESP_OK 成功,其他失败。 size_t resp_max,
*/ size_t *resp_len,
esp_err_t meta_save(const ota_meta_t *meta); uint32_t poll_timeout_ms,
uint32_t wait_rounds,
uint32_t maxExtraPolls,
bool (*matcher)(const uint8_t *, size_t));
static bool parseAndVerifyChallengePayload(const uint8_t *resp_payload, size_t resp_len,
uint8_t *out_k_session, uint8_t *out_uid);
static bool aes128EcbEncrypt48Bytes(const uint8_t *key, const uint8_t *in, uint8_t *out);
static void calculateCrc32WithKey(const uint8_t *key, const uint8_t *data48, uint8_t *output);
/** void requestGd32AppEnterBootloader();
* @brief 更新已存储大小与最后分块序号等 OTA 进度信息。
* @param stored_size 已存储的固件大小(字节)。
* @param last_seq 最后成功的分块序号。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq);
/** private:
* @brief 清空 OTA 元数据,通常在完成或中止后调用。 OtaRelay() = default;
* @return ESP_OK 成功,其他失败。 ~OtaRelay() = default;
*/ OtaRelay(const OtaRelay&) = delete;
esp_err_t meta_clear(void); OtaRelay& operator=(const OtaRelay&) = delete;
};
// GD32 APP 存活标志(OTA 完成后由 APP PING 确认)
extern volatile bool g_gd32_app_alive;
// 当前中继状态(供健康监控外部访问)
extern relay_state_t g_relay_state;
extern uint32_t g_relay_seq;
extern uint32_t g_relay_total_packets;
#endif // OTA_RELAY_H #endif // OTA_RELAY_H
+132 -133
View File
@@ -1,5 +1,6 @@
/* /*
* ble_ota.cpp - BLE OTA GATT 服务器实现 (ESP-IDF 6.0 NimBLE) * ble_ota.cpp - BLE OTA GATT 服务器实现 (ESP-IDF 6.0 NimBLE)
* 所有功能封装为 BleOta 类方法。
* T5.1 - T5.5 * T5.1 - T5.5
* *
* 通过 BLE GATT 接收上位机固件,支持两种目标: * 通过 BLE GATT 接收上位机固件,支持两种目标:
@@ -57,27 +58,25 @@
// 前向声明(GAP event handler 在广播函数之前定义,需要声明) // 前向声明(GAP event handler 在广播函数之前定义,需要声明)
// ============================================================ // ============================================================
static void ble_ota_start_advertising(void); static void ble_ota_start_advertising(void);
static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t completed_bytes); static void sendWindowAck(uint8_t cmd, uint16_t seq, uint32_t completed_bytes);
static void ble_ota_send_finish_event(uint8_t stage, uint32_t value); static void sendFinishEvent(uint8_t stage, uint32_t value);
// ============================================================ // ============================================================
// 静态状态 // 静态状态
// ============================================================ // ============================================================
static bool s_ble_ready = false; // NimBLE 同步完成
static bool s_ble_connected = false; // GATT 客户端已连接 // ============================================================
/** @brief 当前 BLE 连接句柄。 */ BleOta& BleOta::instance()
static uint16_t s_conn_handle = BLE_HS_CONN_HANDLE_NONE; {
static uint16_t s_data_out_val_handle = 0; // DATA_OUT 值句柄 (供通知用) static BleOta inst;
return inst;
}
// File-scope val_handle for GATT service definition (struct literal needs compile-time address)
static uint16_t s_data_out_val_handle = 0;
// ESP32 自身 OTA 状态 // ESP32 自身 OTA 状态
static bool s_esp32_ota_active = false;
static esp_ota_handle_t s_esp32_ota_handle = 0;
static const esp_partition_t *s_esp32_update_partition = NULL;
static size_t s_esp32_received = 0;
static bool s_esp32_reboot_pending = false;
static uint8_t s_esp32_last_cmd = 0;
static uint16_t s_esp32_last_seq = 0xFFFFU;
/** /**
* @brief 异步完成 ESP32 OTA 收尾并触发重启。 * @brief 异步完成 ESP32 OTA 收尾并触发重启。
@@ -88,80 +87,80 @@ static void ble_esp32_finish_and_reboot_task(void *pvParameters)
(void)pvParameters; (void)pvParameters;
DEBUG_INFO(TAG, "ESP32 OTA 异步收尾任务启动"); DEBUG_INFO(TAG, "ESP32 OTA 异步收尾任务启动");
if (s_esp32_last_seq != 0xFFFFU) { if (BleOta::instance().m_esp32LastSeq != 0xFFFFU) {
ble_ota_send_window_ack(s_esp32_last_cmd, s_esp32_last_seq, (uint32_t)s_esp32_received); sendWindowAck(BleOta::instance().m_esp32LastCmd, BleOta::instance().m_esp32LastSeq, (uint32_t)BleOta::instance().m_esp32Received);
DEBUG_INFO(TAG, "ESP32 最终 ACK 已补发: seq=%u", s_esp32_last_seq); DEBUG_INFO(TAG, "ESP32 最终 ACK 已补发: seq=%u", BleOta::instance().m_esp32LastSeq);
} }
vTaskDelay(pdMS_TO_TICKS(150)); vTaskDelay(pdMS_TO_TICKS(150));
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) { if (!BleOta::instance().m_esp32OtaActive || BleOta::instance().m_esp32UpdatePartition == NULL) {
DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启"); DEBUG_WARN(TAG, "异步收尾时 OTA 状态无效,放弃重启");
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
vTaskDelete(NULL); vTaskDelete(NULL);
return; return;
} }
DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节, 设置启动分区...", (unsigned int)s_esp32_received); DEBUG_INFO(TAG, "ESP32 OTA 收尾: 共 %u 字节, 设置启动分区...", (unsigned int)BleOta::instance().m_esp32Received);
int64_t t0_us = esp_timer_get_time(); int64_t t0_us = esp_timer_get_time();
DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_end()"); DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_end()");
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_OTA_END, 0); sendFinishEvent(BLE_OTA_FINISH_BEGIN_OTA_END, 0);
esp_err_t err = esp_ota_end(s_esp32_ota_handle); esp_err_t err = esp_ota_end(BleOta::instance().m_esp32OtaHandle);
int64_t t1_us = esp_timer_get_time(); int64_t t1_us = esp_timer_get_time();
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "[FINISH] esp_ota_end 失败: %s (耗时=%lld ms)", DEBUG_ERR(TAG, "[FINISH] esp_ota_end 失败: %s (耗时=%lld ms)",
esp_err_to_name(err), (long long)((t1_us - t0_us) / 1000)); esp_err_to_name(err), (long long)((t1_us - t0_us) / 1000));
ble_ota_send_finish_event(BLE_OTA_FINISH_ERR_OTA_END, (uint32_t)err); sendFinishEvent(BLE_OTA_FINISH_ERR_OTA_END, (uint32_t)err);
esp_ota_abort(s_esp32_ota_handle); esp_ota_abort(BleOta::instance().m_esp32OtaHandle);
s_esp32_ota_active = false; BleOta::instance().m_esp32OtaActive = false;
s_esp32_ota_handle = 0; BleOta::instance().m_esp32OtaHandle = 0;
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
s_esp32_received = 0; BleOta::instance().m_esp32Received = 0;
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
vTaskDelete(NULL); vTaskDelete(NULL);
return; return;
} }
DEBUG_INFO(TAG, "[FINISH] esp_ota_end 完成 (耗时=%lld ms)", DEBUG_INFO(TAG, "[FINISH] esp_ota_end 完成 (耗时=%lld ms)",
(long long)((t1_us - t0_us) / 1000)); (long long)((t1_us - t0_us) / 1000));
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_OTA_END, (uint32_t)((t1_us - t0_us) / 1000)); sendFinishEvent(BLE_OTA_FINISH_DONE_OTA_END, (uint32_t)((t1_us - t0_us) / 1000));
int64_t t2_us = esp_timer_get_time(); int64_t t2_us = esp_timer_get_time();
DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_set_boot_partition()"); DEBUG_INFO(TAG, "[FINISH] 即将执行 esp_ota_set_boot_partition()");
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_SET_BOOT, 0); sendFinishEvent(BLE_OTA_FINISH_BEGIN_SET_BOOT, 0);
err = esp_ota_set_boot_partition(s_esp32_update_partition); err = esp_ota_set_boot_partition(BleOta::instance().m_esp32UpdatePartition);
int64_t t3_us = esp_timer_get_time(); int64_t t3_us = esp_timer_get_time();
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "[FINISH] 设置启动分区失败: %s (耗时=%lld ms)", DEBUG_ERR(TAG, "[FINISH] 设置启动分区失败: %s (耗时=%lld ms)",
esp_err_to_name(err), (long long)((t3_us - t2_us) / 1000)); esp_err_to_name(err), (long long)((t3_us - t2_us) / 1000));
ble_ota_send_finish_event(BLE_OTA_FINISH_ERR_SET_BOOT, (uint32_t)err); sendFinishEvent(BLE_OTA_FINISH_ERR_SET_BOOT, (uint32_t)err);
s_esp32_ota_active = false; BleOta::instance().m_esp32OtaActive = false;
s_esp32_ota_handle = 0; BleOta::instance().m_esp32OtaHandle = 0;
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
s_esp32_received = 0; BleOta::instance().m_esp32Received = 0;
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
vTaskDelete(NULL); vTaskDelete(NULL);
return; return;
} }
DEBUG_INFO(TAG, "[FINISH] esp_ota_set_boot_partition 完成 (耗时=%lld ms)", DEBUG_INFO(TAG, "[FINISH] esp_ota_set_boot_partition 完成 (耗时=%lld ms)",
(long long)((t3_us - t2_us) / 1000)); (long long)((t3_us - t2_us) / 1000));
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_SET_BOOT, (uint32_t)((t3_us - t2_us) / 1000)); sendFinishEvent(BLE_OTA_FINISH_DONE_SET_BOOT, (uint32_t)((t3_us - t2_us) / 1000));
s_esp32_ota_active = false; BleOta::instance().m_esp32OtaActive = false;
s_esp32_ota_handle = 0; BleOta::instance().m_esp32OtaHandle = 0;
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
s_esp32_received = 0; BleOta::instance().m_esp32Received = 0;
DEBUG_INFO(TAG, "ESP32 OTA 收尾完成,800ms 后重启..."); DEBUG_INFO(TAG, "ESP32 OTA 收尾完成,800ms 后重启...");
int64_t t4_us = esp_timer_get_time(); int64_t t4_us = esp_timer_get_time();
DEBUG_INFO(TAG, "[FINISH] 即将进入 800ms 重启前等待"); DEBUG_INFO(TAG, "[FINISH] 即将进入 800ms 重启前等待");
ble_ota_send_finish_event(BLE_OTA_FINISH_BEGIN_DELAY, 800); sendFinishEvent(BLE_OTA_FINISH_BEGIN_DELAY, 800);
vTaskDelay(pdMS_TO_TICKS(800)); vTaskDelay(pdMS_TO_TICKS(800));
int64_t t5_us = esp_timer_get_time(); int64_t t5_us = esp_timer_get_time();
DEBUG_INFO(TAG, "[FINISH] 800ms 等待结束 (实际耗时=%lld ms)", DEBUG_INFO(TAG, "[FINISH] 800ms 等待结束 (实际耗时=%lld ms)",
(long long)((t5_us - t4_us) / 1000)); (long long)((t5_us - t4_us) / 1000));
ble_ota_send_finish_event(BLE_OTA_FINISH_DONE_DELAY, (uint32_t)((t5_us - t4_us) / 1000)); sendFinishEvent(BLE_OTA_FINISH_DONE_DELAY, (uint32_t)((t5_us - t4_us) / 1000));
DEBUG_INFO(TAG, "ESP32 即将重启"); DEBUG_INFO(TAG, "ESP32 即将重启");
ble_ota_send_finish_event(BLE_OTA_FINISH_REBOOT_NOW, 0); sendFinishEvent(BLE_OTA_FINISH_REBOOT_NOW, 0);
esp_restart(); esp_restart();
} }
@@ -194,56 +193,56 @@ static esp_err_t ble_esp32_ota_begin(size_t total_size)
DEBUG_INFO(TAG, "当前启动分区: %s, size=%u, address=0x%lx", DEBUG_INFO(TAG, "当前启动分区: %s, size=%u, address=0x%lx",
boot_partition->label, boot_partition->size, boot_partition->address); boot_partition->label, boot_partition->size, boot_partition->address);
if (s_esp32_ota_active) { if (BleOta::instance().m_esp32OtaActive) {
DEBUG_WARN(TAG, "检测到未完成的 ESP32 OTA,会先中止旧会话"); DEBUG_WARN(TAG, "检测到未完成的 ESP32 OTA,会先中止旧会话");
esp_ota_abort(s_esp32_ota_handle); esp_ota_abort(BleOta::instance().m_esp32OtaHandle);
s_esp32_ota_active = false; BleOta::instance().m_esp32OtaActive = false;
s_esp32_ota_handle = 0; BleOta::instance().m_esp32OtaHandle = 0;
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
s_esp32_received = 0; BleOta::instance().m_esp32Received = 0;
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
s_esp32_last_seq = 0xFFFFU; BleOta::instance().m_esp32LastSeq = 0xFFFFU;
} }
s_esp32_update_partition = esp_partition_find_first( BleOta::instance().m_esp32UpdatePartition = esp_partition_find_first(
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL); ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
if (s_esp32_update_partition == NULL || if (BleOta::instance().m_esp32UpdatePartition == NULL ||
s_esp32_update_partition->address == boot_partition->address) { BleOta::instance().m_esp32UpdatePartition->address == boot_partition->address) {
s_esp32_update_partition = esp_partition_find_first( BleOta::instance().m_esp32UpdatePartition = esp_partition_find_first(
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL); ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
} }
if (s_esp32_update_partition == NULL) { if (BleOta::instance().m_esp32UpdatePartition == NULL) {
DEBUG_ERR(TAG, "找不到可用的 OTA 分区"); DEBUG_ERR(TAG, "找不到可用的 OTA 分区");
return ESP_FAIL; return ESP_FAIL;
} }
DEBUG_INFO(TAG, "ESP32 OTA 目标分区: %s, size=%u, address=0x%lx", DEBUG_INFO(TAG, "ESP32 OTA 目标分区: %s, size=%u, address=0x%lx",
s_esp32_update_partition->label, BleOta::instance().m_esp32UpdatePartition->label,
s_esp32_update_partition->size, BleOta::instance().m_esp32UpdatePartition->size,
s_esp32_update_partition->address); BleOta::instance().m_esp32UpdatePartition->address);
if (total_size > s_esp32_update_partition->size) { if (total_size > BleOta::instance().m_esp32UpdatePartition->size) {
DEBUG_ERR(TAG, "ESP32 固件太大: %u > 分区大小: %u", DEBUG_ERR(TAG, "ESP32 固件太大: %u > 分区大小: %u",
total_size, s_esp32_update_partition->size); total_size, BleOta::instance().m_esp32UpdatePartition->size);
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
return ESP_FAIL; return ESP_FAIL;
} }
esp_err_t err = esp_ota_begin(s_esp32_update_partition, total_size, &s_esp32_ota_handle); esp_err_t err = esp_ota_begin(BleOta::instance().m_esp32UpdatePartition, total_size, &BleOta::instance().m_esp32OtaHandle);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err)); DEBUG_ERR(TAG, "esp_ota_begin 失败: %s", esp_err_to_name(err));
s_esp32_update_partition = NULL; BleOta::instance().m_esp32UpdatePartition = NULL;
s_esp32_ota_handle = 0; BleOta::instance().m_esp32OtaHandle = 0;
return err; return err;
} }
s_esp32_received = 0; BleOta::instance().m_esp32Received = 0;
s_esp32_ota_active = true; BleOta::instance().m_esp32OtaActive = true;
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
s_esp32_last_cmd = 0; BleOta::instance().m_esp32LastCmd = 0;
s_esp32_last_seq = 0xFFFFU; BleOta::instance().m_esp32LastSeq = 0xFFFFU;
DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s", s_esp32_update_partition->label); DEBUG_INFO(TAG, "ESP32 OTA 开始,目标分区: %s", BleOta::instance().m_esp32UpdatePartition->label);
return ESP_OK; return ESP_OK;
} }
@@ -255,12 +254,12 @@ static esp_err_t ble_esp32_ota_begin(size_t total_size)
*/ */
static esp_err_t ble_esp32_ota_write(const uint8_t *data, size_t len) static esp_err_t ble_esp32_ota_write(const uint8_t *data, size_t len)
{ {
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) { if (!BleOta::instance().m_esp32OtaActive || BleOta::instance().m_esp32UpdatePartition == NULL) {
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
esp_err_t err = esp_ota_write(s_esp32_ota_handle, data, len); esp_err_t err = esp_ota_write(BleOta::instance().m_esp32OtaHandle, data, len);
if (err == ESP_OK) { if (err == ESP_OK) {
s_esp32_received += len; BleOta::instance().m_esp32Received += len;
} }
return err; return err;
} }
@@ -271,7 +270,7 @@ static esp_err_t ble_esp32_ota_write(const uint8_t *data, size_t len)
* @param seq 当前确认的分块序号。 * @param seq 当前确认的分块序号。
* @param completed_bytes 已完成写入的累计字节数。 * @param completed_bytes 已完成写入的累计字节数。
*/ */
static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t completed_bytes) static void sendWindowAck(uint8_t cmd, uint16_t seq, uint32_t completed_bytes)
{ {
uint8_t ack[8] = {0}; uint8_t ack[8] = {0};
ack[0] = BLE_OTA_NOTIFY_ACK; ack[0] = BLE_OTA_NOTIFY_ACK;
@@ -283,7 +282,7 @@ static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t complete
ack[6] = (uint8_t)((completed_bytes >> 16) & 0xFFU); ack[6] = (uint8_t)((completed_bytes >> 16) & 0xFFU);
ack[7] = (uint8_t)((completed_bytes >> 24) & 0xFFU); ack[7] = (uint8_t)((completed_bytes >> 24) & 0xFFU);
esp_err_t err = ble_ota_notify_response(ack, sizeof(ack)); esp_err_t err = BleOta::instance().notifyResponse(ack, sizeof(ack));
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_WARN(TAG, "发送 ACK 失败: cmd=0x%02X seq=%u err=%s", DEBUG_WARN(TAG, "发送 ACK 失败: cmd=0x%02X seq=%u err=%s",
cmd, seq, esp_err_to_name(err)); cmd, seq, esp_err_to_name(err));
@@ -298,7 +297,7 @@ static void ble_ota_send_window_ack(uint8_t cmd, uint16_t seq, uint32_t complete
* @param stage 阶段标识码。 * @param stage 阶段标识码。
* @param value 阶段附带值(错误码或耗时等)。 * @param value 阶段附带值(错误码或耗时等)。
*/ */
static void ble_ota_send_finish_event(uint8_t stage, uint32_t value) static void sendFinishEvent(uint8_t stage, uint32_t value)
{ {
uint8_t pkt[6] = {0}; uint8_t pkt[6] = {0};
pkt[0] = BLE_OTA_NOTIFY_FINISH; pkt[0] = BLE_OTA_NOTIFY_FINISH;
@@ -307,7 +306,7 @@ static void ble_ota_send_finish_event(uint8_t stage, uint32_t value)
pkt[3] = (uint8_t)((value >> 8) & 0xFFU); pkt[3] = (uint8_t)((value >> 8) & 0xFFU);
pkt[4] = (uint8_t)((value >> 16) & 0xFFU); pkt[4] = (uint8_t)((value >> 16) & 0xFFU);
pkt[5] = (uint8_t)((value >> 24) & 0xFFU); pkt[5] = (uint8_t)((value >> 24) & 0xFFU);
esp_err_t err = ble_ota_notify_response(pkt, sizeof(pkt)); esp_err_t err = BleOta::instance().notifyResponse(pkt, sizeof(pkt));
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_WARN(TAG, "发送 FINISH 事件失败: stage=0x%02X err=%s", DEBUG_WARN(TAG, "发送 FINISH 事件失败: stage=0x%02X err=%s",
stage, esp_err_to_name(err)); stage, esp_err_to_name(err));
@@ -354,28 +353,28 @@ static int ble_ota_ctrl_access(uint16_t conn_handle, uint16_t attr_handle,
if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) { if (buf[0] == BLE_OTA_CTRL_TRIGGER_GD32) {
// 检查固件是否存储完毕 → 触发 GD32 OTA 中继 // 检查固件是否存储完毕 → 触发 GD32 OTA 中继
ota_meta_t meta; ota_meta_t meta;
esp_err_t err = meta_load(&meta); esp_err_t err = OtaRelay::metaLoad(&meta);
if (err == ESP_OK && if (err == ESP_OK &&
meta.total_size > 0 && meta.total_size > 0 &&
meta.stored_size >= meta.total_size) { meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继", DEBUG_INFO(TAG, "GD32 固件就绪 (%" PRIu32 " / %" PRIu32 " 字节),触发中继",
meta.stored_size, meta.total_size); meta.stored_size, meta.total_size);
ble_ota_signal_relay(); BleOta::instance().signalRelay();
} else { } else {
DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")", DEBUG_WARN(TAG, "GD32 固件未就绪 (stored=%" PRIu32 ", total=%" PRIu32 ")",
meta.stored_size, meta.total_size); meta.stored_size, meta.total_size);
} }
} else if (buf[0] == BLE_OTA_CTRL_TRIGGER_ESP32) { } else if (buf[0] == BLE_OTA_CTRL_TRIGGER_ESP32) {
if (!s_esp32_ota_active || s_esp32_update_partition == NULL) { if (!BleOta::instance().m_esp32OtaActive || BleOta::instance().m_esp32UpdatePartition == NULL) {
DEBUG_WARN(TAG, "ESP32 OTA 未激活,无法触发升级"); DEBUG_WARN(TAG, "ESP32 OTA 未激活,无法触发升级");
return 0; return 0;
} }
if (s_esp32_reboot_pending) { if (BleOta::instance().m_esp32RebootPending) {
DEBUG_WARN(TAG, "ESP32 OTA 收尾已在进行中,忽略重复 CTRL"); DEBUG_WARN(TAG, "ESP32 OTA 收尾已在进行中,忽略重复 CTRL");
return 0; return 0;
} }
s_esp32_reboot_pending = true; BleOta::instance().m_esp32RebootPending = true;
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task, BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
"ble_ota_finish", "ble_ota_finish",
4096, 4096,
@@ -384,7 +383,7 @@ static int ble_ota_ctrl_access(uint16_t conn_handle, uint16_t attr_handle,
NULL); NULL);
if (ok != pdPASS) { if (ok != pdPASS) {
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败"); DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
} else { } else {
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建(由设备端异步完成)"); DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建(由设备端异步完成)");
} }
@@ -485,21 +484,21 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
} }
DEBUG_DBG(TAG, "ESP32 OTA 写入成功: seq=%u received=%u", DEBUG_DBG(TAG, "ESP32 OTA 写入成功: seq=%u received=%u",
seq, (unsigned int)s_esp32_received); seq, (unsigned int)BleOta::instance().m_esp32Received);
if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U || if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U ||
(total_size > 0 && s_esp32_received >= total_size)) { (total_size > 0 && BleOta::instance().m_esp32Received >= total_size)) {
ble_ota_send_window_ack(cmd, seq, (uint32_t)s_esp32_received); sendWindowAck(cmd, seq, (uint32_t)BleOta::instance().m_esp32Received);
} }
if (total_size > 0 && s_esp32_received >= total_size) { if (total_size > 0 && BleOta::instance().m_esp32Received >= total_size) {
DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)", DEBUG_INFO(TAG, "★ ESP32 固件接收完毕: %u / %" PRIu32 " 字节 (共 %u 块)",
(unsigned int)s_esp32_received, total_size, seq + 1); (unsigned int)BleOta::instance().m_esp32Received, total_size, seq + 1);
s_esp32_last_cmd = cmd; BleOta::instance().m_esp32LastCmd = cmd;
s_esp32_last_seq = seq; BleOta::instance().m_esp32LastSeq = seq;
if (!s_esp32_reboot_pending) { if (!BleOta::instance().m_esp32RebootPending) {
s_esp32_reboot_pending = true; BleOta::instance().m_esp32RebootPending = true;
BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task, BaseType_t ok = xTaskCreate(ble_esp32_finish_and_reboot_task,
"ble_ota_finish", "ble_ota_finish",
4096, 4096,
@@ -508,7 +507,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
NULL); NULL);
if (ok != pdPASS) { if (ok != pdPASS) {
DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败"); DEBUG_ERR(TAG, "创建 ESP32 OTA 收尾任务失败");
s_esp32_reboot_pending = false; BleOta::instance().m_esp32RebootPending = false;
return BLE_ATT_ERR_UNLIKELY; return BLE_ATT_ERR_UNLIKELY;
} }
DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建,等待设备端自动重启"); DEBUG_INFO(TAG, "ESP32 OTA 收尾任务已创建,等待设备端自动重启");
@@ -534,7 +533,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
meta.last_seq = 0; meta.last_seq = 0;
meta.chunk_count = (total_size + (uint32_t)data_len - 1U) / (uint32_t)data_len; meta.chunk_count = (total_size + (uint32_t)data_len - 1U) / (uint32_t)data_len;
esp_err_t err = meta_save(&meta); esp_err_t err = OtaRelay::metaSave(&meta);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "元数据保存失败"); DEBUG_ERR(TAG, "元数据保存失败");
return BLE_ATT_ERR_UNLIKELY; return BLE_ATT_ERR_UNLIKELY;
@@ -545,7 +544,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
} }
ota_meta_t meta = {}; ota_meta_t meta = {};
if (meta_load(&meta) != ESP_OK || meta.total_size == 0) { if (OtaRelay::metaLoad(&meta) != ESP_OK || meta.total_size == 0) {
DEBUG_ERR(TAG, "GD32 元数据无效,拒绝写入: seq=%u", seq); DEBUG_ERR(TAG, "GD32 元数据无效,拒绝写入: seq=%u", seq);
return BLE_ATT_ERR_UNLIKELY; return BLE_ATT_ERR_UNLIKELY;
} }
@@ -565,7 +564,7 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
return BLE_ATT_ERR_UNLIKELY; return BLE_ATT_ERR_UNLIKELY;
} }
esp_err_t err = flash_write_chunk(flash_offset, &buf[data_offset], data_len); esp_err_t err = OtaRelay::flashWriteChunk(flash_offset, &buf[data_offset], data_len);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "Flash 写入失败: seq=%u offset=%" PRIu32 " err=%s", DEBUG_ERR(TAG, "Flash 写入失败: seq=%u offset=%" PRIu32 " err=%s",
seq, flash_offset, esp_err_to_name(err)); seq, flash_offset, esp_err_to_name(err));
@@ -573,21 +572,21 @@ static int ble_ota_data_in_access(uint16_t conn_handle, uint16_t attr_handle,
} }
uint32_t stored_size = flash_offset + (uint32_t)data_len; uint32_t stored_size = flash_offset + (uint32_t)data_len;
err = meta_update(stored_size, seq); err = OtaRelay::metaUpdate(stored_size, seq);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_WARN(TAG, "元数据更新失败 (seq=%u)", seq); DEBUG_WARN(TAG, "元数据更新失败 (seq=%u)", seq);
} }
if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U || if ((((uint32_t)seq + 1U) % BLE_OTA_ACK_WINDOW) == 0U ||
(meta.total_size > 0 && stored_size >= meta.total_size)) { (meta.total_size > 0 && stored_size >= meta.total_size)) {
ble_ota_send_window_ack(cmd, seq, stored_size); sendWindowAck(cmd, seq, stored_size);
} }
DEBUG_DBG(TAG, "Flash 写入成功: seq=%u offset=%" PRIu32 " stored=%" PRIu32, DEBUG_DBG(TAG, "Flash 写入成功: seq=%u offset=%" PRIu32 " stored=%" PRIu32,
seq, flash_offset, stored_size); seq, flash_offset, stored_size);
// --- 检查 GD32 固件是否接收完毕 --- // --- 检查 GD32 固件是否接收完毕 ---
if (meta_load(&meta) == ESP_OK && if (OtaRelay::metaLoad(&meta) == ESP_OK &&
meta.total_size > 0 && meta.total_size > 0 &&
meta.stored_size >= meta.total_size) { meta.stored_size >= meta.total_size) {
DEBUG_INFO(TAG, "★ GD32 固件接收完毕: %" PRIu32 " / %" PRIu32 " 字节 (共 %u 块)", DEBUG_INFO(TAG, "★ GD32 固件接收完毕: %" PRIu32 " / %" PRIu32 " 字节 (共 %u 块)",
@@ -627,7 +626,7 @@ static int ble_ota_version_access(uint16_t conn_handle, uint16_t attr_handle,
/* /*
* DATA_OUT (0x0003) - Notify only * DATA_OUT (0x0003) - Notify only
* 访问回调不做读写处理,通知由 ble_ota_notify_response() 发送 * 访问回调不做读写处理,通知由 BleOta::instance().notifyResponse() 发送
*/ */
/** /**
* @brief GATT DATA_OUT 特征访问回调(Notify-only,占位实现)。 * @brief GATT DATA_OUT 特征访问回调(Notify-only,占位实现)。
@@ -727,10 +726,9 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
case BLE_GAP_EVENT_CONNECT: case BLE_GAP_EVENT_CONNECT:
if (event->connect.status == 0) { if (event->connect.status == 0) {
// 连接成功 // 连接成功
s_ble_connected = true; BleOta::instance().m_bleConnected = true;
/** @brief 当前 BLE 连接句柄。 */ BleOta::instance().m_connHandle = event->connect.conn_handle;
s_conn_handle = event->connect.conn_handle; DEBUG_INFO(TAG, "BLE 客户端已连接: handle=%d", BleOta::instance().m_connHandle);
DEBUG_INFO(TAG, "BLE 客户端已连接: handle=%d", s_conn_handle);
// 向对端请求更快的连接参数,提升 OTA 吞吐 // 向对端请求更快的连接参数,提升 OTA 吞吐
struct ble_gap_upd_params up = {}; struct ble_gap_upd_params up = {};
@@ -740,7 +738,7 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
up.supervision_timeout = 2000; // 2 秒监控超时 up.supervision_timeout = 2000; // 2 秒监控超时
up.min_ce_len = 0; // 控制器自行决定 up.min_ce_len = 0; // 控制器自行决定
up.max_ce_len = 0; up.max_ce_len = 0;
int rc = ble_gap_update_params(s_conn_handle, &up); int rc = ble_gap_update_params(BleOta::instance().m_connHandle, &up);
if (rc != 0) { if (rc != 0) {
DEBUG_WARN(TAG, "请求更新连接参数失败: rc=%d(对端不一定接受,继续运行)", rc); DEBUG_WARN(TAG, "请求更新连接参数失败: rc=%d(对端不一定接受,继续运行)", rc);
} else { } else {
@@ -750,7 +748,7 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
} else { } else {
DEBUG_WARN(TAG, "BLE 连接失败: status=%d", event->connect.status); DEBUG_WARN(TAG, "BLE 连接失败: status=%d", event->connect.status);
/** @brief BLE GATT 客户端连接状态。 */ /** @brief BLE GATT 客户端连接状态。 */
s_ble_connected = false; BleOta::instance().m_bleConnected = false;
} }
return 0; return 0;
@@ -758,9 +756,8 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d", DEBUG_INFO(TAG, "BLE 客户端已断开: reason=%d",
event->disconnect.reason); event->disconnect.reason);
/** @brief BLE GATT 客户端连接状态。 */ /** @brief BLE GATT 客户端连接状态。 */
s_ble_connected = false; BleOta::instance().m_bleConnected = false;
/** @brief 当前 BLE 连接句柄。 */ BleOta::instance().m_connHandle = BLE_HS_CONN_HANDLE_NONE;
s_conn_handle = BLE_HS_CONN_HANDLE_NONE;
// 恢复广播以接受新连接 // 恢复广播以接受新连接
ble_ota_start_advertising(); ble_ota_start_advertising();
return 0; return 0;
@@ -794,7 +791,7 @@ static int ble_ota_gap_event(struct ble_gap_event *event, void *arg)
/** /**
* @brief 启动 BLE 广播,使设备可被发现和连接。 * @brief 启动 BLE 广播,使设备可被发现和连接。
*/ */
static void ble_ota_start_advertising(void) static void ble_ota_start_advertising(void) // kept as free function for BLE callback compatibility
{ {
int rc; int rc;
@@ -883,10 +880,10 @@ static void ble_ota_on_sync(void)
{ {
DEBUG_INFO(TAG, "NimBLE 协议栈同步完成"); DEBUG_INFO(TAG, "NimBLE 协议栈同步完成");
// GATT 服务已在 ble_ota_server_start() 中注册,不再重复注册 // GATT 服务已在 serverStart() 中注册,不再重复注册
// 重复调用 ble_gatts_count_cfg / ble_gatts_add_svcs 会导致 BLE_HS_EBUSY // 重复调用 ble_gatts_count_cfg / ble_gatts_add_svcs 会导致 BLE_HS_EBUSY
s_ble_ready = true; BleOta::instance().m_bleReady = true;
ble_ota_start_advertising(); ble_ota_start_advertising();
} }
@@ -898,7 +895,7 @@ static void ble_ota_on_reset(int reason)
{ {
DEBUG_WARN(TAG, "NimBLE 协议栈重置: reason=%d", reason); DEBUG_WARN(TAG, "NimBLE 协议栈重置: reason=%d", reason);
/** @brief NimBLE 协议栈同步完成标志。 */ /** @brief NimBLE 协议栈同步完成标志。 */
s_ble_ready = false; BleOta::instance().m_bleReady = false;
} }
// ============================================================ // ============================================================
@@ -924,7 +921,7 @@ static void ble_ota_nimble_host_task(void *param)
* @brief 启动 BLE GATT 服务器并注册 OTA 服务。 * @brief 启动 BLE GATT 服务器并注册 OTA 服务。
* @return ESP_OK 成功,其他失败。 * @return ESP_OK 成功,其他失败。
*/ */
esp_err_t ble_ota_server_start(void) esp_err_t BleOta::serverStart()
{ {
int rc; int rc;
@@ -976,6 +973,8 @@ esp_err_t ble_ota_server_start(void)
nimble_port_freertos_init(ble_ota_nimble_host_task); nimble_port_freertos_init(ble_ota_nimble_host_task);
// Sync val_handle to class member
BleOta::instance().mDataOutValHandle = s_data_out_val_handle;
DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成"); DEBUG_INFO(TAG, "BLE OTA 服务器初始化完成");
return ESP_OK; return ESP_OK;
} }
@@ -986,12 +985,12 @@ esp_err_t ble_ota_server_start(void)
* @param len 数据长度(字节)。 * @param len 数据长度(字节)。
* @return ESP_OK 成功,其他失败。 * @return ESP_OK 成功,其他失败。
*/ */
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len) esp_err_t BleOta::notifyResponse(const uint8_t *data, size_t len)
{ {
if (!s_ble_connected) { if (!BleOta::instance().m_bleConnected) {
return ESP_ERR_NOT_SUPPORTED; return ESP_ERR_NOT_SUPPORTED;
} }
if (s_data_out_val_handle == 0) { if (BleOta::instance().mDataOutValHandle == 0) {
DEBUG_WARN(TAG, "DATA_OUT 值句柄未设置"); DEBUG_WARN(TAG, "DATA_OUT 值句柄未设置");
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
@@ -1004,7 +1003,7 @@ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len)
return ESP_ERR_NO_MEM; return ESP_ERR_NO_MEM;
} }
int rc = ble_gattc_notify_custom(s_conn_handle, s_data_out_val_handle, om); int rc = ble_gattc_notify_custom(BleOta::instance().m_connHandle, BleOta::instance().mDataOutValHandle, om);
if (rc != 0) { if (rc != 0) {
DEBUG_WARN(TAG, "通知发送失败: rc=%d", rc); DEBUG_WARN(TAG, "通知发送失败: rc=%d", rc);
return ESP_FAIL; return ESP_FAIL;
@@ -1018,24 +1017,24 @@ esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len)
* @brief 检查当前是否有 BLE 客户端连接。 * @brief 检查当前是否有 BLE 客户端连接。
* @return true 已连接,false 未连接。 * @return true 已连接,false 未连接。
*/ */
bool ble_ota_is_connected(void) bool BleOta::isConnected()
{ {
/** @brief BLE GATT 客户端连接状态。 */ /** @brief BLE GATT 客户端连接状态。 */
return s_ble_connected; return BleOta::instance().m_bleConnected;
} }
/** /**
* @brief 通知中继模块开始向 GD32 转发固件。 * @brief 通知中继模块开始向 GD32 转发固件。
*/ */
void ble_ota_signal_relay(void) void BleOta::signalRelay()
{ {
if (g_ota_dispatch_queue == NULL) { if (OtaRelay::dispatchQueue == NULL) {
DEBUG_ERR(TAG, "OTA 调度队列未初始化"); DEBUG_ERR(TAG, "OTA 调度队列未初始化");
return; return;
} }
uint32_t msg = 1; uint32_t msg = 1;
BaseType_t ret = xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(200)); BaseType_t ret = xQueueSend(OtaRelay::dispatchQueue, &msg, pdMS_TO_TICKS(200));
if (ret != pdTRUE) { if (ret != pdTRUE) {
DEBUG_ERR(TAG, "OTA 调度信号发送失败: 队列满"); DEBUG_ERR(TAG, "OTA 调度信号发送失败: 队列满");
return; return;
@@ -1049,18 +1048,18 @@ void ble_ota_signal_relay(void)
// //
// 注意: 此任务的职责是初始化 BLE 服务器并保持存活。 // 注意: 此任务的职责是初始化 BLE 服务器并保持存活。
// 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。 // 实际的 BLE 事件循环在独立的 NimBLE Host 任务中运行。
// 为避免与 ota_relay_task 竞争,此任务不从 g_ota_dispatch_queue 消费。 // 为避免与 ota_relay_task 竞争,此任务不从 OtaRelay::dispatchQueue 消费。
// ============================================================ // ============================================================
/** /**
* @brief BLE OTA FreeRTOS 任务,初始化 NimBLE 并运行 GATT 服务。 * @brief BLE OTA FreeRTOS 任务,初始化 NimBLE 并运行 GATT 服务。
* @param pvParameters FreeRTOS 任务参数,未使用。 * @param pvParameters FreeRTOS 任务参数,未使用。
*/ */
void ble_ota_task(void *pvParameters) void BleOta::taskEntry(void *pvParameters)
{ {
(void)pvParameters; (void)pvParameters;
DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ==="); DEBUG_INFO(TAG, "=== BLE OTA 任务启动 ===");
esp_err_t err = ble_ota_server_start(); esp_err_t err = BleOta::instance().serverStart();
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用", DEBUG_ERR(TAG, "BLE 服务器启动失败: %s → BLE OTA 不可用",
esp_err_to_name(err)); esp_err_to_name(err));
@@ -1075,8 +1074,8 @@ void ble_ota_task(void *pvParameters)
if (heartbeat % 6 == 0) { if (heartbeat % 6 == 0) {
DEBUG_DBG(TAG, "心跳 #%lu | BLE %s | 连接: %s", DEBUG_DBG(TAG, "心跳 #%lu | BLE %s | 连接: %s",
(unsigned long)(heartbeat / 6), (unsigned long)(heartbeat / 6),
s_ble_ready ? "就绪" : "未就绪", BleOta::instance().m_bleReady ? "就绪" : "未就绪",
s_ble_connected ? "已连接" : "未连接"); BleOta::instance().m_bleConnected ? "已连接" : "未连接");
} }
vTaskDelay(pdMS_TO_TICKS(10000)); vTaskDelay(pdMS_TO_TICKS(10000));
+25 -29
View File
@@ -1,41 +1,37 @@
/* /*
* ble_ota.h - BLE OTA GATT 服务器 * ble_ota.h - BLE OTA GATT 服务器
* T5.1 - T5.5 * T5.1 - T5.5
*/ */
#ifndef BLE_OTA_H #ifndef BLE_OTA_H
#define BLE_OTA_H #define BLE_OTA_H
#include "ota_relay.h" #include <stdint.h>
#include "esp_err.h"
#include "esp_ota_ops.h"
/** class BleOta {
* @brief BLE OTA 任务入口,负责初始化 NimBLE 并运行 GATT OTA 服务。 public:
* @param pvParameters FreeRTOS 任务参数,未使用。 static BleOta& instance();
*/
void ble_ota_task(void *pvParameters);
/** static void taskEntry(void *pvParameters);
* @brief 启动 BLE GATT 服务器并注册 OTA 相关服务与特征。 esp_err_t serverStart();
* @return ESP_OK 成功,其他失败。 esp_err_t notifyResponse(const uint8_t *data, size_t len);
*/ bool isConnected();
esp_err_t ble_ota_server_start(void); void signalRelay();
/** // 以下状态为 public,供 C 回调函数访问
* @brief 通过 BLE Notify 将 GD32 的响应数据发送给上位机 APP。 bool m_bleReady = false;
* @param data 待推送的数据指针。 bool m_bleConnected = false;
* @param len 数据长度(字节)。 uint16_t m_connHandle = 0xFFFF;
* @return ESP_OK 成功,其他失败。 uint16_t mDataOutValHandle = 0;
*/
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len);
/** bool m_esp32OtaActive = false;
* @brief 检查当前是否存在已连接的 BLE 客户端。 esp_ota_handle_t m_esp32OtaHandle = 0;
* @return true 已连接,false 未连接。 const esp_partition_t *m_esp32UpdatePartition = NULL;
*/ size_t m_esp32Received = 0;
bool ble_ota_is_connected(void); bool m_esp32RebootPending = false;
uint8_t m_esp32LastCmd = 0;
/** uint16_t m_esp32LastSeq = 0xFFFFU;
* @brief 在固件存储完成后通知中继模块开始向 GD32 转发。 };
*/
void ble_ota_signal_relay(void);
#endif // BLE_OTA_H #endif // BLE_OTA_H
+191 -239
View File
File diff suppressed because it is too large Load Diff
+40 -34
View File
@@ -1,52 +1,58 @@
/* /*
* wifi_ota.h - WiFi OTA 服务器 * wifi_ota.h - WiFi OTA 服务器
* T4.1 - T4.5 * T4.1 - T4.5
*
* WifiOta 类封装 WiFi 初始化、HTTP OTA 服务器和网络状态查询。
*/ */
#ifndef WIFI_OTA_H #ifndef WIFI_OTA_H
#define WIFI_OTA_H #define WIFI_OTA_H
#include "ota_relay.h" #include "ota_relay.h"
#include "esp_ota_ops.h"
#include "esp_http_server.h"
#include "esp_event.h"
// WiFi 模式: AP 或 STA
typedef enum { typedef enum {
OTA_WIFI_MODE_AP, // AP 模式(ESP32 作为热点) OTA_WIFI_MODE_AP,
OTA_WIFI_MODE_STA // STA 模式(连接路由器) OTA_WIFI_MODE_STA
} ota_wifi_mode_t; } ota_wifi_mode_t;
/** class WifiOta {
* @brief WiFi OTA 任务入口,负责 HTTP 服务、上传处理和 OTA 触发流程。 public:
* @param pvParameters FreeRTOS 任务参数,未使用。 static WifiOta& instance();
*/
void wifi_ota_task(void *pvParameters);
// 在 app_main 早期执行 WiFi 初始化并尝试以 STA 模式联网。 bool earlyInit();
/** static void taskEntry(void *pvParameters);
* @brief 返回 true 表示 STA 初始化已开始,false 表示后续应回退到 AP 模式。 void signalRelay();
* @return true STA 初始化中,false 需回退 AP。
*/
bool wifi_early_init(void);
/** static ota_wifi_mode_t getMode();
* @brief 在固件存储完成后通知 OTA 中继开始向 GD32 转发。 static int8_t getRssi();
*/ static bool isConnected();
void wifi_ota_signal_relay(void);
/** // 以下状态为 public,供 C 回调函数访问
* @brief 获取当前 WiFi 工作模式(AP 或 STA)。 ota_wifi_mode_t m_wifiMode = OTA_WIFI_MODE_AP;
* @return 当前 WiFi 模式枚举值。 bool m_coldBoot = false;
*/ bool m_relayTriggered = false;
ota_wifi_mode_t wifi_get_mode(void); bool m_staConnected = false;
int m_staRetryCount = 0;
bool m_staRetryExhausted = false;
SemaphoreHandle_t m_staConnectedSem = NULL;
SemaphoreHandle_t m_staGotIpSem = NULL;
/** bool m_esp32OtaActive = false;
* @brief 获取当前 WiFi RSSI,未连接时通常返回无效/默认值。 esp_ota_handle_t m_esp32OtaHandle = 0;
* @return 信号强度(dBm),未连接时返回无效值。 const esp_partition_t *m_esp32UpdatePartition = NULL;
*/ size_t m_esp32Received = 0;
int8_t wifi_get_rssi(void); bool m_gd32StorageDone = false;
httpd_handle_t m_httpServer = NULL;
/** void initAp();
* @brief 判断当前 STA 模式下是否已经连接到上级 WiFi。 esp_err_t initSta();
* @return true 已连接,false 未连接。 void setStaticIp();
*/ static void wifiEventHandler(void *arg, esp_event_base_t event_base,
bool wifi_is_connected(void); int32_t event_id, void *event_data);
esp_err_t httpServerStart();
esp_err_t waitStaConnected(char *ip_str, size_t ip_len);
};
#endif // WIFI_OTA_H #endif // WIFI_OTA_H
+53 -91
View File
@@ -1,9 +1,11 @@
/* /*
* protocol.c - 协议层实现 * protocol.cpp - 协议层实现
* T2.1, T2.2, T1.3 * T2.1, T2.2, T1.3
* ESP-IDF 6.0 适配版本 - 使用硬件 AES-128-CBC + 硬件 RNG * ESP-IDF 6.0 适配版本 - 使用硬件 AES-128-CBC + 硬件 RNG
* *
* 加密实现使用 ESP32-C3 硬件 AES 加速器(通过 mbedtls port) * 加密实现使用 ESP32-C3 硬件 AES 加速器(通过 mbedtls port)
*
* 所有功能封装为 Protocol 类的静态方法。
*/ */
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@@ -13,31 +15,30 @@
#include "protocol.h" #include "protocol.h"
#include "monitor/debug_log.h" #include "monitor/debug_log.h"
/** @brief 协议层初始化标志。 */ // ============================================================
static bool s_protocol_initialized = false; // Protocol 单例
// ============================================================
/** Protocol& Protocol::instance()
* @brief 初始化协议层,准备 CRC 和 AES 模块。
*/
void protocol_init(void)
{ {
if (s_protocol_initialized) return; static Protocol inst;
return inst;
}
void Protocol::init()
{
if (m_initialized) return;
DEBUG_INFO("PROT", "=== 初始化协议层 ==="); DEBUG_INFO("PROT", "=== 初始化协议层 ===");
DEBUG_INFO("PROT", "协议层功能: CRC16 Modbus + 硬件 AES-128-CBC"); DEBUG_INFO("PROT", "协议层功能: CRC16 Modbus + 硬件 AES-128-CBC");
/** @brief 协议层初始化标志。 */ m_initialized = true;
s_protocol_initialized = true;
DEBUG_INFO("PROT", "协议层就绪"); DEBUG_INFO("PROT", "协议层就绪");
} }
/** /**
* @brief CRC16 Modbus(LE,低字节在前) * @brief CRC16 Modbus(LE,低字节在前)
* @param data 待校验的数据指针。
* @param len 数据长度(字节)。
* @return CRC16 校验值。
*/ */
uint16_t crc16_modbus(const uint8_t *data, size_t len) uint16_t Protocol::crc16Modbus(const uint8_t *data, size_t len)
{ {
uint16_t crc = 0xFFFF; uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
@@ -55,13 +56,8 @@ uint16_t crc16_modbus(const uint8_t *data, size_t len)
/** /**
* @brief 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE) * @brief 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE)
* @param cmd 命令码。
* @param payload 负载数据指针,可为 NULL。
* @param len 负载长度(字节)。
* @param out_frame 输出帧缓冲区。
* @return 帧总长度,失败返回 -1。
*/ */
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame) int Protocol::buildFrame(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame)
{ {
if (len > FRAME_MAX_PAYLOAD) { if (len > FRAME_MAX_PAYLOAD) {
DEBUG_ERR("PROT", "Payload 太大: %zu > %d", len, FRAME_MAX_PAYLOAD); DEBUG_ERR("PROT", "Payload 太大: %zu > %d", len, FRAME_MAX_PAYLOAD);
@@ -79,7 +75,7 @@ int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_fr
idx += len; idx += len;
} }
uint16_t crc = crc16_modbus(out_frame, idx); uint16_t crc = crc16Modbus(out_frame, idx);
out_frame[idx++] = (uint8_t)(crc & 0xFF); out_frame[idx++] = (uint8_t)(crc & 0xFF);
out_frame[idx++] = (uint8_t)((crc >> 8) & 0xFF); out_frame[idx++] = (uint8_t)((crc >> 8) & 0xFF);
@@ -90,15 +86,9 @@ int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_fr
/** /**
* @brief 解析并校验一帧 SPI 协议数据。 * @brief 解析并校验一帧 SPI 协议数据。
* @param frame 输入帧缓冲区。
* @param frame_len 输入帧长度。
* @param out_cmd 输出命令字节,可为 NULL。
* @param out_payload 输出负载缓冲区,可为 NULL。
* @param out_len 输出负载长度指针,可为 NULL。
* @return 0 成功,-1 失败。
*/ */
int frame_parse(const uint8_t *frame, size_t frame_len, int Protocol::parseFrame(const uint8_t *frame, size_t frame_len,
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len) uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len)
{ {
if (frame_len < 6) { if (frame_len < 6) {
DEBUG_WARN("PROT", "帧太短: %zu < 6", frame_len); DEBUG_WARN("PROT", "帧太短: %zu < 6", frame_len);
@@ -129,7 +119,7 @@ int frame_parse(const uint8_t *frame, size_t frame_len,
} }
uint16_t received_crc = frame[idx + payload_len] | (frame[idx + payload_len + 1] << 8); uint16_t received_crc = frame[idx + payload_len] | (frame[idx + payload_len + 1] << 8);
uint16_t calc_crc = crc16_modbus(frame, idx + payload_len); uint16_t calc_crc = crc16Modbus(frame, idx + payload_len);
if (received_crc != calc_crc) { if (received_crc != calc_crc) {
DEBUG_WARN("PROT", "CRC 不匹配: 收到=0x%04X, 计算=0x%04X", received_crc, calc_crc); DEBUG_WARN("PROT", "CRC 不匹配: 收到=0x%04X, 计算=0x%04X", received_crc, calc_crc);
@@ -151,14 +141,8 @@ int frame_parse(const uint8_t *frame, size_t frame_len,
/** /**
* @brief 使用 AES-128 CBC 对明文进行加密(PKCS#7 填充)。 * @brief 使用 AES-128 CBC 对明文进行加密(PKCS#7 填充)。
* @param plaintext 明文数据指针。
* @param len 明文长度。
* @param key 16 字节密钥。
* @param iv 16 字节初始向量。
* @param ciphertext_out 输出密文缓冲区。
* @return >=0 为密文长度,-1 表示失败。
*/ */
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len, int Protocol::aesEncrypt(const uint8_t *plaintext, size_t len,
const uint8_t *key, const uint8_t *iv, const uint8_t *key, const uint8_t *iv,
uint8_t *ciphertext_out) uint8_t *ciphertext_out)
{ {
@@ -168,15 +152,18 @@ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
} }
// PKCS#7 填充 // PKCS#7 填充
size_t padded_len = ((len + 15) / 16) * 16; size_t padded_len = ((len / 16) + 1) * 16;
uint8_t padded[256]; uint8_t padded[FRAME_MAX_PAYLOAD + 16];
memset(padded, 0, sizeof(padded)); if (padded_len > sizeof(padded)) {
memcpy(padded, plaintext, len); DEBUG_ERR("PROT", "填充后长度超出缓冲区: %zu", padded_len);
uint8_t pad = (uint8_t)(16 - (len % 16)); return -1;
if (pad == 0) pad = 16; }
memset(&padded[len], pad, pad);
DEBUG_DBG("PROT", "AES 加密: 明文=%zu 字节, 填充后=%zu 字节, 填充值=0x%02X", len, padded_len, pad); memcpy(padded, plaintext, len);
uint8_t pad_val = (uint8_t)(padded_len - len);
memset(&padded[len], pad_val, pad_val);
DEBUG_DBG("PROT", "AES 加密: 明文=%zu 字节, 填充后=%zu 字节", len, padded_len);
// 硬件 AES-CBC 加密(使用 ESP32-C3 硬件 AES 加速器) // 硬件 AES-CBC 加密(使用 ESP32-C3 硬件 AES 加速器)
esp_aes_context aes_ctx; esp_aes_context aes_ctx;
@@ -196,25 +183,20 @@ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
esp_aes_free(&aes_ctx); esp_aes_free(&aes_ctx);
if (ret == 0) { if (ret != 0) {
DEBUG_DBG("PROT", "AES 加密成功: %zu -> %zu 字节", len, padded_len);
return padded_len;
} else {
DEBUG_ERR("PROT", "esp_aes_crypt_cbc 加密失败: %d", ret); DEBUG_ERR("PROT", "esp_aes_crypt_cbc 加密失败: %d", ret);
return -1; return -1;
} }
DEBUG_DBG("PROT", "AES 加密成功: %zu -> %zu 字节", len, padded_len);
return padded_len;
} }
/** /**
* @brief 使用 AES-128 CBC 解密密文并移除 PKCS#7 填充。 * @brief 使用 AES-128 CBC 对密文进行解密(去除 PKCS#7 填充)。
* @param ciphertext 密文数据指针。
* @param len 密文长度(必须是 16 的倍数)。
* @param key 16 字节密钥。
* @param iv 16 字节初始向量。
* @param plaintext_out 输出明文缓冲区。
* @return >=0 为明文长度,-1 表示失败。
*/ */
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len, int Protocol::aesDecrypt(const uint8_t *ciphertext, size_t len,
const uint8_t *key, const uint8_t *iv, const uint8_t *key, const uint8_t *iv,
uint8_t *plaintext_out) uint8_t *plaintext_out)
{ {
@@ -268,13 +250,9 @@ int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
/** /**
* @brief 根据时间戳、地址和随机种子生成 16 字节 nonce。 * @brief 根据时间戳、地址和随机种子生成 16 字节 nonce。
* @param timestamp 时间戳输入。
* @param mem_addr 地址输入。
* @param rand_seed 随机种子输入。
* @param nonce_out 输出 nonce 缓冲区(16 字节)。
*/ */
void scramble_generate(uint32_t timestamp, uint32_t mem_addr, void Protocol::scrambleGenerate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out) uint32_t rand_seed, uint8_t *nonce_out)
{ {
uint8_t state[16]; uint8_t state[16];
@@ -302,11 +280,8 @@ void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
/** /**
* @brief 会话密钥初始化 * @brief 会话密钥初始化
* @param sess 待初始化的会话上下文。
* @param k_session 128 位会话密钥。
* @return 0 成功,-1 失败。
*/ */
int session_key_init(session_ctx_t *sess, const uint8_t *k_session) int Protocol::sessionKeyInit(session_ctx_t *sess, const uint8_t *k_session)
{ {
if (!sess || !k_session) return -1; if (!sess || !k_session) return -1;
@@ -319,10 +294,9 @@ int session_key_init(session_ctx_t *sess, const uint8_t *k_session)
} }
/** /**
* @brief 清除会话上下文中的密钥和状态。 * @brief 清除会话上下文中的密钥和状态。
* @param sess 待清除的会话上下文。
*/ */
void session_key_clear(session_ctx_t *sess) void Protocol::sessionKeyClear(session_ctx_t *sess)
{ {
if (sess) { if (sess) {
memset(sess->key, 0, 16); memset(sess->key, 0, 16);
@@ -332,21 +306,16 @@ void session_key_clear(session_ctx_t *sess)
} }
/** /**
* @brief 使用会话上下文加密数据。 * @brief 使用会话上下文加密数据。
* @param sess 会话上下文。
* @param in 输入明文。
* @param len 数据长度。
* @param out 输出密文缓冲区。
* @return 密文长度,失败返回 -1。
*/ */
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out) int Protocol::sessionEncrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
{ {
if (!sess || !in || !out) return -1; if (!sess || !in || !out) return -1;
uint8_t iv_copy[16]; uint8_t iv_copy[16];
memcpy(iv_copy, sess->iv, 16); memcpy(iv_copy, sess->iv, 16);
int ret = protocol_aes_encrypt(in, len, sess->key, iv_copy, out); int ret = aesEncrypt(in, len, sess->key, iv_copy, out);
if (ret > 0) { if (ret > 0) {
memcpy(sess->iv, &out[ret - 16], 16); memcpy(sess->iv, &out[ret - 16], 16);
@@ -356,21 +325,16 @@ int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t
} }
/** /**
* @brief 使用会话上下文解密数据。 * @brief 使用会话上下文解密数据。
* @param sess 会话上下文。
* @param in 输入密文。
* @param len 数据长度。
* @param out 输出明文缓冲区。
* @return 明文长度,失败返回 -1。
*/ */
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out) int Protocol::sessionDecrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
{ {
if (!sess || !in || !out) return -1; if (!sess || !in || !out) return -1;
uint8_t iv_copy[16]; uint8_t iv_copy[16];
memcpy(iv_copy, sess->iv, 16); memcpy(iv_copy, sess->iv, 16);
int ret = protocol_aes_decrypt(in, len, sess->key, iv_copy, out); int ret = aesDecrypt(in, len, sess->key, iv_copy, out);
if (ret > 0) { if (ret > 0) {
memcpy(sess->iv, &in[len - 16], 16); memcpy(sess->iv, &in[len - 16], 16);
@@ -381,10 +345,8 @@ int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t
/** /**
* @brief 使用 ESP-IDF 硬件 RNG 生成随机数 * @brief 使用 ESP-IDF 硬件 RNG 生成随机数
* @param nonce 输出缓冲区。
* @param len 随机数字节数。
*/ */
void generate_random_nonce(uint8_t *nonce, size_t len) void Protocol::generateRandomNonce(uint8_t *nonce, size_t len)
{ {
esp_fill_random(nonce, len); esp_fill_random(nonce, len);
} }
+35 -106
View File
@@ -1,7 +1,9 @@
/* /*
* protocol.h - 帧格式、CRC16、AES 加密解密 * protocol.h - 帧格式、CRC16、AES 加密解密
* T2.2, T1.3 * T2.2, T1.3
* ESP-IDF 6.0 适配版本 - 使用硬件 AES-128-CBC + 硬件 RNG * ESP-IDF 6.0 适配版本 - 使用硬件 AES-128-CBC + 硬件 RNG
*
* Protocol 类封装协议层功能。
*/ */
#ifndef PROTOCOL_H #ifndef PROTOCOL_H
#define PROTOCOL_H #define PROTOCOL_H
@@ -50,115 +52,42 @@ typedef struct {
} session_ctx_t; } session_ctx_t;
/** /**
* @brief 初始化协议层资源,建立 CRC/AES 等基础能力的可用状态。 * @brief 协议层类(单例)
*
* 封装 CRC16、AES-128-CBC、帧构建/解析、会话管理等功能。
* 大部分方法为静态工具方法,单例仅持有初始化标志。
*/ */
void protocol_init(void); class Protocol {
public:
static Protocol& instance();
/** void init();
* @brief 计算一段数据的 CRC16-Modbus 校验值。
* @param data 待校验的数据指针。
* @param len 数据长度(字节)。
* @return CRC16 校验值。
*/
uint16_t crc16_modbus(const uint8_t *data, size_t len);
/** static uint16_t crc16Modbus(const uint8_t *data, size_t len);
* @brief 按协议格式构建一帧数据,返回生成后的总帧长度。 static int buildFrame(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame);
* @param cmd 命令码。 static int parseFrame(const uint8_t *frame, size_t frame_len,
* @param payload 负载数据指针,可为 NULL。 uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len);
* @param len 负载长度(字节)。 static int aesEncrypt(const uint8_t *plaintext, size_t len,
* @param out_frame 输出帧缓冲区。 const uint8_t *key, const uint8_t *iv,
* @return 帧总长度,失败返回 -1。 uint8_t *ciphertext_out);
*/ static int aesDecrypt(const uint8_t *ciphertext, size_t len,
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame); const uint8_t *key, const uint8_t *iv,
uint8_t *plaintext_out);
static void scrambleGenerate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out);
static int sessionKeyInit(session_ctx_t *sess, const uint8_t *k_session);
static void sessionKeyClear(session_ctx_t *sess);
static int sessionEncrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
static int sessionDecrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
static void generateRandomNonce(uint8_t *nonce, size_t len);
/** private:
* @brief 解析一帧协议数据,提取命令字、负载和长度,失败返回 -1。 Protocol() = default;
* @param frame 待解析的帧数据。 ~Protocol() = default;
* @param frame_len 帧数据总长度。 Protocol(const Protocol&) = delete;
* @param out_cmd 输出参数,解析出的命令码。 Protocol& operator=(const Protocol&) = delete;
* @param out_payload 输出缓冲区,存放解析出的负载。
* @param out_len 输出参数,负载实际长度。
* @return 0 成功,-1 失败。
*/
int frame_parse(const uint8_t *frame, size_t frame_len,
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len);
/** bool m_initialized = false;
* @brief 使用 AES-128-CBC 对明文进行加密,返回密文长度,失败返回负值。 };
* @param plaintext 明文数据。
* @param len 明文长度(字节)。
* @param key 128 位 AES 密钥。
* @param iv 128 位 CBC 初始向量。
* @param ciphertext_out 输出缓冲区,存放密文。
* @return 密文长度,失败返回 -1。
*/
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *ciphertext_out);
/**
* @brief 使用 AES-128-CBC 对密文进行解密,返回去除填充后的明文长度。
* @param ciphertext 密文数据。
* @param len 密文长度(字节,须为 16 的倍数)。
* @param key 128 位 AES 密钥。
* @param iv 128 位 CBC 初始向量。
* @param plaintext_out 输出缓冲区,存放解密后的明文。
* @return 明文长度,失败返回 -1。
*/
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *plaintext_out);
/**
* @brief 根据时间戳、内存地址和随机种子生成 16 字节随机数。
* @param timestamp 时间戳种子。
* @param mem_addr 内存地址种子。
* @param rand_seed 随机数种子。
* @param nonce_out 输出缓冲区,存放生成的 16 字节随机数。
*/
void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out);
/**
* @brief 用给定会话密钥初始化会话上下文。
* @param sess 待初始化的会话上下文。
* @param k_session 128 位会话密钥。
* @return 0 成功,-1 失败。
*/
int session_key_init(session_ctx_t *sess, const uint8_t *k_session);
/**
* @brief 清除会话上下文中的密钥与状态信息。
* @param sess 待清除的会话上下文。
*/
void session_key_clear(session_ctx_t *sess);
/**
* @brief 使用当前会话上下文对输入数据执行加密。
* @param sess 会话上下文。
* @param in 输入明文。
* @param len 数据长度。
* @param out 输出密文缓冲区。
* @return 密文长度,失败返回 -1。
*/
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
/**
* @brief 使用当前会话上下文对输入数据执行解密。
* @param sess 会话上下文。
* @param in 输入密文。
* @param len 数据长度。
* @param out 输出明文缓冲区。
* @return 明文长度,失败返回 -1。
*/
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
/**
* @brief 使用 ESP-IDF 硬件 RNG 生成指定长度的随机数。
* @param nonce 输出缓冲区。
* @param len 随机数字节数。
*/
void generate_random_nonce(uint8_t *nonce, size_t len);
#endif // PROTOCOL_H #endif // PROTOCOL_H
+226 -461
View File
@@ -1,8 +1,10 @@
/* /*
* spi_master.c - SPI Master 驱动实现 * spi_master.cpp - SPI Master 驱动实现
* T1.1 - T1.4 * T1.1 - T1.4
* ESP-IDF 6.0 适配版本 * ESP-IDF 6.0 适配版本
* CANFD 3通道发送接口 * CANFD 3通道发送接口
*
* 所有功能封装为 SpiMaster 类方法。
*/ */
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@@ -15,13 +17,24 @@
#include "spi_master.h" #include "spi_master.h"
#include "monitor/debug_log.h" #include "monitor/debug_log.h"
namespace { static constexpr size_t kSpiTransferMaxBytes = 256U;
constexpr size_t kSpiTransferMaxBytes = 256U;
static esp_err_t spi_transfer_clocked(const uint8_t *tx_buf, size_t tx_len, // ============================================================
uint8_t *rx_buf, size_t rx_len) // 单例
// ============================================================
SpiMaster& SpiMaster::instance()
{ {
if (g_spi_device == NULL) { static SpiMaster inst;
return inst;
}
// ============================================================
// 内部 SPI 传输
// ============================================================
esp_err_t SpiMaster::transferClocked(const uint8_t *tx_buf, size_t tx_len,
uint8_t *rx_buf, size_t rx_len)
{
if (m_spiDevice == NULL) {
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
@@ -60,16 +73,17 @@ static esp_err_t spi_transfer_clocked(const uint8_t *tx_buf, size_t tx_len,
t.tx_buffer = tx_ptr; t.tx_buffer = tx_ptr;
t.rx_buffer = (rx_len > 0U) ? rx_ptr : NULL; t.rx_buffer = (rx_len > 0U) ? rx_ptr : NULL;
esp_err_t ret = spi_device_transmit(g_spi_device, &t); esp_err_t ret = spi_device_transmit(m_spiDevice, &t);
if (ret == ESP_OK && rx_len > 0U && rx_ptr != rx_buf) { if (ret == ESP_OK && rx_len > 0U && rx_ptr != rx_buf) {
memcpy(rx_buf, rx_ptr, rx_len); memcpy(rx_buf, rx_ptr, rx_len);
} }
return ret; return ret;
} }
static esp_err_t spi_transfer_request_response(uint8_t cmd, esp_err_t SpiMaster::transferRequestResponse(uint8_t cmd,
const uint8_t *payload, size_t payload_len, const uint8_t *payload, size_t payload_len,
uint8_t *rx_buf, size_t rx_len) uint8_t *rx_buf, size_t rx_len)
{ {
if ((payload_len > 0U && payload == NULL) || (rx_len > 0U && rx_buf == NULL)) { if ((payload_len > 0U && payload == NULL) || (rx_len > 0U && rx_buf == NULL)) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
@@ -99,23 +113,19 @@ static esp_err_t spi_transfer_request_response(uint8_t cmd,
memset(&tx_staging[tx_pos], 0xFF, total_len - tx_pos); memset(&tx_staging[tx_pos], 0xFF, total_len - tx_pos);
} }
esp_err_t ret = spi_transfer_clocked(tx_staging, total_len, esp_err_t ret = transferClocked(tx_staging, total_len,
(rx_len > 0U) ? rx_staging : NULL, (rx_len > 0U) ? rx_staging : NULL,
(rx_len > 0U) ? total_len : 0U); (rx_len > 0U) ? total_len : 0U);
if (ret == ESP_OK && rx_len > 0U) { if (ret == ESP_OK && rx_len > 0U) {
memcpy(rx_buf, &rx_staging[cmd_len + payload_len], rx_len); memcpy(rx_buf, &rx_staging[cmd_len + payload_len], rx_len);
} }
return ret; return ret;
} }
} // namespace
/** // ============================================================
* @brief 计算 SPI OTA 帧头和负载的异或 CRC8。 // OTA 帧工具
* @param data 输入数据指针。 // ============================================================
* @param len 输入长度。 uint8_t SpiMaster::otaCrc8(const uint8_t *data, size_t len)
* @return 计算得到的 CRC8 值。
*/
static uint8_t spi_ota_crc8(const uint8_t *data, size_t len)
{ {
uint8_t crc = 0x00; uint8_t crc = 0x00;
for (size_t i = 0; i < len; ++i) { for (size_t i = 0; i < len; ++i) {
@@ -124,17 +134,8 @@ static uint8_t spi_ota_crc8(const uint8_t *data, size_t len)
return crc; return crc;
} }
/** bool SpiMaster::otaBuildFrame(uint8_t type, const uint8_t *payload, size_t payload_len,
* @brief 构建 SPI OTA 协议帧(SOF + TYPE + LEN + PAYLOAD + CRC)。 uint8_t *out, size_t *out_len)
* @param type 帧类型。
* @param payload 负载数据指针,可为 NULL。
* @param payload_len 负载长度。
* @param out 输出帧缓冲区。
* @param out_len 输出帧长度。
* @return 构建成功返回 true,否则返回 false。
*/
static bool spi_ota_build_frame(uint8_t type, const uint8_t *payload, size_t payload_len,
uint8_t *out, size_t *out_len)
{ {
if (!out || !out_len) return false; if (!out || !out_len) return false;
size_t total = 5 + payload_len; size_t total = 5 + payload_len;
@@ -146,91 +147,67 @@ static bool spi_ota_build_frame(uint8_t type, const uint8_t *payload, size_t pay
if (payload && payload_len > 0) { if (payload && payload_len > 0) {
memcpy(&out[4], payload, payload_len); memcpy(&out[4], payload, payload_len);
} }
out[4 + payload_len] = spi_ota_crc8(out, 4 + payload_len); out[4 + payload_len] = otaCrc8(out, 4 + payload_len);
*out_len = total; *out_len = total;
return true; return true;
} }
/** bool SpiMaster::otaParseFrame(const uint8_t *frame, size_t frame_len,
* @brief 解析并校验一帧 SPI OTA 数据。 uint8_t *type, const uint8_t **payload, size_t *payload_len)
* @param frame 输入帧指针。
* @param frame_len 输入帧缓冲区长度。
* @param type 输出帧类型,可为 NULL。
* @param payload 输出负载指针,可为 NULL。
* @param payload_len 输出负载长度,可为 NULL。
* @return 解析并校验成功返回 true,否则返回 false。
*/
static bool spi_ota_parse_frame(const uint8_t *frame, size_t frame_len,
uint8_t *type, const uint8_t **payload, size_t *payload_len)
{ {
if (!frame || frame_len < 5 || frame[0] != SPI_OTA_SOF) return false; if (!frame || frame_len < 5) return false;
size_t len = (size_t)frame[2] | ((size_t)frame[3] << 8); if (frame[0] != SPI_OTA_SOF) return false;
size_t expected = 5 + len;
if (expected > frame_len) return false; size_t plen = (size_t)frame[2] | ((size_t)frame[3] << 8);
uint8_t crc = spi_ota_crc8(frame, 4 + len); size_t total = 5 + plen;
if (crc != frame[4 + len]) return false; if (frame_len < total) return false;
uint8_t expected_crc = otaCrc8(frame, 4 + plen);
if (frame[4 + plen] != expected_crc) return false;
if (type) *type = frame[1]; if (type) *type = frame[1];
if (payload) *payload = &frame[4]; if (payload) *payload = (plen > 0) ? &frame[4] : nullptr;
if (payload_len) *payload_len = len; if (payload_len) *payload_len = plen;
return true; return true;
} }
/** bool SpiMaster::otaFindFrame(const uint8_t *buf, size_t buf_len,
* @brief 在接收缓冲区中扫描并定位第一帧有效 SPI OTA 数据。 uint8_t *type, const uint8_t **payload, size_t *payload_len,
* @param buf 输入缓冲区。 size_t *frame_offset)
* @param buf_len 缓冲区长度。
* @param type 输出帧类型,可为 NULL。
* @param payload 输出负载指针,可为 NULL。
* @param payload_len 输出负载长度,可为 NULL。
* @param frame_offset 输出帧起始偏移,可为 NULL。
* @return 找到有效帧返回 true,否则返回 false。
*/
static bool spi_ota_find_frame(const uint8_t *buf, size_t buf_len,
uint8_t *type, const uint8_t **payload, size_t *payload_len,
size_t *frame_offset)
{ {
if (!buf || buf_len < 5) return false;
for (size_t i = 0; i + 5 <= buf_len; ++i) { for (size_t i = 0; i + 5 <= buf_len; ++i) {
if (buf[i] != SPI_OTA_SOF) continue; if (buf[i] != SPI_OTA_SOF) continue;
size_t len_field = (size_t)buf[i+2] | ((size_t)buf[i+3] << 8);
size_t needed = 5 + len_field; size_t plen = (size_t)buf[i + 2] | ((size_t)buf[i + 3] << 8);
if (i + needed > buf_len) continue; size_t total = 5 + plen;
uint8_t crc = spi_ota_crc8(&buf[i], 4 + len_field); if (i + total > buf_len) continue;
if (crc != buf[i + 4 + len_field]) {
DEBUG_DBG("SPI", "帧@%u CRC错误: calc=0x%02X recv=0x%02X type=0x%02X len=%u", uint8_t expected_crc = otaCrc8(&buf[i], 4 + plen);
(unsigned)i, crc, buf[i + 4 + len_field], buf[i+1], (unsigned)len_field); if (buf[i + 4 + plen] != expected_crc) continue;
continue;
} if (type) *type = buf[i + 1];
if (spi_ota_parse_frame(&buf[i], buf_len - i, type, payload, payload_len)) { if (payload) *payload = (plen > 0) ? &buf[i + 4] : nullptr;
if (frame_offset) *frame_offset = i; if (payload_len) *payload_len = plen;
return true; if (frame_offset) *frame_offset = i;
} return true;
} }
return false; return false;
} }
/** @brief SPI2 从设备句柄。 */ // ============================================================
spi_device_handle_t g_spi_device = NULL; // 初始化
// ============================================================
/** esp_err_t SpiMaster::init()
* @brief SPI2 总线初始化
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_master_init(void)
{ {
DEBUG_INFO("SPI", "=== 初始化 SPI2 (VSPI) ==="); DEBUG_INFO("SPI", "=== 初始化 SPI2 总线 ===");
spi_bus_config_t bus_cfg = {}; spi_bus_config_t bus_cfg = {};
bus_cfg.mosi_io_num = 5; bus_cfg.miso_io_num = 2;
bus_cfg.miso_io_num = 7; bus_cfg.mosi_io_num = 7;
bus_cfg.sclk_io_num = 6; bus_cfg.sclk_io_num = 6;
bus_cfg.quadwp_io_num = -1; bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1; bus_cfg.quadhd_io_num = -1;
bus_cfg.max_transfer_sz = 256; bus_cfg.max_transfer_sz = kSpiTransferMaxBytes;
DEBUG_INFO("SPI", "SPI2 引脚: MOSI=GPIO5, MISO=GPIO7, SCLK=GPIO6");
DEBUG_INFO("SPI", "SPI2 配置: CLK=%d MHz, CS=GPIO%d, 最大传输=%d 字节",
SPI_CLOCK_HZ / 1000000, SPI_MASTER_CS_GPIO, bus_cfg.max_transfer_sz);
esp_err_t ret = spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO); esp_err_t ret = spi_bus_initialize(SPI2_HOST, &bus_cfg, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) { if (ret != ESP_OK) {
@@ -243,408 +220,150 @@ esp_err_t spi_master_init(void)
dev_cfg.mode = SPI_MODE; dev_cfg.mode = SPI_MODE;
dev_cfg.spics_io_num = SPI_MASTER_CS_GPIO; dev_cfg.spics_io_num = SPI_MASTER_CS_GPIO;
dev_cfg.queue_size = SPI_QUEUE_SIZE; dev_cfg.queue_size = SPI_QUEUE_SIZE;
dev_cfg.command_bits = 0; dev_cfg.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY;
dev_cfg.address_bits = 0;
dev_cfg.dummy_bits = 0;
/* CS↑ 后保持 CS 高的时间(SPI 时钟周期数)。
*
* 0 = 最小 CS 高时间(仅满足 SPI 时序)。
* 设大一点让 GD32 EXTI ISR 有足够时间完成 onCsRelease() 处理:
* 切 internal NSS HIGH + 清 RBNE FIFO + 帧解析 + 准备响应帧。
*
* 10 × 250ns = 2.5µs,加上 SPI 驱动本身的 CS 间隮(~1µs),
* GD32 实际有 ~3.5µs 的 CS 高窗口。足够完成关键操作。
*/
dev_cfg.cs_ena_posttrans = 10;
/* CS↓ 后等待 20 个 SPI 时钟周期再开始发 SCK。
*
* 4MHz SPI → 20×250ns = 5µs 延时。
*
* GD32 SPI 从机在 CS↓ 时需要 EXTI ISR 执行 onCsAssert():
* 设置 s_csActive=true → 清空接收缓冲 → 切 internal NSS LOW → 配置 DMA。
*
* 但 SPI0 RBNE 中断优先级 (1,0) 高于 EXTI (1,1),
* 首字节 RBNE 会打断正在执行的 EXTI ISR →
* s_csActive 还没设为 true → s_rxStreamLen 还没清零 →
* 首字节存到旧位置 → EXTI 返回后清零 → 首字节丢失。
*
* cs_ena_pretrans=20 让 ESP32 在 CS↓ 后多等 5µs,
* GD32 EXTI ISR 只需 ~1µs 就能完成关键初始化,
* 5µs 足够让 onCsAssert() 执行完 s_csActive=true + s_rxStreamLen=0U。
*
* 代价:每次 SPI 事务多 5µs 延时。
* 一次 OTA 事务 = 1次请求 + ~10次轮询 = ~55µs 额外开销,可接受。
*/
dev_cfg.cs_ena_pretrans = 20;
ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &g_spi_device); ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &m_spiDevice);
if (ret != ESP_OK) { if (ret != ESP_OK) {
DEBUG_ERR("SPI", "添加 SPI 设备失败: %s", esp_err_to_name(ret)); DEBUG_ERR("SPI", "SPI 设备添加失败: %s", esp_err_to_name(ret));
return ret; return ret;
} }
DEBUG_INFO("SPI", "SPI2 已初始化: CLK=%dMHz, CS=GPIO%d, 模式=%d", DEBUG_INFO("SPI", "SPI2 总线初始化完成: CS=GPIO%d, CLK=%d Hz",
SPI_CLOCK_HZ / 1000000, SPI_MASTER_CS_GPIO, SPI_MODE); SPI_MASTER_CS_GPIO, SPI_CLOCK_HZ);
DEBUG_INFO("SPI", "SPI 设备句柄: %p", g_spi_device);
return ESP_OK; return ESP_OK;
} }
/** // ============================================================
* @brief 执行一次同步 SPI 事务,先发送请求,再补时钟读取响应尾部。 // 公开 SPI 传输接口
* @param cmd SPI 命令码;为 0 时不额外插入命令字节,直接发送 payload。 // ============================================================
* @param payload 发送数据指针。 esp_err_t SpiMaster::syncTransfer(uint8_t cmd, const uint8_t *payload,
* @param payload_len 发送数据长度。 size_t payload_len, uint8_t *recv_buf,
* @param recv_buf 接收缓冲区指针。 size_t recv_len, uint32_t timeout_ms)
* @param recv_len 期望接收的响应字节数。
* @param timeout_ms 预留超时参数(当前实现未直接使用)。
* @return ESP_OK 成功,其他为失败错误码。
*/
esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
size_t payload_len, uint8_t *recv_buf,
size_t recv_len, uint32_t timeout_ms)
{ {
(void)timeout_ms; (void)timeout_ms;
if (g_spi_device == NULL) { return transferRequestResponse(cmd, payload, payload_len, recv_buf, recv_len);
DEBUG_ERR("SPI", "SPI 设备未初始化"); }
return ESP_ERR_INVALID_STATE;
esp_err_t SpiMaster::send(uint8_t cmd, const uint8_t *payload, size_t len)
{
return transferRequestResponse(cmd, payload, len, NULL, 0);
}
esp_err_t SpiMaster::recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms)
{
(void)timeout_ms;
return transferRequestResponse(cmd, NULL, 0, recv_buf, len);
}
// ============================================================
// SPI OTA 协议
// ============================================================
esp_err_t SpiMaster::otaTransfer(uint8_t type, const uint8_t *payload, size_t payload_len,
uint8_t *resp_type, uint8_t *resp_payload,
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
{
uint8_t tx_frame[SPI_OTA_FRAME_MAX];
size_t tx_len = 0;
if (!otaBuildFrame(type, payload, payload_len, tx_frame, &tx_len)) {
return ESP_ERR_INVALID_ARG;
} }
DEBUG_DBG("SPI", "SPI 传输: cmd=0x%02X, 发送=%zu 字节, 接收=%zu 字节", esp_err_t ret = transferClocked(tx_frame, tx_len, NULL, 0);
cmd, payload_len, recv_len);
esp_err_t ret = spi_transfer_request_response(cmd, payload, payload_len, recv_buf, recv_len);
if (ret != ESP_OK) { if (ret != ESP_OK) {
DEBUG_WARN("SPI", "SPI 发送 cmd=0x%02X 失败: %s", cmd, esp_err_to_name(ret)); DEBUG_WARN("SPI", "OTA 请求发送失败: %s", esp_err_to_name(ret));
} else { return ret;
DEBUG_DBG("SPI", "SPI 传输完成: cmd=0x%02X", cmd);
} }
return ret; return otaPollResponse(resp_type, resp_payload, resp_max, resp_len, timeout_ms);
} }
// 仅发送 esp_err_t SpiMaster::otaPollResponse(uint8_t *resp_type, uint8_t *resp_payload,
/** size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
* @brief 调用同步传输接口发送数据,不要求接收返回内容。
* @param cmd SPI 命令码。
* @param payload 发送数据指针。
* @param len 发送数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len)
{ {
return spi_sync_transfer(cmd, payload, len, NULL, 0, 3000); constexpr size_t kPollBytes = 96U;
}
// 仅接收
/**
* @brief 发送哑数据产生时钟,从从设备同步读取指定长度数据。
* @param cmd SPI 命令码。
* @param recv_buf 接收缓冲区。
* @param len 接收长度。
* @param timeout_ms 超时时间(毫秒)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms)
{
return spi_sync_transfer(cmd, NULL, 0, recv_buf, len, timeout_ms);
}
/**
* @brief 轮询读取 SPI OTA 响应帧,不重复发送请求帧。
* @param resp_type 输出响应类型,可为 NULL。
* @param resp_payload 输出响应负载缓冲区,可为 NULL。
* @param resp_max 响应缓冲区最大长度。
* @param resp_len 输出响应长度,可为 NULL。
* @param timeout_ms 轮询超时时间(毫秒)。
* @return ESP_OK 成功,ESP_ERR_TIMEOUT 超时,其他为底层错误码。
*/
esp_err_t spi_ota_poll_response(uint8_t *resp_type, uint8_t *resp_payload,
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
{
if (g_spi_device == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (resp_len) *resp_len = 0;
if (resp_type) *resp_type = 0;
uint8_t rx_raw[SPI_OTA_FRAME_MAX] = {0};
uint8_t dummy[SPI_OTA_FRAME_MAX];
memset(dummy, 0xFF, sizeof(dummy));
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms); TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms);
uint32_t attempt = 0;
while (xTaskGetTickCount() <= deadline) { while (xTaskGetTickCount() <= deadline) {
++attempt; uint8_t rx_raw[kPollBytes];
memset(rx_raw, 0xFF, sizeof(rx_raw)); uint8_t dummy[kPollBytes];
esp_err_t ret = spi_transfer_clocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw)); memset(dummy, 0xFF, sizeof(dummy));
esp_err_t ret = transferClocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw));
if (ret != ESP_OK) { if (ret != ESP_OK) {
DEBUG_WARN("SPI", "SPI OTA 响应读取失败[%u]: %s", attempt, esp_err_to_name(ret)); DEBUG_WARN("SPI", "OTA 响应读取失败: %s", esp_err_to_name(ret));
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(10));
continue; continue;
} }
uint8_t parsed_type = 0; uint8_t found_type = 0;
const uint8_t *parsed_payload = nullptr; const uint8_t *found_payload = nullptr;
size_t parsed_len = 0; size_t found_len = 0;
size_t frame_offset = 0; size_t frame_offset = 0;
if (spi_ota_find_frame(rx_raw, sizeof(rx_raw), &parsed_type, &parsed_payload, &parsed_len, &frame_offset)) {
DEBUG_DBG("SPI", "OTA响应: type=0x%02X len=%u offset=%u attempt=%u", if (otaFindFrame(rx_raw, sizeof(rx_raw), &found_type, &found_payload, &found_len, &frame_offset)) {
parsed_type, (unsigned)parsed_len, (unsigned)frame_offset, (unsigned)attempt); if (resp_type) *resp_type = found_type;
if (resp_type) *resp_type = parsed_type;
if (resp_payload && resp_max > 0) { if (resp_payload && resp_max > 0) {
size_t copy_len = (parsed_len < resp_max) ? parsed_len : resp_max; size_t copy_len = (found_len < resp_max) ? found_len : resp_max;
memcpy(resp_payload, parsed_payload, copy_len); if (found_payload && copy_len > 0) {
if (resp_len) *resp_len = copy_len; memcpy(resp_payload, found_payload, copy_len);
} else if (resp_len) { }
*resp_len = parsed_len; }
if (resp_len) *resp_len = found_len;
DEBUG_DBG("SPI", "OTA 响应: type=0x%02X len=%u offset=%u",
found_type, (unsigned)found_len, (unsigned)frame_offset);
if (found_type == SPI_OTA_RSP_WAIT) {
vTaskDelay(pdMS_TO_TICKS(10));
continue;
} }
return ESP_OK; return ESP_OK;
} }
bool all_ff = true; vTaskDelay(pdMS_TO_TICKS(10));
for (size_t i = 0; i < sizeof(rx_raw); ++i) {
if (rx_raw[i] != 0xFF) {
all_ff = false;
break;
}
}
DEBUG_DBG("SPI", "OTA响应未就绪[%u]: %s first10=%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
attempt, all_ff ? "all-FF" : "no-valid-SOF",
rx_raw[0], rx_raw[1], rx_raw[2], rx_raw[3],
rx_raw[4], rx_raw[5], rx_raw[6], rx_raw[7],
rx_raw[8], rx_raw[9]);
vTaskDelay(pdMS_TO_TICKS(1));
} }
DEBUG_DBG("SPI", "OTA响应超时: timeout=%ums", (unsigned)timeout_ms); DEBUG_WARN("SPI", "OTA 响应超时: %lu ms", (unsigned long)timeout_ms);
return ESP_ERR_TIMEOUT; return ESP_ERR_TIMEOUT;
} }
/** esp_err_t SpiMaster::appRequestBootloader(uint32_t timeout_ms)
* @brief 执行一次 SPI OTA 请求事务并等待响应帧。
* @param type 请求帧类型。
* @param payload 请求负载。
* @param payload_len 负载长度。
* @param resp_type 输出响应类型,可为 NULL。
* @param resp_payload 输出响应负载缓冲区,可为 NULL。
* @param resp_max 响应负载缓冲区大小。
* @param resp_len 输出响应长度,可为 NULL。
* @param timeout_ms 响应等待超时时间(毫秒)。
* @return ESP_OK 成功,其他为失败错误码。
*/
esp_err_t spi_ota_transfer(uint8_t type, const uint8_t *payload, size_t payload_len,
uint8_t *resp_type, uint8_t *resp_payload,
size_t resp_max, size_t *resp_len, uint32_t timeout_ms)
{ {
if (g_spi_device == NULL) { DEBUG_INFO("SPI", "请求 GD32 APP 进入 Bootloader...");
return ESP_ERR_INVALID_STATE;
}
if (resp_len) *resp_len = 0;
if (resp_type) *resp_type = 0;
uint8_t tx_frame[SPI_OTA_FRAME_MAX] = {0};
size_t tx_len = 0;
if (!spi_ota_build_frame(type, payload, payload_len, tx_frame, &tx_len)) {
return ESP_ERR_INVALID_ARG;
}
DEBUG_DBG("SPI", "OTA事务: req_type=0x%02X req_len=%u", type, (unsigned)payload_len);
/* ============================================================
* 第1次 CS 周期:发送请求帧(只发不收)
*
* ESP32 发完请求帧后 CS↑,GD32 在 CS↑ 的 onCsRelease 中
* 或 RBNE 中断的 tryProcessCompleteFrame 中处理帧,
* 将 ACK 写入 s_txBuf 并启动 DMA。
*
* 注意:不能在同一个 CS 周期内发请求+读响应,因为:
* 1. SPI 全双工:ESP32 发请求时 MISO 同时在输出旧数据
* 2. GD32 收到请求帧后才能准备 ACK,需重启 DMA
* 3. DMA 重启期间 MISO 会输出 0x00/0xFF,导致帧断裂
* ============================================================ */
esp_err_t ret = spi_transfer_clocked(tx_frame, tx_len, NULL, 0);
if (ret != ESP_OK) {
DEBUG_ERR("SPI", "SPI OTA 请求发送失败: %s", esp_err_to_name(ret));
return ret;
}
/* 等 GD32 处理帧 + 准备响应。
*
* GD32 在 CS↑ 的 onCsRelease() 中处理帧:
* isrProcessFrame() → OTA handler 回调 → queueTxFrame() → DMA 启动
* 总耗时约 10-20µs(ISR 内执行,无调度延迟)。
*
* 但 EXTI ISR 可能被更高优先级的 RBNE 中断短暂抢占,
* 实际完成时间可能到 30-50µs。
*
* 500µs 提供了 10x 以上的安全余量,
* 同时对 OTA 事务延迟影响可忽略(一次 OTA 事务 ~1ms 级别)。 */
esp_rom_delay_us(500);
/* ============================================================
* 第2次 CS 周期:发 0xFF dummy 产生时钟,读 MISO 上的响应帧
*
* GD32 的 onCsAssert 会重启 DMA,将 s_txBuf(ACK帧)
* 推到 MISO,ESP32 在 96 字节中扫描定位响应帧。
* ============================================================ */
return spi_ota_poll_response(resp_type, resp_payload, resp_max, resp_len, timeout_ms);
}
// CANFD 帧发送
// 帧格式: [通道(1B)] [扩展帧ID(4B BE)] [数据(1~64B)]
/**
* @brief 发送一帧 CANFD 数据到 GD32,并可选等待响应。
* @param channel CAN 通道号。
* @param ext_id 扩展帧 ID(29-bit)。
* @param data 发送负载指针。
* @param dlc 发送数据长度。
* @param recv_buf 接收缓冲区,可为 NULL。
* @param recv_max 接收缓冲区最大长度。
* @param timeout_ms 等待响应超时(毫秒)。
* @return >=0 为接收字节数,<0 为错误码。
*/
static int can_frame_send(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms)
{
if (g_spi_device == NULL) {
DEBUG_ERR("SPI", "CAN 发送: SPI 设备未初始化");
return -1;
}
if (dlc == 0 || dlc > CAN_MAX_DLC) {
DEBUG_ERR("SPI", "CAN[ch%d] DLC 无效: %d (范围 1~%d)", channel, dlc, CAN_MAX_DLC);
return -2;
}
if (channel >= CAN_CHANNEL_MAX) {
DEBUG_ERR("SPI", "CAN 通道号无效: %d (范围 0~%d)", channel, CAN_CHANNEL_MAX - 1);
return -3;
}
DEBUG_DBG("SPI", "CAN[ch%d] ID=0x%08X 发送: DLC=%d", channel, ext_id, dlc);
// 构建 CANFD 帧: [通道(1B)] [EXT_ID(4B)] [DATA(dlc B)]
uint8_t tx_buf[1 + 4 + CAN_MAX_DLC] = {0};
tx_buf[0] = (uint8_t)channel;
tx_buf[1] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[2] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[3] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[4] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[5], data, dlc);
size_t tx_len = 5 + dlc;
uint8_t rx_buf[5 + CAN_MAX_DLC] = {0};
const size_t rx_len = (recv_buf != NULL && recv_max > 0U) ? (size_t)(5U + recv_max) : 0U;
esp_err_t ret = spi_transfer_clocked(tx_buf, tx_len, rx_buf, rx_len);
if (ret != ESP_OK) {
DEBUG_ERR("SPI", "CAN[ch%d] ID=0x%08X 发送失败: %s",
channel, ext_id, esp_err_to_name(ret));
return -4;
}
if (recv_buf && recv_max > 0) {
// 解析 GD32 响应帧
uint8_t resp_channel = rx_buf[0];
uint32_t resp_id = (rx_buf[1] << 24) | (rx_buf[2] << 16)
| (rx_buf[3] << 8) | rx_buf[4];
uint8_t resp_dlc = resp_id >> 24; // 复用了 ID 字段低字节作为 DLC
resp_id &= 0x00FFFFFF;
uint8_t copy_len = (resp_dlc < recv_max) ? resp_dlc : recv_max;
memcpy(recv_buf, &rx_buf[5], copy_len);
DEBUG_INFO("SPI", "CAN[ch%d] ID=0x%08X 响应: DLC=%d, 接收=%d 字节",
resp_channel, resp_id, copy_len, copy_len);
return copy_len;
}
DEBUG_DBG("SPI", "CAN[ch%d] ID=0x%08X 发送完成 (无响应)", channel, ext_id);
return 0;
}
/**
* @brief 对外暴露的 GD32 CAN 发送接口,必要时修正默认 DLC。
* @param channel CAN 通道。
* @param ext_id 扩展帧 ID。
* @param data 发送数据指针。
* @param dlc 数据长度。
* @param recv_buf 接收缓冲区,可为 NULL。
* @param recv_max 接收缓冲区大小。
* @param timeout_ms 等待响应超时(毫秒)。
* @return >=0 为接收字节数,<0 为错误码。
*/
int gd32_can_send(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms)
{
return can_frame_send(channel, ext_id, data, dlc, recv_buf, recv_max, timeout_ms);
}
/**
* @brief 发送无需响应的 GD32 CAN 帧。
* @param channel CAN 通道。
* @param ext_id 扩展帧 ID。
* @param data 发送数据指针。
* @param dlc 数据长度。
* @return 0 表示已发送完成,<0 为错误码。
*/
int gd32_can_send_no_reply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
return can_frame_send(channel, ext_id, data, dlc, NULL, 0, 0);
}
/**
* @brief 向正在运行的 GD32 DCU_APP 发送进入 Bootloader 的短 SPI 命令。
* @param timeout_ms 等待 APP 返回确认的最长时间。
* @return ESP_OK 表示 APP 已接收命令;超时则通常表示 GD32 已不在 APP 或 SPI 未就绪。
*/
esp_err_t gd32_app_request_bootloader(uint32_t timeout_ms)
{
uint8_t cmd = SPI_APP_ENTER_BOOT_CMD; uint8_t cmd = SPI_APP_ENTER_BOOT_CMD;
esp_err_t ret = spi_transfer_clocked(&cmd, 1U, NULL, 0U); esp_err_t ret = transferClocked(&cmd, 1, NULL, 0);
if (ret != ESP_OK) { if (ret != ESP_OK) {
DEBUG_WARN("SPI", "APP进入BOOT命令发送失败: %s", esp_err_to_name(ret)); DEBUG_WARN("SPI", "发送 Bootloader 请求失败: %s", esp_err_to_name(ret));
return ret; return ret;
} }
uint8_t rx[8] = {0};
uint8_t dummy[8];
memset(dummy, 0xFF, sizeof(dummy));
TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms); TickType_t deadline = xTaskGetTickCount() + pdMS_TO_TICKS(timeout_ms);
while (xTaskGetTickCount() <= deadline) { while (xTaskGetTickCount() <= deadline) {
memset(rx, 0xFF, sizeof(rx)); uint8_t rx = 0;
ret = spi_transfer_clocked(dummy, sizeof(dummy), rx, sizeof(rx)); uint8_t dummy = 0xFF;
if (ret == ESP_OK) { ret = transferClocked(&dummy, 1, &rx, 1);
for (size_t i = 0; i + 3U < sizeof(rx); ++i) { if (ret == ESP_OK && rx == SPI_APP_ENTER_BOOT_ACK_OK) {
if (rx[i] == SPI_APP_ENTER_BOOT_CMD && rx[i + 1U] == SPI_APP_ENTER_BOOT_ACK_OK) { DEBUG_INFO("SPI", "GD32 APP 已确认进入 Bootloader");
DEBUG_INFO("SPI", "GD32 APP确认进入Bootloader OTA窗口"); return ESP_OK;
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 确认超时");
return ESP_ERR_TIMEOUT; return ESP_ERR_TIMEOUT;
} }
esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec) esp_err_t SpiMaster::appPing(uint32_t timeout_ms, uint32_t *uptime_sec)
{ {
/* APP 侧 SPI 从机当前是中断逐字节收发,不适合像 BOOT 那样一次读 96B 长轮询。
* 这里改为:每次都完整重发一帧 PING 请求,然后只读一个短窗口寻找 ACK。
* 这样即便某次响应窗口错过,也会在下一次重发时重新生成 APP_ALIVE。 */
constexpr uint8_t kAppPingType = 0xA1U; constexpr uint8_t kAppPingType = 0xA1U;
constexpr size_t kAppPingPollBytes = 32U; constexpr size_t kAppPingPollBytes = 32U;
uint8_t tx_frame[8] = {0}; uint8_t tx_frame[8] = {0};
size_t tx_len = 0U; size_t tx_len = 0U;
if (!spi_ota_build_frame(kAppPingType, NULL, 0U, tx_frame, &tx_len)) { if (!otaBuildFrame(kAppPingType, NULL, 0U, tx_frame, &tx_len)) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
@@ -654,7 +373,7 @@ esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec)
while (xTaskGetTickCount() <= deadline) { while (xTaskGetTickCount() <= deadline) {
++attempt; ++attempt;
esp_err_t err = spi_transfer_clocked(tx_frame, tx_len, NULL, 0U); esp_err_t err = transferClocked(tx_frame, tx_len, NULL, 0U);
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s", DEBUG_DBG("SPI", "APP PING 请求发送失败[%lu]: %s",
(unsigned long)attempt, esp_err_to_name(err)); (unsigned long)attempt, esp_err_to_name(err));
@@ -669,7 +388,7 @@ esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec)
memset(dummy, 0xFF, sizeof(dummy)); memset(dummy, 0xFF, sizeof(dummy));
memset(rx_raw, 0xFF, sizeof(rx_raw)); memset(rx_raw, 0xFF, sizeof(rx_raw));
err = spi_transfer_clocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw)); err = transferClocked(dummy, sizeof(dummy), rx_raw, sizeof(rx_raw));
if (err != ESP_OK) { if (err != ESP_OK) {
DEBUG_DBG("SPI", "APP PING 响应读取失败[%lu]: %s", DEBUG_DBG("SPI", "APP PING 响应读取失败[%lu]: %s",
(unsigned long)attempt, esp_err_to_name(err)); (unsigned long)attempt, esp_err_to_name(err));
@@ -681,8 +400,8 @@ esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec)
const uint8_t *resp_payload = nullptr; const uint8_t *resp_payload = nullptr;
size_t resp_len = 0U; size_t resp_len = 0U;
size_t frame_offset = 0U; size_t frame_offset = 0U;
if (spi_ota_find_frame(rx_raw, sizeof(rx_raw), if (otaFindFrame(rx_raw, sizeof(rx_raw),
&resp_type, &resp_payload, &resp_len, &frame_offset)) { &resp_type, &resp_payload, &resp_len, &frame_offset)) {
if (resp_type == SPI_OTA_RSP_ACK && resp_len >= 3U && resp_payload[0] == kAppPingType) { if (resp_type == SPI_OTA_RSP_ACK && resp_len >= 3U && resp_payload[0] == kAppPingType) {
if (uptime_sec) { if (uptime_sec) {
*uptime_sec = (uint32_t)resp_payload[1] | ((uint32_t)resp_payload[2] << 8); *uptime_sec = (uint32_t)resp_payload[1] | ((uint32_t)resp_payload[2] << 8);
@@ -714,20 +433,9 @@ esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec)
return ESP_ERR_TIMEOUT; return ESP_ERR_TIMEOUT;
} }
// 带重试的 SPI 传输 esp_err_t SpiMaster::transferWithRetry(uint8_t cmd, const uint8_t *payload,
/** size_t payload_len, uint8_t *recv_buf,
* @brief 执行带重试的 SPI 同步传输。 size_t recv_len, int max_retries)
* @param cmd SPI 命令码。
* @param payload 发送数据指针。
* @param payload_len 发送长度。
* @param recv_buf 接收缓冲区。
* @param recv_len 接收长度。
* @param max_retries 最大重试次数。
* @return ESP_OK 成功,重试耗尽返回 ESP_ERR_TIMEOUT。
*/
esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
size_t payload_len, uint8_t *recv_buf,
size_t recv_len, int max_retries)
{ {
esp_err_t ret; esp_err_t ret;
int retry_count = 0; int retry_count = 0;
@@ -735,8 +443,8 @@ esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
DEBUG_INFO("SPI", "SPI 传输 (带重试): cmd=0x%02X, 最大重试=%d", cmd, max_retries); DEBUG_INFO("SPI", "SPI 传输 (带重试): cmd=0x%02X, 最大重试=%d", cmd, max_retries);
while (retry_count < max_retries) { while (retry_count < max_retries) {
ret = spi_sync_transfer(cmd, payload, payload_len, ret = syncTransfer(cmd, payload, payload_len,
recv_buf, recv_len, 3000); recv_buf, recv_len, 3000);
if (ret == ESP_OK) { if (ret == ESP_OK) {
if (retry_count > 0) { if (retry_count > 0) {
DEBUG_INFO("SPI", "SPI 传输成功 (重试 %d 次后成功)", retry_count); DEBUG_INFO("SPI", "SPI 传输成功 (重试 %d 次后成功)", retry_count);
@@ -753,3 +461,60 @@ esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败, cmd=0x%02X", max_retries, cmd); DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败, cmd=0x%02X", max_retries, cmd);
return ESP_ERR_TIMEOUT; return ESP_ERR_TIMEOUT;
} }
// ============================================================
// CANFD 接口
// ============================================================
int SpiMaster::canSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms)
{
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = syncTransfer(0, tx_buf, tx_len, recv_buf, recv_max, timeout_ms);
if (ret != ESP_OK) {
return -1;
}
return (int)recv_max;
}
int SpiMaster::canSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC || !data) {
return -1;
}
uint8_t tx_buf[72];
size_t tx_len = 0;
tx_buf[tx_len++] = (uint8_t)(channel & CAN_CHANNEL_MASK);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 24) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 16) & 0xFF);
tx_buf[tx_len++] = (uint8_t)((ext_id >> 8) & 0xFF);
tx_buf[tx_len++] = (uint8_t)(ext_id & 0xFF);
memcpy(&tx_buf[tx_len], data, dlc);
tx_len += dlc;
esp_err_t ret = send(0, tx_buf, tx_len);
if (ret != ESP_OK) {
return -1;
}
return 0;
}
+84 -93
View File
@@ -1,6 +1,8 @@
/* /*
* spi_master.h - SPI Master 驱动头文件 * spi_master.h - SPI Master 驱动头文件
* CANFD 3通道发送接口 + 底层 SPI 总线 * CANFD 3通道发送接口 + 底层 SPI 总线
*
* SpiMaster 类封装 SPI 总线初始化、CANFD 通信和 SPI OTA 协议。
*/ */
#ifndef SPI_MASTER_H #ifndef SPI_MASTER_H
#define SPI_MASTER_H #define SPI_MASTER_H
@@ -26,9 +28,6 @@
#define SPI_OTA_RSP_WAIT (0x81) #define SPI_OTA_RSP_WAIT (0x81)
#define SPI_OTA_RSP_ERROR (0x82) #define SPI_OTA_RSP_ERROR (0x82)
// SPI 设备句柄(外部引用)
extern spi_device_handle_t g_spi_device;
// ============================================================ // ============================================================
// CANFD 帧格式(3通道) // CANFD 帧格式(3通道)
// [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B] // [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B]
@@ -55,104 +54,96 @@ typedef struct {
} can_frame_t; } can_frame_t;
/** /**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应内容。 * @brief SPI Master 驱动类(单例)
* @param channel CAN 通道(0~2)。 *
* @param ext_id 29 位扩展帧 ID。 * 封装 SPI 总线初始化、同步传输、CANFD 通信和 SPI OTA 协议。
* @param data 数据负载指针。
* @param dlc 数据长度,范围 1~64。
* @param recv_buf 接收缓冲区,可为 NULL。
* @param recv_max 期望最大接收字节数。
* @param timeout_ms 超时时间(毫秒)。
* @return >= 0 接收字节数,< 0 失败。
*/ */
int gd32_can_send(can_channel_t channel, uint32_t ext_id, class SpiMaster {
const uint8_t *data, uint8_t dlc, public:
uint8_t *recv_buf, uint8_t recv_max, static SpiMaster& instance();
uint32_t timeout_ms);
/** /** @brief 初始化 SPI2 总线与目标从设备。 */
* @brief 发送一帧无需响应的 CANFD 数据,适合广播或通知场景。 esp_err_t init();
* @param channel CAN 通道(0~2)。
* @param ext_id 29 位扩展帧 ID。
* @param data 数据负载指针。
* @param dlc 数据长度,范围 1~64。
* @return >= 0 成功,< 0 失败。
*/
int gd32_can_send_no_reply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc);
/** // ============================================================
* @brief 初始化 SPI2 总线与目标从设备。 // 底层 SPI 传输
* @return ESP_OK 成功,其他失败。 // ============================================================
*/
esp_err_t spi_master_init(void);
/** /** @brief 执行一次同步 SPI 事务,先发送请求,再读取响应字节。 */
* @brief 执行一次同步 SPI 事务,先发送请求,再读取请求之后的响应字节。 esp_err_t syncTransfer(uint8_t cmd, const uint8_t *payload,
* @param cmd SPI 命令码;为 0 时不额外插入命令字节。 size_t payload_len, uint8_t *recv_buf,
* @param payload 发送数据指针。 size_t recv_len, uint32_t timeout_ms);
* @param payload_len 发送数据长度。
* @param recv_buf 接收缓冲区,可为 NULL。
* @param recv_len 期望接收的响应长度。
* @param timeout_ms 超时时间(毫秒)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
size_t payload_len, uint8_t *recv_buf,
size_t recv_len, uint32_t timeout_ms);
/** /** @brief 仅发送 SPI 请求数据,不关心返回内容。 */
* @brief 仅发送 SPI 请求数据,不关心返回内容。 esp_err_t send(uint8_t cmd, const uint8_t *payload, size_t len);
* @param cmd SPI 命令码;为 0 时仅发送 payload。
* @param payload 发送数据指针。
* @param len 发送数据长度。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len);
/** /** @brief 仅接收 SPI 数据,必要时先发送命令字节。 */
* @brief 仅接收 SPI 数据,必要时先发送命令字节,再发送哑数据产生时钟。 esp_err_t recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms);
* @param cmd SPI 命令码;为 0 时只发送哑数据。
* @param recv_buf 接收缓冲区。
* @param len 接收长度。
* @param timeout_ms 超时时间(毫秒)。
* @return ESP_OK 成功,其他失败。
*/
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms);
/** // ============================================================
* @brief 按 GD32 SPI OTA 最小协议执行一次请求事务 + 一次响应读取事务。 // SPI OTA 协议
* @param type 请求帧类型(REQ/DATA/CANCEL)。 // ============================================================
* @param payload 请求负载。
* @param payload_len 负载长度。
* @param resp_type 输出响应类型(ACK/WAIT/ERROR)。
* @param resp_payload 输出响应负载缓冲区。
* @param resp_max 输出响应负载最大长度。
* @param resp_len 输出实际响应负载长度。
* @param timeout_ms 超时。
*/
esp_err_t spi_ota_transfer(uint8_t type, const uint8_t *payload, size_t payload_len,
uint8_t *resp_type, uint8_t *resp_payload,
size_t resp_max, size_t *resp_len, uint32_t timeout_ms);
/** /** @brief 按 GD32 SPI OTA 最小协议执行一次请求 + 响应读取。 */
* @brief 仅执行 SPI OTA 响应读取事务,不重复发送请求帧。 esp_err_t otaTransfer(uint8_t type, const uint8_t *payload, size_t payload_len,
*/ uint8_t *resp_type, uint8_t *resp_payload,
esp_err_t spi_ota_poll_response(uint8_t *resp_type, uint8_t *resp_payload, size_t resp_max, size_t *resp_len, uint32_t timeout_ms);
size_t resp_max, size_t *resp_len, uint32_t timeout_ms);
/** /** @brief 仅执行 SPI OTA 响应读取事务,不重复发送请求帧。 */
* @brief 通知正在运行的 GD32 DCU_APP 设置 OTA 标志并软复位进入 Bootloader。 esp_err_t otaPollResponse(uint8_t *resp_type, uint8_t *resp_payload,
* @return ESP_OK 表示 APP 已确认;ESP_ERR_TIMEOUT 常表示 GD32 已在 Bootloader 或通信未就绪。 size_t resp_max, size_t *resp_len, uint32_t timeout_ms);
*/
esp_err_t gd32_app_request_bootloader(uint32_t timeout_ms);
/** /** @brief 通知 GD32 DCU_APP 设置 OTA 标志并软复位进入 Bootloader。 */
* @brief 向 GD32 APP 发送存活探测命令 (TYPE=0xA1),等待 APP 回复 ALIVE ACK。 esp_err_t appRequestBootloader(uint32_t timeout_ms);
* @param timeout_ms 超时时间(毫秒)。
* @param uptime_sec 输出 APP 运行秒数,可为 NULL。 /** @brief 向 GD32 APP 发送存活探测命令,等待 APP 回复 ALIVE ACK。 */
* @return ESP_OK APP 存活并回复;ESP_ERR_TIMEOUT 超时无响应。 esp_err_t appPing(uint32_t timeout_ms, uint32_t *uptime_sec);
*/
esp_err_t gd32_app_ping(uint32_t timeout_ms, uint32_t *uptime_sec); /** @brief 执行带重试的 SPI 同步传输。 */
esp_err_t transferWithRetry(uint8_t cmd, const uint8_t *payload,
size_t payload_len, uint8_t *recv_buf,
size_t recv_len, int max_retries);
// ============================================================
// CANFD 接口
// ============================================================
/** @brief 发送一帧 CANFD 数据,可选等待并读取响应内容。 */
int canSend(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc,
uint8_t *recv_buf, uint8_t recv_max,
uint32_t timeout_ms);
/** @brief 发送一帧无需响应的 CANFD 数据。 */
int canSendNoReply(can_channel_t channel, uint32_t ext_id,
const uint8_t *data, uint8_t dlc);
/** @brief 获取 SPI 设备句柄(供外部状态查询)。 */
spi_device_handle_t deviceHandle() const { return m_spiDevice; }
private:
SpiMaster() = default;
~SpiMaster() = default;
SpiMaster(const SpiMaster&) = delete;
SpiMaster& operator=(const SpiMaster&) = delete;
spi_device_handle_t m_spiDevice = NULL;
// 内部 SPI 传输
esp_err_t transferClocked(const uint8_t *tx_buf, size_t tx_len,
uint8_t *rx_buf, size_t rx_len);
esp_err_t transferRequestResponse(uint8_t cmd, const uint8_t *payload, size_t payload_len,
uint8_t *rx_buf, size_t rx_len);
// OTA 帧工具(静态,无状态)
static uint8_t otaCrc8(const uint8_t *data, size_t len);
static bool otaBuildFrame(uint8_t type, const uint8_t *payload, size_t payload_len,
uint8_t *out, size_t *out_len);
static bool otaParseFrame(const uint8_t *frame, size_t frame_len,
uint8_t *type, const uint8_t **payload, size_t *payload_len);
static bool otaFindFrame(const uint8_t *buf, size_t buf_len,
uint8_t *type, const uint8_t **payload, size_t *payload_len,
size_t *frame_offset);
};
#endif // SPI_MASTER_H #endif // SPI_MASTER_H