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:
@@ -165,6 +165,10 @@ uint32_t CanFd::s_feedbackSeq = 0U;
|
|||||||
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
||||||
bool CanFd::s_channelHwOk[3]{};
|
bool CanFd::s_channelHwOk[3]{};
|
||||||
|
|
||||||
|
/* TX 错误事件标志(ARD=1 单发丢帧通知):TX 完成 ISR 置位,主循环
|
||||||
|
* consumeTxError() 读清;单写单读,无锁。 */
|
||||||
|
static volatile bool s_txErrorEvent[3]{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 查询指定通道是否已使能。
|
* @brief 查询指定通道是否已使能。
|
||||||
*/
|
*/
|
||||||
@@ -1062,6 +1066,28 @@ void CanFd::setChannelAutoRetrans(uint8_t channelIndex, bool enable)
|
|||||||
(CAN_CTL(canPeriph) & CAN_CTL_ARD) ? 1U : 0U);
|
(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 诊断统计信息只读指针。
|
* @brief 获取 CANFD 诊断统计信息只读指针。
|
||||||
* @param[in] 无。
|
* @param[in] 无。
|
||||||
@@ -1301,6 +1327,9 @@ void CanFd::handleTxInterrupt(uint8_t channelIndex)
|
|||||||
} else if ((tstat & errorBits[mailbox]) != 0U) {
|
} else if ((tstat & errorBits[mailbox]) != 0U) {
|
||||||
s_diag.error_count++;
|
s_diag.error_count++;
|
||||||
s_channelStats[channelIndex].error_count++;
|
s_channelStats[channelIndex].error_count++;
|
||||||
|
/* ARD=1 单发模式下 MTE 置位 = 该帧已确认丢失;置事件标志供停等式
|
||||||
|
* OTA 快速重发(免等 ACK 超时)。单写(ISR)单读清(主循环),无锁。 */
|
||||||
|
s_txErrorEvent[channelIndex] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ public:
|
|||||||
|
|
||||||
/** @brief 运行时切换指定通道 CAN 自动重发(ARD 位,需 init-mode);停等式 OTA 期间关闭、结束后恢复。 */
|
/** @brief 运行时切换指定通道 CAN 自动重发(ARD 位,需 init-mode);停等式 OTA 期间关闭、结束后恢复。 */
|
||||||
static void setChannelAutoRetrans(uint8_t channelIndex, bool enable);
|
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();
|
static const Diag *diag();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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_MAJOR = 1U;
|
||||||
constexpr uint32_t APP_FW_VERSION_MINOR = 4U;
|
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;
|
constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
|
||||||
|
|
||||||
|
|
||||||
@@ -163,8 +163,10 @@ constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9;
|
|||||||
|
|
||||||
/** @brief 仲裁段预分频值。 */
|
/** @brief 仲裁段预分频值。 */
|
||||||
constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U;
|
constexpr uint32_t CONFIG_CAN_NOMINAL_PRESCALER = 9U;
|
||||||
/** @brief 仲裁段再同步跳跃宽度。 */
|
/** @brief 仲裁段再同步跳跃宽度。
|
||||||
constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_1TQ;
|
* 1TQ→2TQ:总线空闲期即持续 STUFF 接收错误(REC 冲 120+),OTA 数据帧被错误帧
|
||||||
|
* 击落触发应用层重发;放宽接收再同步容差(不改波特率/采样点,上限受 BS2=2TQ 限制)。 */
|
||||||
|
constexpr uint32_t CONFIG_CAN_NOMINAL_SJW = CAN_BT_SJW_2TQ;
|
||||||
/** @brief 仲裁段时间段 1(PHSEG1)。 */
|
/** @brief 仲裁段时间段 1(PHSEG1)。 */
|
||||||
constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ;
|
constexpr uint32_t CONFIG_CAN_NOMINAL_BS1 = CAN_BT_BS1_7TQ;
|
||||||
/** @brief 仲裁段时间段 2(PHSEG2)。 */
|
/** @brief 仲裁段时间段 2(PHSEG2)。 */
|
||||||
|
|||||||
@@ -273,6 +273,14 @@ static void setError(uint8_t code) {
|
|||||||
static constexpr uint32_t kStepTimeoutMs = 100U;
|
static constexpr uint32_t kStepTimeoutMs = 100U;
|
||||||
/* 补发次数上限:首发后最多再补发 kMaxResends 次;仍无反馈则判该步失败并打印。 */
|
/* 补发次数上限:首发后最多再补发 kMaxResends 次;仍无反馈则判该步失败并打印。 */
|
||||||
static constexpr uint8_t kMaxResends = 2U;
|
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 错。
|
/* 数据传输每包间隔(ms):正式发固件时每包也留间隔,防电机单缓冲 RX 高速下丢包/CRC 错。
|
||||||
* 现行:5ms/包(用户指定)。SPI 喂数必须能跟上;跟不上时靠水位门控暂停 CAN。 */
|
* 现行:5ms/包(用户指定)。SPI 喂数必须能跟上;跟不上时靠水位门控暂停 CAN。 */
|
||||||
static constexpr uint32_t kTransferGapMs = 5U;
|
static constexpr uint32_t kTransferGapMs = 5U;
|
||||||
@@ -474,6 +482,8 @@ static void sendNextPacket() {
|
|||||||
s_pkt_pending = true;
|
s_pkt_pending = true;
|
||||||
s_retry_count = 0; /* 新数据包:重置补发计数 */
|
s_retry_count = 0; /* 新数据包:重置补发计数 */
|
||||||
s_state_enter_ms = SysTickTimer::getMs(); /* 新数据包:重置整体超时 backstop */
|
s_state_enter_ms = SysTickTimer::getMs(); /* 新数据包:重置整体超时 backstop */
|
||||||
|
/* 丢弃上一包/握手阶段残留的 TX 错误事件,避免新包刚发就被陈旧标志误触快速重发。 */
|
||||||
|
(void)CanFd::consumeTxError(s_channel);
|
||||||
/* 末包 padding 日志在 sendCurrentPacket 的 TX 行之前打印,且用最短格式,
|
/* 末包 padding 日志在 sendCurrentPacket 的 TX 行之前打印,且用最短格式,
|
||||||
* 避免 10ms 高频 TX 期间被 UART 吞掉(之前放在这里会被紧接的 TX pkt= 行挤掉)。 */
|
* 避免 10ms 高频 TX 期间被 UART 吞掉(之前放在这里会被紧接的 TX pkt= 行挤掉)。 */
|
||||||
sendCurrentPacket();
|
sendCurrentPacket();
|
||||||
@@ -492,6 +502,8 @@ static uint8_t s_crc_retry_count = 0;
|
|||||||
static uint16_t s_last_acked_pkt = 0xFFFFU; /* 上次确认的包号,用于识别重复 ACK */
|
static uint16_t s_last_acked_pkt = 0xFFFFU; /* 上次确认的包号,用于识别重复 ACK */
|
||||||
static uint32_t s_dup_ack_count = 0; /* 累计忽略的重复/未前进 ACK 帧数(诊断重传风暴) */
|
static uint32_t s_dup_ack_count = 0; /* 累计忽略的重复/未前进 ACK 帧数(诊断重传风暴) */
|
||||||
static uint32_t s_crc_err_count = 0; /* 累计收到的 0x06 CRC 错误帧数(含电机重传) */
|
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) {
|
static void processTransferAck(const uint8_t *data, uint8_t len) {
|
||||||
/* 擦除等待窗口(首包尚未发出:s_packets_acked==0 且 !s_pkt_pending):电机重传的旧
|
/* 擦除等待窗口(首包尚未发出:s_packets_acked==0 且 !s_pkt_pending):电机重传的旧
|
||||||
@@ -567,9 +579,10 @@ if (next_pkt == 0xFFFFFFFFU) {
|
|||||||
s_packets_acked = s_total_packets;
|
s_packets_acked = s_total_packets;
|
||||||
s_last_acked_pkt = s_total_packets;
|
s_last_acked_pkt = s_total_packets;
|
||||||
s_pkt_pending = false;
|
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)s_packets_acked, (unsigned)s_total_packets,
|
||||||
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count,
|
(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());
|
(unsigned long)SysTickTimer::getMs());
|
||||||
enterState(State::kFinished);
|
enterState(State::kFinished);
|
||||||
pushStatus();
|
pushStatus();
|
||||||
@@ -611,9 +624,10 @@ if (next_pkt == 0xFFFFFFFFU) {
|
|||||||
* 与 0xD1 喂数抢总线,加重缓冲欠载(CANlog 可见周期性 ~500ms 空洞)。
|
* 与 0xD1 喂数抢总线,加重缓冲欠载(CANlog 可见周期性 ~500ms 空洞)。
|
||||||
* 进度由 ESP32 feedTask 按已喂字节估算;终态再 push。 */
|
* 进度由 ESP32 feedTask 按已喂字节估算;终态再 push。 */
|
||||||
if ((s_packets_acked & 0x1F) == 0) {
|
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)s_packets_acked, (unsigned)s_total_packets,
|
||||||
(unsigned long)s_dup_ack_count, (unsigned long)s_crc_err_count,
|
(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());
|
(unsigned long)SysTickTimer::getMs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -633,17 +647,42 @@ void onTxTick() {
|
|||||||
|
|
||||||
const uint32_t now = SysTickTimer::getMs();
|
const uint32_t now = SysTickTimer::getMs();
|
||||||
|
|
||||||
/* ACK 超时重发:包已发出(s_pkt_pending=true)但超过 500ms 未收到 ACK,重发。
|
/* ACK 超时重发:包已发出(s_pkt_pending=true)但超过 kAckResendMs 未收到 ACK,重发。
|
||||||
* 场景:CAN 总线 ERR_PASSIVE(REC>128) 导致电机 ACK 丢失,
|
* 场景:OTA 期间 ARD=1 单发,总线错误(如 STUFF)直接击落数据帧/ACK 丢失,
|
||||||
* GD32 等 ACK、电机等下一包 → 双方死锁。
|
* GD32 等 ACK、电机等下一包 → 双方死锁。
|
||||||
* ★ 注意:检查 s_pkt_pending(包已发等ACK),不是 s_ack_pending(ACK已到等处理)!
|
* ★ 注意:检查 s_pkt_pending(包已发等ACK),不是 s_ack_pending(ACK已到等处理)!
|
||||||
* 重发后电机幂等处理(同包号只 ACK 不重复写 flash)。 */
|
* 重发后电机幂等处理(同包号只 ACK 不重复写 flash)。 */
|
||||||
if (s_pkt_pending && !s_ack_pending && !s_crc_resend_pending) {
|
if (s_pkt_pending && !s_ack_pending && !s_crc_resend_pending) {
|
||||||
if (now - s_last_send_ms >= 500U) {
|
/* 快速路径:TX 完成中断已确认当前帧被总线错误击落(MTE),免等
|
||||||
if (s_retry_count < kMaxResends) {
|
* kAckResendMs 盲等,按 kTransferGapMs 节流立即重发。先判 gap 再 consume,
|
||||||
|
* 避免过早清标志丢事件;重发次数与超时路径共用 kAckMaxResends 上限。 */
|
||||||
|
if ((now - s_last_send_ms) >= kTransferGapMs
|
||||||
|
&& s_retry_count < kAckMaxResends
|
||||||
|
&& CanFd::consumeTxError(s_channel)) {
|
||||||
s_retry_count++;
|
s_retry_count++;
|
||||||
printf("[MOTOR-OTA] ACK 超时重发 pkt=%u retry=%u\r\n",
|
s_fast_resend_total++;
|
||||||
(unsigned)s_packets_acked, (unsigned)s_retry_count);
|
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++;
|
||||||
|
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();
|
sendCurrentPacket();
|
||||||
s_last_send_ms = SysTickTimer::getMs();
|
s_last_send_ms = SysTickTimer::getMs();
|
||||||
}
|
}
|
||||||
@@ -944,6 +983,8 @@ s_channel = can_channel;
|
|||||||
s_pkt_pending = false;
|
s_pkt_pending = false;
|
||||||
s_dup_ack_count = 0;
|
s_dup_ack_count = 0;
|
||||||
s_crc_err_count = 0;
|
s_crc_err_count = 0;
|
||||||
|
s_ack_resend_total = 0;
|
||||||
|
s_fast_resend_total = 0;
|
||||||
s_last_pad_valid = 0xFFFFU;
|
s_last_pad_valid = 0xFFFFU;
|
||||||
|
|
||||||
/* 不在 SPI 中断里同步发握手帧(电机跳 BOOT 期间 CAN 可能短暂断开)。
|
/* 不在 SPI 中断里同步发握手帧(电机跳 BOOT 期间 CAN 可能短暂断开)。
|
||||||
|
|||||||
Reference in New Issue
Block a user