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 实测不作为结论。
219 lines
8.0 KiB
C++
219 lines
8.0 KiB
C++
/**
|
|
* @file ethercat_diff_stats.cpp
|
|
* @brief [DIFF-STATS] 输入镜像差分统计实现(度量定义与存在理由见 ethercat_diff_stats.h)。
|
|
*
|
|
* 整文件由 CONFIG_ECAT_DIFF_STATS 门控:置 0 时不参与编译,挂接点各自也有 #if,
|
|
* 因此关掉之后固件与改造前逐字节等价。
|
|
*/
|
|
#include "comm/ethercat_diff_stats.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "RTE_Components.h"
|
|
#include CMSIS_device_header
|
|
|
|
#include "comm/ethercat_pdo_size.h"
|
|
#include "sys/config.h"
|
|
|
|
#if CONFIG_ECAT_DIFF_STATS
|
|
|
|
namespace {
|
|
|
|
constexpr uint16_t kInBytes = (uint16_t)ECAT_PDO_IN_BYTES;
|
|
constexpr uint32_t kInWords = (uint32_t)kInBytes / 4U; /* 600/4=150,无尾巴字节 */
|
|
constexpr uint8_t kSegCount = 5U;
|
|
|
|
/**
|
|
* @brief 各段字节上界(开区间),用于把变化量归到布局段落。
|
|
* @note 584..599 的"段5 诊断 + 4B 保留"合并计:差分写不会单独优化保留区,
|
|
* 拆开只是多一档,但保留区若在涨就说明有东西在越界写,所以仍要看得见。
|
|
*/
|
|
constexpr uint16_t kSegEnd[kSegCount] = {32U, 320U, 360U, 584U, kInBytes};
|
|
|
|
/** @brief 上一份已发布的镜像。alignas(4) 是让优化器能把 4 字节 memcpy 折成单条
|
|
* LDR 的前提——丢了它,比对会从 ~150 次字访问退化成 600 次字节访问。 */
|
|
alignas(4) uint8_t s_prev[kInBytes];
|
|
bool s_havePrev = false;
|
|
|
|
/* ── 正在累积的窗口 ── */
|
|
uint32_t s_curCycles = 0U;
|
|
uint32_t s_curChangedMax = 0U;
|
|
uint32_t s_curRunsTotal = 0U;
|
|
uint32_t s_curRunsMax = 0U;
|
|
uint32_t s_curCompareCyc = 0U;
|
|
uint32_t s_curSeg[kSegCount] = {0U, 0U, 0U, 0U, 0U};
|
|
uint32_t s_winStart = 0U;
|
|
|
|
/* ── 上一个已完成的窗口(getter 只读这份,保证主站拿到的是稳定值)── */
|
|
uint32_t s_winCycles = 0U;
|
|
uint32_t s_winChangedMax = 0U;
|
|
uint32_t s_winRunsTotal = 0U;
|
|
uint32_t s_winRunsMax = 0U;
|
|
uint32_t s_winCompareCyc = 0U;
|
|
uint32_t s_winSeg[kSegCount] = {0U, 0U, 0U, 0U, 0U};
|
|
uint32_t s_windowSeq = 0U;
|
|
|
|
/** @brief 字节偏移所属段号。只对"确实变了"的字调用,线性 5 次比较足够。 */
|
|
uint8_t segOf(uint32_t byteOff)
|
|
{
|
|
for (uint8_t i = 0U; i < kSegCount; ++i) {
|
|
if (byteOff < (uint32_t)kSegEnd[i]) {
|
|
return i;
|
|
}
|
|
}
|
|
return (uint8_t)(kSegCount - 1U); /* 理论上到不了:kSegEnd[4] == kInBytes */
|
|
}
|
|
|
|
/** @brief 结算窗口:把累加值搬成"上一个完整窗口",再清零重新计时。 */
|
|
void rollWindow(uint32_t now)
|
|
{
|
|
s_winCycles = s_curCycles;
|
|
s_winChangedMax = s_curChangedMax;
|
|
s_winRunsTotal = s_curRunsTotal;
|
|
s_winRunsMax = s_curRunsMax;
|
|
s_winCompareCyc = s_curCompareCyc;
|
|
for (uint8_t i = 0U; i < kSegCount; ++i) {
|
|
s_winSeg[i] = s_curSeg[i];
|
|
s_curSeg[i] = 0U;
|
|
}
|
|
s_curCycles = 0U;
|
|
s_curChangedMax = 0U;
|
|
s_curRunsTotal = 0U;
|
|
s_curRunsMax = 0U;
|
|
s_curCompareCyc = 0U;
|
|
s_winStart = now;
|
|
s_windowSeq++;
|
|
}
|
|
|
|
} /* namespace */
|
|
|
|
/**
|
|
* @brief 与上一份已发布镜像比对并累计统计。
|
|
* @note .ramfunc:每拍在 PDI_Isr 里跑。放 flash 会带来等待态,既污染本仪表
|
|
* 自己要测的 compareCyc,也给被测的写侧窗口添无谓的额外占用。
|
|
*/
|
|
__attribute__((section(".ramfunc"), noinline))
|
|
void EthercatDiffStats::observe(const uint8_t *cur) noexcept
|
|
{
|
|
const uint32_t t0 = DWT->CYCCNT;
|
|
|
|
if (!s_havePrev) {
|
|
/* 首拍只建基准:否则第一拍会把"整 600B 都变了"记成样本,把 changedMax
|
|
* 和段计数全钉在无意义的上限上。 */
|
|
std::memcpy(s_prev, __builtin_assume_aligned(cur, 4), kInBytes);
|
|
s_havePrev = true;
|
|
s_winStart = DWT->CYCCNT;
|
|
return;
|
|
}
|
|
|
|
uint32_t changed = 0U;
|
|
uint32_t runs = 0U;
|
|
bool inRun = false;
|
|
|
|
/* 对齐必须在调用点再声明一次:alignas(4) 只到形参为止,函数签名是
|
|
* const uint8_t*,优化器看不见对齐就无从折字访问。实测两版都因此慢到不能用——
|
|
* 移位版反汇编出 8 条 ldrb.w/字(41µs/拍),memcpy 版 21µs/拍 —— 而这个数会被
|
|
* 直接读成"差分判断很贵",是仪表自己造的假象,不是差分的成本。 */
|
|
const uint8_t *prevW = static_cast<const uint8_t *>(__builtin_assume_aligned(s_prev, 4));
|
|
const uint8_t *curW = static_cast<const uint8_t *>(__builtin_assume_aligned(cur, 4));
|
|
|
|
for (uint32_t w = 0U; w < kInWords; ++w) {
|
|
const uint32_t o = w * 4U;
|
|
uint32_t a;
|
|
uint32_t b;
|
|
/* assume_aligned + memcpy:不碰严格别名,又能让 -Oz 折成单条 LDR。 */
|
|
std::memcpy(&a, prevW + o, 4U);
|
|
std::memcpy(&b, curW + o, 4U);
|
|
if (a == b) {
|
|
inRun = false;
|
|
continue;
|
|
}
|
|
changed += 4U;
|
|
if (!inRun) {
|
|
runs++;
|
|
inRun = true;
|
|
}
|
|
s_curSeg[segOf(o)] += 4U;
|
|
}
|
|
|
|
s_curCycles++;
|
|
s_curRunsTotal += runs;
|
|
if (changed > s_curChangedMax) { s_curChangedMax = changed; }
|
|
if (runs > s_curRunsMax) { s_curRunsMax = runs; }
|
|
|
|
std::memcpy(s_prev, curW, kInBytes);
|
|
|
|
const uint32_t t1 = DWT->CYCCNT;
|
|
/* 比对自身开销:不含上面那次 600B 快照搬运(那是差分写真要做时要付的成本之一,
|
|
* 单列出来才看得清"光判断"值多少钱)。 */
|
|
s_curCompareCyc += (uint32_t)(t1 - t0);
|
|
|
|
/* SystemCoreClock 个计数 = 1s(与 sys/cpu_load.cpp 同法;DWT 32 位约 23.8s
|
|
* 绕回,窗口远短于此,无绕回问题)。 */
|
|
if ((uint32_t)(t1 - s_winStart) >= SystemCoreClock) {
|
|
rollWindow(t1);
|
|
}
|
|
}
|
|
|
|
void EthercatDiffStats::reset() noexcept
|
|
{
|
|
s_havePrev = false;
|
|
}
|
|
|
|
uint32_t EthercatDiffStats::windowSeq() noexcept { return s_windowSeq; }
|
|
uint32_t EthercatDiffStats::cycles() noexcept { return s_winCycles; }
|
|
uint32_t EthercatDiffStats::changedMax() noexcept { return s_winChangedMax; }
|
|
uint32_t EthercatDiffStats::runsTotal() noexcept { return s_winRunsTotal; }
|
|
uint32_t EthercatDiffStats::runsMax() noexcept { return s_winRunsMax; }
|
|
uint32_t EthercatDiffStats::seg1Bytes() noexcept { return s_winSeg[0]; }
|
|
uint32_t EthercatDiffStats::seg2Bytes() noexcept { return s_winSeg[1]; }
|
|
uint32_t EthercatDiffStats::seg3Bytes() noexcept { return s_winSeg[2]; }
|
|
uint32_t EthercatDiffStats::seg4Bytes() noexcept { return s_winSeg[3]; }
|
|
uint32_t EthercatDiffStats::seg5Bytes() noexcept { return s_winSeg[4]; }
|
|
uint32_t EthercatDiffStats::compareCyc() noexcept { return s_winCompareCyc; }
|
|
|
|
/* ── 供 dc_app.c(C 侧对象字典) 取值:一次拿全 11 个窗口值,避免 11 次跨语言调用 ── */
|
|
extern "C" {
|
|
|
|
/**
|
|
* @brief [DIFF-STATS] 读上一个完整 1s 窗口的统计值。
|
|
* @param[out] out 依次对应 0x2000 sub13..sub23。
|
|
* 形参用 uint32_t 而非 SSC 的 UINT32 别名:本翻译单元不引 SSC 头,
|
|
* 两者同为 32 位无符号,C 侧声明仍按 UINT32 写。
|
|
*/
|
|
void EcatDiffStats_GetWindow(uint32_t *out)
|
|
{
|
|
if (out == nullptr) {
|
|
return;
|
|
}
|
|
out[0] = EthercatDiffStats::windowSeq();
|
|
out[1] = EthercatDiffStats::cycles();
|
|
out[2] = EthercatDiffStats::changedMax();
|
|
out[3] = EthercatDiffStats::runsTotal();
|
|
out[4] = EthercatDiffStats::runsMax();
|
|
out[5] = EthercatDiffStats::seg1Bytes();
|
|
out[6] = EthercatDiffStats::seg2Bytes();
|
|
out[7] = EthercatDiffStats::seg3Bytes();
|
|
out[8] = EthercatDiffStats::seg4Bytes();
|
|
out[9] = EthercatDiffStats::seg5Bytes();
|
|
out[10] = EthercatDiffStats::compareCyc();
|
|
}
|
|
|
|
} /* extern "C" */
|
|
|
|
#else /* !CONFIG_ECAT_DIFF_STATS */
|
|
/* 关掉时仍导出同名取值函数并填 0:这样 dc_app.c 的挂接点不需要再套一层
|
|
* #if,子索引数量也保持与开启版一致(主站读到的恒 0,脚本按 winseq==0 判'未启用')。
|
|
* 留一个空函数体反而会漏掉符号——1.6.20 首编就是栽在这里(L6218E)。 */
|
|
extern "C" void EcatDiffStats_GetWindow(uint32_t *out)
|
|
{
|
|
if (out == nullptr) {
|
|
return;
|
|
}
|
|
for (uint32_t i = 0U; i < 11U; ++i) {
|
|
out[i] = 0U;
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_ECAT_DIFF_STATS */
|