feat(ota_relay): 实现WiFi/AP双模式和CANFD 3通道SPI协议
- 新增AP/STA双工作模式,通过app_config.h配置切换 - 实现CANFD 3通道SPI协议,支持主通信通道和备用通道 - 添加GD32命令ID定义和HTTP API接口 - 集成上位机WebUI,支持固件上传和CAN帧发送 refactor(debug_log): 支持UDP+UART双通道调试输出 - 新增UART0串口调试功能,TX=GPIO21, RX=GPIO20, 115200波特率 - 实现UDP socket延迟创建机制,避免网络初始化冲突 - 调试日志同时输出到UDP和UART双通道 docs(README): 更新项目文档和目录结构说明 - 更新硬件平台和模块功能描述 - 添加WiFi工作模式配置说明 - 补充CANFD协议和GD32命令定义 - 增加HTTP API和上位机WebUI使用指南 fix(health_monitor): 优化健康监控和系统状态报告 - 增强温度和电压监控的警告提示信息 - 添加WiFi RSSI监控和健康状态更新 - 修复安全关机触发条件和状态变更日志 style(main): 优化程序入口和任务创建流程 - 修复网络接口初始化顺序问题 - 添加任务创建结果检查和错误处理 - 增加系统启动信息和CPU频率显示 - 新增周期性状态监控任务,每10秒打印系统状态
This commit is contained in:
@@ -19,7 +19,8 @@ void protocol_init(void)
|
||||
{
|
||||
if (s_protocol_initialized) return;
|
||||
|
||||
DEBUG_INFO("PROT", "初始化协议层(CRC16、硬件AES-CBC)...");
|
||||
DEBUG_INFO("PROT", "=== 初始化协议层 ===");
|
||||
DEBUG_INFO("PROT", "协议层功能: CRC16 Modbus + 硬件 AES-128-CBC");
|
||||
|
||||
s_protocol_initialized = true;
|
||||
DEBUG_INFO("PROT", "协议层就绪");
|
||||
@@ -65,6 +66,8 @@ int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_fr
|
||||
out_frame[idx++] = (uint8_t)(crc & 0xFF);
|
||||
out_frame[idx++] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
|
||||
DEBUG_DBG("PROT", "帧构建: cmd=0x%02X, len=%zu, crc=0x%04X", cmd, len, crc);
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -73,13 +76,14 @@ int frame_parse(const uint8_t *frame, size_t frame_len,
|
||||
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len)
|
||||
{
|
||||
if (frame_len < 6) {
|
||||
DEBUG_WARN("PROT", "帧太短: %zu < 6", frame_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
if (frame[idx++] != FRAME_SOF) {
|
||||
DEBUG_WARN("PROT", "无效 SOF: 0x%02X", frame[0]);
|
||||
DEBUG_WARN("PROT", "无效 SOF: 0x%02X (期望 0x%02X)", frame[0], FRAME_SOF);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -89,13 +93,13 @@ int frame_parse(const uint8_t *frame, size_t frame_len,
|
||||
idx += 2;
|
||||
|
||||
if (payload_len > FRAME_MAX_PAYLOAD) {
|
||||
DEBUG_ERR("PROT", "Payload 长度太大: %zu", payload_len);
|
||||
DEBUG_ERR("PROT", "Payload 长度太大: %zu > %d", payload_len, FRAME_MAX_PAYLOAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t expected_len = 1 + 1 + 2 + payload_len + 2;
|
||||
if (frame_len < expected_len) {
|
||||
DEBUG_WARN("PROT", "帧太短: %zu < %zu", frame_len, expected_len);
|
||||
DEBUG_WARN("PROT", "帧太短: %zu < %zu (期望)", frame_len, expected_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -103,10 +107,12 @@ int frame_parse(const uint8_t *frame, size_t frame_len,
|
||||
uint16_t calc_crc = crc16_modbus(frame, idx + payload_len);
|
||||
|
||||
if (received_crc != calc_crc) {
|
||||
DEBUG_WARN("PROT", "CRC 不匹配: 0x%04X vs 0x%04X", received_crc, calc_crc);
|
||||
DEBUG_WARN("PROT", "CRC 不匹配: 收到=0x%04X, 计算=0x%04X", received_crc, calc_crc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_DBG("PROT", "帧解析: cmd=0x%02X, len=%zu, crc=0x%04X OK", cmd, payload_len, calc_crc);
|
||||
|
||||
if (out_payload && out_len) {
|
||||
memcpy(out_payload, &frame[idx], payload_len);
|
||||
*out_len = payload_len;
|
||||
@@ -124,6 +130,7 @@ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
|
||||
uint8_t *ciphertext_out)
|
||||
{
|
||||
if (!plaintext || !key || !iv || !ciphertext_out) {
|
||||
DEBUG_ERR("PROT", "AES 加密参数无效");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -136,6 +143,8 @@ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
|
||||
if (pad == 0) pad = 16;
|
||||
memset(&padded[len], pad, pad);
|
||||
|
||||
DEBUG_DBG("PROT", "AES 加密: 明文=%zu 字节, 填充后=%zu 字节, 填充值=0x%02X", len, padded_len, pad);
|
||||
|
||||
// 硬件 AES-CBC 加密(使用 ESP32-C3 硬件 AES 加速器)
|
||||
esp_aes_context aes_ctx;
|
||||
esp_aes_init(&aes_ctx);
|
||||
@@ -155,6 +164,7 @@ int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
|
||||
esp_aes_free(&aes_ctx);
|
||||
|
||||
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);
|
||||
@@ -168,14 +178,17 @@ int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
|
||||
uint8_t *plaintext_out)
|
||||
{
|
||||
if (!ciphertext || !key || !iv || !plaintext_out) {
|
||||
DEBUG_ERR("PROT", "AES 解密参数无效");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (len % 16 != 0 || len == 0) {
|
||||
DEBUG_ERR("PROT", "Ciphertext 长度无效: %zu", len);
|
||||
DEBUG_ERR("PROT", "Ciphertext 长度无效: %zu (必须为 16 的倍数)", len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_DBG("PROT", "AES 解密: 密文=%zu 字节", len);
|
||||
|
||||
// 硬件 AES-CBC 解密(使用 ESP32-C3 硬件 AES 加速器)
|
||||
esp_aes_context aes_ctx;
|
||||
esp_aes_init(&aes_ctx);
|
||||
@@ -206,7 +219,10 @@ int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len - pad;
|
||||
size_t orig_len = len - pad;
|
||||
DEBUG_DBG("PROT", "AES 解密成功: %zu -> %zu 字节 (填充=%d)", len, orig_len, pad);
|
||||
|
||||
return orig_len;
|
||||
}
|
||||
|
||||
// 搅伴算法 - 生成随机数
|
||||
|
||||
@@ -20,7 +20,7 @@ spi_device_handle_t g_spi_device = NULL;
|
||||
// SPI2 总线初始化
|
||||
esp_err_t spi_master_init(void)
|
||||
{
|
||||
DEBUG_INFO("SPI", "初始化 SPI2 (VSPI)...");
|
||||
DEBUG_INFO("SPI", "=== 初始化 SPI2 (VSPI) ===");
|
||||
|
||||
spi_bus_config_t bus_cfg = {};
|
||||
bus_cfg.mosi_io_num = 8;
|
||||
@@ -30,6 +30,10 @@ esp_err_t spi_master_init(void)
|
||||
bus_cfg.quadhd_io_num = -1;
|
||||
bus_cfg.max_transfer_sz = 256;
|
||||
|
||||
DEBUG_INFO("SPI", "SPI2 引脚: MOSI=GPIO8, 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);
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_ERR("SPI", "SPI 总线初始化失败: %s", esp_err_to_name(ret));
|
||||
@@ -53,7 +57,9 @@ esp_err_t spi_master_init(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEBUG_INFO("SPI", "SPI2 已初始化: CLK=40MHz, CS=GPIO4");
|
||||
DEBUG_INFO("SPI", "SPI2 已初始化: CLK=%dMHz, CS=GPIO%d, 模式=%d",
|
||||
SPI_CLOCK_HZ / 1000000, SPI_MASTER_CS_GPIO, SPI_MODE);
|
||||
DEBUG_INFO("SPI", "SPI 设备句柄: %p", g_spi_device);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -69,6 +75,9 @@ esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
DEBUG_DBG("SPI", "SPI 传输: cmd=0x%02X, 发送=%zu 字节, 接收=%zu 字节",
|
||||
cmd, payload_len, recv_len);
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.cmd = cmd;
|
||||
t.length = payload_len * 8;
|
||||
@@ -83,6 +92,8 @@ esp_err_t spi_sync_transfer(uint8_t cmd, const uint8_t *payload,
|
||||
esp_err_t ret = spi_device_transmit(g_spi_device, &t);
|
||||
if (ret != ESP_OK) {
|
||||
DEBUG_WARN("SPI", "SPI 发送 cmd=0x%02X 失败: %s", cmd, esp_err_to_name(ret));
|
||||
} else {
|
||||
DEBUG_DBG("SPI", "SPI 传输完成: cmd=0x%02X", cmd);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -110,19 +121,22 @@ static int can_frame_send(can_channel_t channel, uint32_t ext_id,
|
||||
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", "DLC 无效: %d", 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", "通道号无效: %d", channel);
|
||||
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;
|
||||
@@ -159,11 +173,12 @@ static int can_frame_send(can_channel_t channel, uint32_t ext_id,
|
||||
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",
|
||||
resp_channel, resp_id, 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;
|
||||
}
|
||||
|
||||
@@ -192,18 +207,24 @@ esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
|
||||
esp_err_t ret;
|
||||
int retry_count = 0;
|
||||
|
||||
DEBUG_INFO("SPI", "SPI 传输 (带重试): cmd=0x%02X, 最大重试=%d", cmd, max_retries);
|
||||
|
||||
while (retry_count < max_retries) {
|
||||
ret = spi_sync_transfer(cmd, payload, payload_len,
|
||||
recv_buf, recv_len, 3000);
|
||||
if (ret == ESP_OK) {
|
||||
if (retry_count > 0) {
|
||||
DEBUG_INFO("SPI", "SPI 传输成功 (重试 %d 次后成功)", retry_count);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
retry_count++;
|
||||
DEBUG_WARN("SPI", "SPI 重试 %d/%d, cmd=0x%02X", retry_count, max_retries, cmd);
|
||||
DEBUG_WARN("SPI", "SPI 重试 %d/%d, cmd=0x%02X, err=%s",
|
||||
retry_count, max_retries, cmd, esp_err_to_name(ret));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败", max_retries);
|
||||
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败, cmd=0x%02X", max_retries, cmd);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user