feat(canfd): 修复CAN发送问题。实现CANFD发送完成中断处理和通道健康检查
- 添加CANFD发送完成中断处理函数,确保只有MTFNERR置位时才计数发送成功 - 新增通道健康检查功能,防止病态通道被持续重发的测试帧反复推回BUS_OFF - 重构CANFD配置结构体,添加TX中断配置项并修正GPIO映射设置 - 移除原有的软件发送队列机制,改为直接硬件发送模式 - 更新项目配置文件中的CANFD源文件路径命名 fix(canfd): 修复CANFD发送计数和错误处理逻辑 - 修正发送计数只在硬件MTFNERR确认后增加,而非请求进入邮箱时 - 改进错误日志输出,添加详细的错误类型和通道状态信息 - 优化总线恢复机制,通过收发器静默-唤醒协助硬件自动恢复 - 添加条件编译开关控制启动时自检和回环测试功能
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# A project translates into one executable or library.
|
||||
# A project translates into one executable or library.
|
||||
project:
|
||||
|
||||
groups:
|
||||
@@ -25,7 +25,7 @@ project:
|
||||
- file: ./device/adc.h
|
||||
- file: ./device/adc.cpp
|
||||
- file: ./device/canfd.h
|
||||
- file: ./device/canfd.cpp
|
||||
- file: ./device/CANFD.cpp
|
||||
- file: ./device/timer.h
|
||||
- file: ./device/timer.cpp
|
||||
- file: ./device/usart0.h
|
||||
|
||||
+276
-177
@@ -7,22 +7,11 @@
|
||||
#include "device/canfd.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/critical.h"
|
||||
#include "sys/systick.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
struct TxFrame {
|
||||
can_trasnmit_message_struct tx_message;
|
||||
uint8_t payload[64];
|
||||
};
|
||||
|
||||
struct TxQueue {
|
||||
TxFrame frames[4];
|
||||
uint8_t head;
|
||||
uint8_t tail;
|
||||
uint8_t count;
|
||||
};
|
||||
|
||||
struct CanChannelConfig {
|
||||
uint32_t canPeriph;
|
||||
rcu_periph_enum canRcu;
|
||||
@@ -32,14 +21,15 @@ struct CanChannelConfig {
|
||||
uint32_t rxPin;
|
||||
uint32_t slpPort;
|
||||
uint32_t slpPin;
|
||||
IRQn_Type txIrq;
|
||||
IRQn_Type rx0Irq;
|
||||
uint8_t filterNumber;
|
||||
};
|
||||
|
||||
constexpr CanChannelConfig kChannels[] = {
|
||||
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_RX0_IRQn, 0U },
|
||||
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_14, CAN1_RX0_IRQn, 14U },
|
||||
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_RX0_IRQn, 2U },
|
||||
{ CAN0, RCU_CAN0, GPIOA, GPIO_PIN_12, GPIOA, GPIO_PIN_11, GPIOA, GPIO_PIN_8, CAN0_TX_IRQn, CAN0_RX0_IRQn, 0U },
|
||||
{ CAN1, RCU_CAN1, GPIOB, GPIO_PIN_13, GPIOB, GPIO_PIN_12, GPIOB, GPIO_PIN_14, CAN1_TX_IRQn, CAN1_RX0_IRQn, 14U },
|
||||
{ CAN2, RCU_CAN2, GPIOE, GPIO_PIN_1, GPIOE, GPIO_PIN_0, GPIOB, GPIO_PIN_9, CAN2_TX_IRQn, CAN2_RX0_IRQn, 2U },
|
||||
};
|
||||
|
||||
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
|
||||
@@ -51,9 +41,16 @@ constexpr bool kChannelEnabled[kChannelCount] = {
|
||||
CONFIG_CAN_CH2_ENABLE,
|
||||
};
|
||||
|
||||
constexpr uint8_t kTxQueueDepth = 4U;
|
||||
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
|
||||
|
||||
static const char *canLastErrorName(uint32_t errReg)
|
||||
{
|
||||
static const char *const names[] = {
|
||||
"NONE", "STUFF", "FORM", "ACK", "BIT_RECESSIVE", "BIT_DOMINANT", "CRC", "SOFTWARE"
|
||||
};
|
||||
return names[(errReg >> 4U) & 0x07U];
|
||||
}
|
||||
|
||||
struct CanSelfTestResult {
|
||||
bool txReq;
|
||||
uint8_t mailbox;
|
||||
@@ -158,7 +155,6 @@ CanFd::ChannelStats CanFd::s_channelStats[3]{};
|
||||
CanFd::ChannelFeedback CanFd::s_feedback[3]{};
|
||||
uint32_t CanFd::s_feedbackSeq = 0U;
|
||||
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
||||
static TxQueue s_txQueues[3]{};
|
||||
|
||||
/**
|
||||
* @brief 查询指定通道是否已使能。
|
||||
@@ -181,9 +177,16 @@ void CanFd::initGpio()
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_GPIOE);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
#ifdef GPIO_CAN0_PARTIAL_REMAP
|
||||
/* Bootloader uses CAN0 on PB8/PB9 and jumps to APP without a system reset,
|
||||
* so AFIO_PCF0 survives can_deinit(). Restore APP mapping PA11/P12 here. */
|
||||
gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, DISABLE);
|
||||
#endif
|
||||
#ifdef GPIO_CAN0_FULL_REMAP
|
||||
gpio_pin_remap_config(GPIO_CAN0_FULL_REMAP, DISABLE);
|
||||
#endif
|
||||
#ifdef GPIO_CAN1_REMAP
|
||||
/* CAN1_REMAP=0: CAN1_RX/PB12, CAN1_TX/PB13
|
||||
* GD32E50x_用户手册_Rev2.2.pdf AFIO_PCF0 bit22 */
|
||||
/* CAN1_REMAP=0 selects PB12=RX and PB13=TX on GD32E508. */
|
||||
gpio_pin_remap_config(GPIO_CAN1_REMAP, DISABLE);
|
||||
#endif
|
||||
#ifdef AFIO_PE0_CAN2_CFG
|
||||
@@ -192,11 +195,9 @@ void CanFd::initGpio()
|
||||
#ifdef AFIO_PE1_CAN2_CFG
|
||||
gpio_afio_port_config(AFIO_PE1_CAN2_CFG, ENABLE);
|
||||
#endif
|
||||
/* CAN0/CAN1 引脚选择由 AFIO_PCF0 的 remap 位控制:
|
||||
* CAN0_REMAP[1:0]=00(复位默认) → CAN0_RX/PA11, CAN0_TX/PA12 (bit14:13)
|
||||
* CAN1_REMAP=0(复位默认) → CAN1_RX/PB12, CAN1_TX/PB13 (bit22)
|
||||
* AFIO_PCFA/PCFB 的 AFCFG 位不含 CAN0/CAN1 选项(GD32E50x_用户手册_Rev2.2.pdf
|
||||
* 第8.5.17/8.5.18节),不可用 AFCFG 配置 CAN0/CAN1。 */
|
||||
|
||||
printf("[CAN-GPIO] AFIO_PCF0=0x%08lX CAN0=PA11/PA12 CAN1=PB12/PB13\r\n",
|
||||
static_cast<unsigned long>(AFIO_PCF0));
|
||||
|
||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||
const auto &ch = kChannels[i];
|
||||
@@ -302,83 +303,6 @@ bool CanFd::initFdMode(uint32_t canPeriph)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 构造内部发送缓冲中的状态帧内容。
|
||||
* @param[in] status 指向状态载荷的指针。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前实现中状态帧构造逻辑已内联至 `enqueueTxFrame()`,此函数仅保留接口兼容性。
|
||||
*/
|
||||
void CanFd::buildStatusFrame(const StatusPayload *status)
|
||||
{
|
||||
if(status == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t temp_x100 = static_cast<int16_t>(status->temperature * 100.0f);
|
||||
const uint16_t vdda_x1000 = static_cast<uint16_t>(status->vdda * 1000.0f);
|
||||
|
||||
auto &frame = s_txQueues[0].frames[0];
|
||||
auto &p = frame.payload;
|
||||
p[0] = static_cast<uint8_t>(status->temp_raw & 0xFFU);
|
||||
p[1] = static_cast<uint8_t>((status->temp_raw >> 8) & 0xFFU);
|
||||
p[2] = static_cast<uint8_t>(status->vref_raw & 0xFFU);
|
||||
p[3] = static_cast<uint8_t>((status->vref_raw >> 8) & 0xFFU);
|
||||
p[4] = static_cast<uint8_t>(temp_x100 & 0xFF);
|
||||
p[5] = static_cast<uint8_t>((temp_x100 >> 8) & 0xFF);
|
||||
p[6] = static_cast<uint8_t>(vdda_x1000 & 0xFFU);
|
||||
p[7] = static_cast<uint8_t>((vdda_x1000 >> 8) & 0xFFU);
|
||||
p[8] = CONFIG_CAN_STATUS_TAG0;
|
||||
p[9] = CONFIG_CAN_STATUS_TAG1;
|
||||
p[10] = CONFIG_CAN_STATUS_TAG2;
|
||||
p[11] = CONFIG_CAN_STATUS_TAG3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向指定 CAN 通道的发送 FIFO 入队一帧状态报文。
|
||||
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
|
||||
* @param[in] status 指向状态载荷的指针。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功入队,false 表示参数无效或该通道发送队列已满。
|
||||
* @note 仅完成软件发送队列入队,不会立即触发硬件发送。
|
||||
*/
|
||||
bool CanFd::enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status)
|
||||
{
|
||||
if(status == nullptr || channelIndex >= kChannelCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &queue = s_txQueues[channelIndex];
|
||||
if(queue.count >= kTxQueueDepth) {
|
||||
s_diag.tx_drop_count++;
|
||||
s_channelStats[channelIndex].error_count++;
|
||||
return false;
|
||||
}
|
||||
|
||||
buildStatusFrame(status);
|
||||
|
||||
auto &frame = queue.frames[queue.tail];
|
||||
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &frame.tx_message);
|
||||
frame.tx_message.tx_efid = CONFIG_CAN_STATUS_ID;
|
||||
frame.tx_message.tx_ff = CAN_FF_EXTENDED;
|
||||
frame.tx_message.tx_ft = CAN_FT_DATA;
|
||||
frame.tx_message.tx_dlen = CANFD_STATUS_DLEN;
|
||||
frame.tx_message.fd_flag = ENABLE;
|
||||
frame.tx_message.fd_brs = ENABLE;
|
||||
frame.tx_message.fd_esi = CAN_ESI_DOMINANT;
|
||||
|
||||
const auto &src = s_txQueues[0].frames[0].payload;
|
||||
auto &p = frame.payload;
|
||||
for(uint8_t j = 0; j < CANFD_STATUS_DLEN; ++j) {
|
||||
p[j] = src[j];
|
||||
frame.tx_message.tx_data[j] = p[j];
|
||||
}
|
||||
|
||||
queue.tail = static_cast<uint8_t>((queue.tail + 1U) % kTxQueueDepth);
|
||||
queue.count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化三路 CANFD 控制器、过滤器、中断与软件队列。
|
||||
* @param[in] 无。
|
||||
@@ -399,6 +323,10 @@ void CanFd::init()
|
||||
|
||||
initGpio();
|
||||
|
||||
printf("[CAN-CLOCK] SYS=%luHz APB1=%luHz nominal=1Mbps data=2Mbps\r\n",
|
||||
static_cast<unsigned long>(SystemCoreClock),
|
||||
static_cast<unsigned long>(rcu_clock_freq_get(CK_APB1)));
|
||||
|
||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||
const auto &ch = kChannels[i];
|
||||
if (!kChannelEnabled[i]) {
|
||||
@@ -480,15 +408,14 @@ void CanFd::init()
|
||||
static_cast<unsigned long>(tecAfterExit),
|
||||
static_cast<unsigned long>(recAfterExit));
|
||||
|
||||
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
|
||||
CAN_TSTAT(ch.canPeriph) = CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2;
|
||||
can_interrupt_enable(ch.canPeriph, CAN_INT_TME | CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
|
||||
nvic_irq_enable(ch.txIrq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
|
||||
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
|
||||
|
||||
s_rxQueues[i].head = 0U;
|
||||
s_rxQueues[i].tail = 0U;
|
||||
s_rxQueues[i].count = 0U;
|
||||
s_txQueues[i].head = 0U;
|
||||
s_txQueues[i].tail = 0U;
|
||||
s_txQueues[i].count = 0U;
|
||||
}
|
||||
|
||||
s_nextConsumeChannel = 0U;
|
||||
@@ -554,6 +481,7 @@ void CanFd::init()
|
||||
}
|
||||
|
||||
/* ===== Self-test (NORMAL mode, 发到物理总线测 ACK) ===== */
|
||||
#if (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE != 0)
|
||||
printf("=== CAN SELF-TEST (normal mode) ===\r\n");
|
||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||
if (!kChannelEnabled[i]) {
|
||||
@@ -583,9 +511,13 @@ void CanFd::init()
|
||||
static_cast<unsigned long>(fd.tec));
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
printf("=== CAN SELF-TEST SKIPPED (CONFIG_CAN_STARTUP_BUS_TEST_ENABLE=0) ===\r\n");
|
||||
#endif
|
||||
|
||||
|
||||
/* ===== Loopback 回环测试 (内部 TX→RX,不走物理总线) ===== */
|
||||
#if (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE != 0)
|
||||
printf("=== CAN LOOPBACK (internal) ===\r\n");
|
||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||
const auto &ch = kChannels[i];
|
||||
@@ -723,6 +655,9 @@ void CanFd::init()
|
||||
static_cast<unsigned long>(CAN_CTL(ch.canPeriph)));
|
||||
}
|
||||
printf("========================\r\n");
|
||||
#else
|
||||
printf("=== CAN LOOPBACK SKIPPED (CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE=0) ===\r\n");
|
||||
#endif
|
||||
|
||||
/* ===== 初始化后全寄存器快照:诊断 CAN0/CAN1 不收帧 ===== */
|
||||
printf("=== POST-INIT FULL REG DUMP ===\r\n");
|
||||
@@ -802,68 +737,6 @@ void CanFd::poll()
|
||||
/* 兼容保留:三通道接收已由 RX0 中断驱动 */
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据状态载荷为三路发送队列批量准备状态报文。
|
||||
* @param[in] status 指向状态载荷的指针;为空时直接返回。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前默认不会周期性调用;仅在外部显式触发发送测试或业务发送时使用。
|
||||
*/
|
||||
void CanFd::prepareStatus(const StatusPayload *status)
|
||||
{
|
||||
if(status == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||
(void)enqueueTxFrame(static_cast<uint8_t>(i), status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 根据状态载荷仅为指定通道准备一帧状态报文。
|
||||
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
|
||||
* @param[in] status 指向状态载荷的指针;为空时返回失败。
|
||||
* @param[out] 无。
|
||||
* @retval bool true 表示成功入队,false 表示参数非法或发送队列已满。
|
||||
*/
|
||||
bool CanFd::prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status)
|
||||
{
|
||||
return enqueueTxFrame(channelIndex, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将当前各通道已准备好的状态报文送入硬件邮箱发送。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 当前默认不会周期性调用;仅处理调用方显式准备过的发送队列内容。
|
||||
*/
|
||||
void CanFd::sendPrepared()
|
||||
{
|
||||
for(size_t i = 0; i < kChannelCount; ++i) {
|
||||
auto &queue = s_txQueues[i];
|
||||
if(queue.count == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &frame = queue.frames[queue.head];
|
||||
const uint8_t mailbox = can_message_transmit(kChannels[i].canPeriph, &frame.tx_message);
|
||||
if(mailbox != CAN_NOMAILBOX) {
|
||||
s_diag.tx_count++;
|
||||
s_diag.last_payload_length = frame.tx_message.tx_dlen;
|
||||
s_diag.last_tx_id = frame.tx_message.tx_sfid;
|
||||
s_diag.last_tx_channel = static_cast<uint8_t>(i);
|
||||
s_channelStats[i].tx_count++;
|
||||
queue.head = static_cast<uint8_t>((queue.head + 1U) % kTxQueueDepth);
|
||||
queue.count--;
|
||||
} else {
|
||||
s_diag.error_count++;
|
||||
s_channelStats[i].error_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询三路 CAN 通道是否存在待消费接收报文。
|
||||
* @param[in] 无。
|
||||
@@ -937,11 +810,14 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
|
||||
* @param[in] ext_id 扩展帧 ID(29 位)。
|
||||
* @param[in] data 数据负载指针。
|
||||
* @param[in] dlc 数据长度(1~64)。
|
||||
* @retval bool true 表示成功入队硬件邮箱,false 表示参数无效或邮箱已满。
|
||||
* @param[in] periodic 是否为周期性发送;true 时仅允许一个邮箱保持自动重发,
|
||||
* 不主动 abort 旧帧,且跳过 TEC 保护以匹配诊断行为。
|
||||
* @retval bool true 表示发送请求已进入硬件邮箱,false 表示参数无效或邮箱已满。
|
||||
* @note 绕过软件发送队列,直接调用硬件发送;适用于 SPI 桥接等即时发送场景。
|
||||
* 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。
|
||||
*/
|
||||
bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc)
|
||||
const uint8_t *data, uint8_t dlc, bool periodic)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]
|
||||
|| data == nullptr || dlc == 0U || dlc > 64U) {
|
||||
@@ -952,13 +828,48 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
|
||||
const uint32_t errReg = CAN_ERR(canPeriph);
|
||||
if (errReg & CAN_ERR_BOERR) {
|
||||
static uint32_t s_lastBusOffLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU};
|
||||
if (s_lastBusOffLogSeq[channelIndex] != s_feedbackSeq) {
|
||||
printf("[CAN-TX-REJECT] CH%u BUS_OFF ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
|
||||
static_cast<unsigned>(channelIndex),
|
||||
static_cast<unsigned long>(errReg),
|
||||
static_cast<unsigned long>((errReg >> 4U) & 0x07U),
|
||||
canLastErrorName(errReg),
|
||||
static_cast<unsigned long>((errReg >> 16U) & 0xFFU),
|
||||
static_cast<unsigned long>((errReg >> 24U) & 0xFFU),
|
||||
static_cast<unsigned long>(CAN_TSTAT(canPeriph)));
|
||||
s_lastBusOffLogSeq[channelIndex] = s_feedbackSeq;
|
||||
}
|
||||
return false; /* 通道处于 BUS_OFF,不发送 */
|
||||
}
|
||||
const uint32_t tec = (errReg >> 16) & 0xFFU;
|
||||
if (tec > 96U) {
|
||||
if (tec > 96U && !periodic) {
|
||||
static uint32_t s_lastTecLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU};
|
||||
if (s_lastTecLogSeq[channelIndex] != s_feedbackSeq) {
|
||||
printf("[CAN-TX-REJECT] CH%u TEC_HIGH ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
|
||||
static_cast<unsigned>(channelIndex),
|
||||
static_cast<unsigned long>(errReg),
|
||||
static_cast<unsigned long>((errReg >> 4U) & 0x07U),
|
||||
canLastErrorName(errReg),
|
||||
static_cast<unsigned long>(tec),
|
||||
static_cast<unsigned long>((errReg >> 24U) & 0xFFU),
|
||||
static_cast<unsigned long>(CAN_TSTAT(canPeriph)));
|
||||
s_lastTecLogSeq[channelIndex] = s_feedbackSeq;
|
||||
}
|
||||
return false; /* TEC 过高(>75% 错误被动阈值),暂停发送保护通道 */
|
||||
}
|
||||
|
||||
/* 周期发送只允许一个邮箱保持自动重发。无 ACK 时同一帧会持续产生
|
||||
* 总线波形;不要每 1ms 再填满其余邮箱,更不能主动 abort 旧帧,否则
|
||||
* TX 完成中断会把软件中止累计为发送失败,掩盖真实 TEC/ERRN。 */
|
||||
if (periodic) {
|
||||
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
|
||||
if (can_transmit_states(canPeriph, mb) == CAN_TRANSMIT_PENDING) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
can_trasnmit_message_struct tx_msg{};
|
||||
can_struct_para_init(CAN_TX_MESSAGE_STRUCT, &tx_msg);
|
||||
tx_msg.tx_efid = ext_id;
|
||||
@@ -980,7 +891,7 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
|
||||
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
|
||||
|
||||
/* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
||||
/* 非周期业务发送在邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
||||
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
|
||||
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */
|
||||
if (mailbox == CAN_NOMAILBOX) {
|
||||
@@ -997,18 +908,50 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
}
|
||||
|
||||
if (mailbox != CAN_NOMAILBOX) {
|
||||
s_diag.tx_count++;
|
||||
s_diag.last_tx_id = ext_id;
|
||||
s_diag.last_tx_channel = channelIndex;
|
||||
s_diag.last_payload_length = dlc;
|
||||
s_channelStats[channelIndex].tx_count++;
|
||||
return true;
|
||||
}
|
||||
s_diag.error_count++;
|
||||
s_channelStats[channelIndex].error_count++;
|
||||
static uint32_t s_lastMailboxLogSeq[kChannelCount] = {0xFFFFFFFFU, 0xFFFFFFFFU, 0xFFFFFFFFU};
|
||||
if (s_lastMailboxLogSeq[channelIndex] != s_feedbackSeq) {
|
||||
const uint32_t errAfter = CAN_ERR(canPeriph);
|
||||
printf("[CAN-TX-REJECT] CH%u MAILBOX ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
|
||||
static_cast<unsigned>(channelIndex),
|
||||
static_cast<unsigned long>(errAfter),
|
||||
static_cast<unsigned long>((errAfter >> 4U) & 0x07U),
|
||||
canLastErrorName(errAfter),
|
||||
static_cast<unsigned long>((errAfter >> 16U) & 0xFFU),
|
||||
static_cast<unsigned long>((errAfter >> 24U) & 0xFFU),
|
||||
static_cast<unsigned long>(CAN_TSTAT(canPeriph)));
|
||||
s_lastMailboxLogSeq[channelIndex] = s_feedbackSeq;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查指定 CAN 通道当前是否处于健康可发送状态。
|
||||
* @param[in] channelIndex 通道索引 0/1/2。
|
||||
* @retval bool true=健康(未 BUS_OFF 且 TEC<=96),false=应避免发送。
|
||||
* @note 主要用于 taskCanTxSend 等周期性测试帧发送前判断,防止病态通道被
|
||||
* 持续重发的测试帧反复推回 BUS_OFF,干扰硬件 auto_bus_off_recovery。
|
||||
*/
|
||||
bool CanFd::isChannelHealthy(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
|
||||
const uint32_t errReg = CAN_ERR(canPeriph);
|
||||
if (errReg & CAN_ERR_BOERR) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t tec = (errReg >> 16) & 0xFFU;
|
||||
return tec <= 96U;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 CANFD 诊断统计信息只读指针。
|
||||
* @param[in] 无。
|
||||
@@ -1140,11 +1083,9 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
const uint32_t bridgePeriph = kChannels[bridgeCh].canPeriph;
|
||||
const bool ok = bridgeTransmitRaw(bridgePeriph, rx_message);
|
||||
if (ok) {
|
||||
s_diag.tx_count++;
|
||||
s_diag.last_tx_id = s_diag.last_rx_id;
|
||||
s_diag.last_tx_channel = bridgeCh;
|
||||
s_diag.last_payload_length = rx_message.rx_dlen;
|
||||
s_channelStats[bridgeCh].tx_count++;
|
||||
} else {
|
||||
s_diag.error_count++;
|
||||
s_channelStats[bridgeCh].error_count++;
|
||||
@@ -1255,6 +1196,47 @@ void CanFd::onRx0Interrupt()
|
||||
handleRxInterrupt(0U);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理指定通道的发送完成中断。
|
||||
* @param[in] channelIndex 目标 CAN 通道索引(0/1/2)。
|
||||
* @note 只有 MTFNERR 置位才表示完整发送且未出错;邮箱受理、仲裁丢失、
|
||||
* 主动中止和发送错误均不能计入网页“发送数”。
|
||||
*/
|
||||
void CanFd::handleTxInterrupt(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
|
||||
const uint32_t tstat = CAN_TSTAT(canPeriph);
|
||||
constexpr uint32_t finishedBits[] = {
|
||||
CAN_TSTAT_MTF0, CAN_TSTAT_MTF1, CAN_TSTAT_MTF2
|
||||
};
|
||||
constexpr uint32_t successBits[] = {
|
||||
CAN_TSTAT_MTFNERR0, CAN_TSTAT_MTFNERR1, CAN_TSTAT_MTFNERR2
|
||||
};
|
||||
constexpr uint32_t errorBits[] = {
|
||||
CAN_TSTAT_MTE0, CAN_TSTAT_MTE1, CAN_TSTAT_MTE2
|
||||
};
|
||||
|
||||
for (size_t mailbox = 0U; mailbox < 3U; ++mailbox) {
|
||||
if ((tstat & finishedBits[mailbox]) == 0U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((tstat & successBits[mailbox]) != 0U) {
|
||||
s_diag.tx_count++;
|
||||
s_channelStats[channelIndex].tx_count++;
|
||||
} else if ((tstat & errorBits[mailbox]) != 0U) {
|
||||
s_diag.error_count++;
|
||||
s_channelStats[channelIndex].error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷新三路 CAN 通道的 1Hz 反馈快照。
|
||||
* @note 在临界区内拷贝实时计数与最近一帧信息到 s_feedback;保证 SPI 命令读到一致快照。
|
||||
@@ -1284,13 +1266,27 @@ void CanFd::refreshFeedbackSnapshot()
|
||||
}
|
||||
|
||||
s_feedbackSeq++;
|
||||
/* 每 5 秒打印一次 seq 心跳,确认 refreshFeedbackSnapshot 在运行 */
|
||||
/* 每 5 秒打印一次 seq 心跳;诊断期间同时输出实际错误通道的最后错误类型。 */
|
||||
if (s_feedbackSeq % 5U == 0U) {
|
||||
printf("[CAN-FB] refresh seq=%lu rx0=%lu rx1=%lu rx2=%lu\r\n",
|
||||
(unsigned long)s_feedbackSeq,
|
||||
(unsigned long)s_channelStats[0].rx_count,
|
||||
(unsigned long)s_channelStats[1].rx_count,
|
||||
(unsigned long)s_channelStats[2].rx_count);
|
||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||
if (hw_tec[i] == 0U && hw_rec[i] == 0U
|
||||
&& (hw_err[i] & (CAN_ERR_WERR | CAN_ERR_PERR | CAN_ERR_BOERR)) == 0U) {
|
||||
continue;
|
||||
}
|
||||
printf("[CAN-HW-ERR] CH%u ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu TSTAT=0x%08lX\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long>(hw_err[i]),
|
||||
static_cast<unsigned long>((hw_err[i] >> 4U) & 0x07U),
|
||||
canLastErrorName(hw_err[i]),
|
||||
static_cast<unsigned long>(hw_tec[i]),
|
||||
static_cast<unsigned long>(hw_rec[i]),
|
||||
static_cast<unsigned long>(CAN_TSTAT(kChannels[i].canPeriph)));
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||
s_feedback[i].snapshot_seq = s_feedbackSeq;
|
||||
@@ -1320,13 +1316,16 @@ void CanFd::refreshFeedbackSnapshot()
|
||||
else if (cur & (1U << 3)) stateStr = "BUS_OFF";
|
||||
else if (cur & (1U << 2)) stateStr = "ERR_PASSIVE";
|
||||
else if (cur & (1U << 4)) stateStr = "ERR_WARN";
|
||||
printf("[CAN-BUS] CH%u: %s -> %s TEC=%lu REC=%lu seq=%lu\r\n",
|
||||
printf("[CAN-BUS] CH%u: %s -> %s ERR=0x%08lX ERRN=%lu(%s) TEC=%lu REC=%lu seq=%lu\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
(s_prevBusState[i] == 0xFF) ? "?" :
|
||||
(s_prevBusState[i] & (1U << 3)) ? "BUS_OFF" :
|
||||
(s_prevBusState[i] & (1U << 2)) ? "ERR_PASSIVE" :
|
||||
(s_prevBusState[i] & (1U << 4)) ? "ERR_WARN" : "NORMAL",
|
||||
stateStr,
|
||||
static_cast<unsigned long>(hw_err[i]),
|
||||
static_cast<unsigned long>((hw_err[i] >> 4U) & 0x07U),
|
||||
canLastErrorName(hw_err[i]),
|
||||
static_cast<unsigned long>(hw_tec[i]),
|
||||
static_cast<unsigned long>(hw_rec[i]),
|
||||
static_cast<unsigned long>(s_feedbackSeq));
|
||||
@@ -1335,6 +1334,97 @@ void CanFd::refreshFeedbackSnapshot()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行时 BUS_OFF / 严重错误被动状态软件恢复。
|
||||
* @note 由 1Hz 心跳任务调用。若某通道持续 2 秒以上处于 BUS_OFF 或 TEC>96,
|
||||
* 通过收发器静默-唤醒切换让总线获得 128×11 个连续隐性位,协助硬件
|
||||
* auto_bus_off_recovery 退出 BUS_OFF。本函数不执行 CAN 控制器 hardReset,
|
||||
* 避免破坏 CAN0/CAN1 共享的 filter 配置;若切换后仍不健康,说明物理层
|
||||
* 问题未消除,需要排查终端电阻/ACK/波特率/接线。
|
||||
*/
|
||||
void CanFd::recoverBusOffIfNeeded()
|
||||
{
|
||||
static uint32_t s_unhealthySinceSeq[kChannelCount] = {0U, 0U, 0U};
|
||||
static bool s_recoveryInProgress[kChannelCount] = {false, false, false};
|
||||
|
||||
for (size_t i = 0; i < kChannelCount; ++i) {
|
||||
if (!kChannelEnabled[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto &ch = kChannels[i];
|
||||
const uint32_t errReg = CAN_ERR(ch.canPeriph);
|
||||
const bool busOff = (errReg & CAN_ERR_BOERR) != 0U;
|
||||
const uint32_t tec = (errReg >> 16) & 0xFFU;
|
||||
const bool healthy = !busOff && (tec <= 96U);
|
||||
|
||||
if (healthy) {
|
||||
if (s_unhealthySinceSeq[i] != 0U) {
|
||||
printf("[CAN-RECOVER] CH%u healthy again (TEC=%lu) seq=%lu\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long>(tec),
|
||||
static_cast<unsigned long>(s_feedbackSeq));
|
||||
s_unhealthySinceSeq[i] = 0U;
|
||||
}
|
||||
s_recoveryInProgress[i] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_unhealthySinceSeq[i] == 0U) {
|
||||
s_unhealthySinceSeq[i] = s_feedbackSeq;
|
||||
}
|
||||
|
||||
/* 仅在连续 2 秒不健康后才干预,避免对短暂波动过度反应 */
|
||||
const uint32_t elapsed = s_feedbackSeq - s_unhealthySinceSeq[i];
|
||||
if (elapsed < 2U) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s_recoveryInProgress[i]) {
|
||||
/* 上一轮恢复后仍在下一周期观察到不健康:物理层问题未解决,不要刷屏 */
|
||||
static uint32_t s_lastStillBadPrintSeq[kChannelCount] = {0U, 0U, 0U};
|
||||
if ((s_feedbackSeq - s_lastStillBadPrintSeq[i]) >= 10U) {
|
||||
printf("[CAN-RECOVER] CH%u still unhealthy after toggle (TEC=%lu bus_off=%u) seq=%lu; check physical layer\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long>(tec),
|
||||
busOff ? 1U : 0U,
|
||||
static_cast<unsigned long>(s_feedbackSeq));
|
||||
s_lastStillBadPrintSeq[i] = s_feedbackSeq;
|
||||
}
|
||||
s_recoveryInProgress[i] = false; /* 允许下一轮再次尝试 */
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("[CAN-RECOVER] CH%u attempting recovery (TEC=%lu bus_off=%u elapsed=%lus)\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long>(tec),
|
||||
busOff ? 1U : 0U,
|
||||
static_cast<unsigned long>(elapsed));
|
||||
|
||||
/* 步骤:让收发器进入 standby 再切回 normal,强制总线出现连续隐性位,
|
||||
* 帮助硬件 ABOR 完成 128×11 个连续隐性位的恢复序列。 */
|
||||
gpio_bit_set(ch.slpPort, ch.slpPin);
|
||||
SysTickTimer::delayMs(20U);
|
||||
gpio_bit_reset(ch.slpPort, ch.slpPin);
|
||||
SysTickTimer::delayMs(10U);
|
||||
|
||||
const uint32_t errAfterToggle = CAN_ERR(ch.canPeriph);
|
||||
const bool stillBad = ((errAfterToggle & CAN_ERR_BOERR) != 0U) ||
|
||||
(((errAfterToggle >> 16) & 0xFFU) > 96U);
|
||||
if (stillBad) {
|
||||
printf("[CAN-RECOVER] CH%u toggle done but still unhealthy (TEC=%lu bus_off=%u); need physical-layer fix\r\n",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long>((errAfterToggle >> 16) & 0xFFU),
|
||||
((errAfterToggle & CAN_ERR_BOERR) != 0U) ? 1U : 0U);
|
||||
} else {
|
||||
printf("[CAN-RECOVER] CH%u recovered by transceiver toggle\r\n",
|
||||
static_cast<unsigned>(i));
|
||||
}
|
||||
|
||||
s_recoveryInProgress[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 拷贝指定通道的最新反馈快照。
|
||||
@@ -1376,6 +1466,9 @@ bool CanFd::copyFeedbackSnapshot(uint8_t channelIndex, ChannelFeedback *out)
|
||||
*/
|
||||
extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); }
|
||||
|
||||
/** @brief CAN0 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN0_TX_IRQHandler(void) { CanFd::handleTxInterrupt(0U); }
|
||||
|
||||
/**
|
||||
* @brief CAN1 RX0 中断向量入口。
|
||||
* @param[in] 无。
|
||||
@@ -1385,6 +1478,9 @@ extern "C" void CAN0_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(0U); }
|
||||
*/
|
||||
extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); }
|
||||
|
||||
/** @brief CAN1 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN1_TX_IRQHandler(void) { CanFd::handleTxInterrupt(1U); }
|
||||
|
||||
/**
|
||||
* @brief CAN2 RX0 中断向量入口。
|
||||
* @param[in] 无。
|
||||
@@ -1393,3 +1489,6 @@ extern "C" void CAN1_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(1U); }
|
||||
* @note C 向量包装函数,内部转调 `CanFd::handleRxInterrupt(2U)`。
|
||||
*/
|
||||
extern "C" void CAN2_RX0_IRQHandler(void) { CanFd::handleRxInterrupt(2U); }
|
||||
|
||||
/** @brief CAN2 TX 完成中断向量入口。 */
|
||||
extern "C" void CAN2_TX_IRQHandler(void) { CanFd::handleTxInterrupt(2U); }
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief CANFD 状态报文的数据长度,单位字节。 */
|
||||
#define CANFD_STATUS_DLEN 12U
|
||||
|
||||
/** @brief 每通道反馈快照中保存的最近一帧最大数据长度,受 SPI 应答帧总长度约束。 */
|
||||
#define CANFD_FEEDBACK_LAST_DATA_MAX 32U
|
||||
|
||||
@@ -58,7 +55,7 @@ public:
|
||||
*/
|
||||
struct ChannelStats {
|
||||
uint32_t rx_count; /**< 该通道累计成功接收帧数。 */
|
||||
uint32_t tx_count; /**< 该通道累计成功发送帧数。 */
|
||||
uint32_t tx_count; /**< 该通道累计由硬件 MTFNERR 确认的成功发送帧数。 */
|
||||
uint32_t rx_drop_count; /**< 该通道累计软件丢帧数。 */
|
||||
uint32_t error_count; /**< 该通道累计错误计数(与发送邮箱失败合并)。 */
|
||||
uint32_t last_rx_id; /**< 该通道最近一帧报文标识符。 */
|
||||
@@ -104,15 +101,6 @@ public:
|
||||
uint8_t count; /**< 当前占用条目数。 */
|
||||
uint32_t seq; /**< 全局更新序号(每次 refresh 时递增)。 */
|
||||
};
|
||||
/**
|
||||
* @brief CANFD 状态发送载荷。
|
||||
*/
|
||||
struct StatusPayload {
|
||||
uint16_t temp_raw;
|
||||
uint16_t vref_raw;
|
||||
float vdda;
|
||||
float temperature;
|
||||
};
|
||||
|
||||
static void init();
|
||||
|
||||
@@ -132,21 +120,32 @@ public:
|
||||
static Mode mode();
|
||||
|
||||
static void poll();
|
||||
static void prepareStatus(const StatusPayload *status);
|
||||
static bool prepareStatusForChannel(uint8_t channelIndex, const StatusPayload *status);
|
||||
static void sendPrepared();
|
||||
static bool hasRxFrame();
|
||||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||||
static bool transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false);
|
||||
static const Diag *diag();
|
||||
|
||||
/**
|
||||
* @brief 检查指定 CAN 通道当前是否处于健康可发送状态。
|
||||
* @retval true 未处于 BUS_OFF 且 TEC 未超过预警阈值。
|
||||
* @retval false 通道处于 BUS_OFF 或 TEC 过高,应避免再向其发送测试帧。
|
||||
*/
|
||||
static bool isChannelHealthy(uint8_t channelIndex);
|
||||
|
||||
/**
|
||||
* @brief 刷新三路 1Hz 反馈快照。
|
||||
* @note 通常由任务调度器在 1 秒节拍调用,将各通道实时计数与最近一帧信息抓拍到 s_feedback。
|
||||
*/
|
||||
static void refreshFeedbackSnapshot();
|
||||
|
||||
/**
|
||||
* @brief 保留的手动 BUS_OFF 恢复诊断入口。
|
||||
* @note 正常周期任务不调用;控制器已启用硬件自动 BUS_OFF 恢复,避免在
|
||||
* 发送重试期间切换收发器 S 脚而人为制造额外位错误。
|
||||
*/
|
||||
static void recoverBusOffIfNeeded();
|
||||
|
||||
/**
|
||||
* @brief 拷贝指定通道的最新反馈快照到调用方缓冲。
|
||||
* @param[in] channelIndex 目标 CAN 通道号(0/1/2)。
|
||||
@@ -167,6 +166,7 @@ public:
|
||||
static void setSilentMode();
|
||||
static void onRx0Interrupt();
|
||||
static void handleRxInterrupt(uint8_t channelIndex);
|
||||
static void handleTxInterrupt(uint8_t channelIndex);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kRxQueueDepth = 16U;
|
||||
@@ -195,8 +195,6 @@ private:
|
||||
static void configureRxFilter(uint32_t canPeriph, uint8_t filterNumber, Mode mode);
|
||||
static bool initNominalTiming(uint32_t canPeriph);
|
||||
static bool initFdMode(uint32_t canPeriph);
|
||||
static void buildStatusFrame(const StatusPayload *status);
|
||||
static bool enqueueTxFrame(uint8_t channelIndex, const StatusPayload *status);
|
||||
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
@@ -209,6 +207,5 @@ private:
|
||||
};
|
||||
|
||||
using canfd_diag_t = CanFd::Diag;
|
||||
using canfd_status_payload_t = CanFd::StatusPayload;
|
||||
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
#endif /* DEVICE_CANFD_H */
|
||||
|
||||
@@ -17,8 +17,7 @@ enum system_task_t {
|
||||
TASK_ADC_PROCESS, /**< 对 ADC 原始值进行电压与温度计算任务。 */
|
||||
TASK_CAN_RX_POLL, /**< 兼容保留的 CANFD 接收轮询任务。 */
|
||||
TASK_CAN_RX_PARSE, /**< 解析并消费已接收 CANFD 帧任务。 */
|
||||
TASK_CAN_TX_PREPARE, /**< 预留的 CANFD 发送准备任务,默认不主动置位。 */
|
||||
TASK_CAN_TX_SEND, /**< 预留的 CANFD 发送执行任务,默认不主动置位。 */
|
||||
TASK_CAN_TX_SEND, /**< CANFD 周期发送任务,每 1ms 每通道发送一帧。 */
|
||||
TASK_STATUS_LED, /**< 状态灯维护任务。 */
|
||||
TASK_HEARTBEAT, /**< 系统心跳维护任务。 */
|
||||
TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */
|
||||
@@ -46,8 +45,10 @@ struct task_scheduler_t {
|
||||
volatile uint16_t div_temp_sample;
|
||||
/** @brief 调度分频计数:CAN 接收处理。 */
|
||||
volatile uint16_t div_can_rx;
|
||||
/** @brief 调度分频计数:状态灯。 */
|
||||
/** @brief 调度分频计数:CAN 周期发送(1ms)。 */
|
||||
volatile uint16_t div_can_tx;
|
||||
/** @brief 调度分频计数:状态灯(100ms)。 */
|
||||
volatile uint16_t div_status_led;
|
||||
/** @brief 调度分频计数:心跳与诊断打印。 */
|
||||
volatile uint16_t div_heartbeat;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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] 无。
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user