- 段2(32..543): count=实际电机数(0..9), 空节点不占位, 槽序按 node_id 升序 - 同号多通道取 seq 最新; magic@35=0xA5 区分密排与旧滑窗 - 差分填充: 节点集合+seq/online 未变时整段复用影子 - 删段4 fillRxWindow; 非STATUS仍走段1/3最近一帧 - 输入PDO总长仍600B, SII无需重刷; 主站需按count密排解码 - 动机: 接3电机却看到5状态帧(空槽占位); win_base滑窗对已识别电机无意义
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 1.6.21 布局:段1(0..31) 段2(32..543) 段3(544..583) 段4 已删 段5(584..599)。
|
|
* 段4 上界=段5 起点,区间为空、恒 0,子索引保留以免动对象字典。
|
|
*/
|
|
constexpr uint16_t kSegEnd[kSegCount] = {32U, 544U, 584U, 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 */
|