From 6aa21a1f9deb783d14a769662f7caf0f5caf6194 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Thu, 2 Jul 2026 02:02:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E6=B7=BB=E5=8A=A0CANFD=E8=87=AA?= =?UTF-8?q?=E6=A3=80=E5=8A=9F=E8=83=BD=E5=92=8C=E6=A1=A5=E6=8E=A5=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加完整的CANFD初始化流程,包括硬复位机制解决CAN1卡死问题 - 实现CANFD自检功能,包含物理总线测试和内部回环测试 - 支持CAN0到CAN1/CAN2的透传桥接模式 - 添加详细的初始化诊断信息输出 - 修复CAN滤波器配置和GPIO映射问题 - 增强错误处理和状态监控功能 perf(system): 优化系统时钟配置提高性能 - 将AHB时钟从SYSCLK/4调整为SYSCLK/2 (60MHz) - 优化PLL预分频配置参数 - 更新相关时序注释以反映实际频率变化 docs(esc): 更新EXMC总线时序配置注释 - 添加LAN9252 HBI时序要求详细说明 - 注明当前ESC未启用状态下的占位符说明 - 提供实际计算得出的HCLK延迟要求 --- .../RTE/Device/GD32E508VE/system_gd32e50x.c | 20 +- GD32E508_DCU_APP/comm/esc_exmc_bus.cpp | 11 +- GD32E508_DCU_APP/device/CANFD.cpp | 509 +++++++++++++++++- GD32E508_DCU_APP/device/canfd.h | 10 +- .../device/spi/app_spi_cmd_can_feedback.cpp | 11 +- .../device/spi/app_spi_command.cpp | 14 + GD32E508_DCU_APP/device/spi/app_spi_command.h | 3 +- GD32E508_DCU_APP/sys/config.h | 46 +- GD32E508_DCU_APP/sys/hardfault.cpp | 4 +- GD32E508_DCU_APP/sys/task_manager.cpp | 2 +- GD32E508_DCU_APP/sys/task_runner.cpp | 35 +- 11 files changed, 608 insertions(+), 57 deletions(-) diff --git a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c index f2c4030..68212f9 100644 --- a/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c +++ b/GD32E508_DCU_APP/RTE/Device/GD32E508VE/system_gd32e50x.c @@ -1,4 +1,4 @@ -/*! +/*! \file system_gd32e50x.c \brief CMSIS Cortex-M33 Device Peripheral Access Layer Source File for GD32E50x Device Series @@ -850,11 +850,11 @@ static void system_clock_120m_hxtal(void) RCU_APB1EN |= RCU_APB1EN_PMUEN; /* HXTAL is stable */ - /* AHB = SYSCLK/4 */ - RCU_CFG0 |= RCU_AHB_CKSYS_DIV4; - /* APB2 = AHB/1 */ + /* AHB = SYSCLK/2 (60 MHz) */ + RCU_CFG0 |= RCU_AHB_CKSYS_DIV2; + /* APB2 = AHB/1 (60 MHz) */ RCU_CFG0 |= RCU_APB2_CKAHB_DIV1; - /* APB1 = AHB/2 */ + /* APB1 = AHB/2 (30 MHz) */ RCU_CFG0 |= RCU_APB1_CKAHB_DIV2; #if defined(GD32E50X_HD) @@ -867,13 +867,13 @@ static void system_clock_120m_hxtal(void) RCU_CFG0 |= RCU_PLL_MUL30; #elif (defined(GD32E50X_CL) || defined(GD32E508)) - /* CK_PLL = (CK_PREDIV0) * 30 = 120 MHz */ + /* CK_PLL = (CK_PREDIV0) * 30 = 120 MHz */ RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5); RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL30); - /* CK_PREDIV0 = (CK_HXTAL)/5 *8 /10 = 4 MHz */ + /* CK_PREDIV0 = (CK_HXTAL)/2 *8 /8 = 4 MHz */ RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0); - RCU_CFG1 |= (RCU_PLLPRESRC_HXTAL | RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV5 | RCU_PREDV0_DIV10); + RCU_CFG1 |= (RCU_PLLPRESRC_HXTAL | RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV2 | RCU_PREDV0_DIV8); /* enable PLL1 */ RCU_CTL |= RCU_CTL_PLL1EN; @@ -882,11 +882,11 @@ static void system_clock_120m_hxtal(void) } #elif defined(GD32EPRT) - /* CK_PLL = (CK_PREDIV0) * 30 = 120 MHz */ + /* CK_PLL = (CK_PREDIV0) * 30 = 120 MHz */ RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLMF_4 | RCU_CFG0_PLLMF_5); RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M | RCU_PLL_MUL30); - /* CK_PREDIV0 = (CK_HXTAL)/2 *8 /8 = 4 MHz */ + /* CK_PREDIV0 = (CK_HXTAL)/2 *8 /8 = 4 MHz */ RCU_CFG1 &= ~(RCU_CFG1_PLLPRESEL | RCU_CFG1_PREDV0SEL | RCU_CFG1_PLL1MF | RCU_CFG1_PREDV1 | RCU_CFG1_PREDV0); RCU_CFG1 |= (RCU_PLLPRESRC_HXTAL | RCU_PREDV0SRC_CKPLL1 | RCU_PLL1_MUL8 | RCU_PREDV1_DIV2 | RCU_PREDV0_DIV8); diff --git a/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp b/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp index b6bdede..c690ff7 100644 --- a/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp +++ b/GD32E508_DCU_APP/comm/esc_exmc_bus.cpp @@ -60,9 +60,14 @@ void EscExmcBus::init() norsram_init.asyn_wait = DISABLE; norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW; norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B; - /* 时序:按 LAN9252 HBI 要求配置 read/write wait state */ - norsram_init.read_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整 - norsram_init.write_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整 + /* 时序:按 LAN9252 HBI 要求配置 read/write wait state + * LAN9252 HBI 最小 read/write cycle = 100ns。 + * AHB=180MHz(RCU_MODIFY_UP 强制 /1),1 HCLK = 5.6ns。 + * 6 HCLK = 33ns → 不满足,需 ≥ 18 HCLK 才能达到 100ns。 + * 当前 ESC 未启用(selfTest FAIL),EXMC 库未纳入编译,此参数仅占位。 + * 启用 ESC 时需安装 EXMC 库并实测调整 HCLK 数。 */ + norsram_init.read_latency = EXMC_DEN_ALE_TR_6_HCLK; // 占位,启用 ESC 时需调整为 ≥18 HCLK + norsram_init.write_latency = EXMC_DEN_ALE_TR_6_HCLK; // 占位,启用 ESC 时需调整为 ≥18 HCLK exmc_norsram_init(&norsram_init); exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1); #else diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index e853e2b..f6f1148 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -1,4 +1,4 @@ -/** +/** * @file canfd.cpp * @brief CANFD 通信模块实现。 */ @@ -37,14 +37,109 @@ struct CanChannelConfig { }; 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 }, + { 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, 14U }, + { 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); + +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); + + for (volatile uint32_t dly = 0; dly < 40000U; ++dly) { } + + 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 >> 8) & 0xFFU; + result.busOff = (result.stat & (1U << 8)) != 0U; + return result; +} + +/** + * @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; + for (volatile uint32_t d = 0; d < 100000U; ++d) {} + + /* 2. 确认 SWRST 已被硬件自动清零;若未清零则强制写 0 并等待 */ + if (CAN_CTL(canPeriph) & CAN_CTL_SWRST) { + CAN_CTL(canPeriph) &= ~CAN_CTL_SWRST; + for (volatile uint32_t d = 0; d < 50000U; ++d) {} + } + + /* 3. 关 RCU 时钟,等待外设域完全放电 */ + rcu_periph_clock_disable(canRcu); + for (volatile uint32_t d = 0; d < 200000U; ++d) {} /* ~1.1ms @180MHz */ + + /* 4. 重新开时钟,等待外设域上电稳定 */ + rcu_periph_clock_enable(canRcu); + for (volatile uint32_t d = 0; d < 200000U; ++d) {} /* ~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{}; @@ -70,6 +165,15 @@ void CanFd::initGpio() rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_AF); +#ifdef GPIO_CAN1_REMAP + gpio_pin_remap_config(GPIO_CAN1_REMAP, ENABLE); +#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 for(size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; @@ -95,7 +199,7 @@ void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mod can_filter.filter_number = filterNumber; can_filter.filter_mode = CAN_FILTERMODE_MASK; can_filter.filter_bits = CAN_FILTERBITS_32BIT; - if (mode == Mode::kModeAllPass) { + if (mode == Mode::kModeAllPass || mode == Mode::kModeBridge0To12) { /* 全部接收:列表与掩码都为 0,硬件不再对 ID 做比较。 */ can_filter.filter_list_high = 0x0000U; can_filter.filter_list_low = 0x0000U; @@ -117,9 +221,10 @@ void CanFd::configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mod * @param[in] canPeriph 目标 CAN 外设实例。 * @param[out] 无。 * @retval 无返回值。 - * @note 当前按 1 Mbps 仲裁段参数完成普通 CAN 初始化。 + * @note 时钟源为 APB1=90MHz;按 CONFIG_CAN_NOMINAL_* 宏配置,目标 1 Mbps。 + * 实际波特率 = 90MHz / (CONFIG_CAN_NOMINAL_PRESCALER × (1+BS1+BS2))。 */ -void CanFd::initNominalTiming(uint32_t canPeriph) +bool CanFd::initNominalTiming(uint32_t canPeriph) { can_parameter_struct can_parameter{}; @@ -131,11 +236,11 @@ void CanFd::initNominalTiming(uint32_t canPeriph) 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); + 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; } /** @@ -143,9 +248,11 @@ void CanFd::initNominalTiming(uint32_t canPeriph) * @param[in] canPeriph 目标 CAN 外设实例。 * @param[out] 无。 * @retval 无返回值。 - * @note 在 GD32E508 平台下启用 FD、BRS 与 TDC;非该平台则仅使能 FD 功能。 + * @note 时钟源为 APB1=90MHz;按 CONFIG_CAN_DATA_* 宏配置,目标 2.0 Mbps。 + * 实际数据段波特率 = 90MHz / (CONFIG_CAN_DATA_PRESCALER × (1+BS1+BS2))。 + * BRS 受 CONFIG_CAN_FD_BRS_ENABLE 控制。 */ -void CanFd::initFdMode(uint32_t canPeriph) +bool CanFd::initFdMode(uint32_t canPeriph) { #ifdef GD32E508 can_fdframe_struct can_fd_parameter{}; @@ -161,13 +268,14 @@ void CanFd::initFdMode(uint32_t canPeriph) 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); + 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 } @@ -228,16 +336,13 @@ bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *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_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; -#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; @@ -275,10 +380,82 @@ void CanFd::init() 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); + 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; + } + + printf("[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)); + } + + /* GD32E508: CAN0/CAN1 共享 28 个滤波器 bank,必须显式设置 CAN1 起始 bank */ + if (ch.canPeriph == CAN1) { + can1_filter_start_bank(14U); /* CAN0: bank 0-13, CAN1: bank 14-27 */ + } + configureRxFilter(ch.canPeriph, ch.filterNumber, s_mode); + + /* 退出 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; + for (volatile uint32_t d = 0; d < 50000U; ++d) {} + 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; + } + printf("[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 >> 8) & 0xFFU; + printf("[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, + ((statAfterExit & CAN_STAT_IWS) == 0U) ? 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_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0); nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast(i)); @@ -291,6 +468,178 @@ void CanFd::init() } s_nextConsumeChannel = 0U; + + /* ===== 初始化结果汇总 ===== */ + printf("=== 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; + printf("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; + printf("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)); + } + + /* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */ + printf("=== CAN SELF-TEST (normal mode) ===\r\n"); + for (size_t i = 0; i < kChannelCount; ++i) { + 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); + printf("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 + printf("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 + } + + /* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */ + printf("=== CAN LOOPBACK (internal) ===\r\n"); + for (size_t i = 0; i < kChannelCount; ++i) { + const auto &ch = kChannels[i]; + + if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) { + printf("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) { + printf("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; + printf("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)); + + /* 回到 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); /* 清 LCMOD+SCMOD */ + 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; } + } + printf("========================\r\n"); } /** @@ -481,7 +830,11 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, 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 for (uint8_t i = 0U; i < dlc; ++i) { @@ -559,6 +912,54 @@ void CanFd::setSilentMode() } } +/** + * @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)。 @@ -574,9 +975,11 @@ void CanFd::handleRxInterrupt(uint8_t 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++; } @@ -595,6 +998,23 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex) 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.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++; + } + } + } /* 维护每通道独立计数与最近一帧快照,用于 1Hz 反馈给 ESP32。 */ auto &stats = s_channelStats[channelIndex]; @@ -706,14 +1126,45 @@ void CanFd::onRx0Interrupt() */ void CanFd::refreshFeedbackSnapshot() { - uint32_t save = RT_CRITICAL_SAVE(); + /* 先在临界区内采集硬件寄存器,确保三通道快照一致 */ + 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++; 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; } - RT_CRITICAL_RESTORE(save); } /** diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index fa80a33..13b34c5 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -1,4 +1,4 @@ -#ifndef DEVICE_CANFD_H +#ifndef DEVICE_CANFD_H #define DEVICE_CANFD_H #include @@ -28,6 +28,7 @@ public: enum class Mode : uint8_t { kModeAllPass = 0U, /**< 全部接收模式:禁用硬件 ID/掩码匹配,所有 11/29 位帧都进入 FIFO0。 */ kModeFilterId = 1U, /**< 过滤模式:按 CONFIG_CAN_RX_FILTER_ID/MASK 做标准帧掩码匹配。 */ + kModeBridge0To12 = 2U, /**< CAN0 桥接模式:硬件过滤等同 AllPass,CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2。 */ }; /** @@ -66,6 +67,9 @@ public: uint8_t last_rx_brs; /**< 该通道最近一帧 BRS 标志。 */ uint8_t last_rx_esi; /**< 该通道最近一帧 ESI 标志。 */ uint8_t last_rx_data[CANFD_FEEDBACK_LAST_DATA_MAX]; + uint8_t tec; /**< 最近一次采样时硬件发送错误计数器 (0~255)。 */ + uint8_t rec; /**< 最近一次采样时硬件接收错误计数器 (0~255)。 */ + uint8_t bus_state; /**< 总线状态位图:bit0=init_mode, bit1=sleep, bit2=error_passive, bit3=bus_off, bit4=error_warning。 */ }; /** @@ -182,8 +186,8 @@ private: * @param[in] mode 目标接收过滤模式。 */ static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode); - static void initNominalTiming(uint32_t canPeriph); - static void initFdMode(uint32_t canPeriph); + 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); diff --git a/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp b/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp index 0066f0e..61ac575 100644 --- a/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_cmd_can_feedback.cpp @@ -19,7 +19,10 @@ * [13..16] tx_count (LE) 通道累计发送 * [17..20] drop_count (LE) * [21..24] error_count (LE) - * [25..] 条目逐条拼接: + * [25] tec 硬件发送错误计数器 (0~255) + * [26] rec 硬件接收错误计数器 (0~255) + * [27] bus_state 总线状态位图:bit0=init,bit1=sleep,bit2=error_passive,bit3=bus_off,bit4=error_warning + * [28..] 条目逐条拼接: * id(4LE) + dlc(1) + flags(1) + reserved(2) + data(dlc) * 每条 = 8 + dlc 字节 * @@ -43,7 +46,7 @@ namespace { constexpr uint8_t kFeedbackChannelCount = 3U; constexpr uint16_t kFeedbackPayloadLen = 123U; /* kTxBufSize=128 - overhead=5 */ constexpr uint8_t kPageEntries = 8U; -constexpr uint16_t kFixedHeaderLen = 25U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4) */ +constexpr uint16_t kFixedHeaderLen = 28U; /* ch(1)+page(1)+total(1)+cnt(1)+mode(1)+seq(4)+rx(4)+tx(4)+drop(4)+err(4)+tec(1)+rec(1)+bus_state(1) */ void writeU32Le(uint8_t *dst, uint32_t value) { @@ -119,6 +122,9 @@ private: writeU32Le(&payload[13], snap.stats.tx_count); writeU32Le(&payload[17], snap.stats.rx_drop_count); writeU32Le(&payload[21], snap.stats.error_count); + payload[25] = snap.stats.tec; + payload[26] = snap.stats.rec; + payload[27] = snap.stats.bus_state; /* 打包 ID 条目 */ uint16_t offset = kFixedHeaderLen; @@ -170,6 +176,7 @@ private: switch (requested) { case 0U: target = CanFd::Mode::kModeAllPass; break; case 1U: target = CanFd::Mode::kModeFilterId; break; + case 2U: target = CanFd::Mode::kModeBridge0To12; break; default: { uint8_t err = 0xE1U; bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len, diff --git a/GD32E508_DCU_APP/device/spi/app_spi_command.cpp b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp index 5486d03..56d864a 100644 --- a/GD32E508_DCU_APP/device/spi/app_spi_command.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp @@ -313,6 +313,7 @@ void AppSpiCommand::init() m_txLen = 0U; m_txPos = 0U; m_logPending = false; + m_txActiveThisCs = false; m_commandCount = 0U; memset(m_txBuf, 0xFF, sizeof(m_txBuf)); memset(m_logBuf, 0, sizeof(m_logBuf)); @@ -495,6 +496,7 @@ void AppSpiCommand::onCsAssert() m_csActive = true; m_rxLen = 0U; m_txPos = 0U; + m_txActiveThisCs = false; /* 停止 DMA 并清理旧状态 */ DMA_CHCTL(SPI_TX_DMA_PERIPH, SPI_TX_DMA_CH) &= ~DMA_CHXCTL_CHEN; @@ -516,6 +518,7 @@ void AppSpiCommand::onCsAssert() /* 启动 DMA 发送响应帧(与 BOOT 完全一致) */ if (m_txLen > 0U) { + m_txActiveThisCs = true; spi_dma_enable(SPI0, SPI_DMA_TRANSMIT); startSpiTxDma(m_txBuf, m_txLen); } else { @@ -545,8 +548,19 @@ void AppSpiCommand::onCsRelease() if (shouldIgnoreTransaction(m_rxBuf, m_rxLen)) { m_rxLen = 0U; + if (m_txActiveThisCs) { + m_txLen = 0U; + m_txPos = 0U; + memset(m_txBuf, 0xFF, sizeof(m_txBuf)); + } + m_txActiveThisCs = false; return; } + + m_txLen = 0U; + m_txPos = 0U; + m_txActiveThisCs = false; + memset(m_txBuf, 0xFF, sizeof(m_txBuf)); m_cmdPending = true; TaskManager::set(1UL << TASK_SPI_CMD_PROCESS); } diff --git a/GD32E508_DCU_APP/device/spi/app_spi_command.h b/GD32E508_DCU_APP/device/spi/app_spi_command.h index 558bbca..b2e0b43 100644 --- a/GD32E508_DCU_APP/device/spi/app_spi_command.h +++ b/GD32E508_DCU_APP/device/spi/app_spi_command.h @@ -1,4 +1,4 @@ -#ifndef DEVICE_APP_SPI_COMMAND_H +#ifndef DEVICE_APP_SPI_COMMAND_H #define DEVICE_APP_SPI_COMMAND_H #include @@ -70,6 +70,7 @@ private: volatile uint8_t m_txPos = 0U; volatile bool m_logPending = false; volatile bool m_cmdPending = false; /**< ISR 置位,标记有待主循环解析的 SPI 命令。 */ + bool m_txActiveThisCs = false; uint8_t m_rxBuf[kRxBufSize] = {}; uint8_t m_txBuf[kTxBufSize] = {}; char m_logBuf[kLogBufSize] = {}; diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index 4e6a91c..7b5665d 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -60,6 +60,40 @@ #define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xB5U /** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */ #define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U +/* ============================================================ + * CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用) + * 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) + * → APB1=/2=90MHz (CAN_CLK) + * + * 仲裁段目标: 1.0 Mbps = 90MHz / (9 × 10 TQ) + * 数据段目标: 2.0 Mbps = 90MHz / (9 × 5 TQ) + * + * @note RCU_MODIFY_UP 宏在 system_clock_180m_hxtal() 末尾将 AHB 从 /4 + * 临时切到 /1,这是 GigaDevice 官方行为,不可删除。 + * 最终 AHB=180MHz, APB1=90MHz, APB2=180MHz, TIMER2_CLK=180MHz。 + * ============================================================ */ + +/** @brief 仲裁段预分频值。 */ +#define CONFIG_CAN_NOMINAL_PRESCALER 9U +/** @brief 仲裁段再同步跳跃宽度。 */ +#define CONFIG_CAN_NOMINAL_SJW CAN_BT_SJW_1TQ +/** @brief 仲裁段时间段 1(PHSEG1)。 */ +#define CONFIG_CAN_NOMINAL_BS1 CAN_BT_BS1_7TQ +/** @brief 仲裁段时间段 2(PHSEG2)。 */ +#define CONFIG_CAN_NOMINAL_BS2 CAN_BT_BS2_2TQ + +/** @brief 数据段预分频值。 */ +#define CONFIG_CAN_DATA_PRESCALER 9U +/** @brief 数据段再同步跳跃宽度。 */ +#define CONFIG_CAN_DATA_SJW CAN_BT_SJW_1TQ +/** @brief 数据段时间段 1。 */ +#define CONFIG_CAN_DATA_BS1 CAN_BT_BS1_3TQ +/** @brief 数据段时间段 2。 */ +#define CONFIG_CAN_DATA_BS2 CAN_BT_BS2_1TQ + +/** @brief BRS(比特率切换)使能开关:1=启用数据段加速,0=关闭仅经典 CAN。 */ +#define CONFIG_CAN_FD_BRS_ENABLE 1U + /** @brief CAN 接收过滤器目标标准帧 ID。 */ #define CONFIG_CAN_RX_FILTER_ID CONFIG_CAN_STATUS_ID /** @brief CAN 接收过滤器标准帧掩码;`0x7FFU` 表示精确匹配 11-bit ID。 */ @@ -81,7 +115,10 @@ #define CONFIG_SCHED_TIMER_IRQn TIMER2_IRQn /** @brief 用户调度定时器时钟使能宏参数。 */ #define CONFIG_SCHED_TIMER_RCU RCU_TIMER2 -/** @brief 用户调度定时器预分频值。 */ +/** @brief 用户调度定时器预分频值。 + * 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) → APB1=/2=90MHz → TIMER2_CLK=APB1×2=180MHz。 + * 保持 100us tick: (PSC+1)×(PER+1)=18000 → 180×100 → PSC=179, PER=99。 + */ #define CONFIG_SCHED_TIMER_PRESCALER 179U /** @brief 用户调度定时器周期重装值。 */ #define CONFIG_SCHED_TIMER_PERIOD 99U @@ -127,7 +164,7 @@ /** @brief APP 主版本号。 */ #define APP_FW_VERSION_MAJOR 1U /** @brief APP 次版本号。 */ -#define APP_FW_VERSION_MINOR 1U +#define APP_FW_VERSION_MINOR 2U /** @brief APP 补丁版本号。 */ #define APP_FW_VERSION_PATCH 2U /** @brief APP 构建编号。 */ @@ -179,8 +216,9 @@ #define CONFIG_ETHERCAT_SYNC_MODE 0 /** @brief periodicIsr 最大执行时间告警阈值(µs)。实测超过此值需评估减小 PDO 大小或优化 EXMC 时序。 - * 当前阈值 5µs:基于 32 字节 PDO + EXMC 6 HCLK 时序的估算上限。 - * preempt=0 ISR 阻塞窗口 ≤ 此阈值时,CAN/SPI 中断需容忍。 */ + * 当前阈值 5µs:基于 180MHz + 32 字节 PDO + EXMC 6 HCLK 时序的估算上限。 + * preempt=0 ISR 阻塞窗口 ≤ 此阈值时,CAN/SPI 中断需容忍。 + * @note 启用 ESC 后需根据实测 HCLK 数重新评估。 */ #define CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD 5U /** @brief 输入 PDO(TxPDO)在 PDI 内存区的起始偏移。 diff --git a/GD32E508_DCU_APP/sys/hardfault.cpp b/GD32E508_DCU_APP/sys/hardfault.cpp index 20b32b1..b2b2c19 100644 --- a/GD32E508_DCU_APP/sys/hardfault.cpp +++ b/GD32E508_DCU_APP/sys/hardfault.cpp @@ -1,4 +1,4 @@ -/** +/** * @file hardfault.cpp * @brief HardFault 诊断处理——通过 USART0 输出故障寄存器信息。 * @@ -56,7 +56,7 @@ static uint32_t detectApb2Clock(void) { uint32_t scss = (cfg0 >> 2) & 0x3U; uint32_t sysclk; if (scss == 0x2U) { - sysclk = 180000000U; /* PLL = 180MHz(唯一配置的 PLL 目标频率) */ + sysclk = 180000000U; /* PLL = 180MHz(当前 PLL 目标频率) */ } else { sysclk = 8000000U; /* IRC8M = 8MHz 或 HXTAL(未倍频时视为 8MHz) */ } diff --git a/GD32E508_DCU_APP/sys/task_manager.cpp b/GD32E508_DCU_APP/sys/task_manager.cpp index 7e75625..a9eb309 100644 --- a/GD32E508_DCU_APP/sys/task_manager.cpp +++ b/GD32E508_DCU_APP/sys/task_manager.cpp @@ -95,7 +95,7 @@ void TaskManager::tick100us() if(++g_scheduler.div_can_tx >= CONFIG_TASK_PERIOD_CAN_TX_TICKS) { g_scheduler.div_can_tx = 0U; g_scheduler.tick_100ms++; - set(1UL << TASK_STATUS_LED); + set((1UL << TASK_STATUS_LED) | (1UL << TASK_CAN_TX_SEND)); } 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 06e08db..f58d1f3 100644 --- a/GD32E508_DCU_APP/sys/task_runner.cpp +++ b/GD32E508_DCU_APP/sys/task_runner.cpp @@ -1,4 +1,4 @@ -/** +/** * @file task_runner.cpp * @brief 任务执行器实现。 */ @@ -152,7 +152,37 @@ void TaskRunner::taskCanRxPoll() { CanFd::poll(); } */ void TaskRunner::taskCanTxSend() { - /* 当前阶段不做周期性发送调度。 */ + /* 启动后每 100ms 向三路 CAN 通道发送 CANFD+BRS 64 字节测试帧。 + * 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。 + * 载荷布局: + * [0] 通道号 0/1/2 + * [1] 保留 + * [2..5] 发送序号(小端 uint32,每通道独立递增) + * [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32) + * [10..63] 递增填充 0x00..0x35(固定模式,便于抓包校验) */ + static uint32_t seq[3] = {0U, 0U, 0U}; + + for (uint8_t ch = 0U; ch < 3U; ++ch) { + 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] = i; + } + + const uint32_t extId = 0x1FFFFF00U | static_cast(ch); + CanFd::transmitFrame(ch, extId, data, 64U); + seq[ch]++; + } } /** @@ -357,6 +387,7 @@ void TaskRunner::runPending() if(pending & (1UL << TASK_ADC_PROCESS)) taskAdcProcess(); if(pending & (1UL << TASK_CAN_RX_POLL)) taskCanRxPoll(); if(pending & (1UL << TASK_CAN_RX_PARSE)) taskCanRxParse(); + if(pending & (1UL << TASK_CAN_TX_SEND)) taskCanTxSend(); if(pending & (1UL << TASK_STATUS_LED)) taskStatusLed(); if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat(); if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();