- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪, 满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放 - 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000; ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0) - 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output, 否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF; gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐 - 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环 烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过); HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址 - DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器, task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示 - AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码, task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag - 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B); EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控 - CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径, 邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1) - motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3 - SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印 - 版本号更新至1.5.32
292 lines
13 KiB
C++
292 lines
13 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;
|
||
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();
|
||
|
||
/**
|
||
* @brief L0 轻量自检(不向物理总线发帧)。
|
||
* @retval true 所有已使能通道通过;false 无使能通道或任一路失败。
|
||
* @note 检查:硬件 init 结果、退出 init mode、收发器 Normal、无 BUS_OFF、TEC/REC 阈值。
|
||
*/
|
||
static bool selfTestL0();
|
||
|
||
/**
|
||
* @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 */
|