Files
DCU_APP/GD32E508_DCU_APP/device/CANFD.cpp
T
cbb 1612e326ee feat(cproject): 重构项目文件结构并添加EtherCAT通信支持
- 将设备相关文件重新组织为device/spi和comm分组
- 添加EtherCAT从站通信模块(esc_exmc_bus、esc_lan9252、ethercat_slave等)
- 添加EXMC外设组件支持
- 统一文件命名规范为小写

refactor(canfd): 优化CANFD模块中断安全性和配置方式

- 替换自定义临界区函数为RT_CRITICAL_SAVE/RESTORE宏
- 使用配置常量CONFIG_IRQ_PREEMPT_CAN替代硬编码值
- 简化邮箱满时的处理逻辑,移除不必要的SysTick等待
- 移除废弃的中断保存头文件引用

refactor(timer): 更新定时器配置以使用配置常量

- 修复头文件包含路径大小写问题
- 使用CONFIG_NVIC_PRIORITY_GROUP配置项替代硬编码值
2026-06-26 00:57:59 +08:00

617 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file canfd.cpp
* @brief CANFD 通信模块实现。
*/
#include "main.h"
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/task_manager.h"
namespace {
struct TxFrame {
can_trasnmit_message_struct tx_message;
uint8_t payload[64];
};
struct TxQueue {
TxFrame frames[4];
uint8_t head;
uint8_t tail;
uint8_t count;
};
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 rx0Irq;
uint8_t filterNumber;
};
constexpr CanChannelConfig kChannels[] = {
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_RX0_IRQn, 0U },
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_14, CAN1_RX0_IRQn, 1U },
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_RX0_IRQn, 2U },
};
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
constexpr uint8_t kTxQueueDepth = 4U;
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
} // namespace
CanFd::Diag CanFd::s_diag{};
CanFd::RxQueue CanFd::s_rxQueues[3]{};
uint8_t CanFd::s_nextConsumeChannel = 0U;
static TxQueue s_txQueues[3]{};
/**
* @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);
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);
}
}
/**
* @brief 为指定 CAN 控制器配置接收过滤器。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[in] filterNumber 过滤器编号。
* @param[out] 无。
* @retval 无返回值。
* @note 当前默认按 `CONFIG_CAN_RX_FILTER_ID`/`CONFIG_CAN_RX_FILTER_MASK` 对标准帧做掩码匹配,并统一进入 FIFO0。
*/
void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber)
{
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;
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 当前按 1 Mbps 仲裁段参数完成普通 CAN 初始化。
*/
void 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;
can_parameter.rec_fifo_overwrite = ENABLE;
can_parameter.trans_fifo_order = ENABLE;
can_parameter.working_mode = CAN_NORMAL_MODE;
can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;
can_parameter.time_segment_1 = CAN_BT_BS1_7TQ;
can_parameter.time_segment_2 = CAN_BT_BS2_2TQ;
can_parameter.prescaler = 6U;
can_init(canPeriph, &can_parameter);
}
/**
* @brief 配置指定 CAN 控制器的 CAN FD 数据段参数。
* @param[in] canPeriph 目标 CAN 外设实例。
* @param[out] 无。
* @retval 无返回值。
* @note 在 GD32E508 平台下启用 FD、BRS 与 TDC;非该平台则仅使能 FD 功能。
*/
void 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 = 6U;
can_fd_parameter.data_resync_jump_width = CAN_BT_SJW_1TQ;
can_fd_parameter.data_time_segment_1 = CAN_BT_BS1_3TQ;
can_fd_parameter.data_time_segment_2 = CAN_BT_BS2_1TQ;
can_fd_init(canPeriph, &can_fd_parameter);
#else
can_fd_function_enable(canPeriph);
#endif
}
/**
* @brief 构造内部发送缓冲中的状态帧内容。
* @param[in] status 指向状态载荷的指针。
* @param[out] 无。
* @retval 无返回值。
* @note 当前实现中状态帧构造逻辑已内联至 `enqueueTxFrame()`,此函数仅保留接口兼容性。
*/
void CanFd::buildStatusFrame(const StatusPayload *status)
{
if(status == nullptr) {
return;
}
const int16_t temp_x100 = static_cast<int16_t>(status->temperature * 100.0f);
const uint16_t vdda_x1000 = static_cast<uint16_t>(status->vdda * 1000.0f);
auto &frame = s_txQueues[0].frames[0];
auto &p = frame.payload;
p[0] = static_cast<uint8_t>(status->temp_raw & 0xFFU);
p[1] = static_cast<uint8_t>((status->temp_raw >> 8) & 0xFFU);
p[2] = static_cast<uint8_t>(status->vref_raw & 0xFFU);
p[3] = static_cast<uint8_t>((status->vref_raw >> 8) & 0xFFU);
p[4] = static_cast<uint8_t>(temp_x100 & 0xFF);
p[5] = static_cast<uint8_t>((temp_x100 >> 8) & 0xFF);
p[6] = static_cast<uint8_t>(vdda_x1000 & 0xFFU);
p[7] = static_cast<uint8_t>((vdda_x1000 >> 8) & 0xFFU);
p[8] = CONFIG_CAN_STATUS_TAG0;
p[9] = CONFIG_CAN_STATUS_TAG1;
p[10] = CONFIG_CAN_STATUS_TAG2;
p[11] = CONFIG_CAN_STATUS_TAG3;
}
/**
* @brief 向指定 CAN 通道的发送 FIFO 入队一帧状态报文。
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[in] status 指向状态载荷的指针。
* @param[out] 无。
* @retval bool true 表示成功入队,false 表示参数无效或该通道发送队列已满。
* @note 仅完成软件发送队列入队,不会立即触发硬件发送。
*/
bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status)
{
if(status == nullptr || channelIndex >= kChannelCount) {
return false;
}
auto &queue = s_txQueues[channelIndex];
if(queue.count >= kTxQueueDepth) {
s_diag.tx_drop_count++;
return false;
}
buildStatusFrame(status);
auto &frame = queue.frames[queue.tail];
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &frame.tx_message);
frame.tx_message.tx_sfid = CONFIG_CAN_STATUS_ID;
frame.tx_message.tx_efid = 0U;
frame.tx_message.tx_ff = CAN_FF_STANDARD;
frame.tx_message.tx_ft = CAN_FT_DATA;
frame.tx_message.tx_dlen = CANFD_STATUS_DLEN;
#ifdef GD32E508
frame.tx_message.fd_flag = ENABLE;
frame.tx_message.fd_brs = ENABLE;
frame.tx_message.fd_esi = CAN_ESI_DOMINANT;
#endif
const auto &src = s_txQueues[0].frames[0].payload;
auto &p = frame.payload;
for(uint8_t j = 0; j < CANFD_STATUS_DLEN; ++j) {
p[j] = src[j];
frame.tx_message.tx_data[j] = p[j];
}
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kTxQueueDepth);
queue.count++;
return true;
}
/**
* @brief 初始化三路 CANFD 控制器、过滤器、中断与软件队列。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 初始化后默认将三路 TCAN1057A 收发器置为 Normal,并使能 FIFO0 的非空/满/overfull 中断统计。
*/
void CanFd::init()
{
s_diag = {};
s_nextConsumeChannel = 0U;
initGpio();
for(size_t i = 0; i < kChannelCount; ++i) {
const auto &ch = kChannels[i];
rcu_periph_clock_enable(ch.canRcu);
gpio_bit_reset(ch.slpPort, ch.slpPin); /* STB=0 -> Normal */
can_deinit(ch.canPeriph);
initNominalTiming(ch.canPeriph);
initFdMode(ch.canPeriph);
configureRxFilter(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
s_rxQueues[i].head = 0U;
s_rxQueues[i].tail = 0U;
s_rxQueues[i].count = 0U;
s_txQueues[i].head = 0U;
s_txQueues[i].tail = 0U;
s_txQueues[i].count = 0U;
}
s_nextConsumeChannel = 0U;
}
/**
* @brief 执行兼容保留的 CAN 轮询入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前三路接收均由 RX0 中断驱动,该函数仅保留旧调用兼容性。
*/
void CanFd::poll()
{
/* 兼容保留:三通道接收已由 RX0 中断驱动 */
}
/**
* @brief 根据状态载荷为三路发送队列批量准备状态报文。
* @param[in] status 指向状态载荷的指针;为空时直接返回。
* @param[out] 无。
* @retval 无返回值。
* @note 当前默认不会周期性调用;仅在外部显式触发发送测试或业务发送时使用。
*/
void CanFd::prepareStatus(const StatusPayload *status)
{
if(status == nullptr) {
return;
}
for(size_t i = 0; i < kChannelCount; ++i) {
(void)enqueueTxFrame(static_cast<uint8_t>(i), status);
}
}
/**
* @brief 根据状态载荷仅为指定通道准备一帧状态报文。
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[in] status 指向状态载荷的指针;为空时返回失败。
* @param[out] 无。
* @retval bool true 表示成功入队,false 表示参数非法或发送队列已满。
*/
bool CanFd::prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status)
{
return enqueueTxFrame(channelIndex, status);
}
/**
* @brief 将当前各通道已准备好的状态报文送入硬件邮箱发送。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前默认不会周期性调用;仅处理调用方显式准备过的发送队列内容。
*/
void CanFd::sendPrepared()
{
for(size_t i = 0; i < kChannelCount; ++i) {
auto &queue = s_txQueues[i];
if(queue.count == 0U) {
continue;
}
auto &frame = queue.frames[queue.head];
const uint8_t mailbox = can_message_transmit(kChannels[i].canPeriph, &frame.tx_message);
if(mailbox != CAN_NOMAILBOX) {
s_diag.tx_count++;
s_diag.last_payload_length = frame.tx_message.tx_dlen;
s_diag.last_tx_id = frame.tx_message.tx_sfid;
s_diag.last_tx_channel = static_cast<uint8_t>(i);
queue.head = static_cast<uint8_t>((queue.head + 1U) % kTxQueueDepth);
queue.count--;
} else {
s_diag.error_count++;
}
}
}
/**
* @brief 查询三路 CAN 通道是否存在待消费接收报文。
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示至少一条接收队列非空,false 表示当前三路均无待消费报文。
*/
bool CanFd::hasRxFrame()
{
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
found = true;
break;
}
}
RT_CRITICAL_RESTORE(save);
return found;
}
/**
* @brief 轮转读取并消费一帧接收报文。
* @param[in] 无。
* @param[out] id 输出报文标识符;允许为空。
* @param[out] data 输出报文数据缓存;允许为空。
* @param[out] length 输出报文长度;允许为空。
* @param[out] channel 输出报文来源通道号;允许为空。
* @retval bool true 表示成功消费一帧报文,false 表示当前三路接收队列均为空。
* @note 消费顺序采用从 `s_nextConsumeChannel` 开始的轮转扫描,避免长期偏向固定通道。
*/
bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel)
{
RxFrame localFrame{};
bool found = false;
uint32_t save = RT_CRITICAL_SAVE();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
if(queue.count == 0U) {
continue;
}
localFrame = queue.frames[queue.head];
queue.head = static_cast<uint8_t>((queue.head + 1U) % kRxQueueDepth);
queue.count--;
s_nextConsumeChannel = static_cast<uint8_t>((idx + 1U) % kChannelCount);
found = true;
break;
}
RT_CRITICAL_RESTORE(save);
if(!found) {
return false;
}
if(channel) *channel = localFrame.channel;
if(id) *id = localFrame.id;
if(length) *length = localFrame.length;
if(data) {
for(uint8_t i = 0; i < localFrame.length; ++i) {
data[i] = localFrame.data[i];
}
}
return true;
}
/**
* @brief 直接向指定 CAN 通道发送一帧任意数据报文(扩展帧)。
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
* @param[in] ext_id 扩展帧 ID(29 位)。
* @param[in] data 数据负载指针。
* @param[in] dlc 数据长度(1~64)。
* @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。
* @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。
*/
bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
const uint8_t *data, uint8_t dlc)
{
if (channelIndex >= kChannelCount || data == nullptr || dlc == 0U || dlc > 64U) {
return false;
}
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;
tx_msg.fd_brs = (dlc > 8U) ? ENABLE : DISABLE;
tx_msg.fd_esi = CAN_ESI_DOMINANT;
#endif
for (uint8_t i = 0U; i < dlc; ++i) {
tx_msg.tx_data[i] = data[i];
}
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
/* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */
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) {
s_diag.tx_abort_count++;
break;
}
}
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
if (mailbox != CAN_NOMAILBOX) {
s_diag.tx_count++;
s_diag.last_tx_id = ext_id;
s_diag.last_tx_channel = channelIndex;
s_diag.last_payload_length = dlc;
return true;
}
s_diag.error_count++;
return false;
}
/**
* @brief 获取 CANFD 诊断统计信息只读指针。
* @param[in] 无。
* @param[out] 无。
* @retval const CanFd::Diag* 返回内部诊断统计结构体只读指针。
*/
const CanFd::Diag *CanFd::diag()
{
return &s_diag;
}
/**
* @brief 将三路 TCAN1057A 收发器置为 Normal 模式。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 对所有通道统一将 SLP/STB 控制脚拉低。
*/
void CanFd::setNormalMode()
{
for(size_t i = 0; i < kChannelCount; ++i) {
gpio_bit_reset(kChannels[i].slpPort, kChannels[i].slpPin);
}
}
/**
* @brief 将三路 TCAN1057A 收发器置为 Silent 模式。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 对所有通道统一将 SLP/STB 控制脚拉高。
*/
void CanFd::setSilentMode()
{
for(size_t i = 0; i < kChannelCount; ++i) {
gpio_bit_set(kChannels[i].slpPort, kChannels[i].slpPin);
}
}
/**
* @brief 处理指定通道的 RX0 中断接收。
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
* @param[out] 无。
* @retval 无返回值。
* @note 从对应控制器 FIFO0 循环取空所有待处理报文,更新诊断统计,写入该通道软件接收队列,并置位解析任务。
*/
void CanFd::handleRxInterrupt(uint8_t channelIndex)
{
if(channelIndex >= kChannelCount) 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) {
s_diag.rx_fifo_full_count++;
}
if(fifoOverfull) {
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;
auto &queue = s_rxQueues[channelIndex];
if(queue.count >= kRxQueueDepth) {
s_diag.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<uint8_t>((queue.tail + 1U) % kRxQueueDepth);
queue.count++;
}
if(fifoFull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFF0);
}
if(fifoOverfull) {
can_interrupt_flag_clear(ch.canPeriph, CAN_INT_FLAG_RFO0);
}
if(hadTraffic) {
TaskManager::set(kCanRxParseTaskMask);
}
}
/**
* @brief CAN0 兼容中断处理入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 兼容保留入口,内部统一转调 `handleRxInterrupt(0U)`。
*/
void CanFd::onRx0Interrupt()
{
handleRxInterrupt(0U);
}
/* ----- 中断入口 C 包装 ------------------------------------------------- */
/**
* @brief CAN0 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(0U)`。
*/
extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); }
/**
* @brief CAN1 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(1U)`。
*/
extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); }
/**
* @brief CAN2 RX0 中断向量入口。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(2U)`。
*/
extern "C" void CAN2_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(2U); }