From 10989939e9c744e9f39f60cfc37d826538af995f Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Sat, 11 Jul 2026 17:52:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E4=BF=AE=E5=A4=8DCAN=E5=8F=91?= =?UTF-8?q?=E9=80=81=E9=97=AE=E9=A2=98=E3=80=82=E5=AE=9E=E7=8E=B0CANFD?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=AE=8C=E6=88=90=E4=B8=AD=E6=96=AD=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=92=8C=E9=80=9A=E9=81=93=E5=81=A5=E5=BA=B7=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加CANFD发送完成中断处理函数,确保只有MTFNERR置位时才计数发送成功 - 新增通道健康检查功能,防止病态通道被持续重发的测试帧反复推回BUS_OFF - 重构CANFD配置结构体,添加TX中断配置项并修正GPIO映射设置 - 移除原有的软件发送队列机制,改为直接硬件发送模式 - 更新项目配置文件中的CANFD源文件路径命名 fix(canfd): 修复CANFD发送计数和错误处理逻辑 - 修正发送计数只在硬件MTFNERR确认后增加,而非请求进入邮箱时 - 改进错误日志输出,添加详细的错误类型和通道状态信息 - 优化总线恢复机制,通过收发器静默-唤醒协助硬件自动恢复 - 添加条件编译开关控制启动时自检和回环测试功能 --- .../GD32E508_DCU_APP.cproject.yml | 4 +- GD32E508_DCU_APP/device/CANFD.cpp | 453 +++++++++++------- GD32E508_DCU_APP/device/canfd.h | 39 +- GD32E508_DCU_APP/main.h | 7 +- GD32E508_DCU_APP/sys/config.h | 22 +- GD32E508_DCU_APP/sys/initial.cpp | 4 + GD32E508_DCU_APP/sys/task_manager.cpp | 9 +- GD32E508_DCU_APP/sys/task_runner.cpp | 84 ++-- GD32E508_DCU_APP/sys/task_runner.h | 4 +- 9 files changed, 361 insertions(+), 265 deletions(-) diff --git a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml index b8a4e5d..848eee7 100644 --- a/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml +++ b/GD32E508_DCU_APP/GD32E508_DCU_APP.cproject.yml @@ -1,4 +1,4 @@ -# A project translates into one executable or library. +# A project translates into one executable or library. project: groups: @@ -25,7 +25,7 @@ project: - file: ./device/adc.h - file: ./device/adc.cpp - file: ./device/canfd.h - - file: ./device/canfd.cpp + - file: ./device/CANFD.cpp - file: ./device/timer.h - file: ./device/timer.cpp - file: ./device/usart0.h diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index 37bacb4..39f24b7 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -7,22 +7,11 @@ #include "device/canfd.h" #include "sys/config.h" #include "sys/critical.h" +#include "sys/systick.h" #include "sys/task_manager.h" #include 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; @@ -32,14 +21,15 @@ struct CanChannelConfig { 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_RX0_IRQn, 0U }, - { CAN1, RCU_CAN1, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_14, CAN1_RX0_IRQn, 14U }, - { CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_RX0_IRQn, 2U }, + { 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]); @@ -51,9 +41,16 @@ constexpr bool kChannelEnabled[kChannelCount] = { CONFIG_CAN_CH2_ENABLE, }; -constexpr uint8_t kTxQueueDepth = 4U; 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]; +} + struct CanSelfTestResult { bool txReq; uint8_t mailbox; @@ -158,7 +155,6 @@ CanFd::ChannelStats CanFd::s_channelStats[3]{}; CanFd::ChannelFeedback CanFd::s_feedback[3]{}; uint32_t CanFd::s_feedbackSeq = 0U; CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{}; -static TxQueue s_txQueues[3]{}; /** * @brief 查询指定通道是否已使能。 @@ -181,9 +177,16 @@ void CanFd::initGpio() 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: CAN1_RX/PB12, CAN1_TX/PB13 - * GD32E50x_用户手册_Rev2.2.pdf AFIO_PCF0 bit22 */ + /* 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 @@ -192,11 +195,9 @@ void CanFd::initGpio() #ifdef AFIO_PE1_CAN2_CFG gpio_afio_port_config(AFIO_PE1_CAN2_CFG, ENABLE); #endif - /* CAN0/CAN1 引脚选择由 AFIO_PCF0 的 remap 位控制: - * CAN0_REMAP[1:0]=00(复位默认) → CAN0_RX/PA11, CAN0_TX/PA12 (bit14:13) - * CAN1_REMAP=0(复位默认) → CAN1_RX/PB12, CAN1_TX/PB13 (bit22) - * AFIO_PCFA/PCFB 的 AFCFG 位不含 CAN0/CAN1 选项(GD32E50x_用户手册_Rev2.2.pdf - * 第8.5.17/8.5.18节),不可用 AFCFG 配置 CAN0/CAN1。 */ + + printf("[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]; @@ -302,83 +303,6 @@ bool CanFd::initFdMode(uint32_t 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(status->temperature * 100.0f); - const uint16_t vdda_x1000 = static_cast(status->vdda * 1000.0f); - - auto &frame = s_txQueues[0].frames[0]; - auto &p = frame.payload; - p[0] = static_cast(status->temp_raw & 0xFFU); - p[1] = static_cast((status->temp_raw >> 8) & 0xFFU); - p[2] = static_cast(status->vref_raw & 0xFFU); - p[3] = static_cast((status->vref_raw >> 8) & 0xFFU); - p[4] = static_cast(temp_x100 & 0xFF); - p[5] = static_cast((temp_x100 >> 8) & 0xFF); - p[6] = static_cast(vdda_x1000 & 0xFFU); - p[7] = static_cast((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++; - s_channelStats[channelIndex].error_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_efid = CONFIG_CAN_STATUS_ID; - frame.tx_message.tx_ff = CAN_FF_EXTENDED; - frame.tx_message.tx_ft = CAN_FT_DATA; - frame.tx_message.tx_dlen = CANFD_STATUS_DLEN; - frame.tx_message.fd_flag = ENABLE; - frame.tx_message.fd_brs = ENABLE; - frame.tx_message.fd_esi = CAN_ESI_DOMINANT; - - 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((queue.tail + 1U) % kTxQueueDepth); - queue.count++; - return true; -} - /** * @brief 初始化三路 CANFD 控制器、过滤器、中断与软件队列。 * @param[in] 无。 @@ -399,6 +323,10 @@ void CanFd::init() initGpio(); + printf("[CAN-CLOCK] SYS=%luHz APB1=%luHz nominal=1Mbps data=2Mbps\r\n", + static_cast(SystemCoreClock), + static_cast(rcu_clock_freq_get(CK_APB1))); + for(size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; if (!kChannelEnabled[i]) { @@ -480,15 +408,14 @@ void CanFd::init() static_cast(tecAfterExit), static_cast(recAfterExit)); - can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0); + 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; } s_nextConsumeChannel = 0U; @@ -554,6 +481,7 @@ void CanFd::init() } /* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */ +#if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0) printf("=== CAN SELF-TEST (normal mode) ===\r\n"); for (size_t i = 0; i < kChannelCount; ++i) { if (!kChannelEnabled[i]) { @@ -583,9 +511,13 @@ void CanFd::init() static_cast(fd.tec)); #endif } +#else + printf("=== CAN SELF-TEST SKIPPED (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE=0) ===\r\n"); +#endif /* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */ +#if (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE != 0) printf("=== CAN LOOPBACK (internal) ===\r\n"); for (size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; @@ -723,6 +655,9 @@ void CanFd::init() static_cast(CAN_CTL(ch.canPeriph))); } printf("========================\r\n"); +#else + printf("=== CAN LOOPBACK SKIPPED (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE=0) ===\r\n"); +#endif /* ===== 初始化后全寄存器快照:诊断 CAN0/CAN1 不收帧 ===== */ printf("=== POST-INIT FULL REG DUMP ===\r\n"); @@ -802,68 +737,6 @@ 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(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(i); - s_channelStats[i].tx_count++; - queue.head = static_cast((queue.head + 1U) % kTxQueueDepth); - queue.count--; - } else { - s_diag.error_count++; - s_channelStats[i].error_count++; - } - } -} - /** * @brief 查询三路 CAN 通道是否存在待消费接收报文。 * @param[in] 无。 @@ -937,11 +810,14 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t * @param[in] ext_id 扩展帧 ID(29 位)。 * @param[in] data 数据负载指针。 * @param[in] dlc 数据长度(1~64)。 - * @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。 + * @param[in] periodic 是否为周期性发送;true 时仅允许一个邮箱保持自动重发, + * 不主动 abort 旧帧,且跳过 TEC 保护以匹配诊断行为。 + * @retval bool true 表示发送请求已进入硬件邮箱,false 表示参数无效或邮箱已满。 * @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。 + * 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。 */ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, - const uint8_t *data, uint8_t dlc) + const uint8_t *data, uint8_t dlc, bool periodic) { if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || data == nullptr || dlc == 0U || dlc > 64U) { @@ -952,13 +828,48 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, const uint32_t canPeriph = kChannels[channelIndex].canPeriph; const uint32_t errReg = CAN_ERR(canPeriph); if (errReg & CAN_ERR_BOERR) { + static uint32_t s_lastBusOffLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU}; + if (s_lastBusOffLogSeq[channelIndex] != s_feedbackSeq) { + printf("[CAN-TX-REJECT] CH%u BUS_OFF ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n", + static_cast(channelIndex), + static_cast(errReg), + static_cast((errReg >> 4U) & 0x07U), + canLastErrorName(errReg), + static_cast((errReg >> 16U) & 0xFFU), + static_cast((errReg >> 24U) & 0xFFU), + static_cast(CAN_TSTAT(canPeriph))); + s_lastBusOffLogSeq[channelIndex] = s_feedbackSeq; + } return false; /* 通道处于 BUS_OFF,不发送 */ } const uint32_t tec = (errReg >> 16) & 0xFFU; - if (tec > 96U) { + if (tec > 96U && !periodic) { + static uint32_t s_lastTecLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU}; + if (s_lastTecLogSeq[channelIndex] != s_feedbackSeq) { + printf("[CAN-TX-REJECT] CH%u TEC_HIGH ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n", + static_cast(channelIndex), + static_cast(errReg), + static_cast((errReg >> 4U) & 0x07U), + canLastErrorName(errReg), + static_cast(tec), + static_cast((errReg >> 24U) & 0xFFU), + static_cast(CAN_TSTAT(canPeriph))); + s_lastTecLogSeq[channelIndex] = s_feedbackSeq; + } return false; /* TEC 过高(>75% 错误被动阈值),暂停发送保护通道 */ } + /* 周期发送只允许一个邮箱保持自动重发。无 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 false; + } + } + } + can_trasnmit_message_struct tx_msg{}; can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg); tx_msg.tx_efid = ext_id; @@ -980,7 +891,7 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg); - /* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待 + /* 非周期业务发送在邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待 * SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort * 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */ if (mailbox == CAN_NOMAILBOX) { @@ -997,18 +908,50 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, } 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; - s_channelStats[channelIndex].tx_count++; return true; } 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); + printf("[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 false; } +/** + * @brief 检查指定 CAN 通道当前是否处于健康可发送状态。 + * @param[in] channelIndex 通道索引 0/1/2。 + * @retval bool true=健康(未 BUS_OFF 且 TEC<=96),false=应避免发送。 + * @note 主要用于 taskCanTxSend 等周期性测试帧发送前判断,防止病态通道被 + * 持续重发的测试帧反复推回 BUS_OFF,干扰硬件 auto_bus_off_recovery。 + */ +bool CanFd::isChannelHealthy(uint8_t channelIndex) +{ + if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { + return false; + } + const uint32_t canPeriph = kChannels[channelIndex].canPeriph; + const uint32_t errReg = CAN_ERR(canPeriph); + if (errReg & CAN_ERR_BOERR) { + return false; + } + const uint32_t tec = (errReg >> 16) & 0xFFU; + return tec <= 96U; +} + /** * @brief 获取 CANFD 诊断统计信息只读指针。 * @param[in] 无。 @@ -1140,11 +1083,9 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex) const uint32_t bridgePeriph = kChannels[bridgeCh].canPeriph; const bool ok = bridgeTransmitRaw(bridgePeriph, rx_message); if (ok) { - s_diag.tx_count++; 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; - s_channelStats[bridgeCh].tx_count++; } else { s_diag.error_count++; s_channelStats[bridgeCh].error_count++; @@ -1255,6 +1196,47 @@ void CanFd::onRx0Interrupt() handleRxInterrupt(0U); } +/** + * @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++; + } + } + + CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2); +} + /** * @brief 刷新三路 CAN 通道的 1Hz 反馈快照。 * @note 在临界区内拷贝实时计数与最近一帧信息到 s_feedback;保证 SPI 命令读到一致快照。 @@ -1284,13 +1266,27 @@ void CanFd::refreshFeedbackSnapshot() } s_feedbackSeq++; - /* 每 5 秒打印一次 seq 心跳,确认 refreshFeedbackSnapshot 在运行 */ + /* 每 5 秒打印一次 seq 心跳;诊断期间同时输出实际错误通道的最后错误类型。 */ if (s_feedbackSeq % 5U == 0U) { printf("[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); + 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; + } + printf("[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; @@ -1320,13 +1316,16 @@ void CanFd::refreshFeedbackSnapshot() else if (cur & (1U << 3)) stateStr = "BUS_OFF"; else if (cur & (1U << 2)) stateStr = "ERR_PASSIVE"; else if (cur & (1U << 4)) stateStr = "ERR_WARN"; - printf("[CAN-BUS] CH%u: %s -> %s TEC=%lu REC=%lu seq=%lu\r\n", + printf("[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)); @@ -1335,6 +1334,97 @@ void CanFd::refreshFeedbackSnapshot() } } +/** + * @brief 运行时 BUS_OFF / 严重错误被动状态软件恢复。 + * @note 由 1Hz 心跳任务调用。若某通道持续 2 秒以上处于 BUS_OFF 或 TEC>96, + * 通过收发器静默-唤醒切换让总线获得 128×11 个连续隐性位,协助硬件 + * auto_bus_off_recovery 退出 BUS_OFF。本函数不执行 CAN 控制器 hardReset, + * 避免破坏 CAN0/CAN1 共享的 filter 配置;若切换后仍不健康,说明物理层 + * 问题未消除,需要排查终端电阻/ACK/波特率/接线。 + */ +void CanFd::recoverBusOffIfNeeded() +{ + static uint32_t s_unhealthySinceSeq[kChannelCount] = {0U, 0U, 0U}; + static bool s_recoveryInProgress[kChannelCount] = {false, false, false}; + + for (size_t i = 0; i < kChannelCount; ++i) { + if (!kChannelEnabled[i]) { + continue; + } + + const auto &ch = kChannels[i]; + const uint32_t errReg = CAN_ERR(ch.canPeriph); + const bool busOff = (errReg & CAN_ERR_BOERR) != 0U; + const uint32_t tec = (errReg >> 16) & 0xFFU; + const bool healthy = !busOff && (tec <= 96U); + + if (healthy) { + if (s_unhealthySinceSeq[i] != 0U) { + printf("[CAN-RECOVER] CH%u healthy again (TEC=%lu) seq=%lu\r\n", + static_cast(i), + static_cast(tec), + static_cast(s_feedbackSeq)); + s_unhealthySinceSeq[i] = 0U; + } + s_recoveryInProgress[i] = false; + continue; + } + + if (s_unhealthySinceSeq[i] == 0U) { + s_unhealthySinceSeq[i] = s_feedbackSeq; + } + + /* 仅在连续 2 秒不健康后才干预,避免对短暂波动过度反应 */ + const uint32_t elapsed = s_feedbackSeq - s_unhealthySinceSeq[i]; + if (elapsed < 2U) { + continue; + } + + if (s_recoveryInProgress[i]) { + /* 上一轮恢复后仍在下一周期观察到不健康:物理层问题未解决,不要刷屏 */ + static uint32_t s_lastStillBadPrintSeq[kChannelCount] = {0U, 0U, 0U}; + if ((s_feedbackSeq - s_lastStillBadPrintSeq[i]) >= 10U) { + printf("[CAN-RECOVER] CH%u still unhealthy after toggle (TEC=%lu bus_off=%u) seq=%lu; check physical layer\r\n", + static_cast(i), + static_cast(tec), + busOff ? 1U : 0U, + static_cast(s_feedbackSeq)); + s_lastStillBadPrintSeq[i] = s_feedbackSeq; + } + s_recoveryInProgress[i] = false; /* 允许下一轮再次尝试 */ + continue; + } + + printf("[CAN-RECOVER] CH%u attempting recovery (TEC=%lu bus_off=%u elapsed=%lus)\r\n", + static_cast(i), + static_cast(tec), + busOff ? 1U : 0U, + static_cast(elapsed)); + + /* 步骤:让收发器进入 standby 再切回 normal,强制总线出现连续隐性位, + * 帮助硬件 ABOR 完成 128×11 个连续隐性位的恢复序列。 */ + gpio_bit_set(ch.slpPort, ch.slpPin); + SysTickTimer::delayMs(20U); + gpio_bit_reset(ch.slpPort, ch.slpPin); + SysTickTimer::delayMs(10U); + + const uint32_t errAfterToggle = CAN_ERR(ch.canPeriph); + const bool stillBad = ((errAfterToggle & CAN_ERR_BOERR) != 0U) || + (((errAfterToggle >> 16) & 0xFFU) > 96U); + if (stillBad) { + printf("[CAN-RECOVER] CH%u toggle done but still unhealthy (TEC=%lu bus_off=%u); need physical-layer fix\r\n", + static_cast(i), + static_cast((errAfterToggle >> 16) & 0xFFU), + ((errAfterToggle & CAN_ERR_BOERR) != 0U) ? 1U : 0U); + } else { + printf("[CAN-RECOVER] CH%u recovered by transceiver toggle\r\n", + static_cast(i)); + } + + s_recoveryInProgress[i] = true; + } +} + /** * @brief 拷贝指定通道的最新反馈快照。 @@ -1376,6 +1466,9 @@ bool CanFd::copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out) */ extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); } +/** @brief CAN0 TX 完成中断向量入口。 */ +extern "C" void CAN0_TX_IRQHandler(void) { CanFd::handleTxInterrupt(0U); } + /** * @brief CAN1 RX0 中断向量入口。 * @param[in] 无。 @@ -1385,6 +1478,9 @@ extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); } */ extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); } +/** @brief CAN1 TX 完成中断向量入口。 */ +extern "C" void CAN1_TX_IRQHandler(void) { CanFd::handleTxInterrupt(1U); } + /** * @brief CAN2 RX0 中断向量入口。 * @param[in] 无。 @@ -1393,3 +1489,6 @@ extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); } * @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(2U)`。 */ extern "C" void CAN2_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(2U); } + +/** @brief CAN2 TX 完成中断向量入口。 */ +extern "C" void CAN2_TX_IRQHandler(void) { CanFd::handleTxInterrupt(2U); } diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index 7c343b6..534b3aa 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -3,9 +3,6 @@ #include -/** @brief CANFD 状态报文的数据长度,单位字节。 */ -#define CANFD_STATUS_DLEN 12U - /** @brief 每通道反馈快照中保存的最近一帧最大数据长度,受 SPI 应答帧总长度约束。 */ #define CANFD_FEEDBACK_LAST_DATA_MAX 32U @@ -58,7 +55,7 @@ public: */ struct ChannelStats { uint32_t rx_count; /**< 该通道累计成功接收帧数。 */ - uint32_t tx_count; /**< 该通道累计成功发送帧数。 */ + uint32_t tx_count; /**< 该通道累计由硬件 MTFNERR 确认的成功发送帧数。 */ uint32_t rx_drop_count; /**< 该通道累计软件丢帧数。 */ uint32_t error_count; /**< 该通道累计错误计数(与发送邮箱失败合并)。 */ uint32_t last_rx_id; /**< 该通道最近一帧报文标识符。 */ @@ -104,15 +101,6 @@ public: uint8_t count; /**< 当前占用条目数。 */ uint32_t seq; /**< 全局更新序号(每次 refresh 时递增)。 */ }; - /** - * @brief CANFD 状态发送载荷。 - */ - struct StatusPayload { - uint16_t temp_raw; - uint16_t vref_raw; - float vdda; - float temperature; - }; static void init(); @@ -132,21 +120,32 @@ public: static Mode mode(); static void poll(); - static void prepareStatus(const StatusPayload *status); - static bool prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status); - static void sendPrepared(); static bool hasRxFrame(); static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr); static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id, - const uint8_t *data, uint8_t dlc); + const uint8_t *data, uint8_t dlc, bool periodic = false); static const Diag *diag(); + /** + * @brief 检查指定 CAN 通道当前是否处于健康可发送状态。 + * @retval true 未处于 BUS_OFF 且 TEC 未超过预警阈值。 + * @retval false 通道处于 BUS_OFF 或 TEC 过高,应避免再向其发送测试帧。 + */ + static bool isChannelHealthy(uint8_t channelIndex); + /** * @brief 刷新三路 1Hz 反馈快照。 * @note 通常由任务调度器在 1 秒节拍调用,将各通道实时计数与最近一帧信息抓拍到 s_feedback。 */ static void refreshFeedbackSnapshot(); + /** + * @brief 保留的手动 BUS_OFF 恢复诊断入口。 + * @note 正常周期任务不调用;控制器已启用硬件自动 BUS_OFF 恢复,避免在 + * 发送重试期间切换收发器 S 脚而人为制造额外位错误。 + */ + static void recoverBusOffIfNeeded(); + /** * @brief 拷贝指定通道的最新反馈快照到调用方缓冲。 * @param[in] channelIndex 目标 CAN 通道号(0/1/2)。 @@ -167,6 +166,7 @@ public: static void setSilentMode(); static void onRx0Interrupt(); static void handleRxInterrupt(uint8_t channelIndex); + static void handleTxInterrupt(uint8_t channelIndex); private: static constexpr uint8_t kRxQueueDepth = 16U; @@ -195,8 +195,6 @@ private: static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode); static bool initNominalTiming(uint32_t canPeriph); static bool initFdMode(uint32_t canPeriph); - static void buildStatusFrame(const StatusPayload *status); - static bool enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status); static Diag s_diag; static RxQueue s_rxQueues[3]; @@ -209,6 +207,5 @@ private: }; using canfd_diag_t = CanFd::Diag; -using canfd_status_payload_t = CanFd::StatusPayload; -#endif /* DEVICE_CANFD_H */ \ No newline at end of file +#endif /* DEVICE_CANFD_H */ diff --git a/GD32E508_DCU_APP/main.h b/GD32E508_DCU_APP/main.h index fb9b175..4c47d5c 100644 --- a/GD32E508_DCU_APP/main.h +++ b/GD32E508_DCU_APP/main.h @@ -17,8 +17,7 @@ enum system_task_t { TASK_ADC_PROCESS, /**< 对 ADC 原始值进行电压与温度计算任务。 */ TASK_CAN_RX_POLL, /**< 兼容保留的 CANFD 接收轮询任务。 */ TASK_CAN_RX_PARSE, /**< 解析并消费已接收 CANFD 帧任务。 */ - TASK_CAN_TX_PREPARE, /**< 预留的 CANFD 发送准备任务,默认不主动置位。 */ - TASK_CAN_TX_SEND, /**< 预留的 CANFD 发送执行任务,默认不主动置位。 */ + TASK_CAN_TX_SEND, /**< CANFD 周期发送任务,每 1ms 每通道发送一帧。 */ TASK_STATUS_LED, /**< 状态灯维护任务。 */ TASK_HEARTBEAT, /**< 系统心跳维护任务。 */ TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */ @@ -46,8 +45,10 @@ struct task_scheduler_t { volatile uint16_t div_temp_sample; /** @brief 调度分频计数:CAN 接收处理。 */ volatile uint16_t div_can_rx; - /** @brief 调度分频计数:状态灯。 */ + /** @brief 调度分频计数:CAN 周期发送(1ms)。 */ volatile uint16_t div_can_tx; + /** @brief 调度分频计数:状态灯(100ms)。 */ + volatile uint16_t div_status_led; /** @brief 调度分频计数:心跳与诊断打印。 */ volatile uint16_t div_heartbeat; }; diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index 07c2a00..9ace2f8 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -73,6 +73,12 @@ constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR = 0xE2U; constexpr uint32_t CONFIG_SPI_READY_PORT = GPIOB; /** @brief SPI Ready 握手输出引脚。 */ constexpr uint32_t CONFIG_SPI_READY_PIN = GPIO_PIN_6; + +/** @brief ESP32 硬件复位控制端口(PC9 接 ESP32 EN/RESET)。 */ +constexpr uint32_t CONFIG_ESP32_RESET_PORT = GPIOC; +/** @brief ESP32 硬件复位控制引脚。 */ +constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9; + /* ============================================================ * CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用) * 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) @@ -108,6 +114,11 @@ constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ; * @note 保留 #define:canfd.cpp 中用 #if 做编译期分支。 */ #define CONFIG_CAN_FD_BRS_ENABLE 1U +/** @brief 启动时向物理总线发送 classic/FD/BRS 探测帧;正常固件默认关闭。 */ +#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0 +/** @brief 启动时切换控制器到内部回环执行自检;正常固件默认关闭。 */ +#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0 + /** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */ constexpr bool CONFIG_CAN_CH0_ENABLE = true; /** @brief CAN1 通道使能开关。 */ @@ -121,11 +132,6 @@ constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID; constexpr uint32_t CONFIG_CAN_RX_FILTER_MASK = 0x7FFU; /** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数。 */ constexpr uint32_t CONFIG_CAN_RX_PARSE_BUDGET = 48U; -/** @brief CAN 状态载荷中调试标识字符 0~3。 */ -constexpr uint8_t CONFIG_CAN_STATUS_TAG0 = 0x43U; -constexpr uint8_t CONFIG_CAN_STATUS_TAG1 = 0x41U; -constexpr uint8_t CONFIG_CAN_STATUS_TAG2 = 0x4EU; -constexpr uint8_t CONFIG_CAN_STATUS_TAG3 = 0x46U; /** @brief 用户调度定时器实例。 */ constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2; @@ -151,8 +157,10 @@ constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U; constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U; /** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */ constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U; -/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */ -constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 1000U; +/** @brief 调度基准节拍:CAN 周期发送任务周期,10 tick = 1ms。 */ +constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 10U; +/** @brief 调度基准节拍:状态灯任务周期,1000 tick = 100ms。 */ +constexpr uint32_t CONFIG_TASK_PERIOD_STATUS_LED_TICKS = 1000U; /** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */ constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U; diff --git a/GD32E508_DCU_APP/sys/initial.cpp b/GD32E508_DCU_APP/sys/initial.cpp index 475b74b..6f5462d 100644 --- a/GD32E508_DCU_APP/sys/initial.cpp +++ b/GD32E508_DCU_APP/sys/initial.cpp @@ -34,6 +34,10 @@ void AppInitializer::init() nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP); printf("[APP] init: SysTick...\r\n"); SysTickTimer::initMs(); + /* ESP32 EN/RESET (PC9) 直接配置为推挽输出高电平,保持 ESP32 正常运行 */ + rcu_periph_clock_enable(RCU_GPIOC); + gpio_bit_set(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN); + gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_ESP32_RESET_PIN); printf("[APP] init: USART0...\r\n"); Usart0::configurePins(); Usart0::init(); diff --git a/GD32E508_DCU_APP/sys/task_manager.cpp b/GD32E508_DCU_APP/sys/task_manager.cpp index a9eb309..0a0a335 100644 --- a/GD32E508_DCU_APP/sys/task_manager.cpp +++ b/GD32E508_DCU_APP/sys/task_manager.cpp @@ -31,6 +31,7 @@ void TaskManager::init() g_scheduler.div_temp_sample = 0U; g_scheduler.div_can_rx = 0U; g_scheduler.div_can_tx = 0U; + g_scheduler.div_status_led = 0U; g_scheduler.div_heartbeat = 0U; } @@ -94,8 +95,14 @@ void TaskManager::tick100us() if(++g_scheduler.div_can_tx >= CONFIG_TASK_PERIOD_CAN_TX_TICKS) { g_scheduler.div_can_tx = 0U; + g_scheduler.tick_1ms++; + set(1UL << TASK_CAN_TX_SEND); + } + + if(++g_scheduler.div_status_led >= CONFIG_TASK_PERIOD_STATUS_LED_TICKS) { + g_scheduler.div_status_led = 0U; g_scheduler.tick_100ms++; - set((1UL << TASK_STATUS_LED) | (1UL << TASK_CAN_TX_SEND)); + set(1UL << TASK_STATUS_LED); } if(++g_scheduler.div_heartbeat >= CONFIG_TASK_PERIOD_HEARTBEAT_TICKS) { diff --git a/GD32E508_DCU_APP/sys/task_runner.cpp b/GD32E508_DCU_APP/sys/task_runner.cpp index 7726137..69558a5 100644 --- a/GD32E508_DCU_APP/sys/task_runner.cpp +++ b/GD32E508_DCU_APP/sys/task_runner.cpp @@ -144,54 +144,48 @@ void TaskRunner::taskAdcProcess() void TaskRunner::taskCanRxPoll() { CanFd::poll(); } /** - * @brief 执行预留的 CAN 发送执行任务。 + * @brief 执行 CAN 周期发送任务:每 1ms 在每个使能通道上发送一帧 CANFD 测试帧。 * @param[in] 无。 * @param[out] 无。 * @retval 无返回值。 - * @note 当前阶段不做周期性发送调度;若后续需要显式验证某一路发送能力,可由调用方先 prepare,再直接调用 `CanFd::sendPrepared()`。 + * @note 帧格式:扩展帧 29-bit ID,CAN-FD,DLC=64,BRS 受 CONFIG_CAN_FD_BRS_ENABLE 控制。 + * 载荷布局: + * [0] 固定标识 0xC0 + * [1] 通道号 + * [2..5] 发送序号(小端 uint32) + * [6..7] 系统运行秒数低 16 位 + * [8..63] 填充 0xA0 + (index & 0x0F) + * periodic=true:仅允许一个邮箱保持自动重发,不主动 abort 旧帧, + * 跳过 TEC 保护以匹配诊断行为。 */ void TaskRunner::taskCanTxSend() { - /* 启动后每 100ms 轮流发送一路 CAN 通道的 CANFD+BRS 64 字节测试帧。 - * 三路通道时分复用:CH0→CH1→CH2→CH0→...,每 100ms 只发一路, - * 避免多路同时发送导致总线仲裁冲突(当多路并接到同一总线时)。 - * 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。 - * 载荷布局: - * [0] 通道号 0/1/2 - * [1] 保留 - * [2..5] 发送序号(小端 uint32,每通道独立递增) - * [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32) - * [10..63] 递增填充,按通道偏移便于抓包区分 */ - static uint32_t seq[3] = {0U, 0U, 0U}; - static uint8_t round = 0U; /* 0=CH0, 1=CH1, 2=CH2, 轮流 */ + static uint32_t seq = 0U; - const uint8_t ch = round; - round = (round + 1U) % 3U; + for (uint8_t ch = 0U; ch < 3U; ++ch) { + if (!CanFd::isChannelEnabled(ch)) { + continue; + } - /* 跳过已禁用的通道 */ - if (!CanFd::isChannelEnabled(ch)) { - return; + uint8_t data[64]; + data[0] = 0xC0U; + data[1] = ch; + const uint32_t s = seq; + data[2] = static_cast(s & 0xFFU); + data[3] = static_cast((s >> 8) & 0xFFU); + data[4] = static_cast((s >> 16) & 0xFFU); + data[5] = static_cast((s >> 24) & 0xFFU); + data[6] = static_cast(g_app_uptime_sec & 0xFFU); + data[7] = static_cast((g_app_uptime_sec >> 8) & 0xFFU); + for (uint8_t i = 8U; i < 64U; ++i) { + data[i] = static_cast(0xA0U + (i & 0x0FU)); + } + + const uint32_t extId = 0x18FF5000U | static_cast(ch); + if (CanFd::transmitFrame(ch, extId, data, sizeof(data), true)) { + seq++; + } } - - uint8_t data[64]; - data[0] = ch; - data[1] = 0U; - const uint32_t s = seq[ch]; - data[2] = static_cast(s & 0xFFU); - data[3] = static_cast((s >> 8) & 0xFFU); - data[4] = static_cast((s >> 16) & 0xFFU); - data[5] = static_cast((s >> 24) & 0xFFU); - data[6] = static_cast(g_app_uptime_sec & 0xFFU); - data[7] = static_cast((g_app_uptime_sec >> 8) & 0xFFU); - data[8] = static_cast((g_app_uptime_sec >> 16) & 0xFFU); - data[9] = static_cast((g_app_uptime_sec >> 24) & 0xFFU); - for (uint8_t i = 10U; i < 64U; ++i) { - data[i] = static_cast(i + ch * 0x40U); - } - - const uint32_t extId = 0x1FFFFF00U | static_cast(ch); - CanFd::transmitFrame(ch, extId, data, 64U); - seq[ch]++; } /** @@ -223,18 +217,6 @@ void TaskRunner::taskCanRxParse() } } -/** - * @brief 执行预留的 CAN 发送准备任务。 - * @param[in] 无。 - * @param[out] 无。 - * @retval 无返回值。 - * @note 当前阶段三路 CANFD 仅要求底层收发能力可用,不做周期性业务状态广播。 - */ -void TaskRunner::taskCanTxPrepare() -{ - /* 当前阶段不做周期性业务状态广播。 */ -} - /** * @brief 执行状态灯维护任务。 * @param[in] 无。 diff --git a/GD32E508_DCU_APP/sys/task_runner.h b/GD32E508_DCU_APP/sys/task_runner.h index 559afd9..79ce7b5 100644 --- a/GD32E508_DCU_APP/sys/task_runner.h +++ b/GD32E508_DCU_APP/sys/task_runner.h @@ -39,9 +39,7 @@ private: static void taskCanRxPoll(); /** @brief 解析并消费接收帧。 */ static void taskCanRxParse(); - /** @brief 预留的 CAN 发送准备任务;当前不主动生成周期业务帧。 */ - static void taskCanTxPrepare(); - /** @brief 预留的 CAN 发送执行任务;当前仅保留接口,不参与周期调度。 */ + /** @brief CAN 周期发送任务;每 1ms 每通道发送一帧测试报文。 */ static void taskCanTxSend(); /** @brief 状态灯维护任务(当前预留)。 */ static void taskStatusLed();