变更点: 1. CAN-FD(device/CANFD.cpp、sys/config.h) - 发送统一 FD+BRS(DLC≤8 不再回退经典 CAN);OTA 期仍按通道压制 BRS - 空通道自动识别:连续 5 个 1Hz 窗口无有效帧且有错误 → 静默上报;有帧即恢复 - 三路 CAN 默认全开;RX parse budget 48→64 2. EtherCAT 桥 / PDO - 段1 增 txtxid/rx00ff;版本应答 0x00FF 粘滞发布,避免被 CONFIG 冲掉 - STATUS 禁止写入 xrx 快照;processSeq 新鲜度救援 3. OTA / 配置 - sys/config.h include 仓库内 shared/ota_layout.h(与 BOOT 共用,不再依赖仓外路径) - ota_boot_request 对齐配置页 word 布局 4. docs/audit_ecat_spi_wifi_ota_compatibility.md:ECAT/SPI/WiFi/OTA 兼容性审计 5. .gitignore:忽略 .trae/、docs/traework-import、评审过程稿
331 lines
16 KiB
C++
331 lines
16 KiB
C++
#ifndef DEVICE_CANFD_H
|
||
#define DEVICE_CANFD_H
|
||
|
||
#include <stdint.h>
|
||
|
||
#include "sys/config.h"
|
||
|
||
/** @brief 每通道反馈快照中保存的最近一帧最大数据长度(本地诊断用)。 */
|
||
#define CANFD_FEEDBACK_LAST_DATA_MAX 32U
|
||
|
||
/** @brief 每通道不重复 ID 快照表最大条目数(CONFIG_CANFD_ID_SNAPSHOT_MAX)。 */
|
||
#define CANFD_ID_SNAPSHOT_MAX CONFIG_CANFD_ID_SNAPSHOT_MAX
|
||
|
||
/** @brief 快照条目中最大数据字节数(CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX)。 */
|
||
#define CANFD_ID_SNAPSHOT_DATA_MAX CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX
|
||
|
||
/**
|
||
* @brief CANFD 通信模块。
|
||
* @note 负责 GPIO/模式控制、波特率配置、异步接收缓存与按需发送能力。
|
||
*/
|
||
class CanFd {
|
||
public:
|
||
/**
|
||
* @brief CANFD 接收过滤模式枚举。
|
||
* @note 默认模式为 kModeAllPass,即三路控制器关闭硬件 ID 过滤,全部接收。
|
||
*/
|
||
enum class Mode : uint8_t {
|
||
kModeAllPass = 0U, /**< 全部接收模式:禁用硬件 ID/掩码匹配,所有 11/29 位帧都进入 FIFO0。 */
|
||
kModeFilterId = 1U, /**< 过滤模式:按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧掩码匹配。 */
|
||
kModeBridge0To12 = 2U, /**< CAN0 桥接模式:硬件过滤等同 AllPass,CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2。 */
|
||
};
|
||
|
||
/**
|
||
* @brief CAN 发送结果枚举,枚举值即 SPI 错误响应码,便于 handler 直接透传。
|
||
* @note 0xE0/0xE1 保留给 handler 入口校验(payload 过短/通道非法),
|
||
* 不经 transmitFrame 返回;0xE2~0xE5 由 transmitFrame 区分四种失败原因。
|
||
*/
|
||
enum class CanTxResult : uint8_t {
|
||
kOk = 0x00U, /**< 发送成功,已进入硬件邮箱。 */
|
||
kErrBusOff = 0xE2U, /**< 通道处于 BUS_OFF,不发送。 */
|
||
kErrTecHigh = 0xE3U, /**< TEC > 96,暂停发送保护通道。 */
|
||
kErrMailbox = 0xE4U, /**< 3 个硬件邮箱全 PENDING,abort+重投仍失败。 */
|
||
kErrInvalid = 0xE5U, /**< 入口参数无效(channelIndex/dlc/data 等)。 */
|
||
};
|
||
|
||
/**
|
||
* @brief CANFD 运行诊断统计信息。
|
||
*/
|
||
struct Diag {
|
||
uint32_t tx_count;
|
||
uint32_t rx_count;
|
||
uint32_t error_count;
|
||
uint32_t rx_drop_count;
|
||
uint32_t rx_fifo_full_count;
|
||
uint32_t rx_fifo_overfull_count;
|
||
/** 取帧期间被硬件换帧、整帧丢弃的次数(1.6.6,TS/DLC 前后不一致即判定)。
|
||
* 这是"读到半条帧"的唯一可观测量,稳态下应当只在高负载时缓慢增长。 */
|
||
uint32_t rx_torn_count;
|
||
/** 释放邮箱后等待 RFD 硬件自清超时的次数(手册 26.3.4 第 3 步要求等待)。 */
|
||
uint32_t rx_release_timeout_count;
|
||
/** 内容级拼接帧拦截次数(1.6.11):(id, data) 结构自相矛盾(STATUS 的
|
||
* 温度/电压出量程、CONFIG 的未使用区非 0)⇒ 整帧作废。与 rx_torn 互补:
|
||
* torn 挡"取数期间窗口被换",这个挡"窗口没换但 FIFO 缓冲指针错位"。 */
|
||
uint32_t rx_content_reject_count;
|
||
uint32_t tx_abort_count;
|
||
uint32_t last_rx_id;
|
||
uint32_t last_tx_id;
|
||
uint8_t last_rx_channel;
|
||
uint8_t last_tx_channel;
|
||
uint8_t last_payload_length;
|
||
uint8_t last_rx_fd_flag;
|
||
uint8_t last_rx_brs;
|
||
uint8_t last_rx_esi;
|
||
};
|
||
|
||
/**
|
||
* @brief 单通道运行计数(每路独立累计)。
|
||
*/
|
||
struct ChannelStats {
|
||
uint32_t rx_count; /**< 该通道累计成功接收帧数。 */
|
||
uint32_t tx_count; /**< 该通道累计由硬件 MTFNERR 确认的成功发送帧数。 */
|
||
uint32_t rx_drop_count; /**< 该通道累计软件丢帧数。 */
|
||
uint32_t error_count; /**< 该通道累计错误计数(与发送邮箱失败合并)。 */
|
||
uint32_t last_rx_id; /**< 该通道最近一帧报文标识符。 */
|
||
uint8_t last_rx_dlc; /**< 该通道最近一帧报文数据长度。 */
|
||
uint8_t last_rx_fd_flag; /**< 该通道最近一帧 FD 帧标志。 */
|
||
uint8_t last_rx_brs; /**< 该通道最近一帧 BRS 标志。 */
|
||
uint8_t last_rx_esi; /**< 该通道最近一帧 ESI 标志。 */
|
||
uint8_t last_rx_data[CANFD_FEEDBACK_LAST_DATA_MAX];
|
||
uint8_t tec; /**< 最近一次采样时硬件发送错误计数器 (0~255)。 */
|
||
uint8_t rec; /**< 最近一次采样时硬件接收错误计数器 (0~255)。 */
|
||
uint8_t bus_state; /**< 总线状态位图:bit0=init_mode, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 单通道 1Hz 反馈快照(每秒刷新一次)。
|
||
*/
|
||
struct ChannelFeedback {
|
||
uint32_t snapshot_seq; /**< 第几次刷新(自启动起递增)。 */
|
||
ChannelStats stats; /**< 抓拍时刻的该通道统计与最近一帧信息。 */
|
||
};
|
||
|
||
|
||
/**
|
||
* @brief 单个不重复 ID 的最新帧快照。
|
||
*/
|
||
struct IdSnapshotEntry {
|
||
uint32_t id; /**< CAN 报文标识符。 */
|
||
uint32_t seq; /**< 该条目被更新的序号(自启动起递增)。 */
|
||
uint32_t rx_count; /**< 该 ID 累计接收帧数。 */
|
||
uint8_t dlc; /**< 最新一帧数据长度。 */
|
||
uint8_t fd_flag; /**< 最新一帧 FD 标志。 */
|
||
uint8_t brs; /**< 最新一帧 BRS 标志。 */
|
||
uint8_t esi; /**< 最新一帧 ESI 标志。 */
|
||
uint8_t data[CANFD_ID_SNAPSHOT_DATA_MAX]; /**< 最新一帧数据。 */
|
||
bool occupied; /**< 条目是否已被占用。 */
|
||
};
|
||
|
||
/**
|
||
* @brief 每通道不重复 ID 快照表(1Hz 刷新,SPI 命令分页拉取)。
|
||
*/
|
||
struct IdSnapshotTable {
|
||
IdSnapshotEntry entries[CANFD_ID_SNAPSHOT_MAX];
|
||
uint8_t count; /**< 当前占用条目数。 */
|
||
uint32_t seq; /**< 全局更新序号(每次 refresh 时递增)。 */
|
||
};
|
||
|
||
static void init();
|
||
|
||
/** L0 自检故障原因码(selfTestL0 输出;经 0xA3 fault_codes[] 上报,高 3 位带通道号见 initial.h) */
|
||
enum SelfTestFault : uint8_t {
|
||
kSelfTestOk = 0U, /**< 通过 */
|
||
kSelfTestHwInit = 1U, /**< 控制器初始化失败(重试 5 次后仍不成功) */
|
||
kSelfTestStuckInit = 2U, /**< 卡在初始化模式(IWS 未清零) */
|
||
kSelfTestStbLevel = 3U, /**< 收发器 STB 输出未拉低(配置未生效) */
|
||
};
|
||
|
||
/**
|
||
* @brief L0 轻量自检(不向物理总线发帧),仅校验 GD32 侧控制器自身状态。
|
||
* @param[out] fault_code 首个失败通道的 SelfTestFault 原因码;全部通过时为 kSelfTestOk。允许空。
|
||
* @param[out] fault_channel 首个失败通道号 0/1/2;全部通过时无意义。允许空。
|
||
* @retval true 所有已使能通道通过;false 无使能通道或任一路失败。
|
||
* @note 检查:硬件 init 结果、退出 init mode、收发器 STB 已置 Normal。
|
||
* 1.5.35 移除 BUS_OFF 与 TEC/REC 检查:它们是总线侧状态,
|
||
* 通道未接从站(RX 浮空噪声)时会误报——「没接设备」不等于错误;
|
||
* 总线健康经 0xC1 反馈帧(bus_state/TEC/REC)运行期上报,网页 CAN 页可见。
|
||
*/
|
||
static bool selfTestL0(uint8_t *fault_code = nullptr, uint8_t *fault_channel = nullptr);
|
||
|
||
/**
|
||
* @brief 查询指定通道是否已使能。
|
||
* @param[in] channelIndex 通道索引 0/1/2。
|
||
* @retval bool true=使能,false=禁用。
|
||
*/
|
||
static bool isChannelEnabled(uint8_t channelIndex);
|
||
|
||
/**
|
||
* @brief 切换 CANFD 接收过滤模式。
|
||
*/
|
||
static void setMode(Mode mode);
|
||
|
||
/** @brief 查询当前 CANFD 接收过滤模式。 */
|
||
static Mode mode();
|
||
|
||
static bool hasRxFrame();
|
||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||
/**
|
||
* @brief 直接向指定 CAN 通道发送一帧(绕过软件队列)。
|
||
* @param allow_abort 邮箱全占时是否允许 abort 一个 PENDING 邮箱并重投一次。
|
||
* - true(底层非阻塞顶替):适合最新单发覆盖场景;高总线负载下慎用(可能 abort 掉正在等仲裁的帧);
|
||
* - false(突发/队列路径):邮箱满直接返回 kErrMailbox,交由 enqueueTx
|
||
* 的软件队列缓存,由 TX 完成中断回灌——避免 abort 把同通道已装载的
|
||
* 旧帧静默丢弃(>3 帧同通道突发只剩最后 3 帧上线的根因)。
|
||
*/
|
||
static CanTxResult transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||
const uint8_t *data, uint8_t dlc, bool periodic = false,
|
||
bool allow_abort = true);
|
||
|
||
/** @brief 立即停掉指定通道所有 PENDING 发送邮箱(阻止 auto_retrans 残留重发,供停等式协议用)。 */
|
||
static void abortPendingTx(uint8_t channelIndex);
|
||
|
||
/** @brief 运行时切换指定通道 CAN 自动重发(ARD 位,需 init-mode);停等式 OTA 期间关闭、结束后恢复。 */
|
||
static void setChannelAutoRetrans(uint8_t channelIndex, bool enable);
|
||
|
||
/** @brief 运行时压制指定通道 TX 帧的 BRS:压制中 FD 帧数据段回退仲裁段速率。
|
||
* 电机 BOOT 无法正确接收 BRS 高速段(见 sys/config.h CONFIG_CAN_FD_BRS_ENABLE 注释),
|
||
* 电机 OTA relay 经 enterState 钩子与 setChannelAutoRetrans 同点切换,OTA 结束必恢复。 */
|
||
static void setChannelBrsSuppress(uint8_t channelIndex, bool suppress);
|
||
|
||
/** @brief 读取指定通道 CAN_ERR 寄存器原始值(事件时刻只读快照,不依赖 1Hz 采样)。
|
||
* 解码:TEC=bit16-23,REC=bit24-31,ERRN=bit4-6;无效通道返回 0。仅供诊断日志,禁用于发送门控。 */
|
||
static uint32_t readChannelErrorReg(uint8_t channelIndex);
|
||
|
||
/** @brief 读取并清除指定通道的 TX 错误事件(ARD=1 单发下 MTE 置位 = 帧已确认丢失)。
|
||
* 供停等式 OTA 在丢帧后立即重发,免等 ACK 超时;仅主循环调用。 */
|
||
static bool consumeTxError(uint8_t channelIndex);
|
||
static const Diag *diag();
|
||
|
||
/**
|
||
* @brief 刷新三路 1Hz 反馈快照。
|
||
* @note 通常由任务调度器在 1 秒节拍调用,将各通道实时计数与最近一帧信息抓拍到 s_feedback。
|
||
*/
|
||
static void refreshFeedbackSnapshot();
|
||
|
||
/**
|
||
* @brief 拷贝指定通道的最新反馈快照到调用方缓冲。
|
||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||
* @param[out] out 目标快照缓冲;为空或通道非法时返回 false。
|
||
* @retval bool true 表示成功拷贝,false 表示参数非法。
|
||
*/
|
||
static bool copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out);
|
||
|
||
/**
|
||
* @brief 拷贝指定通道的不重复 ID 快照表。
|
||
* @param[in] channelIndex 目标通道 0/1/2。
|
||
* @param[out] out 输出缓冲。
|
||
* @retval true 成功。
|
||
*/
|
||
static bool copyIdSnapshotTable(uint8_t channelIndex, IdSnapshotTable *out);
|
||
|
||
static void handleRxInterrupt(uint8_t channelIndex);
|
||
static void handleTxInterrupt(uint8_t channelIndex);
|
||
|
||
/**
|
||
* @brief 突发排入软件 TX 队列(供 SPI 0xC7 burst handler / 突发场景使用)。
|
||
*
|
||
* 解决「3 个发送邮箱是物理上限、>3 帧突发必丢」的问题:
|
||
* - 队列空时尝试直通 transmitFrame(不排队、零延迟)。
|
||
* - 邮箱满时把本帧追加到软件队列,TX 完成中断里持续出队回灌,
|
||
* 保证任意大小突发(同通道 ≤ 队列深度)都不丢帧。
|
||
*
|
||
* 同步:本函数可在 EXTI15 ISR 内运行(SPI burst handler),与 CAN TX ISR
|
||
* 并发;队列 Push/Pop 通过关中段互斥,无需 LDREX/STREX。
|
||
*
|
||
* @param[in] channelIndex 目标通道(0/1/2)。
|
||
* @param[in] ext_id 29 位扩展 ID。
|
||
* @param[in] data 数据负载;dlc>0 时必填。
|
||
* @param[in] dlc 数据长度 0..64。
|
||
* @retval CanTxResult::kOk 已进入硬件邮箱 OR 已入软件队列(统一视为已受理)。
|
||
* @retval CanTxResult::kErrMailbox 软件队列也满。
|
||
* @retval CanTxResult::kErrInvalid 参数非法。
|
||
*/
|
||
static CanTxResult enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||
const uint8_t *data, uint8_t dlc);
|
||
|
||
/** @brief 软件 TX 队列当前积压帧数(统计/诊断用)。 */
|
||
static uint32_t txQueueBacklog(uint8_t channelIndex);
|
||
|
||
/** @brief 接收队列容量深度(2 的幂;8kHz PDO 下主循环间隙短,加深缓冲防 STATUS 溢出丢帧)。 */
|
||
static constexpr uint8_t rxQueueDepth() { return kRxQueueDepth; }
|
||
|
||
/** @brief 查询指定通道软件接收队列当前积压帧数(无锁 SPSC,由 tail 与 head 计算)。 */
|
||
static uint32_t rxQueueCount(uint8_t channelIndex);
|
||
|
||
private:
|
||
static constexpr uint8_t kRxQueueDepth = 64U; /* 16→64:1kHz STATUS×3 在 PDI 风暴下不再轻易打满 */
|
||
static constexpr uint8_t kRxQueueMask = kRxQueueDepth - 1U;
|
||
/** 每通道软件突发队列深度:覆盖整块突发(CAN_BURST_HARD_MAX=16 帧)中同一通道的
|
||
* 全部超载帧(3 邮箱之外最多 13 帧 = 16-3),保证任意 DLC 的整块突发不因队列满丢帧。 */
|
||
static constexpr uint8_t kTxQueueDepth = 13U;
|
||
|
||
struct RxFrame {
|
||
uint8_t channel;
|
||
uint32_t id;
|
||
uint8_t data[64];
|
||
uint8_t length;
|
||
};
|
||
|
||
/**
|
||
* @brief Lock-Free 单生产者单消费者 (SPSC) 环形接收队列。
|
||
* @note 生产者为 CAN RX ISR (Preempt 0) 写入 tail,消费者为 consumeRxFrame (Thread) 推进 head。
|
||
* 移除共享 count 变量,消除 BASEPRI=0x10 临界区对 Preempt 0 无效导致的并发撕裂与计数下溢风险。
|
||
*/
|
||
struct RxQueue {
|
||
RxFrame frames[kRxQueueDepth];
|
||
volatile uint8_t head;
|
||
volatile uint8_t tail;
|
||
};
|
||
|
||
/** 排队等待硬件邮箱空位的 CAN 帧(仅 SDO 突发用,payload ≤ 64B)。 */
|
||
struct TxQueuedFrame {
|
||
uint32_t id;
|
||
uint8_t data[64];
|
||
uint8_t dlc;
|
||
};
|
||
struct TxQueue {
|
||
TxQueuedFrame frames[kTxQueueDepth];
|
||
volatile uint8_t head;
|
||
volatile uint8_t tail;
|
||
volatile uint8_t count;
|
||
};
|
||
|
||
/** @brief CAN TX ISR 内驱动:把队列里能装的全部装进硬件邮箱。 */
|
||
static void drainTxQueueIsr(uint8_t channelIndex);
|
||
|
||
static void initGpio();
|
||
/**
|
||
* @brief 根据当前模式为指定控制器配置接收过滤器。
|
||
* @param[in] canPeriph 目标 CAN 外设实例。
|
||
* @param[in] filterNumber 过滤器编号。
|
||
* @param[in] mode 目标接收过滤模式。
|
||
*/
|
||
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode);
|
||
static bool initNominalTiming(uint32_t canPeriph);
|
||
static bool initFdMode(uint32_t canPeriph);
|
||
|
||
static void printClockInfo();
|
||
static void initChannelHardware(uint8_t i);
|
||
static void configureAllFilters();
|
||
static void printInitResult();
|
||
static void printErrorState();
|
||
static void runSelfTest();
|
||
static void runLoopbackTest();
|
||
static void dumpRegisters();
|
||
|
||
static Diag s_diag;
|
||
static RxQueue s_rxQueues[3];
|
||
static TxQueue s_txQueues[3]; /**< CAN 突发软件队列(邮箱装不下时缓存) */
|
||
static uint8_t s_nextConsumeChannel;
|
||
static volatile Mode s_mode;
|
||
static ChannelStats s_channelStats[3];
|
||
static bool s_brsSuppress[3]; /**< TX BRS 压制(电机 OTA 期间该通道 DLC>8 帧禁 BRS) */
|
||
static ChannelFeedback s_feedback[3];
|
||
/** 各通道硬件 init(时序+退出 init mode)是否成功 */
|
||
static bool s_channelHwOk[3];
|
||
static uint32_t s_feedbackSeq;
|
||
static IdSnapshotTable s_idSnapshot[3];
|
||
};
|
||
|
||
#endif /* DEVICE_CANFD_H */
|