feat(canfd): 添加TX错误事件驱动的快速重发机制

添加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,增强总线容错能力
This commit is contained in:
cbb
2026-07-27 00:02:14 +08:00
parent 079c6442d4
commit edebde4a02
4 changed files with 91 additions and 11 deletions
+5 -3
View File
@@ -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)。 */
+49 -8
View File
@@ -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 可能短暂断开)。