feat(comm): 1.6.14~1.6.20 —— CAN-FD 数据段 5M、批量区改定长覆盖窗口(输入 PDO 496→600)、drop 口径修复、差分写仪表

1.6.14 CAN-FD 数据段 2Mbps→5Mbps:CONFIG_CAN_DATA_PRESCALER 9→3、BS1 3→4TQ
  (90MHz/(3×6TQ),采样点 80%→83.3%),仲裁段仍 1M。TDC filter/offset 维持 4 不做
  等比缩放(TDCMOD=CALC_AND_OFFSET 下硬件已自动测回路延时;放到 12 会让本节点回采
  错开约 6 个数据位,自发热帧被判错误帧)。配套电机 APP/BOOT data_prescaler=2。

1.6.15 段4 批量接收由"环形队列 + 变长紧排排空"改为 5×44B 定长覆盖窗口,输入 PDO
  496→600(75×64bit),段5 诊断区 484→584。动机两条:现场 4000 帧/s 超旧容量 3 帧/拍
  恒丢 1 帧;更关键是排空赤字把队列长期压在饱和态,而 1.6.9~1.6.13 追的错标/拼接竞态
  正是饱和态喂出来的(判满用的 tail 滞后→生产者写进消费者刚读过的槽)。定长消灭
  tail/判满/pending,边界越界写在结构上不可能再犯。每槽带 u32 seqlock:buildInput 在
  PDI_Isr(preempt=0) 抢占主循环写侧,无它则主站会拿到"新 ID + 旧数据"拼出的假帧。
  out+in=984 ≤ 1024 硬上限,DPRAM 留 120B。语义变更:批量区从"不丢的事件流"变"最近
  N 帧窗口",去重与丢帧判据移交主站按 seq 归一。三端同步 size.h/bridge/dc_app/
  gen_eeprom/DCU_ESI.xml,必须重烧固件 + 烧 EEPROM + 断电冷启动,只烧一边 SAFEOP 报
  AL 0x001A/0x0011。

1.6.16 段1/段3 快照 drop 计数口径修复(纯判据,PDO 布局逐字节不动):旧判据
  s_rxValid 置位后永不清零 ⇒ 开机首帧后每帧都算丢弃,drop ≡ rx−1,是零灵敏度假仪表
  且把网页状态栏刷成 ERR。新增 s_rxPublished,判据改 s_rxValid && !s_rxPublished。

1.6.17~19 [DIFF-STATS] 差分写取证仪表,零行为变更,只加 0x2000 sub13..23(11 个只读
  UINT32) ⇒ 不动 PDO 长度/映射,无需重生成 SII。实测:每拍 27.4% 字节真变化(164B)、
  run 峰值 12~13、段2 占变化量 ~53%、段4 ~25%。
  1.6.19 推翻 1.6.18 的自我诊断:比对开销虚报不是 -Oz 把 memcpy 编成调用(手拼移位
  反而 21.3µs→41µs),真根因是 alignas(4) 到 const uint8_t* 形参处丢失,优化器无法
  证明 4 字节对齐。__builtin_assume_aligned + memcpy 后循环体缩成 ldr/ldr/cmp/bne、
  bl 归零。教训:测量工具自身的开销会直接变成结论。

1.6.20 基线对照版(当前提交状态 CONFIG_ECAT_DIFF_STATS=0):同工况(电机在线无指令)
  实测 load 49.1% vs 仪表开的 51.6% ⇒ 仪表自身成本 2.4 个点,差分改造收益 = 0。
  按实测变化量推算 A1(段粒度条件写) 只值约 1 点(段2/段4 每拍都变,必然整块重写),
  仅 A2(8B 块粒度 + run 上限退化为整写) 有空间 —— 推算值,未做 A/B 实测不作为结论。
This commit is contained in:
cbb
2026-09-09 03:04:53 +08:00
parent 0cd3d9bed2
commit 959e2aef6e
13 changed files with 775 additions and 224 deletions
@@ -0,0 +1,60 @@
/**
* @file ethercat_diff_stats.h
* @brief [DIFF-STATS] 临时仪表:量化"若把输入 PDO 改成差分写,每拍真正需要写多少字节、
* 变化点碎成几个连续段(run)"。只测量,不改变任何现有写行为。
*
* 存在的理由:差分写(方案 A)的收益完全取决于"每拍有多少字节真的变了",而 run 碎片
* 数量决定它会不会把一次 600B 整块写退化成几百次带 AVAIL 轮询的小写。这两个数今天
* 都没有实测依据,所以先只上仪表,拿分布再决定 A1(语义分区条件写) 值不值得做、
* A2(块粒度差分) 的碎片上限 N 该取几。
*
* 顺带把"比较本身要多久"也测出来(sub23 累计 DWT 周期)——这是差分方案最容易被
* 质疑的成本项,用实测代替拍脑袋。
*
* @note 结果按 1s 窗口滚动(与 sys/cpu_load.cpp 同法,用 DWT 周期判窗口边界),
* getter 返回**上一个已完成的完整窗口**,因此主站任何时刻 SDO 读到的都是
* 稳定值,不会拿到半累积的窗口。窗口未满时 windowSeq()==0,读侧应显示
* "等待首个完整窗口"而不是当 0 解读。
*
* @note 删除方法:删本文件与 ethercat_diff_stats.cpp + 删全工程 [DIFF-STATS] 标记行
* + 把 sys/config.h 的 CONFIG_ECAT_DIFF_STATS 置 0。对象字典 0x2000 的
* sub13..23 字段建议保留(恒 0,不影响任何既有子索引)。
*/
#ifndef COMM_ETHERCAT_DIFF_STATS_H
#define COMM_ETHERCAT_DIFF_STATS_H
#include <cstdint>
/**
* @brief 输入镜像差分统计。
* @note 全静态;observe() 在 PDI_Isr 上下文每拍调用一次,故内部状态无需临界区
* (唯一写者),getter 由 APPL_InputMapping 同上下文读取。
*/
class EthercatDiffStats {
public:
/**
* @brief 与上一份已发布的输入镜像比对并累计统计。
* @param[in] cur 刚发布完成的完整输入 PDO 镜像(kEcatPdoInBytes 字节)。
* @note 内部自持上一份副本,调用方无需额外维护缓冲、也不必自己 memcpy。
* 首次调用只建立基准,不产生样本(否则会把"全 600B 都变了"记成第一拍)。
*/
static void observe(const uint8_t *cur) noexcept;
/** @brief 丢弃基准,下一次调用重新起算(AL 状态跳变、布局变更时用)。 */
static void reset() noexcept;
/* ── 对象字典 0x2000 sub13..23 的取值接口,均为"上一个完整 1s 窗口" ── */
static uint32_t windowSeq() noexcept; /**< sub13 已完成窗口数,0=还没有完整窗口 */
static uint32_t cycles() noexcept; /**< sub14 窗口内参与比对的拍数 */
static uint32_t changedMax() noexcept; /**< sub15 单拍变化字节峰值 */
static uint32_t runsTotal() noexcept; /**< sub16 窗口内 run 总数(配 cycles 求均值) */
static uint32_t runsMax() noexcept; /**< sub17 单拍 run 数峰值,A2 的 N 由它定 */
static uint32_t seg1Bytes() noexcept; /**< sub18 段1(0..31) 桥状态+短快照 */
static uint32_t seg2Bytes() noexcept; /**< sub19 段2(32..319) 电机状态窗口 */
static uint32_t seg3Bytes() noexcept; /**< sub20 段3(320..359) 长帧快照 */
static uint32_t seg4Bytes() noexcept; /**< sub21 段4(360..583) 批量覆盖窗 */
static uint32_t seg5Bytes() noexcept; /**< sub22 段5(584..599) 诊断区+保留 */
static uint32_t compareCyc() noexcept; /**< sub23 比对自身累计 DWT 周期(本征开销) */
};
#endif /* COMM_ETHERCAT_DIFF_STATS_H */