From b78e36ff3780f82d26d967ef7836ecb316c5aa0f Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Mon, 6 Jul 2026 19:13:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E6=B7=BB=E5=8A=A0=E9=80=9A?= =?UTF-8?q?=E9=81=93=E4=BD=BF=E8=83=BD=E6=8E=A7=E5=88=B6=E5=92=8CSPI?= =?UTF-8?q?=E6=8F=A1=E6=89=8B=E4=BF=A1=E5=8F=B7=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加通道使能开关配置,通过sys/config.h中的CONFIG_CAN_CHx_ENABLE控制各通道 - 实现isChannelEnabled()方法用于查询通道使能状态 - 修正CAN1引脚映射配置,将RX/TX引脚对调并禁用重映射 - 修正错误计数器读取逻辑,REC字段从bit24开始提取而非bit8 - 修正busOff状态判断,使用CAN_ERR_BOERR标志位 - 调整滤波器配置时机,移到所有hardReset完成后防止共享寄存器被清零 - 在发送时增加总线健康检查,避免高错误计数时继续发送导致bus off - 添加SPI Ready握手信号(PB6)控制,改善主从机通信同步 fix(spi): 实现SPI命令处理中的READY信号握手协议 - 添加SPI Ready信号控制,PB6输出指示从机就绪状态 - CS下降沿立即拉低READY信号防主机抢跑 - 命令处理完成且无待处理命令时拉高READY信号 - 忽略无效事务后恢复READY信号避免超时 - 在繁忙期间保持READY信号低电平通知主机等待 --- GD32E508_DCU_APP/device/CANFD.cpp | 195 ++++++++++- GD32E508_DCU_APP/device/canfd.h | 7 + .../device/spi/app_spi_command.cpp | 31 +- GD32E508_DCU_APP/device/spi/app_spi_command.h | 8 +- GD32E508_DCU_APP/sys/config.h | 331 ++++++++---------- GD32E508_DCU_APP/sys/task_runner.cpp | 51 +-- 6 files changed, 397 insertions(+), 226 deletions(-) diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index f6f1148..5e252d5 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -1,4 +1,4 @@ -/** +/** * @file canfd.cpp * @brief CANFD 通信模块实现。 */ @@ -38,11 +38,19 @@ 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, 14U }, + { 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 }, }; 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 uint8_t kTxQueueDepth = 4U; constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE); @@ -98,8 +106,8 @@ static CanSelfTestResult runSelfTestFrame(uint8_t channelIndex, uint32_t extId, 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; + result.rec = (result.err >> 24) & 0xFFU; + result.busOff = (result.err & CAN_ERR_BOERR) != 0U; return result; } @@ -152,6 +160,14 @@ uint32_t CanFd::s_feedbackSeq = 0U; CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{}; static TxQueue s_txQueues[3]{}; +/** + * @brief 查询指定通道是否已使能。 + */ +bool CanFd::isChannelEnabled(uint8_t channelIndex) +{ + return (channelIndex < kChannelCount) && kChannelEnabled[channelIndex]; +} + /** * @brief 初始化三路 CANFD 相关 GPIO。 * @param[in] 无。 @@ -166,7 +182,9 @@ void CanFd::initGpio() rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_AF); #ifdef GPIO_CAN1_REMAP - gpio_pin_remap_config(GPIO_CAN1_REMAP, ENABLE); + /* CAN1_REMAP=0: CAN1_RX/PB12, CAN1_TX/PB13 + * GD32E50x_用户手册_Rev2.2.pdf AFIO_PCF0 bit22 */ + gpio_pin_remap_config(GPIO_CAN1_REMAP, DISABLE); #endif #ifdef AFIO_PE0_CAN2_CFG gpio_afio_port_config(AFIO_PE0_CAN2_CFG, ENABLE); @@ -174,6 +192,11 @@ 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。 */ for(size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; @@ -378,6 +401,10 @@ void CanFd::init() for(size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; + if (!kChannelEnabled[i]) { + printf("[CAN] CH%u DISABLED, skip init\r\n", static_cast(i)); + continue; + } 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; @@ -408,12 +435,9 @@ void CanFd::init() 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); + /* 滤波器配置延迟到循环结束后统一执行: + * CAN1 的 hardResetCan 含 SWRST,会清零 CAN0/CAN1 共享滤波器寄存器, + * 若在循环内配 CAN0 filter 后再做 CAN1 hardReset,CAN0 filter 会被破坏。 */ /* 退出 init mode,确保进入 NORMAL 工作模式 */ CAN_CTL(ch.canPeriph) &= ~CAN_CTL_IWMOD; @@ -441,7 +465,7 @@ void CanFd::init() 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; + const uint32_t recAfterExit = (errAfterExit >> 24) & 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, @@ -469,6 +493,20 @@ void CanFd::init() s_nextConsumeChannel = 0U; + /* ===== 统一配置滤波器(必须在所有 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); + } + printf("[CAN] filters configured post-init: HBC1F=%lu FW=0x%08lX\r\n", + static_cast((CAN_FCTL(CAN0) >> 8) & 0x3FU), + static_cast(CAN_FW(CAN0))); + /* ===== 初始化结果汇总 ===== */ printf("=== CAN INIT RESULT ===\r\n"); for (size_t i = 0; i < kChannelCount; ++i) { @@ -498,9 +536,30 @@ void CanFd::init() static_cast((fw2 >> 2) & 1U)); } + /* ===== 初始化后错误计数器快照(不发帧,避免启动时累积 TEC)===== */ + printf("=== 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; + printf("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 */ + } + /* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */ printf("=== CAN SELF-TEST (normal mode) ===\r\n"); for (size_t i = 0; i < kChannelCount; ++i) { + if (!kChannelEnabled[i]) { + printf("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]; @@ -525,11 +584,17 @@ void CanFd::init() #endif } + /* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */ printf("=== CAN LOOPBACK (internal) ===\r\n"); for (size_t i = 0; i < kChannelCount; ++i) { const auto &ch = kChannels[i]; + if (!kChannelEnabled[i]) { + printf("CH%u: SKIP(disabled)\r\n", static_cast(i)); + continue; + } + if (CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) { printf("CH%u: SKIP(stuck_init)\r\n", static_cast(i)); continue; @@ -632,14 +697,74 @@ void CanFd::init() brsOk ? "PASS" : "FAIL", static_cast(tecLb)); - /* 回到 normal */ + /* 回环前后寄存器快照,用于诊断 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 < 100000U; ++t) { if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U) break; } + 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); + printf("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))); } printf("========================\r\n"); + + /* ===== 初始化后全寄存器快照:诊断 CAN0/CAN1 不收帧 ===== */ + printf("=== 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); + printf("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)); + + /* 逐位解析关键标志 */ + printf(" 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 */ + } + printf("===============================\r\n"); } /** @@ -818,10 +943,22 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t 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) { + if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] + || data == nullptr || dlc == 0U || dlc > 64U) { return false; } + /* 总线健康检查:BUS_OFF 或 TEC 过高时跳过发送,防止测试帧把通道推向 BUS_OFF */ + const uint32_t canPeriph = kChannels[channelIndex].canPeriph; + const uint32_t errReg = CAN_ERR(canPeriph); + if (errReg & CAN_ERR_BOERR) { + return false; /* 通道处于 BUS_OFF,不发送 */ + } + const uint32_t tec = (errReg >> 16) & 0xFFU; + if (tec > 96U) { + return false; /* TEC 过高(>75% 错误被动阈值),暂停发送保护通道 */ + } + can_trasnmit_message_struct tx_msg{}; can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg); tx_msg.tx_efid = ext_id; @@ -841,7 +978,6 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id, 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 中断链路,不能等待 @@ -969,7 +1105,7 @@ static bool bridgeTransmitRaw(uint32_t canPeriph, const can_receive_message_stru */ void CanFd::handleRxInterrupt(uint8_t channelIndex) { - if(channelIndex >= kChannelCount) return; + 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); @@ -1165,8 +1301,33 @@ void CanFd::refreshFeedbackSnapshot() 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"; + printf("[CAN-BUS] CH%u: %s -> %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_tec[i]), + static_cast(hw_rec[i]), + static_cast(s_feedbackSeq)); + s_prevBusState[i] = cur; + } + } } + /** * @brief 拷贝指定通道的最新反馈快照。 * @param[in] channelIndex 目标 CAN 通道号(0/1/2)。 diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index 13b34c5..7c343b6 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -116,6 +116,13 @@ public: static void init(); + /** + * @brief 查询指定通道是否已使能。 + * @param[in] channelIndex 通道索引 0/1/2。 + * @retval bool true=使能,false=禁用。 + */ + static bool isChannelEnabled(uint8_t channelIndex); + /** * @brief 切换 CANFD 接收过滤模式。 */ diff --git a/GD32E508_DCU_APP/device/spi/app_spi_command.cpp b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp index 56d864a..61bec04 100644 --- a/GD32E508_DCU_APP/device/spi/app_spi_command.cpp +++ b/GD32E508_DCU_APP/device/spi/app_spi_command.cpp @@ -1,4 +1,4 @@ -/** +/** * @file app_spi_command.cpp * @brief APP 运行期 SPI 链路与命令路由实现。 * @@ -237,6 +237,19 @@ void AppSpiCommand::configureGpio() gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3 | GPIO_PIN_5); gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_4); gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_15); + + /* PB6 复用为 SPI Ready 握手输出,默认 HIGH(就绪),允许主机立刻发起首帧 */ + gpio_init(CONFIG_SPI_READY_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_SPI_READY_PIN); + gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); +} + +void AppSpiCommand::setSpiReady(bool ready) +{ + if (ready) { + gpio_bit_set(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); + } else { + gpio_bit_reset(CONFIG_SPI_READY_PORT, CONFIG_SPI_READY_PIN); + } } void AppSpiCommand::configureSpi0() @@ -451,6 +464,13 @@ void AppSpiCommand::processCommand() /* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */ TaskManager::set(1UL << TASK_SPI_LOG); } + + /* 应答帧已就绪:若解析期间未再收到新帧(m_cmdPending 仍为 false), + * 拉高 PB6 通知主机可发起下一笔事务;否则保持 LOW,等待下一轮 + * processCommand 完成后再翻高,避免主机在从机未就绪时抢跑。 */ + if (!m_cmdPending) { + setSpiReady(true); + } } /* ============================================================ @@ -494,6 +514,10 @@ void AppSpiCommand::onCsAssert() return; } m_csActive = true; + /* CS 下降沿立即拉低 PB6:表示从机即将忙碌, + * 防止主机在 onCsRelease 拉低 PB6 前抢跑发起下一笔 CS。 + * (若此帧被 shouldIgnoreTransaction 忽略,onCsRelease 会恢复 HIGH) */ + setSpiReady(false); m_rxLen = 0U; m_txPos = 0U; m_txActiveThisCs = false; @@ -554,6 +578,9 @@ void AppSpiCommand::onCsRelease() memset(m_txBuf, 0xFF, sizeof(m_txBuf)); } m_txActiveThisCs = false; + /* 忽略事务(空轮询/残包)后恢复 PB6=HIGH, + * 避免 PB6 卡 LOW 导致 ESP32 waitSpiReady 超时 */ + setSpiReady(true); return; } @@ -562,6 +589,8 @@ void AppSpiCommand::onCsRelease() m_txActiveThisCs = false; memset(m_txBuf, 0xFF, sizeof(m_txBuf)); m_cmdPending = true; + /* 拉低 PB6 通知主机:从机正忙,在主循环解析完前不要再发起 CS */ + setSpiReady(false); 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 b2e0b43..5b8c4ab 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 @@ -84,6 +84,12 @@ private: void onCsAssert(); void onCsRelease(); void processCommand(); + + /** @brief 翻转 SPI Ready 握手引脚(PB6)电平。 + * @param[in] ready true=就绪(HIGH), false=忙(LOW)。 + * @note GPIO BSRR/BRR 写入原子,无需临界区;可由 ISR 与主循环共同调用。 + */ + void setSpiReady(bool ready); }; /** @brief 全局 SPI 命令总线实例。 */ diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index 7b5665d..f253846 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -1,4 +1,4 @@ -/** +/** * @file config.h * @brief 应用级集中配置项定义。 * @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。 @@ -6,60 +6,73 @@ #ifndef SYS_CONFIG_H #define SYS_CONFIG_H +#include +#include "gd32e50x.h" + /** @brief 应用启动横幅字符串。 */ -#define APP_BANNER_TEXT "GD32E508 DCU task scheduler start" +constexpr const char *APP_BANNER_TEXT = "GD32E508 DCU task scheduler start"; + +/* ============================================================ + * APP 固件版本号 + * ============================================================ */ +constexpr uint32_t APP_FW_VERSION_MAJOR = 1U; +constexpr uint32_t APP_FW_VERSION_MINOR = 2U; +constexpr uint32_t APP_FW_VERSION_PATCH = 3U; +constexpr uint32_t APP_FW_VERSION_BUILD = 1U; + /** @brief USART0 默认波特率。 */ -#define CONFIG_USART0_BAUDRATE 115200U +constexpr uint32_t CONFIG_USART0_BAUDRATE = 115200U; /** @brief ADC 外设实例。 */ -#define CONFIG_ADC_INSTANCE ADC0 +constexpr uint32_t CONFIG_ADC_INSTANCE = ADC0; /** @brief ADC 外部 VIN 检测通道号(原理图:PA6 -> VIN_ADC)。 */ -#define CONFIG_ADC_VIN_CHANNEL ADC_CHANNEL_6 +constexpr uint32_t CONFIG_ADC_VIN_CHANNEL = ADC_CHANNEL_6; /** @brief ADC 内部温度通道号。 */ -#define CONFIG_ADC_TEMP_CHANNEL ADC_CHANNEL_16 +constexpr uint32_t CONFIG_ADC_TEMP_CHANNEL = ADC_CHANNEL_16; /** @brief ADC 内部参考电压通道号。 */ -#define CONFIG_ADC_VREFINT_CHANNEL ADC_CHANNEL_17 +constexpr uint32_t CONFIG_ADC_VREFINT_CHANNEL = ADC_CHANNEL_17; /** @brief ADC 满量程计数值。 */ -#define CONFIG_ADC_MAX_VALUE 4095.0f +constexpr float CONFIG_ADC_MAX_VALUE = 4095.0f; /** @brief 内部参考电压典型值,单位 V。 */ -#define CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE 1.20f +constexpr float CONFIG_ADC_VREFINT_TYPICAL_VOLTAGE = 1.20f; /** @brief 温度传感器 25℃ 对应电压,单位 V。 */ -#define CONFIG_ADC_TEMP_V25 1.43f +constexpr float CONFIG_ADC_TEMP_V25 = 1.43f; /** @brief 温度传感器平均斜率,单位 V/℃。 */ -#define CONFIG_ADC_TEMP_AVG_SLOPE 0.0043f +constexpr float CONFIG_ADC_TEMP_AVG_SLOPE = 0.0043f; /** @brief ADC 上电后稳定等待时间,单位 ms。 */ -#define CONFIG_ADC_STABILIZE_DELAY_MS 2U +constexpr uint32_t CONFIG_ADC_STABILIZE_DELAY_MS = 2U; /** @brief ADC 规则通道采样时间配置。 */ -#define CONFIG_ADC_SAMPLE_TIME ADC_SAMPLETIME_239POINT5 +constexpr uint32_t CONFIG_ADC_SAMPLE_TIME = ADC_SAMPLETIME_239POINT5; /** @brief ADC 空输入保护时默认 VDDA,单位 V。 */ -#define CONFIG_ADC_DEFAULT_VDDA 3.3f +constexpr float CONFIG_ADC_DEFAULT_VDDA = 3.3f; /** @brief ADC 中值滤波窗口长度(最终样本数为该值 + 1)。 */ -#define CONFIG_ADC_FILTER_SIZE 6U +constexpr uint32_t CONFIG_ADC_FILTER_SIZE = 6U; /** @brief CAN 外设实例。 */ -#define CONFIG_CAN_INSTANCE CAN0 +constexpr uint32_t CONFIG_CAN_INSTANCE = CAN0; /** @brief CAN 外设时钟使能枚举。 */ -#define CONFIG_CAN_RCU RCU_CAN0 +constexpr rcu_periph_enum CONFIG_CAN_RCU = RCU_CAN0; /** @brief CAN 状态报文标准帧 ID。 */ -#define CONFIG_CAN_STATUS_ID 0x321U +constexpr uint32_t CONFIG_CAN_STATUS_ID = 0x321U; /** @brief ESP32 请求 DCU_APP 进入 Bootloader OTA 窗口的扩展帧 ID(备用 CAN 入口)。 */ -#define CONFIG_CAN_OTA_BOOT_REQ_ID 0x00000320U -/** @brief 进入 Bootloader OTA 请求魔术字节 0。 */ -#define CONFIG_CAN_OTA_BOOT_MAGIC0 0x4FU -/** @brief 进入 Bootloader OTA 请求魔术字节 1。 */ -#define CONFIG_CAN_OTA_BOOT_MAGIC1 0x54U -/** @brief 进入 Bootloader OTA 请求魔术字节 2。 */ -#define CONFIG_CAN_OTA_BOOT_MAGIC2 0x41U -/** @brief 进入 Bootloader OTA 请求魔术字节 3。 */ -#define CONFIG_CAN_OTA_BOOT_MAGIC3 0x21U +constexpr uint32_t CONFIG_CAN_OTA_BOOT_REQ_ID = 0x00000320U; +/** @brief 进入 Bootloader OTA 请求魔术字节 0~3。 */ +constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC0 = 0x4FU; +constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC1 = 0x54U; +constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC2 = 0x41U; +constexpr uint8_t CONFIG_CAN_OTA_BOOT_MAGIC3 = 0x21U; /** @brief ESP32 在 APP 运行期通过 SPI 发送的进入 Bootloader OTA 命令。 */ -#define CONFIG_SPI_APP_OTA_BOOT_CMD 0xB0U +constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_CMD = 0xB0U; /** @brief SPI 进入 Bootloader OTA 命令确认状态:成功。 */ -#define CONFIG_SPI_APP_OTA_BOOT_ACK_OK 0xB5U +constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_OK = 0xB5U; /** @brief SPI 进入 Bootloader OTA 命令确认状态:失败。 */ -#define CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR 0xE2U +constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR = 0xE2U; +/** @brief SPI Ready 握手输出端口(PB6 复用为 SPI Ready)。 */ +constexpr uint32_t CONFIG_SPI_READY_PORT = GPIOB; +/** @brief SPI Ready 握手输出引脚。 */ +constexpr uint32_t CONFIG_SPI_READY_PIN = GPIO_PIN_6; /* ============================================================ * CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用) * 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制) @@ -74,168 +87,134 @@ * ============================================================ */ /** @brief 仲裁段预分频值。 */ -#define CONFIG_CAN_NOMINAL_PRESCALER 9U +constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U; /** @brief 仲裁段再同步跳跃宽度。 */ -#define CONFIG_CAN_NOMINAL_SJW CAN_BT_SJW_1TQ +constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_1TQ; /** @brief 仲裁段时间段 1(PHSEG1)。 */ -#define CONFIG_CAN_NOMINAL_BS1 CAN_BT_BS1_7TQ +constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ; /** @brief 仲裁段时间段 2(PHSEG2)。 */ -#define CONFIG_CAN_NOMINAL_BS2 CAN_BT_BS2_2TQ +constexpr uint32_t CONFIG_CAN_NOMINAL_BS2 = CAN_BT_BS2_2TQ; /** @brief 数据段预分频值。 */ -#define CONFIG_CAN_DATA_PRESCALER 9U +constexpr uint32_t CONFIG_CAN_DATA_PRESCALER = 9U; /** @brief 数据段再同步跳跃宽度。 */ -#define CONFIG_CAN_DATA_SJW CAN_BT_SJW_1TQ +constexpr uint32_t CONFIG_CAN_DATA_SJW = CAN_BT_SJW_1TQ; /** @brief 数据段时间段 1。 */ -#define CONFIG_CAN_DATA_BS1 CAN_BT_BS1_3TQ +constexpr uint32_t CONFIG_CAN_DATA_BS1 = CAN_BT_BS1_3TQ; /** @brief 数据段时间段 2。 */ -#define CONFIG_CAN_DATA_BS2 CAN_BT_BS2_1TQ +constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ; -/** @brief BRS(比特率切换)使能开关:1=启用数据段加速,0=关闭仅经典 CAN。 */ +/** @brief BRS(比特率切换)使能开关。 + * @note 保留 #define:canfd.cpp 中用 #if 做编译期分支。 */ #define CONFIG_CAN_FD_BRS_ENABLE 1U +/** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */ +constexpr bool CONFIG_CAN_CH0_ENABLE = true; +/** @brief CAN1 通道使能开关。 */ +constexpr bool CONFIG_CAN_CH1_ENABLE = false; +/** @brief CAN2 通道使能开关。 */ +constexpr bool CONFIG_CAN_CH2_ENABLE = true; + /** @brief CAN 接收过滤器目标标准帧 ID。 */ -#define CONFIG_CAN_RX_FILTER_ID CONFIG_CAN_STATUS_ID -/** @brief CAN 接收过滤器标准帧掩码;`0x7FFU` 表示精确匹配 11-bit ID。 */ -#define CONFIG_CAN_RX_FILTER_MASK 0x7FFU -/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数,避免主循环长期被接收流量占用。 */ -#define CONFIG_CAN_RX_PARSE_BUDGET 48U -/** @brief CAN 状态载荷中调试标识字符 0。 */ -#define CONFIG_CAN_STATUS_TAG0 0x43U -/** @brief CAN 状态载荷中调试标识字符 1。 */ -#define CONFIG_CAN_STATUS_TAG1 0x41U -/** @brief CAN 状态载荷中调试标识字符 2。 */ -#define CONFIG_CAN_STATUS_TAG2 0x4EU -/** @brief CAN 状态载荷中调试标识字符 3。 */ -#define CONFIG_CAN_STATUS_TAG3 0x46U +constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID; +/** @brief CAN 接收过滤器标准帧掩码;0x7FF 表示精确匹配 11-bit 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 用户调度定时器实例。 */ -#define CONFIG_SCHED_TIMER_INSTANCE TIMER2 +constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2; /** @brief 用户调度定时器中断号。 */ -#define CONFIG_SCHED_TIMER_IRQn TIMER2_IRQn +constexpr IRQn_Type CONFIG_SCHED_TIMER_IRQn = TIMER2_IRQn; /** @brief 用户调度定时器时钟使能宏参数。 */ -#define CONFIG_SCHED_TIMER_RCU RCU_TIMER2 +constexpr rcu_periph_enum CONFIG_SCHED_TIMER_RCU = RCU_TIMER2; /** @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 +constexpr uint32_t CONFIG_SCHED_TIMER_PRESCALER = 179U; /** @brief 用户调度定时器周期重装值。 */ -#define CONFIG_SCHED_TIMER_PERIOD 99U +constexpr uint32_t CONFIG_SCHED_TIMER_PERIOD = 99U; /** @brief 用户调度定时器 NVIC 抢占优先级。 */ -#define CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO 1U +constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO = 1U; /** @brief 用户调度定时器 NVIC 子优先级。 */ -#define CONFIG_SCHED_TIMER_IRQ_SUB_PRIO 0U +constexpr uint32_t CONFIG_SCHED_TIMER_IRQ_SUB_PRIO = 0U; /** @brief 调度基准节拍:ADC VREF 采样周期,单位 100us tick。 */ -#define CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS 5U +constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U; /** @brief 调度基准节拍:ADC 温度采样/处理周期,单位 100us tick。 */ -#define CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS 10U +constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U; /** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */ -#define CONFIG_TASK_PERIOD_CAN_RX_TICKS 100U +constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U; /** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */ -#define CONFIG_TASK_PERIOD_CAN_TX_TICKS 1000U +constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 1000U; /** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */ -#define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U +constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U; -/** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */ +/** @brief 周期温度/电压 UART 打印开关。@note 保留 #define:task_manager.cpp #if 使用。 */ #define CONFIG_UART_TEMP_PERIODIC_PRINT 1 -/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */ +/** @brief UART 诊断打印级别。@note 保留 #define:task_runner.cpp #if 使用。 */ #define CONFIG_UART_DIAG_LEVEL 1 -/** @brief SPI 命令逐帧日志开关:0=关闭,1=开启。高速 CAN 桥接时应关闭。 */ +/** @brief SPI 命令逐帧日志开关。@note 保留 #define:#if 使用。 */ #define CONFIG_SPI_APP_LOG_ENABLE 0 -/** @brief CAN_SEND 成功帧详细日志开关:0=关闭,1=开启。开启会显著影响高速传输。 */ +/** @brief CAN_SEND 成功帧详细日志开关。@note 保留 #define:#if 使用。 */ #define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0 /** @brief FWDGT 独立看门狗预分频值。 */ -#define CONFIG_FWDGT_PSC FWDGT_PSC_DIV64 +constexpr uint32_t CONFIG_FWDGT_PSC = FWDGT_PSC_DIV64; /** @brief FWDGT 独立看门狗重载值。 * 超时 = (64 / 40000) * (RLD + 1) = 1.6ms * (RLD+1) * RLD=1874 → 1.6ms * 1875 ≈ 3.0s */ -#define CONFIG_FWDGT_RLD 1874U +constexpr uint32_t CONFIG_FWDGT_RLD = 1874U; /** @brief FWDGT 超时近似值(毫秒),仅供文档参考。 */ -#define CONFIG_FWDGT_TIMEOUT_MS 3000U - -/* ============================================================ - * APP 固件版本号 - * ============================================================ */ -/** @brief APP 主版本号。 */ -#define APP_FW_VERSION_MAJOR 1U -/** @brief APP 次版本号。 */ -#define APP_FW_VERSION_MINOR 2U -/** @brief APP 补丁版本号。 */ -#define APP_FW_VERSION_PATCH 2U -/** @brief APP 构建编号。 */ -#define APP_FW_VERSION_BUILD 0U +constexpr uint32_t CONFIG_FWDGT_TIMEOUT_MS = 3000U; /* ============================================================ * BOOT 版本在 OTA 配置页中的 Flash 存储地址 * 与 BOOT 工程 sys/config.h 保持一致 * ============================================================ */ -/** @brief OTA 配置页基地址。 */ -#define CONFIG_OTA_CONFIG_PAGE_ADDR 0x0807E000U -/** @brief BOOT 版本存储地址(word 6,低 32 位:major|minor)。 */ -#define CONFIG_BOOT_VERSION_ADDR0 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U) -/** @brief BOOT 版本存储地址(word 7,低 32 位:patch|build)。 */ -#define CONFIG_BOOT_VERSION_ADDR1 (CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU) -/** @brief OTA 配置页总 word 数(6 个 OTA 标志 + 2 个 BOOT 版本)。 */ -#define CONFIG_OTA_CONFIG_WORD_COUNT 8U +constexpr uint32_t CONFIG_OTA_CONFIG_PAGE_ADDR = 0x0807E000U; +constexpr uint32_t CONFIG_BOOT_VERSION_ADDR0 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x18U; +constexpr uint32_t CONFIG_BOOT_VERSION_ADDR1 = CONFIG_OTA_CONFIG_PAGE_ADDR + 0x1CU; +constexpr uint32_t CONFIG_OTA_CONFIG_WORD_COUNT = 8U; /* ============================================================ * NVIC 优先级分组与中断优先级分层 * (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级) * ============================================================ */ -/** @brief NVIC 优先级分组:4 位抢占 + 0 位子优先级。 */ -#define CONFIG_NVIC_PRIORITY_GROUP NVIC_PRIGROUP_PRE4_SUB0 - -/** @brief EtherCAT SYNC0 周期中断抢占优先级(最高,永不被阻塞)。 */ -#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC 0U -/** @brief 调度器定时器抢占优先级。 */ -#define CONFIG_IRQ_PREEMPT_SCHED 1U -/** @brief CAN RX0 抢占优先级。 */ -#define CONFIG_IRQ_PREEMPT_CAN 2U -/** @brief EtherCAT ESC 事件中断抢占优先级(可被 SYNC0 抢占)。 */ -#define CONFIG_IRQ_PREEMPT_ETHERCAT_ESC 1U -/** @brief EtherCAT SYNC1 备用抢占优先级。 */ -#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 1U -/** @brief SPI / EXTI / USART 抢占优先级(最低)。 */ -#define CONFIG_IRQ_PREEMPT_SPI_USART 3U - +constexpr uint32_t CONFIG_NVIC_PRIORITY_GROUP = NVIC_PRIGROUP_PRE4_SUB0; +constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC = 0U; +constexpr uint32_t CONFIG_IRQ_PREEMPT_SCHED = 1U; +constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN = 2U; +constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_ESC = 1U; +constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U; +constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U; /* ============================================================ * 主循环与实时策略 * ============================================================ */ -/** @brief 主循环 WFI 开关:0=关闭(忙等,适配 EtherCAT),1=开启(省电)。 */ +/** @brief 主循环 WFI 开关。@note 保留 #define:main.cpp #if 使用。 */ #define CONFIG_MAIN_LOOP_WFI_ENABLE 0 /* ============================================================ * EtherCAT LAN9252 使能与配置 * ============================================================ */ -/** @brief SYNC0 同步模式:0=EXTI 外部触发(ESC 硬件同步,默认),1=TIMER0 降级(DC 未同步时)。 */ +/** @brief SYNC0 同步模式。@note 保留 #define:#if 使用。 */ #define CONFIG_ETHERCAT_SYNC_MODE 0 -/** @brief periodicIsr 最大执行时间告警阈值(µs)。实测超过此值需评估减小 PDO 大小或优化 EXMC 时序。 - * 当前阈值 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 内存区的起始偏移。 - * 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。 - * @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */ -#define CONFIG_ESC_PDI_INPUT_OFFSET 0x1000U - -/** @brief 输出 PDO(RxPDO)在 PDI 内存区的起始偏移。 - * 对应 LAN9252 SyncManager 内存映射,需与主站 ESI 配置匹配。 - * @note 占位值,实际数值待主站配置确定后在集成阶段填入。 */ -#define CONFIG_ESC_PDI_OUTPUT_OFFSET 0x1200U +constexpr uint32_t CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD = 5U; +constexpr uint32_t CONFIG_ESC_PDI_INPUT_OFFSET = 0x1000U; +constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U; /* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */ -/** @brief EXMC NOR/SRAM Region1 基址(NE1 片选)。 */ -#define CONFIG_ESC_EXMC_BASE_ADDR 0x60000000U -/** @brief EXMC BANK 选择(BANK0 Region1)。 - * @note EXMC 库未纳入编译时回退为 0U;启用 EtherCAT 时 EXMC 组件将被引入。 */ +constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x60000000U; +/** @note EXMC_BANK0_NORSRAM_REGION1 条件编译,保留 #define。 */ #ifdef EXMC_BANK0_NORSRAM_REGION1 #define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1 #else @@ -244,67 +223,47 @@ /* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */ /** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */ -#define CONFIG_ESC_SYNC0_PORT GPIOB -#define CONFIG_ESC_SYNC0_PIN GPIO_PIN_0 -#define CONFIG_ESC_SYNC0_EXTI EXTI_0 -#define CONFIG_ESC_SYNC0_IRQn EXTI0_IRQn -#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB -#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_0 +constexpr uint32_t CONFIG_ESC_SYNC0_PORT = GPIOB; +constexpr uint32_t CONFIG_ESC_SYNC0_PIN = GPIO_PIN_0; +constexpr exti_line_enum CONFIG_ESC_SYNC0_EXTI = EXTI_0; +constexpr IRQn_Type CONFIG_ESC_SYNC0_IRQn = EXTI0_IRQn; +constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB; +constexpr uint32_t CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_0; -#define CONFIG_ESC_IRQ_PORT GPIOB -#define CONFIG_ESC_IRQ_PIN GPIO_PIN_1 -#define CONFIG_ESC_IRQ_EXTI EXTI_1 -#define CONFIG_ESC_IRQ_IRQn EXTI1_IRQn -#define CONFIG_ESC_IRQ_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB -#define CONFIG_ESC_IRQ_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_1 +constexpr uint32_t CONFIG_ESC_IRQ_PORT = GPIOB; +constexpr uint32_t CONFIG_ESC_IRQ_PIN = GPIO_PIN_1; +constexpr exti_line_enum CONFIG_ESC_IRQ_EXTI = EXTI_1; +constexpr IRQn_Type CONFIG_ESC_IRQ_IRQn = EXTI1_IRQn; +constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB; +constexpr uint32_t CONFIG_ESC_IRQ_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_1; -#define CONFIG_ESC_SYNC1_PORT GPIOB -#define CONFIG_ESC_SYNC1_PIN GPIO_PIN_2 -#define CONFIG_ESC_SYNC1_EXTI EXTI_2 -#define CONFIG_ESC_SYNC1_IRQn EXTI2_IRQn -#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB -#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_2 +constexpr uint32_t CONFIG_ESC_SYNC1_PORT = GPIOB; +constexpr uint32_t CONFIG_ESC_SYNC1_PIN = GPIO_PIN_2; +constexpr exti_line_enum CONFIG_ESC_SYNC1_EXTI = EXTI_2; +constexpr IRQn_Type CONFIG_ESC_SYNC1_IRQn = EXTI2_IRQn; +constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB; +constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_2; /* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */ -#define CONFIG_EXMC_D0_PORT GPIOD -#define CONFIG_EXMC_D0_PIN GPIO_PIN_14 -#define CONFIG_EXMC_D1_PORT GPIOD -#define CONFIG_EXMC_D1_PIN GPIO_PIN_15 -#define CONFIG_EXMC_D2_PORT GPIOD -#define CONFIG_EXMC_D2_PIN GPIO_PIN_0 -#define CONFIG_EXMC_D3_PORT GPIOD -#define CONFIG_EXMC_D3_PIN GPIO_PIN_1 -#define CONFIG_EXMC_D4_PORT GPIOE -#define CONFIG_EXMC_D4_PIN GPIO_PIN_7 -#define CONFIG_EXMC_D5_PORT GPIOE -#define CONFIG_EXMC_D5_PIN GPIO_PIN_8 -#define CONFIG_EXMC_D6_PORT GPIOE -#define CONFIG_EXMC_D6_PIN GPIO_PIN_9 -#define CONFIG_EXMC_D7_PORT GPIOE -#define CONFIG_EXMC_D7_PIN GPIO_PIN_10 -#define CONFIG_EXMC_D8_PORT GPIOE -#define CONFIG_EXMC_D8_PIN GPIO_PIN_11 -#define CONFIG_EXMC_D9_PORT GPIOE -#define CONFIG_EXMC_D9_PIN GPIO_PIN_12 -#define CONFIG_EXMC_D10_PORT GPIOE -#define CONFIG_EXMC_D10_PIN GPIO_PIN_13 -#define CONFIG_EXMC_D11_PORT GPIOE -#define CONFIG_EXMC_D11_PIN GPIO_PIN_14 -#define CONFIG_EXMC_D12_PORT GPIOE -#define CONFIG_EXMC_D12_PIN GPIO_PIN_15 -#define CONFIG_EXMC_D13_PORT GPIOD -#define CONFIG_EXMC_D13_PIN GPIO_PIN_8 -#define CONFIG_EXMC_D14_PORT GPIOD -#define CONFIG_EXMC_D14_PIN GPIO_PIN_9 -#define CONFIG_EXMC_D15_PORT GPIOD -#define CONFIG_EXMC_D15_PIN GPIO_PIN_10 -#define CONFIG_EXMC_A16_PORT GPIOD -#define CONFIG_EXMC_A16_PIN GPIO_PIN_11 -#define CONFIG_EXMC_NOE_PORT GPIOD -#define CONFIG_EXMC_NOE_PIN GPIO_PIN_4 -#define CONFIG_EXMC_NWE_PORT GPIOD -#define CONFIG_EXMC_NWE_PIN GPIO_PIN_5 -#define CONFIG_EXMC_NE1_PORT GPIOD -#define CONFIG_EXMC_NE1_PIN GPIO_PIN_7 +constexpr uint32_t CONFIG_EXMC_D0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D0_PIN = GPIO_PIN_14; +constexpr uint32_t CONFIG_EXMC_D1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D1_PIN = GPIO_PIN_15; +constexpr uint32_t CONFIG_EXMC_D2_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D2_PIN = GPIO_PIN_0; +constexpr uint32_t CONFIG_EXMC_D3_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D3_PIN = GPIO_PIN_1; +constexpr uint32_t CONFIG_EXMC_D4_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D4_PIN = GPIO_PIN_7; +constexpr uint32_t CONFIG_EXMC_D5_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D5_PIN = GPIO_PIN_8; +constexpr uint32_t CONFIG_EXMC_D6_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D6_PIN = GPIO_PIN_9; +constexpr uint32_t CONFIG_EXMC_D7_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D7_PIN = GPIO_PIN_10; +constexpr uint32_t CONFIG_EXMC_D8_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D8_PIN = GPIO_PIN_11; +constexpr uint32_t CONFIG_EXMC_D9_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D9_PIN = GPIO_PIN_12; +constexpr uint32_t CONFIG_EXMC_D10_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D10_PIN = GPIO_PIN_13; +constexpr uint32_t CONFIG_EXMC_D11_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D11_PIN = GPIO_PIN_14; +constexpr uint32_t CONFIG_EXMC_D12_PORT = GPIOE; constexpr uint32_t CONFIG_EXMC_D12_PIN = GPIO_PIN_15; +constexpr uint32_t CONFIG_EXMC_D13_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D13_PIN = GPIO_PIN_8; +constexpr uint32_t CONFIG_EXMC_D14_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D14_PIN = GPIO_PIN_9; +constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D15_PIN = GPIO_PIN_10; +constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11; +constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4; +constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5; +constexpr uint32_t CONFIG_EXMC_NE1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE1_PIN = GPIO_PIN_7; #endif /* SYS_CONFIG_H */ diff --git a/GD32E508_DCU_APP/sys/task_runner.cpp b/GD32E508_DCU_APP/sys/task_runner.cpp index f58d1f3..4fc9a49 100644 --- a/GD32E508_DCU_APP/sys/task_runner.cpp +++ b/GD32E508_DCU_APP/sys/task_runner.cpp @@ -152,37 +152,46 @@ void TaskRunner::taskCanRxPoll() { CanFd::poll(); } */ void TaskRunner::taskCanTxSend() { - /* 启动后每 100ms 向三路 CAN 通道发送 CANFD+BRS 64 字节测试帧。 + /* 启动后每 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] 递增填充 0x00..0x35(固定模式,便于抓包校验) */ + * [10..63] 递增填充,按通道偏移便于抓包区分 */ static uint32_t seq[3] = {0U, 0U, 0U}; + static uint8_t round = 0U; /* 0=CH0, 1=CH1, 2=CH2, 轮流 */ - 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 uint8_t ch = round; + round = (round + 1U) % 3U; - const uint32_t extId = 0x1FFFFF00U | static_cast(ch); - CanFd::transmitFrame(ch, extId, data, 64U); - seq[ch]++; + /* 跳过已禁用的通道 */ + if (!CanFd::isChannelEnabled(ch)) { + return; } + + 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]++; } /**