/** * @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 namespace { /* 纯软件延时循环(不依赖 SysTick,可在任何中断状态下使用) */ static inline void spinDelay(uint32_t loops) { for (volatile uint32_t d = 0; d < loops; ++d) { } } 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(can_transmit_states(canPeriph, result.mailbox)); if (result.txState == CAN_TRANSMIT_PENDING) { (void)can_transmission_stop(canPeriph, result.mailbox); } } result.stat = CAN_STAT(canPeriph); result.err = CAN_ERR(canPeriph); result.tec = (result.err >> 16) & 0xFFU; result.rec = (result.err >> 24) & 0xFFU; result.busOff = (result.err & CAN_ERR_BOERR) != 0U; return result; } #endif /* CONFIG_CAN_STARTUP_BUS_TEST_ENABLE */ /** * @brief 对 CAN 外设执行完整硬件级复位(SWRST + RCU 电源周期)。 * @note 解决 GD32E508 CAN1 反复初始化后卡死在 init mode 的问题。 * 步骤:软件复位 → 长延时 → 关时钟 → 长延时 → 开时钟 → 长延时 → 残留状态清理。 */ static void hardResetCan(uint32_t canPeriph, rcu_periph_enum canRcu) { /* 1. 触发 SWRST 软件复位 */ CAN_CTL(canPeriph) |= CAN_CTL_SWRST; spinDelay(100000U); /* 2. 确认 SWRST 已被硬件自动清零;若未清零则强制写 0 并等待 */ if (CAN_CTL(canPeriph) & CAN_CTL_SWRST) { CAN_CTL(canPeriph) &= ~CAN_CTL_SWRST; spinDelay(50000U); } /* 3. 关 RCU 时钟,等待外设域完全放电 */ rcu_periph_clock_disable(canRcu); spinDelay(200000U); /* ~1.1ms @180MHz */ /* 4. 重新开时钟,等待外设域上电稳定 */ rcu_periph_clock_enable(canRcu); spinDelay(200000U); /* ~1.1ms @180MHz */ /* 5. can_deinit() 走标准固件库复位流程 */ can_deinit(canPeriph); /* 6. 若复位后 IWMOD 仍然置位(外设残留状态),强制清 init mode */ if (CAN_CTL(canPeriph) & CAN_CTL_IWMOD) { CAN_CTL(canPeriph) &= ~CAN_CTL_IWMOD; for (volatile uint32_t d = 0; d < 100000U; ++d) { if ((CAN_STAT(canPeriph) & CAN_STAT_IWS) == 0U) break; } } } } // namespace CanFd::Diag CanFd::s_diag{}; CanFd::RxQueue CanFd::s_rxQueues[3]{}; CanFd::TxQueue CanFd::s_txQueues[3]{}; uint8_t CanFd::s_nextConsumeChannel = 0U; volatile CanFd::Mode CanFd::s_mode = CanFd::Mode::kModeAllPass; CanFd::ChannelStats CanFd::s_channelStats[3]{}; CanFd::ChannelFeedback CanFd::s_feedback[3]{}; uint32_t CanFd::s_feedbackSeq = 0U; CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{}; bool CanFd::s_channelHwOk[3]{}; /* 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(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((CONFIG_CAN_RX_FILTER_ID & 0x7FFU) << 5U); can_filter.filter_list_low = 0x0000U; can_filter.filter_mask_high = static_cast((CONFIG_CAN_RX_FILTER_MASK & 0x7FFU) << 5U); can_filter.filter_mask_low = static_cast(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 */ can_parameter.rec_fifo_overwrite = ENABLE; 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_* 宏配置,目标 2.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; 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(SystemCoreClock), static_cast(rcu_clock_freq_get(CK_APB1))); } #endif /** @brief 单通道硬件初始化(hardResetCan+重试+退出init mode+中断使能+RX队列清零)。 */ void CanFd::initChannelHardware(uint8_t i) { const auto &ch = kChannels[i]; if (!kChannelEnabled[i]) { s_channelHwOk[i] = false; return; } s_channelHwOk[i] = false; rcu_periph_clock_enable(ch.canRcu); gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */ const uint8_t rxLevelBefore = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U; const uint8_t stbLevel = (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) ? 1U : 0U; /* 强制复位 + 重试机制:使用 hardResetCan 完整电源周期解决 CAN1 卡死问题 */ bool nominalOk = false; bool fdOk = false; for (uint8_t retry = 0U; retry < 5U; ++retry) { hardResetCan(ch.canPeriph, ch.canRcu); nominalOk = initNominalTiming(ch.canPeriph); fdOk = initFdMode(ch.canPeriph); if (nominalOk && fdOk) { break; /* 初始化成功,无需重试 */ } /* can_init 失败后外设可能卡在 init mode,先强制退出让下次重试从干净状态开始 */ CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD; for (volatile uint32_t d = 0; d < 50000U; ++d) { if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break; } LOG_ERR("[CAN-WARN] CH%u init failed (nom=%u fd=%u STAT=0x%08lX), retry %u/5\r\n", static_cast(i), nominalOk ? 1U : 0U, fdOk ? 1U : 0U, static_cast(CAN_STAT(ch.canPeriph)), static_cast(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(i), static_cast(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(i), nominalOk ? 1U : 0U, fdOk ? 1U : 0U, exitedInitMode ? 1U : 0U, static_cast(exitWait), static_cast(rxLevelBefore), static_cast(rxLevelAfter), static_cast(stbLevel), static_cast(statAfterExit), static_cast(errAfterExit), static_cast(tecAfterExit), static_cast(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(i)); nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast(i)); s_rxQueues[i].head = 0U; s_rxQueues[i].tail = 0U; s_rxQueues[i].count = 0U; s_txQueues[i].head = 0U; s_txQueues[i].tail = 0U; s_txQueues[i].count = 0U; const bool exitOk = ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U); s_channelHwOk[i] = (nominalOk && fdOk && exitOk); } /** @brief 统一配置三路滤波器(HBC1F + per-channel filter)。 */ void CanFd::configureAllFilters() { /* ===== 统一配置滤波器(必须在所有 hardResetCan 完成之后) ===== * CAN1 hardResetCan 的 SWRST 会清零 CAN0/CAN1 共享滤波器寄存器域。 * 若在循环内先配 CAN0 filter、后做 CAN1 hardReset,CAN0 的 filter 0 会被 * 硬件清零(CAN_FW bit0 = 0),导致 CAN0 无法接收任何帧。 * 修复方案:所有 hardReset 完成后,再统一配 HBC1F + 三路 filter。 */ can1_filter_start_bank(14U); /* CAN0: bank 0-13, CAN1: bank 14-27 */ for (size_t i = 0; i < kChannelCount; ++i) { if (!kChannelEnabled[i]) continue; configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, s_mode); } CAN_LOGI("[CAN] filters configured post-init: HBC1F=%lu FW=0x%08lX\r\n", static_cast((CAN_FCTL(CAN0) >> 8) & 0x3FU), static_cast(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(i), ok ? "OK" : "FAIL(init_mode)", static_cast(bt), static_cast(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(hbc), static_cast(fw), static_cast((fw >> 0) & 1U), static_cast((fw >> 2) & 1U), static_cast((fw >> 14) & 1U), static_cast(fw2), static_cast((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(i), static_cast(err), static_cast(tec), static_cast(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(i)); continue; } const uint8_t ch = static_cast(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(i), c.tec == 0 ? "ACK" : "NO_ACK", fd.tec == 0 ? "ACK" : "NO_ACK", brs.tec == 0 ? "ACK" : "NO_ACK", static_cast(brs.tec > fd.tec ? brs.tec : fd.tec)); #else CAN_LOGI("CH%u: classic=%s fd_nobrs=%s TEC=%lu\r\n", static_cast(i), c.tec == 0 ? "ACK" : "NO_ACK", fd.tec == 0 ? "ACK" : "NO_ACK", static_cast(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(i)); continue; } if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) { CAN_LOGI("CH%u: SKIP(stuck_init)\r\n", static_cast(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(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(i), cOk ? "PASS" : "FAIL", fdOk ? "PASS" : "FAIL", brsOk ? "PASS" : "FAIL", static_cast(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(i), static_cast(tecAfterLb), static_cast(recAfterLb), static_cast(statAfterLb), static_cast(CAN_BT(ch.canPeriph)), static_cast(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(i), normalMode ? "NORMAL" : "!!!NOT_NORMAL!!!", static_cast(ctl), static_cast(stat), static_cast(bt), static_cast(dbt), static_cast(err), static_cast(inten), static_cast(fctl), static_cast(fw), static_cast(rfl0)); /* 逐位解析关键标志 */ CAN_LOGI(" IWMOD=%u SLPWMOD=%u ARD=%u ABOR=%u | IWS=%u SLPWS=%u | LCMOD=%u SCMOD=%u\r\n", (ctl & CAN_CTL_IWMOD) ? 1U : 0U, (ctl & CAN_CTL_SLPWMOD) ? 1U : 0U, /* bit1: sleep mode request */ (ctl & CAN_CTL_ARD) ? 1U : 0U, /* bit4: auto retrans disable */ (ctl & CAN_CTL_ABOR) ? 1U : 0U, /* bit6: auto bus-off recovery */ (stat & CAN_STAT_IWS) ? 1U : 0U, (stat & CAN_STAT_SLPWS) ? 1U : 0U, (bt & (1U << 30)) ? 1U : 0U, /* LCMOD */ (bt & (1U << 31)) ? 1U : 0U); /* SCMOD */ } CAN_LOGI("===============================\r\n"); } #endif /* CONFIG_CAN_STARTUP_DIAG_VERBOSE */ /** * @brief 初始化三路 CANFD 控制器、过滤器、中断与软件队列。 * @param[in] 无。 * @param[out] 无。 * @retval 无返回值。 * @note 初始化收发器 GPIO 后延时进入 Normal,并启用 FIFO0 中断统计。 */ void CanFd::init() { s_diag = {}; s_nextConsumeChannel = 0U; s_mode = Mode::kModeAllPass; for (size_t i = 0; i < kChannelCount; ++i) { s_channelStats[i] = {}; s_feedback[i] = {}; s_channelHwOk[i] = false; } s_feedbackSeq = 0U; initGpio(); #if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0) printClockInfo(); #endif /* 收发器待机保持后,延时再进入 Normal */ constexpr uint32_t kCanPowerOnDeferMs = 200U; SysTickTimer::delayMs(kCanPowerOnDeferMs); for (uint8_t i = 0; i < kChannelCount; ++i) { initChannelHardware(i); } s_nextConsumeChannel = 0U; configureAllFilters(); #if (CONFIG_CAN_STARTUP_DIAG_VERBOSE != 0) printInitResult(); printErrorState(); dumpRegisters(); #endif #if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0) runSelfTest(); #endif #if (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE != 0) runLoopbackTest(); #endif } /** * @brief L0 轻量自检:无总线发帧,仅校验使能通道控制器自身状态(GD32 侧故障)。 * @note 1.5.35:不再检查 BUS_OFF/TEC/REC——未接从站的通道 RX 浮空噪声会累积 REC, * 「没接设备」不等于错误;总线侧健康由 0xC1 反馈帧运行期上报。 */ bool CanFd::selfTestL0(uint8_t *fault_code, uint8_t *fault_channel) { bool anyEnabled = false; bool allOk = true; uint8_t firstCode = kSelfTestOk; uint8_t firstCh = 0U; for (uint8_t i = 0U; i < static_cast(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(i), static_cast(code), static_cast(CAN_STAT(ch.canPeriph)), static_cast(CAN_ERR(ch.canPeriph))); } } if (fault_code != nullptr) { *fault_code = firstCode; } if (fault_channel != nullptr) { *fault_channel = firstCh; } return anyEnabled && allOk; } /** * @brief 切换 CANFD 接收过滤模式。 * @param[in] mode 目标过滤模式。 * @note 切换时逐路重新配置过滤器;can_filter_init 内部会清 FCTL_FLD,无需手动进入 INITIAL 模式。 */ void CanFd::setMode(Mode mode) { uint32_t save = RT_CRITICAL_SAVE(); s_mode = mode; for (size_t i = 0; i < kChannelCount; ++i) { configureRxFilter(kChannels[i].canPeriph, kChannels[i].filterNumber, mode); } RT_CRITICAL_RESTORE(save); } /** * @brief 查询当前 CANFD 接收过滤模式。 */ CanFd::Mode CanFd::mode() { return s_mode; } /** * @brief 查询三路 CAN 通道是否存在待消费接收报文。 * @param[in] 无。 * @param[out] 无。 * @retval bool true 表示至少一条接收队列非空,false 表示当前三路均无待消费报文。 */ bool CanFd::hasRxFrame() { bool found = false; uint32_t save = RT_CRITICAL_SAVE(); for(size_t i = 0; i < kChannelCount; ++i) { if(s_rxQueues[i].count != 0U) { found = true; break; } } RT_CRITICAL_RESTORE(save); return found; } /** * @brief 轮转读取并消费一帧接收报文。 * @param[in] 无。 * @param[out] id 输出报文标识符;允许为空。 * @param[out] data 输出报文数据缓存;允许为空。 * @param[out] length 输出报文长度;允许为空。 * @param[out] channel 输出报文来源通道号;允许为空。 * @retval bool true 表示成功消费一帧报文,false 表示当前三路接收队列均为空。 * @note 消费顺序采用从 `s_nextConsumeChannel` 开始的轮转扫描,避免长期偏向固定通道。 */ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel) { RxFrame localFrame{}; bool found = false; uint32_t save = RT_CRITICAL_SAVE(); for(size_t n = 0; n < kChannelCount; ++n) { const uint8_t idx = static_cast((s_nextConsumeChannel + n) % kChannelCount); auto &queue = s_rxQueues[idx]; if(queue.count == 0U) { continue; } localFrame = queue.frames[queue.head]; queue.head = static_cast((queue.head + 1U) % kRxQueueDepth); queue.count--; s_nextConsumeChannel = static_cast((idx + 1U) % kChannelCount); found = true; break; } RT_CRITICAL_RESTORE(save); if(!found) { return false; } if(channel) *channel = localFrame.channel; if(id) *id = localFrame.id; if(length) *length = localFrame.length; if(data) { for(uint8_t i = 0; i < localFrame.length; ++i) { data[i] = localFrame.data[i]; } } return true; } /** * @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。 * @param[in] channelIndex 目标 CAN 通道号(0/1/2)。 * @param[in] ext_id 扩展帧 ID(29 位)。 * @param[in] data 数据负载指针。 * @param[in] dlc 数据长度(0~64;0 表示无数据负载/远程类空帧)。 * @param[in] periodic 是否为周期性发送;true 时仅允许一个邮箱保持自动重发, * 不主动 abort 旧帧,且跳过 TEC 保护以匹配诊断行为。 * @retval CanTxResult kOk 表示发送请求已进入硬件邮箱;其它值表示具体失败原因。 * @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。 * 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。 */ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, const uint8_t *data, uint8_t dlc, bool periodic, bool allow_abort) { /* DLC=0 合法(仅 ID);DLC>0 时 data 必填 */ if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) { return CanTxResult::kErrInvalid; } if (dlc > 0U && data == nullptr) { return CanTxResult::kErrInvalid; } /* 诊断:捕获任何通过 transmitFrame 发 0x100 DLC=16 的帧(排查幽灵帧来源) */ if (ext_id == 0x00000100U && dlc == 16U) { CAN_LOGI("[CAN-DIAG] TX 0x100 DLC=16 via transmitFrame ch=%u\r\n", (unsigned)channelIndex); } /* BUS_OFF 不再软件拦截——CAN_ERR_BOERR 是粘性标志,控制器恢复后仍置位, * 导致总线正常时仍拒绝发送(用户确认其他设备能收到 GD32 帧)。 * CAN 硬件自带恢复机制(128×11隐性位后自动回 Normal); * 若确实发不出,can_message_transmit 会返回 NOMAILBOX → kErrMailbox。 */ const uint32_t canPeriph = kChannels[channelIndex].canPeriph; /* CAN 硬件自有 error-passive(TEC>127 自动加间隔) * 和 bus-off(TEC=256 → 自动恢复) 保护。软件不再做任何发送拦截。 */ /* 周期发送只允许一个邮箱保持自动重发。无 ACK 时同一帧会持续产生 * 总线波形;不要每 1ms 再填满其余邮箱,更不能主动 abort 旧帧,否则 * TX 完成中断会把软件中止累计为发送失败,掩盖真实 TEC/ERRN。 */ if (periodic) { for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) { if (can_transmit_states(canPeriph, mb) == CAN_TRANSMIT_PENDING) { return CanTxResult::kErrMailbox; } } } can_trasnmit_message_struct tx_msg{}; can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg); tx_msg.tx_efid = ext_id; tx_msg.tx_ff = CAN_FF_EXTENDED; tx_msg.tx_ft = CAN_FT_DATA; tx_msg.tx_dlen = dlc; #ifdef GD32E508 tx_msg.fd_flag = (dlc > 8U) ? ENABLE : DISABLE; #if (CONFIG_CAN_FD_BRS_ENABLE != 0) tx_msg.fd_brs = (dlc > 8U) ? 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(channelIndex), static_cast(errAfter), static_cast((errAfter >> 4U) & 0x07U), canLastErrorName(errAfter), static_cast((errAfter >> 16U) & 0xFFU), static_cast((errAfter >> 24U) & 0xFFU), static_cast(CAN_TSTAT(canPeriph))); s_lastMailboxLogSeq[channelIndex] = s_feedbackSeq; } return CanTxResult::kErrMailbox; } /** * @brief 突发排入软件 TX 队列(详见头声明)。 * * 场景:电机控制台 0xC7 突发一次发 4 帧同通道命令, * GD32 只有 3 个硬件邮箱,第 4 帧必丢。此队列把超载帧留下来, * TX 完成中断里持续回灌,前端 /can/send_burst 不再因 >3 帧掉帧。 */ CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id, const uint8_t *data, uint8_t dlc) { if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) { return CanTxResult::kErrInvalid; } if (dlc > 0U && data == nullptr) { return CanTxResult::kErrInvalid; } TxQueue &q = s_txQueues[channelIndex]; /* 快速路径:队列空 → 直通硬件邮箱。装得下立即返回,不用等待 TX 完成中断。 * ★ allow_abort=false:邮箱满时禁止 abort 旧帧顶替(那会把同通道已装载的 * 旧帧静默丢弃,>3 帧同通道突发只剩最后 3 帧上线),一律落入软件队列, * 由 TX 完成中断 drainTxQueueIsr 回灌。 */ if (q.count == 0U) { const CanTxResult r = transmitFrame(channelIndex, ext_id, data, dlc, false, false); if (r != CanTxResult::kErrMailbox) { return r; /* 装上了(kOk)或失败原因非邮箱满(kErrInvalid/kErrBusOff) */ } /* 邮箱满:落入下面的入队路径 */ } /* 关中段:EXTI15(SPI burst) 与 CANx TX ISR 并发,保护 head/tail/count。 * 用 CMSIS NVIC 接口(SPL 的 nvic_irq_enable 是 3 参版)。 * 关的是该通道自身的 txIrq,避免关全局影响其他通道的实时性。 */ const IRQn_Type irqn = kChannels[channelIndex].txIrq; NVIC_DisableIRQ(irqn); if (q.count >= kTxQueueDepth) { /* 队列也满了:彻底装不下。此时不能再返回 kOk, * 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */ NVIC_EnableIRQ(irqn); s_diag.error_count++; s_channelStats[channelIndex].error_count++; return CanTxResult::kErrMailbox; } TxQueuedFrame &f = q.frames[q.tail]; f.id = ext_id; f.dlc = dlc; if (dlc > 0U) { memcpy(f.data, data, dlc); } q.tail = static_cast((q.tail + 1U) % kTxQueueDepth); q.count++; NVIC_EnableIRQ(irqn); return CanTxResult::kOk; } /** * @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。 * @note 只在 ISR 内调用;关中断保护已在 transmitFrame 返回值判定外层完成, * 本函数本身不关中(邮箱空位是"单调减少再单调增加",多读无害)。 */ void CanFd::drainTxQueueIsr(uint8_t channelIndex) { if (channelIndex >= kChannelCount) { return; } TxQueue &q = s_txQueues[channelIndex]; while (q.count > 0U) { TxQueuedFrame &f = q.frames[q.head]; /* 复用 transmitFrame 的装载逻辑;allow_abort=false:这里邮箱刚空出来, * 必能装上,禁止 abort 其它 PENDING 邮箱(会静默丢弃同通道排队帧)。 */ const CanTxResult r = transmitFrame(channelIndex, f.id, f.data, f.dlc, false, false); if (r == CanTxResult::kOk) { q.head = static_cast((q.head + 1U) % kTxQueueDepth); q.count--; } else { /* kErrMailbox:理论上不该发生(刚刚空出一个邮箱),保守 break; * 其它错误(kErrInvalid)已经入队又因参数错必败,直接丢弃防死循环。 */ if (r != CanTxResult::kErrMailbox) { q.head = static_cast((q.head + 1U) % kTxQueueDepth); q.count--; } else { break; } } } } uint32_t CanFd::txQueueBacklog(uint8_t channelIndex) { if (channelIndex >= kChannelCount) { return 0U; } return s_txQueues[channelIndex].count; } void CanFd::abortPendingTx(uint8_t channelIndex) { if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { return; } const uint32_t canPeriph = kChannels[channelIndex].canPeriph; for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) { if (can_transmit_states(canPeriph, mb) == CAN_TRANSMIT_PENDING) { (void)can_transmission_stop(canPeriph, mb); } } } void CanFd::setChannelAutoRetrans(uint8_t channelIndex, bool enable) { if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { return; } const uint32_t canPeriph = kChannels[channelIndex].canPeriph; /* ARD 位仅在 init mode 可写:进 init mode → 改 ARD → 退 init mode。 */ CAN_CTL(canPeriph) |= CAN_CTL_IWMOD; for (volatile uint32_t t = 0U; t < 200000U; ++t) { if (CAN_STAT(canPeriph) & CAN_STAT_IWS) break; } if (enable) { CAN_CTL(canPeriph) &= ~CAN_CTL_ARD; /* 自动重发 ON(业务默认) */ } else { CAN_CTL(canPeriph) |= CAN_CTL_ARD; /* 自动重发 OFF(单发,停等式 OTA 用) */ } CAN_CTL(canPeriph) &= ~CAN_CTL_IWMOD; for (volatile uint32_t t = 0U; t < 200000U; ++t) { if ((CAN_STAT(canPeriph) & CAN_STAT_IWS) == 0U) break; } CAN_LOGI("[CAN] CH%u set auto_retrans=%u -> ARD=%u\r\n", (unsigned)channelIndex, (unsigned)enable, (CAN_CTL(canPeriph) & CAN_CTL_ARD) ? 1U : 0U); } 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); can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message); 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 = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid; /* 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 = s_diag.last_rx_id; 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 = s_diag.last_rx_id; 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(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]; const uint32_t rxId = s_diag.last_rx_id; /* 查找已有条目 */ 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(CANFD_ID_SNAPSHOT_DATA_MAX); for (uint8_t si = 0U; si < snapCopy; ++si) { entry.data[si] = rx_message.rx_data[si]; } for (uint8_t si = snapCopy; si < CANFD_ID_SNAPSHOT_DATA_MAX; ++si) { entry.data[si] = 0U; } } auto &queue = s_rxQueues[channelIndex]; if(queue.count >= kRxQueueDepth) { s_diag.rx_drop_count++; stats.rx_drop_count++; continue; } auto &frame = queue.frames[queue.tail]; frame.channel = channelIndex; frame.id = s_diag.last_rx_id; frame.length = rx_message.rx_dlen; for(uint8_t i = 0; i < rx_message.rx_dlen; ++i) { frame.data[i] = rx_message.rx_data[i]; } queue.tail = static_cast((queue.tail + 1U) % kRxQueueDepth); queue.count++; } if(fifoFull) { can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0); } if(fifoOverfull) { can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0); } if(hadTraffic) { TaskManager::set(kCanRxParseTaskMask); } } /** * @brief 处理指定通道的发送完成中断。 * @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。 * @note 只有 MTFNERR 置位才表示完整发送且未出错;邮箱受理、仲裁丢失、 * 主动中止和发送错误均不能计入网页“发送数”。 */ void CanFd::handleTxInterrupt(uint8_t channelIndex) { if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { return; } const uint32_t canPeriph = kChannels[channelIndex].canPeriph; const uint32_t tstat = CAN_TSTAT(canPeriph); constexpr uint32_t finishedBits[] = { CAN_TSTAT_MTF0, CAN_TSTAT_MTF1, CAN_TSTAT_MTF2 }; constexpr uint32_t successBits[] = { CAN_TSTAT_MTFNERR0, CAN_TSTAT_MTFNERR1, CAN_TSTAT_MTFNERR2 }; constexpr uint32_t errorBits[] = { CAN_TSTAT_MTE0, CAN_TSTAT_MTE1, CAN_TSTAT_MTE2 }; for (size_t mailbox = 0U; mailbox < 3U; ++mailbox) { if ((tstat & finishedBits[mailbox]) == 0U) { continue; } if ((tstat & successBits[mailbox]) != 0U) { s_diag.tx_count++; s_channelStats[channelIndex].tx_count++; } else if ((tstat & errorBits[mailbox]) != 0U) { s_diag.error_count++; s_channelStats[channelIndex].error_count++; /* ARD=1 单发模式下 MTE 置位 = 该帧已确认丢失;置事件标志供停等式 * OTA 快速重发(免等 ACK 超时)。单写(ISR)单读清(主循环),无锁。 */ s_txErrorEvent[channelIndex] = true; } } CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2); /* 队列里有积压帧时,趁刚空出的邮箱位置回灌。必须在 TSTAT 清标志之后调, * 否则 transmitFrame 看到的还是旧 PENDING 状态。 */ drainTxQueueIsr(channelIndex); } /** * @brief 刷新三路 CAN 通道的 1Hz 反馈快照。 * @note 在临界区内拷贝实时计数与最近一帧信息到 s_feedback;保证 SPI 命令读到一致快照。 * 典型调用频率 1Hz,由任务调度器在心跳节拍触发。 */ void CanFd::refreshFeedbackSnapshot() { /* 先在临界区内采集硬件寄存器,确保三通道快照一致 */ uint32_t hw_tec[3] = {}; uint32_t hw_rec[3] = {}; uint32_t hw_stat[3] = {}; uint32_t hw_err[3] = {}; uint32_t hw_rfl0[3] = {}; { uint32_t save = RT_CRITICAL_SAVE(); for (size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; const uint32_t err_val = CAN_ERR(ch.canPeriph); const uint32_t stat_val = CAN_STAT(ch.canPeriph); hw_tec[i] = (err_val >> 16) & 0xFFU; hw_rec[i] = (err_val >> 24) & 0xFFU; hw_stat[i] = stat_val; hw_err[i] = err_val; hw_rfl0[i] = can_receive_message_length_get(ch.canPeriph, CAN_FIFO0); } RT_CRITICAL_RESTORE(save); } s_feedbackSeq++; /* 每 5 秒采样硬件错误态;正常心跳不打印(统计走 SPI 0xC1)。 * 仅当 TEC/REC 或告警位非零时输出 [CAN-HW-ERR];全量 seq 心跳见 CONFIG_UART_DIAG_LEVEL。 */ if (s_feedbackSeq % 5U == 0U) { #if (CONFIG_UART_DIAG_LEVEL != 0) CAN_LOGI("[CAN-FB] refresh seq=%lu rx0=%lu rx1=%lu rx2=%lu\r\n", (unsigned long)s_feedbackSeq, (unsigned long)s_channelStats[0].rx_count, (unsigned long)s_channelStats[1].rx_count, (unsigned long)s_channelStats[2].rx_count); #endif for (size_t i = 0; i < kChannelCount; ++i) { if (hw_tec[i] == 0U && hw_rec[i] == 0U && (hw_err[i] & (CAN_ERR_WERR | CAN_ERR_PERR | CAN_ERR_BOERR)) == 0U) { continue; } LOG_ERR("[CAN-HW-ERR] CH%u ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n", static_cast(i), static_cast(hw_err[i]), static_cast((hw_err[i] >> 4U) & 0x07U), canLastErrorName(hw_err[i]), static_cast(hw_tec[i]), static_cast(hw_rec[i]), static_cast(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(hw_tec[i]); s_feedback[i].stats.rec = static_cast(hw_rec[i]); /* 总线状态位图:bit0=init, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning * init/sleep 来自 CAN_STAT,error_passive/bus_off/error_warning 来自 CAN_ERR */ uint8_t busState = 0U; if (hw_stat[i] & CAN_STAT_IWS) busState |= (1U << 0); /* 初始化模式 */ if (hw_stat[i] & CAN_STAT_SLPWS) busState |= (1U << 1); /* 睡眠模式 */ if (hw_err[i] & CAN_ERR_PERR) busState |= (1U << 2); /* 错误被动 */ if (hw_err[i] & CAN_ERR_BOERR) busState |= (1U << 3); /* 总线关闭 */ if (hw_err[i] & CAN_ERR_WERR) busState |= (1U << 4); /* 错误警告 */ s_feedback[i].stats.bus_state = busState; s_idSnapshot[i].seq = s_feedbackSeq; } /* BUS_OFF 状态变化检测:仅在状态切换时打印,避免刷屏 */ static uint8_t s_prevBusState[3] = {0xFF, 0xFF, 0xFF}; /* 0xFF=首次 */ for (size_t i = 0; i < kChannelCount; ++i) { uint8_t cur = s_feedback[i].stats.bus_state; if (cur != s_prevBusState[i]) { const char *stateStr = "NORMAL"; if (cur & (1U << 0)) stateStr = "INIT_MODE"; else if (cur & (1U << 3)) stateStr = "BUS_OFF"; else if (cur & (1U << 2)) stateStr = "ERR_PASSIVE"; else if (cur & (1U << 4)) stateStr = "ERR_WARN"; LOG_ERR("[CAN-BUS] CH%u: %s -> %s ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu seq=%lu\r\n", static_cast(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(hw_err[i]), static_cast((hw_err[i] >> 4U) & 0x07U), canLastErrorName(hw_err[i]), static_cast(hw_tec[i]), static_cast(hw_rec[i]), static_cast(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(); }