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:
@@ -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;
|
||||
|
||||
@@ -2,9 +2,33 @@
|
||||
#include "device/app_spi_command.h"
|
||||
#include "device/app_spi_command_defs.h"
|
||||
#include "device/canfd.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kCanLogMaxDlc = 64U;
|
||||
|
||||
void postCanSendLog(AppSpiCommand &bus, const char *result,
|
||||
uint8_t can_ch, uint32_t can_id,
|
||||
const uint8_t *can_data, uint8_t can_dlc)
|
||||
{
|
||||
const uint8_t logged_dlc = (can_dlc <= kCanLogMaxDlc) ? can_dlc : kCanLogMaxDlc;
|
||||
char data_hex[(kCanLogMaxDlc * 2U) + 1U] = {};
|
||||
for (uint8_t i = 0U; i < logged_dlc; ++i) {
|
||||
(void)snprintf(&data_hex[i * 2U], sizeof(data_hex) - (i * 2U), "%02X", can_data[i]);
|
||||
}
|
||||
|
||||
char log_msg[AppSpiCommand::kLogBufSize] = {};
|
||||
(void)snprintf(log_msg, sizeof(log_msg),
|
||||
"CAN_SEND CH%u ID=0x%08lX DLC=%u DATA=%s -> %s",
|
||||
static_cast<unsigned>(can_ch),
|
||||
static_cast<unsigned long>(can_id),
|
||||
static_cast<unsigned>(can_dlc),
|
||||
(logged_dlc > 0U) ? data_hex : "-",
|
||||
result ? result : "UNKNOWN");
|
||||
bus.postLog(log_msg);
|
||||
}
|
||||
|
||||
class SpiCanBridgeHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
bool canHandle(uint8_t frame_type) const override
|
||||
@@ -15,7 +39,11 @@ public:
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
if (ctx.payload_len < 6U) {
|
||||
bus.postLog("[SPI-APP] CAN_SEND 载荷太短 → ERROR");
|
||||
char log_msg[AppSpiCommand::kLogBufSize] = {};
|
||||
(void)snprintf(log_msg, sizeof(log_msg),
|
||||
"CAN_SEND payload too short: len=%u -> ERROR",
|
||||
static_cast<unsigned>(ctx.payload_len));
|
||||
bus.postLog(log_msg);
|
||||
uint8_t err = 0xE0U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
@@ -31,7 +59,7 @@ public:
|
||||
const uint8_t can_dlc = static_cast<uint8_t>(ctx.payload_len - 5U);
|
||||
|
||||
if (can_ch >= 3U) {
|
||||
bus.postLogFmt("[SPI-APP] CAN_SEND 通道非法: CH%d → ERROR", can_ch);
|
||||
postCanSendLog(bus, "ERROR(channel)", can_ch, can_id, can_data, can_dlc);
|
||||
uint8_t err = 0xE1U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
@@ -40,12 +68,12 @@ public:
|
||||
|
||||
const bool ok = CanFd::transmitFrame(can_ch, can_id, can_data, can_dlc);
|
||||
if (ok) {
|
||||
bus.postLogFmt("[SPI-APP] CAN_SEND CH%d → OK", can_ch);
|
||||
postCanSendLog(bus, "OK", can_ch, can_id, can_data, can_dlc);
|
||||
const uint8_t ack = 0x00U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &ack, 1U);
|
||||
} else {
|
||||
bus.postLogFmt("[SPI-APP] CAN_SEND CH%d → BUSY", can_ch);
|
||||
postCanSendLog(bus, "BUSY", can_ch, can_id, can_data, can_dlc);
|
||||
uint8_t err = 0xE2U;
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspError, &err, 1U);
|
||||
|
||||
@@ -16,7 +16,6 @@ public:
|
||||
|
||||
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
|
||||
{
|
||||
|
||||
uint8_t alive_payload[19];
|
||||
std::memset(alive_payload, 0, sizeof(alive_payload));
|
||||
alive_payload[0] = 0xA1U; /* magic */
|
||||
|
||||
@@ -92,6 +92,19 @@ static bool shouldIgnoreTransaction(const uint8_t *data, uint8_t len)
|
||||
!(len == 1U && data[0] == CONFIG_SPI_APP_OTA_BOOT_CMD);
|
||||
}
|
||||
|
||||
static bool isFlashRelatedFrame(uint8_t frame_type)
|
||||
{
|
||||
switch (frame_type) {
|
||||
case kTypeOtaReq:
|
||||
case kTypeOtaData:
|
||||
case kTypeOtaCancel:
|
||||
case kTypeBootReq:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* CRC8 / 帧构建 / 帧解析(与 BOOT SpiOta 一致)
|
||||
* ============================================================ */
|
||||
@@ -328,6 +341,10 @@ void AppSpiCommand::processCommand()
|
||||
uint16_t payload_len = 0U;
|
||||
|
||||
if (spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len)) {
|
||||
if (!isFlashRelatedFrame(frame_type)) {
|
||||
postLogFmt("[SPI-APP] 收到非刷写 SPI type=0x%02X", frame_type);
|
||||
}
|
||||
|
||||
/* 合法帧:按 TYPE 分发给已注册命令处理器 */
|
||||
uint16_t tx_len = 0U;
|
||||
bool handled = false;
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
|
||||
static constexpr uint16_t kRxBufSize = 128U;
|
||||
static constexpr uint16_t kTxBufSize = 64U;
|
||||
static constexpr uint16_t kLogBufSize = 128U;
|
||||
static constexpr uint16_t kLogBufSize = 256U;
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kMaxCommands = 8U;
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
uint32_t rx_fifo_full_count; /**< 硬件 FIFO0 进入满状态的次数。 */
|
||||
uint32_t rx_fifo_overfull_count; /**< 硬件 FIFO0 发生 overfull 的次数。 */
|
||||
uint32_t tx_drop_count; /**< 发送 FIFO 满导致丢弃的报文计数。 */
|
||||
uint32_t tx_abort_count; /**< 因邮箱全占主动中止占用邮箱的次数(典型:总线无 ACK 导致自动重传卡死)。 */
|
||||
uint32_t last_rx_id; /**< 最近一次接收报文的标识符。 */
|
||||
uint32_t last_tx_id; /**< 最近一次发送报文的标识符。 */
|
||||
uint8_t last_rx_channel; /**< 最近一次接收报文所在的 CAN 通道号(0/1/2)。 */
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
/** @brief APP 补丁版本号。 */
|
||||
#define APP_FW_VERSION_PATCH 1U
|
||||
/** @brief APP 构建编号。 */
|
||||
#define APP_FW_VERSION_BUILD 0U
|
||||
#define APP_FW_VERSION_BUILD 2U
|
||||
|
||||
/* ============================================================
|
||||
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
|
||||
|
||||
@@ -241,7 +241,7 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
static_cast<unsigned long>(vin_adc_mV / 1000U),
|
||||
static_cast<unsigned long>(vin_adc_mV % 1000U));
|
||||
#else
|
||||
printf("[APP] uptime=%lus valid=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, sw_drop=%lu, hw_full=%lu, hw_over=%lu, tx_drop=%lu, can_err=%lu\r\n",
|
||||
printf("[APP] uptime=%lus valid=%u, temp=%ld.%02ld C, vdda=%lu.%03lu V, vin_adc=%lu.%03lu V, rx_ch=%u, rx_id=0x%08lX, rx_len=%u, tx_ch=%u, can_tx=%lu, can_rx=%lu, sw_drop=%lu, hw_full=%lu, hw_over=%lu, tx_drop=%lu, tx_abort=%lu, can_err=%lu\r\n",
|
||||
(unsigned long)g_app_uptime_sec,
|
||||
static_cast<unsigned int>(s.valid),
|
||||
static_cast<long>(temp_x100 / 100),
|
||||
@@ -260,6 +260,7 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
static_cast<unsigned long>(diag->rx_fifo_full_count),
|
||||
static_cast<unsigned long>(diag->rx_fifo_overfull_count),
|
||||
static_cast<unsigned long>(diag->tx_drop_count),
|
||||
static_cast<unsigned long>(diag->tx_abort_count),
|
||||
static_cast<unsigned long>(diag->error_count));
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user