2026-07-02 02:02:27 +08:00
|
|
|
|
/*
|
2026-07-16 01:24:12 +08:00
|
|
|
|
* canfd_bridge.cpp - CANFD 3通道通信桥接实现
|
2026-07-16 23:00:15 +08:00
|
|
|
|
*
|
|
|
|
|
|
* SPI 业务类型:
|
|
|
|
|
|
* 0xC0 发 CAN 帧(canFdSend)
|
|
|
|
|
|
* 0xC1 反馈页解析(canFdParseFeedbackPage;由 GD32 主动推送,本文件不轮询)
|
|
|
|
|
|
* 0xC2 设 RX 模式(canFdSetRxMode)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 0xC1 线格式与 GD32 app_spi_cmd_can_feedback.cpp 对齐:
|
|
|
|
|
|
* 固定头 28B + 条目 id(4LE)+dlc(1)+flags(1)+rsvd(2)+data(dlc)
|
|
|
|
|
|
* 业务 payload 上限 256 - V2_OVERHEAD(20) = 236
|
2026-06-22 01:53:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include "canfd_bridge.h"
|
|
|
|
|
|
#include "spi_master.h"
|
2026-07-06 19:14:13 +08:00
|
|
|
|
#include "monitor/debug_log.h"
|
2026-07-26 20:40:16 +08:00
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
|
#include "freertos/task.h"
|
2026-06-22 01:53:16 +08:00
|
|
|
|
|
2026-06-25 01:42:31 +08:00
|
|
|
|
namespace {
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
constexpr uint8_t kSpiCanFrameType = 0xC0U;
|
2026-08-10 00:54:12 +08:00
|
|
|
|
constexpr uint8_t kSpiCanBurstType = 0xC7U;
|
2026-07-16 23:00:15 +08:00
|
|
|
|
constexpr uint8_t kSpiCanSetModeType = 0xC2U;
|
|
|
|
|
|
constexpr uint8_t kFeedbackFixedHeader = 28U;
|
|
|
|
|
|
constexpr uint8_t kFeedbackMaxPage = 1U; /* GD32 仅 page 0/1 */
|
2026-06-25 01:42:31 +08:00
|
|
|
|
|
2026-08-10 00:54:12 +08:00
|
|
|
|
/* 突发 payload 上限 = V2_MAX_PAYLOAD(236);头 4B = count+dlc+suffix(2)。 */
|
|
|
|
|
|
constexpr uint8_t kBurstHeaderLen = 4U;
|
|
|
|
|
|
constexpr uint16_t kBurstPayloadCap = 236U;
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 经 SPI 发送一帧 CAN,并解析 ACK/ERROR。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 两步法:transact 发命令(丢弃延迟一拍陈旧响应)+ poll 真实响应。
|
|
|
|
|
|
* CAN 帧非幂等,命令只发一次,不重发。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return >=0 ACK 时响应负载长度;-2 SPI 失败;-3 GD32 ERROR。
|
|
|
|
|
|
*/
|
2026-06-25 01:42:31 +08:00
|
|
|
|
static int sendCanFrameOverSpi(can_channel_t channel, uint32_t ext_id,
|
|
|
|
|
|
const uint8_t *data, uint8_t dlc,
|
|
|
|
|
|
uint8_t *recv_buf, uint8_t recv_max,
|
2026-07-15 00:35:02 +08:00
|
|
|
|
uint32_t timeout_ms,
|
|
|
|
|
|
uint8_t *gd32_err_out)
|
2026-06-25 01:42:31 +08:00
|
|
|
|
{
|
|
|
|
|
|
uint8_t tx_buf[72];
|
|
|
|
|
|
size_t tx_len = 0;
|
|
|
|
|
|
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>(channel & CAN_CHANNEL_MASK);
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>((ext_id >> 24) & 0xFFU);
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>((ext_id >> 16) & 0xFFU);
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>((ext_id >> 8) & 0xFFU);
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>(ext_id & 0xFFU);
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (dlc > 0U && data != nullptr) {
|
2026-06-25 01:42:31 +08:00
|
|
|
|
memcpy(&tx_buf[tx_len], data, dlc);
|
|
|
|
|
|
tx_len += dlc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* 独立缓冲收响应:调用方 recv_buf 为空时也能拿到 ERROR 细分码。 */
|
|
|
|
|
|
uint8_t local_resp[72];
|
|
|
|
|
|
uint8_t *spi_rx = (recv_buf != nullptr && recv_max > 0U) ? recv_buf : local_resp;
|
|
|
|
|
|
const size_t spi_rx_max = (recv_buf != nullptr && recv_max > 0U)
|
|
|
|
|
|
? static_cast<size_t>(recv_max)
|
|
|
|
|
|
: sizeof(local_resp);
|
|
|
|
|
|
|
2026-06-25 01:42:31 +08:00
|
|
|
|
uint8_t resp_type = 0U;
|
|
|
|
|
|
size_t resp_len = 0U;
|
2026-07-26 20:40:16 +08:00
|
|
|
|
esp_err_t ret = ESP_OK;
|
2026-08-02 17:37:19 +08:00
|
|
|
|
/* 0xC0 发送只会收到 ACK(0x80) 或 ERROR(0x82)。若读到 WAIT(0x81),必是上一轮电机 OTA
|
2026-07-26 20:40:16 +08:00
|
|
|
|
* 喂数(0xD1→WAIT)残留在 SPI 响应管道里的陈旧帧(0xC0 命令本身永不产生 WAIT)。
|
|
|
|
|
|
* 陈旧 WAIT 会被下方误判为 ERROR → resp_len=0 → gd32_err=0xFF → 无声拒绝发送(
|
2026-08-02 17:37:19 +08:00
|
|
|
|
* 表现为“失败一次就再也发不出去”)。此处最多重试 4 次排空陈旧帧,
|
|
|
|
|
|
* 直到读到本命令真正的 ACK/ERROR。
|
|
|
|
|
|
* SPI 传输失败/无响应的重发已由 SpiMaster::otaSendAndPoll 统一处理
|
|
|
|
|
|
* (同 TX_SEQ,最多 3 次尝试),此处不再各自重试;GD32 拒绝(ERROR)同样不重试。 */
|
2026-07-26 20:40:16 +08:00
|
|
|
|
for (uint8_t attempt = 0U; attempt < 4U; ++attempt) {
|
|
|
|
|
|
resp_type = 0U;
|
|
|
|
|
|
resp_len = 0U;
|
|
|
|
|
|
ret = SpiMaster::instance().transact(
|
|
|
|
|
|
kSpiCanFrameType,
|
|
|
|
|
|
tx_buf, tx_len,
|
|
|
|
|
|
&resp_type, spi_rx, spi_rx_max, &resp_len, timeout_ms);
|
2026-08-02 17:37:19 +08:00
|
|
|
|
if (resp_type != SPI_OTA_RSP_WAIT) {
|
|
|
|
|
|
break; /* 已拿到真正响应(ACK/ERROR);SPI 失败(ret!=OK)也退出交上层处理 */
|
2026-07-26 20:40:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(3)); /* 让 GD32 更新响应,再读一次排空陈旧 WAIT */
|
|
|
|
|
|
}
|
2026-07-09 02:49:24 +08:00
|
|
|
|
|
|
|
|
|
|
if (ret != ESP_OK) {
|
2026-07-17 02:02:36 +08:00
|
|
|
|
/* 同类错误节流日志,降低高频失败时的日志开销 */
|
2026-07-17 00:24:09 +08:00
|
|
|
|
static uint32_t s_spi_fail_n = 0U;
|
|
|
|
|
|
static int s_last_spi_err = 0;
|
|
|
|
|
|
const int err_i = static_cast<int>(ret);
|
|
|
|
|
|
if (s_spi_fail_n == 0U || err_i != s_last_spi_err || (s_spi_fail_n % 32U) == 0U) {
|
2026-07-20 03:10:42 +08:00
|
|
|
|
DEBUG_WARN("CAN", "SEND SPI fail ch=%u id=%08lX %s x%lu",
|
2026-07-17 00:24:09 +08:00
|
|
|
|
(unsigned)channel, (unsigned long)ext_id, esp_err_to_name(ret),
|
|
|
|
|
|
(unsigned long)(s_spi_fail_n + 1U));
|
|
|
|
|
|
}
|
|
|
|
|
|
s_last_spi_err = err_i;
|
|
|
|
|
|
++s_spi_fail_n;
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = 0U;
|
|
|
|
|
|
}
|
|
|
|
|
|
return -2;
|
2026-07-09 02:49:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (resp_type == SPI_OTA_RSP_ACK) {
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = 0U;
|
|
|
|
|
|
}
|
2026-07-09 02:49:24 +08:00
|
|
|
|
if (recv_buf == nullptr || recv_max == 0U) {
|
|
|
|
|
|
return static_cast<int>(resp_len);
|
2026-07-06 19:14:13 +08:00
|
|
|
|
}
|
2026-07-09 02:49:24 +08:00
|
|
|
|
const size_t copied_len = (resp_len < recv_max) ? resp_len : recv_max;
|
|
|
|
|
|
return static_cast<int>(copied_len);
|
2026-06-25 01:42:31 +08:00
|
|
|
|
}
|
2026-07-16 23:00:15 +08:00
|
|
|
|
|
|
|
|
|
|
/* ERROR:细分码在 payload[0];不依赖调用方是否提供 recv_buf */
|
|
|
|
|
|
const uint8_t gd32_err = (resp_len > 0U) ? spi_rx[0] : 0xFFU;
|
|
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = gd32_err;
|
|
|
|
|
|
}
|
2026-07-17 00:24:09 +08:00
|
|
|
|
{
|
|
|
|
|
|
static uint32_t s_rej_n = 0U;
|
|
|
|
|
|
static uint8_t s_last_rej = 0xFFU;
|
|
|
|
|
|
if (s_rej_n == 0U || gd32_err != s_last_rej || (s_rej_n % 32U) == 0U) {
|
2026-07-20 03:10:42 +08:00
|
|
|
|
DEBUG_WARN("CAN", "SEND拒 ch=%u id=%08lX r=%02X e=%02X x%lu",
|
2026-07-17 00:24:09 +08:00
|
|
|
|
(unsigned)channel, (unsigned long)ext_id, resp_type, gd32_err,
|
|
|
|
|
|
(unsigned long)(s_rej_n + 1U));
|
|
|
|
|
|
}
|
|
|
|
|
|
s_last_rej = gd32_err;
|
|
|
|
|
|
++s_rej_n;
|
|
|
|
|
|
}
|
2026-07-16 23:00:15 +08:00
|
|
|
|
return -3;
|
2026-06-25 01:42:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/** @brief 小端读 uint32(0xC1 固定头/条目 id)。 */
|
|
|
|
|
|
static inline uint32_t readU32Le(const uint8_t *p)
|
|
|
|
|
|
{
|
|
|
|
|
|
return static_cast<uint32_t>(p[0])
|
|
|
|
|
|
| (static_cast<uint32_t>(p[1]) << 8)
|
|
|
|
|
|
| (static_cast<uint32_t>(p[2]) << 16)
|
|
|
|
|
|
| (static_cast<uint32_t>(p[3]) << 24);
|
2026-06-29 01:51:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 01:42:31 +08:00
|
|
|
|
} // namespace
|
|
|
|
|
|
|
2026-06-22 01:53:16 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送一帧 CANFD 数据,可选等待并读取响应。
|
2026-07-16 23:00:15 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 返回值: >=0 成功(接收数据长度), -1 参数错误, -2 SPI通信失败, -3 GD32拒绝。
|
|
|
|
|
|
* gd32_err_out 在返回 -3 时输出 GD32 细分错误码,其它情况清零。
|
|
|
|
|
|
* dlc==0 时允许 data 为 nullptr(仅发 ID 的远程帧/空数据帧)。
|
2026-06-22 01:53:16 +08:00
|
|
|
|
*/
|
|
|
|
|
|
int canFdSend(can_channel_t channel, uint32_t ext_id,
|
|
|
|
|
|
const uint8_t *data, uint8_t dlc,
|
|
|
|
|
|
uint8_t *recv_buf, uint8_t recv_max,
|
2026-07-15 00:35:02 +08:00
|
|
|
|
uint32_t timeout_ms,
|
|
|
|
|
|
uint8_t *gd32_err_out)
|
2026-06-22 01:53:16 +08:00
|
|
|
|
{
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (channel >= CAN_CHANNEL_MAX || dlc > CAN_MAX_DLC) {
|
|
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = 0U;
|
|
|
|
|
|
}
|
2026-06-22 01:53:16 +08:00
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* dlc>0 时 data 必填;dlc==0 允许空指针 */
|
|
|
|
|
|
if (dlc > 0U && data == nullptr) {
|
|
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = 0U;
|
|
|
|
|
|
}
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return sendCanFrameOverSpi(channel, ext_id, data, dlc,
|
|
|
|
|
|
recv_buf, recv_max, timeout_ms, gd32_err_out);
|
2026-06-22 01:53:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 00:54:12 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 给定统一 DLC,返回单次突发最多可容纳的帧数。
|
|
|
|
|
|
* payload = 4B 头 + count*(1+dlc),须 ≤ 236B;并受 GD32 硬上限约束。
|
|
|
|
|
|
*/
|
|
|
|
|
|
uint8_t canBurstCapacity(uint8_t dlc)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dlc > CAN_MAX_DLC) {
|
|
|
|
|
|
return 0U;
|
|
|
|
|
|
}
|
|
|
|
|
|
const uint16_t entry_len = static_cast<uint16_t>(1U + dlc);
|
|
|
|
|
|
const uint16_t avail = (kBurstPayloadCap > kBurstHeaderLen)
|
|
|
|
|
|
? static_cast<uint16_t>(kBurstPayloadCap - kBurstHeaderLen)
|
|
|
|
|
|
: 0U;
|
|
|
|
|
|
const uint16_t cap = static_cast<uint16_t>(avail / entry_len);
|
|
|
|
|
|
return static_cast<uint8_t>((cap < CAN_BURST_HARD_MAX) ? cap : CAN_BURST_HARD_MAX);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送一次 CAN 突发(SPI 0xC7)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* GD32 在 CS 释放 ISR 内对每帧调用非阻塞 transmitFrame(仅装载邮箱),
|
|
|
|
|
|
* 全部装载完成后才回 ACK,因此各通道帧近同时上线;ESP32 这里等待的只是
|
|
|
|
|
|
* 这一个 ACK,并非逐帧等待,不会破坏组内同时性。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 返回值: count 成功, -1 参数错误, -2 SPI 通信失败, -3 GD32 拒绝。
|
|
|
|
|
|
*/
|
|
|
|
|
|
int canFdSendBurst(uint16_t id_suffix, uint8_t dlc,
|
|
|
|
|
|
const can_burst_entry_t *entries, uint8_t count,
|
|
|
|
|
|
uint8_t *status_out, uint8_t status_max,
|
|
|
|
|
|
uint32_t timeout_ms, uint8_t *gd32_err_out)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = 0U;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (dlc > CAN_MAX_DLC || entries == nullptr || count == 0U) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (count > canBurstCapacity(dlc)) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t tx_buf[240];
|
|
|
|
|
|
size_t tx_len = 0U;
|
|
|
|
|
|
tx_buf[tx_len++] = count;
|
|
|
|
|
|
tx_buf[tx_len++] = dlc;
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>((id_suffix >> 8) & 0xFFU);
|
|
|
|
|
|
tx_buf[tx_len++] = static_cast<uint8_t>(id_suffix & 0xFFU);
|
|
|
|
|
|
for (uint8_t i = 0U; i < count; ++i) {
|
|
|
|
|
|
tx_buf[tx_len++] = entries[i].chnode;
|
|
|
|
|
|
if (dlc > 0U) {
|
|
|
|
|
|
if (entries[i].data == nullptr) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
memcpy(&tx_buf[tx_len], entries[i].data, dlc);
|
|
|
|
|
|
tx_len += dlc;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t resp[32];
|
|
|
|
|
|
uint8_t resp_type = 0U;
|
|
|
|
|
|
size_t resp_len = 0U;
|
|
|
|
|
|
esp_err_t ret = ESP_OK;
|
|
|
|
|
|
/* 与 0xC0 相同:排空可能残留的陈旧 WAIT,直到读到本次真正的 ACK/ERROR */
|
|
|
|
|
|
for (uint8_t attempt = 0U; attempt < 4U; ++attempt) {
|
|
|
|
|
|
resp_type = 0U;
|
|
|
|
|
|
resp_len = 0U;
|
|
|
|
|
|
ret = SpiMaster::instance().transact(
|
|
|
|
|
|
kSpiCanBurstType, tx_buf, tx_len,
|
|
|
|
|
|
&resp_type, resp, sizeof(resp), &resp_len, timeout_ms);
|
|
|
|
|
|
if (resp_type != SPI_OTA_RSP_WAIT) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(3));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (ret != ESP_OK) {
|
|
|
|
|
|
DEBUG_WARN("CAN", "BURST SPI fail n=%u %s",
|
|
|
|
|
|
(unsigned)count, esp_err_to_name(ret));
|
|
|
|
|
|
return -2;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (resp_type == SPI_OTA_RSP_ACK) {
|
|
|
|
|
|
/* resp[0]=count, resp[1..count]=每帧状态(CanTxResult 码) */
|
|
|
|
|
|
const uint8_t n = (resp_len > 0U) ? resp[0] : 0U;
|
|
|
|
|
|
if (status_out != nullptr && status_max > 0U) {
|
|
|
|
|
|
const uint8_t copy_n = (n < status_max) ? n : status_max;
|
|
|
|
|
|
for (uint8_t i = 0U; i < copy_n; ++i) {
|
|
|
|
|
|
status_out[i] = (static_cast<size_t>(1U + i) < resp_len)
|
|
|
|
|
|
? resp[1U + i] : 0xFFU;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return static_cast<int>(n);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t gd32_err = (resp_len > 0U) ? resp[0] : 0xFFU;
|
|
|
|
|
|
if (gd32_err_out) {
|
|
|
|
|
|
*gd32_err_out = gd32_err;
|
|
|
|
|
|
}
|
|
|
|
|
|
DEBUG_WARN("CAN", "BURST拒 n=%u r=%02X e=%02X",
|
|
|
|
|
|
(unsigned)count, resp_type, gd32_err);
|
|
|
|
|
|
return -3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 01:51:44 +08:00
|
|
|
|
/**
|
2026-07-08 02:51:38 +08:00
|
|
|
|
* @brief 解析 GD32 主动推送的一页 CAN 反馈 payload,累积到结果结构中。
|
|
|
|
|
|
*
|
2026-07-16 23:00:15 +08:00
|
|
|
|
* page=0 → 清空 entries、重置 entryIdx,填充 28 字节固定头 + 首批条目;
|
|
|
|
|
|
* page=1 → 校验 channel 一致后在已有条目后追加。
|
|
|
|
|
|
* 调用方按通道维护 entryIdx。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return 0 成功;-1 参数/页号/通道/截断导致无法解析。
|
2026-06-29 01:51:44 +08:00
|
|
|
|
*/
|
2026-07-08 02:51:38 +08:00
|
|
|
|
int canFdParseFeedbackPage(const uint8_t *payload, size_t len,
|
|
|
|
|
|
can_feedback_result_t *out, uint8_t *entryIdx)
|
2026-06-29 01:51:44 +08:00
|
|
|
|
{
|
2026-07-08 02:51:38 +08:00
|
|
|
|
if (payload == nullptr || out == nullptr || entryIdx == nullptr) {
|
2026-06-29 01:51:44 +08:00
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (len < kFeedbackFixedHeader) {
|
2026-07-08 02:51:38 +08:00
|
|
|
|
return -1;
|
2026-06-29 01:51:44 +08:00
|
|
|
|
}
|
2026-07-08 02:51:38 +08:00
|
|
|
|
|
|
|
|
|
|
const uint8_t channel = payload[0];
|
|
|
|
|
|
const uint8_t page = payload[1];
|
|
|
|
|
|
if (channel >= CAN_CHANNEL_MAX) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* GD32 仅 page 0/1;非法页拒绝,避免当追加污染缓冲 */
|
|
|
|
|
|
if (page > kFeedbackMaxPage) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
2026-07-08 02:51:38 +08:00
|
|
|
|
|
|
|
|
|
|
if (page == 0U) {
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* 防 total 变少时 HTTP 读到上一轮残留 ID */
|
2026-07-08 02:51:38 +08:00
|
|
|
|
*entryIdx = 0U;
|
2026-07-16 23:00:15 +08:00
|
|
|
|
memset(out->entries, 0, sizeof(out->entries));
|
2026-07-08 02:51:38 +08:00
|
|
|
|
out->stats.channel = channel;
|
|
|
|
|
|
out->stats.mode = payload[4];
|
|
|
|
|
|
out->stats.snapshot_seq = readU32Le(&payload[5]);
|
|
|
|
|
|
out->stats.rx_count = readU32Le(&payload[9]);
|
|
|
|
|
|
out->stats.tx_count = readU32Le(&payload[13]);
|
|
|
|
|
|
out->stats.rx_drop_count = readU32Le(&payload[17]);
|
|
|
|
|
|
out->stats.error_count = readU32Le(&payload[21]);
|
|
|
|
|
|
out->stats.tec = payload[25];
|
|
|
|
|
|
out->stats.rec = payload[26];
|
|
|
|
|
|
out->stats.bus_state = payload[27];
|
|
|
|
|
|
out->total_count = payload[2];
|
2026-07-16 23:00:15 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
/* page>=1:必须与 page0 已建立的通道一致 */
|
|
|
|
|
|
if (out->stats.channel != channel) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
2026-07-08 02:51:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t entryCount = payload[3];
|
2026-07-16 23:00:15 +08:00
|
|
|
|
const uint8_t idx_before = *entryIdx;
|
|
|
|
|
|
uint16_t offset = kFeedbackFixedHeader;
|
|
|
|
|
|
|
2026-07-08 02:51:38 +08:00
|
|
|
|
for (uint8_t i = 0U; i < entryCount && *entryIdx < CANFD_ID_SNAPSHOT_MAX; ++i) {
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (offset + 8U > len) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-07-08 02:51:38 +08:00
|
|
|
|
|
|
|
|
|
|
id_snapshot_entry_t &e = out->entries[*entryIdx];
|
|
|
|
|
|
e.id = readU32Le(&payload[offset]);
|
|
|
|
|
|
e.dlc = payload[offset + 4U];
|
|
|
|
|
|
const uint8_t flags = payload[offset + 5U];
|
|
|
|
|
|
e.fd_flag = (flags & 0x01U) ? 1U : 0U;
|
|
|
|
|
|
e.brs = (flags & 0x02U) ? 1U : 0U;
|
|
|
|
|
|
e.esi = (flags & 0x04U) ? 1U : 0U;
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* 线协议不携带 per-ID seq/rx_count,显式置 0 避免脏读 */
|
|
|
|
|
|
e.seq = 0U;
|
|
|
|
|
|
e.rx_count = 0U;
|
2026-07-08 02:51:38 +08:00
|
|
|
|
|
|
|
|
|
|
const uint8_t dataLen = (e.dlc < CANFD_ID_SNAPSHOT_DATA_MAX)
|
|
|
|
|
|
? e.dlc : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
|
2026-08-02 17:37:19 +08:00
|
|
|
|
// 线协议条目实际大小按原始 dlc 计算(CANFD 帧可能 64B),offset 必须按
|
|
|
|
|
|
// 实际条目推进,否则后续条目错位。data 拷贝按 dataLen 截断。
|
|
|
|
|
|
const uint16_t wireEntrySize = static_cast<uint16_t>(8U + e.dlc);
|
|
|
|
|
|
if (offset + wireEntrySize > len) {
|
2026-07-16 23:00:15 +08:00
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-07-08 02:51:38 +08:00
|
|
|
|
|
|
|
|
|
|
memcpy(e.data, &payload[offset + 8U], dataLen);
|
|
|
|
|
|
for (uint8_t d = dataLen; d < CANFD_ID_SNAPSHOT_DATA_MAX; ++d) {
|
|
|
|
|
|
e.data[d] = 0U;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-02 17:37:19 +08:00
|
|
|
|
offset += wireEntrySize;
|
2026-07-08 02:51:38 +08:00
|
|
|
|
(*entryIdx)++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* 声明了条目但一个都没完整解析 → 截断/坏帧 */
|
|
|
|
|
|
if (entryCount > 0U && *entryIdx == idx_before) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 01:51:44 +08:00
|
|
|
|
out->valid = 1;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-02 17:37:19 +08:00
|
|
|
|
/* ── 0xC1 请求/响应查询(2026-08 架构改造:替代被动推送缓存)── */
|
|
|
|
|
|
|
|
|
|
|
|
int canFdQueryFeedback(uint8_t channel, uint8_t page,
|
|
|
|
|
|
can_feedback_result_t *out, uint8_t *entryIdx)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (out == nullptr || entryIdx == nullptr) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (channel >= CAN_CHANNEL_MAX || page > 1U) {
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t req[2] = { channel, page };
|
|
|
|
|
|
uint8_t resp_type = 0;
|
|
|
|
|
|
/* SPI v2 payload 上限 236B(256 帧 - 20 开销),与 GD32 kFeedbackPayloadLen 一致 */
|
|
|
|
|
|
uint8_t resp_buf[SPI_OTA_FRAME_MAX];
|
|
|
|
|
|
size_t resp_len = 0;
|
|
|
|
|
|
|
|
|
|
|
|
esp_err_t ret = SpiMaster::instance().transact(
|
|
|
|
|
|
0xC1, req, sizeof(req),
|
|
|
|
|
|
&resp_type, resp_buf, sizeof(resp_buf), &resp_len, 200U);
|
|
|
|
|
|
|
|
|
|
|
|
if (ret != ESP_OK) {
|
|
|
|
|
|
DEBUG_WARN("CAN", "queryFeedback ch=%u p=%u SPI失败: %s",
|
|
|
|
|
|
(unsigned)channel, (unsigned)page, esp_err_to_name(ret));
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (resp_type == SPI_OTA_RSP_ERROR) {
|
|
|
|
|
|
uint8_t gd32_err = (resp_len > 0) ? resp_buf[0] : 0;
|
|
|
|
|
|
DEBUG_WARN("CAN", "queryFeedback ch=%u p=%u GD32拒绝 err=0x%02X",
|
|
|
|
|
|
(unsigned)channel, (unsigned)page, (unsigned)gd32_err);
|
|
|
|
|
|
return -2;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (resp_type != SPI_OTA_RSP_ACK || resp_len < 1U) {
|
|
|
|
|
|
DEBUG_WARN("CAN", "queryFeedback ch=%u p=%u 非ACK type=0x%02X len=%zu",
|
|
|
|
|
|
(unsigned)channel, (unsigned)page, (unsigned)resp_type, resp_len);
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 解析复用现有 canFdParseFeedbackPage(payload 格式与推送一致) */
|
|
|
|
|
|
int parse_rc = canFdParseFeedbackPage(resp_buf, resp_len, out, entryIdx);
|
|
|
|
|
|
if (parse_rc != 0) {
|
|
|
|
|
|
DEBUG_WARN("CAN", "queryFeedback ch=%u p=%u 解析失败 rc=%d len=%zu",
|
|
|
|
|
|
(unsigned)channel, (unsigned)page, parse_rc, resp_len);
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 切换 GD32 三路 CAN 的接收过滤模式(SPI 0xC2)。
|
|
|
|
|
|
* @return 0 成功;-1 参数非法或 SPI/应答失败。
|
|
|
|
|
|
*/
|
2026-06-29 01:51:44 +08:00
|
|
|
|
int canFdSetRxMode(can_rx_mode_t mode,
|
|
|
|
|
|
can_rx_mode_t *actual,
|
|
|
|
|
|
uint32_t timeout_ms)
|
|
|
|
|
|
{
|
2026-07-16 23:00:15 +08:00
|
|
|
|
if (mode != CAN_RX_MODE_ALL_PASS && mode != CAN_RX_MODE_FILTER_ID
|
|
|
|
|
|
&& mode != CAN_RX_MODE_BRIDGE_0_TO_12) {
|
|
|
|
|
|
DEBUG_WARN("CAN", "SET_MODE 非法 mode=%u", (unsigned)mode);
|
2026-06-29 01:51:44 +08:00
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint8_t req = static_cast<uint8_t>(mode);
|
|
|
|
|
|
uint8_t resp_buf[1] = {0};
|
|
|
|
|
|
uint8_t resp_type = 0U;
|
|
|
|
|
|
size_t resp_len = 0U;
|
|
|
|
|
|
|
2026-07-16 23:00:15 +08:00
|
|
|
|
/* 两步法:发命令一次 + poll 真实响应。0xC2 幂等,但避免无效重试。 */
|
2026-07-14 12:10:13 +08:00
|
|
|
|
esp_err_t ret = SpiMaster::instance().transact(
|
2026-06-29 01:51:44 +08:00
|
|
|
|
kSpiCanSetModeType,
|
|
|
|
|
|
&req, 1U,
|
|
|
|
|
|
&resp_type, resp_buf, sizeof(resp_buf), &resp_len, timeout_ms);
|
2026-07-09 02:49:24 +08:00
|
|
|
|
|
|
|
|
|
|
if (ret == ESP_OK && resp_type == SPI_OTA_RSP_ACK && resp_len >= 1U) {
|
|
|
|
|
|
if (actual != nullptr) {
|
|
|
|
|
|
*actual = static_cast<can_rx_mode_t>(resp_buf[0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
2026-06-29 01:51:44 +08:00
|
|
|
|
}
|
2026-07-16 23:00:15 +08:00
|
|
|
|
|
2026-07-20 03:10:42 +08:00
|
|
|
|
DEBUG_WARN("CAN", "SET_MODE fail want=%u %s r=0x%02X n=%u",
|
2026-07-16 23:00:15 +08:00
|
|
|
|
(unsigned)mode,
|
|
|
|
|
|
esp_err_to_name(ret),
|
|
|
|
|
|
(unsigned)resp_type,
|
|
|
|
|
|
(unsigned)resp_len);
|
2026-07-09 02:49:24 +08:00
|
|
|
|
return -1;
|
2026-06-29 01:51:44 +08:00
|
|
|
|
}
|