Files
DCU_APP/docs/compose/spec/ecat-wkc-periodic-dip.md
cbb f3d7fc0c30 feat(canfd): 添加收发器静默模式支持和SPI命令接口
添加了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命令处理文件的分组
2026-10-10 02:25:56 +08:00

15 KiB
Raw Permalink Blame History

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

  1. 先验把差分 skip 的「内容变化节奏」当主时间轴,被 fillDiagTick 每拍自增证伪——稳态进不了全 skip。
  2. 首轮 reviewer 误报 tests/ 不存在;Test-Path + TEST_READY.md L18/L150 驳回。分析报告引用测试时必须落到真实路径与断言名。
  3. 输出侧 256 拍兜底是唯一与内容无关的机械时钟,与「总是、规律」同构,故升主因。
  4. esc_exmc_bus.h 注释写 EXTI0+1,实现是 EXTI0/1/2——以实现为准。
  5. T6 留给现场 A/B 对拍;未对拍前不写补丁。

[S1] Problem

用户现象:EtherCAT(E卡特)总是出现规律性波动,观察面为 WKC / 丢帧,节奏大约 几十 ms 到几百 ms。

WKC(Working Counter)在过程数据里表达「本拍从站是否提供了/吃掉了新缓冲」:

  • 输入侧(SM3,从站→主站):主站读到未被从站重写的 3-buffer 槽时 不累加 WKC(仓库内已有明确注释,dc_app.c APPL_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):

  1. APPL_InputMapping → EcatPdo_WriteInputs 整填对象存储 aPdInputData
  2. EcatPdo_EscWritePlan 产出多段写计划(有副作用:更新影子/flush)
  3. 逐段 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)每拍:

  1. 读 12B 头探针(SEQ@0 / WIN@8 / FLAGS@3)
  2. EcatPdo_OutputProbe 假(应用层 seq 无变化沿)→ 只读 SM2 末字 2B
  3. 为真 → 至少读 52B 前缀;count@48>0 才读满 384B
  4. 兜底: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 事件。

未闭环点:

  1. 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。
  2. 稀疏写只写「段1+段2有效区」与「段3+段5」,中间空洞不写。每次 pramWrite 是独立的 ADDR_LEN+CMD 事务,SM 写指针/字节计数是否跨事务累计,固件侧无回读观测。
  3. 仓库内 WKC 失败模式描述与「缓冲未被重写」一致(dc_app.c APPL_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 周期工作:

  1. EcatDbg_GuardAleventMask(task_runner.cpp ethercatAleMaskGuard,250 ms)
    连续读 SM0/1 几何、0x204、0x220、看门狗状态;HW_EscRead* 会走 EscExmcBus::maskEnter(屏蔽 EXTI0/1/2)。
    屏蔽期内 SYNC0/PDI 的 HBI 事务被推迟,单次守护可达数十~上百 µs。每 250 ms 打一次节拍孔 → WKC 尖峰/凹坑周期 ≈ 250 ms。

  2. 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 建议的上机对拍(不改代码也能做)

  1. 输出侧(先做,验证根因 B)

    • 临时把 process 内 256 拍强制全量读改成 1(或主站持续递增 seq),看输出 WKC 起伏是否消失/周期是否拉长 → 对齐 256×T 即实锤 B。
  2. 输入侧开关 A/B(验证根因 A)

    • 组1:CONFIG_ECAT_DIFF_ESC_WRITE=0 CONFIG_ECAT_SPARSE_ESC_WRITE=0(恒 600B 整包写)
    • 组2:现网 1/1
      同主站周期看 WKC 基线是否抬高、毛刺是否变缓 → 验证 A1。同时盯 reentryCount / s_opFreshRescues 是否与凹坑同相(A2)。
  3. 250 ms 孔

    • 临时让 ethercatAleMaskGuard 空转(或把 250 ms 改到 2 s),看凹坑周期是否离开 250 ms → 验证根因 C。
  4. 仪表(已有,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 到达时刻对齐
  5. 看门狗鉴别

    • 确认串口启动行 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)