Files
DCU_APP/GD32E508_DCU_APP/device/canfd.h
T
cbb f3d7fc0c30 feat(canfd): 添加收发器静默模式支持和SPI命令接口
添加了CAN FD收发器静默模式功能,允许通过硬件引脚控制发送使能:
- 新增setTxSilent/isTxSilent接口控制TCAN1057收发器Silent模式
- 静默时驱动器关断但接收机继续工作,支持只听不发场景
- 添加SPI命令0xC8用于ESP32远程控制静默模式切换
- 在总线状态位图中增加bit5表示静默状态
- 新增canBusyWaitUs函数用于ISR安全的微秒延时

fix(esc_lan9252): 改进warm jump后LAN9252探测逻辑

修复warm jump导致的LAN9252初始化问题:
- 识别warm jump后芯片非POR状态(idx0!=0x1234)的情况
- 自动补发硬件复位对齐到POR状态后再进行正常探测
- 改进调试信息输出,区分POR状态和warm jump残值情况
- 修正失败路径的复位标志初始化逻辑

refactor(spi): 整理SPI命令文件结构

调整SPI命令相关文件的组织结构:
- 移动诊断相关文件到更合适的位置
- 重新整理SPI命令处理文件的分组
2026-10-10 02:25:56 +08:00

351 lines
18 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 区分四种失败原因;
* 0xE6=通道处于收发器 Silent(只听不发),在入口即拒绝,不计任何错误计数。
*/
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 等)。 */
kErrSilent = 0xE6U, /**< 通道收发器处于 Silent(驱动器关断),拒绝发送。 */
};
/**
* @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, bit5=tx_silent(收发器 Silent,只听不发)。bit0~4 是控制器/总线侧状态,bit5 是本端 S 脚状态。 */
};
/**
* @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 切换指定通道收发器(TCAN1057AV)的 Silent 模式:只听不发。
* S 脚(CAN0=PA8/CAN1=PB14/CAN2=PB9)置高 → 驱动器关断、**接收机照常工作**
* (手册 Table 8-4:RXD 仍镜像总线),所以静默期间接收与上报完全不受影响;
* 置低 → 回 Normal。这是硬件级禁止发送,S 脚内部上拉,MCU 复位/浮空即为 Silent。
* @note 进 Silent 前先 abort 本通道 PENDING 邮箱:驱动器已关,残留邮箱只会让
* 控制器因收不到 ACK 而白涨 TEC。
* 退 Silent 时忙等收发器模式建立时间 tMODE=20µs 再返回,否则紧跟的第一帧
* 驱动不上线。用 DWT CYCCNT 忙等,可在 CS EXTI ISR 上下文调用
* (SysTickTimer::delayMs 在该上下文会死等)。
* 与 canfd.cpp 内 s_chanQuiet(空通道上报节流)无关,也别混为一谈。
* 静默不持久化:上电 init 一律回 Normal。
* @retval true 通道合法且已生效;false 通道非法。 */
static bool setTxSilent(uint8_t channelIndex, bool silent);
/** @brief 查询指定通道是否处于收发器 Silent(唯一真源 s_txSilent,0xC1 bus_state bit5 用它)。 */
static bool isTxSilent(uint8_t channelIndex);
/** @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 volatile uint8_t s_txSilent[3]; /**< 收发器 Silent(只听不发):1=S 脚已置高,TX 入口拒绝 */
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 */