Files
DCU_APP/GD32E508_DCU_APP/device/canfd.h
T
cbb 86c0ec730b feat(canfd): 添加CANFD发送邮箱占用处理机制
当CAN控制器因总线无ACK导致邮箱卡死时,实现两段式恢复策略:
1) 短自旋轮询邮箱状态,大多数场景下可避免触发abort而不丢帧
2) 若仍全部PENDING则只中止第一个占用邮箱,限制最坏丢帧数量为1帧

添加了tx_abort_count诊断计数器用于监控该情况,并引入SysTick延时功能

refactor(spi-cmd): 优化SPI命令日志输出格式

将CAN发送相关日志改为统一的格式化函数输出,增加数据字段十六进制显示功能,
并扩大日志缓冲区大小从128到256字节以支持更详细的日志信息

chore(spi-command): 添加SPI帧类型分类和非刷写帧日志记录

新增isFlashRelatedFrame函数区分刷写相关帧类型,在处理命令时对非刷写帧进行日志记录

perf(diagnostic): 在诊断打印中添加发送中止计数统计

更新诊断信息打印格式,增加tx_abort计数项,便于监控CAN发送异常情况
2026-06-25 01:11:31 +08:00

209 lines
9.0 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>
/** @brief CANFD 状态报文的数据长度,单位字节。 */
#define CANFD_STATUS_DLEN 12U
/**
* @brief CANFD 通信模块。
* @note 负责 GPIO/模式控制、波特率配置、异步接收缓存与按需发送能力。
*/
class CanFd {
public:
/**
* @brief CANFD 运行诊断统计信息。
* @note 记录发送、接收、错误计数以及最近一次收发报文的关键信息。
*/
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; /**< 硬件 FIFO0 进入满状态的次数。 */
uint32_t rx_fifo_overfull_count; /**< 硬件 FIFO0 发生 overfull 的次数。 */
uint32_t tx_drop_count; /**< 发送 FIFO 满导致丢弃的报文计数。 */
uint32_t tx_abort_count; /**< 因邮箱全占主动中止占用邮箱的次数(典型:总线无 ACK 导致自动重传卡死)。 */
uint32_t last_rx_id; /**< 最近一次接收报文的标识符。 */
uint32_t last_tx_id; /**< 最近一次发送报文的标识符。 */
uint8_t last_rx_channel; /**< 最近一次接收报文所在的 CAN 通道号(0/1/2)。 */
uint8_t last_tx_channel; /**< 最近一次发送报文所在的 CAN 通道号(0/1/2)。 */
uint8_t last_payload_length; /**< 最近一次收发报文的数据长度。 */
uint8_t last_rx_fd_flag; /**< 最近一次接收报文的 FD 帧标志。 */
uint8_t last_rx_brs; /**< 最近一次接收报文的波特率切换标志。 */
uint8_t last_rx_esi; /**< 最近一次接收报文的错误状态指示位。 */
};
/**
* @brief CANFD 状态发送载荷。
* @note 当前保持协议兼容,仅包含温度与供电相关字段;VIN_ADC 单独在诊断打印中输出。
*/
struct StatusPayload {
uint16_t temp_raw; /**< 内部温度通道原始 ADC 数值。 */
uint16_t vref_raw; /**< 内部参考电压通道原始 ADC 数值。 */
float vdda; /**< 根据内部参考电压换算得到的 MCU 供电电压,单位 V。 */
float temperature; /**< 根据温度通道换算得到的芯片温度,单位 ℃。 */
};
/**
* @brief 初始化 CANFD 外设、GPIO、模式控制、过滤器与中断。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 初始化后默认进入 Normal 模式,并使能 3 路 CAN 控制器的 RX0 中断用于异步接收。
*/
static void init();
/**
* @brief 兼容保留的轮询接口,中断模式下为空壳。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 实际接收已由 CAN0/CAN1/CAN2 的 RX0 中断驱动。
*/
static void poll();
/**
* @brief 根据状态载荷准备一帧待发送的 CANFD 状态报文,并广播入队到 3 路发送 FIFO。
* @param[in] status 指向 StatusPayload 的指针;为空时不入队。
* @param[out] 无。
* @retval 无返回值。
* @note 当前模块默认不会周期性调用该接口;仅在外部显式触发发送测试或业务发送时使用。
*/
static void prepareStatus(const StatusPayload *status);
/**
* @brief 根据状态载荷仅向指定 CAN 通道的发送 FIFO 入队一帧状态报文。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[in] status 指向 StatusPayload 的指针;为空时不入队。
* @param[out] 无。
* @retval bool true 表示成功入队,false 表示通道非法或该通道发送 FIFO 已满。
* @note 当前模块默认不会周期性调用该接口;仅在外部显式触发指定通道发送时使用。
*/
static bool prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status);
/**
* @brief 发送当前已准备好的 CANFD 状态报文。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前模块默认不会周期性调用该接口;仅发送调用方显式准备过的报文;若队列为空则直接返回。
*/
static void sendPrepared();
/**
* @brief 查询 3 个 CAN 通道中是否存在尚未消费的接收报文。
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示至少有一路存在待消费报文,false 表示当前所有通道缓存为空。
*/
static bool hasRxFrame();
/**
* @brief 读取并消费最近一轮扫描到的某一路缓存接收帧。
* @param[in] 无。
* @param[out] id 输出报文标识符的指针;允许为空。
* @param[out] data 输出报文数据缓存区指针;允许为空。
* @param[out] length 输出报文数据长度的指针;允许为空。
* @param[out] channel 输出报文来源的 CAN 通道号指针;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前 3 路缓存均为空。
* @note 每路通道使用轻量环形缓冲保留多帧;消费顺序采用轮转扫描,避免长期偏向固定通道。
*/
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
/**
* @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[in] ext_id 扩展帧 ID(29 位)。
* @param[in] data 数据负载指针。
* @param[in] dlc 数据长度(1~64)。
* @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。
* @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。
*/
static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id,
const uint8_t *data, uint8_t dlc);
/**
* @brief 获取 CANFD 诊断统计信息只读指针。
* @param[in] 无。
* @param[out] 无。
* @retval const Diag* 指向内部诊断统计结构体的只读指针。
*/
static const Diag *diag();
/**
* @brief 将 TCAN1057A 收发器置为正常工作模式。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 模式控制脚按 STB 语义处理:低电平为 Normal,高电平为 Silent。
*/
static void setNormalMode();
/**
* @brief 将 TCAN1057A 收发器置为静默模式。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 静默模式下收发器不驱动总线显性位,可用于总线监听或受控隔离。
*/
static void setSilentMode();
/**
* @brief CAN0 兼容中断处理入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 兼容保留给 CAN0 中断包装使用;三路统一接收逻辑已收口到 handleRxInterrupt()。
*/
static void onRx0Interrupt();
/**
* @brief 处理指定 CAN 通道的 RX0 中断接收。
* @param[in] channelIndex CAN 通道索引(0/1/2)。
* @param[out] 无。
* @retval 无返回值。
*/
static void handleRxInterrupt(uint8_t channelIndex);
private:
static constexpr uint8_t kRxQueueDepth = 16U;
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;
};
/** @brief 初始化 CAN0/CAN1/CAN2 相关 GPIO。 */
static void initGpio();
/** @brief 为指定控制器配置接收过滤器。 */
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber);
/** @brief 配置指定控制器的 CAN 仲裁段标称位时序。 */
static void initNominalTiming(uint32_t canPeriph);
/** @brief 配置指定控制器的 CAN FD 数据段位时序与 TDC。 */
static void initFdMode(uint32_t canPeriph);
/** @brief 构造内部发送缓冲中的状态帧内容。 */
static void buildStatusFrame(const StatusPayload *status);
/** @brief 向指定 CAN 通道的发送 FIFO 写入一帧状态报文。 */
static bool enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status);
static Diag s_diag;
static RxQueue s_rxQueues[3];
static uint8_t s_nextConsumeChannel;
};
using canfd_diag_t = CanFd::Diag;
using canfd_status_payload_t = CanFd::StatusPayload;
#endif /* DEVICE_CANFD_H */