feat(canfd): 修复CAN发送问题。实现CANFD发送完成中断处理和通道健康检查

- 添加CANFD发送完成中断处理函数,确保只有MTFNERR置位时才计数发送成功
- 新增通道健康检查功能,防止病态通道被持续重发的测试帧反复推回BUS_OFF
- 重构CANFD配置结构体,添加TX中断配置项并修正GPIO映射设置
- 移除原有的软件发送队列机制,改为直接硬件发送模式
- 更新项目配置文件中的CANFD源文件路径命名

fix(canfd): 修复CANFD发送计数和错误处理逻辑

- 修正发送计数只在硬件MTFNERR确认后增加,而非请求进入邮箱时
- 改进错误日志输出,添加详细的错误类型和通道状态信息
- 优化总线恢复机制,通过收发器静默-唤醒协助硬件自动恢复
- 添加条件编译开关控制启动时自检和回环测试功能
This commit is contained in:
cbb
2026-07-11 17:52:05 +08:00
parent 3691cf2aba
commit 10989939e9
9 changed files with 361 additions and 265 deletions
+15 -7
View File
@@ -73,6 +73,12 @@ constexpr uint8_t CONFIG_SPI_APP_OTA_BOOT_ACK_ERROR = 0xE2U;
constexpr uint32_t CONFIG_SPI_READY_PORT = GPIOB;
/** @brief SPI Ready 握手输出引脚。 */
constexpr uint32_t CONFIG_SPI_READY_PIN = GPIO_PIN_6;
/** @brief ESP32 硬件复位控制端口(PC9 接 ESP32 EN/RESET)。 */
constexpr uint32_t CONFIG_ESP32_RESET_PORT = GPIOC;
/** @brief ESP32 硬件复位控制引脚。 */
constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9;
/* ============================================================
* CAN-FD 位时序配置(三路 CAN0/CAN1/CAN2 共用)
* 时钟链: SYSCLK=180MHz → AHB=/1=180MHz(RCU_MODIFY_UP 强制)
@@ -108,6 +114,11 @@ constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ;
* @note 保留 #define:canfd.cpp 中用 #if 做编译期分支。 */
#define CONFIG_CAN_FD_BRS_ENABLE 1U
/** @brief 启动时向物理总线发送 classic/FD/BRS 探测帧;正常固件默认关闭。 */
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
/** @brief 启动时切换控制器到内部回环执行自检;正常固件默认关闭。 */
#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0
/** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
/** @brief CAN1 通道使能开关。 */
@@ -121,11 +132,6 @@ constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID;
constexpr uint32_t CONFIG_CAN_RX_FILTER_MASK = 0x7FFU;
/** @brief 单次 CAN 接收解析任务最多消费的软件队列帧数。 */
constexpr uint32_t CONFIG_CAN_RX_PARSE_BUDGET = 48U;
/** @brief CAN 状态载荷中调试标识字符 0~3。 */
constexpr uint8_t CONFIG_CAN_STATUS_TAG0 = 0x43U;
constexpr uint8_t CONFIG_CAN_STATUS_TAG1 = 0x41U;
constexpr uint8_t CONFIG_CAN_STATUS_TAG2 = 0x4EU;
constexpr uint8_t CONFIG_CAN_STATUS_TAG3 = 0x46U;
/** @brief 用户调度定时器实例。 */
constexpr uint32_t CONFIG_SCHED_TIMER_INSTANCE = TIMER2;
@@ -151,8 +157,10 @@ constexpr uint32_t CONFIG_TASK_PERIOD_VREF_SAMPLE_TICKS = 5U;
constexpr uint32_t CONFIG_TASK_PERIOD_TEMP_SAMPLE_TICKS = 10U;
/** @brief 调度基准节拍:10ms 计数周期,单位 100us tick。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_RX_TICKS = 100U;
/** @brief 调度基准节拍:CAN 发送/状态灯周期,单位 100us tick。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 1000U;
/** @brief 调度基准节拍:CAN 周期发送任务周期,10 tick = 1ms。 */
constexpr uint32_t CONFIG_TASK_PERIOD_CAN_TX_TICKS = 10U;
/** @brief 调度基准节拍:状态灯任务周期,1000 tick = 100ms。 */
constexpr uint32_t CONFIG_TASK_PERIOD_STATUS_LED_TICKS = 1000U;
/** @brief 调度基准节拍:心跳/诊断打印周期,单位 100us tick。 */
constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U;
+4
View File
@@ -34,6 +34,10 @@ void AppInitializer::init()
nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP);
printf("[APP] init: SysTick...\r\n");
SysTickTimer::initMs();
/* ESP32 EN/RESET (PC9) 直接配置为推挽输出高电平,保持 ESP32 正常运行 */
rcu_periph_clock_enable(RCU_GPIOC);
gpio_bit_set(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN);
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_ESP32_RESET_PIN);
printf("[APP] init: USART0...\r\n");
Usart0::configurePins();
Usart0::init();
+8 -1
View File
@@ -31,6 +31,7 @@ void TaskManager::init()
g_scheduler.div_temp_sample = 0U;
g_scheduler.div_can_rx = 0U;
g_scheduler.div_can_tx = 0U;
g_scheduler.div_status_led = 0U;
g_scheduler.div_heartbeat = 0U;
}
@@ -94,8 +95,14 @@ void TaskManager::tick100us()
if(++g_scheduler.div_can_tx >= CONFIG_TASK_PERIOD_CAN_TX_TICKS) {
g_scheduler.div_can_tx = 0U;
g_scheduler.tick_1ms++;
set(1UL << TASK_CAN_TX_SEND);
}
if(++g_scheduler.div_status_led >= CONFIG_TASK_PERIOD_STATUS_LED_TICKS) {
g_scheduler.div_status_led = 0U;
g_scheduler.tick_100ms++;
set((1UL << TASK_STATUS_LED) | (1UL << TASK_CAN_TX_SEND));
set(1UL << TASK_STATUS_LED);
}
if(++g_scheduler.div_heartbeat >= CONFIG_TASK_PERIOD_HEARTBEAT_TICKS) {
+33 -51
View File
@@ -144,54 +144,48 @@ void TaskRunner::taskAdcProcess()
void TaskRunner::taskCanRxPoll() { CanFd::poll(); }
/**
* @brief 执行预留的 CAN 发送执行任务。
* @brief 执行 CAN 周期发送任务:每 1ms 在每个使能通道上发送一帧 CANFD 测试帧。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前阶段不做周期性发送调度;若后续需要显式验证某一路发送能力,可由调用方先 prepare,再直接调用 `CanFd::sendPrepared()`。
* @note 帧格式:扩展帧 29-bit ID,CAN-FD,DLC=64,BRS 受 CONFIG_CAN_FD_BRS_ENABLE 控制。
* 载荷布局:
* [0] 固定标识 0xC0
* [1] 通道号
* [2..5] 发送序号(小端 uint32)
* [6..7] 系统运行秒数低 16 位
* [8..63] 填充 0xA0 + (index & 0x0F)
* periodic=true:仅允许一个邮箱保持自动重发,不主动 abort 旧帧,
* 跳过 TEC 保护以匹配诊断行为。
*/
void TaskRunner::taskCanTxSend()
{
/* 启动后每 100ms 轮流发送一路 CAN 通道的 CANFD+BRS 64 字节测试帧。
* 三路通道时分复用:CH0→CH1→CH2→CH0→...,每 100ms 只发一路,
* 避免多路同时发送导致总线仲裁冲突(当多路并接到同一总线时)。
* 帧格式:扩展帧 29-bit ID,CANFD+BRS,DLC=64。
* 载荷布局:
* [0] 通道号 0/1/2
* [1] 保留
* [2..5] 发送序号(小端 uint32,每通道独立递增)
* [6..9] 系统运行秒数 g_app_uptime_sec(小端 uint32)
* [10..63] 递增填充,按通道偏移便于抓包区分 */
static uint32_t seq[3] = {0U, 0U, 0U};
static uint8_t round = 0U; /* 0=CH0, 1=CH1, 2=CH2, 轮流 */
static uint32_t seq = 0U;
const uint8_t ch = round;
round = (round + 1U) % 3U;
for (uint8_t ch = 0U; ch < 3U; ++ch) {
if (!CanFd::isChannelEnabled(ch)) {
continue;
}
/* 跳过已禁用的通道 */
if (!CanFd::isChannelEnabled(ch)) {
return;
uint8_t data[64];
data[0] = 0xC0U;
data[1] = ch;
const uint32_t s = seq;
data[2] = static_cast<uint8_t>(s & 0xFFU);
data[3] = static_cast<uint8_t>((s >> 8) & 0xFFU);
data[4] = static_cast<uint8_t>((s >> 16) & 0xFFU);
data[5] = static_cast<uint8_t>((s >> 24) & 0xFFU);
data[6] = static_cast<uint8_t>(g_app_uptime_sec & 0xFFU);
data[7] = static_cast<uint8_t>((g_app_uptime_sec >> 8) & 0xFFU);
for (uint8_t i = 8U; i < 64U; ++i) {
data[i] = static_cast<uint8_t>(0xA0U + (i & 0x0FU));
}
const uint32_t extId = 0x18FF5000U | static_cast<uint32_t>(ch);
if (CanFd::transmitFrame(ch, extId, data, sizeof(data), true)) {
seq++;
}
}
uint8_t data[64];
data[0] = ch;
data[1] = 0U;
const uint32_t s = seq[ch];
data[2] = static_cast<uint8_t>(s & 0xFFU);
data[3] = static_cast<uint8_t>((s >> 8) & 0xFFU);
data[4] = static_cast<uint8_t>((s >> 16) & 0xFFU);
data[5] = static_cast<uint8_t>((s >> 24) & 0xFFU);
data[6] = static_cast<uint8_t>(g_app_uptime_sec & 0xFFU);
data[7] = static_cast<uint8_t>((g_app_uptime_sec >> 8) & 0xFFU);
data[8] = static_cast<uint8_t>((g_app_uptime_sec >> 16) & 0xFFU);
data[9] = static_cast<uint8_t>((g_app_uptime_sec >> 24) & 0xFFU);
for (uint8_t i = 10U; i < 64U; ++i) {
data[i] = static_cast<uint8_t>(i + ch * 0x40U);
}
const uint32_t extId = 0x1FFFFF00U | static_cast<uint32_t>(ch);
CanFd::transmitFrame(ch, extId, data, 64U);
seq[ch]++;
}
/**
@@ -223,18 +217,6 @@ void TaskRunner::taskCanRxParse()
}
}
/**
* @brief 执行预留的 CAN 发送准备任务。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 当前阶段三路 CANFD 仅要求底层收发能力可用,不做周期性业务状态广播。
*/
void TaskRunner::taskCanTxPrepare()
{
/* 当前阶段不做周期性业务状态广播。 */
}
/**
* @brief 执行状态灯维护任务。
* @param[in] 无。
+1 -3
View File
@@ -39,9 +39,7 @@ private:
static void taskCanRxPoll();
/** @brief 解析并消费接收帧。 */
static void taskCanRxParse();
/** @brief 预留的 CAN 发送准备任务;当前不主动生成周期业务帧。 */
static void taskCanTxPrepare();
/** @brief 预留的 CAN 发送执行任务;当前仅保留接口,不参与周期调度。 */
/** @brief CAN 周期发送任务;每 1ms 每通道发送一帧测试报文。 */
static void taskCanTxSend();
/** @brief 状态灯维护任务(当前预留)。 */
static void taskStatusLed();