- 新增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秒打印系统状态
231 lines
7.6 KiB
C++
231 lines
7.6 KiB
C++
/*
|
|
* 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"
|
|
|
|
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;
|
|
|
|
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));
|
|
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=%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;
|
|
}
|
|
|
|
// 带超时的同步 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;
|
|
}
|
|
|
|
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;
|
|
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));
|
|
} else {
|
|
DEBUG_DBG("SPI", "SPI 传输完成: cmd=0x%02X", cmd);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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) {
|
|
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};
|
|
|
|
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, 接收=%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;
|
|
}
|
|
|
|
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,
|
|
size_t recv_len, int max_retries)
|
|
{
|
|
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, err=%s",
|
|
retry_count, max_retries, cmd, esp_err_to_name(ret));
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
DEBUG_ERR("SPI", "SPI 传输 %d 次重试后失败, cmd=0x%02X", max_retries, cmd);
|
|
return ESP_ERR_TIMEOUT;
|
|
}
|