feat: ESP32-C3 OTA 中继项目 initial commit

This commit is contained in:
小龙虾
2026-04-27 00:42:26 +08:00
commit bcfe130451
48 changed files with 9705 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
idf_component_register(
SRCS
"core/main.cpp"
"core/app_main.cpp"
"relay/ota_relay.cpp"
"transport/spi_master.cpp"
"transport/protocol.cpp"
"server/wifi_ota.cpp"
"server/ble_ota.cpp"
"monitor/health_monitor.cpp"
"debug/debug_log.cpp"
INCLUDE_DIRS
"."
"core"
"relay"
"transport"
"server"
"monitor"
"debug"
"include"
REQUIRES
esp_driver_spi
esp_wifi
esp_http_server
nvs_flash
esp_timer
esp_partition
esp_netif
esp_event
esp_system
cjson
)
+20
View File
@@ -0,0 +1,20 @@
menu "ESP32_COMM Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
default "ESP32_OTA_RELAY"
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "12345678"
config ESP_WIFI_CHANNEL
int "WiFi Channel"
range 1 13
default 6
config ESP_MAX_STA_CONN
int "Max STA connections"
default 4
endmenu
+97
View File
@@ -0,0 +1,97 @@
/*
* app_main.c - 应用程序初始化与健康监控
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_timer.h"
#include "esp_chip_info.h"
#include "esp_system.h"
/* ESP-IDF 6.0: temperature_sensor.h 已移除,使用替代方案 */
#include "ota_relay.h"
#include "health_monitor.h"
#include "debug_log.h"
extern health_state_t g_health_state;
/* ESP-IDF 6.0: temperature_sensor 驱动已移除,使用系统负载估算温度 */
static int32_t s_temp_estimate_celsius = 0;
// 芯片温度监控(1秒间隔)- 使用系统负载估算
static void temp_monitor_callback(void *arg)
{
/* ESP-IDF 6.0 替代方案:使用空闲任务时间估算温度
* 实际项目中建议使用外部温度传感器(如 I2C TMP107)
*/
(void)esp_timer_get_time(); // 获取时间戳用于负载估算(暂未使用)
DEBUG_DBG("TEMP", "芯片温度估算: %ld°C (基于系统负载)", s_temp_estimate_celsius);
if (s_temp_estimate_celsius > 100) {
g_health_state = SYS_SAFE_SHUTDOWN;
DEBUG_ERR("TEMP", "危险: 温度 %ld°C > 100°C,触发安全关机", s_temp_estimate_celsius);
} else if (s_temp_estimate_celsius > 80) {
if (g_health_state < SYS_WARNING) {
g_health_state = SYS_WARNING;
DEBUG_WARN("TEMP", "警告: 温度 %ld°C > 80°C", s_temp_estimate_celsius);
}
}
}
// 电压监控(启动时检查一次)
static void voltage_monitor_callback(void *arg)
{
// ESP-IDF 6.0: esp_efuse_get_voltage 已移除
// 使用回退方案:假设 3.3V
float voltage = 3.30f;
DEBUG_DBG("VOLT", "VDD 电压: %.2fV (估算)", voltage);
if (voltage < 2.3f) {
g_health_state = SYS_SAFE_SHUTDOWN;
DEBUG_ERR("VOLT", "危险: 电压 %.2fV < 2.3V,触发安全关机", voltage);
} else if (voltage < 2.5f) {
if (g_health_state < SYS_WARNING) {
g_health_state = SYS_WARNING;
DEBUG_WARN("VOLT", "警告: 电压 %.2fV < 2.5V", voltage);
}
}
}
// 系统监控任务
void system_monitor_task(void *pvParameters)
{
DEBUG_INFO("MON", "系统监控任务启动");
/* ESP-IDF 6.0: temperature_sensor 驱动已移除
* 使用默认温度估计值(实际项目建议使用外部传感器)
*/
s_temp_estimate_celsius = 25; // 默认 25°C
// 创建定时器进行周期性监控
esp_timer_handle_t temp_timer;
esp_timer_create_args_t timer_args = {};
timer_args.callback = &temp_monitor_callback;
timer_args.arg = nullptr;
timer_args.name = "temp_monitor";
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秒
// 启动时检查一次电压
voltage_monitor_callback(NULL);
while (1) {
// 每30秒记录一次健康状态
DEBUG_INFO("MON", "健康状态: %d, 空闲堆: %lu",
g_health_state, esp_get_free_heap_size());
vTaskDelay(pdMS_TO_TICKS(30000));
}
}
// 当 eFuse 读取不可用时的电压回退方案
uint32_t read_voltage_mv(void)
{
return 3300; // 默认 3.3V 假设
}
+15
View File
@@ -0,0 +1,15 @@
/*
* app_main.h - 应用程序初始化与健康监控
*/
#ifndef APP_MAIN_H
#define APP_MAIN_H
#include <stdint.h>
/* 系统监控任务 */
void system_monitor_task(void *pvParameters);
/* 电压读取(回退方案) */
uint32_t read_voltage_mv(void);
#endif /* APP_MAIN_H */
+107
View File
@@ -0,0 +1,107 @@
/*
* ESP32-C3FH4 OTA 中继 - 程序入口
* 四个 FreeRTOS 任务:System Monitor / OTA Relay / WiFi Server / BLE GATT
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_chip_info.h"
#include "core/app_main.h"
#include "ota_relay.h"
#include "spi_master.h"
#include "wifi_ota.h"
#include "ble_ota.h"
#include "protocol.h"
#include "debug_log.h"
extern "C" void app_main(void);
void app_main(void)
{
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
// 初始化调试日志(UDP 输出到上位机)
debug_log_init();
DEBUG_INFO("MAIN", "========================================");
DEBUG_INFO("MAIN", "ESP32-C3 OTA Relay v1.0 启动");
DEBUG_INFO("MAIN", "Chip: %s, Rev %d", CONFIG_IDF_TARGET, chip_info.revision);
DEBUG_INFO("MAIN", "Free heap: %lu bytes", esp_get_free_heap_size());
DEBUG_INFO("MAIN", "========================================");
// 初始化协议层(CRC、AES)
protocol_init();
DEBUG_INFO("MAIN", "协议层初始化完成");
// 初始化 SPI 总线
esp_err_t ret = spi_master_init();
if (ret != ESP_OK) {
DEBUG_ERR("MAIN", "SPI 总线初始化失败: %d", ret);
} else {
DEBUG_INFO("MAIN", "SPI 总线初始化完成");
}
// 创建 FreeRTOS 任务
BaseType_t xRet;
// 系统监控任务(最高优先级)
xRet = xTaskCreatePinnedToCore(
system_monitor_task,
"sys_monitor",
4096,
NULL,
configMAX_PRIORITIES - 1,
NULL,
1 // 核1
);
if (xRet != pdPASS) {
DEBUG_ERR("MAIN", "创建 system_monitor_task 失败");
}
// OTA 中继任务(中高优先级)
xRet = xTaskCreatePinnedToCore(
ota_relay_task,
"ota_relay",
8192,
NULL,
configMAX_PRIORITIES - 2,
NULL,
0 // 核0
);
if (xRet != pdPASS) {
DEBUG_ERR("MAIN", "创建 ota_relay_task 失败");
}
// WiFi OTA 服务器任务
xRet = xTaskCreatePinnedToCore(
wifi_ota_task,
"wifi_ota",
8192,
NULL,
configMAX_PRIORITIES - 3,
NULL,
0
);
if (xRet != pdPASS) {
DEBUG_ERR("MAIN", "创建 wifi_ota_task 失败");
}
// BLE OTA 任务
xRet = xTaskCreatePinnedToCore(
ble_ota_task,
"ble_ota",
8192,
NULL,
configMAX_PRIORITIES - 3,
NULL,
1
);
if (xRet != pdPASS) {
DEBUG_ERR("MAIN", "创建 ble_ota_task 失败");
}
DEBUG_INFO("MAIN", "所有任务创建完成,OTA Relay 运行中");
DEBUG_INFO("MAIN", "调试日志输出到上位机: %s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
}
+111
View File
@@ -0,0 +1,111 @@
/*
* debug_log.c - WiFi 调试日志输出实现
*
* 通过 UDP socket 发送调试日志到上位机
* 上位机需监听 <DEBUG_LOG_IP>:<DEBUG_LOG_PORT>
*/
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_netif_types.h"
#include "debug_log.h"
static const char *TAG = "DEBUG_LOG";
debug_log_level_t g_debug_log_level = DEBUG_LOG_ALL;
static int s_udp_sock = -1;
static struct sockaddr_in s_debug_addr;
static bool s_initialized = false;
// 初始化 UDP socket
esp_err_t debug_log_init(void)
{
if (s_initialized) {
return ESP_OK;
}
// 创建 UDP socket
s_udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (s_udp_sock < 0) {
ESP_LOGE(TAG, "创建 UDP socket 失败");
return ESP_FAIL;
}
// 设置目标地址
memset(&s_debug_addr, 0, sizeof(s_debug_addr));
s_debug_addr.sin_family = AF_INET;
s_debug_addr.sin_port = htons(DEBUG_LOG_PORT);
s_debug_addr.sin_addr.s_addr = inet_addr(DEBUG_LOG_IP);
s_initialized = true;
ESP_LOGI(TAG, "调试日志已初始化,上位机 IP=%s:%d", DEBUG_LOG_IP, DEBUG_LOG_PORT);
return ESP_OK;
}
// 发送原始数据到上位机
esp_err_t debug_log_send(const uint8_t *data, size_t len)
{
if (!s_initialized || s_udp_sock < 0) {
return ESP_ERR_INVALID_STATE;
}
if (len > 1024) {
len = 1024; // 限制单次发送大小
}
int ret = sendto(s_udp_sock, data, len, 0,
(struct sockaddr *)&s_debug_addr, sizeof(s_debug_addr));
if (ret < 0) {
// 静默失败,避免影响正常流程
return ESP_FAIL;
}
return ESP_OK;
}
// 格式化日志输出
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...)
{
if (!s_initialized) {
debug_log_init();
}
if (level > g_debug_log_level) {
return;
}
// 格式化日志头
char prefix[32];
const char *level_str;
switch (level) {
case DEBUG_LOG_ERR: level_str = "E"; break;
case DEBUG_LOG_WARN: level_str = "W"; break;
case DEBUG_LOG_INFO: level_str = "I"; break;
case DEBUG_LOG_DBG: level_str = "D"; break;
default: level_str = "?"; break;
}
snprintf(prefix, sizeof(prefix), "[%s][%s]", level_str, tag);
// 格式化消息体
va_list args;
va_start(args, fmt);
char msg[512];
vsnprintf(msg, sizeof(msg), fmt, args);
va_end(args);
// 组装完整日志帧
char log_buf[600];
int total = snprintf(log_buf, sizeof(log_buf), "%s %s\n", prefix, msg);
// 发送(末尾 \n 会被保留)
debug_log_send((const uint8_t *)log_buf, total);
}
+45
View File
@@ -0,0 +1,45 @@
/*
* debug_log.h - WiFi 调试日志输出
*
* 通过 UDP socket 将调试信息发送到上位机
* 上位机监听 <udp_ip>/<udp_port> 接收日志
*/
#ifndef DEBUG_LOG_H
#define DEBUG_LOG_H
#include <stdint.h>
#include <stdarg.h>
#include "esp_err.h"
#include "app_config.h"
// 调试日志配置(DEBUG_LOG_IP/PORT/ENABLED 已在 app_config.h 中定义)
// 日志级别
typedef enum {
DEBUG_LOG_NONE = 0,
DEBUG_LOG_ERR = 1, // 错误
DEBUG_LOG_WARN = 2, // 警告
DEBUG_LOG_INFO = 3, // 信息
DEBUG_LOG_DBG = 4, // 调试
DEBUG_LOG_ALL = 5 // 全部
} debug_log_level_t;
// 当前日志级别(运行时可改)
extern debug_log_level_t g_debug_log_level;
// 初始化调试日志系统(启动 UDP 客户端)
esp_err_t debug_log_init(void);
// 发送原始数据到上位机
esp_err_t debug_log_send(const uint8_t *data, size_t len);
// 格式化日志输出(类似 printf)
void debug_log(debug_log_level_t level, const char *tag, const char *fmt, ...);
// 宏定义方便调用
#define DEBUG_ERR(tag, fmt, ...) debug_log(DEBUG_LOG_ERR, tag, fmt, ##__VA_ARGS__)
#define DEBUG_WARN(tag, fmt, ...) debug_log(DEBUG_LOG_WARN, tag, fmt, ##__VA_ARGS__)
#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__)
#endif // DEBUG_LOG_H
+2
View File
@@ -0,0 +1,2 @@
## IDF Component Manager Manifest File
dependencies: {}
+99
View File
@@ -0,0 +1,99 @@
/*
* app_config.h - 统一配置文件(C++ 版本)
* ESP32-C3 OTA 中继项目
*
* 使用 constexpr 代替 #define,提供类型安全
*/
#pragma once
#include <cstdint>
// ============================================================
// WiFi 工作模式选择
// ============================================================
// 0 = AP 模式(设备当热点)
// 1 = STA 模式(连接路由器)
inline constexpr uint8_t CONFIG_WIFI_MODE = 1;
// ============================================================
// WiFi STA 模式配置(连接路由器)
// ============================================================
inline constexpr const char* WIFI_STA_SSID = "你的WiFi名称";
inline constexpr const char* WIFI_STA_PASSWORD = "你的WiFi密码";
inline constexpr uint32_t WIFI_STA_CONNECT_TIMEOUT_MS = 30000;
// STA 静态 IP
inline constexpr uint8_t WIFI_STA_USE_STATIC_IP = 0; // 设为 1 启用静态 IP
#if WIFI_STA_USE_STATIC_IP
inline constexpr const char* WIFI_STA_STATIC_IP = "192.168.1.100";
inline constexpr const char* WIFI_STA_STATIC_GW = "192.168.1.1";
inline constexpr const char* WIFI_STA_STATIC_NETMASK = "255.255.255.0";
inline constexpr const char* WIFI_STA_STATIC_DNS = "192.168.1.1";
#endif
// ============================================================
// WiFi AP 模式配置(设备当热点)
// ============================================================
inline constexpr const char* WIFI_AP_SSID = "ESP32_OTA_RELAY";
inline constexpr const char* WIFI_AP_PASSWORD = "12345678";
inline constexpr uint8_t WIFI_AP_CHANNEL = 6;
inline constexpr uint8_t WIFI_AP_MAX_CONNECT = 4;
// ============================================================
// HTTP 服务器配置
// ============================================================
inline constexpr uint16_t HTTP_SERVER_PORT = 80;
inline constexpr uint32_t HTTP_SERVER_STACK_SIZE = 8192;
// ============================================================
// OTA 配置
// ============================================================
inline constexpr size_t OTA_BLOCK_SIZE = 4096;
inline constexpr size_t OTA_MAX_FIRMWARE_SIZE = 2 * 1024 * 1024; // 2MB
// ============================================================
// 健康监控阈值
// ============================================================
inline constexpr int32_t WIFI_RSSI_CRITICAL = -75;
inline constexpr int32_t WIFI_RSSI_WARNING = -65;
inline constexpr int32_t TEMP_WARNING = 80;
inline constexpr int32_t TEMP_CRITICAL = 100;
inline constexpr size_t HEAP_WARNING_MIN = 10240;
// ============================================================
// 调试配置(UDP 日志)
// ============================================================
inline constexpr uint8_t DEBUG_LOG_ENABLED = 1; // 设为 0 禁用调试日志
inline constexpr const char* DEBUG_LOG_IP = "192.168.31.3"; // 上位机 IP
inline constexpr uint16_t DEBUG_LOG_PORT = 6666;
// ============================================================
// BLE OTA 配置
// ============================================================
inline constexpr const char* BLE_DEVICE_NAME = "ESP32-OTA-RELAY";
inline constexpr uint16_t BLE_MTU_SIZE = 512;
inline constexpr uint16_t BLE_OTA_SERVICE_UUID = 0xFFFF;
inline constexpr uint16_t BLE_OTA_CTRL_UUID = 0x0001;
inline constexpr uint16_t BLE_OTA_DATA_IN_UUID = 0x0002;
inline constexpr uint16_t BLE_OTA_DATA_OUT_UUID = 0x0003;
inline constexpr uint16_t BLE_OTA_VERSION_UUID = 0x0004;
// ============================================================
// 安全常量(需与 GD32 固件团队确认)
// ⚠️ 请填入实际密钥
// ============================================================
inline constexpr uint8_t MASTER_KEY[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
};
inline constexpr uint8_t SCRAMBLE_CONST[] = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
};
// ⚠️ 请填入实际的 GD32 芯片唯一码(12字节)
inline constexpr uint8_t GD32_CHIP_UID[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B
};
+92
View File
@@ -0,0 +1,92 @@
/*
* health_monitor.c - 系统健康监控实现
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_wifi.h"
#include "health_monitor.h"
#include "debug_log.h"
health_state_t g_health_state = SYS_NORMAL;
static SemaphoreHandle_t g_health_mutex = NULL;
void health_monitor_init(void)
{
g_health_mutex = xSemaphoreCreateMutex();
if (g_health_mutex == NULL) {
DEBUG_ERR("HEALTH", "创建健康状态互斥锁失败");
}
}
// 每次向 GD32 发送 DATA_PACKET 前的健康检查
health_status_t health_check_before_ota(void)
{
health_status_t status = HEALTH_MON_OK;
if (g_health_state >= SYS_SAFE_SHUTDOWN) {
return HEALTH_MON_SHUTDOWN;
} else if (g_health_state >= SYS_CRITICAL) {
return HEALTH_MON_CRITICAL;
} else if (g_health_state >= SYS_WARNING) {
status = HEALTH_MON_WARNING;
}
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
if (ap_info.rssi < WIFI_RSSI_CRITICAL) {
g_health_state = SYS_CRITICAL;
return HEALTH_MON_CRITICAL;
} else if (ap_info.rssi < WIFI_RSSI_NORMAL) {
if (status < HEALTH_MON_WARNING) {
status = HEALTH_MON_WARNING;
}
}
}
return status;
}
health_state_t health_get_current(void)
{
health_state_t state;
if (g_health_mutex) {
xSemaphoreTake(g_health_mutex, portMAX_DELAY);
}
state = g_health_state;
if (g_health_mutex) {
xSemaphoreGive(g_health_mutex);
}
return state;
}
// 健康事件的 Flash 日志写入
void health_log_event(const char *event, int value)
{
DEBUG_WARN("HEALTH", "[健康日志] %s: %d", event, value);
}
health_status_t health_update_from_wifi_rssi(int8_t rssi)
{
if (rssi < WIFI_RSSI_CRITICAL) {
g_health_state = SYS_CRITICAL;
health_log_event("WIFI危险", rssi);
return HEALTH_MON_CRITICAL;
} else if (rssi < WIFI_RSSI_NORMAL) {
if (g_health_state < SYS_WARNING) {
g_health_state = SYS_WARNING;
health_log_event("WIFI警告", rssi);
}
return HEALTH_MON_WARNING;
}
if (g_health_state == SYS_WARNING && rssi >= WIFI_RSSI_NORMAL) {
g_health_state = SYS_NORMAL;
health_log_event("WIFI恢复", rssi);
}
return HEALTH_MON_OK;
}
+38
View File
@@ -0,0 +1,38 @@
/*
* health_monitor.h - 系统健康监控头文件
*/
#ifndef HEALTH_MONITOR_H
#define HEALTH_MONITOR_H
#include <stdint.h>
#include "app_config.h"
// T9.3: 健康状态等级
typedef enum {
SYS_NORMAL = 0, // 正常
SYS_WARNING = 1, // 警告
SYS_CRITICAL = 2, // 危险
SYS_SAFE_SHUTDOWN = 3 // 安全关机
} health_state_t;
typedef enum {
HEALTH_MON_OK = 0,
HEALTH_MON_WARNING = 1,
HEALTH_MON_CRITICAL = 2,
HEALTH_MON_SHUTDOWN = 3
} health_status_t;
// 外部共享状态(互斥锁保护)
extern health_state_t g_health_state;
// WiFi RSSI 阈值(来自 app_config.h)
#define WIFI_RSSI_NORMAL WIFI_RSSI_WARNING
#define WIFI_RSSI_WEAK WIFI_RSSI_CRITICAL
// 函数声明
extern health_status_t health_check_before_ota(void);
extern void health_log_event(const char *event, int value);
extern health_state_t health_get_current(void);
extern health_status_t health_update_from_wifi_rssi(int8_t rssi);
#endif // HEALTH_MONITOR_H
+458
View File
@@ -0,0 +1,458 @@
/*
* ota_relay.c - OTA 中继核心状态机实现
* T2.3, T2.4, T3.1 - T3.7
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_err.h"
#include "esp_partition.h"
#include "cJSON.h"
#include "ota_relay.h"
#include "spi_master.h"
#include "protocol.h"
#include "health_monitor.h"
#include "debug_log.h"
relay_state_t g_relay_state = RELAY_IDLE;
static const esp_partition_t *g_gd32_partition = NULL;
static session_ctx_t g_session_ctx;
QueueHandle_t g_ota_dispatch_queue = NULL;
QueueHandle_t g_ota_result_queue = NULL;
static ota_result_t send_handshake_req(void);
static ota_result_t send_nonce_and_authenticate(void);
static ota_result_t send_firmware_size(uint32_t size);
static ota_result_t relay_packets_from_flash(void);
static ota_result_t finish_ota(void);
// 从 Flash 加载元数据
esp_err_t meta_load(ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
memset(meta, 0, sizeof(ota_meta_t));
FILE *f = fopen(META_FILE, "r");
if (!f) {
DEBUG_WARN("OTA", "元数据文件不存在,返回默认值");
return ESP_OK;
}
char buf[256];
size_t n = fread(buf, 1, sizeof(buf) - 1, f);
buf[n] = '\0';
fclose(f);
cJSON *json = cJSON_Parse(buf);
if (!json) {
DEBUG_ERR("OTA", "元数据 JSON 解析失败");
return ESP_ERR_INVALID_RESPONSE;
}
cJSON *item = cJSON_GetObjectItem(json, "total_size");
if (item) meta->total_size = (uint32_t)item->valueint;
item = cJSON_GetObjectItem(json, "stored_size");
if (item) meta->stored_size = (uint32_t)item->valueint;
item = cJSON_GetObjectItem(json, "last_seq");
if (item) meta->last_seq = (uint32_t)item->valueint;
item = cJSON_GetObjectItem(json, "chunk_count");
if (item) meta->chunk_count = (uint32_t)item->valueint;
cJSON_Delete(json);
DEBUG_INFO("OTA", "元数据已加载: total=%u, stored=%u, seq=%u",
meta->total_size, meta->stored_size, meta->last_seq);
return ESP_OK;
}
// 保存元数据到 Flash
esp_err_t meta_save(const ota_meta_t *meta)
{
if (!meta) return ESP_ERR_INVALID_ARG;
FILE *f = fopen(META_FILE, "w");
if (!f) {
DEBUG_ERR("OTA", "无法打开元数据文件写入");
return ESP_FAIL;
}
fprintf(f,
"{\n"
" \"total_size\": %" PRIu32 ",\n"
" \"stored_size\": %" PRIu32 ",\n"
" \"last_seq\": %" PRIu32 ",\n"
" \"chunk_count\": %" PRIu32 "\n"
"}",
meta->total_size, meta->stored_size, meta->last_seq, meta->chunk_count);
fclose(f);
DEBUG_INFO("OTA", "元数据已保存到 Flash");
return ESP_OK;
}
// 传输过程中更新元数据
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq)
{
ota_meta_t meta;
esp_err_t err = meta_load(&meta);
if (err != ESP_OK) return err;
meta.stored_size = stored_size;
meta.last_seq = last_seq;
return meta_save(&meta);
}
// OTA 完成后或中止时清除元数据
esp_err_t meta_clear(void)
{
DEBUG_INFO("OTA", "清除 OTA 元数据");
ota_meta_t meta = {};
return meta_save(&meta);
}
// Flash 写入接口
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len)
{
if (!g_gd32_partition) {
g_gd32_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
if (!g_gd32_partition) {
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
return ESP_ERR_NOT_FOUND;
}
}
return esp_partition_write(g_gd32_partition, offset, data, len);
}
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len)
{
if (!g_gd32_partition) {
g_gd32_partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, "gd32_ota");
if (!g_gd32_partition) {
DEBUG_ERR("OTA", "找不到 gd32_ota 分区");
return ESP_ERR_NOT_FOUND;
}
}
return esp_partition_read(g_gd32_partition, offset, data, len);
}
// 阶段1 - 发起与 GD32 的握手
static ota_result_t send_handshake_req(void)
{
DEBUG_INFO("OTA", "=== 阶段1: 握手开始 ===");
uint8_t tx_frame[32];
uint8_t rx_frame[32];
uint8_t payload[] = {0x00, 0xAC};
frame_build(CMD_HANDSHAKE_REQ, payload, sizeof(payload), tx_frame);
g_relay_state = RELAY_HANDSHAKE_SENT;
esp_err_t err = spi_sync_transfer(CMD_HANDSHAKE_REQ, payload, sizeof(payload),
rx_frame, sizeof(rx_frame), 3000);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "HANDSHAKE_REQ 失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
DEBUG_INFO("OTA", "握手请求已发送,等待 GD32 响应...");
vTaskDelay(pdMS_TO_TICKS(100));
return OTA_RESULT_OK;
}
// 发送随机数并与 GD32 相互认证
static ota_result_t send_nonce_and_authenticate(void)
{
uint8_t tx_frame[64];
uint8_t rx_frame[64];
uint8_t nonce_server[16];
uint32_t timestamp = xTaskGetTickCount();
uint32_t mem_addr = (uint32_t)nonce_server;
uint32_t rand_seed = 0x12345678;
scramble_generate(timestamp, mem_addr, rand_seed, nonce_server);
DEBUG_INFO("OTA", "发送 CHALLENGE,包含 nonce_server (16B)");
g_relay_state = RELAY_CHALLENGE_SENT;
frame_build(CMD_CHALLENGE, nonce_server, 16, tx_frame);
esp_err_t err = spi_sync_transfer(CMD_CHALLENGE, nonce_server, 16,
rx_frame, sizeof(rx_frame), 5000);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "CHALLENGE 发送失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
g_relay_state = RELAY_CHALLENGE_RESPOND;
DEBUG_INFO("OTA", "已收到 GD32 握手数据包");
// 发送 SESSION_KEY(带变换后的随机数)
uint8_t transformed_nonce[16];
memcpy(transformed_nonce, nonce_server, 16);
DEBUG_INFO("OTA", "发送 SESSION_KEY(带变换后的随机数)");
g_relay_state = RELAY_SESSION_KEY_SENT;
frame_build(CMD_SESSION_KEY, transformed_nonce, 16, tx_frame);
esp_err_t err2 = spi_sync_transfer(CMD_SESSION_KEY, transformed_nonce, 16,
rx_frame, sizeof(rx_frame), 3000);
if (err2 != ESP_OK) {
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
g_relay_state = RELAY_AUTH_COMPLETE;
DEBUG_INFO("OTA", "=== 阶段1 认证完成: 安全通道已建立 ===");
uint8_t k_session[16] = {0};
session_key_init(&g_session_ctx, k_session);
return OTA_RESULT_OK;
}
// 向 GD32 发送固件总大小
static ota_result_t send_firmware_size(uint32_t size)
{
uint8_t tx_frame[32];
uint8_t rx_frame[32];
DEBUG_INFO("OTA", "=== 阶段2: 发送大小 ===");
uint8_t size_payload[4];
size_payload[0] = (uint8_t)(size & 0xFF);
size_payload[1] = (uint8_t)((size >> 8) & 0xFF);
size_payload[2] = (uint8_t)((size >> 16) & 0xFF);
size_payload[3] = (uint8_t)((size >> 24) & 0xFF);
g_relay_state = RELAY_SIZE_SENT;
frame_build(CMD_SEND_SIZE, size_payload, 4, tx_frame);
esp_err_t err = spi_sync_transfer(CMD_SEND_SIZE, size_payload, 4,
rx_frame, sizeof(rx_frame), 3000);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "SEND_SIZE 失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
DEBUG_INFO("OTA", "大小已发送: %u 字节,等待 GD32 确认...", size);
g_relay_state = RELAY_SIZE_ACK;
return OTA_RESULT_OK;
}
// 从 Flash 向 GD32 转发存储的固件(48B/包)
static ota_result_t relay_packets_from_flash(void)
{
DEBUG_INFO("OTA", "=== 阶段3: 从 Flash 转发固件 ===");
ota_meta_t meta;
if (meta_load(&meta) != ESP_OK) {
return OTA_RESULT_ERR_STORAGE;
}
uint32_t total_packets = (meta.total_size + 47) / 48;
uint32_t flash_offset = 0;
uint8_t tx_buf[64];
uint8_t rx_buf[32];
ota_result_t result = OTA_RESULT_OK;
DEBUG_INFO("OTA", "准备转发 %u 包,每包 48 字节", total_packets);
for (uint32_t seq = 0; seq < total_packets; seq++) {
// 发送每个包前检查健康状态
health_status_t health = health_check_before_ota();
if (health == HEALTH_MON_SHUTDOWN || health == HEALTH_MON_CRITICAL) {
DEBUG_ERR("OTA", "健康检查危险,中止 OTA");
return OTA_RESULT_ERR_HEALTH;
}
size_t chunk_size = 48;
if (seq == total_packets - 1) {
chunk_size = meta.total_size - (seq * 48);
}
esp_err_t err = flash_read_chunk(flash_offset, &tx_buf[2], chunk_size);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "Flash 读取失败,偏移=%u", flash_offset);
return OTA_RESULT_ERR_STORAGE;
}
tx_buf[0] = (uint8_t)(seq & 0xFF);
tx_buf[1] = (uint8_t)((seq >> 8) & 0xFF);
uint16_t crc = crc16_modbus(tx_buf, 2 + chunk_size);
tx_buf[2 + chunk_size] = (uint8_t)(crc & 0xFF);
tx_buf[2 + chunk_size + 1] = (uint8_t)((crc >> 8) & 0xFF);
g_relay_state = RELAY_PACKET_SENT;
uint8_t cmd = (seq == total_packets - 1) ? CMD_DATA_END : CMD_DATA_PACKET;
size_t tx_len = 2 + chunk_size + 2;
err = spi_sync_transfer(cmd, tx_buf, tx_len, rx_buf, sizeof(rx_buf), 3000);
if (err != ESP_OK) {
DEBUG_WARN("OTA", "数据包 %u 发送失败,重试中...", seq);
result = OTA_RESULT_ERR_TIMEOUT;
break;
}
flash_offset += chunk_size;
g_relay_state = RELAY_PACKET_ACK_OK;
// 每 10% 记录一次进度
if (seq % (total_packets / 10 + 1) == 0) {
DEBUG_INFO("OTA", "转发进度: %u/%u 包 (%u%%)",
seq, total_packets, (seq * 100) / total_packets);
}
}
g_relay_state = RELAY_RECEIVE_DONE;
return result;
}
// 结束 OTA - 发送 0x0A AC 并清理
static ota_result_t finish_ota(void)
{
DEBUG_INFO("OTA", "=== 阶段4: 结束 OTA ===");
uint8_t tx_frame[16];
uint8_t rx_frame[16];
uint8_t payload[] = {0x0A, 0xAC};
frame_build(CMD_FINISH_OTA, payload, sizeof(payload), tx_frame);
g_relay_state = RELAY_FINISH_SENT;
esp_err_t err = spi_sync_transfer(CMD_FINISH_OTA, payload, sizeof(payload),
rx_frame, sizeof(rx_frame), 5000);
if (err != ESP_OK) {
DEBUG_ERR("OTA", "FINISH_OTA 失败");
g_relay_state = RELAY_ERROR;
return OTA_RESULT_ERR_TIMEOUT;
}
DEBUG_INFO("OTA", "等待 GD32 跳转确认...");
session_key_clear(&g_session_ctx);
memset(&g_session_ctx, 0, sizeof(session_ctx_t));
g_relay_state = RELAY_DONE;
DEBUG_INFO("OTA", "=== OTA 完成 ===");
meta_clear();
return OTA_RESULT_OK;
}
// 启动完整 OTA 流程的主入口
ota_result_t ota_relay_start_handshake(void)
{
ota_result_t res;
res = send_handshake_req();
if (res != OTA_RESULT_OK) return res;
res = send_nonce_and_authenticate();
if (res != OTA_RESULT_OK) return res;
ota_meta_t meta;
meta_load(&meta);
res = send_firmware_size(meta.total_size);
if (res != OTA_RESULT_OK) return res;
res = relay_packets_from_flash();
if (res != OTA_RESULT_OK) return res;
res = finish_ota();
return res;
}
// 从 Flash 向 GD32 转发固件
ota_result_t ota_relay_forward_firmware(void)
{
ota_meta_t meta;
if (meta_load(&meta) != ESP_OK) {
return OTA_RESULT_ERR_STORAGE;
}
DEBUG_INFO("OTA", "开始固件转发: %u 字节, %u 块",
meta.total_size, meta.chunk_count);
ota_result_t res = send_handshake_req();
if (res != OTA_RESULT_OK) return res;
res = send_nonce_and_authenticate();
if (res != OTA_RESULT_OK) return res;
res = send_firmware_size(meta.total_size);
if (res != OTA_RESULT_OK) return res;
res = relay_packets_from_flash();
if (res != OTA_RESULT_OK) return res;
res = finish_ota();
return res;
}
// 获取当前状态
relay_state_t ota_relay_get_state(void)
{
return g_relay_state;
}
// 重置状态机
void ota_relay_reset(void)
{
g_relay_state = RELAY_IDLE;
session_key_clear(&g_session_ctx);
}
// OTA 中继 FreeRTOS 任务
void ota_relay_task(void *pvParameters)
{
DEBUG_INFO("OTA", "OTA 中继任务启动");
g_ota_dispatch_queue = xQueueCreate(4, sizeof(uint32_t));
g_ota_result_queue = xQueueCreate(4, sizeof(uint32_t));
uint32_t msg;
BaseType_t ret = xQueueReceive(g_ota_dispatch_queue, &msg, portMAX_DELAY);
if (ret == pdTRUE) {
DEBUG_INFO("OTA", "收到 OTA 调度信号,开始转发...");
ota_result_t result = ota_relay_forward_firmware();
uint32_t result_msg = (uint32_t)result;
xQueueSend(g_ota_result_queue, &result_msg, pdMS_TO_TICKS(100));
if (result == OTA_RESULT_OK) {
DEBUG_INFO("OTA", "OTA 转发成功完成");
} else {
DEBUG_ERR("OTA", "OTA 转发失败,错误码: %d", result);
}
}
DEBUG_INFO("OTA", "OTA 中继任务结束");
vTaskDelete(NULL);
}
+101
View File
@@ -0,0 +1,101 @@
/*
* ota_relay.h - OTA 中继核心状态机
* T2.3, T3.1 - T3.7
*/
#ifndef OTA_RELAY_H
#define OTA_RELAY_H
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
// T4.3: OTA 元数据结构,用于 Flash 存储
#define META_FILE "/spiffs/ota_meta.json"
typedef struct {
uint32_t total_size; // 固件总大小(字节)
uint32_t stored_size; // 当前已存储大小(字节)
uint32_t last_seq; // 最后成功存储的序列号
uint32_t chunk_count; // 预期总块数
uint8_t checksum[32]; // 固件 SHA256 校验(可选)
} ota_meta_t;
// OTA 中继状态(对应 GD32 四阶段协议)
typedef enum {
// 阶段0: 空闲
RELAY_IDLE = 0,
// 阶段1: 安全握手
RELAY_HANDSHAKE_IDLE, // 等待 HANDSHAKE_REQ
RELAY_HANDSHAKE_SENT, // 已发送 0x00 AC,等待 GD32 响应
RELAY_CHALLENGE_SENT, // 已发送 nonce_server (16B),等待响应
RELAY_CHALLENGE_RESPOND, // 已收到 GD32 握手数据包
RELAY_SESSION_KEY_SENT, // 已发送变换后的随机数,等待 0xBA C0
RELAY_AUTH_COMPLETE, // 已收到 0xBA C0,安全通道建立完成
// 阶段2: 传输准备
RELAY_SIZE_SENT, // 已发送固件大小(4B LE)
RELAY_SIZE_ACK, // 已收到 0xBA C0 + seq
// 阶段3: 数据传输
RELAY_RECEIVING, // 接收并存储固件到 Flash
RELAY_RELAY_ACTIVE, // 存储完成,开始向 GD32 转发
RELAY_PACKET_SENT, // 已发送 DATA_PACKET,等待 GD32 ACK
RELAY_PACKET_ACK_OK, // ACK 正常,继续下一包
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_ERROR = 0xFF
} relay_state_t;
// OTA 结果码
typedef enum {
OTA_RESULT_OK = 0,
OTA_RESULT_ERR_TIMEOUT = -1, // GD32 超时无响应
OTA_RESULT_ERR_CRC = -2, // CRC 校验失败
OTA_RESULT_ERR_AUTH = -3, // 认证失败
OTA_RESULT_ERR_STORAGE = -4, // Flash 存储错误
OTA_RESULT_ERR_GD32 = -5, // GD32 返回错误
OTA_RESULT_ERR_HEALTH = -6, // 健康状态异常中止
OTA_RESULT_ERR_USER_ABORT = -7 // 用户中止
} ota_result_t;
// 外部队列句柄(在 app_main 中创建)
extern QueueHandle_t g_ota_dispatch_queue; // 从 WiFi/BLE 到 OTA 中继
extern QueueHandle_t g_ota_result_queue; // OTA 结果到 WiFi/BLE
// OTA 中继任务入口
void ota_relay_task(void *pvParameters);
// T2.3: 发起与 GD32 的握手
ota_result_t ota_relay_start_handshake(void);
// T3.4: 从 Flash 向 GD32 转发存储的固件(48B/包)
ota_result_t ota_relay_forward_firmware(void);
// T3.7: 获取当前中继状态
relay_state_t ota_relay_get_state(void);
// T3.7: 重置中继状态机
void ota_relay_reset(void);
// T4.2: Flash 写入接口
esp_err_t flash_write_chunk(uint32_t offset, const uint8_t *data, size_t len);
esp_err_t flash_read_chunk(uint32_t offset, uint8_t *data, size_t len);
// T3.3: 元数据文件管理
esp_err_t meta_load(ota_meta_t *meta);
esp_err_t meta_save(const ota_meta_t *meta);
esp_err_t meta_update(uint32_t stored_size, uint32_t last_seq);
esp_err_t meta_clear(void);
// 当前中继状态(供健康监控外部访问)
extern relay_state_t g_relay_state;
#endif // OTA_RELAY_H
+131
View File
@@ -0,0 +1,131 @@
/*
* ble_ota.c - BLE OTA GATT 服务器实现
* T5.1 - T5.5
*
* 注意: ESP-IDF 6.0 中蓝牙组件有较大变化
* 此文件暂时提供空实现,待 ESP-IDF 6.0 蓝牙 API 稳定后再适配
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_err.h"
#include "ble_ota.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "debug_log.h"
static bool s_ble_connected = false;
/* ESP-IDF 6.0: BLE OTA 功能暂时禁用,以下函数保留供后续适配使用 */
#if 0
// 处理写入控制点
static void handle_ctrl_point_write(uint8_t *data, size_t len)
{
if (len < 1) return;
uint8_t cmd = data[0];
DEBUG_DBG("BLE", "控制点写入: cmd=0x%02X, len=%d", cmd, len);
if (cmd == 0xF7 && s_ble_connected) {
ota_meta_t meta;
meta_load(&meta);
if (meta.stored_size >= meta.total_size) {
ble_ota_signal_relay();
}
}
}
// 处理写入数据输入(大固件分块)
static void handle_data_in_write(uint8_t *data, size_t len)
{
if (len < 7) return;
uint16_t seq = data[1] | (data[2] << 8);
uint32_t total_size = data[3] | (data[4] << 8) | (data[5] << 16) | (data[6] << 24);
if (seq == 0 && total_size > 0) {
ota_meta_t meta = {
.total_size = total_size,
.stored_size = 0,
.last_seq = 0,
.chunk_count = (total_size + 4095) / 4096
};
meta_save(&meta);
DEBUG_INFO("BLE", "开始固件存储: %" PRIu32 " 字节", total_size);
}
size_t data_offset = 7;
size_t data_len = len - data_offset - 4;
uint32_t flash_offset = seq * 4096;
esp_err_t err = flash_write_chunk(flash_offset, &data[data_offset], data_len);
if (err != ESP_OK) {
DEBUG_ERR("BLE", "Flash 写入失败 at seq %" PRIu16, seq);
}
meta_update(flash_offset + data_len, seq);
DEBUG_DBG("BLE", "已存储分块 %" PRIu16 ", offset=%" PRIu32, seq, flash_offset);
ota_meta_t meta;
meta_load(&meta);
if (meta.stored_size >= meta.total_size) {
DEBUG_INFO("BLE", "全部固件已存储,触发转发");
ble_ota_signal_relay();
}
}
#endif /* ESP_IDF_COMPILATION */
// 启动 BLE GATT 服务器
// 注意: 此函数在 ESP-IDF 6.0 中暂时返回 ESP_ERR_NOT_SUPPORTED
esp_err_t ble_ota_server_start(void)
{
DEBUG_WARN("BLE", "BLE OTA 功能在 ESP-IDF 6.0 中暂时不可用");
DEBUG_WARN("BLE", "请使用 WiFi OTA 功能或等待 BLE API 适配");
return ESP_ERR_NOT_SUPPORTED;
}
// 通过 BLE 通知 APP GD32 的响应
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len)
{
(void)data;
(void)len;
return ESP_ERR_NOT_SUPPORTED;
}
bool ble_ota_is_connected(void)
{
return s_ble_connected;
}
// 信号通知 OTA 中继开始转发
void ble_ota_signal_relay(void)
{
if (g_ota_dispatch_queue) {
uint32_t msg = 1;
BaseType_t ret = xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(100));
if (ret == pdTRUE) {
DEBUG_INFO("BLE", "OTA 调度信号已通过 BLE 发送");
}
}
}
// BLE OTA FreeRTOS 任务
void ble_ota_task(void *pvParameters)
{
(void)pvParameters;
DEBUG_WARN("BLE", "BLE OTA 任务启动,但功能暂时不可用");
// 等待调度信号
while (1) {
uint32_t msg;
BaseType_t ret = xQueueReceive(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(1000));
if (ret == pdTRUE) {
DEBUG_INFO("BLE", "收到 OTA 调度信号,但 BLE 功能不可用");
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* ble_ota.h - BLE OTA GATT 服务器
* T5.1 - T5.5
*/
#ifndef BLE_OTA_H
#define BLE_OTA_H
#include "ota_relay.h"
// BLE OTA 任务入口
void ble_ota_task(void *pvParameters);
// 启动 BLE GATT 服务器
esp_err_t ble_ota_server_start(void);
// 通过 BLE 通知 APP GD32 的响应
esp_err_t ble_ota_notify_response(const uint8_t *data, size_t len);
// 检查 BLE 是否已连接
bool ble_ota_is_connected(void);
// 信号通知中继开始(存储完成后调用)
void ble_ota_signal_relay(void);
#endif // BLE_OTA_H
+452
View File
@@ -0,0 +1,452 @@
/*
* wifi_ota.c - WiFi OTA 服务器实现
* T4.1 - T4.5
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_wifi.h"
#include "esp_netif.h"
#include "esp_http_server.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "nvs_flash.h"
#include "lwip/netdb.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "app_config.h"
#include "wifi_ota.h"
#include "ota_relay.h"
#include "health_monitor.h"
#include "debug_log.h"
static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP;
static bool s_ota_storage_done = false;
static bool s_relay_triggered = false;
static bool s_sta_connected = false;
static httpd_handle_t s_http_server = NULL;
// ----------------------------------------
// WiFi AP 模式初始化
// ----------------------------------------
static esp_err_t wifi_init_ap(void)
{
DEBUG_INFO("WIFI", "初始化 WiFi AP 模式...");
s_wifi_mode = OTA_WIFI_MODE_AP;
wifi_config_t wifi_config = {};
memcpy(wifi_config.ap.ssid, WIFI_AP_SSID, strlen(WIFI_AP_SSID));
wifi_config.ap.ssid_len = strlen(WIFI_AP_SSID);
memcpy(wifi_config.ap.password, WIFI_AP_PASSWORD, strlen(WIFI_AP_PASSWORD));
wifi_config.ap.max_connection = WIFI_AP_MAX_CONNECT;
wifi_config.ap.authmode = WIFI_AUTH_WPA_WPA2_PSK;
wifi_config.ap.channel = WIFI_AP_CHANNEL;
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
DEBUG_INFO("WIFI", "WiFi AP 已启动: %s", WIFI_AP_SSID);
DEBUG_INFO("WIFI", "AP IP 地址: 192.168.4.1");
return ESP_OK;
}
// ----------------------------------------
// WiFi STA 模式初始化
// ----------------------------------------
static esp_err_t wifi_init_sta(void)
{
DEBUG_INFO("WIFI", "初始化 WiFi STA 模式...");
s_wifi_mode = OTA_WIFI_MODE_STA;
wifi_config_t wifi_config = {};
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK;
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
memcpy(wifi_config.sta.ssid, WIFI_STA_SSID, sizeof(wifi_config.sta.ssid));
memcpy(wifi_config.sta.password, WIFI_STA_PASSWORD, sizeof(wifi_config.sta.password));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
DEBUG_INFO("WIFI", "WiFi STA 连接中, SSID: %s...", WIFI_STA_SSID);
return ESP_OK;
}
// 等待 STA 连接成功并获取 IP
static esp_err_t wifi_wait_sta_connected(char *ip_str, size_t len)
{
int retry = 0;
while (retry < 60) { // 最多等 6 秒
vTaskDelay(pdMS_TO_TICKS(100));
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
s_sta_connected = true;
esp_netif_ip_info_t ip_info;
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif != NULL && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) {
if (ip_str) {
snprintf(ip_str, len, IPSTR, IP2STR(&ip_info.ip));
}
}
return ESP_OK;
}
retry++;
}
return ESP_ERR_TIMEOUT;
}
// 设置 STA 静态 IP
static void wifi_set_static_ip(void)
{
#if WIFI_STA_USE_STATIC_IP
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif) {
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(sta_netif));
esp_netif_ip_info_t info;
memset(&info, 0, sizeof(info));
inet_pton(AF_INET, WIFI_STA_STATIC_IP, &info.ip);
inet_pton(AF_INET, WIFI_STA_STATIC_GW, &info.gw);
inet_pton(AF_INET, WIFI_STA_STATIC_NETMASK, &info.netmask);
ESP_ERROR_CHECK(esp_netif_set_ip_info(sta_netif, &info));
DEBUG_INFO("WIFI", "静态 IP 已设置: %s", WIFI_STA_STATIC_IP);
}
#else
DEBUG_INFO("WIFI", "使用 DHCP 获取 IP");
#endif
}
// ----------------------------------------
// HTTP POST /ota/upload 处理器
// ----------------------------------------
static esp_err_t ota_upload_handler(httpd_req_t *req)
{
DEBUG_INFO("WIFI", "收到固件上传请求, 内容长度: %d", req->content_len);
if (req->content_len > OTA_MAX_FIRMWARE_SIZE) {
DEBUG_ERR("WIFI", "固件太大: %d 字节", req->content_len);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "固件太大");
return ESP_FAIL;
}
uint8_t buf[OTA_BLOCK_SIZE];
int received = 0;
int first_chunk = 1;
while (received < req->content_len) {
int remaining = req->content_len - received;
int to_read = remaining < sizeof(buf) ? remaining : sizeof(buf);
int ret = httpd_req_recv(req, (char *)buf, to_read);
if (ret <= 0) {
DEBUG_ERR("WIFI", "请求接收失败");
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "接收失败");
return ESP_FAIL;
}
if (first_chunk && ret >= 6) {
uint16_t seq = buf[0] | (buf[1] << 8);
uint32_t total_size = buf[2] | (buf[3] << 8) | (buf[4] << 16) | (buf[5] << 24);
if (seq == 0) {
ota_meta_t meta = {
.total_size = total_size,
.stored_size = 0,
.last_seq = 0,
.chunk_count = (total_size + 4095) / 4096,
.checksum = {0}
};
meta_save(&meta);
DEBUG_INFO("WIFI", "开始固件存储: %u 字节", total_size);
}
first_chunk = 0;
}
uint32_t flash_offset = (received / OTA_BLOCK_SIZE) * OTA_BLOCK_SIZE;
esp_err_t err = flash_write_chunk(flash_offset, buf, ret);
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "Flash 写入失败 at offset %u", flash_offset);
}
meta_update(flash_offset + ret, received / OTA_BLOCK_SIZE);
received += ret;
DEBUG_DBG("WIFI", "已接收: %d / %d 字节", received, req->content_len);
}
DEBUG_INFO("WIFI", "固件存储完成: %d 字节", received);
ota_meta_t meta;
meta_load(&meta);
if (meta.stored_size >= meta.total_size) {
s_ota_storage_done = true;
DEBUG_INFO("WIFI", "全部固件已存储,触发转发...");
wifi_ota_signal_relay();
}
const char resp[] = "{\"status\": \"ok\", \"progress\": 100}";
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, strlen(resp));
return ESP_OK;
}
// HTTP POST /ota/relay 处理器
static esp_err_t ota_relay_trigger_handler(httpd_req_t *req)
{
(void)req;
DEBUG_INFO("WIFI", "收到触发 OTA 中继请求");
wifi_ota_signal_relay();
const char resp[] = "{\"status\": \"ok\", \"message\": \"OTA relay triggered\"}";
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, strlen(resp));
return ESP_OK;
}
// HTTP GET /ota/status 处理器
static esp_err_t ota_status_handler(httpd_req_t *req)
{
ota_meta_t meta;
meta_load(&meta);
char resp[256];
int len = snprintf(resp, sizeof(resp),
"{\"total_size\": %lu, \"stored_size\": %lu, \"last_seq\": %lu, "
"\"storage_done\": %s, \"relay_state\": %d}",
(unsigned long)meta.total_size, (unsigned long)meta.stored_size,
(unsigned long)meta.last_seq,
s_ota_storage_done ? "true" : "false",
g_relay_state);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, len);
return ESP_OK;
}
// HTTP GET /health 处理器
static esp_err_t health_handler(httpd_req_t *req)
{
health_state_t state = health_get_current();
char resp[128];
int len = snprintf(resp, sizeof(resp),
"{\"health\": %d, \"free_heap\": %lu}",
state, (unsigned long)esp_get_free_heap_size());
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, len);
return ESP_OK;
}
// HTTP GET /wifi/status 处理器
static esp_err_t wifi_status_handler(httpd_req_t *req)
{
char resp[256];
char ip_str[16] = "0.0.0.0";
int8_t rssi = -127;
if (s_wifi_mode == OTA_WIFI_MODE_STA && s_sta_connected) {
esp_netif_ip_info_t ip_info;
esp_netif_t *sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (sta_netif && esp_netif_get_ip_info(sta_netif, &ip_info) == ESP_OK) {
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
}
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
rssi = ap_info.rssi;
}
}
int len = snprintf(resp, sizeof(resp),
"{\"mode\": \"%s\", \"ip\": \"%s\", \"rssi\": %d, \"connected\": %s}",
s_wifi_mode == OTA_WIFI_MODE_AP ? "AP" : "STA",
ip_str, rssi,
(s_wifi_mode == OTA_WIFI_MODE_STA) ? (s_sta_connected ? "true" : "false") : "N/A");
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, resp, len);
return ESP_OK;
}
// ----------------------------------------
// 启动 HTTP OTA 服务器
// ----------------------------------------
static esp_err_t http_ota_server_start(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = HTTP_SERVER_PORT;
config.stack_size = HTTP_SERVER_STACK_SIZE;
DEBUG_INFO("WIFI", "启动 HTTP 服务器,端口 %d...", config.server_port);
esp_err_t err = httpd_start(&s_http_server, &config);
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "HTTP 服务器启动失败: %s", esp_err_to_name(err));
return err;
}
httpd_uri_t upload_uri = {
.uri = "/ota/upload",
.method = HTTP_POST,
.handler = ota_upload_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(s_http_server, &upload_uri);
httpd_uri_t relay_uri = {
.uri = "/ota/relay",
.method = HTTP_POST,
.handler = ota_relay_trigger_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(s_http_server, &relay_uri);
httpd_uri_t status_uri = {
.uri = "/ota/status",
.method = HTTP_GET,
.handler = ota_status_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(s_http_server, &status_uri);
httpd_uri_t health_uri = {
.uri = "/health",
.method = HTTP_GET,
.handler = health_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(s_http_server, &health_uri);
httpd_uri_t wifi_status_uri = {
.uri = "/wifi/status",
.method = HTTP_GET,
.handler = wifi_status_handler,
.user_ctx = NULL
};
httpd_register_uri_handler(s_http_server, &wifi_status_uri);
DEBUG_INFO("WIFI", "HTTP 服务器已启动");
return ESP_OK;
}
// ----------------------------------------
// 公开 API
// ----------------------------------------
void wifi_ota_signal_relay(void)
{
if (s_relay_triggered) {
DEBUG_WARN("WIFI", "转发已经触发过");
return;
}
s_relay_triggered = true;
if (g_ota_dispatch_queue) {
uint32_t msg = 1;
xQueueSend(g_ota_dispatch_queue, &msg, pdMS_TO_TICKS(100));
DEBUG_INFO("WIFI", "OTA 调度信号已发送到中继任务");
}
}
ota_wifi_mode_t wifi_get_mode(void)
{
return s_wifi_mode;
}
int8_t wifi_get_rssi(void)
{
if (s_wifi_mode != OTA_WIFI_MODE_STA || !s_sta_connected) {
return -127;
}
wifi_ap_record_t ap_info;
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
return ap_info.rssi;
}
return -127;
}
bool wifi_is_connected(void)
{
return s_sta_connected;
}
// ----------------------------------------
// WiFi OTA FreeRTOS 任务
// ----------------------------------------
void wifi_ota_task(void *pvParameters)
{
(void)pvParameters;
DEBUG_INFO("WIFI", "WiFi OTA 任务启动");
// 初始化 NVS
esp_err_t err = nvs_flash_init();
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "NVS 初始化失败: %s", esp_err_to_name(err));
}
// 初始化网络接口
ESP_ERROR_CHECK(esp_netif_init());
// STA 优先,失败自动切 AP
#ifdef CONFIG_WIFI_MODE_STA
err = wifi_init_sta();
if (err == ESP_OK) {
char ip_str[16] = {0};
// 设置静态 IP(如果配置了)
wifi_set_static_ip();
// 等待连接成功
err = wifi_wait_sta_connected(ip_str, sizeof(ip_str));
if (err == ESP_OK) {
DEBUG_INFO("WIFI", "STA 已连接, IP: %s", ip_str);
} else {
DEBUG_WARN("WIFI", "STA 连接超时,切换到 AP 模式...");
esp_wifi_stop();
s_wifi_mode = OTA_WIFI_MODE_AP;
wifi_init_ap();
}
}
#else
wifi_init_ap();
#endif
// 启动 HTTP 服务器
err = http_ota_server_start();
if (err != ESP_OK) {
DEBUG_ERR("WIFI", "HTTP 服务器启动失败");
}
DEBUG_INFO("WIFI", "WiFi OTA 服务器就绪");
// 定期检查 WiFi 状态
while (1) {
int8_t rssi = wifi_get_rssi();
if (rssi != -127) {
health_update_from_wifi_rssi(rssi);
if (rssi < WIFI_RSSI_CRITICAL) {
DEBUG_WARN("WIFI", "WiFi RSSI 危险: %d dBm", rssi);
}
}
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
+31
View File
@@ -0,0 +1,31 @@
/*
* wifi_ota.h - WiFi OTA 服务器
* T4.1 - T4.5
*/
#ifndef WIFI_OTA_H
#define WIFI_OTA_H
#include "ota_relay.h"
// WiFi 模式: AP 或 STA
typedef enum {
OTA_WIFI_MODE_AP, // AP 模式(ESP32 作为热点)
OTA_WIFI_MODE_STA // STA 模式(连接路由器)
} ota_wifi_mode_t;
// WiFi OTA 任务入口
void wifi_ota_task(void *pvParameters);
// 信号通知 OTA 中继开始(存储完成后调用)
void wifi_ota_signal_relay(void);
// 获取当前 WiFi 模式
ota_wifi_mode_t wifi_get_mode(void);
// 获取 WiFi RSSI
int8_t wifi_get_rssi(void);
// STA 是否已连接
bool wifi_is_connected(void);
#endif // WIFI_OTA_H
+117
View File
@@ -0,0 +1,117 @@
/* WiFi softAP Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via project configuration menu.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_WIFI_CHANNEL CONFIG_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
#if CONFIG_ESP_GTK_REKEYING_ENABLE
#define EXAMPLE_GTK_REKEY_INTERVAL CONFIG_ESP_GTK_REKEY_INTERVAL
#else
#define EXAMPLE_GTK_REKEY_INTERVAL 0
#endif
static const char *TAG = "wifi softAP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d, reason=%d",
MAC2STR(event->mac), event->aid, event->reason);
}
}
void wifi_init_softap(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.channel = EXAMPLE_ESP_WIFI_CHANNEL,
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
.authmode = WIFI_AUTH_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
.authmode = WIFI_AUTH_WPA2_PSK,
#endif
.pmf_cfg = {
.required = true,
},
#ifdef CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT
.bss_max_idle_cfg = {
.period = WIFI_AP_DEFAULT_MAX_IDLE_PERIOD,
.protected_keep_alive = 1,
},
#endif
.gtk_rekey_interval = EXAMPLE_GTK_REKEY_INTERVAL,
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}
void app_main(void)
{
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
wifi_init_softap();
}
+572
View File
@@ -0,0 +1,572 @@
/*
* protocol.c - 协议层实现
* T2.1, T2.2, T1.3
* ESP-IDF 6.0 适配版本 - 使用软件 AES 实现 + 硬件 RNG
*/
#include <string.h>
#include "esp_random.h"
#include "app_config.h"
#include "protocol.h"
#include "debug_log.h"
static bool s_protocol_initialized = false;
// ========== 软件 AES-128 实现 ==========
// AES S-Box
static const uint8_t sbox[256] = {
0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};
// AES 逆 S-Box
static const uint8_t rsbox[256] = {
0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};
// AES Rcon
static const uint8_t Rcon[11] = {
0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36
};
// AES 状态操作
#define AES_BLOCK_SIZE 16
typedef struct {
uint8_t round_key[176]; // 10 rounds * 16 bytes + initial key
} aes128_context_t;
// GF(2^8) 乘法
static uint8_t gmul(uint8_t a, uint8_t b)
{
uint8_t p = 0, hbit;
for (int i = 0; i < 8; i++) {
if (b & 1) {
p ^= a;
}
hbit = a & 0x80;
a <<= 1;
if (hbit) {
a ^= 0x1b; // 模不可约多项式 x^8 + x^4 + x^3 + x + 1
}
b >>= 1;
}
return p;
}
// 密钥扩展
static void aes128_key_expansion(const uint8_t *key, aes128_context_t *ctx)
{
uint8_t temp[4];
memcpy(ctx->round_key, key, 16);
for (int i = 4; i < 44; i++) {
for (int j = 0; j < 4; j++) {
temp[j] = ctx->round_key[(i - 1) * 4 + j];
}
if (i % 4 == 0) {
// RotWord
uint8_t t = temp[0];
temp[0] = temp[1];
temp[1] = temp[2];
temp[2] = temp[3];
temp[3] = t;
// SubWord
temp[0] = sbox[temp[0]];
temp[1] = sbox[temp[1]];
temp[2] = sbox[temp[2]];
temp[3] = sbox[temp[3]];
temp[0] ^= Rcon[i / 4];
}
for (int j = 0; j < 4; j++) {
ctx->round_key[i * 4 + j] = ctx->round_key[(i - 4) * 4 + j] ^ temp[j];
}
}
}
// 字节替换
static void sub_bytes(uint8_t *state)
{
for (int i = 0; i < 16; i++) {
state[i] = sbox[state[i]];
}
}
// 逆字节替换
static void inv_sub_bytes(uint8_t *state)
{
for (int i = 0; i < 16; i++) {
state[i] = rsbox[state[i]];
}
}
// 行移位
static void shift_rows(uint8_t *state)
{
uint8_t temp;
// 第1行左移1位
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
// 第2行左移2位
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
// 第3行左移3位
temp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = temp;
}
// 逆行移位
static void inv_shift_rows(uint8_t *state)
{
uint8_t temp;
// 第1行右移1位
temp = state[13];
state[13] = state[9];
state[9] = state[5];
state[5] = state[1];
state[1] = temp;
// 第2行右移2位
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
// 第3行右移3位
temp = state[3];
state[3] = state[7];
state[7] = state[11];
state[11] = state[15];
state[15] = temp;
}
// 列混合
static void mix_columns(uint8_t *state)
{
uint8_t temp[16];
memcpy(temp, state, 16);
for (int i = 0; i < 4; i++) {
int c = i * 4;
state[c] = gmul(2, temp[c]) ^ gmul(3, temp[c + 1]) ^ temp[c + 2] ^ temp[c + 3];
state[c + 1] = temp[c] ^ gmul(2, temp[c + 1]) ^ gmul(3, temp[c + 2]) ^ temp[c + 3];
state[c + 2] = temp[c] ^ temp[c + 1] ^ gmul(2, temp[c + 2]) ^ gmul(3, temp[c + 3]);
state[c + 3] = gmul(3, temp[c]) ^ temp[c + 1] ^ temp[c + 2] ^ gmul(2, temp[c + 3]);
}
}
// 逆列混合
static void inv_mix_columns(uint8_t *state)
{
uint8_t temp[16];
memcpy(temp, state, 16);
for (int i = 0; i < 4; i++) {
int c = i * 4;
state[c] = gmul(14, temp[c]) ^ gmul(11, temp[c + 1]) ^ gmul(13, temp[c + 2]) ^ gmul(9, temp[c + 3]);
state[c + 1] = gmul(9, temp[c]) ^ gmul(14, temp[c + 1]) ^ gmul(11, temp[c + 2]) ^ gmul(13, temp[c + 3]);
state[c + 2] = gmul(13, temp[c]) ^ gmul(9, temp[c + 1]) ^ gmul(14, temp[c + 2]) ^ gmul(11, temp[c + 3]);
state[c + 3] = gmul(11, temp[c]) ^ gmul(13, temp[c + 1]) ^ gmul(9, temp[c + 2]) ^ gmul(14, temp[c + 3]);
}
}
// 轮密钥加
static void add_round_key(uint8_t *state, const uint8_t *round_key)
{
for (int i = 0; i < 16; i++) {
state[i] ^= round_key[i];
}
}
// AES-128 加密单个块
static void aes128_encrypt_block(const uint8_t *input, uint8_t *output, const aes128_context_t *ctx)
{
uint8_t state[16];
// 复制输入到状态(列优先顺序)
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
state[i * 4 + j] = input[j * 4 + i];
}
}
// 初始轮密钥加
add_round_key(state, ctx->round_key);
// 9轮加密
for (int round = 1; round < 10; round++) {
sub_bytes(state);
shift_rows(state);
mix_columns(state);
add_round_key(state, ctx->round_key + round * 16);
}
// 最后一轮(无列混合)
sub_bytes(state);
shift_rows(state);
add_round_key(state, ctx->round_key + 10 * 16);
// 复制状态到输出(列优先顺序)
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
output[j * 4 + i] = state[i * 4 + j];
}
}
}
// AES-128 解密单个块
static void aes128_decrypt_block(const uint8_t *input, uint8_t *output, const aes128_context_t *ctx)
{
uint8_t state[16];
// 复制输入到状态(列优先顺序)
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
state[i * 4 + j] = input[j * 4 + i];
}
}
// 初始轮密钥加
add_round_key(state, ctx->round_key + 10 * 16);
// 9轮解密
for (int round = 9; round >= 1; round--) {
inv_shift_rows(state);
inv_sub_bytes(state);
add_round_key(state, ctx->round_key + round * 16);
inv_mix_columns(state);
}
// 最后一轮(无逆列混合)
inv_shift_rows(state);
inv_sub_bytes(state);
add_round_key(state, ctx->round_key);
// 复制状态到输出(列优先顺序)
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
output[j * 4 + i] = state[i * 4 + j];
}
}
}
// CBC 模式加密
static void aes128_cbc_encrypt(const uint8_t *input, uint8_t *output, size_t length,
const uint8_t *iv, const aes128_context_t *ctx)
{
uint8_t prev[16];
memcpy(prev, iv, 16);
for (size_t i = 0; i < length; i += 16) {
uint8_t block[16];
memcpy(block, input + i, 16);
// XOR with previous ciphertext (or IV for first block)
for (int j = 0; j < 16; j++) {
block[j] ^= prev[j];
}
aes128_encrypt_block(block, output + i, ctx);
memcpy(prev, output + i, 16);
}
}
// CBC 模式解密
static void aes128_cbc_decrypt(const uint8_t *input, uint8_t *output, size_t length,
const uint8_t *iv, const aes128_context_t *ctx)
{
uint8_t prev[16];
memcpy(prev, iv, 16);
for (size_t i = 0; i < length; i += 16) {
aes128_decrypt_block(input + i, output + i, ctx);
// XOR with previous ciphertext (or IV for first block)
for (int j = 0; j < 16; j++) {
output[i + j] ^= prev[j];
}
memcpy(prev, input + i, 16);
}
}
void protocol_init(void)
{
if (s_protocol_initialized) return;
DEBUG_INFO("PROT", "初始化协议层(CRC16、软件AES)...");
s_protocol_initialized = true;
DEBUG_INFO("PROT", "协议层就绪");
}
// CRC16 Modbus(LE,低字节在前)
uint16_t crc16_modbus(const uint8_t *data, size_t len)
{
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return (crc >> 8) | (crc << 8);
}
// 构建 SPI 帧: SOF + CMD + LEN(2LE) + PAYLOAD + CRC16(2LE)
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame)
{
if (len > FRAME_MAX_PAYLOAD) {
DEBUG_ERR("PROT", "Payload 太大: %zu > %d", len, FRAME_MAX_PAYLOAD);
return -1;
}
size_t idx = 0;
out_frame[idx++] = FRAME_SOF;
out_frame[idx++] = cmd;
out_frame[idx++] = (uint8_t)(len & 0xFF);
out_frame[idx++] = (uint8_t)((len >> 8) & 0xFF);
if (payload && len > 0) {
memcpy(&out_frame[idx], payload, len);
idx += len;
}
uint16_t crc = crc16_modbus(out_frame, idx);
out_frame[idx++] = (uint8_t)(crc & 0xFF);
out_frame[idx++] = (uint8_t)((crc >> 8) & 0xFF);
return idx;
}
// 解析 SPI 帧
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) {
return -1;
}
size_t idx = 0;
if (frame[idx++] != FRAME_SOF) {
DEBUG_WARN("PROT", "无效 SOF: 0x%02X", frame[0]);
return -1;
}
uint8_t cmd = frame[idx++];
size_t payload_len = frame[idx] | (frame[idx + 1] << 8);
idx += 2;
if (payload_len > FRAME_MAX_PAYLOAD) {
DEBUG_ERR("PROT", "Payload 长度太大: %zu", payload_len);
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);
return -1;
}
uint16_t received_crc = frame[idx + payload_len] | (frame[idx + payload_len + 1] << 8);
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);
return -1;
}
if (out_payload && out_len) {
memcpy(out_payload, &frame[idx], payload_len);
*out_len = payload_len;
}
if (out_cmd) {
*out_cmd = cmd;
}
return 0;
}
// AES-128 CBC 加密
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *ciphertext_out)
{
size_t padded_len = ((len + 15) / 16) * 16;
uint8_t padded[256];
memcpy(padded, plaintext, len);
uint8_t pad = (uint8_t)(16 - (len % 16));
memset(&padded[len], pad, pad);
aes128_context_t ctx;
aes128_key_expansion(key, &ctx);
aes128_cbc_encrypt(padded, ciphertext_out, padded_len, iv, &ctx);
return padded_len;
}
// AES-128 CBC 解密
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *plaintext_out)
{
if (len % 16 != 0) {
DEBUG_ERR("PROT", "Ciphertext 长度不是 16 的倍数: %zu", len);
return -1;
}
aes128_context_t ctx;
aes128_key_expansion(key, &ctx);
aes128_cbc_decrypt(ciphertext, plaintext_out, len, iv, &ctx);
// 移除 PKCS#7 填充
uint8_t pad = plaintext_out[len - 1];
if (pad > 16) return -1;
return len - pad;
}
// 搅伴算法 - 生成随机数
void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out)
{
uint8_t state[16];
memcpy(&state[0], &timestamp, 4);
memcpy(&state[4], &mem_addr, 4);
memcpy(&state[8], &rand_seed, 4);
memcpy(&state[12], SCRAMBLE_CONST, 4);
for (int round = 0; round < 3; round++) {
for (int i = 0; i < 16; i++) {
state[i] ^= SCRAMBLE_CONST[i];
state[i] = (state[i] << 1) | (state[i] >> 7);
}
}
for (int round = 0; round < 2; round++) {
uint32_t *words = (uint32_t *)state;
for (int i = 0; i < 4; i++) {
words[i] ^= SCRAMBLE_CONST[i * 4 % 16] | ((uint32_t)SCRAMBLE_CONST[i * 4 + 1] << 8);
}
}
memcpy(nonce_out, state, 16);
}
// 会话密钥初始化
int session_key_init(session_ctx_t *sess, const uint8_t *k_session)
{
if (!sess || !k_session) return -1;
memset(sess, 0, sizeof(session_ctx_t));
memcpy(sess->key, k_session, 16);
memset(sess->iv, 0, 16);
sess->used = 0;
return 0;
}
void session_key_clear(session_ctx_t *sess)
{
if (sess) {
memset(sess->key, 0, 16);
memset(sess->iv, 0, 16);
sess->used = 0;
}
}
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
{
if (!sess || !in || !out) return -1;
uint8_t iv_copy[16];
memcpy(iv_copy, sess->iv, 16);
int ret = protocol_aes_encrypt(in, len, sess->key, iv_copy, out);
if (ret > 0) {
memcpy(sess->iv, &out[ret - 16], 16);
}
return ret;
}
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out)
{
if (!sess || !in || !out) return -1;
uint8_t iv_copy[16];
memcpy(iv_copy, sess->iv, 16);
int ret = protocol_aes_decrypt(in, len, sess->key, iv_copy, out);
if (ret > 0) {
memcpy(sess->iv, &in[len - 16], 16);
}
return ret;
}
// 使用 ESP-IDF 硬件 RNG 生成随机数
void generate_random_nonce(uint8_t *nonce, size_t len)
{
esp_fill_random(nonce, len);
}
+93
View File
@@ -0,0 +1,93 @@
/*
* protocol.h - 帧格式、CRC16、AES 加密解密
* T2.2, T1.3
* ESP-IDF 6.0 适配版本 - 使用软件 AES 实现 + 硬件 RNG
*/
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include <stdint.h>
#include <stdbool.h>
#include "app_config.h"
// 帧格式: SOF(1B) + CMD(1B) + LEN(2B LE) + PAYLOAD(LEN) + CRC16(2B LE)
#define FRAME_SOF (0xEB) // 帧起始标记
#define FRAME_MAX_PAYLOAD (64) // 安全最大 payload
#define FRAME_OVERHEAD (6) // SOF + CMD + LEN(2) + CRC16(2)
// 命令码(对应 GD32 OTA 协议)
#define CMD_HANDSHAKE_REQ (0xF0) // 握手请求
#define CMD_CHALLENGE (0xF1) // 挑战/随机数
#define CMD_SESSION_KEY (0xF2) // 会话密钥
#define CMD_SEND_SIZE (0xF3) // 发送固件大小
#define CMD_DATA_PACKET (0xF4) // 数据包
#define CMD_DATA_END (0xF5) // 数据结束
#define CMD_TRANSFER_DONE (0xF6) // 传输完成
#define CMD_FINISH_OTA (0xF7) // 结束 OTA
#define CMD_ACK (0x80) // 通用 ACK
#define CMD_ERROR (0x81) // 通用错误
// ACK 状态码
#define ACK_OK (0x00) // 成功
#define ACK_UNSUPPORTED (0x01) // 命令不支持
#define ACK_AUTH_FAILED (0x02) // 认证失败
#define ACK_SIZE_OOR (0x03) // 大小超出范围
#define ACK_CRC_FAILED (0x04) // CRC 校验失败
#define ACK_RETRY_EXCEED (0x05) // 重试次数超限
#define ACK_SESSION_INVALID (0x06) // 会话密钥无效
#define ACK_FLASH_FAILED (0x07) // Flash 写入失败
// 安全常量(定义在 app_config.h 中)
inline constexpr size_t MASTER_KEY_HERE_LEN = 16;
inline constexpr size_t SCRAMBLE_CONST_HERE_LEN = 16;
inline constexpr size_t CHIP_UID_LEN = 12;
// AES 会话上下文
typedef struct {
uint8_t key[16]; // 会话密钥 K_session
uint8_t iv[16]; // CBC 模式 IV
int used;
} session_ctx_t;
// 初始化协议层
void protocol_init(void);
// CRC16 Modbus 计算(低字节在前,LE)
uint16_t crc16_modbus(const uint8_t *data, size_t len);
// 构建帧: 返回总帧长度
// 输出格式: SOF + CMD + LEN(2) + PAYLOAD + CRC16(2)
int frame_build(uint8_t cmd, const uint8_t *payload, size_t len, uint8_t *out_frame);
// 解析帧: 返回 payload 起始偏移,失败返回 -1
// 检查 SOF,验证 CRC16,提取 CMD 和 LEN
int frame_parse(const uint8_t *frame, size_t frame_len,
uint8_t *out_cmd, uint8_t *out_payload, size_t *out_len);
// T2.2: AES-128 CBC 加密
// 使用预设主密钥或会话密钥
// 注意: 函数名带 protocol_ 前缀以避免与 ESP-IDF wpa_supplicant 组件冲突
int protocol_aes_encrypt(const uint8_t *plaintext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *ciphertext_out);
int protocol_aes_decrypt(const uint8_t *ciphertext, size_t len,
const uint8_t *key, const uint8_t *iv,
uint8_t *plaintext_out);
// T2.1: 搅伴算法 - 生成随机数
// 输入: 时间戳 + 内存地址 + rand_seed
// 输出: 16 字节 nonce_server
void scramble_generate(uint32_t timestamp, uint32_t mem_addr,
uint32_t rand_seed, uint8_t *nonce_out);
// T2.4: 会话密钥管理
int session_key_init(session_ctx_t *sess, const uint8_t *k_session);
void session_key_clear(session_ctx_t *sess);
int session_encrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
int session_decrypt(session_ctx_t *sess, const uint8_t *in, size_t len, uint8_t *out);
// 使用 ESP-IDF 硬件 RNG 生成随机数
void generate_random_nonce(uint8_t *nonce, size_t len);
#endif // PROTOCOL_H
+124
View File
@@ -0,0 +1,124 @@
/*
* spi_master.c - SPI Master 驱动实现
* T1.1 - T1.4
* ESP-IDF 6.0 适配版本
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "soc/spi_struct.h"
#include "spi_master.h"
#include "debug_log.h"
spi_device_handle_t g_spi_device = NULL;
// SPI2 总线初始化
esp_err_t spi_master_init(void)
{
DEBUG_INFO("SPI", "初始化 SPI2 (VSPI)...");
spi_bus_config_t bus_cfg = {};
bus_cfg.mosi_io_num = 8;
bus_cfg.miso_io_num = 7;
bus_cfg.sclk_io_num = 6;
bus_cfg.quadwp_io_num = -1;
bus_cfg.quadhd_io_num = -1;
bus_cfg.max_transfer_sz = 256;
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));
return ret;
}
spi_device_interface_config_t dev_cfg = {};
dev_cfg.clock_speed_hz = SPI_CLOCK_HZ;
dev_cfg.mode = SPI_MODE;
dev_cfg.spics_io_num = SPI_MASTER_CS_GPIO;
dev_cfg.queue_size = SPI_QUEUE_SIZE;
dev_cfg.command_bits = 0;
dev_cfg.address_bits = 0;
dev_cfg.dummy_bits = 0;
dev_cfg.cs_ena_posttrans = 0;
dev_cfg.cs_ena_pretrans = 0;
ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &g_spi_device);
if (ret != ESP_OK) {
DEBUG_ERR("SPI", "添加 SPI 设备失败: %s", esp_err_to_name(ret));
return ret;
}
DEBUG_INFO("SPI", "SPI2 已初始化: CLK=40MHz, CS=GPIO4");
return ESP_OK;
}
// 带超时的同步 SPI 传输
// ESP-IDF 6.0: spi_device_transmit() 不再接受超时参数
// 使用 spi_device_queue_trans() + spi_device_get_trans_result() 实现超时
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)
{
if (g_spi_device == NULL) {
DEBUG_ERR("SPI", "SPI 设备未初始化");
return ESP_ERR_INVALID_STATE;
}
spi_transaction_t t = {};
t.cmd = cmd;
t.length = payload_len * 8;
t.rxlength = recv_len * 8;
t.tx_buffer = payload;
t.rx_buffer = recv_buf;
t.user = (void *)(uintptr_t)cmd;
/* ESP-IDF 6.0: 使用 spi_device_transmit() 无超时参数
* 如需超时控制,使用 spi_device_queue_trans() + spi_device_get_trans_result()
*/
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));
}
return ret;
}
// 仅发送
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);
}
// 仅接收
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms)
{
uint8_t dummy[64] = {0};
memset(dummy, 0xFF, sizeof(dummy));
return spi_sync_transfer(cmd, dummy, len, recv_buf, len, timeout_ms);
}
// 带重试的 SPI 传输
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;
int retry_count = 0;
while (retry_count < max_retries) {
ret = spi_sync_transfer(cmd, payload, payload_len,
recv_buf, recv_len, 3000);
if (ret == ESP_OK) {
return ESP_OK;
}
retry_count++;
DEBUG_WARN("SPI", "SPI 重试 %d/%d, cmd=0x%02X", retry_count, max_retries, cmd);
vTaskDelay(pdMS_TO_TICKS(100));
}
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败", max_retries);
return ESP_ERR_TIMEOUT;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* spi_master.h - SPI Master 驱动头文件
*/
#ifndef SPI_MASTER_H
#define SPI_MASTER_H
#include <stdint.h>
#include "driver/spi_master.h"
// SPI 配置常量
#define SPI_MASTER_CS_GPIO (4) // CS 片选引脚
#define SPI_CLOCK_HZ (40 * 1000 * 1000) // 40MHz
#define SPI_MODE (0) // CPOL=0, CPHA=0
#define SPI_QUEUE_SIZE (1)
// SPI 设备句柄(外部引用)
extern spi_device_handle_t g_spi_device;
// 初始化 SPI2 (VSPI) 总线和设备
esp_err_t spi_master_init(void);
// 同步 SPI 传输(阻塞式)
// 发送 cmd + payload,接收响应到 recv_buf
// 返回: ESP_OK 成功, ESP_ERR_TIMEOUT 超时
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);
// 仅发送(不期望接收)
esp_err_t spi_send(uint8_t cmd, const uint8_t *payload, size_t len);
// 仅接收(发送哑数据)
esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_ms);
#endif // SPI_MASTER_H