From edebde4a0268edc5f1bc261ef2f5101dfc5e5060 Mon Sep 17 00:00:00 2001 From: cbb <2625274797@qq.com> Date: Mon, 27 Jul 2026 00:02:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(canfd):=20=E6=B7=BB=E5=8A=A0TX=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BA=8B=E4=BB=B6=E9=A9=B1=E5=8A=A8=E7=9A=84=E5=BF=AB?= =?UTF-8?q?=E9=80=9F=E9=87=8D=E5=8F=91=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加TX错误事件标志和相关API以支持ARD模式下单发帧丢失的快速检测和重发, 实现停等式OTA在丢帧后的立即重发,避免等待ACK超时的延迟。 - 新增s_txErrorEvent数组跟踪各通道TX错误事件 - 添加readChannelErrorReg()读取CAN_ERR寄存器原始值 - 添加consumeTxError()消费TX错误事件并返回状态 - 在TX中断中检测MTE位设置错误事件标志 fix(motor-ota): 优化OTA重发策略和错误处理 调整OTA重发参数和逻辑,提升传输可靠性和响应速度。 - 将ACK超时重发间隔从500ms调整为100ms,提高响应速度 - 增加每包补发上限从2次到4次,保持总容错时间窗 - 实现TX错误驱动的快速重发,丢帧后立即按间隔补发 - 增加错误统计信息输出,包含重发和快速重发计数 - 提高仲裁段再同步跳跃宽度从1TQ到2TQ,增强总线容错能力 --- GD32E508_DCU_APP/device/CANFD.cpp | 29 ++++++++++++ GD32E508_DCU_APP/device/canfd.h | 8 ++++ GD32E508_DCU_APP/sys/config.h | 8 ++-- GD32E508_DCU_APP/sys/motor_ota_relay.cpp | 57 ++++++++++++++++++++---- 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/GD32E508_DCU_APP/device/CANFD.cpp b/GD32E508_DCU_APP/device/CANFD.cpp index 725be1e..7b2f96d 100644 --- a/GD32E508_DCU_APP/device/CANFD.cpp +++ b/GD32E508_DCU_APP/device/CANFD.cpp @@ -165,6 +165,10 @@ uint32_t CanFd::s_feedbackSeq = 0U; CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{}; bool CanFd::s_channelHwOk[3]{}; +/* TX 错误事件标志(ARD=1 单发丢帧通知):TX 完成 ISR 置位,主循环 + * consumeTxError() 读清;单写单读,无锁。 */ +static volatile bool s_txErrorEvent[3]{}; + /** * @brief 查询指定通道是否已使能。 */ @@ -1062,6 +1066,28 @@ void CanFd::setChannelAutoRetrans(uint8_t channelIndex, bool enable) (CAN_CTL(canPeriph) & CAN_CTL_ARD) ? 1U : 0U); } +uint32_t CanFd::readChannelErrorReg(uint8_t channelIndex) +{ + if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { + return 0U; + } + /* 单次寄存器读,供事件时刻(如 OTA ACK 超时)抓取总线错误状态; + * 不走 1Hz refreshFeedbackSnapshot,避免采样混叠。 */ + return CAN_ERR(kChannels[channelIndex].canPeriph); +} + +bool CanFd::consumeTxError(uint8_t channelIndex) +{ + if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) { + return false; + } + if (!s_txErrorEvent[channelIndex]) { + return false; + } + s_txErrorEvent[channelIndex] = false; + return true; +} + /** * @brief 获取 CANFD 诊断统计信息只读指针。 * @param[in] 无。 @@ -1301,6 +1327,9 @@ void CanFd::handleTxInterrupt(uint8_t channelIndex) } else if ((tstat & errorBits[mailbox]) != 0U) { s_diag.error_count++; s_channelStats[channelIndex].error_count++; + /* ARD=1 单发模式下 MTE 置位 = 该帧已确认丢失;置事件标志供停等式 + * OTA 快速重发(免等 ACK 超时)。单写(ISR)单读清(主循环),无锁。 */ + s_txErrorEvent[channelIndex] = true; } } diff --git a/GD32E508_DCU_APP/device/canfd.h b/GD32E508_DCU_APP/device/canfd.h index d12fb68..98f8331 100644 --- a/GD32E508_DCU_APP/device/canfd.h +++ b/GD32E508_DCU_APP/device/canfd.h @@ -150,6 +150,14 @@ public: /** @brief 运行时切换指定通道 CAN 自动重发(ARD 位,需 init-mode);停等式 OTA 期间关闭、结束后恢复。 */ static void setChannelAutoRetrans(uint8_t channelIndex, bool enable); + + /** @brief 读取指定通道 CAN_ERR 寄存器原始值(事件时刻只读快照,不依赖 1Hz 采样)。 + * 解码:TEC=bit16-23,REC=bit24-31,ERRN=bit4-6;无效通道返回 0。仅供诊断日志,禁用于发送门控。 */ + static uint32_t readChannelErrorReg(uint8_t channelIndex); + + /** @brief 读取并清除指定通道的 TX 错误事件(ARD=1 单发下 MTE 置位 = 帧已确认丢失)。 + * 供停等式 OTA 在丢帧后立即重发,免等 ACK 超时;仅主循环调用。 */ + static bool consumeTxError(uint8_t channelIndex); static const Diag *diag(); /** diff --git a/GD32E508_DCU_APP/sys/config.h b/GD32E508_DCU_APP/sys/config.h index e39915e..57719d0 100644 --- a/GD32E508_DCU_APP/sys/config.h +++ b/GD32E508_DCU_APP/sys/config.h @@ -80,7 +80,7 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = false; * ============================================================ */ constexpr uint32_t APP_FW_VERSION_MAJOR = 1U; constexpr uint32_t APP_FW_VERSION_MINOR = 4U; -constexpr uint32_t APP_FW_VERSION_PATCH = 75U; /* 1.4.75:尾段fed>=fw_size旁路水位门控,修复末6包死锁 */ +constexpr uint32_t APP_FW_VERSION_PATCH = 78U; /* 1.4.78:TX错误(MTE)事件驱动快速重发:丢帧后按gap立即补发免等100ms超时,日志加fast=计数 */ constexpr uint32_t APP_FW_VERSION_BUILD = 0U; @@ -163,8 +163,10 @@ constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9; /** @brief 仲裁段预分频值。 */ constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U; -/** @brief 仲裁段再同步跳跃宽度。 */ -constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_1TQ; +/** @brief 仲裁段再同步跳跃宽度。 + * 1TQ→2TQ:总线空闲期即持续 STUFF 接收错误(REC 冲 120+),OTA 数据帧被错误帧 + * 击落触发应用层重发;放宽接收再同步容差(不改波特率/采样点,上限受 BS2=2TQ 限制)。 */ +constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_2TQ; /** @brief 仲裁段时间段 1(PHSEG1)。 */ constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ; /** @brief 仲裁段时间段 2(PHSEG2)。 */ diff --git a/GD32E508_DCU_APP/sys/motor_ota_relay.cpp b/GD32E508_DCU_APP/sys/motor_ota_relay.cpp index a24c69d..bf0b3a3 100644 --- a/GD32E508_DCU_APP/sys/motor_ota_relay.cpp +++ b/GD32E508_DCU_APP/sys/motor_ota_relay.cpp @@ -273,6 +273,14 @@ static void setError(uint8_t code) { static constexpr uint32_t kStepTimeoutMs = 100U; /* 补发次数上限:首发后最多再补发 kMaxResends 次;仍无反馈则判该步失败并打印。 */ static constexpr uint8_t kMaxResends = 2U; +/* 数据阶段 ACK 超时重发间隔(ms):正常 ACK 往返 ~11ms(电机 BOOT 主循环 ~10ms), + * 100ms 留 10 倍余量且避开电机偶发 flash 写慢。原 500ms 硬编码:11 次丢帧盲等 + * 共 ~5.5s,占整场传输过半时间。电机对同包号幂等(只 ACK 不重复写 flash), + * 早发导致的双 ACK 由 processTransferAck 的重复 ACK 过滤兜底。 */ +static constexpr uint32_t kAckResendMs = 100U; +/* 数据阶段每包补发上限:随 kAckResendMs 500→100 同步 2→4,保持总容错时间窗; + * 握手阶段仍用 kMaxResends,不动 v1.4.51 抗干扰逻辑。超限后由 process() 整体超时兜底。 */ +static constexpr uint8_t kAckMaxResends = 4U; /* 数据传输每包间隔(ms):正式发固件时每包也留间隔,防电机单缓冲 RX 高速下丢包/CRC 错。 * 现行:5ms/包(用户指定)。SPI 喂数必须能跟上;跟不上时靠水位门控暂停 CAN。 */ static constexpr uint32_t kTransferGapMs = 5U; @@ -474,6 +482,8 @@ static void sendNextPacket() { s_pkt_pending = true; s_retry_count = 0; /* 新数据包:重置补发计数 */ s_state_enter_ms = SysTickTimer::getMs(); /* 新数据包:重置整体超时 backstop */ + /* 丢弃上一包/握手阶段残留的 TX 错误事件,避免新包刚发就被陈旧标志误触快速重发。 */ + (void)CanFd::consumeTxError(s_channel); /* 末包 padding 日志在 sendCurrentPacket 的 TX 行之前打印,且用最短格式, * 避免 10ms 高频 TX 期间被 UART 吞掉(之前放在这里会被紧接的 TX pkt= 行挤掉)。 */ sendCurrentPacket(); @@ -492,6 +502,8 @@ static uint8_t s_crc_retry_count = 0; static uint16_t s_last_acked_pkt = 0xFFFFU; /* 上次确认的包号,用于识别重复 ACK */ static uint32_t s_dup_ack_count = 0; /* 累计忽略的重复/未前进 ACK 帧数(诊断重传风暴) */ static uint32_t s_crc_err_count = 0; /* 累计收到的 0x06 CRC 错误帧数(含电机重传) */ +static uint32_t s_ack_resend_total = 0; /* 累计 ACK 超时重发次数(整场不清零,丢帧率基线) */ +static uint32_t s_fast_resend_total = 0; /* 累计 TX 错误快速重发次数(MTE 事件驱动,免等超时) */ static void processTransferAck(const uint8_t *data, uint8_t len) { /* 擦除等待窗口(首包尚未发出:s_packets_acked==0 且 !s_pkt_pending):电机重传的旧 @@ -567,9 +579,10 @@ if (next_pkt == 0xFFFFFFFFU) { s_packets_acked = s_total_packets; s_last_acked_pkt = s_total_packets; s_pkt_pending = false; - printf("[MOTOR-OTA] FINISHED pkt=%u/%u dup=%lu crc=%lu t=%lu\r\n", + printf("[MOTOR-OTA] FINISHED pkt=%u/%u dup=%lu crc=%lu resend=%lu fast=%lu t=%lu\r\n", (unsigned)s_packets_acked, (unsigned)s_total_packets, (unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count, + (unsigned long)s_ack_resend_total, (unsigned long)s_fast_resend_total, (unsigned long)SysTickTimer::getMs()); enterState(State::kFinished); pushStatus(); @@ -611,9 +624,10 @@ if (next_pkt == 0xFFFFFFFFU) { * 与 0xD1 喂数抢总线,加重缓冲欠载(CANlog 可见周期性 ~500ms 空洞)。 * 进度由 ESP32 feedTask 按已喂字节估算;终态再 push。 */ if ((s_packets_acked & 0x1F) == 0) { - printf("[MOTOR-OTA] 进度 pkt=%u/%u dup=%lu crc=%lu t=%lu\r\n", + printf("[MOTOR-OTA] 进度 pkt=%u/%u dup=%lu crc=%lu resend=%lu fast=%lu t=%lu\r\n", (unsigned)s_packets_acked, (unsigned)s_total_packets, (unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count, + (unsigned long)s_ack_resend_total, (unsigned long)s_fast_resend_total, (unsigned long)SysTickTimer::getMs()); } } @@ -633,17 +647,42 @@ void onTxTick() { const uint32_t now = SysTickTimer::getMs(); - /* ACK 超时重发:包已发出(s_pkt_pending=true)但超过 500ms 未收到 ACK,重发。 - * 场景:CAN 总线 ERR_PASSIVE(REC>128) 导致电机 ACK 丢失, + /* ACK 超时重发:包已发出(s_pkt_pending=true)但超过 kAckResendMs 未收到 ACK,重发。 + * 场景:OTA 期间 ARD=1 单发,总线错误(如 STUFF)直接击落数据帧/ACK 丢失, * GD32 等 ACK、电机等下一包 → 双方死锁。 * ★ 注意:检查 s_pkt_pending(包已发等ACK),不是 s_ack_pending(ACK已到等处理)! * 重发后电机幂等处理(同包号只 ACK 不重复写 flash)。 */ if (s_pkt_pending && !s_ack_pending && !s_crc_resend_pending) { - if (now - s_last_send_ms >= 500U) { - if (s_retry_count < kMaxResends) { + /* 快速路径:TX 完成中断已确认当前帧被总线错误击落(MTE),免等 + * kAckResendMs 盲等,按 kTransferGapMs 节流立即重发。先判 gap 再 consume, + * 避免过早清标志丢事件;重发次数与超时路径共用 kAckMaxResends 上限。 */ + if ((now - s_last_send_ms) >= kTransferGapMs + && s_retry_count < kAckMaxResends + && CanFd::consumeTxError(s_channel)) { + s_retry_count++; + s_fast_resend_total++; + const uint32_t err = CanFd::readChannelErrorReg(s_channel); + printf("[MOTOR-OTA] TX错快重发 pkt=%u retry=%u tec=%lu rec=%lu errn=%lu\r\n", + (unsigned)s_packets_acked, (unsigned)s_retry_count, + (unsigned long)((err >> 16) & 0xFFU), + (unsigned long)((err >> 24) & 0xFFU), + (unsigned long)((err >> 4) & 0x07U)); + sendCurrentPacket(); + s_last_send_ms = SysTickTimer::getMs(); + return; + } + if (now - s_last_send_ms >= kAckResendMs) { + if (s_retry_count < kAckMaxResends) { s_retry_count++; - printf("[MOTOR-OTA] ACK 超时重发 pkt=%u retry=%u\r\n", - (unsigned)s_packets_acked, (unsigned)s_retry_count); + s_ack_resend_total++; + /* 事件时刻 CAN_ERR 快照:钉死丢帧时总线状态(TEC 涨=数据帧发丢, + * REC 高=接收方向损坏),摆脱 1Hz [CAN-BUS] 采样混叠。仅日志,不做发送门控。 */ + const uint32_t err = CanFd::readChannelErrorReg(s_channel); + printf("[MOTOR-OTA] ACK 超时重发 pkt=%u retry=%u tec=%lu rec=%lu errn=%lu\r\n", + (unsigned)s_packets_acked, (unsigned)s_retry_count, + (unsigned long)((err >> 16) & 0xFFU), + (unsigned long)((err >> 24) & 0xFFU), + (unsigned long)((err >> 4) & 0x07U)); sendCurrentPacket(); s_last_send_ms = SysTickTimer::getMs(); } @@ -944,6 +983,8 @@ s_channel = can_channel; s_pkt_pending = false; s_dup_ack_count = 0; s_crc_err_count = 0; + s_ack_resend_total = 0; + s_fast_resend_total = 0; s_last_pad_valid = 0xFFFFU; /* 不在 SPI 中断里同步发握手帧(电机跳 BOOT 期间 CAN 可能短暂断开)。