feat(canfd): 添加CANFD发送邮箱占用处理机制

当CAN控制器因总线无ACK导致邮箱卡死时,实现两段式恢复策略:
1) 短自旋轮询邮箱状态,大多数场景下可避免触发abort而不丢帧
2) 若仍全部PENDING则只中止第一个占用邮箱,限制最坏丢帧数量为1帧

添加了tx_abort_count诊断计数器用于监控该情况,并引入SysTick延时功能

refactor(spi-cmd): 优化SPI命令日志输出格式

将CAN发送相关日志改为统一的格式化函数输出,增加数据字段十六进制显示功能,
并扩大日志缓冲区大小从128到256字节以支持更详细的日志信息

chore(spi-command): 添加SPI帧类型分类和非刷写帧日志记录

新增isFlashRelatedFrame函数区分刷写相关帧类型,在处理命令时对非刷写帧进行日志记录

perf(diagnostic): 在诊断打印中添加发送中止计数统计

更新诊断信息打印格式,增加tx_abort计数项,便于监控CAN发送异常情况
This commit is contained in:
cbb
2026-06-25 01:11:31 +08:00
parent dc91883f1e
commit 86c0ec730b
8 changed files with 93 additions and 9 deletions
+39 -1
View File
@@ -7,6 +7,7 @@
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/task_manager.h"
#include "sys/systick.h"
namespace {
struct TxFrame {
@@ -463,7 +464,44 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
tx_msg.tx_data[i] = data[i];
}
const uint8_t mailbox = can_message_transmit(kChannels[channelIndex].canPeriph, &tx_msg);
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
/* 邮箱全占时的恢复处理:
* 当目标控制器初始化时 auto_retrans = ENABLE,且总线上没有其它节点回 ACK,
* 已入邮箱的帧会无限自动重传,TME 位永不置回,导致后续帧一律 CAN_NOMAILBOX。
*
* 改良策略(两段式,尽量不丢帧):
* 1) 先短自旋 ~2 ms 轮询邮箱状态。健康总线上 1 Mbps + 8 字节帧的实际发送
* 仅约 130 µs,正常重传几百 µs 内即可 drain,绝大多数“偶发 NOMAILBOX”
* 场景到这一步就已经恢复,不会触发 abort,也就不会丢帧。
* 2) 若 2 ms 后仍全部 PENDING(典型:物理层不通、TCAN1057A 没焊或 CAN_L/H
* 断线导致 ACK Error 死循环),只 abort 第一个 PENDING 邮箱腾一个位置,
* 最坏丢 1 帧而不是 3 帧,并通过 s_diag.tx_abort_count 暴露给串口 diag,
* 上层(含 SPI 桥)仍能把后续帧正常下发。 */
if (mailbox == CAN_NOMAILBOX) {
constexpr uint8_t kDrainPolls = 2U;
for (uint8_t poll = 0U; poll < kDrainPolls && mailbox == CAN_NOMAILBOX; ++poll) {
SysTickTimer::delayMs(1U);
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
}
if (mailbox == CAN_NOMAILBOX) {
/* 只 abort 一个 PENDING 邮箱:找到首个 stop 成功的就停手,
* 不再扫描后续邮箱,把丢帧上限收敛到 1。stop 失败的邮箱
* (硬件正在物理发送)继续尝试下一个,避免一次 abort 都没成功。 */
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) {
s_diag.tx_abort_count++;
break;
}
}
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
if (mailbox != CAN_NOMAILBOX) {
s_diag.tx_count++;
s_diag.last_tx_id = ext_id;