Files
DCU_APP/GD32E508_DCU_APP/device/CANFD.cpp
T
cbb 959e2aef6e feat(comm): 1.6.14~1.6.20 —— CAN-FD 数据段 5M、批量区改定长覆盖窗口(输入 PDO 496→600)、drop 口径修复、差分写仪表
1.6.14 CAN-FD 数据段 2Mbps→5Mbps:CONFIG_CAN_DATA_PRESCALER 9→3、BS1 3→4TQ
  (90MHz/(3×6TQ),采样点 80%→83.3%),仲裁段仍 1M。TDC filter/offset 维持 4 不做
  等比缩放(TDCMOD=CALC_AND_OFFSET 下硬件已自动测回路延时;放到 12 会让本节点回采
  错开约 6 个数据位,自发热帧被判错误帧)。配套电机 APP/BOOT data_prescaler=2。

1.6.15 段4 批量接收由"环形队列 + 变长紧排排空"改为 5×44B 定长覆盖窗口,输入 PDO
  496→600(75×64bit),段5 诊断区 484→584。动机两条:现场 4000 帧/s 超旧容量 3 帧/拍
  恒丢 1 帧;更关键是排空赤字把队列长期压在饱和态,而 1.6.9~1.6.13 追的错标/拼接竞态
  正是饱和态喂出来的(判满用的 tail 滞后→生产者写进消费者刚读过的槽)。定长消灭
  tail/判满/pending,边界越界写在结构上不可能再犯。每槽带 u32 seqlock:buildInput 在
  PDI_Isr(preempt=0) 抢占主循环写侧,无它则主站会拿到"新 ID + 旧数据"拼出的假帧。
  out+in=984 ≤ 1024 硬上限,DPRAM 留 120B。语义变更:批量区从"不丢的事件流"变"最近
  N 帧窗口",去重与丢帧判据移交主站按 seq 归一。三端同步 size.h/bridge/dc_app/
  gen_eeprom/DCU_ESI.xml,必须重烧固件 + 烧 EEPROM + 断电冷启动,只烧一边 SAFEOP 报
  AL 0x001A/0x0011。

1.6.16 段1/段3 快照 drop 计数口径修复(纯判据,PDO 布局逐字节不动):旧判据
  s_rxValid 置位后永不清零 ⇒ 开机首帧后每帧都算丢弃,drop ≡ rx−1,是零灵敏度假仪表
  且把网页状态栏刷成 ERR。新增 s_rxPublished,判据改 s_rxValid && !s_rxPublished。

1.6.17~19 [DIFF-STATS] 差分写取证仪表,零行为变更,只加 0x2000 sub13..23(11 个只读
  UINT32) ⇒ 不动 PDO 长度/映射,无需重生成 SII。实测:每拍 27.4% 字节真变化(164B)、
  run 峰值 12~13、段2 占变化量 ~53%、段4 ~25%。
  1.6.19 推翻 1.6.18 的自我诊断:比对开销虚报不是 -Oz 把 memcpy 编成调用(手拼移位
  反而 21.3µs→41µs),真根因是 alignas(4) 到 const uint8_t* 形参处丢失,优化器无法
  证明 4 字节对齐。__builtin_assume_aligned + memcpy 后循环体缩成 ldr/ldr/cmp/bne、
  bl 归零。教训:测量工具自身的开销会直接变成结论。

1.6.20 基线对照版(当前提交状态 CONFIG_ECAT_DIFF_STATS=0):同工况(电机在线无指令)
  实测 load 49.1% vs 仪表开的 51.6% ⇒ 仪表自身成本 2.4 个点,差分改造收益 = 0。
  按实测变化量推算 A1(段粒度条件写) 只值约 1 点(段2/段4 每拍都变,必然整块重写),
  仅 A2(8B 块粒度 + run 上限退化为整写) 有空间 —— 推算值,未做 A/B 实测不作为结论。
2026-09-09 03:04:53 +08:00

1864 lines
80 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.
/**
* @file canfd.cpp
* @brief CANFD 通信模块实现。
*/
#include "main.h"
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/cpu_load.h"
#include "sys/systick.h"
#include "sys/task_manager.h"
#include <cstring>
namespace {
/* 纯软件延时循环(不依赖 SysTick,可在任何中断状态下使用) */
static inline void spinDelay(uint32_t loops) {
for (volatile uint32_t d = 0; d < loops; ++d) { }
}
/* DLENC>8 的长度映射,与厂商库 g_can_fdlength_table 一致 */
constexpr uint8_t kFdLenTable[7] = { 12U, 16U, 20U, 24U, 32U, 48U, 64U };
/** 释放 FIFO0 邮箱并等待 RFD0 由硬件自动清 0(手册 26.3.4 第 3 步)。
*
* 两处与厂商库 can_message_receive() 的差别,都是它漏掉的:
* 1) 用**直接赋值**而不是 `|=`。CAN_RFIFO0 里 RFF0(bit3)/RFO0(bit4) 是 rc_w1
* (写 1 清除),读-改-写会把这期间硬件刚置起来的"FIFO 满/溢出"标志顺手
* 清掉 —— 溢出证据就是这么丢的。写 0 对 rc_w1 位无副作用。
* 2) 置位后**等它自清**。手册原文:"将 RFD 置 1 释放邮箱,并且等待其由硬件
* 自动清 0"。不等就接着读下一条,会撞上 dequeue 尚未完成的邮箱窗口。
* @return true=已确认释放完成;false=超时(异常,计数后照常继续,不死等)。 */
static inline bool releaseFifo0(uint32_t canPeriph)
{
CAN_RFIFO0(canPeriph) = CAN_RFIFO0_RFD0;
for (uint32_t n = 0U; n < 2000U; ++n) {
if ((CAN_RFIFO0(canPeriph) & CAN_RFIFO0_RFD0) == 0U) {
/* 1.6.11 注:这里曾加过 128/2048 拍的 settle 延迟,假设"自清后 MDATA
* 还在搬运"是装载时间竞态 —— 实测无效(正弦指令负载下拼接率不变)。
* 节点指纹实验证明拼接是 FIFO 缓冲指针与帧边界失同步(node3 的
* STATUS 帧头 24B = node2 的完整 CONFIG,错位恰为 CONFIG 帧长 6 字),
* 不是延迟能挡的;结构矛盾的帧由 handleRxInterrupt 的内容校验计数
* (1.6.12 起只计不丢 —— 管道必须诚实;源头修复见 1.6.13 的
* CONFIG_IRQ_PREEMPT_CAN_RX=0)。 */
return true;
}
}
return false;
}
/** 取一帧 FIFO0 报文,并校验取数期间硬件没有换帧。
*
* 为什么自己实现(不用厂商库 can_message_receive):一条 CANFD 报文要对同一个
* 地址 CAN_RFIFOMDATA0 连读 dlen/4 次才取完(48B = 12 次),加上头部两次,一条
* 帧就是十几次有先后依赖的总线访问,整个过程**不是原子的**。若这期间硬件把下
* 一帧搬进邮箱窗口,就会读出"前几个字属于上一帧、后几个字属于这一帧"的拼接
* 体,而 id/dlc 取自最开始那次读 —— 实测表现为 STATUS 帧的 ID 配上 CONFIG 帧
* 的前 12~24 字节,且混合边界 100% 落在 4 字节边界上。
*
* 判据用 MP 寄存器:TS[31:16] 是硬件接收时间戳、DLENC[3:0] 是长度,同一帧内两
* 者恒定。取数前后各读一次,只要有一个变了就说明窗口内容已不是原来那帧,整帧
* 作废(计入 rx_torn_count)—— 宁可丢一帧,也不把拼接数据递上去。
*
* @return true=取到一帧完整报文;false=本帧判为撕裂、应丢弃(邮箱仍需释放)。 */
static bool receiveFrameChecked(uint32_t canPeriph, can_receive_message_struct *msg)
{
/* 整段取数放进 BASEPRI 临界区:屏蔽 preempt>=1,把 EtherCAT 的 ESC(1)/SYNC1(1)
* 挡在外面。取数是十几次有先后依赖的寄存器读,被抢占就会读出跨帧拼接体。
* 1.6.13 起本 ISR 自身就是 preempt=0(CONFIG_IRQ_PREEMPT_CAN_RX),与 SYNC0
* 同级互不抢占,取数自此真正原子;MI/MP 回读校验保留,作为对硬件自发换帧
* (dequeue 异步完成等)的兜底观测 —— rx_torn 计数即"该病理是否还在发生"。 */
const uint32_t basepri = RT_CRITICAL_SAVE();
const uint32_t mi0 = CAN_RFIFOMI(canPeriph, CAN_FIFO0);
const uint32_t mp0 = CAN_RFIFOMP(canPeriph, CAN_FIFO0);
msg->rx_ff = static_cast<uint8_t>(CAN_RFIFOMI_FF & mi0);
if (CAN_FF_STANDARD == msg->rx_ff) {
msg->rx_sfid = static_cast<uint32_t>(GET_RFIFOMI_SFID(mi0));
} else {
msg->rx_efid = static_cast<uint32_t>(GET_RFIFOMI_EFID(mi0));
}
msg->rx_ft = static_cast<uint8_t>(CAN_RFIFOMI_FT & mi0);
msg->rx_fi = static_cast<uint8_t>(GET_RFIFOMP_FI(mp0));
msg->fd_flag = static_cast<uint8_t>((CAN_RFIFOMP_FDF & mp0) >> 7);
msg->fd_brs = static_cast<uint8_t>((mp0 & CAN_RFIFOMP_BRS) >> 5);
msg->fd_esi = static_cast<uint8_t>((mp0 & CAN_RFIFOMP_ESI) >> 4);
const uint8_t dlenc = static_cast<uint8_t>(GET_RFIFOMP_DLENC(mp0));
const uint8_t dlen = (dlenc <= 8U) ? dlenc
: ((dlenc <= 15U) ? kFdLenTable[dlenc - 9U] : 64U);
msg->rx_dlen = dlen;
/* 按字取数:与厂商库同样是对同一地址重复读(硬件读指针自动前进)。
* 5~7 字节要多读一个字才取全,与厂商库口径一致。 */
uint32_t words = static_cast<uint32_t>(dlen) / 4U;
if ((1U == words) && (4U != dlen)) {
words++;
}
if (0U == words) {
words = 1U; /* dlen<4 也要读走一个字 */
}
for (uint32_t i = 0U; i < words; ++i) {
const uint32_t w = CAN_RFIFOMDATA0(canPeriph, CAN_FIFO0);
std::memcpy(&msg->rx_data[i * 4U], &w, sizeof(w));
}
/* 取数后回读 MI+MP:任一变化 ⇒ 邮箱窗口在取数期间被换帧,这一帧是拼接体。
* 必须连 MI(完整 ID)一起比 —— 只比 MP 的 TS/DLENC 会漏:TS 是 16 位计数器,
* 相邻两帧可能同值,DLENC 也常常相同(都是 48),于是"ID 报 STATUS、数据却是
* CONFIG"这种错配能从校验里溜过去(实测占坏帧的绝大多数)。ID 变了就一定是
* 换了帧,这条判据没有巧合空间。 */
const uint32_t keep = CAN_RFIFOMP_TS | CAN_RFIFOMP_DLENC;
const uint32_t mp1 = CAN_RFIFOMP(canPeriph, CAN_FIFO0);
const uint32_t mi1 = CAN_RFIFOMI(canPeriph, CAN_FIFO0);
const bool intact = ((mp1 & keep) == (mp0 & keep)) && (mi1 == mi0);
RT_CRITICAL_RESTORE(basepri);
return intact;
}
struct CanChannelConfig {
uint32_t canPeriph;
rcu_periph_enum canRcu;
uint32_t txPort;
uint32_t txPin;
uint32_t rxPort;
uint32_t rxPin;
uint32_t slpPort;
uint32_t slpPin;
IRQn_Type txIrq;
IRQn_Type rx0Irq;
uint8_t filterNumber;
};
constexpr CanChannelConfig kChannels[] = {
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_TX_IRQn, CAN0_RX0_IRQn, 0U },
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_14, CAN1_TX_IRQn, CAN1_RX0_IRQn, 14U },
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_TX_IRQn, CAN2_RX0_IRQn, 2U },
};
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
/* ===== 通道使能开关:在 sys/config.h 中修改 CONFIG_CAN_CHx_ENABLE ===== */
constexpr bool kChannelEnabled[kChannelCount] = {
CONFIG_CAN_CH0_ENABLE,
CONFIG_CAN_CH1_ENABLE,
CONFIG_CAN_CH2_ENABLE,
};
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
static const char *canLastErrorName(uint32_t errReg)
{
static const char *const names[] = {
"NONE", "STUFF", "FORM", "ACK", "BIT_RECESSIVE", "BIT_DOMINANT", "CRC", "SOFTWARE"
};
return names[(errReg >> 4U) & 0x07U];
}
#if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0)
struct CanSelfTestResult {
bool txReq;
uint8_t mailbox;
uint8_t txState;
uint32_t stat;
uint32_t err;
uint32_t tec;
uint32_t rec;
bool busOff;
};
static CanSelfTestResult runSelfTestFrame(uint8_t channelIndex, uint32_t extId, const uint8_t *data, uint8_t len, bool fd, bool brs)
{
CanSelfTestResult result{};
result.mailbox = CAN_NOMAILBOX;
result.txState = CAN_TRANSMIT_FAILED;
if (channelIndex >= kChannelCount || data == nullptr || len == 0U || len > 64U) {
return result;
}
can_trasnmit_message_struct tx_msg{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
tx_msg.tx_efid = extId;
tx_msg.tx_ff = CAN_FF_EXTENDED;
tx_msg.tx_ft = CAN_FT_DATA;
tx_msg.tx_dlen = len;
#ifdef GD32E508
tx_msg.fd_flag = fd ? ENABLE : DISABLE;
tx_msg.fd_brs = (fd && brs) ? ENABLE : DISABLE;
tx_msg.fd_esi = CAN_ESI_DOMINANT;
#endif
for (uint8_t i = 0U; i < len; ++i) {
tx_msg.tx_data[i] = data[i];
}
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
result.mailbox = can_message_transmit(canPeriph, &tx_msg);
result.txReq = (result.mailbox != CAN_NOMAILBOX);
spinDelay(40000U);
if (result.txReq) {
result.txState = static_cast<uint8_t>(can_transmit_states(canPeriph, result.mailbox));
if (result.txState == CAN_TRANSMIT_PENDING) {
(void)can_transmission_stop(canPeriph, result.mailbox);
}
}
result.stat = CAN_STAT(canPeriph);
result.err = CAN_ERR(canPeriph);
result.tec = (result.err >> 16) & 0xFFU;
result.rec = (result.err >> 24) & 0xFFU;
result.busOff = (result.err & CAN_ERR_BOERR) != 0U;
return result;
}
#endif /* CONFIG_CAN_STARTUP_BUS_TEST_ENABLE */
/**
* @brief 对 CAN 外设执行完整硬件级复位(SWRST + RCU 电源周期)。
* @note 解决 GD32E508 CAN1 反复初始化后卡死在 init mode 的问题。
* 步骤:软件复位 → 长延时 → 关时钟 → 长延时 → 开时钟 → 长延时 → 残留状态清理。
*/
static void hardResetCan(uint32_t canPeriph, rcu_periph_enum canRcu)
{
/* 1. 触发 SWRST 软件复位 */
CAN_CTL(canPeriph) |= CAN_CTL_SWRST;
spinDelay(100000U);
/* 2. 确认 SWRST 已被硬件自动清零;若未清零则强制写 0 并等待 */
if (CAN_CTL(canPeriph) & CAN_CTL_SWRST) {
CAN_CTL(canPeriph) &= ~CAN_CTL_SWRST;
spinDelay(50000U);
}
/* 3. 关 RCU 时钟,等待外设域完全放电 */
rcu_periph_clock_disable(canRcu);
spinDelay(200000U); /* ~1.1ms @180MHz */
/* 4. 重新开时钟,等待外设域上电稳定 */
rcu_periph_clock_enable(canRcu);
spinDelay(200000U); /* ~1.1ms @180MHz */
/* 5. can_deinit() 走标准固件库复位流程 */
can_deinit(canPeriph);
/* 6. 若复位后 IWMOD 仍然置位(外设残留状态),强制清 init mode */
if (CAN_CTL(canPeriph) & CAN_CTL_IWMOD) {
CAN_CTL(canPeriph) &= ~CAN_CTL_IWMOD;
for (volatile uint32_t d = 0; d < 100000U; ++d) {
if ((CAN_STAT(canPeriph) & CAN_STAT_IWS) == 0U) break;
}
}
}
} // namespace
CanFd::Diag CanFd::s_diag{};
CanFd::RxQueue CanFd::s_rxQueues[3]{};
CanFd::TxQueue CanFd::s_txQueues[3]{};
uint8_t CanFd::s_nextConsumeChannel = 0U;
volatile CanFd::Mode CanFd::s_mode = CanFd::Mode::kModeAllPass;
CanFd::ChannelStats CanFd::s_channelStats[3]{};
CanFd::ChannelFeedback CanFd::s_feedback[3]{};
uint32_t CanFd::s_feedbackSeq = 0U;
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
bool CanFd::s_channelHwOk[3]{};
bool CanFd::s_brsSuppress[3]{};
/* TX 错误事件标志(ARD=1 单发丢帧通知):TX 完成 ISR 置位,主循环
* consumeTxError() 读清;单写单读,无锁。 */
static volatile bool s_txErrorEvent[3]{};
/**
* @brief 查询指定通道是否已使能。
*/
bool CanFd::isChannelEnabled(uint8_t channelIndex)
{
return (channelIndex < kChannelCount) && kChannelEnabled[channelIndex];
}
/**
* @brief 初始化三路 CANFD 相关 GPIO。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 为 CAN0/CAN1/CAN2 的 TX、RX 及收发器 SLP 控制脚统一完成 GPIO 时钟与模式配置。
*/
void CanFd::initGpio()
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_AF);
#ifdef GPIO_CAN0_PARTIAL_REMAP
/* Bootloader uses CAN0 on PB8/PB9 and jumps to APP without a system reset,
* so AFIO_PCF0 survives can_deinit(). Restore APP mapping PA11/P12 here. */
gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, DISABLE);
#endif
#ifdef GPIO_CAN0_FULL_REMAP
gpio_pin_remap_config(GPIO_CAN0_FULL_REMAP, DISABLE);
#endif
#ifdef GPIO_CAN1_REMAP
/* CAN1_REMAP=0 selects PB12=RX and PB13=TX on GD32E508. */
gpio_pin_remap_config(GPIO_CAN1_REMAP, DISABLE);
#endif
#ifdef AFIO_PE0_CAN2_CFG
gpio_afio_port_config(AFIO_PE0_CAN2_CFG, ENABLE);
#endif
#ifdef AFIO_PE1_CAN2_CFG
gpio_afio_port_config(AFIO_PE1_CAN2_CFG, ENABLE);
#endif
CAN_LOGI("[CAN-GPIO] AFIO_PCF0=0x%08lX CAN0=PA11/PA12 CAN1=PB12/PB13\r\n",
static_cast<unsigned long>(AFIO_PCF0));
for(size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
gpio_init(ch.rxPort, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, ch.rxPin);
gpio_init(ch.txPort, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, ch.txPin);
gpio_init(ch.slpPort, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, ch.slpPin);
/* 上电先 STB=1(待机),延时后再进入 Normal */
gpio_bit_set(ch.slpPort, ch.slpPin);
}
}
/**
* @brief 为指定 CAN 控制器配置接收过滤器。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] filterNumber 过滤器编号。
* @param[in] mode 目标接收过滤模式。
* @param[out] 无。
* @retval 无返回值。
* @note kModeAllPass 模式下使用 32 位掩码模式且 mask=0/list=0,硬件直接放行所有标准/扩展帧到 FIFO0;
* kModeFilterId 模式下按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧掩码匹配。
*/
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode)
{
can_filter_parameter_struct can_filter{};
can_filter.filter_number = filterNumber;
can_filter.filter_mode = CAN_FILTERMODE_MASK;
can_filter.filter_bits = CAN_FILTERBITS_32BIT;
if (mode == Mode::kModeAllPass || mode == Mode::kModeBridge0To12) {
/* 全部接收:列表与掩码都为 0,硬件不再对 ID 做比较。 */
can_filter.filter_list_high = 0x0000U;
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = 0x0000U;
can_filter.filter_mask_low = 0x0000U;
} else {
can_filter.filter_list_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U);
can_filter.filter_list_low = 0x0000U;
can_filter.filter_mask_high = static_cast<uint16_t>((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U);
can_filter.filter_mask_low = static_cast<uint16_t>(1U << 2U);
}
can_filter.filter_fifo_number = CAN_FIFO0;
can_filter.filter_enable = ENABLE;
can_filter_init(canPeriph, &can_filter);
}
/**
* @brief 配置指定 CAN 控制器的标称仲裁段位时序。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[out] 无。
* @retval 无返回值。
* @note 时钟源为 APB1=90MHz;按 CONFIG_CAN_NOMINAL_* 宏配置,目标 1 Mbps。
* 实际波特率 = 90MHz / (CONFIG_CAN_NOMINAL_PRESCALER × (1+BS1+BS2))。
*/
bool CanFd::initNominalTiming(uint32_t canPeriph)
{
can_parameter_struct can_parameter{};
can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
can_parameter.time_triggered = DISABLE;
can_parameter.auto_bus_off_recovery = ENABLE;
can_parameter.auto_wake_up = DISABLE;
can_parameter.auto_retrans = ENABLE; /* 默认自动重发兜底偶发 ACK 丢失;OTA 停等协议期间由 setChannelAutoRetrans(ch,false) 临时关闭,避免重发打穿电机单缓冲 RX */
/* 1.6.11 真根因:这里曾是 ENABLE —— FIFO 满时**覆盖最旧邮箱**,而最旧邮箱
* 正是 CPU 正在读取的输出窗口。buildInput 在 PDI_Isr(preempt1) 占 ~357µs
* 会压住 CAN ISR(preempt2),发指令时电机 CONFIG 翻倍到 2kHz/节点、总线
* ~8kHz,阻塞窗口内到达 ~3 帧恰好打满 FIFO 深度 → 覆盖进行到一半被读走
* = 跨节点/跨类型拼接帧(指纹实验实锤:node2 的 CONFIG 条目带 node3 的
* 速度限 150 与反相角度;settle 延迟/重取/MI-MP 校验都挡不住,因为
* MI/MP 是覆盖序列里最后更新的一环)。DISABLE = 溢出丢新帧并置 RFO0
* (rx_fifo_overfull_count 已在统计):STATUS/CONFIG 均周期量,丢一拍下拍
* 即补;覆盖递上来的才是网页乱码。与本文件软件队列"满丢新不覆旧"同则。 */
can_parameter.rec_fifo_overwrite = DISABLE;
can_parameter.trans_fifo_order = ENABLE;
can_parameter.working_mode = CAN_NORMAL_MODE;
can_parameter.resync_jump_width = CONFIG_CAN_NOMINAL_SJW;
can_parameter.time_segment_1 = CONFIG_CAN_NOMINAL_BS1;
can_parameter.time_segment_2 = CONFIG_CAN_NOMINAL_BS2;
can_parameter.prescaler = CONFIG_CAN_NOMINAL_PRESCALER;
return can_init(canPeriph, &can_parameter) == SUCCESS;
}
/**
* @brief 配置指定 CAN 控制器的 CAN FD 数据段参数。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[out] 无。
* @retval 无返回值。
* @note 时钟源为 APB1=90MHz;按 CONFIG_CAN_DATA_* 宏配置,目标 5.0 Mbps。
* 实际数据段波特率 = 90MHz / (CONFIG_CAN_DATA_PRESCALER × (1+BS1+BS2))。
* BRS 受 CONFIG_CAN_FD_BRS_ENABLE 控制。
*/
bool CanFd::initFdMode(uint32_t canPeriph)
{
#ifdef GD32E508
can_fdframe_struct can_fd_parameter{};
can_fd_tdc_struct can_fd_tdc_parameter{};
can_struct_para_init(CAN_FD_FRAME_STRUCT, &can_fd_parameter);
can_fd_parameter.fd_frame = ENABLE;
can_fd_parameter.excp_event_detect = ENABLE;
can_fd_parameter.delay_compensation = ENABLE;
/* TDCF/TDCO 不随数据波特率等比缩放:TDCMOD=CALC_AND_OFFSET 下硬件已自动测得
* 回路延时(TDCV),这里只是微调,经验取 TDCO ≈ 数据段 BS1 的 tq 数(4TQ→4)。
* 放到 12 会让本节点回采时错开约 6 个数据位,自己发出的 FD+BRS 帧被判成错误帧。 */
can_fd_tdc_parameter.tdc_filter = 0x04U;
can_fd_tdc_parameter.tdc_mode = CAN_TDCMOD_CALC_AND_OFFSET;
can_fd_tdc_parameter.tdc_offset = 0x04U;
can_fd_parameter.p_delay_compensation = &can_fd_tdc_parameter;
can_fd_parameter.iso_bosch = CAN_FDMOD_ISO;
can_fd_parameter.esi_mode = CAN_ESIMOD_HARDWARE;
can_fd_parameter.data_prescaler = CONFIG_CAN_DATA_PRESCALER;
can_fd_parameter.data_resync_jump_width = CONFIG_CAN_DATA_SJW;
can_fd_parameter.data_time_segment_1 = CONFIG_CAN_DATA_BS1;
can_fd_parameter.data_time_segment_2 = CONFIG_CAN_DATA_BS2;
return can_fd_init(canPeriph, &can_fd_parameter) == SUCCESS;
#else
can_fd_function_enable(canPeriph);
return true;
#endif
}
#if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0)
/** @brief 打印 SYS/APB1 时钟与目标波特率。 */
void CanFd::printClockInfo()
{
CAN_LOGI("[CAN-CLOCK] SYS=%luHz APB1=%luHz nominal=1Mbps data=2Mbps\r\n",
static_cast<unsigned long>(SystemCoreClock),
static_cast<unsigned long>(rcu_clock_freq_get(CK_APB1)));
}
#endif
/** @brief 单通道硬件初始化(hardResetCan+重试+退出init mode+中断使能+RX队列清零)。 */
void CanFd::initChannelHardware(uint8_t i)
{
const auto &ch = kChannels[i];
if (!kChannelEnabled[i]) {
s_channelHwOk[i] = false;
return;
}
s_channelHwOk[i] = false;
rcu_periph_clock_enable(ch.canRcu);
gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */
const uint8_t rxLevelBefore = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
const uint8_t stbLevel = (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) ? 1U : 0U;
/* 强制复位 + 重试机制:使用 hardResetCan 完整电源周期解决 CAN1 卡死问题 */
bool nominalOk = false;
bool fdOk = false;
for (uint8_t retry = 0U; retry < 5U; ++retry) {
hardResetCan(ch.canPeriph, ch.canRcu);
nominalOk = initNominalTiming(ch.canPeriph);
fdOk = initFdMode(ch.canPeriph);
if (nominalOk && fdOk) {
break; /* 初始化成功,无需重试 */
}
/* can_init 失败后外设可能卡在 init mode,先强制退出让下次重试从干净状态开始 */
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
for (volatile uint32_t d = 0; d < 50000U; ++d) {
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break;
}
LOG_ERR("[CAN-WARN] CH%u init failed (nom=%u fd=%u STAT=0x%08lX), retry %u/5\r\n",
static_cast<unsigned>(i),
nominalOk ? 1U : 0U, fdOk ? 1U : 0U,
static_cast<unsigned long>(CAN_STAT(ch.canPeriph)),
static_cast<unsigned>(retry + 1U));
}
/* 滤波器配置延迟到循环结束后统一执行:
* CAN1 的 hardResetCan 含 SWRST,会清零 CAN0/CAN1 共享滤波器寄存器,
* 若在循环内配 CAN0 filter 后再做 CAN1 hardReset,CAN0 filter 会被破坏。 */
/* 退出 init mode,确保进入 NORMAL 工作模式 */
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
uint32_t exitWait = 0U;
for (; exitWait < 200000U; ++exitWait) {
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) {
break;
}
}
/* 兜底:若仍然卡在 init mode,做一次额外的 SWRST + 强制退出 */
if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) {
CAN_CTL(ch.canPeriph) |= CAN_CTL_SWRST;
spinDelay(50000U);
CAN_CTL(ch.canPeriph) &= ~(CAN_CTL_SWRST | CAN_CTL_IWMOD);
for (volatile uint32_t d = 0; d < 200000U; ++d) {
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break;
}
LOG_ERR("[CAN-WARN] CH%u needed extra SWRST to exit init mode (STAT=0x%08lX)\r\n",
static_cast<unsigned>(i),
static_cast<unsigned long>(CAN_STAT(ch.canPeriph)));
}
const uint32_t statAfterExit = CAN_STAT(ch.canPeriph);
const uint32_t errAfterExit = CAN_ERR(ch.canPeriph);
const uint8_t rxLevelAfter = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
const uint32_t tecAfterExit = (errAfterExit >> 16) & 0xFFU;
const uint32_t recAfterExit = (errAfterExit >> 24) & 0xFFU;
/* 正常上电静默:只在本端控制器初始化失败(can_init/can_fd_init 失败或卡 init mode)
* 时打印一行诊断。TEC/REC 属总线侧状态,未接从站时浮空噪声会累积,不作为打印条件。 */
const bool exitedInitMode = ((statAfterExit & CAN_STAT_IWS) == 0U);
if (!nominalOk || !fdOk || !exitedInitMode) {
LOG_ERR("[CAN-DIAG] CH%u init_nom=%u init_fd=%u exit_ok=%u wait=%lu rx_before=%u rx_after=%u stb=%u STAT=0x%08lX ERR=0x%08lX TEC=%lu REC=%lu\r\n",
static_cast<unsigned>(i),
nominalOk ? 1U : 0U,
fdOk ? 1U : 0U,
exitedInitMode ? 1U : 0U,
static_cast<unsigned long>(exitWait),
static_cast<unsigned>(rxLevelBefore),
static_cast<unsigned>(rxLevelAfter),
static_cast<unsigned>(stbLevel),
static_cast<unsigned long>(statAfterExit),
static_cast<unsigned long>(errAfterExit),
static_cast<unsigned long>(tecAfterExit),
static_cast<unsigned long>(recAfterExit));
}
CAN_TSTAT(ch.canPeriph) = CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2;
can_interrupt_enable(ch.canPeriph, CAN_INT_TME | CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.txIrq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
/* 1.6.13:RX 提到 preempt=0 —— 见 sys/config.h CONFIG_IRQ_PREEMPT_CAN_RX 注释。 */
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN_RX, static_cast<uint8_t>(i));
s_rxQueues[i].head = 0U;
s_rxQueues[i].tail = 0U;
s_rxQueues[i].count = 0U;
s_txQueues[i].head = 0U;
s_txQueues[i].tail = 0U;
s_txQueues[i].count = 0U;
const bool exitOk = ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U);
s_channelHwOk[i] = (nominalOk && fdOk && exitOk);
}
/** @brief 统一配置三路滤波器(HBC1F + per-channel filter)。 */
void CanFd::configureAllFilters()
{
/* ===== 统一配置滤波器(必须在所有 hardResetCan 完成之后) =====
* CAN1 hardResetCan 的 SWRST 会清零 CAN0/CAN1 共享滤波器寄存器域。
* 若在循环内先配 CAN0 filter、后做 CAN1 hardReset,CAN0 的 filter 0 会被
* 硬件清零(CAN_FW bit0 = 0),导致 CAN0 无法接收任何帧。
* 修复方案:所有 hardReset 完成后,再统一配 HBC1F + 三路 filter。 */
can1_filter_start_bank(14U); /* CAN0: bank 0-13, CAN1: bank 14-27 */
for (size_t i = 0; i < kChannelCount; ++i) {
if (!kChannelEnabled[i]) continue;
configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, s_mode);
}
CAN_LOGI("[CAN] filters configured post-init: HBC1F=%lu FW=0x%08lX\r\n",
static_cast<unsigned long>((CAN_FCTL(CAN0) >> 8) & 0x3FU),
static_cast<unsigned long>(CAN_FW(CAN0)));
}
#if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0)
/** @brief 打印初始化结果汇总(BT/DBT/滤波器状态)。 */
void CanFd::printInitResult()
{
/* ===== 初始化结果汇总 ===== */
CAN_LOGI("=== CAN INIT RESULT ===\r\n");
for (size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
uint32_t stat = CAN_STAT(ch.canPeriph);
uint32_t bt = CAN_BT(ch.canPeriph);
uint32_t dbt = CAN_DBT(ch.canPeriph);
bool ok = (stat & CAN_STAT_IWS) == 0U;
CAN_LOGI("CH%u: %s BT=0x%08lX DBT=0x%08lX\r\n",
static_cast<unsigned>(i),
ok ? "OK" : "FAIL(init_mode)",
static_cast<unsigned long>(bt),
static_cast<unsigned long>(dbt));
}
/* 滤波器一行 */
{
uint32_t fw = CAN_FW(CAN0);
uint32_t fw2 = CAN_FW(CAN2);
uint32_t hbc = (CAN_FCTL(CAN0) >> 8) & 0x3FU;
CAN_LOGI("FILTER: HBC1F=%lu CAN01_FW=0x%08lX(b0=%lu b2=%lu b14=%lu) CAN2_FW=0x%08lX(b2=%lu)\r\n",
static_cast<unsigned long>(hbc),
static_cast<unsigned long>(fw),
static_cast<unsigned long>((fw >> 0) & 1U),
static_cast<unsigned long>((fw >> 2) & 1U),
static_cast<unsigned long>((fw >> 14) & 1U),
static_cast<unsigned long>(fw2),
static_cast<unsigned long>((fw2 >> 2) & 1U));
}
}
/** @brief 打印初始化后错误计数器快照(TEC/REC/BOFF/EPV/EW)。 */
void CanFd::printErrorState()
{
/* ===== 初始化后错误计数器快照(不发帧,避免启动时累积 TEC)===== */
LOG_ERR("=== CAN INIT ERR STATE ===\r\n");
for (size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
uint32_t err = CAN_ERR(ch.canPeriph);
uint32_t tec = (err >> 16) & 0xFFU;
uint32_t rec = (err >> 24) & 0xFFU;
LOG_ERR("CH%u: ERR=0x%08lX TEC=%lu REC=%lu boff=%u epv=%u ew=%u\r\n",
static_cast<unsigned>(i),
static_cast<unsigned long>(err),
static_cast<unsigned long>(tec),
static_cast<unsigned long>(rec),
(err & CAN_ERR_BOERR) ? 1U : 0U, /* BOFF bit2 */
(err & CAN_ERR_PERR) ? 1U : 0U, /* EPV bit1 */
(err & CAN_ERR_WERR) ? 1U : 0U); /* EWARN bit0 */
}
}
#endif /* CONFIG_CAN_STARTUP_DIAG_VERBOSE */
#if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0)
/** @brief 物理总线自测。 */
void CanFd::runSelfTest()
{
/* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */
CAN_LOGI("=== CAN SELF-TEST (normal mode) ===\r\n");
for (size_t i = 0; i < kChannelCount; ++i) {
if (!kChannelEnabled[i]) {
CAN_LOGI("CH%u: SKIP(disabled)\r\n", static_cast<unsigned>(i));
continue;
}
const uint8_t ch = static_cast<uint8_t>(i);
uint8_t data8[8] = {0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57};
uint8_t data64[64];
for (uint8_t j = 0; j < 64U; ++j) data64[j] = 0xA0 + (j & 0x0F);
const CanSelfTestResult c = runSelfTestFrame(ch, 0x1FFFFF00U | i, data8, 8, false, false);
const CanSelfTestResult fd = runSelfTestFrame(ch, 0x1FFFFF10U | i, data64, 64, true, false);
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
const CanSelfTestResult brs = runSelfTestFrame(ch, 0x1FFFFF20U | i, data64, 64, true, true);
CAN_LOGI("CH%u: classic=%s(fd=0) fd_nobrs=%s(fd=1) fd_brs=%s(brs=1) TEC=%lu\r\n",
static_cast<unsigned>(i),
c.tec == 0 ? "ACK" : "NO_ACK",
fd.tec == 0 ? "ACK" : "NO_ACK",
brs.tec == 0 ? "ACK" : "NO_ACK",
static_cast<unsigned long>(brs.tec > fd.tec ? brs.tec : fd.tec));
#else
CAN_LOGI("CH%u: classic=%s fd_nobrs=%s TEC=%lu\r\n",
static_cast<unsigned>(i),
c.tec == 0 ? "ACK" : "NO_ACK",
fd.tec == 0 ? "ACK" : "NO_ACK",
static_cast<unsigned long>(fd.tec));
#endif
}
}
#endif /* CONFIG_CAN_STARTUP_BUS_TEST_ENABLE */
#if (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE != 0)
/** @brief 内部回环测试。 */
void CanFd::runLoopbackTest()
{
/* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */
CAN_LOGI("=== CAN LOOPBACK (internal) ===\r\n");
for (size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
if (!kChannelEnabled[i]) {
CAN_LOGI("CH%u: SKIP(disabled)\r\n", static_cast<unsigned>(i));
continue;
}
if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) {
CAN_LOGI("CH%u: SKIP(stuck_init)\r\n", static_cast<unsigned>(i));
continue;
}
/* 进入 loopback 模式:用固件库 API,失败则用寄存器直接操作兜底 */
ErrStatus r1 = can_working_mode_set(ch.canPeriph, CAN_MODE_INITIALIZE);
ErrStatus r2 = can_monitor_mode_set(ch.canPeriph, CAN_LOOPBACK_MODE);
ErrStatus r3 = can_working_mode_set(ch.canPeriph, CAN_MODE_NORMAL);
bool lbkmOk = (CAN_BT(ch.canPeriph) & (1U << 30)) != 0U;
/* 如果 API 失败,尝试直接操作寄存器 */
if (!lbkmOk) {
CAN_CTL(ch.canPeriph) |= CAN_CTL_IWMOD; /* 强制进入 init mode */
for (volatile uint32_t t = 0; t < 100000U; ++t) {
if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) break;
}
CAN_BT(ch.canPeriph) |= (1U << 30); /* 直接设 LCMOD=1 */
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD; /* 退出 init mode */
for (volatile uint32_t t = 0; t < 100000U; ++t) {
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break;
}
lbkmOk = (CAN_BT(ch.canPeriph) & (1U << 30)) != 0U;
}
if (!lbkmOk) {
CAN_LOGI("CH%u: SKIP(lbk_fail API:%d/%d/%d reg:LCMOD=0)\r\n",
static_cast<unsigned>(i), r1, r2, r3);
/* 恢复 normal */
CAN_CTL(ch.canPeriph) |= CAN_CTL_IWMOD;
for (volatile uint32_t t = 0; t < 100000U; ++t) { if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) break; }
CAN_BT(ch.canPeriph) &= ~(3U << 30);
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
for (volatile uint32_t t = 0; t < 100000U; ++t) { if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break; }
continue;
}
/* --- 经典 CAN --- */
const uint32_t rxB1 = s_channelStats[i].rx_count;
{
can_trasnmit_message_struct tx{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx);
tx.tx_sfid = 0x100U + i; tx.tx_ff = CAN_FF_STANDARD; tx.tx_ft = CAN_FT_DATA; tx.tx_dlen = 8;
for (uint8_t j = 0; j < 8U; ++j) tx.tx_data[j] = 0x55 + j;
can_message_transmit(ch.canPeriph, &tx);
}
for (volatile uint32_t t = 0; t < 500000U; ++t) {
if (s_channelStats[i].rx_count > rxB1 || can_receive_message_length_get(ch.canPeriph, CAN_FIFO0)) break;
}
/* 消费 FIFO 中可能残留的帧 */
while (can_receive_message_length_get(ch.canPeriph, CAN_FIFO0) > 0U) {
can_receive_message_struct rx{}; can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx);
}
bool cOk = (s_channelStats[i].rx_count - rxB1) > 0U;
/* --- FD no BRS --- */
const uint32_t rxB2 = s_channelStats[i].rx_count;
{
can_trasnmit_message_struct tx{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx);
tx.tx_sfid = 0x200U + i; tx.tx_ff = CAN_FF_STANDARD; tx.tx_ft = CAN_FT_DATA; tx.tx_dlen = 64;
tx.fd_flag = ENABLE; tx.fd_brs = DISABLE; tx.fd_esi = CAN_ESI_DOMINANT;
for (uint8_t j = 0; j < 64U; ++j) tx.tx_data[j] = 0xAA + (j & 0x0F);
can_message_transmit(ch.canPeriph, &tx);
}
for (volatile uint32_t t = 0; t < 500000U; ++t) {
if (s_channelStats[i].rx_count > rxB2 || can_receive_message_length_get(ch.canPeriph, CAN_FIFO0)) break;
}
while (can_receive_message_length_get(ch.canPeriph, CAN_FIFO0) > 0U) {
can_receive_message_struct rx{}; can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx);
}
bool fdOk = (s_channelStats[i].rx_count - rxB2) > 0U;
/* --- FD BRS --- */
const uint32_t rxB3 = s_channelStats[i].rx_count;
{
can_trasnmit_message_struct tx{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx);
tx.tx_sfid = 0x300U + i; tx.tx_ff = CAN_FF_STANDARD; tx.tx_ft = CAN_FT_DATA; tx.tx_dlen = 64;
tx.fd_flag = ENABLE; tx.fd_brs = ENABLE; tx.fd_esi = CAN_ESI_DOMINANT;
for (uint8_t j = 0; j < 64U; ++j) tx.tx_data[j] = 0xBB + (j & 0x0F);
can_message_transmit(ch.canPeriph, &tx);
}
for (volatile uint32_t t = 0; t < 500000U; ++t) {
if (s_channelStats[i].rx_count > rxB3 || can_receive_message_length_get(ch.canPeriph, CAN_FIFO0)) break;
}
while (can_receive_message_length_get(ch.canPeriph, CAN_FIFO0) > 0U) {
can_receive_message_struct rx{}; can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx);
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx);
}
bool brsOk = (s_channelStats[i].rx_count - rxB3) > 0U;
uint32_t tecLb = (CAN_ERR(ch.canPeriph) >> 16) & 0xFFU;
CAN_LOGI("CH%u: classic=%s fd=%s fd_brs=%s TEC=%lu\r\n",
static_cast<unsigned>(i),
cOk ? "PASS" : "FAIL",
fdOk ? "PASS" : "FAIL",
brsOk ? "PASS" : "FAIL",
static_cast<unsigned long>(tecLb));
/* 回环前后寄存器快照,用于诊断 LCMOD 清除是否干净 */
uint32_t btBeforeExit = CAN_BT(ch.canPeriph);
uint32_t ctlBeforeExit = CAN_CTL(ch.canPeriph);
/* 回到 NORMAL 模式:进 init → 清 LCMOD+SCMOD → 退出 init */
CAN_CTL(ch.canPeriph) |= CAN_CTL_IWMOD;
for (volatile uint32_t t = 0; t < 100000U; ++t) { if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) break; }
CAN_BT(ch.canPeriph) &= ~(3U << 30); /* 清 LCMOD+SCMOD */
CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD;
for (volatile uint32_t t = 0; t < 200000U; ++t) {
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break;
}
uint32_t errAfterLb = CAN_ERR(ch.canPeriph);
uint32_t tecAfterLb = (errAfterLb >> 16) & 0xFFU;
uint32_t recAfterLb = (errAfterLb >> 24) & 0xFFU;
uint32_t statAfterLb = CAN_STAT(ch.canPeriph);
CAN_LOGI("CH%u: post-lb TEC=%lu REC=%lu STAT=0x%08lX BT=0x%08lX CTL=0x%08lX\r\n",
static_cast<unsigned>(i),
static_cast<unsigned long>(tecAfterLb),
static_cast<unsigned long>(recAfterLb),
static_cast<unsigned long>(statAfterLb),
static_cast<unsigned long>(CAN_BT(ch.canPeriph)),
static_cast<unsigned long>(CAN_CTL(ch.canPeriph)));
}
CAN_LOGI("========================\r\n");
}
#endif /* CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE */
#if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0)
/** @brief 全寄存器快照转储(CTL/STAT/BT/DBT/ERR/INTEN/FCTL/FW/RFL0)。 */
void CanFd::dumpRegisters()
{
/* ===== 初始化后全寄存器快照:诊断 CAN0/CAN1 不收帧 ===== */
CAN_LOGI("=== POST-INIT FULL REG DUMP ===\r\n");
for (size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
uint32_t ctl = CAN_CTL(ch.canPeriph);
uint32_t stat = CAN_STAT(ch.canPeriph);
uint32_t bt = CAN_BT(ch.canPeriph);
uint32_t dbt = CAN_DBT(ch.canPeriph);
uint32_t err = CAN_ERR(ch.canPeriph);
uint32_t inten = CAN_INTEN(ch.canPeriph);
uint32_t fw = (ch.canPeriph == CAN2) ? CAN_FW(CAN2) : CAN_FW(CAN0);
uint32_t fctl = CAN_FCTL(ch.canPeriph);
uint32_t rfl0 = CAN_RFIFO0(ch.canPeriph) & 0x3U; /* RFL0 bits 1:0 */
bool normalMode = ((stat & CAN_STAT_IWS) == 0U) && ((bt & (3U << 30)) == 0U);
CAN_LOGI("CH%u: %s CTL=0x%08lX STAT=0x%08lX BT=0x%08lX DBT=0x%08lX\r\n"
" ERR=0x%08lX INTEN=0x%08lX FCTL=0x%08lX FW=0x%08lX RFL0=%lu\r\n",
static_cast<unsigned>(i),
normalMode ? "NORMAL" : "!!!NOT_NORMAL!!!",
static_cast<unsigned long>(ctl),
static_cast<unsigned long>(stat),
static_cast<unsigned long>(bt),
static_cast<unsigned long>(dbt),
static_cast<unsigned long>(err),
static_cast<unsigned long>(inten),
static_cast<unsigned long>(fctl),
static_cast<unsigned long>(fw),
static_cast<unsigned long>(rfl0));
/* 逐位解析关键标志 */
CAN_LOGI(" IWMOD=%u SLPWMOD=%u ARD=%u ABOR=%u | IWS=%u SLPWS=%u | LCMOD=%u SCMOD=%u\r\n",
(ctl & CAN_CTL_IWMOD) ? 1U : 0U,
(ctl & CAN_CTL_SLPWMOD) ? 1U : 0U, /* bit1: sleep mode request */
(ctl & CAN_CTL_ARD) ? 1U : 0U, /* bit4: auto retrans disable */
(ctl & CAN_CTL_ABOR) ? 1U : 0U, /* bit6: auto bus-off recovery */
(stat & CAN_STAT_IWS) ? 1U : 0U,
(stat & CAN_STAT_SLPWS) ? 1U : 0U,
(bt & (1U << 30)) ? 1U : 0U, /* LCMOD */
(bt & (1U << 31)) ? 1U : 0U); /* SCMOD */
}
CAN_LOGI("===============================\r\n");
}
#endif /* CONFIG_CAN_STARTUP_DIAG_VERBOSE */
/**
* @brief 初始化三路 CANFD 控制器、过滤器、中断与软件队列。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 初始化收发器 GPIO 后延时进入 Normal,并启用 FIFO0 中断统计。
*/
void CanFd::init()
{
s_diag = {};
s_nextConsumeChannel = 0U;
s_mode = Mode::kModeAllPass;
for (size_t i = 0; i < kChannelCount; ++i) {
s_channelStats[i] = {};
s_feedback[i] = {};
s_channelHwOk[i] = false;
}
s_feedbackSeq = 0U;
initGpio();
#if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0)
printClockInfo();
#endif
/* 收发器待机保持后,延时再进入 Normal */
constexpr uint32_t kCanPowerOnDeferMs = 200U;
SysTickTimer::delayMs(kCanPowerOnDeferMs);
for (uint8_t i = 0; i < kChannelCount; ++i) {
initChannelHardware(i);
}
s_nextConsumeChannel = 0U;
configureAllFilters();
#if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0)
printInitResult();
printErrorState();
dumpRegisters();
#endif
#if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0)
runSelfTest();
#endif
#if (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE != 0)
runLoopbackTest();
#endif
}
/**
* @brief L0 轻量自检:无总线发帧,仅校验使能通道控制器自身状态(GD32 侧故障)。
* @note 1.5.35:不再检查 BUS_OFF/TEC/REC——未接从站的通道 RX 浮空噪声会累积 REC,
* 「没接设备」不等于错误;总线侧健康由 0xC1 反馈帧运行期上报。
*/
bool CanFd::selfTestL0(uint8_t *fault_code, uint8_t *fault_channel)
{
bool anyEnabled = false;
bool allOk = true;
uint8_t firstCode = kSelfTestOk;
uint8_t firstCh = 0U;
for (uint8_t i = 0U; i < static_cast<uint8_t>(kChannelCount); ++i) {
if (!kChannelEnabled[i]) {
continue;
}
anyEnabled = true;
const auto &ch = kChannels[i];
uint8_t code = kSelfTestOk;
if (!s_channelHwOk[i]) {
code = kSelfTestHwInit;
} else if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) != 0U) {
/* 仍停在初始化模式 */
code = kSelfTestStuckInit;
} else if (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) {
/* STB=0 为 Normal;输出寄存器回读确认配置生效 */
code = kSelfTestStbLevel;
}
if (code != kSelfTestOk) {
allOk = false;
if (firstCode == kSelfTestOk) {
firstCode = code;
firstCh = i;
}
LOG_ERR("[CAN] L0 selftest CH%u FAIL code=%u (STAT=0x%08lX ERR=0x%08lX)\r\n",
static_cast<unsigned>(i), static_cast<unsigned>(code),
static_cast<unsigned long>(CAN_STAT(ch.canPeriph)),
static_cast<unsigned long>(CAN_ERR(ch.canPeriph)));
}
}
if (fault_code != nullptr) {
*fault_code = firstCode;
}
if (fault_channel != nullptr) {
*fault_channel = firstCh;
}
return anyEnabled && allOk;
}
/**
* @brief 切换 CANFD 接收过滤模式。
* @param[in] mode 目标过滤模式。
* @note 切换时逐路重新配置过滤器;can_filter_init 内部会清 FCTL_FLD,无需手动进入 INITIAL 模式。
*/
void CanFd::setMode(Mode mode)
{
uint32_t save = RT_CRITICAL_SAVE();
s_mode = mode;
for (size_t i = 0; i < kChannelCount; ++i) {
configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, mode);
}
RT_CRITICAL_RESTORE(save);
}
/**
* @brief 查询当前 CANFD 接收过滤模式。
*/
CanFd::Mode CanFd::mode()
{
return s_mode;
}
/**
* @brief 查询三路 CAN 通道是否存在待消费接收报文。
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示至少一条接收队列非空,false 表示当前三路均无待消费报文。
*/
bool CanFd::hasRxFrame()
{
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
found = true;
break;
}
}
RT_CRITICAL_RESTORE(save);
return found;
}
/**
* @brief 轮转读取并消费一帧接收报文。
* @param[in] 无。
* @param[out] id 输出报文标识符;允许为空。
* @param[out] data 输出报文数据缓存;允许为空。
* @param[out] length 输出报文长度;允许为空。
* @param[out] channel 输出报文来源通道号;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前三路接收队列均为空。
* @note 消费顺序采用从 `s_nextConsumeChannel` 开始的轮转扫描,避免长期偏向固定通道。
*/
bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel)
{
RxFrame localFrame{};
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
if(queue.count == 0U) {
continue;
}
localFrame = queue.frames[queue.head];
queue.head = static_cast<uint8_t>((queue.head + 1U) % kRxQueueDepth);
queue.count--;
s_nextConsumeChannel = static_cast<uint8_t>((idx + 1U) % kChannelCount);
found = true;
break;
}
RT_CRITICAL_RESTORE(save);
if(!found) {
return false;
}
if(channel) *channel = localFrame.channel;
if(id) *id = localFrame.id;
if(length) *length = localFrame.length;
if(data) {
for(uint8_t i = 0; i < localFrame.length; ++i) {
data[i] = localFrame.data[i];
}
}
return true;
}
/**
* @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[in] ext_id 扩展帧 ID(29 位)。
* @param[in] data 数据负载指针。
* @param[in] dlc 数据长度(0~64;0 表示无数据负载/远程类空帧)。
* @param[in] periodic 是否为周期性发送;true 时仅允许一个邮箱保持自动重发,
* 不主动 abort 旧帧,且跳过 TEC 保护以匹配诊断行为。
* @retval CanTxResult kOk 表示发送请求已进入硬件邮箱;其它值表示具体失败原因。
* @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。
* 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。
*/
CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
const uint8_t *data, uint8_t dlc, bool periodic,
bool allow_abort)
{
/* DLC=0 合法(仅 ID);DLC>0 时 data 必填 */
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
return CanTxResult::kErrInvalid;
}
if (dlc > 0U && data == nullptr) {
return CanTxResult::kErrInvalid;
}
/* 诊断:捕获任何通过 transmitFrame 发 0x100 DLC=16 的帧(排查幽灵帧来源) */
if (ext_id == 0x00000100U && dlc == 16U) {
CAN_LOGI("[CAN-DIAG] TX 0x100 DLC=16 via transmitFrame ch=%u\r\n",
(unsigned)channelIndex);
}
/* BUS_OFF 不再软件拦截——CAN_ERR_BOERR 是粘性标志,控制器恢复后仍置位,
* 导致总线正常时仍拒绝发送(用户确认其他设备能收到 GD32 帧)。
* CAN 硬件自带恢复机制(128×11隐性位后自动回 Normal);
* 若确实发不出,can_message_transmit 会返回 NOMAILBOX → kErrMailbox。 */
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
/* CAN 硬件自有 error-passive(TEC>127 自动加间隔)
* 和 bus-off(TEC=256 → 自动恢复) 保护。软件不再做任何发送拦截。 */
/* 周期发送只允许一个邮箱保持自动重发。无 ACK 时同一帧会持续产生
* 总线波形;不要每 1ms 再填满其余邮箱,更不能主动 abort 旧帧,否则
* TX 完成中断会把软件中止累计为发送失败,掩盖真实 TEC/ERRN。 */
if (periodic) {
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
if (can_transmit_states(canPeriph, mb) == CAN_TRANSMIT_PENDING) {
return CanTxResult::kErrMailbox;
}
}
}
can_trasnmit_message_struct tx_msg{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
tx_msg.tx_efid = ext_id;
tx_msg.tx_ff = CAN_FF_EXTENDED;
tx_msg.tx_ft = CAN_FT_DATA;
tx_msg.tx_dlen = dlc;
#ifdef GD32E508
tx_msg.fd_flag = (dlc > 8U) ? ENABLE : DISABLE;
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
/* 电机 BOOT 收不了 BRS 数据段,OTA 期间 relay 对该通道压制(setChannelBrsSuppress),
* DLC>8 帧回仲裁段速率(FD-no-BRS,OTA 长期验证过的组合)。 */
tx_msg.fd_brs = (dlc > 8U && !s_brsSuppress[channelIndex]) ? ENABLE : DISABLE;
#else
tx_msg.fd_brs = DISABLE;
#endif
tx_msg.fd_esi = CAN_ESI_DOMINANT;
#endif
if (data != nullptr && dlc > 0U) {
for (uint8_t i = 0U; i < dlc; ++i) {
tx_msg.tx_data[i] = data[i];
}
}
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
/* 非周期业务发送在邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。
*
* ★ allow_abort=false(突发/软件队列路径)时不做 abort:邮箱全满直接返回
* kErrMailbox,由 enqueueTx 把帧送入软件队列、TX 完成中断回灌。
* 否则 abort+重投会把同通道已装载的旧帧顶掉(7 帧同通道突发只发 3 帧)。 */
if (mailbox == CAN_NOMAILBOX && allow_abort) {
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) {
continue;
}
if (can_transmission_stop(canPeriph, mb) == SUCCESS) {
s_diag.tx_abort_count++;
break;
}
}
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
if (mailbox != CAN_NOMAILBOX) {
s_diag.last_tx_id = ext_id;
s_diag.last_tx_channel = channelIndex;
s_diag.last_payload_length = dlc;
return CanTxResult::kOk;
}
s_diag.error_count++;
s_channelStats[channelIndex].error_count++;
static uint32_t s_lastMailboxLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU};
if (s_lastMailboxLogSeq[channelIndex] != s_feedbackSeq) {
const uint32_t errAfter = CAN_ERR(canPeriph);
LOG_ERR("[CAN-TX-REJECT] CH%u MAILBOX ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
static_cast<unsigned>(channelIndex),
static_cast<unsigned long>(errAfter),
static_cast<unsigned long>((errAfter >> 4U) & 0x07U),
canLastErrorName(errAfter),
static_cast<unsigned long>((errAfter >> 16U) & 0xFFU),
static_cast<unsigned long>((errAfter >> 24U) & 0xFFU),
static_cast<unsigned long>(CAN_TSTAT(canPeriph)));
s_lastMailboxLogSeq[channelIndex] = s_feedbackSeq;
}
return CanTxResult::kErrMailbox;
}
/**
* @brief 突发排入软件 TX 队列(详见头声明)。
*
* 场景:电机控制台 0xC7 突发一次发 4 帧同通道命令,
* GD32 只有 3 个硬件邮箱,第 4 帧必丢。此队列把超载帧留下来,
* TX 完成中断里持续回灌,前端 /can/send_burst 不再因 >3 帧掉帧。
*/
CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
return CanTxResult::kErrInvalid;
}
if (dlc > 0U && data == nullptr) {
return CanTxResult::kErrInvalid;
}
TxQueue &q = s_txQueues[channelIndex];
/* 快速路径:队列空 → 直通硬件邮箱。装得下立即返回,不用等待 TX 完成中断。
* ★ allow_abort=false:邮箱满时禁止 abort 旧帧顶替(那会把同通道已装载的
* 旧帧静默丢弃,>3 帧同通道突发只剩最后 3 帧上线),一律落入软件队列,
* 由 TX 完成中断 drainTxQueueIsr 回灌。 */
if (q.count == 0U) {
const CanTxResult r = transmitFrame(channelIndex, ext_id, data, dlc, false, false);
if (r != CanTxResult::kErrMailbox) {
return r; /* 装上了(kOk)或失败原因非邮箱满(kErrInvalid/kErrBusOff) */
}
/* 邮箱满:落入下面的入队路径 */
}
/* 关中段:EXTI15(SPI burst) 与 CANx TX ISR 并发,保护 head/tail/count。
* 用 CMSIS NVIC 接口(SPL 的 nvic_irq_enable 是 3 参版)。
* 关的是该通道自身的 txIrq,避免关全局影响其他通道的实时性。 */
const IRQn_Type irqn = kChannels[channelIndex].txIrq;
NVIC_DisableIRQ(irqn);
if (q.count >= kTxQueueDepth) {
/* 队列也满了:彻底装不下。此时不能再返回 kOk,
* 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */
NVIC_EnableIRQ(irqn);
s_diag.error_count++;
s_channelStats[channelIndex].error_count++;
return CanTxResult::kErrMailbox;
}
TxQueuedFrame &f = q.frames[q.tail];
f.id = ext_id;
f.dlc = dlc;
if (dlc > 0U) {
memcpy(f.data, data, dlc);
}
q.tail = static_cast<uint8_t>((q.tail + 1U) % kTxQueueDepth);
q.count++;
NVIC_EnableIRQ(irqn);
return CanTxResult::kOk;
}
/**
* @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。
* @note 只在 ISR 内调用;关中断保护已在 transmitFrame 返回值判定外层完成,
* 本函数本身不关中(邮箱空位是"单调减少再单调增加",多读无害)。
*/
void CanFd::drainTxQueueIsr(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return;
}
TxQueue &q = s_txQueues[channelIndex];
while (q.count > 0U) {
TxQueuedFrame &f = q.frames[q.head];
/* 复用 transmitFrame 的装载逻辑;allow_abort=false:这里邮箱刚空出来,
* 必能装上,禁止 abort 其它 PENDING 邮箱(会静默丢弃同通道排队帧)。 */
const CanTxResult r = transmitFrame(channelIndex, f.id, f.data, f.dlc, false, false);
if (r == CanTxResult::kOk) {
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
q.count--;
} else {
/* kErrMailbox:理论上不该发生(刚刚空出一个邮箱),保守 break;
* 其它错误(kErrInvalid)已经入队又因参数错必败,直接丢弃防死循环。 */
if (r != CanTxResult::kErrMailbox) {
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
q.count--;
} else {
break;
}
}
}
}
uint32_t CanFd::txQueueBacklog(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return 0U;
}
return s_txQueues[channelIndex].count;
}
void CanFd::abortPendingTx(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return;
}
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
if (can_transmit_states(canPeriph, mb) == CAN_TRANSMIT_PENDING) {
(void)can_transmission_stop(canPeriph, mb);
}
}
}
void CanFd::setChannelAutoRetrans(uint8_t channelIndex, bool enable)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return;
}
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
/* ARD 位仅在 init mode 可写:进 init mode → 改 ARD → 退 init mode。 */
CAN_CTL(canPeriph) |= CAN_CTL_IWMOD;
for (volatile uint32_t t = 0U; t < 200000U; ++t) {
if (CAN_STAT(canPeriph) & CAN_STAT_IWS) break;
}
if (enable) {
CAN_CTL(canPeriph) &= ~CAN_CTL_ARD; /* 自动重发 ON(业务默认) */
} else {
CAN_CTL(canPeriph) |= CAN_CTL_ARD; /* 自动重发 OFF(单发,停等式 OTA 用) */
}
CAN_CTL(canPeriph) &= ~CAN_CTL_IWMOD;
for (volatile uint32_t t = 0U; t < 200000U; ++t) {
if ((CAN_STAT(canPeriph) & CAN_STAT_IWS) == 0U) break;
}
CAN_LOGI("[CAN] CH%u set auto_retrans=%u -> ARD=%u\r\n",
(unsigned)channelIndex, (unsigned)enable,
(CAN_CTL(canPeriph) & CAN_CTL_ARD) ? 1U : 0U);
}
void CanFd::setChannelBrsSuppress(uint8_t channelIndex, bool suppress)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return;
}
/* 仅置运行时标志,打点在 transmitFrame:BRS 是逐帧属性(fd_brs 位),无需动寄存器。
* 写方(relay enterState 钩子)与读方(主循环/CS 中断的 transmitFrame)间 bool 单写单读,原子。 */
s_brsSuppress[channelIndex] = suppress;
CAN_LOGI("[CAN] CH%u set brs_suppress=%u\r\n", (unsigned)channelIndex, (unsigned)suppress);
}
uint32_t CanFd::readChannelErrorReg(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return 0U;
}
/* 单次寄存器读,供事件时刻(如 OTA ACK 超时)抓取总线错误状态;
* 不走 1Hz refreshFeedbackSnapshot,避免采样混叠。 */
return CAN_ERR(kChannels[channelIndex].canPeriph);
}
bool CanFd::consumeTxError(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return false;
}
if (!s_txErrorEvent[channelIndex]) {
return false;
}
s_txErrorEvent[channelIndex] = false;
return true;
}
/**
* @brief 获取 CANFD 诊断统计信息只读指针。
* @param[in] 无。
* @param[out] 无。
* @retval const CanFd::Diag* 返回内部诊断统计结构体只读指针。
*/
const CanFd::Diag *CanFd::diag()
{
return &s_diag;
}
/**
* @brief 透传式发送:按接收帧的原格式(标准/扩展、CAN-FD/经典、BRS/ESI、DLC)原样转发到目标 CAN 通道。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] rx 刚从 FIFO0 读出的接收帧。
* @retval bool true 表示成功入队硬件邮箱;false 表示三个邮箱均忙且无法 abort 重投。
* @note 仅供桥接模式在 RX0 中断中调用;非阻塞,与 transmitFrame 共用 abort+重投策略,时间占用控制在寄存器访问级别。
*/
static bool bridgeTransmitRaw(uint32_t canPeriph, const can_receive_message_struct &rx)
{
can_trasnmit_message_struct tx_msg{};
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
tx_msg.tx_ff = rx.rx_ff;
if (rx.rx_ff == CAN_FF_STANDARD) {
tx_msg.tx_sfid = rx.rx_sfid;
} else {
tx_msg.tx_efid = rx.rx_efid;
}
tx_msg.tx_ft = CAN_FT_DATA;
tx_msg.tx_dlen = rx.rx_dlen;
#ifdef GD32E508
tx_msg.fd_flag = rx.fd_flag;
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
tx_msg.fd_brs = rx.fd_brs;
#else
tx_msg.fd_brs = DISABLE;
#endif
tx_msg.fd_esi = rx.fd_esi;
#endif
const uint8_t copyLen = (rx.rx_dlen <= 64U) ? rx.rx_dlen : 64U;
for (uint8_t i = 0U; i < copyLen; ++i) {
tx_msg.tx_data[i] = rx.rx_data[i];
}
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
if (mailbox == CAN_NOMAILBOX) {
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) {
continue;
}
if (can_transmission_stop(canPeriph, mb) == SUCCESS) {
break;
}
}
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
return mailbox != CAN_NOMAILBOX;
}
/**
* @brief 处理指定通道的 RX0 中断接收。
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[out] 无。
* @retval 无返回值。
* @note 从对应控制器 FIFO0 循环取空所有待处理报文,更新诊断统计,写入该通道软件接收队列,并置位解析任务。
*/
void CanFd::handleRxInterrupt(uint8_t channelIndex)
{
if(channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) return;
const auto &ch = kChannels[channelIndex];
const bool fifoFull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFF0) != RESET);
const bool fifoOverfull = (can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFO0) != RESET);
if(fifoFull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0);
s_diag.rx_fifo_full_count++;
}
if(fifoOverfull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0);
s_diag.rx_fifo_overfull_count++;
}
bool hadTraffic = false;
while(can_interrupt_flag_get(ch.canPeriph, CAN_INT_FLAG_RFL0) != RESET) {
hadTraffic = true;
can_receive_message_struct rx_message{};
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
/* 自己取帧并校验(见 receiveFrameChecked):厂商库不校验也不等 RFD 自清,
* 会把"半条上一帧 + 半条本帧"的拼接体当正常帧交上来。
*
* 1.6.10:**校验失败只丢不重取**。1.6.7 起这里是"原地重取最多 3 次",
* 实测被证伪为撕裂的直接制造者:RFIFOMDATA0 是"重复读同一地址、硬件读
* 指针自动前进"(厂商驱动证实),attempt1 失败时读指针已随取数前进若干
* 字,重取的 attempt2 从错位指针开读 —— MI/MP 回读比对的是"窗口身份",
* 管不住"指针相对窗口起点的错位",于是校验通过、内容却是"上一帧的尾巴
* 拼下一帧的开头"。自测实锤:重取版 1.6.9 在电机活跃时错标率 9.155%
* (56835/620838),而 rx_torn 全程恒 0 —— 拼接恰好都从校验里溜过去。
* 丢帧无害:STATUS/CONFIG 都是 1kHz 周期量,下拍即补;递上去才会变乱码。
* CAN ISR 是 preempt=2,EtherCAT 的 SYNC(0)/ESC(1)/SYNC1(1) 都能抢占它,
* 这正是取数被打断的来源,也解释了为什么只有 EtherCAT 链路上看得到拼接、
* WiFi 链路干净。 */
const bool intact = receiveFrameChecked(ch.canPeriph, &rx_message);
if (!releaseFifo0(ch.canPeriph)) {
s_diag.rx_release_timeout_count++;
}
if (!intact) {
/* 校验判为拼接:整帧作废,不计 rx_count、不入队、不转发。
* 本版起 rx_torn_count 恢复真实语义 —— 它恒 0 说明校验没在挡东西。 */
s_diag.rx_torn_count++;
continue;
}
/* 本帧的 ID 用局部量,不要读 s_diag.last_rx_id ——三路通道共用那个全局,
* 嵌套/交错时会串出"A 通道的 id 配 B 通道的数据"。 */
const uint32_t rxId = (rx_message.rx_ff == CAN_FF_STANDARD)
? rx_message.rx_sfid : rx_message.rx_efid;
/* ── 1.6.11:内容级拼接帧拦截 ──
* MI/MP 回读校验管不住 FIFO 缓冲指针与帧边界的失同步(1.6.10 的
* settle 延迟实测无效,指纹实验证明 node3 的 STATUS 帧头 24B 可以是
* node2 的完整 CONFIG —— 错位恰为 CONFIG 帧长 6 字,与混合帧长交错
* 相关,是 CAN IP 的指针病理,软件无法从取数时序上根除)。
* 改为在入队前做结构自洽校验:拼接帧的 (id, data) 必矛盾,而真帧必自洽。
* STATUS(0x000N0100, dlc48):CONFIG 头灌入必然把 MCU/Motor 温度推出
* [-40,125]℃ 物理量程(CONFIG B0-1 = 03+限压低字节 → 123~250℃ 或
* 负值),母线电压同步失序;
* CONFIG(0x000N0101, dlc24):DBC v0.74 规定 B15-23 未使用恒 0,
* STATUS 体灌入几乎必非 0;模式字节限 0..7(电机模式表最大 7)。
* 命中即计数(1.6.12 起不再丢帧——管道必须诚实,计数仅做"源头是否
* 还在产拼接帧"的测量):源头修复生效后本计数应恒 0,非 0 即失效告警。
* 计数经段5@494 上报。
* 残余风险:限压字节恰使温度落回量程内的理论组合(24V 台架实测限压
* ≥15V → 对应"温度"≥153℃,仍在量程外),可接受。 */
{
const uint16_t idLo = (uint16_t)(rxId & 0xFFFFU);
bool reject = false;
/* 上一帧已接受的温度/电压(按节点):拼接帧的 CONFIG 字节可能恰好落进
* 静态量程(实测 vel=150 指纹时 0301→2.59℃、5a00→0.9℃、cace→52.9V
* 全部"合法"漏过),但绝不可能在 1ms 帧间隔里物理地跳过去 —— 斜率判据
* 是静态量程补不上的洞。阈值留 3 个数量级余量:温度 20℃/帧(实际
* ≤0.005℃),电压 5V/帧(实际 ≤0.05V)。 */
static int16_t s_lastMcu[16];
static int16_t s_lastMot[16];
static uint16_t s_lastPwr[16];
static uint8_t s_lastOk[16];
if (idLo == 0x0100U && rx_message.rx_dlen == 48U) {
const int16_t mcu = (int16_t)((uint16_t)rx_message.rx_data[0] |
((uint16_t)rx_message.rx_data[1] << 8));
const int16_t mot = (int16_t)((uint16_t)rx_message.rx_data[2] |
((uint16_t)rx_message.rx_data[3] << 8));
const uint16_t pwr = (uint16_t)rx_message.rx_data[11] |
((uint16_t)rx_message.rx_data[12] << 8);
if (mcu < -4000 || mcu > 12500 || mot < -4000 || mot > 12500 ||
pwr < 8000 || pwr > 60000) {
reject = true;
}
const uint8_t nd = (uint8_t)((rxId >> 16) & 0x0FU);
if (!reject && nd >= 1U && nd < 16U && (s_lastOk[nd] & 1U)) {
int16_t dmcu = (int16_t)(mcu - s_lastMcu[nd]);
int16_t dmot = (int16_t)(mot - s_lastMot[nd]);
if (dmcu < 0) { dmcu = (int16_t)-dmcu; }
if (dmot < 0) { dmot = (int16_t)-dmot; }
/* 只比温度斜率:拼接帧的 CONFIG 头必然把 mcu/mot 推出 20℃/帧
* (实测最小跳变 35℃,而真实温度 ≤0.005℃/帧,4 个数量级余量)。
* 电压不比斜率 —— 正弦换向/堵转时母线真实跌落可超 5V/帧,
* 误杀会让显示冻结;拼接的电压特征(≈48V)已由量程+温度斜率兜住。 */
if (dmcu > 2000 || dmot > 2000) {
reject = true;
}
}
if (!reject && nd >= 1U && nd < 16U) {
s_lastMcu[nd] = mcu;
s_lastMot[nd] = mot;
s_lastPwr[nd] = pwr;
s_lastOk[nd] = 1U;
}
} else if (idLo == 0x0101U && rx_message.rx_dlen == 24U) {
if (rx_message.rx_data[0] > 7U) {
reject = true;
}
for (uint8_t i = 15U; !reject && i < 24U; ++i) {
if (rx_message.rx_data[i] != 0U) {
reject = true;
}
}
}
if (reject) {
/* 1.6.12:只计不丢 —— 数据管道必须诚实(用户裁定:过滤是遮羞布,
* 修的是采集源头)。网页/ESP32/主站看到的就是源头产出的原始字节,
* 本计数退化为纯测量仪表:源头修好 = 恒 0;还在涨 = 源头问题仍在,
* 且网页如实显示着,不存在"看着干净其实坏了"。 */
s_diag.rx_content_reject_count++;
}
}
s_diag.rx_count++;
s_diag.last_rx_channel = channelIndex;
s_diag.last_payload_length = rx_message.rx_dlen;
s_diag.last_rx_fd_flag = rx_message.fd_flag;
s_diag.last_rx_brs = rx_message.fd_brs;
s_diag.last_rx_esi = rx_message.fd_esi;
s_diag.last_rx_id = rxId;
/* CAN0 桥接模式:将刚收到的原帧透传到 CAN1 与 CAN2,保留 11/29 位、FD、BRS、ESI。 */
if (channelIndex == 0U && s_mode == Mode::kModeBridge0To12) {
for (uint8_t bridgeCh = 1U; bridgeCh <= 2U; ++bridgeCh) {
const uint32_t bridgePeriph = kChannels[bridgeCh].canPeriph;
const bool ok = bridgeTransmitRaw(bridgePeriph, rx_message);
if (ok) {
s_diag.last_tx_id = rxId;
s_diag.last_tx_channel = bridgeCh;
s_diag.last_payload_length = rx_message.rx_dlen;
} else {
s_diag.error_count++;
s_channelStats[bridgeCh].error_count++;
}
}
}
/* 维护每通道独立计数与最近一帧快照,用于 1Hz 反馈给 ESP32。 */
auto &stats = s_channelStats[channelIndex];
stats.rx_count++;
stats.last_rx_id = rxId;
stats.last_rx_dlc = rx_message.rx_dlen;
stats.last_rx_fd_flag = rx_message.fd_flag;
stats.last_rx_brs = rx_message.fd_brs;
stats.last_rx_esi = rx_message.fd_esi;
const uint8_t copyLen = (rx_message.rx_dlen < CANFD_FEEDBACK_LAST_DATA_MAX)
? rx_message.rx_dlen : static_cast<uint8_t>(CANFD_FEEDBACK_LAST_DATA_MAX);
for (uint8_t i = 0U; i < copyLen; ++i) {
stats.last_rx_data[i] = rx_message.rx_data[i];
}
for (uint8_t i = copyLen; i < CANFD_FEEDBACK_LAST_DATA_MAX; ++i) {
stats.last_rx_data[i] = 0U;
}
/* 维护每通道不重复 ID 快照表,用于 1Hz 分页拉取给 ESP32。 */
{
auto &table = s_idSnapshot[channelIndex];
/* 查找已有条目 */
uint8_t found = CANFD_ID_SNAPSHOT_MAX;
for (uint8_t si = 0U; si < table.count; ++si) {
if (table.entries[si].occupied && table.entries[si].id == rxId) {
found = si;
break;
}
}
if (found >= CANFD_ID_SNAPSHOT_MAX) {
/* 未找到:表未满则新建,满了淘汰最旧(index 0) */
if (table.count < CANFD_ID_SNAPSHOT_MAX) {
found = table.count;
table.count++;
} else {
found = 0U;
/* 淘汰后整体左移一格让新条目在末尾 */
for (uint8_t si = 0U; si < CANFD_ID_SNAPSHOT_MAX - 1U; ++si) {
table.entries[si] = table.entries[si + 1U];
}
found = CANFD_ID_SNAPSHOT_MAX - 1U;
}
table.entries[found] = {};
table.entries[found].id = rxId;
table.entries[found].occupied = true;
}
auto &entry = table.entries[found];
entry.rx_count++;
entry.dlc = rx_message.rx_dlen;
entry.fd_flag = rx_message.fd_flag;
entry.brs = rx_message.fd_brs;
entry.esi = rx_message.fd_esi;
const uint8_t snapCopy = (rx_message.rx_dlen < CANFD_ID_SNAPSHOT_DATA_MAX)
? rx_message.rx_dlen : static_cast<uint8_t>(CANFD_ID_SNAPSHOT_DATA_MAX);
for (uint8_t si = 0U; si < snapCopy; ++si) {
entry.data[si] = rx_message.rx_data[si];
}
for (uint8_t si = snapCopy; si < CANFD_ID_SNAPSHOT_DATA_MAX; ++si) {
entry.data[si] = 0U;
}
}
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.rx_drop_count++;
stats.rx_drop_count++;
continue;
}
auto &frame = queue.frames[queue.tail];
frame.channel = channelIndex;
frame.id = rxId;
frame.length = rx_message.rx_dlen;
for(uint8_t i = 0; i < rx_message.rx_dlen; ++i) {
frame.data[i] = rx_message.rx_data[i];
}
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kRxQueueDepth);
queue.count++;
}
if(fifoFull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0);
}
if(fifoOverfull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0);
}
if(hadTraffic) {
TaskManager::set(kCanRxParseTaskMask);
}
}
/**
* @brief 处理指定通道的发送完成中断。
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @note 只有 MTFNERR 置位才表示完整发送且未出错;邮箱受理、仲裁丢失、
* 主动中止和发送错误均不能计入网页“发送数”。
*/
void CanFd::handleTxInterrupt(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return;
}
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
const uint32_t tstat = CAN_TSTAT(canPeriph);
constexpr uint32_t finishedBits[] = {
CAN_TSTAT_MTF0, CAN_TSTAT_MTF1, CAN_TSTAT_MTF2
};
constexpr uint32_t successBits[] = {
CAN_TSTAT_MTFNERR0, CAN_TSTAT_MTFNERR1, CAN_TSTAT_MTFNERR2
};
constexpr uint32_t errorBits[] = {
CAN_TSTAT_MTE0, CAN_TSTAT_MTE1, CAN_TSTAT_MTE2
};
for (size_t mailbox = 0U; mailbox < 3U; ++mailbox) {
if ((tstat & finishedBits[mailbox]) == 0U) {
continue;
}
if ((tstat & successBits[mailbox]) != 0U) {
s_diag.tx_count++;
s_channelStats[channelIndex].tx_count++;
} else if ((tstat & errorBits[mailbox]) != 0U) {
s_diag.error_count++;
s_channelStats[channelIndex].error_count++;
/* ARD=1 单发模式下 MTE 置位 = 该帧已确认丢失;置事件标志供停等式
* OTA 快速重发(免等 ACK 超时)。单写(ISR)单读清(主循环),无锁。 */
s_txErrorEvent[channelIndex] = true;
}
}
CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2);
/* 队列里有积压帧时,趁刚空出的邮箱位置回灌。必须在 TSTAT 清标志之后调,
* 否则 transmitFrame 看到的还是旧 PENDING 状态。 */
drainTxQueueIsr(channelIndex);
}
/**
* @brief 刷新三路 CAN 通道的 1Hz 反馈快照。
* @note 在临界区内拷贝实时计数与最近一帧信息到 s_feedback;保证 SPI 命令读到一致快照。
* 典型调用频率 1Hz,由任务调度器在心跳节拍触发。
*/
void CanFd::refreshFeedbackSnapshot()
{
/* 先在临界区内采集硬件寄存器,确保三通道快照一致 */
uint32_t hw_tec[3] = {};
uint32_t hw_rec[3] = {};
uint32_t hw_stat[3] = {};
uint32_t hw_err[3] = {};
uint32_t hw_rfl0[3] = {};
{
uint32_t save = RT_CRITICAL_SAVE();
for (size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
const uint32_t err_val = CAN_ERR(ch.canPeriph);
const uint32_t stat_val = CAN_STAT(ch.canPeriph);
hw_tec[i] = (err_val >> 16) & 0xFFU;
hw_rec[i] = (err_val >> 24) & 0xFFU;
hw_stat[i] = stat_val;
hw_err[i] = err_val;
hw_rfl0[i] = can_receive_message_length_get(ch.canPeriph, CAN_FIFO0);
}
RT_CRITICAL_RESTORE(save);
}
s_feedbackSeq++;
/* 每 5 秒采样硬件错误态;正常心跳不打印(统计走 SPI 0xC1)。
* 仅当 TEC/REC 或告警位非零时输出 [CAN-HW-ERR];全量 seq 心跳见 CONFIG_UART_DIAG_LEVEL。 */
if (s_feedbackSeq % 5U == 0U) {
#if (CONFIG_UART_DIAG_LEVEL != 0)
CAN_LOGI("[CAN-FB] refresh seq=%lu rx0=%lu rx1=%lu rx2=%lu\r\n",
(unsigned long)s_feedbackSeq,
(unsigned long)s_channelStats[0].rx_count,
(unsigned long)s_channelStats[1].rx_count,
(unsigned long)s_channelStats[2].rx_count);
#endif
for (size_t i = 0; i < kChannelCount; ++i) {
if (hw_tec[i] == 0U && hw_rec[i] == 0U
&& (hw_err[i] & (CAN_ERR_WERR | CAN_ERR_PERR | CAN_ERR_BOERR)) == 0U) {
continue;
}
LOG_ERR("[CAN-HW-ERR] CH%u ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
static_cast<unsigned>(i),
static_cast<unsigned long>(hw_err[i]),
static_cast<unsigned long>((hw_err[i] >> 4U) & 0x07U),
canLastErrorName(hw_err[i]),
static_cast<unsigned long>(hw_tec[i]),
static_cast<unsigned long>(hw_rec[i]),
static_cast<unsigned long>(CAN_TSTAT(kChannels[i].canPeriph)));
}
}
for (size_t i = 0; i < kChannelCount; ++i) {
s_feedback[i].snapshot_seq = s_feedbackSeq;
s_feedback[i].stats = s_channelStats[i];
/* 写入硬件层错误状态供 SPI 反馈使用 */
s_feedback[i].stats.tec = static_cast<uint8_t>(hw_tec[i]);
s_feedback[i].stats.rec = static_cast<uint8_t>(hw_rec[i]);
/* 总线状态位图:bit0=init, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning
* init/sleep 来自 CAN_STAT,error_passive/bus_off/error_warning 来自 CAN_ERR */
uint8_t busState = 0U;
if (hw_stat[i] & CAN_STAT_IWS) busState |= (1U << 0); /* 初始化模式 */
if (hw_stat[i] & CAN_STAT_SLPWS) busState |= (1U << 1); /* 睡眠模式 */
if (hw_err[i] & CAN_ERR_PERR) busState |= (1U << 2); /* 错误被动 */
if (hw_err[i] & CAN_ERR_BOERR) busState |= (1U << 3); /* 总线关闭 */
if (hw_err[i] & CAN_ERR_WERR) busState |= (1U << 4); /* 错误警告 */
s_feedback[i].stats.bus_state = busState;
s_idSnapshot[i].seq = s_feedbackSeq;
}
/* BUS_OFF 状态变化检测:仅在状态切换时打印,避免刷屏 */
static uint8_t s_prevBusState[3] = {0xFF, 0xFF, 0xFF}; /* 0xFF=首次 */
for (size_t i = 0; i < kChannelCount; ++i) {
uint8_t cur = s_feedback[i].stats.bus_state;
if (cur != s_prevBusState[i]) {
const char *stateStr = "NORMAL";
if (cur & (1U << 0)) stateStr = "INIT_MODE";
else if (cur & (1U << 3)) stateStr = "BUS_OFF";
else if (cur & (1U << 2)) stateStr = "ERR_PASSIVE";
else if (cur & (1U << 4)) stateStr = "ERR_WARN";
LOG_ERR("[CAN-BUS] CH%u: %s -> %s ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu seq=%lu\r\n",
static_cast<unsigned>(i),
(s_prevBusState[i] == 0xFF) ? "?" :
(s_prevBusState[i] & (1U << 3)) ? "BUS_OFF" :
(s_prevBusState[i] & (1U << 2)) ? "ERR_PASSIVE" :
(s_prevBusState[i] & (1U << 4)) ? "ERR_WARN" : "NORMAL",
stateStr,
static_cast<unsigned long>(hw_err[i]),
static_cast<unsigned long>((hw_err[i] >> 4U) & 0x07U),
canLastErrorName(hw_err[i]),
static_cast<unsigned long>(hw_tec[i]),
static_cast<unsigned long>(hw_rec[i]),
static_cast<unsigned long>(s_feedbackSeq));
s_prevBusState[i] = cur;
}
}
}
/**
* @brief 拷贝指定通道的最新反馈快照。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[out] out 目标快照缓冲;为空或通道非法时返回 false。
* @retval bool true 表示成功拷贝。
*/
/**
* @brief 拷贝指定通道的不重复 ID 快照表。
*/
bool CanFd::copyIdSnapshotTable(uint8_t channelIndex, IdSnapshotTable *out)
{
if (out == nullptr || channelIndex >= kChannelCount) {
return false;
}
uint32_t save = RT_CRITICAL_SAVE();
*out = s_idSnapshot[channelIndex];
RT_CRITICAL_RESTORE(save);
return true;
}
bool CanFd::copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out)
{
if (out == nullptr || channelIndex >= kChannelCount) {
return false;
}
uint32_t save = RT_CRITICAL_SAVE();
*out = s_feedback[channelIndex];
RT_CRITICAL_RESTORE(save);
return true;
}
/* ----- 中断入口 C 包装 ------------------------------------------------- */
/**
* @brief CAN0 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(0U)`。
*/
extern "C" void CAN0_RX0_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(0U); CpuLoad::isrExit(); }
/** @brief CAN0 TX 完成中断向量入口。 */
extern "C" void CAN0_TX_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(0U); CpuLoad::isrExit(); }
/**
* @brief CAN1 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(1U)`。
*/
extern "C" void CAN1_RX0_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(1U); CpuLoad::isrExit(); }
/** @brief CAN1 TX 完成中断向量入口。 */
extern "C" void CAN1_TX_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(1U); CpuLoad::isrExit(); }
/**
* @brief CAN2 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(2U)`。
*/
extern "C" void CAN2_RX0_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleRxInterrupt(2U); CpuLoad::isrExit(); }
/** @brief CAN2 TX 完成中断向量入口。 */
extern "C" void CAN2_TX_IRQHandler(void)
{ CpuLoad::isrEnter(); CanFd::handleTxInterrupt(2U); CpuLoad::isrExit(); }