fix(comm): 回退LAN9252批量读写优化并修复多个CAN-EtherCAT桥竞态问题
回退1.6.0引入的LAN9252 PRAM批量读写优化,因其AVAIL_CNT计数器对短读存在语义问题, 导致邮箱短读和探针传输时出现FIFO陈旧数据损坏问题。 同时修复EtherCAT-CAN桥中的多个竞态条件: - 修复消费者在fillRxBatch中直接访问共享ring buffer导致的数据混淆问题 - 修复生产者-消费者间tail指针发布时机不当造成的槽位覆盖竞态 - 修复WIN窗口基址更新位置错误导致电机状态窗口被锁定的问题 - 修复buildInput中断上下文与主循环间的数据竞争和内存撕裂问题 - 添加STATUS帧过滤机制减少批量区压力并解决队列饱和问题 此外还增加了多个诊断计数器用于监控撕裂、溢出和拼接错误,并调整了 输入PDO段布局和大小参数以支持新的48字节STATUS格式。 这些修改解决了上位机观察到的帧标签错误、电机状态丢失和数据拼接问题。
This commit is contained in:
@@ -19,6 +19,107 @@ static inline void spinDelay(uint32_t loops) {
|
||||
for (volatile uint32_t d = 0; d < loops; ++d) { }
|
||||
}
|
||||
|
||||
/* DLENC>8 的长度映射,与厂商库 g_can_fdlength_table 一致 */
|
||||
constexpr uint8_t kFdLenTable[7] = { 12U, 16U, 20U, 24U, 32U, 48U, 64U };
|
||||
|
||||
/** 释放 FIFO0 邮箱并等待 RFD0 由硬件自动清 0(手册 26.3.4 第 3 步)。
|
||||
*
|
||||
* 两处与厂商库 can_message_receive() 的差别,都是它漏掉的:
|
||||
* 1) 用**直接赋值**而不是 `|=`。CAN_RFIFO0 里 RFF0(bit3)/RFO0(bit4) 是 rc_w1
|
||||
* (写 1 清除),读-改-写会把这期间硬件刚置起来的"FIFO 满/溢出"标志顺手
|
||||
* 清掉 —— 溢出证据就是这么丢的。写 0 对 rc_w1 位无副作用。
|
||||
* 2) 置位后**等它自清**。手册原文:"将 RFD 置 1 释放邮箱,并且等待其由硬件
|
||||
* 自动清 0"。不等就接着读下一条,会撞上 dequeue 尚未完成的邮箱窗口。
|
||||
* @return true=已确认释放完成;false=超时(异常,计数后照常继续,不死等)。 */
|
||||
static inline bool releaseFifo0(uint32_t canPeriph)
|
||||
{
|
||||
CAN_RFIFO0(canPeriph) = CAN_RFIFO0_RFD0;
|
||||
for (uint32_t n = 0U; n < 2000U; ++n) {
|
||||
if ((CAN_RFIFO0(canPeriph) & CAN_RFIFO0_RFD0) == 0U) {
|
||||
/* 1.6.11 注:这里曾加过 128/2048 拍的 settle 延迟,假设"自清后 MDATA
|
||||
* 还在搬运"是装载时间竞态 —— 实测无效(正弦指令负载下拼接率不变)。
|
||||
* 节点指纹实验证明拼接是 FIFO 缓冲指针与帧边界失同步(node3 的
|
||||
* STATUS 帧头 24B = node2 的完整 CONFIG,错位恰为 CONFIG 帧长 6 字),
|
||||
* 不是延迟能挡的;结构矛盾的帧由 handleRxInterrupt 的内容校验计数
|
||||
* (1.6.12 起只计不丢 —— 管道必须诚实;源头修复见 1.6.13 的
|
||||
* CONFIG_IRQ_PREEMPT_CAN_RX=0)。 */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 取一帧 FIFO0 报文,并校验取数期间硬件没有换帧。
|
||||
*
|
||||
* 为什么自己实现(不用厂商库 can_message_receive):一条 CANFD 报文要对同一个
|
||||
* 地址 CAN_RFIFOMDATA0 连读 dlen/4 次才取完(48B = 12 次),加上头部两次,一条
|
||||
* 帧就是十几次有先后依赖的总线访问,整个过程**不是原子的**。若这期间硬件把下
|
||||
* 一帧搬进邮箱窗口,就会读出"前几个字属于上一帧、后几个字属于这一帧"的拼接
|
||||
* 体,而 id/dlc 取自最开始那次读 —— 实测表现为 STATUS 帧的 ID 配上 CONFIG 帧
|
||||
* 的前 12~24 字节,且混合边界 100% 落在 4 字节边界上。
|
||||
*
|
||||
* 判据用 MP 寄存器:TS[31:16] 是硬件接收时间戳、DLENC[3:0] 是长度,同一帧内两
|
||||
* 者恒定。取数前后各读一次,只要有一个变了就说明窗口内容已不是原来那帧,整帧
|
||||
* 作废(计入 rx_torn_count)—— 宁可丢一帧,也不把拼接数据递上去。
|
||||
*
|
||||
* @return true=取到一帧完整报文;false=本帧判为撕裂、应丢弃(邮箱仍需释放)。 */
|
||||
static bool receiveFrameChecked(uint32_t canPeriph, can_receive_message_struct *msg)
|
||||
{
|
||||
/* 整段取数放进 BASEPRI 临界区:屏蔽 preempt>=1,把 EtherCAT 的 ESC(1)/SYNC1(1)
|
||||
* 挡在外面。取数是十几次有先后依赖的寄存器读,被抢占就会读出跨帧拼接体。
|
||||
* 1.6.13 起本 ISR 自身就是 preempt=0(CONFIG_IRQ_PREEMPT_CAN_RX),与 SYNC0
|
||||
* 同级互不抢占,取数自此真正原子;MI/MP 回读校验保留,作为对硬件自发换帧
|
||||
* (dequeue 异步完成等)的兜底观测 —— rx_torn 计数即"该病理是否还在发生"。 */
|
||||
const uint32_t basepri = RT_CRITICAL_SAVE();
|
||||
|
||||
const uint32_t mi0 = CAN_RFIFOMI(canPeriph, CAN_FIFO0);
|
||||
const uint32_t mp0 = CAN_RFIFOMP(canPeriph, CAN_FIFO0);
|
||||
|
||||
msg->rx_ff = static_cast<uint8_t>(CAN_RFIFOMI_FF & mi0);
|
||||
if (CAN_FF_STANDARD == msg->rx_ff) {
|
||||
msg->rx_sfid = static_cast<uint32_t>(GET_RFIFOMI_SFID(mi0));
|
||||
} else {
|
||||
msg->rx_efid = static_cast<uint32_t>(GET_RFIFOMI_EFID(mi0));
|
||||
}
|
||||
msg->rx_ft = static_cast<uint8_t>(CAN_RFIFOMI_FT & mi0);
|
||||
msg->rx_fi = static_cast<uint8_t>(GET_RFIFOMP_FI(mp0));
|
||||
msg->fd_flag = static_cast<uint8_t>((CAN_RFIFOMP_FDF & mp0) >> 7);
|
||||
msg->fd_brs = static_cast<uint8_t>((mp0 & CAN_RFIFOMP_BRS) >> 5);
|
||||
msg->fd_esi = static_cast<uint8_t>((mp0 & CAN_RFIFOMP_ESI) >> 4);
|
||||
|
||||
const uint8_t dlenc = static_cast<uint8_t>(GET_RFIFOMP_DLENC(mp0));
|
||||
const uint8_t dlen = (dlenc <= 8U) ? dlenc
|
||||
: ((dlenc <= 15U) ? kFdLenTable[dlenc - 9U] : 64U);
|
||||
msg->rx_dlen = dlen;
|
||||
|
||||
/* 按字取数:与厂商库同样是对同一地址重复读(硬件读指针自动前进)。
|
||||
* 5~7 字节要多读一个字才取全,与厂商库口径一致。 */
|
||||
uint32_t words = static_cast<uint32_t>(dlen) / 4U;
|
||||
if ((1U == words) && (4U != dlen)) {
|
||||
words++;
|
||||
}
|
||||
if (0U == words) {
|
||||
words = 1U; /* dlen<4 也要读走一个字 */
|
||||
}
|
||||
for (uint32_t i = 0U; i < words; ++i) {
|
||||
const uint32_t w = CAN_RFIFOMDATA0(canPeriph, CAN_FIFO0);
|
||||
std::memcpy(&msg->rx_data[i * 4U], &w, sizeof(w));
|
||||
}
|
||||
|
||||
/* 取数后回读 MI+MP:任一变化 ⇒ 邮箱窗口在取数期间被换帧,这一帧是拼接体。
|
||||
* 必须连 MI(完整 ID)一起比 —— 只比 MP 的 TS/DLENC 会漏:TS 是 16 位计数器,
|
||||
* 相邻两帧可能同值,DLENC 也常常相同(都是 48),于是"ID 报 STATUS、数据却是
|
||||
* CONFIG"这种错配能从校验里溜过去(实测占坏帧的绝大多数)。ID 变了就一定是
|
||||
* 换了帧,这条判据没有巧合空间。 */
|
||||
const uint32_t keep = CAN_RFIFOMP_TS | CAN_RFIFOMP_DLENC;
|
||||
const uint32_t mp1 = CAN_RFIFOMP(canPeriph, CAN_FIFO0);
|
||||
const uint32_t mi1 = CAN_RFIFOMI(canPeriph, CAN_FIFO0);
|
||||
const bool intact = ((mp1 & keep) == (mp0 & keep)) && (mi1 == mi0);
|
||||
|
||||
RT_CRITICAL_RESTORE(basepri);
|
||||
return intact;
|
||||
}
|
||||
|
||||
struct CanChannelConfig {
|
||||
uint32_t canPeriph;
|
||||
rcu_periph_enum canRcu;
|
||||
@@ -166,6 +267,7 @@ CanFd::ChannelFeedback CanFd::s_feedback[3]{};
|
||||
uint32_t CanFd::s_feedbackSeq = 0U;
|
||||
CanFd::IdSnapshotTable CanFd::s_idSnapshot[3]{};
|
||||
bool CanFd::s_channelHwOk[3]{};
|
||||
bool CanFd::s_brsSuppress[3]{};
|
||||
|
||||
/* TX 错误事件标志(ARD=1 单发丢帧通知):TX 完成 ISR 置位,主循环
|
||||
* consumeTxError() 读清;单写单读,无锁。 */
|
||||
@@ -274,7 +376,16 @@ bool CanFd::initNominalTiming(uint32_t canPeriph)
|
||||
can_parameter.auto_bus_off_recovery = ENABLE;
|
||||
can_parameter.auto_wake_up = DISABLE;
|
||||
can_parameter.auto_retrans = ENABLE; /* 默认自动重发兜底偶发 ACK 丢失;OTA 停等协议期间由 setChannelAutoRetrans(ch,false) 临时关闭,避免重发打穿电机单缓冲 RX */
|
||||
can_parameter.rec_fifo_overwrite = ENABLE;
|
||||
/* 1.6.11 真根因:这里曾是 ENABLE —— FIFO 满时**覆盖最旧邮箱**,而最旧邮箱
|
||||
* 正是 CPU 正在读取的输出窗口。buildInput 在 PDI_Isr(preempt1) 占 ~357µs
|
||||
* 会压住 CAN ISR(preempt2),发指令时电机 CONFIG 翻倍到 2kHz/节点、总线
|
||||
* ~8kHz,阻塞窗口内到达 ~3 帧恰好打满 FIFO 深度 → 覆盖进行到一半被读走
|
||||
* = 跨节点/跨类型拼接帧(指纹实验实锤:node2 的 CONFIG 条目带 node3 的
|
||||
* 速度限 150 与反相角度;settle 延迟/重取/MI-MP 校验都挡不住,因为
|
||||
* MI/MP 是覆盖序列里最后更新的一环)。DISABLE = 溢出丢新帧并置 RFO0
|
||||
* (rx_fifo_overfull_count 已在统计):STATUS/CONFIG 均周期量,丢一拍下拍
|
||||
* 即补;覆盖递上来的才是网页乱码。与本文件软件队列"满丢新不覆旧"同则。 */
|
||||
can_parameter.rec_fifo_overwrite = DISABLE;
|
||||
can_parameter.trans_fifo_order = ENABLE;
|
||||
can_parameter.working_mode = CAN_NORMAL_MODE;
|
||||
can_parameter.resync_jump_width = CONFIG_CAN_NOMINAL_SJW;
|
||||
@@ -422,7 +533,8 @@ void CanFd::initChannelHardware(uint8_t i)
|
||||
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));
|
||||
/* 1.6.13:RX 提到 preempt=0 —— 见 sys/config.h CONFIG_IRQ_PREEMPT_CAN_RX 注释。 */
|
||||
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN_RX, static_cast<uint8_t>(i));
|
||||
|
||||
s_rxQueues[i].head = 0U;
|
||||
s_rxQueues[i].tail = 0U;
|
||||
@@ -987,7 +1099,9 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
#ifdef GD32E508
|
||||
tx_msg.fd_flag = (dlc > 8U) ? ENABLE : DISABLE;
|
||||
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
|
||||
tx_msg.fd_brs = (dlc > 8U) ? ENABLE : DISABLE;
|
||||
/* 电机 BOOT 收不了 BRS 数据段,OTA 期间 relay 对该通道压制(setChannelBrsSuppress),
|
||||
* DLC>8 帧回仲裁段速率(FD-no-BRS,OTA 长期验证过的组合)。 */
|
||||
tx_msg.fd_brs = (dlc > 8U && !s_brsSuppress[channelIndex]) ? ENABLE : DISABLE;
|
||||
#else
|
||||
tx_msg.fd_brs = DISABLE;
|
||||
#endif
|
||||
@@ -1182,6 +1296,17 @@ void CanFd::setChannelAutoRetrans(uint8_t channelIndex, bool enable)
|
||||
(CAN_CTL(canPeriph) & CAN_CTL_ARD) ? 1U : 0U);
|
||||
}
|
||||
|
||||
void CanFd::setChannelBrsSuppress(uint8_t channelIndex, bool suppress)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
return;
|
||||
}
|
||||
/* 仅置运行时标志,打点在 transmitFrame:BRS 是逐帧属性(fd_brs 位),无需动寄存器。
|
||||
* 写方(relay enterState 钩子)与读方(主循环/CS 中断的 transmitFrame)间 bool 单写单读,原子。 */
|
||||
s_brsSuppress[channelIndex] = suppress;
|
||||
CAN_LOGI("[CAN] CH%u set brs_suppress=%u\r\n", (unsigned)channelIndex, (unsigned)suppress);
|
||||
}
|
||||
|
||||
uint32_t CanFd::readChannelErrorReg(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
@@ -1292,7 +1417,113 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
|
||||
can_receive_message_struct rx_message{};
|
||||
can_struct_para_init(CAN_RX_MESSAGE_STRUCT, &rx_message);
|
||||
can_message_receive(ch.canPeriph, CAN_FIFO0, &rx_message);
|
||||
/* 自己取帧并校验(见 receiveFrameChecked):厂商库不校验也不等 RFD 自清,
|
||||
* 会把"半条上一帧 + 半条本帧"的拼接体当正常帧交上来。
|
||||
*
|
||||
* 1.6.10:**校验失败只丢不重取**。1.6.7 起这里是"原地重取最多 3 次",
|
||||
* 实测被证伪为撕裂的直接制造者:RFIFOMDATA0 是"重复读同一地址、硬件读
|
||||
* 指针自动前进"(厂商驱动证实),attempt1 失败时读指针已随取数前进若干
|
||||
* 字,重取的 attempt2 从错位指针开读 —— MI/MP 回读比对的是"窗口身份",
|
||||
* 管不住"指针相对窗口起点的错位",于是校验通过、内容却是"上一帧的尾巴
|
||||
* 拼下一帧的开头"。自测实锤:重取版 1.6.9 在电机活跃时错标率 9.155%
|
||||
* (56835/620838),而 rx_torn 全程恒 0 —— 拼接恰好都从校验里溜过去。
|
||||
* 丢帧无害:STATUS/CONFIG 都是 1kHz 周期量,下拍即补;递上去才会变乱码。
|
||||
* CAN ISR 是 preempt=2,EtherCAT 的 SYNC(0)/ESC(1)/SYNC1(1) 都能抢占它,
|
||||
* 这正是取数被打断的来源,也解释了为什么只有 EtherCAT 链路上看得到拼接、
|
||||
* WiFi 链路干净。 */
|
||||
const bool intact = receiveFrameChecked(ch.canPeriph, &rx_message);
|
||||
if (!releaseFifo0(ch.canPeriph)) {
|
||||
s_diag.rx_release_timeout_count++;
|
||||
}
|
||||
if (!intact) {
|
||||
/* 校验判为拼接:整帧作废,不计 rx_count、不入队、不转发。
|
||||
* 本版起 rx_torn_count 恢复真实语义 —— 它恒 0 说明校验没在挡东西。 */
|
||||
s_diag.rx_torn_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 本帧的 ID 用局部量,不要读 s_diag.last_rx_id ——三路通道共用那个全局,
|
||||
* 嵌套/交错时会串出"A 通道的 id 配 B 通道的数据"。 */
|
||||
const uint32_t rxId = (rx_message.rx_ff == CAN_FF_STANDARD)
|
||||
? rx_message.rx_sfid : rx_message.rx_efid;
|
||||
|
||||
/* ── 1.6.11:内容级拼接帧拦截 ──
|
||||
* MI/MP 回读校验管不住 FIFO 缓冲指针与帧边界的失同步(1.6.10 的
|
||||
* settle 延迟实测无效,指纹实验证明 node3 的 STATUS 帧头 24B 可以是
|
||||
* node2 的完整 CONFIG —— 错位恰为 CONFIG 帧长 6 字,与混合帧长交错
|
||||
* 相关,是 CAN IP 的指针病理,软件无法从取数时序上根除)。
|
||||
* 改为在入队前做结构自洽校验:拼接帧的 (id, data) 必矛盾,而真帧必自洽。
|
||||
* STATUS(0x000N0100, dlc48):CONFIG 头灌入必然把 MCU/Motor 温度推出
|
||||
* [-40,125]℃ 物理量程(CONFIG B0-1 = 03+限压低字节 → 123~250℃ 或
|
||||
* 负值),母线电压同步失序;
|
||||
* CONFIG(0x000N0101, dlc24):DBC v0.74 规定 B15-23 未使用恒 0,
|
||||
* STATUS 体灌入几乎必非 0;模式字节限 0..7(电机模式表最大 7)。
|
||||
* 命中即计数(1.6.12 起不再丢帧——管道必须诚实,计数仅做"源头是否
|
||||
* 还在产拼接帧"的测量):源头修复生效后本计数应恒 0,非 0 即失效告警。
|
||||
* 计数经段5@494 上报。
|
||||
* 残余风险:限压字节恰使温度落回量程内的理论组合(24V 台架实测限压
|
||||
* ≥15V → 对应"温度"≥153℃,仍在量程外),可接受。 */
|
||||
{
|
||||
const uint16_t idLo = (uint16_t)(rxId & 0xFFFFU);
|
||||
bool reject = false;
|
||||
/* 上一帧已接受的温度/电压(按节点):拼接帧的 CONFIG 字节可能恰好落进
|
||||
* 静态量程(实测 vel=150 指纹时 0301→2.59℃、5a00→0.9℃、cace→52.9V
|
||||
* 全部"合法"漏过),但绝不可能在 1ms 帧间隔里物理地跳过去 —— 斜率判据
|
||||
* 是静态量程补不上的洞。阈值留 3 个数量级余量:温度 20℃/帧(实际
|
||||
* ≤0.005℃),电压 5V/帧(实际 ≤0.05V)。 */
|
||||
static int16_t s_lastMcu[16];
|
||||
static int16_t s_lastMot[16];
|
||||
static uint16_t s_lastPwr[16];
|
||||
static uint8_t s_lastOk[16];
|
||||
if (idLo == 0x0100U && rx_message.rx_dlen == 48U) {
|
||||
const int16_t mcu = (int16_t)((uint16_t)rx_message.rx_data[0] |
|
||||
((uint16_t)rx_message.rx_data[1] << 8));
|
||||
const int16_t mot = (int16_t)((uint16_t)rx_message.rx_data[2] |
|
||||
((uint16_t)rx_message.rx_data[3] << 8));
|
||||
const uint16_t pwr = (uint16_t)rx_message.rx_data[11] |
|
||||
((uint16_t)rx_message.rx_data[12] << 8);
|
||||
if (mcu < -4000 || mcu > 12500 || mot < -4000 || mot > 12500 ||
|
||||
pwr < 8000 || pwr > 60000) {
|
||||
reject = true;
|
||||
}
|
||||
const uint8_t nd = (uint8_t)((rxId >> 16) & 0x0FU);
|
||||
if (!reject && nd >= 1U && nd < 16U && (s_lastOk[nd] & 1U)) {
|
||||
int16_t dmcu = (int16_t)(mcu - s_lastMcu[nd]);
|
||||
int16_t dmot = (int16_t)(mot - s_lastMot[nd]);
|
||||
if (dmcu < 0) { dmcu = (int16_t)-dmcu; }
|
||||
if (dmot < 0) { dmot = (int16_t)-dmot; }
|
||||
/* 只比温度斜率:拼接帧的 CONFIG 头必然把 mcu/mot 推出 20℃/帧
|
||||
* (实测最小跳变 35℃,而真实温度 ≤0.005℃/帧,4 个数量级余量)。
|
||||
* 电压不比斜率 —— 正弦换向/堵转时母线真实跌落可超 5V/帧,
|
||||
* 误杀会让显示冻结;拼接的电压特征(≈48V)已由量程+温度斜率兜住。 */
|
||||
if (dmcu > 2000 || dmot > 2000) {
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
if (!reject && nd >= 1U && nd < 16U) {
|
||||
s_lastMcu[nd] = mcu;
|
||||
s_lastMot[nd] = mot;
|
||||
s_lastPwr[nd] = pwr;
|
||||
s_lastOk[nd] = 1U;
|
||||
}
|
||||
} else if (idLo == 0x0101U && rx_message.rx_dlen == 24U) {
|
||||
if (rx_message.rx_data[0] > 7U) {
|
||||
reject = true;
|
||||
}
|
||||
for (uint8_t i = 15U; !reject && i < 24U; ++i) {
|
||||
if (rx_message.rx_data[i] != 0U) {
|
||||
reject = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (reject) {
|
||||
/* 1.6.12:只计不丢 —— 数据管道必须诚实(用户裁定:过滤是遮羞布,
|
||||
* 修的是采集源头)。网页/ESP32/主站看到的就是源头产出的原始字节,
|
||||
* 本计数退化为纯测量仪表:源头修好 = 恒 0;还在涨 = 源头问题仍在,
|
||||
* 且网页如实显示着,不存在"看着干净其实坏了"。 */
|
||||
s_diag.rx_content_reject_count++;
|
||||
}
|
||||
}
|
||||
|
||||
s_diag.rx_count++;
|
||||
s_diag.last_rx_channel = channelIndex;
|
||||
@@ -1300,14 +1531,14 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
s_diag.last_rx_fd_flag = rx_message.fd_flag;
|
||||
s_diag.last_rx_brs = rx_message.fd_brs;
|
||||
s_diag.last_rx_esi = rx_message.fd_esi;
|
||||
s_diag.last_rx_id = (rx_message.rx_ff == CAN_FF_STANDARD) ? rx_message.rx_sfid : rx_message.rx_efid;
|
||||
s_diag.last_rx_id = rxId;
|
||||
/* CAN0 桥接模式:将刚收到的原帧透传到 CAN1 与 CAN2,保留 11/29 位、FD、BRS、ESI。 */
|
||||
if (channelIndex == 0U && s_mode == Mode::kModeBridge0To12) {
|
||||
for (uint8_t bridgeCh = 1U; bridgeCh <= 2U; ++bridgeCh) {
|
||||
const uint32_t bridgePeriph = kChannels[bridgeCh].canPeriph;
|
||||
const bool ok = bridgeTransmitRaw(bridgePeriph, rx_message);
|
||||
if (ok) {
|
||||
s_diag.last_tx_id = s_diag.last_rx_id;
|
||||
s_diag.last_tx_id = rxId;
|
||||
s_diag.last_tx_channel = bridgeCh;
|
||||
s_diag.last_payload_length = rx_message.rx_dlen;
|
||||
} else {
|
||||
@@ -1320,7 +1551,7 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
/* 维护每通道独立计数与最近一帧快照,用于 1Hz 反馈给 ESP32。 */
|
||||
auto &stats = s_channelStats[channelIndex];
|
||||
stats.rx_count++;
|
||||
stats.last_rx_id = s_diag.last_rx_id;
|
||||
stats.last_rx_id = rxId;
|
||||
stats.last_rx_dlc = rx_message.rx_dlen;
|
||||
stats.last_rx_fd_flag = rx_message.fd_flag;
|
||||
stats.last_rx_brs = rx_message.fd_brs;
|
||||
@@ -1336,7 +1567,6 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
/* 维护每通道不重复 ID 快照表,用于 1Hz 分页拉取给 ESP32。 */
|
||||
{
|
||||
auto &table = s_idSnapshot[channelIndex];
|
||||
const uint32_t rxId = s_diag.last_rx_id;
|
||||
/* 查找已有条目 */
|
||||
uint8_t found = CANFD_ID_SNAPSHOT_MAX;
|
||||
for (uint8_t si = 0U; si < table.count; ++si) {
|
||||
@@ -1387,7 +1617,7 @@ void CanFd::handleRxInterrupt(uint8_t channelIndex)
|
||||
|
||||
auto &frame = queue.frames[queue.tail];
|
||||
frame.channel = channelIndex;
|
||||
frame.id = s_diag.last_rx_id;
|
||||
frame.id = rxId;
|
||||
frame.length = rx_message.rx_dlen;
|
||||
for(uint8_t i = 0; i < rx_message.rx_dlen; ++i) {
|
||||
frame.data[i] = rx_message.rx_data[i];
|
||||
|
||||
Reference in New Issue
Block a user