Files
ESP32_COMM/main/transport/canfd_bridge.h
T
小龙虾 af1f68222e docs(readme): 更新文档说明
- 添加项目使用指南
- 完善API接口说明
- 修正错误的配置示例
2026-10-13 00:47:15 +08:00

216 lines
9.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* canfd_bridge.h - CANFD 3通道通信桥接
*
* 封装 CANFD 帧格式构建 + SPI 传输,与 SpiMaster 的 OTA 协议职责分离。
* 通过 SpiMaster 的公共 SPI 接口完成底层传输。
*/
#ifndef CANFD_BRIDGE_H
#define CANFD_BRIDGE_H
#include <stdint.h>
// ============================================================
// CANFD 帧格式(3通道)
// [通道编号: 1B] [扩展帧ID: 4B BE] [数据: 1~64B]
// ============================================================
#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]; ///< CAN 数据负载 (最大 64 字节)
uint8_t dlc; ///< 数据长度 (0~64)
} can_frame_t;
/**
* @brief 发送一帧 CANFD 数据,可选等待并读取响应。
* 内部通过 SpiMaster 的公共 SPI 接口完成传输。
* @param channel CAN 通道编号(0~2)。
* @param ext_id 扩展帧 ID(29 位)。
* @param data CAN 数据负载;dlc==0 时可为 nullptr。
* @param dlc 数据长度(0~64);0 表示空数据帧,仍发送 ID。
* @param recv_buf 接收数据缓冲区(可为 nullptr)。
* @param recv_max 接收缓冲区最大长度。
* @param timeout_ms 超时时间(毫秒)。
* @param gd32_err_out 可选输出,GD32 返回的细分错误码(0xE0/0xE1/0xE2/0xE3/0xE4/0xE5/0xE6=通道静默拒发/0xE7=该通道被电机 OTA 独占);
* 仅在返回 -3 时有效,其它情况清零。可为 nullptr。
* @return 成功时返回接收数据长度,失败返回 -1/-2/-3。
*/
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,
uint32_t timeout_ms,
uint8_t *gd32_err_out = nullptr);
// ============================================================
// CAN 跨通道突发发送(SPI 0xC7)
// 一次 SPI 事务携带多帧,GD32 背靠背装载各通道 CAN 邮箱,
// 组内近同时上线(偏差 <1ms),用于多电机同步驱动。
// ============================================================
#define CAN_BURST_HARD_MAX (16) ///< GD32 单次突发硬上限(帧数)
/** @brief 突发条目:chnode=(ch<<4)|node,data 指向 dlc 字节数据。 */
typedef struct {
uint8_t chnode; ///< (channel<<4) | node
const uint8_t *data; ///< dlc 字节数据
} can_burst_entry_t;
/**
* @brief 给定统一 DLC,返回单次突发最多可容纳的帧数。
* 受 236B SPI payload(4B 头 + count*(1+dlc))与 GD32 硬上限约束。
*/
uint8_t canBurstCapacity(uint8_t dlc);
/**
* @brief 发送一次 CAN 突发(SPI 0xC7)。count 必须 <= canBurstCapacity(dlc)。
* GD32 在 ISR 内对每帧调用非阻塞 transmitFrame,各通道邮箱背靠背装载。
* @param id_suffix CAN ID 低 16 位(CMD=0x0200);id=(node<<16)|suffix。
* @param dlc 所有条目统一数据长度(0~64)。
* @param entries count 个条目(chnode + data)。
* @param count 条目数(1~canBurstCapacity(dlc))。
* @param status_out 输出每帧状态(GD32 CanTxResult 码,0x00=成功),可为 nullptr。
* @param status_max status_out 容量。
* @param timeout_ms SPI 事务超时。
* @param gd32_err_out 返回 -3 时的 GD32 细分错误码;可为 nullptr。
* @return 成功返回 count;-1 参数错误;-2 SPI 通信失败;-3 GD32 ERROR。
*/
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 = nullptr);
// ============================================================
// 不重复 ID 快照表(SPI 0xC3 协议配套)
// ============================================================
#define CANFD_ID_SNAPSHOT_MAX 16 ///< 每通道最大不重复 ID 条目数
#define CANFD_ID_SNAPSHOT_DATA_MAX 64 ///< 快照条目最大数据字节数(CANFD 最大 64B,保持完整不截断)
/**
* @brief 单个不重复 ID 的最新帧快照条目。
*
* 0xC1 线协议条目:id(4LE)+dlc(1)+flags(1)+rsvd(2)+data(dlc)。
* seq / rx_count 未上线,解析后恒为 0(保留字段便于日后扩协议)。
*/
typedef struct {
uint32_t id; ///< CAN ID
uint32_t seq; ///< 线协议未携带,解析后恒 0
uint32_t rx_count; ///< 线协议未携带,解析后恒 0
uint8_t dlc;
uint8_t fd_flag;
uint8_t brs;
uint8_t esi;
uint8_t data[CANFD_ID_SNAPSHOT_DATA_MAX];
} id_snapshot_entry_t;
// ============================================================
// CAN 接收过滤模式 & 反馈快照(GD32 约 1Hz 刷新;ESP32 被动收 0xC1)
// 与 GD32 DCU_APP SPI 协议帧 0xC1/0xC2 配套
// ============================================================
typedef enum {
CAN_RX_MODE_ALL_PASS = 0, ///< 默认:禁用 ID 过滤,三路全部接收
CAN_RX_MODE_FILTER_ID = 1, ///< 按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧过滤
CAN_RX_MODE_BRIDGE_0_TO_12 = 2, ///< CAN0 桥接:CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2
} can_rx_mode_t;
/**
* @brief 单通道 CAN 反馈统计(快照字段;非 ESP32 侧 1Hz 轮询)。
*/
typedef struct {
uint8_t channel;
uint8_t mode;
uint32_t snapshot_seq;
uint32_t rx_count;
uint32_t tx_count;
uint32_t rx_drop_count;
uint32_t error_count;
uint8_t tec; /**< 硬件发送错误计数器 (0~255) */
uint8_t rec; /**< 硬件接收错误计数器 (0~255) */
uint8_t bus_state; /**< 总线状态位图:bit0=init,bit1=控制器sleep,bit2=error_passive,bit3=bus_off,bit4=error_warning,bit5=tx_silent(收发器Silent,只听不发) */
} can_channel_feedback_t;
/**
* @brief 单通道 0xC1 完整响应:统计 + ID 快照表。
*/
typedef struct {
can_channel_feedback_t stats;
id_snapshot_entry_t entries[CANFD_ID_SNAPSHOT_MAX];
uint8_t total_count; /**< 该通道实际 ID 条目总数 */
uint8_t valid; /**< 数据是否有效 */
} can_feedback_result_t;
/**
* @brief 解析 GD32 主动推送的一页 CAN 反馈 payload,累积到结果结构中。
*
* GD32 APP 通过 requestEsp32Read(0xC1) 主动推送每页反馈,ESP32 收到后
* 调用本函数解析:
* - page=0:清空 entries、重置 entryIdx,填固定头 + 首批条目;
* - page=1:校验 channel 与 page0 一致后追加;
* - page>1 或声明条目却截断无法解析 → 失败。
* 成功时置 out->valid=1。
*
* @param payload GD32 推送帧的 payload([channel, page, total, count, mode, seq, ...])。
* @param len payload 长度。
* @param out 累积结果(跨 page 0/1 累积)。
* @param entryIdx 当前已填充条目数(page 0 时被重置为 0,调用方按通道维护)。
* @return 0 成功,<0 失败(payload 过短、页号/通道非法或条目截断)。
*/
int canFdParseFeedbackPage(const uint8_t *payload, size_t len,
can_feedback_result_t *out, uint8_t *entryIdx);
/**
* @brief 主动向 GD32 查询一页 CAN 反馈(SPI 0xC1 请求/响应)。
*
* 2026-08 架构改造:ESP32 不再被动缓存 GD32 推送,改为前端请求时按需查询。
* GD32 handleFeedback 接收 [channel, page] 返回 ACK + 页数据(与推送 payload 格式一致)。
* 查询成功后调 canFdParseFeedbackPage 解析,复用现有累积逻辑。
*
* @param channel 目标通道 0/1/2。
* @param page 页号 0 或 1(page 0 重置累积 + 填统计,page 1 追加 entries)。
* @param out 累积结果(跨 page 0/1 累积,调用方按通道维护)。
* @param entryIdx 当前已填充条目数(page 0 时被重置为 0)。
* @return 0 成功;-1 参数/SPI 失败;-2 GD32 返回 ERROR。
*/
int canFdQueryFeedback(uint8_t channel, uint8_t page,
can_feedback_result_t *out, uint8_t *entryIdx);
/**
* @brief 切换 GD32 三路 CAN 的接收过滤模式(SPI 0xC2)。
* @param mode 目标模式(0=全收,1=按 ID 过滤,2=CAN0 桥接到 1/2)。
* @param actual 可选输出,GD32 实际生效模式。
* @param timeout_ms SPI 事务超时。
* @return 0 成功,<0 失败(失败时打 WARN 日志)。
*/
int canFdSetRxMode(can_rx_mode_t mode,
can_rx_mode_t *actual,
uint32_t timeout_ms);
/**
* @brief 切换指定 CAN 通道收发器的 Silent 模式(SPI 0xC8):只听不发。
* GD32 侧把该通道 TCAN1057AV 的 S 脚置高 → 驱动器关断、**接收机照常工作**
* (手册 Table 8-4:RXD 仍镜像总线),所以静默期间接收与 0xC1 上报不受影响。
* 静默期间该通道所有发送(0xC0/0xC7/桥接/relay)回 0xE6=kErrSilent,不计错误。
* @param channel 目标通道 0/1/2。
* @param silent true=进 Silent,false=回 Normal。
* @param actual 可选输出,GD32 实际生效状态(1=Silent,与其发送门控同一个值)。
* @param timeout_ms SPI 事务超时。
* @return 0 成功,<0 失败(-1 参数/SPI 失败,-2 GD32 返回 ERROR)。
*/
int canFdSetTxSilent(uint8_t channel,
bool silent,
uint8_t *actual,
uint32_t timeout_ms);
#endif // CANFD_BRIDGE_H