Files
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

2027 lines
87 KiB
C++
Raw Permalink 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) { }
}
/** @brief DWT CYCCNT 忙等微秒(ISR 安全)。
* @note setTxSilent 退 Silent 时要等收发器 tMODE=20µs;本函数可能在 SPI CS
* EXTI ISR 内被 0xC8 handler 调用,而 SysTickTimer::delayMs 依赖 SysTick
* 中断计数,在该上下文会被冻死(同 esc_lan9252.cpp 的 pmBusyWaitUs)。
* **不得复位 CYCCNT**:cpu_load / ethercat_diff_stats / SSC 软件定时器都拿它
* 当时间基,清零会让它们的窗口计算瞬时报错。只在未使能时补使能位。 */
static inline void canBusyWaitUs(uint32_t us)
{
if ((DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) == 0U) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
const uint32_t start = DWT->CYCCNT;
const uint32_t cycles = us * (SystemCoreClock / 1000000U);
/* 有界自旋:DWT 因故没在跑时不能变成无限等(最多多转 ~1ms 的圈数) */
const uint32_t spin_cap = cycles + (SystemCoreClock / 1000U);
for (uint32_t i = 0U; i < spin_cap; ++i) {
if ((DWT->CYCCNT - start) >= cycles) break;
}
}
/* 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) {
/* 节点指纹实验表明拼接是 FIFO 缓冲指针与帧边界失同步
* (STATUS 帧头 24B = 完整 CONFIG,错位恰为 CONFIG 帧长 6 字),
* 不是延迟能挡的;结构矛盾的帧由 handleRxInterrupt 的内容校验计数
* (只计不丢 —— 管道必须诚实;源头修复见 CONFIG_IRQ_PREEMPT_CAN_RX)。 */
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)
* 挡在外面。取数是十几次有先后依赖的寄存器读,被抢占就会读出跨帧拼接体。
* 本 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;
}
}
}
/**
* @brief CAN TX 并发保护 RAII 守卫。
*
* 屏蔽 EXTI10_15_IRQn(SPI CS Assert/Release, Preempt 0)与该通道的 txIrq(Preempt 2),
* 防止 EtherCAT PDI_Isr(Preempt 1)与 SPI CS EXTI ISR(Preempt 0)在操作 s_txQueues
* 或硬件发送邮箱寄存器时产生并发撕裂与重入竞争。
* 具备重入安全性:若某中断当前未使能(例如已被外层守卫屏蔽),退出时不会误使能。
*/
class CanTxConcurrencyGuard {
public:
explicit CanTxConcurrencyGuard(uint8_t channelIndex)
: m_txIrq(kChannels[channelIndex].txIrq)
{
m_exti15_enabled = (NVIC_GetEnableIRQ(EXTI10_15_IRQn) != 0U);
m_tx_enabled = (NVIC_GetEnableIRQ(m_txIrq) != 0U);
if (m_exti15_enabled) {
NVIC_DisableIRQ(EXTI10_15_IRQn);
}
if (m_tx_enabled) {
NVIC_DisableIRQ(m_txIrq);
}
__DSB();
__ISB();
}
~CanTxConcurrencyGuard() {
if (m_tx_enabled) {
NVIC_EnableIRQ(m_txIrq);
}
if (m_exti15_enabled) {
NVIC_EnableIRQ(EXTI10_15_IRQn);
}
__DSB();
__ISB();
}
private:
IRQn_Type m_txIrq;
bool m_exti15_enabled;
bool m_tx_enabled;
};
} // 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]{};
volatile uint8_t CanFd::s_txSilent[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 */
/* rec_fifo_overwrite = DISABLE:FIFO 满时丢新帧而非覆盖最旧邮箱。
* 覆盖最旧会伤及 CPU 正在读取的输出窗口。buildInput 在 PDI_Isr(preempt1)
* 占 ~357µs 会压住 CAN ISR(preempt2),发指令时电机 CONFIG 翻倍到 2kHz/节点、
* 总线 ~8kHz,阻塞窗口内到达 ~3 帧恰好打满 FIFO 深度 → 覆盖进行到一半被读走
* = 跨节点/跨类型拼接帧。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); /* S=0 → Normal(静默不跨重启,上电一律放开发送) */
s_txSilent[i] = 0U;
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));
/* 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_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 不检查 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 表示当前三路均无待消费报文。
* @note 无锁检查 head != tail,免关中断。
*/
bool CanFd::hasRxFrame()
{
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].head != s_rxQueues[i].tail) {
return true;
}
}
return false;
}
/**
* @brief 轮转读取并消费一帧接收报文(Lock-Free SPSC 消费者端)。
* @param[in] 无。
* @param[out] id 输出报文标识符;允许为空。
* @param[out] data 输出报文数据缓存;允许为空。
* @param[out] length 输出报文长度;允许为空。
* @param[out] channel 输出报文来源通道号;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前三路接收队列均为空。
* @note 消费顺序采用从 `s_nextConsumeChannel` 开始的轮转扫描,避免长期偏向固定通道。
* 基于 SPSC 无锁环形缓冲区,消费端读取 payload 并执行 __DMB() 后推进 head,
* 无需关中断或依赖 BASEPRI,彻底消除与 Preempt 0 RX ISR 的并发撕裂。
*/
bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel)
{
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];
const uint8_t head = queue.head;
const uint8_t tail = queue.tail;
if(head == tail) {
continue;
}
const auto &src = queue.frames[head];
if(channel) *channel = src.channel;
if(id) *id = src.id;
if(length) *length = src.length;
if(data && src.length > 0U) {
for(uint8_t i = 0; i < src.length; ++i) {
data[i] = src.data[i];
}
}
__DMB();
queue.head = static_cast<uint8_t>((head + 1U) & kRxQueueMask);
s_nextConsumeChannel = static_cast<uint8_t>((idx + 1U) % kChannelCount);
return true;
}
return false;
}
/**
* @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;
}
/* 收发器 Silent(只听不发):本端主动配置的发送禁能,不是总线状态。
* 与下面「BUS_OFF/TEC 不做软件拦截」的约定不冲突——那两条是粘性总线侧标志会误判,
* 这条是 s_txSilent 单一真源,且驱动器已关断:硬发只会让控制器白涨 TEC。
* 拒绝路径不碰任何计数/诊断,静默期间的拒发不是错误。 */
if (s_txSilent[channelIndex] != 0U) {
return CanTxResult::kErrSilent;
}
CanTxConcurrencyGuard guard(channelIndex);
/* 诊断:捕获任何通过 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
/* 只要通道硬件已启用 CAN-FD(3路均由 initFdMode 开启),统一发 FD 帧;
* 避免 DLC<=8 时回退经典 CAN 导致对端电机 DMA / 报文解析异常。 */
tx_msg.fd_flag = ENABLE;
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
/* 电机 BOOT 收不了 BRS 数据段,OTA 期间 relay 对该通道压制(setChannelBrsSuppress),
* 压制中回退仲裁段速率(FD-no-BRS,OTA 长期验证过的组合)。日常发送统一启用 BRS 高速段。 */
tx_msg.fd_brs = (!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;
}
/* 静默通道在入队口就拒掉:否则帧会进软件队列,等 TX 完成中断 drainTxQueueIsr
* 调 transmitFrame 时才被拒,那一轮会把 head/count 推进并静默丢帧,
* 表面上"已受理"实则必丢。 */
if (s_txSilent[channelIndex] != 0U) {
return CanTxResult::kErrSilent;
}
TxQueue &q = s_txQueues[channelIndex];
/* 关中断并发保护:屏蔽 EXTI10_15_IRQn(SPI burst, Preempt 0)与 txIrq(Preempt 2),
* 解决 PDI_Isr(Preempt 1)与 SPI CS release(Preempt 0)并发访问 s_txQueues 的竞态。 */
CanTxConcurrencyGuard guard(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) */
}
/* 邮箱满:落入下面的入队路径 */
}
if (q.count >= kTxQueueDepth) {
/* 队列也满了:彻底装不下。此时不能再返回 kOk,
* 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */
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++;
return CanTxResult::kOk;
}
/**
* @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。
* @note 由 CAN TX 完成 ISR 调用;使用 CanTxConcurrencyGuard 屏蔽 EXTI10_15 与 txIrq,
* 防止出队期间被高优先级的 SPI CS release 抢占导致 head/count 撕裂。
*/
void CanFd::drainTxQueueIsr(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return;
}
TxQueue &q = s_txQueues[channelIndex];
CanTxConcurrencyGuard guard(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;
}
uint32_t CanFd::rxQueueCount(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return 0U;
}
const uint8_t head = s_rxQueues[channelIndex].head;
const uint8_t tail = s_rxQueues[channelIndex].tail;
return static_cast<uint32_t>((tail - head) & kRxQueueMask);
}
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);
}
bool CanFd::setTxSilent(uint8_t channelIndex, bool silent)
{
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
return false;
}
const auto &ch = kChannels[channelIndex];
if (silent) {
/* 先立旗再动脚:并发路径(主循环/其它 ISR)调 transmitFrame 立刻被拒,
* 不会在关驱动器的当口又塞一帧进邮箱。 */
s_txSilent[channelIndex] = 1U;
/* 驱动器即将关断:残留 PENDING 邮箱发不出也等不到 ACK,只会白涨 TEC,
* 进静默前先把它们停掉。 */
abortPendingTx(channelIndex);
gpio_bit_set(ch.slpPort, ch.slpPin); /* S=1 → TCAN1057 Silent(驱动关断,接收照常) */
} else {
gpio_bit_reset(ch.slpPort, ch.slpPin); /* S=0 → Normal */
/* 收发器模式建立时间 tMODE=20µs(手册 6.9):等够再放开发送门,
* 否则紧随其后的第一帧会被驱动器忽略。 */
canBusyWaitUs(20U);
s_txSilent[channelIndex] = 0U;
}
const uint8_t pinHigh = (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) ? 1U : 0U;
CAN_LOGI("[CAN] CH%u tx_silent=%u (S pin=%u)\r\n", (unsigned)channelIndex,
(unsigned)silent, (unsigned)pinHigh);
/* 回读确认脚电平与意图一致;不一致就把标志回滚成原状态再报错,
* 否则 isTxSilent()(发送门控 + 0xC1 bit5)会与物理脚电平各说一套。 */
if (pinHigh != (silent ? 1U : 0U)) {
s_txSilent[channelIndex] = silent ? 0U : 1U;
return false;
}
return true;
}
bool CanFd::isTxSilent(uint8_t channelIndex)
{
if (channelIndex >= kChannelCount) {
return false;
}
return s_txSilent[channelIndex] != 0U;
}
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 自清,
* 会把"半条上一帧 + 半条本帧"的拼接体当正常帧交上来。
*
* **校验失败只丢不重取**。RFIFOMDATA0 是"重复读同一地址、硬件读
* 指针自动前进",取数失败时读指针已随取数前进若干字,
* 重取会从错位指针开读 —— MI/MP 回读比对的是"窗口身份",
* 管不住"指针相对窗口起点的错位",于是校验通过、内容却是"上一帧的尾巴
* 拼下一帧的开头"。
* 丢帧无害:STATUS/CONFIG 都是 1kHz 周期量,下拍即补;递上去才会变乱码。 */
const bool intact = receiveFrameChecked(ch.canPeriph, &rx_message);
if (!releaseFifo0(ch.canPeriph)) {
s_diag.rx_release_timeout_count++;
}
if (!intact) {
/* 校验判为拼接:整帧作废,不计 rx_count、不入队、不转发。 */
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;
/* ── 内容级拼接帧拦截 ──
* MI/MP 回读校验管不住 FIFO 缓冲指针与帧边界的失同步
* (指针病理,软件无法从取数时序上根除)。
* 在入队前做结构自洽校验:拼接帧的 (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)。
* 命中即计数(不丢帧——管道必须诚实,计数仅做"源头是否
* 还在产拼接帧"的测量):源头修复生效后本计数应恒 0,非 0 即失效告警。
* 计数经段5@494 上报。
* 残余风险:限压字节恰使温度落回量程内的理论组合
* (24V 台架实测限压 ≥15V → 对应"温度"≥153℃,仍在量程外),可接受。 */
{
const uint16_t idLo = (uint16_t)(rxId & 0xFFFFU);
bool reject = false;
/* 上一帧已接受的温度/电压(按节点):拼接帧的 CONFIG 字节可能恰好落进
* 静态量程,但绝不可能在 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℃/帧
* (实际温度 ≤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) {
/* 只计不丢 —— 数据管道必须诚实。
* 网页/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];
const uint8_t head = queue.head;
const uint8_t tail = queue.tail;
const uint8_t next_tail = static_cast<uint8_t>((tail + 1U) & kRxQueueMask);
if(next_tail == head) {
s_diag.rx_drop_count++;
stats.rx_drop_count++;
continue;
}
auto &frame = queue.frames[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];
}
__DMB();
queue.tail = next_tail;
}
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);
}
/* ── 空通道自动识别状态(2026-09-18)──
* 无对端的通道 RX 浮空 → 持续 STUFF、REC 冲 ~130 停在 ERR_PASSIVE,每 5s 刷一行
* [CAN-HW-ERR](config.h 老注释"CH1/CH2 未接设备,关闭避免持续 CAN 错误 + 占 SPI
* 带宽")。改为运行时自动判定:连续 CONFIG_CAN_QUIET_WINDOW 个窗口无有效帧增量
* 且有错误 → 静默上报;出现帧增量立刻恢复。只抑制上报,硬件保持 NORMAL 接收。 */
static uint8_t s_chanQuiet[3] = {0U, 0U, 0U}; /* 1=该通道已自动静默上报 */
static uint32_t s_rxCountPrev[3] = {0U, 0U, 0U}; /* 上一拍末尾的 rx_count */
static uint8_t s_quietMiss[3] = {0U, 0U, 0U}; /* 连续"无帧且有错"的窗口数 */
/**
* @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++;
/* 空通道自动识别:本拍无有效帧增量且有错误 → 记一次 miss,连续达到窗口长度才静默
* (加滞回,避免偶发丢拍/慢流量误判);一旦有帧增量立刻恢复上报。
* 时间基 = s_feedbackSeq(本函数 1Hz,见下面 %5 的 5s 打印节拍)。 */
for (size_t i = 0; i < kChannelCount; ++i) {
const uint32_t rxNow = s_channelStats[i].rx_count;
const uint32_t rxDelta = rxNow - s_rxCountPrev[i];
s_rxCountPrev[i] = rxNow;
const bool hasErr = (hw_tec[i] != 0U || hw_rec[i] != 0U
|| (hw_err[i] & (CAN_ERR_WERR | CAN_ERR_PERR | CAN_ERR_BOERR)) != 0U);
if (rxDelta > 0U) {
s_quietMiss[i] = 0U;
if (s_chanQuiet[i] != 0U) {
s_chanQuiet[i] = 0U;
CAN_LOGI("[CAN] CH%u 恢复上报:检测到有效帧 (rx=%lu)\r\n",
static_cast<unsigned>(i), static_cast<unsigned long>(rxNow));
}
} else if (hasErr) {
if (s_quietMiss[i] < 0xFFU) s_quietMiss[i]++;
if (s_chanQuiet[i] == 0U
&& s_feedbackSeq >= CONFIG_CAN_QUIET_AFTER_SEC
&& s_quietMiss[i] >= CONFIG_CAN_QUIET_WINDOW_SEC) {
s_chanQuiet[i] = 1U;
LOG_ERR("[CAN] CH%u 无有效帧且错误累积 → 自动静默上报(本通道无设备/无对端;"
"硬件保持接收,插上设备有帧即自动恢复;TEC=%lu REC=%lu)\r\n",
static_cast<unsigned>(i),
static_cast<unsigned long>(hw_tec[i]), static_cast<unsigned long>(hw_rec[i]));
}
} else {
s_quietMiss[i] = 0U;
}
}
/* 每 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;
}
if (s_chanQuiet[i] != 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, bit5=本端收发器 Silent(只听不发)
* bit0~1 来自 CAN_STAT,bit2~4 来自 CAN_ERR;bit5 来自 s_txSilent(唯一真源,
* 与发送门控同一个变量),别拿 bit1 当收发器状态——两者不是一回事。 */
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); /* 错误警告 */
if (s_txSilent[i] != 0U) busState |= (1U << 5); /* 收发器 Silent */
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]) {
if (s_chanQuiet[i] != 0U) { /* 静默通道的状态变化只记账不打印 */
s_prevBusState[i] = cur;
continue;
}
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(); }