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:
cbb
2026-09-08 00:52:35 +08:00
parent ace5a1671b
commit 0cd3d9bed2
13 changed files with 518 additions and 111 deletions
+42 -14
View File
@@ -104,19 +104,19 @@ enum : uint16_t {
ECAT_MOTW_OFF_ACTIVE = 32U, /**< 在线电机数 */
ECAT_MOTW_OFF_BOOT = 33U, /**< 1=近 3s 收到过 0x103 BOOT 心跳 */
ECAT_MOTW_OFF_WIN_BASE = 34U, /**< 本帧携带的槽窗口起始下标(回显主站请求) */
ECAT_MOTW_OFF_WIN_COUNT = 35U, /**< 本帧携带的有效槽数(≤7) */
ECAT_MOTW_OFF_WIN_COUNT = 35U, /**< 本帧携带的有效槽数(≤5) */
ECAT_MOTW_OFF_GLOBAL_SEQ = 36U,/**< 状态表全局刷新序号,小端 uint32 */
ECAT_MOTW_OFF_SLOTS = 40U, /**< 槽数组起点,7×40B */
ECAT_MOTW_OFF_SLOTS = 40U, /**< 槽数组起点,5×56B(40..319,与段 3 正好相接) */
};
/** 电机状态槽内部布局(40 字节)。 */
/** 电机状态槽内部布局(56 字节)。 */
enum : uint8_t {
ECAT_MOT_OFF_NODE = 0U, /**< 节点号 1..9,0=空槽 */
ECAT_MOT_OFF_CH = 1U, /**< CAN 通道 0/1/2 */
ECAT_MOT_OFF_ONLINE = 2U, /**< 1=在线 */
ECAT_MOT_OFF_DLC = 3U, /**< 最后一帧 STATUS 的 DLC(正常 32) */
ECAT_MOT_OFF_DLC = 3U, /**< 最后一帧 STATUS 的 DLC(正常 48) */
ECAT_MOT_OFF_SEQ = 4U, /**< STATUS 刷新序号,小端 uint32 */
ECAT_MOT_OFF_STATUS = 8U, /**< 原始 STATUS payload,32 字节 */
ECAT_MOT_OFF_STATUS = 8U, /**< 原始 STATUS payload,48 字节(v0.74:B32-35=InitStatus) */
};
/** 输入 PDO 段 3(320..359):最近一帧**完整**快照(版本/CONFIG 帧要 24B)。 */
@@ -147,9 +147,31 @@ enum : uint16_t {
ECAT_DIAG_OFF_LOAD = 484U, /**< 负载率,小端 uint16,单位 0.1%(1000=100%)。
中断 + 非延迟处理段(任务派发/OTA relay)占墙钟比,
不含常开轮询与空转;1s 滚动窗口(见 sys/cpu_load.h) */
ECAT_DIAG_OFF_RSVD = 486U, /**< 保留 2B */
ECAT_DIAG_OFF_SEQ = 488U, /**< 诊断区序号,小端 uint32,每完成一个统计窗口 +1,
主站据此判断负载值是否仍在滚动刷新 */
ECAT_DIAG_OFF_TORN = 486U, /**< CAN 取帧撕裂丢帧数(低 16 位,小端,饱和不回绕)。
1.6.6 起:ISR 取帧时若 MP 的 TS/DLENC 前后不一致,
说明邮箱窗口在取数途中被硬件换帧,该帧整帧丢弃并
在此计数。这是"读到半条帧"的唯一可观测量 —— 主站
据此判断修复是否生效、总线负载是否高到频繁触发。 */
ECAT_DIAG_OFF_SEQ = 488U, /**< 诊断区序号,小端 uint16(1.6.12 起,原 u32 让出
高 2 字节),每完成一个 1s 统计窗口 +1,主站据此
判断负载值是否仍在滚动刷新。u16 足够:主站只判
"非 0 且在涨",18h 才回绕。 */
ECAT_DIAG_OFF_OVERFULL = 490U, /**< CAN RX FIFO 满溢计数 RFO0(u16 饱和,1.6.12)。
1.6.11 把溢出策略改成"满则丢新帧"后拼接并未消失
(段5@494 以 ~384/s 增长),必须直接观测溢出本身
与拼接率是否同涨,才能判定残余拼接是否仍源自溢出
(即本 IP 在 DISCARD 模式下照样毁窗口)。 */
ECAT_DIAG_OFF_RELTIMEOUT = 492U, /**< RFD0 释放握手超时计数(u16 饱和,1.6.12)。
原为 1.6.7 的 PDO 重入计数 ECAT_DIAG_OFF_REENTRY
(引入至今恒 0,不再上线,仍留内部计数)。取帧后
等 RFD0 自清 2000 次未果 = dequeue 未完成,窗口
与读指针可能已失同步 —— 拼接的直接前兆。 */
ECAT_DIAG_OFF_REJECT = 494U, /**< CAN 内容级拼接帧命中率(u16 饱和,1.6.11)。
入队前结构自洽校验(STATUS 温度量程+斜率、CONFIG
未使用区/模式)命中即计数。1.6.12 起**只计不丢**
—— 数据管道必须诚实,该计数退化为"源头是否还在
产生拼接帧"的纯测量仪表:修好 = 恒 0,没修好 =
网页看到什么,计数就是什么的倍率。 */
};
/** 单帧数据在输出 PDO / 长帧快照区中的最大字节数(电机 CMD 是 DLC 32)。 */
@@ -159,10 +181,14 @@ constexpr uint8_t kEcatCanRxDataMax = 8U;
/** 段 3 长快照数据区字节数。 */
constexpr uint8_t kEcatCanXRxDataMax = 32U;
/** 单个电机状态槽字节数。 */
constexpr uint8_t kEcatMotSlotSize = 40U;
/** 一份输入 PDO 携带的电机状态槽数。 */
constexpr uint8_t kEcatMotSlotsPerPdo = 7U;
/** 单个电机状态槽字节数:8B 头 + 48B STATUS(v0.74)。
* 1.6.4 前是 40B(只装得下 32B STATUS),于是 buildInput 按 48B 拷贝时会溢出
* 16B 盖到段 3,且主站永远拿不到 B32-35 的 InitStatus。 */
constexpr uint8_t kEcatMotSlotSize = 56U;
/** 一份输入 PDO 携带的电机状态槽数:段 2 可用 280B(40..319)÷ 56 = 5。
* 27 槽需 ⌈27/5⌉ = 6 个基址扫完。槽数×槽长仍是 280B,输入 PDO 总长不变,
* 所以 EEPROM/SII 无需重刷。 */
constexpr uint8_t kEcatMotSlotsPerPdo = 5U;
/** 过程数据长度,唯一真源见 comm/ethercat_pdo_size.h(C/C++ 共用)。 */
constexpr uint16_t kEcatPdoOutBytes = ECAT_PDO_OUT_BYTES; /* 48×64bit = 384B */
@@ -181,8 +207,10 @@ constexpr uint16_t kEcatTxBatchBytes = (uint16_t)(kEcatPdoOutBytes - ECAT_TXB_OF
constexpr uint8_t kEcatTxBatchMax =
(uint8_t)(kEcatTxBatchBytes / (ECAT_FRM_HDR_SIZE + kEcatCanFdDataMax));
/** 批量接收区可用字节数与最坏情况(全 DLC32 截断满帧)帧数上限。 */
constexpr uint16_t kEcatRxBatchBytes = (uint16_t)(kEcatPdoInBytes - ECAT_RXB_OFF_ITEMS);
/** 批量接收区可用字节数与最坏情况(全 DLC32 截断满帧)帧数上限。
* 可用区到段 5 诊断区起点为止(1.6.10 修正口径:旧式按 PDO 全长 496 算,
* 与 fillRxBatch 的旧边界判据同错;数值上 120/40 与 132/40 同为 3,无行为变化)。 */
constexpr uint16_t kEcatRxBatchBytes = (uint16_t)(ECAT_DIAG_OFF_LOAD - ECAT_RXB_OFF_ITEMS);
constexpr uint8_t kEcatRxBatchMax =
(uint8_t)(kEcatRxBatchBytes / (ECAT_FRM_HDR_SIZE + kEcatRxBatchDataMax));