docs(algo): 采样环缓冲补「为什么需要/设计思想」两节与两张配图
- 采集与算法层设计说明第 5 节由单段说明拆成 5.1~5.5:为什么需要这一层 (三个缺口 + 两个被否决的替代方案)、设计思想(五条不变量)、环内布局 与 start 推导、API 契约、要点与禁令。 - 新增两张配图(生成器 + 产物同批提交): 采样环缓冲_为什么需要.svg —— 上游 1 kHz 覆盖写只交最新一帧 vs 下游 20 点滑窗,缺口 19 点,以及三个消费者必须同源; 采样环缓冲_环形布局.svg —— 40 格环里哪 20 格是本窗、写指针指「下一格」、 回绕方向与三条设计不变量。 - 5.5 补一条「读者与写者必须同任务」,与故障录波设计说明的取窗竞态口径一致。 - 门禁:doc-links --sections-strict 通过(28 处章节引用全部可判定); id-check 新增行无未登记编号;generated-fresh 21 个生成器 16 一致、0 新漂移。
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1060" height="600" viewBox="0 0 1060 600" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1060" height="600" fill="#ffffff"/>
|
||||
<text x="46.0" y="42.0" font-size="21" font-weight="700" fill="#0f172a">上游只交最新一帧,相量却要一整周波 —— 采样环补的就是这 19 点</text>
|
||||
<text x="46.0" y="68.0" font-size="13" fill="#64748b">上游(adc_input)每 1 ms 交付一次,且只交本拍;旧值被覆盖、不保留历史。下游相量要的是「以当前时刻为终止的 20 点滑窗」。</text>
|
||||
<text x="46.0" y="88.0" font-size="13" fill="#64748b">本层不改采集、不改算法,只把「覆盖写的最新值」转成「可回溯的滑动窗」——三个消费者共用同一份。</text>
|
||||
<rect x="34.0" y="112.0" width="992.0" height="188.0" rx="12" fill="#fff1f2"/>
|
||||
<text x="46.0" y="136.0" font-size="14.5" font-weight="700" fill="#e11d48">① 上游交付物:1 kHz 覆盖写,每拍只交一帧</text>
|
||||
<text x="46.0" y="156.0" font-size="11" fill="#64748b">20 个刻度 = 一周波 20 点(1 kHz @ 50 Hz);上游只把最后一个点交出来</text>
|
||||
<line x1="62.0" y1="236.0" x2="806.0" y2="236.0" stroke="#334155" stroke-width="2"/>
|
||||
<line x1="74.0" y1="236.0" x2="74.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="112.0" y1="236.0" x2="112.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="150.0" y1="236.0" x2="150.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="188.0" y1="236.0" x2="188.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="226.0" y1="236.0" x2="226.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="264.0" y1="236.0" x2="264.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="302.0" y1="236.0" x2="302.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="340.0" y1="236.0" x2="340.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="378.0" y1="236.0" x2="378.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="416.0" y1="236.0" x2="416.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="454.0" y1="236.0" x2="454.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="492.0" y1="236.0" x2="492.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="530.0" y1="236.0" x2="530.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="568.0" y1="236.0" x2="568.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="606.0" y1="236.0" x2="606.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="644.0" y1="236.0" x2="644.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="682.0" y1="236.0" x2="682.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="720.0" y1="236.0" x2="720.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="758.0" y1="236.0" x2="758.0" y2="214.0" stroke="#94a3b8" stroke-width="1.4"/>
|
||||
<line x1="796.0" y1="236.0" x2="796.0" y2="214.0" stroke="#2563eb" stroke-width="2"/>
|
||||
<rect x="65.0" y="192.0" width="740.0" height="20.0" rx="6" fill="none" stroke="#e11d48" stroke-width="1.8" stroke-dasharray="7 5"/>
|
||||
<text x="435.0" y="207.0" font-size="11.5" text-anchor="middle" font-weight="700" fill="#e11d48">相量要的 20 点窗(一整周波)</text>
|
||||
<circle cx="796.0" cy="236.0" r="5.5" fill="#2563eb"/>
|
||||
<text x="808.0" y="241.0" font-size="11" font-weight="700" fill="#2563eb">最新一帧(本拍)</text>
|
||||
<rect x="65.0" y="244.0" width="702.0" height="5.0" rx="2.5" fill="#e11d48"/>
|
||||
<text x="74.0" y="266.0" font-size="11" text-anchor="middle" fill="#64748b">k−19</text>
|
||||
<text x="454.0" y="266.0" font-size="11" text-anchor="middle" fill="#64748b">k−9</text>
|
||||
<text x="796.0" y="266.0" font-size="11" text-anchor="middle" fill="#64748b">k</text>
|
||||
<text x="46.0" y="288.0" font-size="11" font-weight="700" fill="#e11d48">这 19 个点上游不保留 —— 不缓冲就永远取不到</text>
|
||||
<text x="46.0" y="334.0" font-size="14.5" font-weight="700" fill="#2563eb">② 下游需求:三个消费者,必须看同一份历史</text>
|
||||
<text x="46.0" y="354.0" font-size="11" fill="#64748b">共用同一份环 = 同一拍只有一个窗 —— 口径不会各自漂移</text>
|
||||
<rect x="46.0" y="370.0" width="300.0" height="112.0" rx="8" fill="#eff6ff" stroke="#2563eb" stroke-width="1.8"/>
|
||||
<text x="58.0" y="394.0" font-size="13.5" font-weight="700" fill="#0f172a">phasor_run()</text>
|
||||
<text x="58.0" y="416.0" font-size="11.5" fill="#64748b">每 1 ms 要一整周波 20 点,算全周傅氏相量(幅值</text>
|
||||
<text x="58.0" y="431.0" font-size="11.5" fill="#64748b"> / 相位)。窗短一点就不是「全周」,谐波抑制立刻</text>
|
||||
<text x="58.0" y="446.0" font-size="11.5" fill="#64748b">失效。</text>
|
||||
<rect x="365.0" y="370.0" width="300.0" height="112.0" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.8"/>
|
||||
<text x="377.0" y="394.0" font-size="13.5" font-weight="700" fill="#0f172a">freq_run()</text>
|
||||
<text x="377.0" y="416.0" font-size="11.5" fill="#64748b">要相邻两拍相量的相位差 → 反推频率。它与相量必须</text>
|
||||
<text x="377.0" y="431.0" font-size="11.5" fill="#64748b">落在同一个窗上,否则相位差里混进窗长误差。</text>
|
||||
<rect x="684.0" y="370.0" width="300.0" height="112.0" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1.8"/>
|
||||
<text x="696.0" y="394.0" font-size="13.5" font-weight="700" fill="#0f172a">meas_tick()</text>
|
||||
<text x="696.0" y="416.0" font-size="11.5" fill="#64748b">每 200 ms 发布遥测快照。快照里的相量 / 频率 /</text>
|
||||
<text x="696.0" y="431.0" font-size="11.5" fill="#64748b"> 品质位都取自同一次窗,不允许各取各的。</text>
|
||||
<line x1="46.0" y1="516.0" x2="1014.0" y2="516.0" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="46.0" y="540.0" font-size="13" fill="#64748b">缓冲只解决「取得到历史」,不解决「这些点是否同刻」:同刻性归上游 adc_input 负责,本层只按时序存 / 取,不做任何跨通道运算。</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,115 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1060" height="580" viewBox="0 0 1060 580" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="1060" height="580" fill="#ffffff"/>
|
||||
<text x="46.0" y="42.0" font-size="21" font-weight="700" fill="#0f172a">40 格环:写指针指「下一格」,窗永远紧贴它身后</text>
|
||||
<text x="46.0" y="68.0" font-size="13" fill="#64748b">DEPTH = 2 周波 × 20 点 = 40;窗长 n 取 1~40,取的是 [head−n, head−1] 这 n 格。</text>
|
||||
<text x="46.0" y="88.0" font-size="13" fill="#64748b">图例:蓝 = 本窗 20 点,灰 = 更旧的 10 点,琥珀 = 最旧的 10 点(下一批被覆盖)。</text>
|
||||
<text x="46.0" y="108.0" font-size="13" fill="#64748b">窗首 start = (head + 40 − n) % 40 —— 先加 40 再减,减法就不落到负数;每写一格整窗右移一格。</text>
|
||||
<rect x="46.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="57.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">0</text>
|
||||
<rect x="70.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="81.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">1</text>
|
||||
<rect x="94.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="105.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">2</text>
|
||||
<rect x="118.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="129.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">3</text>
|
||||
<rect x="142.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="153.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">4</text>
|
||||
<rect x="166.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="177.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">5</text>
|
||||
<rect x="190.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="201.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">6</text>
|
||||
<rect x="214.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="225.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">7</text>
|
||||
<rect x="238.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="249.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">8</text>
|
||||
<rect x="262.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#f1f5f9" stroke="#94a3b8" stroke-width="1.2"/>
|
||||
<text x="273.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">9</text>
|
||||
<rect x="286.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="297.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">10</text>
|
||||
<rect x="310.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="321.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">11</text>
|
||||
<rect x="334.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="345.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">12</text>
|
||||
<rect x="358.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="369.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">13</text>
|
||||
<rect x="382.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="393.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">14</text>
|
||||
<rect x="406.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="417.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">15</text>
|
||||
<rect x="430.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="441.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">16</text>
|
||||
<rect x="454.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="465.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">17</text>
|
||||
<rect x="478.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="489.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">18</text>
|
||||
<rect x="502.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="513.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">19</text>
|
||||
<rect x="526.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="537.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">20</text>
|
||||
<rect x="550.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="561.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">21</text>
|
||||
<rect x="574.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="585.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">22</text>
|
||||
<rect x="598.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="609.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">23</text>
|
||||
<rect x="622.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="633.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">24</text>
|
||||
<rect x="646.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="657.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">25</text>
|
||||
<rect x="670.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="681.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">26</text>
|
||||
<rect x="694.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="705.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">27</text>
|
||||
<rect x="718.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="729.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">28</text>
|
||||
<rect x="742.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="753.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">29</text>
|
||||
<rect x="766.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="777.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">30</text>
|
||||
<rect x="790.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="801.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">31</text>
|
||||
<rect x="814.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="825.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">32</text>
|
||||
<rect x="838.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="849.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">33</text>
|
||||
<rect x="862.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="873.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">34</text>
|
||||
<rect x="886.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="897.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">35</text>
|
||||
<rect x="910.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="921.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">36</text>
|
||||
<rect x="934.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="945.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">37</text>
|
||||
<rect x="958.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="969.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">38</text>
|
||||
<rect x="982.0" y="200.0" width="22.0" height="54.0" rx="3" fill="#fffbeb" stroke="#d97706" stroke-width="1.4"/>
|
||||
<text x="993.0" y="230.5" font-size="9" text-anchor="middle" fill="#64748b">39</text>
|
||||
<line x1="297.0" y1="184.0" x2="297.0" y2="198.0" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<polygon points="297.0,200.0 292.5,192.0 301.5,192.0" fill="#2563eb"/>
|
||||
<text x="297.0" y="176.0" font-size="11" text-anchor="start" font-weight="700" fill="#2563eb">start(最老点)</text>
|
||||
<line x1="753.0" y1="184.0" x2="753.0" y2="198.0" stroke="#2563eb" stroke-width="1.4"/>
|
||||
<polygon points="753.0,200.0 748.5,192.0 757.5,192.0" fill="#2563eb"/>
|
||||
<text x="753.0" y="176.0" font-size="11" text-anchor="end" font-weight="700" fill="#2563eb">head−1(最新点)</text>
|
||||
<line x1="777.0" y1="184.0" x2="777.0" y2="198.0" stroke="#d97706" stroke-width="1.4"/>
|
||||
<polygon points="777.0,200.0 772.5,192.0 781.5,192.0" fill="#d97706"/>
|
||||
<text x="777.0" y="176.0" font-size="11" text-anchor="start" font-weight="700" fill="#d97706">head(下一拍写这里)</text>
|
||||
<line x1="993.0" y1="286.0" x2="65.0" y2="286.0" stroke="#e11d48" stroke-width="1.6" stroke-dasharray="7 5"/>
|
||||
<polygon points="57.0,286.0 66.0,281.5 66.0,290.5" fill="#e11d48"/>
|
||||
<text x="46.0" y="308.0" font-size="11" font-weight="700" fill="#e11d48">写满后回绕:39 的下一格是 0 —— 窗可能跨过环尾,所以取数必须逐点取模</text>
|
||||
<rect x="46.0" y="340.0" width="300.0" height="120.0" rx="8" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.8"/>
|
||||
<text x="58.0" y="364.0" font-size="13.5" font-weight="700" fill="#0f172a">整帧写完才推进 head</text>
|
||||
<text x="58.0" y="386.0" font-size="11.5" fill="#64748b">9 路语义值一次写齐、再动指针。单帧内不存在「半</text>
|
||||
<text x="58.0" y="401.0" font-size="11.5" fill="#64748b">新半旧」,消费侧取到的窗永远落在整帧边界上。</text>
|
||||
<rect x="365.0" y="340.0" width="300.0" height="120.0" rx="8" fill="#eff6ff" stroke="#2563eb" stroke-width="1.8"/>
|
||||
<text x="377.0" y="364.0" font-size="13.5" font-weight="700" fill="#0f172a">只存不算是分层契约</text>
|
||||
<text x="377.0" y="386.0" font-size="11.5" fill="#64748b">同刻性归上游 adc_input 负责;本层不假设、不校</text>
|
||||
<text x="377.0" y="401.0" font-size="11.5" fill="#64748b">正,也不做任何跨通道运算——所以它能脱离硬件做主</text>
|
||||
<text x="377.0" y="416.0" font-size="11.5" fill="#64748b">机侧单测。</text>
|
||||
<rect x="684.0" y="340.0" width="300.0" height="120.0" rx="8" fill="#fffbeb" stroke="#d97706" stroke-width="1.8"/>
|
||||
<text x="696.0" y="364.0" font-size="13.5" font-weight="700" fill="#0f172a">容量取 2 周波</text>
|
||||
<text x="696.0" y="386.0" font-size="11.5" fill="#64748b">1 周波就够算全周傅氏;另 1 周波是给 1.5~2 周波</text>
|
||||
<text x="696.0" y="401.0" font-size="11.5" fill="#64748b">长窗(抗衰减直流)留的余量,改容量只动一个宏。</text>
|
||||
<line x1="46.0" y1="490.0" x2="1014.0" y2="490.0" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<text x="46.0" y="514.0" font-size="13" fill="#64748b">n = 40 时 start == head:环满时「下一拍要覆盖的那一格」恰好就是最老点 —— 不是特例,是唯一自洽解。同理 n = 20 时窗正好是 head 身后的 20 格,不</text>
|
||||
<text x="46.0" y="533.0" font-size="13" fill="#64748b">多不少。</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
+69
-4
@@ -146,9 +146,71 @@ flowchart TD
|
||||
|
||||
## 5. 全周傅氏相量输入 `sample_ring.c`
|
||||
|
||||
**解决的问题**:相量计算要"以当前时刻为终止的一周波",而 语义取数与有效性每次只给最新一帧。本层按**语义通道**
|
||||
存最近 `SAMPLE_RING_CYCLES = 2` 个周波(`SAMPLE_RING_DEPTH = 40` 点),与参考系统
|
||||
`CN_BUFSAM_CYCLE = 2` 一致,并为后续加长数据窗(1.5~2 周波抗衰减直流)预留空间。
|
||||
### 5.1 为什么需要这一层
|
||||
|
||||
**一句话**:上游每 1 ms 只交"本拍那一帧",下游相量要的是"以当前时刻为终止的一整周波",
|
||||
中间那 19 个历史点没有任何一层替它保留——本层就是补这个缺口的地方。
|
||||
|
||||
三个缺口,任一个单独存在都能让相量算不出来或算不准:
|
||||
|
||||
| 缺口 | 上游给什么 | 下游要什么 | 不缓冲的后果 |
|
||||
|---|---|---|---|
|
||||
| **点数** | 每拍 1 帧(9 路语义值) | 一周波 20 点(`PHASOR_N`) | 只有 1 点 → 全周傅氏退化成瞬时值,谐波抑制完全失效 |
|
||||
| **历史** | 覆盖写,新值顶掉旧值 | 以当前时刻为终点的**滑窗** | 每拍都要重算,但每拍只拿到当前点 → 窗永远凑不齐 |
|
||||
| **同源** | —— | `phasor` / `freq` / `meas` 必须看**同一个窗** | 各存一份历史 → 窗边界差一拍,相位差与频率跟着漂 |
|
||||
|
||||

|
||||
|
||||
**两种"更省事"的替代方案,都否决了**:
|
||||
|
||||
- **在 `phasor.c` 里自己存**:`freq` 要相邻两拍相量、`meas` 要同源快照,三个模块各存一份 →
|
||||
同一时刻存在三个"窗",一旦谁跳过一拍就没有单一真值可对,排查时分不清是采集问题还是缓存问题。
|
||||
- **在 `adc_task.c` 里存一个数组**:等于把环、窗、覆盖这些算法语义塞进任务层,既破坏
|
||||
"任务只消费 / 编排"的分层,又让这段逻辑失去主机侧单测能力(`adc_task.c` 依赖 FreeRTOS 与 HAL,
|
||||
不能原样 `#include`)。
|
||||
|
||||
结论:**"保存历史"必须是一个自己的、无依赖的、单一所有者的层**——这就是本模块存在的理由。
|
||||
|
||||
### 5.2 设计思想
|
||||
|
||||
五条,每条都对着一个具体的失败模式:
|
||||
|
||||
1. **按语义通道索引,不认物理通道**:第一维是 `adc_semantic_t`(`ADC_SEM_UA` 等),不是 `AINn`、
|
||||
不是 `ADCn`、也不是转换器通道号。V0.1 上 8 路电气量分属两片转换器、网表名与板级名有 13 路置换
|
||||
(见第 3 节),本层若认物理号,改版即全线错位。
|
||||
2. **整帧原子推进**:`push_frame()` 先把 9 路一次写齐,**最后**才动 `s_head`。消费侧看到的窗
|
||||
因此永远落在整帧边界上——单帧内不可能"半新半旧"。
|
||||
3. **只存不算**:本层不校正、不插值、不做任何跨通道运算。同刻性(8 路电气量是否同一时刻)
|
||||
归上游 `adc_input` 负责,本层既不保证也不假设。这条边界换来的是**可脱离硬件单测**:
|
||||
纯算法、不碰寄存器、不碰 RTOS,生产 `.c` 能原样 `#include` 进主机侧套件。
|
||||
4. **环形覆盖写,运行期不分配**:容量固定 40 格、静态数组,无 `malloc`、无 `memmove`。
|
||||
写满后新值直接覆盖最老值——相量只需要最近一周波,更远的历史没有保留价值。
|
||||
5. **容量取 2 周波而不是 1 周波**:1 周波够算全周傅氏,多留 1 周波是给 1.5~2 周波长窗
|
||||
(抗衰减直流分量)预留的余量,与参考系统 `CN_BUFSAM_CYCLE = 2` 同口径。改容量只动一个宏,
|
||||
但要同步 `docs/内存管理/RAM预算台账.md`(容量翻倍 = 1440 B 变 2880 B,见第 11 节)。
|
||||
|
||||

|
||||
|
||||
### 5.3 环内布局与取窗(`start` 为什么这么算)
|
||||
|
||||
`s_head` 是**下一个待写入**的位置,不是最后写入的位置——这是全部推导的起点。
|
||||
最后写入的一点是 `s_head - 1`,于是"以最新点为终点的 n 点窗"= 下标 `[s_head-n, s_head-1]`,
|
||||
窗首(最老点)就是 `s_head - n`,落到 `[0, DEPTH - 1]` 要取模。
|
||||
|
||||
代码里写成 `(s_head + SAMPLE_RING_DEPTH - n) % SAMPLE_RING_DEPTH` 而不是 `(s_head - n) % DEPTH`,
|
||||
是因为后者有两个独立的坑:
|
||||
|
||||
- **16 位无符号下溢后再取模不等价**:`s_head < n` 时减法先回绕成 `65536-(n-s_head)`,
|
||||
而 65536 不是 40 的倍数,`(x mod 65536) mod 40 ≠ x mod 40`。反例 `s_head = 3`、`n = 20`:
|
||||
正确窗首是 23,回绕后得 65519,`65519 % 40 = 39` → 整窗错位。
|
||||
- **有符号负取模**:两个 `uint16_t` 先提升成 `int`,`3 - 20 = -17`;C99 的 `%` 向零截断,
|
||||
`(-17) % 40 == -17`(不是 23),再转回无符号就是一次**越界读**——不报错、不 HardFault。
|
||||
|
||||
**先加一个 DEPTH** 同时解决两件事:`DEPTH ≡ 0 (mod DEPTH)`,加它不改变同余类、数学值不变;
|
||||
而 `s_head + DEPTH - n ∈ [0, 2·DEPTH - 2]`,恒非负、远小于 65535,两种整数提升规则下都不下溢。
|
||||
逐条反例与边界自检(`n == DEPTH` 时 `start == s_head`)见 `App/algo/sample_ring.c` 同名函数的注释。
|
||||
|
||||
### 5.4 API 契约
|
||||
|
||||
| API | 契约 |
|
||||
|---|---|
|
||||
@@ -157,13 +219,16 @@ flowchart TD
|
||||
| `sample_ring_filled()` | 已累积点数(**饱和**于 40)——"够不够一周波"的判据 |
|
||||
| `sample_ring_frames()` | 累计入环帧数(**不饱和**,1 kHz 下约 49.7 天回绕)——与 `tpafe5160_frame_seq()` 对照即判丢帧 |
|
||||
|
||||
**要点**
|
||||
### 5.5 要点与禁令
|
||||
|
||||
- **整帧写入后才统一推进写指针** → 单帧内不可能"半新半旧",消费侧取到的窗永远落在整帧边界。
|
||||
- 本层**只做按时序存 / 取,不做任何跨通道运算**:同刻性由上游 语义取数与有效性负责,本层既不保证也不假设。
|
||||
判定也不在本层(本层只出计数)。
|
||||
- 某通道本拍取数失败 → **整拍跳过入环**(现 `adc_task.c` 的 `s_frames_skip` 即此),
|
||||
**切勿补 0 点**——单个 0 点会显著污染 20 点 DFT 窗。
|
||||
- **读者与写者必须同任务**:全仓唯一写者是 `adcTask` 的压环语句,读者(`phasor_run` / `meas_tick`)
|
||||
与之同拍串行,故本层不加锁、不用 `volatile`、不关中断。要把读者搬到别的任务,必须先在
|
||||
本层补双区 / 序号快照——`sample_ring_window()` 读 `s_head` 后逐点拷贝,跨任务读活缓冲会取到撕裂的窗。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user