添加了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命令处理文件的分组
2027 lines
87 KiB
C++
2027 lines
87 KiB
C++
/**
|
||
* @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(); }
|