feat(wifi_ota): 添加CAN帧转发功能
添加了HTTP接口用于转发CAN帧,包括/can/send POST接口用于发送CAN帧, 以及/can/status GET接口用于查询CAN通道状态。同时集成了SPI master驱动 以支持与GD32之间的CANFD通信,并定义了完整的CAN帧格式和通道管理机制。 BREAKING CHANGE: 移除了OTA上位机工具脚本
This commit is contained in:
@@ -21,6 +21,8 @@
|
||||
#include "ota_relay.h"
|
||||
#include "health_monitor.h"
|
||||
#include "monitor/debug_log.h"
|
||||
#include "spi_master.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
static ota_wifi_mode_t s_wifi_mode = OTA_WIFI_MODE_AP;
|
||||
static bool s_ota_storage_done = false;
|
||||
@@ -252,6 +254,98 @@ static esp_err_t health_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP POST /can/send 处理器
|
||||
// 上位机发送 CAN 帧,经 ESP32 转发到 GD32
|
||||
// 请求体: {"ch": 0, "id": 123456, "data": "0102030405060708", "dlc": 8}
|
||||
// data 为 hex 字符串(可不填,自动用 dlc 决定实际发送长度)
|
||||
static esp_err_t can_send_handler(httpd_req_t *req)
|
||||
{
|
||||
char buf[256] = {0};
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "接收数据失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
if (!root) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON 解析失败");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *ch_item = cJSON_GetObjectItem(root, "ch");
|
||||
cJSON *id_item = cJSON_GetObjectItem(root, "id");
|
||||
cJSON *data_item = cJSON_GetObjectItem(root, "data");
|
||||
cJSON *dlc_item = cJSON_GetObjectItem(root, "dlc");
|
||||
|
||||
if (!ch_item || !id_item) {
|
||||
cJSON_Delete(root);
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "缺少 ch 或 id 字段");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
can_channel_t ch = (can_channel_t)cJSON_GetNumberValue(ch_item);
|
||||
uint32_t ext_id = (uint32_t)cJSON_GetNumberValue(id_item);
|
||||
uint8_t dlc = dlc_item ? (uint8_t)cJSON_GetNumberValue(dlc_item) : 0;
|
||||
|
||||
uint8_t tx_data[CAN_MAX_DLC] = {0};
|
||||
uint8_t tx_dlc = 0;
|
||||
|
||||
if (data_item && cJSON_IsString(data_item)) {
|
||||
const char *hex_str = cJSON_GetStringValue(data_item);
|
||||
size_t hex_len = strlen(hex_str);
|
||||
for (size_t i = 0; i < hex_len / 2 && i < CAN_MAX_DLC; i++) {
|
||||
char byte_str[3] = { hex_str[i * 2], hex_str[i * 2 + 1], '\0' };
|
||||
tx_data[i] = (uint8_t)strtol(byte_str, NULL, 16);
|
||||
tx_dlc++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx_dlc == 0 && dlc > 0) {
|
||||
tx_dlc = (dlc > CAN_MAX_DLC) ? CAN_MAX_DLC : dlc;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
// 发送 CAN 帧到 GD32
|
||||
uint8_t resp_data[CAN_MAX_DLC] = {0};
|
||||
int resp_len = gd32_can_send(ch, ext_id, tx_data, tx_dlc,
|
||||
resp_data, sizeof(resp_data), 1000);
|
||||
|
||||
char resp[512];
|
||||
if (resp_len >= 0) {
|
||||
// 将响应 data 转为 hex 字符串
|
||||
char resp_hex[CAN_MAX_DLC * 2 + 1] = {0};
|
||||
for (int i = 0; i < resp_len; i++) {
|
||||
sprintf(&resp_hex[i * 2], "%02X", resp_data[i]);
|
||||
}
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"status\": \"ok\", \"ch\": %d, \"id\": %lu, \"sent\": %d, \"recv_len\": %d, \"recv\": \"%s\"}",
|
||||
ch, (unsigned long)ext_id, tx_dlc, resp_len, resp_hex);
|
||||
} else {
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"status\": \"error\", \"code\": %d}", resp_len);
|
||||
}
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP GET /can/status 处理器
|
||||
static esp_err_t can_status_handler(httpd_req_t *req)
|
||||
{
|
||||
char resp[128];
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"channels\": 3, \"max_dlc\": %d, \"spi_init\": %s}",
|
||||
CAN_MAX_DLC,
|
||||
g_spi_device != NULL ? "true" : "false");
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP GET /wifi/status 处理器
|
||||
static esp_err_t wifi_status_handler(httpd_req_t *req)
|
||||
{
|
||||
@@ -340,6 +434,23 @@ static esp_err_t http_ota_server_start(void)
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &wifi_status_uri);
|
||||
|
||||
// CAN 帧转发
|
||||
httpd_uri_t can_send_uri = {
|
||||
.uri = "/can/send",
|
||||
.method = HTTP_POST,
|
||||
.handler = can_send_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &can_send_uri);
|
||||
|
||||
httpd_uri_t can_status_uri = {
|
||||
.uri = "/can/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = can_status_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(s_http_server, &can_status_uri);
|
||||
|
||||
DEBUG_INFO("WIFI", "HTTP 服务器已启动");
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
* spi_master.c - SPI Master 驱动实现
|
||||
* T1.1 - T1.4
|
||||
* ESP-IDF 6.0 适配版本
|
||||
* CANFD 3通道发送接口
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "soc/spi_struct.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "spi_master.h"
|
||||
#include "monitor/debug_log.h"
|
||||
@@ -99,6 +102,88 @@ esp_err_t spi_recv(uint8_t cmd, uint8_t *recv_buf, size_t len, uint32_t timeout_
|
||||
return spi_sync_transfer(cmd, dummy, len, recv_buf, len, timeout_ms);
|
||||
}
|
||||
|
||||
// CANFD 帧发送
|
||||
// 帧格式: [通道(1B)] [扩展帧ID(4B BE)] [数据(1~64B)]
|
||||
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) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (dlc == 0 || dlc > CAN_MAX_DLC) {
|
||||
DEBUG_ERR("SPI", "DLC 无效: %d", dlc);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (channel >= CAN_CHANNEL_MAX) {
|
||||
DEBUG_ERR("SPI", "通道号无效: %d", channel);
|
||||
return -3;
|
||||
}
|
||||
|
||||
// 构建 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};
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.tx_buffer = tx_buf;
|
||||
t.rx_buffer = rx_buf;
|
||||
t.length = tx_len * 8;
|
||||
t.rxlength = (recv_max > 0) ? (5 + recv_max) * 8 : 0;
|
||||
|
||||
esp_err_t ret = spi_device_transmit(g_spi_device, &t);
|
||||
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",
|
||||
resp_channel, resp_id, copy_len);
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
return 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)
|
||||
{
|
||||
if (dlc == 0) {
|
||||
dlc = (data != NULL) ? 64 : 0; // 避免 dlc=0 的情况
|
||||
}
|
||||
return can_frame_send(channel, ext_id, data, dlc, recv_buf, recv_max, timeout_ms);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 带重试的 SPI 传输
|
||||
esp_err_t spi_transfer_with_retry(uint8_t cmd, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *recv_buf,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* spi_master.h - SPI Master 驱动头文件
|
||||
* CANFD 3通道发送接口 + 底层 SPI 总线
|
||||
*/
|
||||
#ifndef SPI_MASTER_H
|
||||
#define SPI_MASTER_H
|
||||
@@ -16,6 +17,54 @@
|
||||
// SPI 设备句柄(外部引用)
|
||||
extern spi_device_handle_t g_spi_device;
|
||||
|
||||
// ============================================================
|
||||
// CANFD 帧格式(3通道)
|
||||
// [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B]
|
||||
// 长度自动识别,无需发送 DLC
|
||||
// ============================================================
|
||||
#define CAN_EXTENDED_ID_BITS (29) // CAN 扩展帧 ID 位数
|
||||
#define CAN_MAX_DLC (64) // 最大数据长度
|
||||
#define CAN_CHANNEL_MASK (0x03) // 通道号掩码 (0~2)
|
||||
|
||||
// 通道定义
|
||||
typedef enum {
|
||||
CAN_CHANNEL_0 = 0,
|
||||
CAN_CHANNEL_1 = 1,
|
||||
CAN_CHANNEL_2 = 2,
|
||||
CAN_CHANNEL_MAX = 3,
|
||||
} can_channel_t;
|
||||
|
||||
// CANFD 帧结构
|
||||
typedef struct {
|
||||
can_channel_t channel; // CAN 通道 (0~2)
|
||||
uint32_t ext_id; // 扩展帧 ID (29bit, Big-Endian 存储)
|
||||
uint8_t data[CAN_MAX_DLC];
|
||||
uint8_t dlc; // 数据长度 (1~64)
|
||||
} can_frame_t;
|
||||
|
||||
// ============================================================
|
||||
// 发送 CANFD 帧
|
||||
// channel: CAN 通道 (0, 1, 2)
|
||||
// ext_id: 扩展帧 ID (29bit)
|
||||
// data: 数据内容 (1~64 字节)
|
||||
// dlc: 数据长度 (自动识别,可传 0 由 data 长度决定)
|
||||
// recv_buf: 接收缓冲区 (可传 NULL)
|
||||
// recv_max: 期望接收最大字节数
|
||||
// timeout_ms: 超时时间 (ms)
|
||||
// 返回: >= 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);
|
||||
|
||||
// 快速发送接口(不带响应接收)
|
||||
// 用于广播/通知类 CAN 帧
|
||||
int gd32_can_send_no_reply(can_channel_t channel, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
// ============================================================
|
||||
|
||||
// 初始化 SPI2 (VSPI) 总线和设备
|
||||
esp_err_t spi_master_init(void);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user