Files
DCU_APP/GD32E508_DCU_APP/device/canfd.h
T
cbb 44d407390b fix(esc_lan9252): 修复LAN9252 HBI访问模型和IRQ输出使能问题
- 修正HBI访问模型:将ESC核心CSR(0x0000-0x0FFF)改为通过ECAT_CSR_CMD/DATA
  间接端口访问,解决此前直接寻址导致核心寄存器恒读0/写丢弃的问题

- 新增csrRead/csrWrite/pramRead/pramWrite接口,正确实现LAN9252访问端口操作

- 添加PDI就绪检测:等待DL_STATUS bit0置位确认EEPROM加载完成和EtherCAT核心使能

- 实现IRQ引脚输出使能:配置IRQ_CFG和INT_EN寄存器,使EtherCAT事件能到达PB1,
  解决PDI_Isr未触发导致SAFEOP->OP无限挂起的问题

- 为ESC_EXMC硬件诊断添加CONFIG_ESC_EXMC_DIAG门控,默认关闭避免影响正常运行

- 修正system_gd32e50x.c中HXTAL故障诊断的寄存器偏移和位设置错
2026-09-02 02:38:35 +08:00

305 lines
14 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.
#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;
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(0xC0 单帧路径默认):非阻塞顶替,适合"最新命令优先"的单帧发送;
* - 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 读取指定通道 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);
private:
static constexpr uint8_t kRxQueueDepth = 16U;
/** 每通道软件突发队列深度:覆盖整块突发(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;
};
struct RxQueue {
RxFrame frames[kRxQueueDepth];
volatile uint8_t head;
volatile uint8_t tail;
volatile uint8_t count;
};
/** 排队等待硬件邮箱空位的 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 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 */