添加了CAN FD收发器静默模式功能,允许通过硬件引脚控制发送使能: - 新增setTxSilent/isTxSilent接口控制TCAN1057收发器Silent模式 - 静默时驱动器关断但接收机继续工作,支持只听不发场景 - 添加SPI命令0xC8用于ESP32远程控制静默模式切换 - 在总线状态位图中增加bit5表示静默状态 - 新增canBusyWaitUs函数用于ISR安全的微秒延时 fix(esc_lan9252): 改进warm jump后LAN9252探测逻辑 修复warm jump导致的LAN9252初始化问题: - 识别warm jump后芯片非POR状态(idx0!=0x1234)的情况 - 自动补发硬件复位对齐到POR状态后再进行正常探测 - 改进调试信息输出,区分POR状态和warm jump残值情况 - 修正失败路径的复位标志初始化逻辑 refactor(spi): 整理SPI命令文件结构 调整SPI命令相关文件的组织结构: - 移动诊断相关文件到更合适的位置 - 重新整理SPI命令处理文件的分组
15 KiB
feature, status, updated, branch, commits
| feature | status | updated | branch | commits |
|---|---|---|---|---|
| ecat-wkc-periodic-dip | delivered | 2026-09-27 | ecat-dcu-flash-crypto-audit (主工作区共享;本文件不提交) | 28d8426..uncommitted |
EtherCAT WKC / 丢帧规律性波动 — 根因定位
Report
What was built — 仅定位报告(无生产代码改动)。结论:EtherCAT WKC/丢帧在「几十~几百 ms」上的规律起伏,默认首选根因 B——输出侧 PDO_OutputMapping 探针跳过 + process 内每 256 拍强制全量读,若「12B 探针+末字」不能释放 SM2 3-buffer,则写侧 WKC 会按 256×T 周期性塌陷/回升(0.5–2 ms PDO → 128–512 ms,与现象带重合)。并列根因 A——输入侧稀疏/多段 PRAM 写是否真正提交 SM3 3-buffer,固件只保证「计划触达 end address」(F1 测试),硬件 WKC 未验证;但 fillDiagTail 的 s_diagTick 使段 3+5 在 process() 正常时永变,不能把「差分 skip 窗口」当作固定时间轴(残余形态为 A1 提交语义 / A2 重入陈旧 skip / A3 写长摆动)。C(250 ms GuardAleventMask + 0xC5 SPI)可把凹坑对齐到 250 ms 并加深。D(PD 看门狗回读失败)签名是 100 ms 全错,仅作鉴别。完整证据链、锚点与 S2.7 对拍步骤见正文。
Verification — 静态:rg 核对 CONFIG_ECAT_SPARSE/DIFF_ESC_WRITE=1、s_lastForceReadSeq>=256、flush=2、末 2B 写、250 ms guard、dc_app.c WKC 注释、PD watchdog 1s 回读、tests/tier1_unit/test_f1_esc_write_plan.cpp + TEST_READY.md F1 均存在且与正文一致。独立复审两轮:首轮 2 critical(tests 误报已驳回并补精确引用;A 权重过高已接受并重排为 B 主 A 并列),复审 clean。无 ARM 交叉编译,未上机。工作区并行 dirty(warm-jump 1.7.0 / flash-crypto 审计)未触碰。
Journey log
- 先验把差分 skip 的「内容变化节奏」当主时间轴,被
fillDiagTick每拍自增证伪——稳态进不了全 skip。 - 首轮 reviewer 误报
tests/不存在;Test-Path+TEST_READY.mdL18/L150 驳回。分析报告引用测试时必须落到真实路径与断言名。 - 输出侧 256 拍兜底是唯一与内容无关的机械时钟,与「总是、规律」同构,故升主因。
esc_exmc_bus.h注释写 EXTI0+1,实现是 EXTI0/1/2——以实现为准。- T6 留给现场 A/B 对拍;未对拍前不写补丁。
[S1] Problem
用户现象:EtherCAT(E卡特)总是出现规律性波动,观察面为 WKC / 丢帧,节奏大约 几十 ms 到几百 ms。
WKC(Working Counter)在过程数据里表达「本拍从站是否提供了/吃掉了新缓冲」:
- 输入侧(SM3,从站→主站):主站读到未被从站重写的 3-buffer 槽时 不累加 WKC(仓库内已有明确注释,
dc_app.cAPPL_StartInputHandler)。 - 输出侧(SM2,主站→从站):若从站长期不完整读走缓冲,3-buffer 被占满后主站写侧 WKC 同样会掉。
因此「WKC 规律性掉」本质是:某一侧的 SM 缓冲没有按主站期望的节奏完成提交/释放,且触发恢复的事件本身有几十~几百 ms 的周期。
本次交付为仅定位报告(用户明确不改生产代码),给出根因排序、代码证据、以及可上机对拍的验证步骤。
[S2] Design / 根因分析
S2.0 周期从站稳态路径(判断前提)
| 模式 | 过程数据节拍 | 输入写 ESC | 输出读 ESC |
|---|---|---|---|
| SM-Synchron / SM2 | PDI_Isr(EXTI1) |
每拍 PDO_InputMapping(u16SyncType 为 SM/SM2 时) |
PDO_OutputMapping |
| DC Sync0 | Sync0_Isr(EXTI0, preempt=0) |
ECAT_Application 每拍;PDO_InputMapping 仅当 LatchInputSync0Value 与计数匹配(ecatappl.c Sync0 分支) |
PDI 未映射时由 Sync0 补 PDO_OutputMapping |
| free-run | MainLoop → ECAT_Application |
同上 | 同上 |
输入写路径(ecatappl.c PDO_InputMapping):
APPL_InputMapping→EcatPdo_WriteInputs整填对象存储aPdInputDataEcatPdo_EscWritePlan产出多段写计划(有副作用:更新影子/flush)- 逐段
HW_EscWriteIsr→EscLan9252::pramWrite(每段一次独立 PRAM FIFO 事务)
当前编译开关(sys/config.h):
CONFIG_ECAT_SPARSE_ESC_WRITE = 1— 跳过密排空洞CONFIG_ECAT_DIFF_ESC_WRITE = 1— 三区差分,稳定且冲刷完则跳过;全跳过时只写末 2 字节「触发 SM3 结束地址提交」
S2.1 根因 B(主因,高)— 输出侧:探针跳过 + 256 拍强制全量读,SM2 缓冲释放可能不足
为何升为主因:这是唯一与内容无关、固定节拍的恢复时钟,与「总是、规律性、几十~几百 ms」最同构。输入侧差分写在稳态下几乎不会进入「全 skip」(见 S2.2 的 s_diagTick 约束),不能单独解释固定节奏。
机制
PDO_OutputMapping(ecatappl.c)每拍:
- 读 12B 头探针(SEQ@0 / WIN@8 / FLAGS@3)
EcatPdo_OutputProbe假(应用层 seq 无变化沿)→ 只读 SM2 末字 2B- 为真 → 至少读 52B 前缀;
count@48>0才读满 384B - 兜底:
EthercatPdo::process内每 256 个 process 拍强制一次输出全量读(s_lastForceReadSeq)
SM2 同样是 3-buffer。若「首字探针 + 末字」不能释放读缓冲,而完整 readOutputs 才释放一格,则:
- 主站应用 seq 不变(空闲/保持命令)时,从站几乎不释放 SM2
- 3 格写满后主站写侧 WKC 下跌
- 每到 256 拍强制全量读释放一格(或若干格)→ WKC 周期性回升
周期算术与「几十~几百 ms」对齐
| 过程数据周期 | 256 拍强制读间隔 |
|---|---|
| 0.5 ms | 128 ms |
| 1 ms | 256 ms |
| 2 ms | 512 ms |
| 4 ms | 1024 ms(偏慢,除非还有其他恢复源) |
1–2 ms 周期下强制读间隔正落在「几十~几百 ms」。这是独立于内容变化的固定时钟。
代码锚点
| 位置 | 内容 |
|---|---|
comm/ssc/src/ecatappl.c PDO_OutputMapping |
12B 探针;skip 只补末字 |
comm/ethercat_pdo.cpp process 末 256 拍兜底 |
seqNow - s_lastForceReadSeq >= 256 |
comm/ethercat_can_bridge.cpp peekOutput |
仅应用 seq 沿 / FLAGS bit0 才要求全量 |
S2.2 根因 A(并列,高)— 输入侧:多段/稀疏 PRAM 写与 LAN9252 3-buffer「提交」语义可能不对齐
机制
SM3 为 3-buffer。主站侧 WKC 只在「从站本轮真正交出一块新缓冲」时 +1。交出条件在 ESC 硬件上通常是:
- 写侧字节计数达到
SM.Length(顺序填满当前写缓冲),或 - 访问到缓冲末地址触发 buffer 切换(部分 ESC 如此)
固件假设是后者(ethercat_pdo.cpp 注释;TEST_READY.md F1「SM3 End Address Commit Guarantee」;tests/tier1_unit/test_f1_esc_write_plan.cpp 的 touchesSm3EndAddress / none_changed_fallback_guarantees_end_addr):
无论各段是否变化,必须保证每周期必定写入 SM3 结束地址 (kPdoInBytes-1),
以触发 LAN9252 3-buffer 提交换冲,否则主站将读到冻结的旧缓冲。
全段跳过时返回「末 2 字节」一段以完成 SM 事件。
未闭环点:
- F1 /
Audit_F1_SM3_End_Address_Guarantee只断言写计划触达 end address(软件计划层)。没有验证 LAN9252 在「非顺序、多段、总长常 < 600B」的 PRAM 写之后是否旋转 3-buffer、主站 WKC 是否 +1。测试文件在树内(tests/tier1_unit/test_f1_esc_write_plan.cpp等),断言对象是计划几何,不是硬件 WKC。 - 稀疏写只写「段1+段2有效区」与「段3+段5」,中间空洞不写。每次
pramWrite是独立的ADDR_LEN+CMD事务,SM 写指针/字节计数是否跨事务累计,固件侧无回读观测。 - 仓库内 WKC 失败模式描述与「缓冲未被重写」一致(
dc_app.cAPPL_StartInputHandler注释),本项目踩过同类坑。
与「几十~几百 ms」的关系(约束后)
fillDiagTail 每拍 ++s_diagTick 写入段 5(584..595),落在段 3+5 差分区间(544..599)。因此 process() 正常跑满时,段 3+5 每拍都判变化,不会进入「全 skip → 只写末 2B」。不能把「差分 skip 窗口 = 内容变化周期」当作主时间轴。
更站得住的 A 形态是:
| 形态 | 节奏 | 是否像本现象 |
|---|---|---|
| A1 多段/稀疏 PRAM 无法提交 3-buffer(即便写了 end) | 常态偏低 + 在「偶发 600B 整包」时回升 | 偏慢性;除非整包/分段交替 |
A2 process() 重入门挡下后 InputMapping 仍走 skip(陈旧镜像) |
跟随抢占/救援,可能呈簇状 | 可与 C 叠加 |
A3 稀疏 count 变化导致写长在「有效区/整段」间摆动 |
跟随电机插拔/离线 | 不如 B 固定 |
代码锚点
| 位置 | 内容 |
|---|---|
comm/ethercat_pdo.cpp diffNeedWrite / escWritePlan / fillDiagTail |
三区差分、flush=2、末 2B 假设;s_diagTick 使段 3+5 稳态永变 |
comm/ssc/src/ecatappl.c PDO_InputMapping |
多段 HW_EscWriteIsr,nseg==0 才 600B 整包 |
comm/esc_lan9252.cpp pramWrite |
每段独立 PRAM 事务,无 SM 字节计数回读 |
comm/ssc/port/dc_app.c APPL_StartInputHandler |
权威注释:未重写缓冲 ⇒ WKC 不涨 |
tests/tier1_unit/test_f1_esc_write_plan.cpp + TEST_READY.md F1 |
只测 end-address 触达,不测硬件 WKC |
sys/config.h CONFIG_ECAT_SPARSE_ESC_WRITE / CONFIG_ECAT_DIFF_ESC_WRITE |
均为 1(省字节路径全开) |
S2.3 根因 C(中)— 250 ms 周期任务挤占 HBI 窗口,输入刷新相对主站读偏晚
dc_app.c 已写明:输入刷新若落在主站读之后,主站读到未重写缓冲 ⇒ 几乎每周期 WKC 偏低。
主循环上有两处 250 ms 量级的 HBI/SPI 周期工作:
-
EcatDbg_GuardAleventMask(task_runner.cppethercatAleMaskGuard,250 ms)
连续读 SM0/1 几何、0x204、0x220、看门狗状态;HW_EscRead*会走EscExmcBus::maskEnter(屏蔽 EXTI0/1/2)。
屏蔽期内 SYNC0/PDI 的 HBI 事务被推迟,单次守护可达数十~上百 µs。每 250 ms 打一次节拍孔 → WKC 尖峰/凹坑周期 ≈ 250 ms。 -
0xC5 多电机状态 SPI 推送(
CONFIG_TASK_PERIOD_MOTOR_STATE_TICKS = 2500= 250 ms)
与 ESP32 SPI 会话耦合;若 SPI 命令处理仍在较高抢占级或与 EXMC 交错,会形成同量级阻塞(历史审计VULN-01曾量化过 SPI 突发对 SYNC0 的 160 µs 级冲击;本轮是否完全异步化需上板确认)。
代码锚点:sys/task_runner.cpp 289–315、627–630;comm/ssc/port/dc_app.c 603–728、730–801;sys/config.h 0xC5 周期。
S2.4 根因 D(低~中,签名不同)— 过程数据看门狗 100 ms 调试周期
EscLan9252::configPdWatchdog 注释:
表现为「100ms 周期全 WKC 错、10/50ms 正常」
已把 PD 看门狗从默认 100 ms 延到 1 s。若回读失败(WARN 路径),主站用 100 ms 调试周期时会 整窗 WKC 全错,节奏精确 100 ms。
与「总是规律性起伏(非全错)」不完全同构,列为鉴别项而非主因。
S2.5 已排除 / 历史同族(避免误归因)
| 项 | 为何降权 |
|---|---|
APPL_StartInputHandler 覆写 pIntMask |
注释已标明不可覆写;若仍覆写会是「几乎每周期」WKC 低,不是几十 ms 节奏 |
| SPI ISR 内 16 帧 CAN 突发 / 0xA2 10 ms 忙等 | 审计后改为快照+异步;若未上线该修复,症状会更接近 Sync0 Lost / SAFEOP,而非纯 WKC 起伏 |
| 批量 PRAM 弹 DWORD | 1.6.2 已回退,与 WKC 无直接关系 |
| 电机 STATUS 1 kHz | s_diagTick 已使段 3+5 稳态永变;STATUS 稀密只影响段 2 写长,不是 skip 主因 |
主站 expected_wkc / 多从站拓扑配错 |
可能造成恒定偏低或错预期,但不会自己产生几十 ms 机械节拍;排除靠主站配置截图/拓扑,不在本仓库 |
S2.6 综合判断(对用户现象的最佳匹配)
现象:WKC/丢帧 · 几十~几百 ms · 总是存在
│
├─ 严格等周期、空闲也抖(0.5–2ms PDO → 128–512ms)→ 根因 B(256 拍)★ 默认首选
├─ 与整包写/分段写切换或 process 重入救援相关 → 根因 A(提交语义 / 陈旧 skip)
├─ 若约 250 ms 对齐、串口/SPI 活动时加重 → 根因 C
└─ 若 100 ms 且 WKC 全错 → 根因 D(看门狗回读失败)
默认主假设(未上板前):B 为主(固定 256 拍恢复时钟),A 为并列机制缺口(多段 PRAM 是否提交 3-buffer 未验证,可造成常态偏低或与 B 叠加的毛刺),C 可把凹坑对齐到 250 ms 并加深。
S2.7 建议的上机对拍(不改代码也能做)
-
输出侧(先做,验证根因 B)
- 临时把
process内 256 拍强制全量读改成 1(或主站持续递增 seq),看输出 WKC 起伏是否消失/周期是否拉长 → 对齐 256×T 即实锤 B。
- 临时把
-
输入侧开关 A/B(验证根因 A)
- 组1:
CONFIG_ECAT_DIFF_ESC_WRITE=0CONFIG_ECAT_SPARSE_ESC_WRITE=0(恒 600B 整包写) - 组2:现网
1/1
同主站周期看 WKC 基线是否抬高、毛刺是否变缓 → 验证 A1。同时盯reentryCount/s_opFreshRescues是否与凹坑同相(A2)。
- 组1:
-
250 ms 孔
- 临时让
ethercatAleMaskGuard空转(或把 250 ms 改到 2 s),看凹坑周期是否离开 250 ms → 验证根因 C。
- 临时让
-
仪表(已有,SDO 0x2000)
sub5..12:PDI/Sync0/InWrite 周期累计(1s 差分)— InWrite 在 skip 时应显著变小sub13..23:DIFF-STATS(需CONFIG_ECAT_DIFF_STATS=1)— 看 changed/runs 是否与 WKC 凹坑同相EcatPdo::reentryCount/s_opFreshRescues:重入与新鲜度救援是否在凹坑时上升- 主站侧:WKC 时间序列与电机 STATUS 到达时刻对齐
-
看门狗鉴别
- 确认串口启动行
PD watchdog = 1s (0x0420=...);若只有 WARN,则单独怀疑根因 D。
- 确认串口启动行
[S3] Out of Scope
- 不修改生产代码(用户指定仅定位报告)。
- 不触碰工作区并行改动(
ecat-dcu-flash-crypto-audit分支上的 warm-jump/1.7.0 与 flash-crypto 审计文档);它们不改变CONFIG_ECAT_*_WRITE开关,不影响本结论。 - 不重刷 SII/EEPROM、不改 PDO 布局或主站解码。
- 不在此文档给出最终补丁设计(待对拍锁定 A/B/C 后另开实现 spec)。
- 不审计 BOOT/OTA 路径(与本次 WKC 节奏无直接证据链)。
Tasks
- T1: 梳理稳态 PDO 节拍与写/读 ESC 路径 — acceptance: S2.0 与代码一致 (covers: S2.0)
- T2: 定位输入侧 WKC 与 3-buffer 提交的机制缺口 — acceptance: S2.2 含证据链;明确
s_diagTick约束 (covers: S2.2) - T3: 定位输出侧探针/256 拍强制读与 SM2 释放缺口 — acceptance: S2.1 含周期算术并升为主因 (covers: S2.1)
- T4: 列出 250 ms HBI 孔与看门狗鉴别项 — acceptance: S2.3/S2.4 可执行 (covers: S2.3; S2.4)
- T5: 给出上机对拍步骤 — acceptance: S2.7 可逐步执行且能区分 A/B/C/D (covers: S2.1; S2.2; S2.3; S2.4)
- T6: (待现场)按 S2.7 完成 A/B 对拍并回填 Report — acceptance: Report 记录开关矩阵与 WKC 曲线结论 (covers: S2.6)