Selaa lähdekoodia

修复了按键消抖

codex/plsr-2026-minimal
ywh 3 viikkoa sitten
vanhempi
commit
f6d93ff341
6 muutettua tiedostoa jossa 221 lisäystä ja 146 poistoa
  1. +0
    -128
      Document/PLSR_document/AI问询会话记录_2026.8.17-2026.8.23.md
  2. +1
    -0
      PLSR/Inc/plsr.h
  3. +89
    -0
      PLSR/Src/plsr.c
  4. +36
    -10
      PLSR/Src/plsr_platform_f407.c
  5. +8
    -1
      tests/plsr_host/run_tests.ps1
  6. +87
    -7
      tests/plsr_host/test_plsr_host.c

+ 0
- 128
Document/PLSR_document/AI问询会话记录_2026.8.17-2026.8.23.md Näytä tiedosto

@@ -1,128 +0,0 @@
# AI 问询会话记录(2026-08-17 ~ 2026-08-23)

---

## 周一(2026-08-17):1 ms 轨迹规划审查

**问:分析一下我的轨迹规划代码。**
答:仓库里不是「一个规划器」,而是两套数学模型叠在一起,真正上硬件的只有 1 ms 时间域。运行路径是:段参数 → `MotionBlock` → `PlsrTrajectoryBegin` → `PlsrPlannerConfigureFrameProfile` → 1 ms 帧表(加速/匀速/减速)→ TIM6 每 1 ms 取下一帧写频率 → 硬件有限脉冲计数到 N 后停。`PlsrPlannerBegin()` 只做参数拷贝和合法性检查,不规划。执行器不管脉冲相位,只按 1 ms 取帧;硬件停靠的是 `pulseCount`,不是「帧全部播完」。也就是频率曲线按时间台阶走,停点按脉冲计数走。`plsr_pulse_trajectory.c` 更接近设计书里的脉冲域规划,但引擎里只拿它做 `PlsrSystemMeasurePulseTrajectory()` 计时,不参与输出。组块时 `exitHz` 只看加减时间,不看完成方式/后续方式,所以同向连续段也会先减到停止速度。

**问:什么意思。**
答:人话就是:规划器像按节拍一格一格调音量,执行器像按粒数停豆子。歌还没放到结尾,豆子可能已经数完;或者歌放完了,豆子还差几粒。长匀速、加减时间够时观感正常。短距离、刚起步、中途改速度时,尾速会偏高、加速段会缺一截、末尾会拖一小段慢爬。同向连续段也会先刹到停止速度再爬,因为组块没闻 `sendMode`。

**问:设计书写的和代码做的差在哪。**
答:设计书 `PLSR重构设计书_加减速与多段统一.md` 要的是脉冲域 `∫f dt = k`、`PlsrPlannerGenerate()` 流式吐 `STREAM_ITEM`、量化误差按真实周期闭环、多段解析 `NEXT_FRESH / NEXT_CARRY`、STOP/EXT/改频统一从 `appliedHz` 扣 `committed` 重规划。现状是时间域 1 ms 台阶,没有 `Generate` 接口,只在 1 ms 帧上用 `actualHz` 求和,`sendMode` 写进段对象后基本不用,在线改频有了但 EXT 仍是 cut,`tests/plsr_host` 不测 planner/trajectory。`PlsrPlannerBegin()` 永远只回 `OK` 或 `INVALID`,从不回 `CLIPPED`;clip 发生在 `ConfigureFrameProfile()` 之后,状态通道是死的。

**问:1 ms 规划器结构上哪些值得留,真正的问题是什么。**
答:值得留:规划/发布/执行分开(`SELECT → PREPARE → PUBLISH → RUNNING`),TIM6 只用段对象副本;轨迹是参数对象不是 N 个点的表;在线改频走 inactive bank;面积用量化后频率;短轮廓允许 clip。真正的问题:时间域曲线 vs 脉冲域停点(主矛盾);两套规划器数学不一致;S 曲线瞬时频率没有插值(64 级台阶);减速区第一帧被钉在峰值;候选搜索最后一帧估计不可靠;计算量配在 1 ms 任务里偏重;多段边界还没进规划;改频没扣 committed 预装载脉冲。

**问:看一下这个时间域求根脚本(线性 / 分段二次 S / 正弦)。**
答:脚本对每个脉冲 n 二分时间 \(t_n\),使 \(N(t)=\int_0^{t} f(\tau)\,d\tau = n\),再 \(f[n]=1000/\Delta t_n\)。这和设计书、脉冲域规划的定义一致,作为加速段连续金标这条是对的。时间折算也对:\(T=|f_{end}-f_{start}|\cdot T_{conf}/f_{default}\)。最大问题是 S 曲线不是这份固件:脚本 \(y(s)\) 是分段二次 \(2s^2\) / \(1-2(1-s)^2\),`plsr_pulse_trajectory.c` 与 `plsr_planner.c` 都是 smoothstep \(3s^2-2s^3\)。两端 \(y(0)=0,y(1)=1,\int y=1/2\) 都成立,平均脉冲数能对上,中间形状对不上。界面没有停止速度、总预算、三角/裁峰值、量化后 actualHz,不能当整段 `MotionBlock` 金标。对拍前必须先定:要对当前固件还是要对信捷/cla.py。

---

## 周二(2026-08-18):Codex 逆映射与段衔接

**问:Codex 说轨迹对象多算、128 点少算,65 点逆相位锚点 + 一次 Newton。什么意思。**
答:现在 S/正弦每个点在解逆问题:已知第 k 个脉冲,反求曲线相位,再求频率。正问题(相位→累计脉冲)容易,逆问题难,多半在 64 格积分表上二分/扫描,128 个点就炸。Codex 的招:建轨迹时先造两张 65 点锚点表(加速一张、减速一张),进度 0%、1/64、…、100% 对应曲线相位。以后每个点只做:算归一化进度 → 落在哪两个锚点 → 线性插值 → 最多 Newton 一次 → 相位→频率→`BuildTimerSetting`。从「每点大约 30 次二分」变成「查表+插值+最多一次校正」。不要直接存 128 个频率,因为 128 只是一批容量,下一批 `firstPulse` 不同、总脉冲数不同还要能算。RAM:65×4×2≈520 字节/对象。时间从实时 1 ms 挪到 OS 建段时算一次。

**问:梯形、S、正弦的轨迹对象分开了吗。**
答:算法分开了,对象没分开。当时是一个大结构体靠 `curveMode` 切换:0 线性用 \(v^2\) 递推,1/2 要逆相位,公式还不一样。但 `accelerationNumerator` 这种只有梯形才用的字段,S/正弦对象里也占着;128 点循环里还要反复 `if (curveMode)`。应先改成公共参数一份 + union{linear, smooth, sine}。好处:128 点入口按模式分派一次,循环里不再问 curveMode;以后只优化 S 不会把梯形搞坏。头文件后来已经拆过一刀,但 `.c` 里 128 点路径一度还是老写法。

**问:这个方案的话,段与段之间衔接很慢了岂不是。**
答:会,但只在一种接法下会。两件事别混:段里面每批 128 点会变快;段和段之间若下一段还没建好,电机会停。Codex 把几百微秒~1~2 ms 从出点挪到建对象。若仍在 `PREPARE_SEGMENT` 里做,S/正弦大约多等 1 拍。当前引擎本就是上一段完成后才 `RESOLVE_NEXT → SELECT → PREPARE → PUBLISH`,设计书 Bug 4 那个约 2.5 ms 低电平间隙,根子就是这个。梯形不用 65 点逆表,段间几乎不增。`WAIT_TIME`/换向方向延时、`NEXT_FRESH` 其实不在乎这 1 ms。真正在乎的是后续发送同向连续(`NEXT_CARRY`)。

**问:怎么避免衔接变慢。**
答:Codex 说「也可以只给当前段和预备段保存,压到约 1 KB」,意思就是双缓冲:当前段还在跑时,OS 空闲/提前几拍把下一段 `Begin()` + 65 点表算完;当前段最后几个脉冲时下一段对象已经热好,直接 publish / 切 cursor。交界只剩切对象、装第一拍。预计算放 Prepare、Prepare 放在段后 → 交界变厚;预计算放预备段、上一段还在跑时做完 → 交界可以更紧。你现在连 carry 都还没接上,每段还先减到停,那时候多 1 ms 建表不是主因。

**问:为什么不要直接预存 128 个频率。**
答:下一批 `firstPulse` 一变表就作废;总脉冲数一变(改频、EXT、剩余预算)又废。65 点逆表跟的是这条斜坡的形状(起速/峰值/终速/曲线),不是某一批 128 点。同向连续下一段只要参数定了,可以在上一段还没结束时就算好。这反而比「预存 128 频率」更适合衔接。

---

## 周三(2026-08-19):128 点耗时与正向表

**问:组数据说明优化只完成一半:正弦对象 352 µs、128 点 2890 µs;S 型 319 / 2821。共同瓶颈是相位插值后 Newton + 64 位除法。想把进度→频率也前移。还能怎么优化。**
答:诊断对。正弦和 S 已经打平,说明曲线公式不再是瓶颈。线性 128 点约 402 µs、每点约 3.1 µs 才是地板。热路径 `CurveFastPointHz` 每个点还在:逆表二分(512 格约 9 次比较)→ 插值出相位(一次 64 位除)→ Newton 最多 2 轮(速度、权重、有符号 64 位除)→ 两个 while 整数相位对齐 → 相位→频率 → `BuildTimerSetting`。1~4 是共同瓶颈。下一步「进度→频率」表方向对,但别用现在的每点算法去填 513 个频率锚点:22.6 µs×513≈11.6 ms,对象构建会从 0.3 ms 炸成十几毫秒。表必须正向填。头文件已经在往 `entryFrequencyHz[513]` 走,`.c` 里却还是 progressQ30 + 运行时 Newton。

**问:OS 任务改计算哪些轨迹对象参数,TIM6 的 1 ms 里要怎么计算 128 个 setting。**
答:分工一句话:OS 把轨迹做成查表就能用的对象;TIM6 每 1 ms 只抽出最多 128 个 setting,不准再解方程。OS 在 `PREPARE_SEGMENT` / 预备段 / 改频重规划时算一次:三种曲线都要的公共几何(start/peak/end、三段脉冲、时间、curveMode);梯形只填 \(v^2\) 分子,不建相位表;S/正弦正向铺进度均匀频率表(建议先 129 点)以及 `entryStepQ30`/`exitStepQ30`。TIM6 先处理 cut/完成/EXT,再读游标;入口按 curveMode 分派一次;S/正弦:`p += step`,`index = p >> shift`,插值,同频则拷贝 setting。这一拍 `n = min(128, 剩余脉冲, 队列空位)`。表还没瘦到 400 µs 之前,128 点仍放 OS 预计算,TIM6 只消费现成数组。2.8 ms 那种东西不准进 1 ms 中断。

**问:启动的时候 S 曲线和正弦的轨迹路径已经算好了吗。为什么算得这么慢。**
答:不是。启动时算好的是「这条坡长什么样」(起/峰/终、三段脉冲数、大约一百来个频率锚点),不是「每一个脉冲已经排好队」。整段几千、几万个 PSC/ARR 不会在启动时算完:脉冲总数随段变,中途改频/EXT 一变后面全作废,RAM 也扛不住。慢的不是启动把整条路算完了,是每个点还在解很难的逆问题。对象 319~352 µs 可以忽略;128 点 2821~2890 µs,每点约 22 µs,比线性贵大约 7 倍。正弦已经和 S 一样慢,正好说明瓶颈不是 sin/cos。

**问:所以这个方案是不是不好。**
答:方向对,做到现在这一半不好。停在「相位均匀进度表 + 热路径 Newton」是累赘:结构更胖、速度几乎没变。只有热路径变成「进度累加 → 移位插值频率 → 同频拷贝 / BuildTimerSetting」,并且 128 点掉到大约 400~700 µs、对象仍 <1 ms,才配进 TIM6,才配预备段。撤当前热路径里的 Newton,留对象和分派;换成正向铺进度均匀频率表(先 129 点);timer 同频拷贝,否则永远打不穿线性那 402 µs 地板。

**问:Begin 正向积分→P(x)→单调游标重采样→边界时间→直接频率表;128 点 cursor 递推→O(1) 索引→插值→timer。能用吗。你确定能满足时间要求吗。我改的代码你看一下。**
答:可以用,而且比逆相位+Newton 更接近该做的那一刀。必须写对三件事:表的横轴是脉冲进度不是相位 x(按 x 均匀建表,按脉冲查又要二分);相邻 Δt 换频率要乘「这一格有多少脉冲」,写成 \(f=1000/\Delta T\) 会偏高大约 \(N_{ramp}/M\) 倍;正向游标是 O(表长) 不是 O(总脉冲)。时间上不能 100% 打包票:128 点 400~700 µs 高把握(对照线性 402 µs 还含 64 位 sqrt);Begin <1 ms 仅当闭式/现成正弦表、M=128/256、无 RK4/libm;TIM6 整拍硬吃 128 个不确定,先 OS 预热。你这版对象已是频率表、Newton 离开 128 点、同频拷贝 setting,但 `BuildCurveMap` 对 513 点各做 RK4(约八千次 64 位除),`CurveTableHz` 还要整数 sqrt。S 还改成了 cla.py 分段二次。N=1 用了 `double`。应扳回均匀 P(x)、先 129 点、禁止热路径 sqrt。

---

## 周四(2026-08-20):架构取舍与发波分支

**问:我觉得我的架构有问题,你觉得呢。**
答:有问题,而且不是 S 曲线慢那么点事,是坐标系和节拍没对齐。电机按脉冲活,控制按 1 ms 活。规划做了两份:`plsr_trajectory`(1 ms)在跑,`plsr_pulse_trajectory` 在 Measure。TIM6 现在是 1 ms 采样器,你要的 128 个 setting 是脉冲队列生产者,两种执行器塞进一个中断。段是「做完再备课」:`RESOLVE → SELECT → PREPARE → PUBLISH` 全在上一段停了之后。`sendMode` 进了段对象,组块仍永远 `exitHz = stopSpeed`。改频有 inactive bank,没扣 committed;EXT 是切断。优化发生在错误的所有权上:热路径该是执行器装下一项,却在给测量用的 `BuildBatch` 堆 RK4/sqrt。该留:规划对象/发布副本、MotionBlock 不读现场、CLIPPED 当短轮廓、线性 \(v^2\)、硬件预装载。应在 A(承认 1 ms 命令率)和 B(改成脉冲命令率)里选一个,不要混用。

**问:现在切了一下分支,仔细分析这个分支里是怎么发波的。**
答:当前分支 `codex/plsr-2026-minimal` 已经是脉冲域规划 + 软件队列 + 硬件按 run 发波,不是 TIM6 每 1 ms 改一次频。任务态把轨迹提前切成「同频重复 N 次」的 run,推进 1024 项环形队列(双 bank)。PWM 按当前 PSC/ARR 吐方波;TIM9/TIM12 数脉冲;当前 run 结束前一拍(CC1)预装下一 run,结束时(UIF)提交。IRQ 里不算规划。`PlsrPulseTimerIrq` 在 `STREAM / STEP_TABLE / STOPPING` 下直接 return。启动时 `PrepareShortProfile` → `TakeRun` 第一 run → `QueueBegin` → Fill 到约 400 项 → `StartCountedStreamPrepared`。之后 1 ms 任务 `Fill`:水位低于 800 就补,每一拍最多 200 项。队列空且规划还没完成:借用最多 2 ms / 200 脉冲,按最后一个 setting 继续发,任务之后把同样数量的规划脉冲还债(推进 planner 但不入队)。

**问:哪种方法好一点。没听懂。**
答:这个分支更好。发波该按脉冲走,不该按 1 ms 走。上一套:规划按时间切台阶,硬件按脉冲个数停,短段/起步/改频一定会偏。现在这套:规划吐 `{setting, N}`,换挡发生在完整脉冲边界,1 ms 任务只负责把未来几毫秒填进队列,IRQ 不准做规划数学。代价:冷启动要先填约 400 项才出第一波;斜坡 100 kHz 时 1 ms 要能产约 200 项;AB 还是旧逐脉冲 IRQ。人话:好的方法是先算每个脉冲多长,再按这个发;差的方法是每毫秒改一次速度,另外再数脉冲停。菜单是 `{这一档周期, 连发 N 个}`;硬件发完这一页再翻下一页,翻页发生在一个完整波结束的时候。

**问:现在的脉冲点对应的频率是怎么算的。我问你在哪算的。拆开揉碎和我讲讲。**
答:每个脉冲频率 = 这个脉冲该占多长时间的倒数,不是「这一毫秒该跑多快」。第 k 个脉冲要满足:从开始到这个脉冲结束,速度曲线底下的面积刚好等于 k 个脉冲。\(f[k]=1/(t_k-t_{k-1})\),再收成 PSC/ARR。`Begin` 不算每个点,只切加速 N1 / 匀速 N2 / 减速 N3。斜坡上把总面积切成 N 等份;目标面积加一格;找时间边界 x(前两个脉冲二分,后面用斜率推);\(\Delta x = x - \)已经按真实频率走过的时间;\(f_{req}=\)总面积\((N\cdot\Delta x)\);`BuildTimerSetting`;用 `actualHz` 把时间账往前推(闭环,量化误差不累计)。算的地方:`plsr_planner.c` 的 `PlsrPlannerTakeRampStep()`(约 468 行)是斜坡频率核心;匀速在 `TakeStep()`(约 553 行)直接用 `peakHz`。`Generate`(约 701 行)是对外入口。`plsr.c` 的 `TakeRunLimited` / `Fill` 只取出来入队。IRQ 不算。

**问:流程和数据流。你觉得复杂吗。OS 是不是一直在算频率帧存到队列里。一拍算几个。计算速度怎么样。**
答:总流程:解析本段 → `PrepareShortProfile`/`PlannerBegin`(还没有每个点的 Hz)→ `TakeRun`/`Generate` 算出下一项 → 灌队列 → 硬件开波 → 同时 1 ms 补队列、硬件按 run 翻页。数据:配置/现场合成 `MotionBlock`(不读 Modbus)→ Begin 得到三段脉冲数和 peak → Generate 吐 `STREAM_ITEM` → 入队成 `PROFILE_ENTRY` → CC1 预装、UIF 提交。三种 Hz 别混:配置目标、requested、actual。复杂在该复杂的地方(队列/双 bank/改频/handoff),公式本身就是面积÷时间。对,OS 一直在算 `{周期, N}` 存进队列,但不是 1 ms 时间帧。一拍最多 200 项(启动 400),堆到约 800 就停;斜坡一项常是 1 个脉冲,匀速一项 N 可以很大。速度:匀速一项约 1~3 µs;线性斜坡约 5~15 µs;S/正弦约 8~25 µs。一拍 200 项线性大约 1~3 ms,可能比 1 ms 墙钟还长,所以靠 8 ms 库存和 2 ms 借债。没有板上逐项 DWT,不能装测过。

---

## 周五(2026-08-21):队列节点与补货

**问:现在是 OS 任务里算好(频率,几个点)放到 1024 的队列里,然后 UIF 不断去消费这个队列是吧。**
答:骨架对,差两点。不是每个 UIF 都从队列拿一项:计数器盯当前这一 run,还差 1 个脉冲(CC1)才取下一项并预装 PSC/ARR;这一 run 的 N 个发完(UIF)才提交、换成下一项的 N。斜坡 N=1 时几乎每个脉冲翻一页;匀速 N=200 时发 200 个相同的波才吃队列一次。IRQ 里只拿现成项,不算频率。

**问:队列的元素叫什么。算好 `{这一档频率(PSC/ARR), 连续发几个脉冲}` 怎么形容。脉冲 run 是怎么算的。**
答:每一格叫 `PLSR_PROFILE_ENTRY`。整条队列叫 `PLSR_PROFILE_QUEUE`(1024 项,双 bank)。规划器吐出来还叫 `PLSR_STREAM_ITEM`,入队时加上 `startsNextSegment`。最短说法:一项 = 一档同频脉冲,「这一档的周期,连发 N 个」。不要说频率帧,要说脉冲 run / 同频项。run 不是单独一套公式:先算出下一个脉冲该用哪个周期,再决定这个周期连发几个。斜坡 `repeatCount` 通常=1;匀速先给剩下的全部,再被 `PlsrProfileRunLimit` 切成大约最多 2 ms;入队时量化后相同的相邻项还会再合并。

**问:计算函数和塞队列函数在哪。每一个节点都放了什么。**
答:算在 `PLSR/Src/plsr_planner.c`:`Begin`(592)切三段,`StartRamp`(398)切面积,`TakeRampStep`(468)算斜坡 Hz,`TakeStep`(553)分派,`Generate`(701)对外入口,`BuildStep`(449)量化。塞在 `PLSR/Src/plsr.c`:`TakeRunLimited`(1998)调 Generate 再按约 2 ms 切 N,`Fill`(3125)1 ms 循环算+写,`AppendLocked`(2624)真正写入、同档合并。IRQ 消费:`PlsrExecTakeCountedRunIrq`(4617)只读。节点只有四样:`setting`(PSC/ARR/actualHz)、`requestedFrequencyHz`(公式 Hz,发波不用)、`repeatCount`、`startsNextSegment`。没有脉冲序号、段号、面积。

**问:计算脉冲 run 到塞进队列到消费的流程,函数调用链。**
答:启动:`BeginSegmentOutput`(3680)→ `PrepareShortProfile` → `TakeRun` 第一 run → `QueueBegin` → `Fill(400)` → `StageCountedHandoff` → 再 Fill → `StartCountedStreamPrepared`。运行:`PlsrPoll1ms`(6028)→ 水位≤100 时预算改回 400 → `Fill(800, 200或400)` → `StageCountedHandoff` → 再 Fill。Fill 每一圈:拷出 producerProfile → `TakeRunLimited` → 核对 generation/bank/epoch → 还债则推进 planner 不 Append,否则 `AppendLocked`。消费:`TIM1_BRK_TIM9` / `TIM8_BRK_TIM12` → `HandleCounterIrq` → CC1 `PrepareNextStepIrq` → `TakeCountedRunIrq` 预写 PSC/ARR;UIF `FiniteCounterIrq` 提交换档。

**问:解释 Poll1ms 里补队列那一段。后面就没有计算了是吧。**
答:那段是 1 ms 任务后半截:先处理 START/STOP/CLEAR,再 `ExecServiceCountedEvents`(段边界必须在计数同步之前消费,否则会把下一段脉冲算进旧段),再位置掉电,再 `ServiceCountedExecutor`(这段波若已经发完就 HandleBoundary,本拍不再 Fill)。还在跑才看水位:≤100 则本拍预算从 200 加到 400。第一次 Fill 补当前段到 800(要 active 且 generatorComplete==0)。第二次 `StageCountedHandoff` 把下一段预热的第一/第二 run 接到队尾,再 Fill 一次。活队列「给当前段流式补 run」到两次 Fill 基本结束。后面还有:`ServiceTimedStartPreparation`(空闲 bank 预热下一段)、延时到 0 的 `BeginSegmentOutput`、动态改频 `ReplanPulseDir`。不是空白,是改干别的计算。

---

## 周六(2026-08-22):Fill、欠债、匀速切段

**问:为什么会欠债。**
答:欠债 = 队列空了,规划还没跟上,硬件先用上一档频率多打了几下。IRQ 要下一项时 `read==write`,但 `generatorComplete` 还是 0。常见原因:斜坡+高速(100 kHz 几乎一个脉冲一项,每毫秒吃 100 项,任务一拍最多算 200 项还可能超时);这一拍先忙了改频/handoff;Fill 在门外算的时候 IRQ 把剩下的格子吃光了。空了就停波会突然断一截。所以用 `lastSetting` 再连发最多约 2 ms(或 200 个脉冲),`underrunDebtPulses += N`,`generation++`。下一拍 Fill 发现有债:照样 Generate 把规划器往前拨 N 个,不 Append,减债。还清之前不能 `generatorComplete=1`。总脉冲数仍然要准:借的是「提前用末频打了规划里后面那 N 个」,不是额外赠送。波形上可能平一段末频(最多 2 ms)。

**问:Fill 这段 while 在干什么。TakeRunLimited 在干什么。`pendingRepeats==0` 是什么情况。**
答:Fill 每圈:关中断看还要不要算(不 active / 已 complete / 深度≥目标 / 预算用尽则成功返回)→ 先扣预算 → 记下 generation/epoch/债 → 拷出 profile → 开中断 Generate → 再关门核对 bank/世代/是否已满,对不上就 continue → 写回 profile → 有债减债不入队,没债 Append → 吐完且无债则 generatorComplete=1。TakeRunLimited:从规划器手里掰下下一项。`pendingRepeats==0` 才 Generate 拿新的一档(可能 N 很大);然后 N 取 min(本档剩余, 本段剩余, maximumRepeats, 约 2 ms 上限)。还债时 maximumRepeats=当前债。人话:规划器有时一次给整根同频香肠;`pendingRepeats` 是还没切出去的长度;等于 0 才再要一根。

**问:假设加速 1000 个脉冲,匀速 5000 个,减速 1000,现象是什么样。**
答:加速 1000:几乎一格一个波,频率一格一变,`pendingRepeats` 几乎每次都是 0,每圈都 Generate,队列大约 1000 格(量化相同才会合并)。匀速 5000:频率不变,但按约 2 ms 切开——1000 Hz 时一格约 2 个,约 2500 格;8000 Hz 时一格约 16 个。Generate 只发生一次,`pendingRepeats` 从 5000 往下减。示波器是很长一串等周期方波,看不出切了多少刀。减速 1000:再变回一格一波,频率下降。高速时盘子主要被斜坡撑满;匀速反而很省格子。

**问:为什么匀速要切这么多次。是不是队列里放 8000,8000…8500 个,或 (8000,8500)、(7999,1)、(7998,1)。入队数量是动态的吗。**
答:因为硬件一旦开始打这一项,就要把这一项的 N 个全部打完,中途改频也插不进这一项的肚子里。一次 8500、1000 Hz 就是 8.5 秒同一档,改频要等 8.5 秒。所以上限约 2 ms。多次的是 TakeRunLimited 进进出出,不是规划器算 4250 遍频率——频率只算 1 次。不是 (8000,8500) 一格吃完;不是频率 7999、7998 往下掉(那是案板上 `pendingRepeats` 在减,不进队列);也不是 8500 个光秃秃的 8000。实际是很多格、频率相同、每格 N 大约是 2 ms 能打多少个,例如 8000 Hz 时 `(8000Hz, 16)` 重复约 531 格加余数。入队多少、每一格里装几个都是当场算的:min(本档剩余, 本段剩余, 2 ms 能打几个, 还债则当前欠多少);这一拍塞几格也随水位和预算变。

---

## 周日(2026-08-23):Generate、规划上下文、会话记录

**问:解释 PlsrPlannerGenerate。局部变量含义讲了吗。**
答:这是规划器对外窗口:一次最多吐 `capacity` 项 `{setting, 请求频率, N}`,能合并的叠到上一项上。`TakeRunLimited` 每次 `capacity=1`,所以几乎总是吐 1 项就回去。开头指针空/`active==0`/`capacity==0` 则返回 0。循环条件:还能写,且 `generatedPulses < pulseBudget`。每圈 `TakeStep` 要下一步;失败则 `active=0` 并 break。若本次已有上一格且 setting 与 requestedHz 都相同且 N 不溢出,则只 `repeatCount +=`;否则新开一格、`produced++`。然后 `generatedPulses += repeatCount`(按脉冲不是按项数)。吐完预算则 `active=0`。返回 `produced`。局部变量:`setting` 是量化后 PSC/ARR;`requestedFrequencyHz` 是公式 Hz;`repeatCount` 是这一档连发几个;`produced` 是本次写了几项。`capacity=1` 时匀速 5000 会整项放在 `output[0]`,切开 2 ms 是后面 TakeRunLimited 的事。

**问:STREAM_ITEM 是什么。和队列节点差在哪。**
答:`PLSR_STREAM_ITEM` 是规划器吐出来的一项,还没进 1024 队列:`setting`、`requestedFrequencyHz`、`repeatCount`。没有 `startsNextSegment`。`pendingItem` 就是一个 STREAM_ITEM。PROFILE_ENTRY 在这上面再加段边界标记,并可能把 N 切短。

**问:TakeStep 是计算核心吗。**
答:算「现在该出哪一档」的分派核心是它;算「这个斜坡脉冲多少 Hz」的核心不是它。它只看 `generatedPulses` 落在加速/匀速/减速哪一段,第一次进斜坡时调 `StartRamp`,加速/减速交给 `TakeRampStep`,匀速 `repeatCount=剩下的匀速个数` 并 `BuildStep(peakHz)`。几何核心是 `Begin`;量化是 `BuildStep`。

**问:给 PLANNER_CONTEXT 带注释(含行尾 // 格式)。**
答:按块输入、三段几何、已吐脉冲、当前斜坡账本排。`block` 是本块输入;`phasePulses` 是起点相位;`startHz/peakHz/endHz` 是裁剪后的实际端点;`entry/steady/exitPulses` 是三段脉冲数;`generatedPulses` 是已交给调用方的脉冲总数。`ramp*` 只在当前加速或减速内有效:相对序号、总脉冲、起终点、折算时间、上一档 actualHz、Q32 总面积/每格面积/目标面积/边界/已走时间/上一相位步长、第 1/2 个精确边界、余数及累加器。`rampKind`:0 无斜坡/匀速,1 加速,2 减速。`clipped` 表示预算不足已裁但仍可执行。`active` 表示是否还可 Generate。Q32 表示把斜坡归一化时间 0~1 放大到 \(2^{32}\)。

+ 1
- 0
PLSR/Inc/plsr.h Näytä tiedosto

@@ -119,6 +119,7 @@ uint16_t PlsrTestProfileProducerNextPeriod(void);
uint16_t PlsrTestActiveProfileNextPeriod(void);
uint32_t PlsrTestLastGateEvent(void);
uint32_t PlsrTestLastDiagnosticFinishEvent(void);
uint8_t PlsrTestInitialDirectionPreparePending(void);
void PlsrTestSetPosition(int32_t position, uint8_t positionValid);
void PlsrTestClearPersistentStorage(void);
void PlsrTestResetSaveCount(void);


+ 89
- 0
PLSR/Src/plsr.c Näytä tiedosto

@@ -334,6 +334,9 @@ static uint8_t PlsrInitialized;
static volatile uint8_t PlsrCurrentSegment;
static uint8_t PlsrDirectionDelayActive;
static uint16_t PlsrDirectionDelayRemainingMs;
static uint8_t PlsrInitialDirectionPreparePending;
static uint8_t PlsrInitialDirectionPrepareLevel;
static uint32_t PlsrInitialDirectionPrepareEpoch;
static volatile uint8_t PlsrSegmentClockStarted;
static volatile uint32_t PlsrSegmentElapsedMs;
static volatile uint32_t PlsrWaitElapsedMs;
@@ -406,6 +409,7 @@ static uint32_t PlsrPulseDirStartFrequency(
uint32_t carryFrequencyHz);
static uint32_t PlsrEffectiveStopFrequency(uint32_t segmentFrequencyHz);
static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz);
static uint8_t PlsrServiceInitialDirectionPrepare(void);
static void PlsrHandleBoundary(uint8_t extEdge);
static void PlsrTransitionToNext(uint8_t allowCarry);
static void PlsrRequestCut(uint32_t expectedEpoch);
@@ -4103,6 +4107,21 @@ static uint8_t PlsrStartSegment(uint8_t segmentNumber,
}
}

if ((PlsrActiveConfig.outputMode == PLSR_OUTPUT_PULSE_DIR)
&& (directionChanged != 0U)
&& (entryType == PLSR_SEGMENT_ENTRY_COMMAND_START))
{
/* Keep the expensive initial profile preparation and the direction
GPIO edge in different real 1 ms polls. The next poll publishes
direction and loads the full delay without decrementing it. */
PlsrInitialDirectionPrepareLevel = directionLevel;
PlsrInitialDirectionPrepareEpoch = PlsrSegmentEpoch;
PlsrInitialDirectionPreparePending = 1U;
PlsrDelayedEntryType = entryType;
PlsrRunStatus = PLSR_STATUS_ACCELERATING;
return 1U;
}

if (PlsrPlatformPrepare((uint8_t)PlsrActiveConfig.pulseOutput,
(uint8_t)PlsrActiveConfig.directionOutput,
directionLevel,
@@ -4136,6 +4155,56 @@ static uint8_t PlsrStartSegment(uint8_t segmentNumber,
return PlsrBeginSegmentOutput(startFrequencyHz);
}

static uint8_t PlsrServiceInitialDirectionPrepare(void)
{
uint64_t magnitude;
uint32_t criticalState;
uint8_t segmentNumber;
uint8_t positive;
uint8_t directionLevel;

if (PlsrInitialDirectionPreparePending == 0U)
{
return 0U;
}
if ((PlsrInitialDirectionPrepareEpoch != PlsrSegmentEpoch)
|| (PlsrCurrentSegment == 0U)
|| (PlsrCurrentSegment > PlsrActiveConfig.segmentCount)
|| (PlsrActiveConfig.outputMode != PLSR_OUTPUT_PULSE_DIR)
|| (PlsrRunStatus != PLSR_STATUS_ACCELERATING))
{
PlsrInitialDirectionPreparePending = 0U;
PlsrEnterError(PLSR_ERROR_INTERNAL);
return 1U;
}

directionLevel = PlsrInitialDirectionPrepareLevel;
segmentNumber = PlsrCurrentSegment;
positive = PlsrCountPositive;
PlsrInitialDirectionPreparePending = 0U;
if (PlsrPlatformPrepare((uint8_t)PlsrActiveConfig.pulseOutput,
(uint8_t)PlsrActiveConfig.directionOutput,
directionLevel,
(uint8_t)PlsrActiveConfig.outputMode,
positive) == 0U)
{
criticalState = PlsrPlatformEnterCritical();
PlsrInvalidateTimedStartLocked();
PlsrPlatformExitCritical(criticalState);
PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE);
return 1U;
}

magnitude = PlsrRemainingSnapshot();
PlsrDiagnosticBeginSegment(segmentNumber, magnitude, positive);
PlsrLastDirectionValid = 1U;
PlsrLastDirectionOutput = (uint8_t)PlsrActiveConfig.directionOutput;
PlsrLastDirectionLevel = directionLevel;
PlsrDirectionDelayRemainingMs = PlsrEffectiveDirectionDelayMs();
PlsrDirectionDelayActive = 1U;
return 1U;
}

static void PlsrMarkPersistenceDirty(uint16_t delayMs)
{
PlsrPersistenceDirty = 1U;
@@ -4191,6 +4260,7 @@ static void PlsrFinishCompleted(void)
PlsrSegmentClockStarted = 0U;
PlsrDirectionDelayActive = 0U;
PlsrDirectionDelayRemainingMs = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrExtCaptureSegment = 0U;
PlsrExtEdgePending = 0U;
PlsrStopRequested = 0U;
@@ -4233,6 +4303,7 @@ static void PlsrFinishStopped(void)
PlsrSegmentClockStarted = 0U;
PlsrDirectionDelayActive = 0U;
PlsrDirectionDelayRemainingMs = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrExtCaptureSegment = 0U;
PlsrExtEdgePending = 0U;
PlsrStopRequested = 0U;
@@ -4275,6 +4346,7 @@ static void PlsrEnterError(PLSR_ERROR error)
PlsrSegmentClockStarted = 0U;
PlsrDirectionDelayActive = 0U;
PlsrDirectionDelayRemainingMs = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrExtCaptureSegment = 0U;
PlsrExtEdgePending = 0U;
PlsrStopRequested = 0U;
@@ -5777,6 +5849,9 @@ uint8_t PlsrInit(void)
PlsrCurrentSegment = 0U;
PlsrDirectionDelayActive = 0U;
PlsrDirectionDelayRemainingMs = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrInitialDirectionPrepareLevel = 0U;
PlsrInitialDirectionPrepareEpoch = 0UL;
PlsrSegmentClockStarted = 0U;
PlsrExtPreviousLevel = 0U;
PlsrExtEdgePending = 0U;
@@ -5894,6 +5969,7 @@ static void PlsrExecuteStart(void)
PlsrShortProfile.active = 0U;
PlsrError = PLSR_ERROR_NONE;
PlsrLastDirectionValid = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrDelayedEntryType = PLSR_SEGMENT_ENTRY_COMMAND_START;
PlsrPulseDirMotionStarted = 0U;
PlsrCheckpointPosition(1U);
@@ -6022,6 +6098,7 @@ static void PlsrExecuteClear(void)
PlsrBoundaryWasCut = 0U;
PlsrDirectionDelayActive = 0U;
PlsrDirectionDelayRemainingMs = 0U;
PlsrInitialDirectionPreparePending = 0U;
PlsrExtCaptureSegment = 0U;
PlsrExtEdgePending = 0U;
PlsrShortProfile.active = 0U;
@@ -6242,6 +6319,13 @@ void PlsrPoll1ms(void)
return;
}

if (PlsrServiceInitialDirectionPrepare() != 0U)
{
/* Direction changed in this poll. Leave the complete configured
delay untouched until the following real 1 ms poll. */
return;
}

/* 段边界事件必须在任何计数同步之前消费:快照是 IRQ 边界时刻的
硬件计数,晚消费会把下一 run 的脉冲算进旧段。 */
PlsrExecServiceCountedEvents();//把 IRQ 丢进边界事件环的东西在任务里消化:切段、记账。只做账,不算频率
@@ -7253,4 +7337,9 @@ uint32_t PlsrTestLastDiagnosticFinishEvent(void)
{
return PlsrHostTestLastDiagnosticFinishEvent;
}

uint8_t PlsrTestInitialDirectionPreparePending(void)
{
return PlsrInitialDirectionPreparePending;
}
#endif

+ 36
- 10
PLSR/Src/plsr_platform_f407.c Näytä tiedosto

@@ -1373,7 +1373,7 @@ uint8_t PlsrTestFlashNeedsStartupRecovery(
#define PLSR_FREQUENCY_VERIFY_AB_AUX_IRQ (2U)
#define PLSR_QUEUE_WRITE_GUARD_COUNTS (64UL)
#define PLSR_FINITE_WRITE_GUARD_COUNTS (128UL)
#define PLSR_INPUT_RETRIGGER_BLOCK_MS (5UL)
#define PLSR_INPUT_LOW_REARM_MS (5UL)
#define PLSR_FLASH_ERASE_NONE (0U)
#define PLSR_FLASH_ERASE_SECTOR_A (1U)
#define PLSR_FLASH_ERASE_SECTOR_B (2U)
@@ -1582,8 +1582,9 @@ static volatile uint16_t PlsrFiniteStepIndex[4];
static volatile uint16_t PlsrFiniteBoundaryReadIndex[4];
static volatile uint16_t PlsrFiniteCompletedStepCount[4];
static volatile uint16_t PlsrPlatformFaultPending;
static uint32_t PlsrInputLastAcceptedTick[2];
static uint8_t PlsrInputAcceptedValidMask;
static volatile uint32_t PlsrInputLowSinceTick[2];
static volatile uint8_t PlsrInputAcceptedValidMask;
static volatile uint8_t PlsrInputLowSeenMask;

static void PlsrHandleTimerIrq(uint8_t pulseOutput);
static void PlsrCounterSuspend(uint8_t pulseOutput);
@@ -2944,7 +2945,7 @@ uint8_t PlsrPlatformInit(void)
gpio.Alternate = 0U;
HAL_GPIO_Init(GPIOH, &gpio);

gpio.Mode = GPIO_MODE_IT_RISING;
gpio.Mode = GPIO_MODE_IT_RISING_FALLING;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
gpio.Alternate = 0U;
@@ -2952,9 +2953,10 @@ uint8_t PlsrPlatformInit(void)
HAL_GPIO_Init(GPIOB, &gpio);
gpio.Pin = GPIO_PIN_12;
HAL_GPIO_Init(GPIOG, &gpio);
PlsrInputLastAcceptedTick[0] = 0UL;
PlsrInputLastAcceptedTick[1] = 0UL;
PlsrInputLowSinceTick[0] = 0UL;
PlsrInputLowSinceTick[1] = 0UL;
PlsrInputAcceptedValidMask = 0U;
PlsrInputLowSeenMask = 0U;
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_5 | GPIO_PIN_12);
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2U, 0U);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
@@ -4451,6 +4453,7 @@ static uint8_t PlsrInputExtiAccept(uint8_t inputSelection)
{
uint32_t now;
uint8_t bit;
uint8_t level;

if (inputSelection > 1U)
{
@@ -4458,13 +4461,36 @@ static uint8_t PlsrInputExtiAccept(uint8_t inputSelection)
}
now = HAL_GetTick();
bit = (uint8_t)(1U << inputSelection);
if (((PlsrInputAcceptedValidMask & bit) != 0U)
&& ((uint32_t)(now - PlsrInputLastAcceptedTick[inputSelection])
< PLSR_INPUT_RETRIGGER_BLOCK_MS))
level = PlsrPlatformReadInput(inputSelection);

if (level == 0U)
{
/* Falling edges never reach the motion state machine. They only
begin a possible stable-low rearm interval. */
PlsrInputLowSinceTick[inputSelection] = now;
PlsrInputLowSeenMask |= bit;
return 0U;
}

if ((PlsrInputAcceptedValidMask & bit) == 0U)
{
/* Preserve the required first-edge-immediate behavior. */
PlsrInputAcceptedValidMask |= bit;
PlsrInputLowSeenMask &= (uint8_t)~bit;
return 1U;
}

if (((PlsrInputLowSeenMask & bit) == 0U)
|| ((uint32_t)(now - PlsrInputLowSinceTick[inputSelection])
< PLSR_INPUT_LOW_REARM_MS))
{
/* Any early rising edge breaks the stable-low interval. A new
falling edge must start the full interval again. */
PlsrInputLowSeenMask &= (uint8_t)~bit;
return 0U;
}
PlsrInputLastAcceptedTick[inputSelection] = now;

PlsrInputLowSeenMask &= (uint8_t)~bit;
PlsrInputAcceptedValidMask |= bit;
return 1U;
}


+ 8
- 1
tests/plsr_host/run_tests.ps1 Näytä tiedosto

@@ -1,3 +1,5 @@
param([string]$Filter)

$ErrorActionPreference = "Stop"

$gcc = "D:\Dev-Cpp\MinGW64\bin\gcc.exe"
@@ -32,7 +34,12 @@ try {
$exitCode = $LASTEXITCODE
}
else {
& $temporaryExe
if ([string]::IsNullOrEmpty($Filter)) {
& $temporaryExe
}
else {
& $temporaryExe $Filter
}
$exitCode = $LASTEXITCODE
}
}


+ 87
- 7
tests/plsr_host/test_plsr_host.c Näytä tiedosto

@@ -2,6 +2,7 @@

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#define PLSR_WAIT_TIME (0U)
#define PLSR_WAIT_SIGNAL (1U)
@@ -237,6 +238,13 @@ static PLSR_MB_RESULT SendCommand(uint16_t command)
if (result == PLSR_MB_OK)
{
PlsrPoll1ms();
if ((command == PLSR_COMMAND_START)
&& (PlsrTestInitialDirectionPreparePending() != 0U))
{
/* Most tests begin at the direction-delay state. Preserve that
abstraction while dedicated tests inspect the isolated poll. */
PlsrPoll1ms();
}
}
return result;
}
@@ -2417,22 +2425,87 @@ static void TestStopAtPendingBoundary(void)
}

static void TestOneMillisecondDirectionDelay(void)
{
static const uint16_t delays[] = {1U, 2U, 10U};
uint8_t segmentCount;
uint8_t delayIndex;

for (segmentCount = 1U; segmentCount <= 2U; segmentCount++)
{
for (delayIndex = 0U;
delayIndex < (uint8_t)(sizeof(delays) / sizeof(delays[0]));
delayIndex++)
{
uint16_t tick;
uint16_t delayMs = delays[delayIndex];

ResetCore();
PlsrTestEnableFinitePulseTrain(1U);
ConfigureCommon(0U, PLSR_POSITION_RELATIVE, segmentCount,
PLSR_SEND_COMPLETE, 1000UL, 1000UL,
100UL, 0U, 0U);
EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, delayMs));
EXPECT_U(PLSR_MB_OK,
SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE,
0U, 0U, 0U));
if (segmentCount > 1U)
{
EXPECT_U(PLSR_MB_OK,
SetSegment(2U, 1000UL, 10L,
PLSR_EXT_OR_COMPLETE, 0U, 0U, 0U));
}

EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_START));
PlsrPoll1ms();
EXPECT_U(1U, PlsrTestInitialDirectionPreparePending());
EXPECT_U(0UL, PlsrTestDirectionWriteCount(0U));
EXPECT_U(0U, PlsrTestPulseIsActive());

PlsrPoll1ms();
EXPECT_U(0U, PlsrTestInitialDirectionPreparePending());
EXPECT_U(1UL, PlsrTestDirectionWriteCount(0U));
EXPECT_U(0U, PlsrTestPulseIsActive());

for (tick = 1U; tick < delayMs; tick++)
{
PlsrPoll1ms();
EXPECT_U(0U, PlsrTestPulseIsActive());
}
PlsrPoll1ms();
EXPECT_U(1U, PlsrTestPulseIsActive());
EXPECT_U(1000UL, PlsrTestOutputFrequency());
StopAndSettle();
}
}
}

static void TestStopCancelsInitialDirectionPrepare(void)
{
ResetCore();
PlsrTestEnableFinitePulseTrain(1U);
ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE,
1000UL, 1000UL, 100UL, 0U, 0U);
EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, 1U));
EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, 5U));
EXPECT_U(PLSR_MB_OK,
SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE,
0U, 0U, 0U));

EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START));
EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_START));
PlsrPoll1ms();
EXPECT_U(1U, PlsrTestInitialDirectionPreparePending());
EXPECT_U(0UL, PlsrTestDirectionWriteCount(0U));
EXPECT_U(0U, PlsrTestPulseIsActive());

EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_STOP));
PlsrPoll1ms();
EXPECT_U(1U, PlsrTestPulseIsActive());
EXPECT_U(1000UL, PlsrTestOutputFrequency());
StopAndSettle();
EXPECT_U(0U, PlsrTestInitialDirectionPreparePending());
EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus());
EXPECT_U(0UL, PlsrTestDirectionWriteCount(0U));
EXPECT_U(0U, PlsrTestPulseIsActive());

PlsrPoll1ms();
EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus());
EXPECT_U(0U, PlsrTestPulseIsActive());
}

static void ExpectDirectionPinLevels(uint8_t selectedOutput,
@@ -2507,6 +2580,7 @@ static void TestDirectionSameAndReverseSegments(void)
uint8_t output;

ResetCore();
PlsrTestEnableFinitePulseTrain(1U);
ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 3U,
PLSR_SEND_COMPLETE, 1000UL, 1000UL, 1000UL,
0U, 0U);
@@ -4442,7 +4516,8 @@ static const TEST_CASE TestCases[] =
{"self_jump_stop", TestSelfJumpCanStop},
{"stop_deceleration", TestStopDeceleratesBeforeCut},
{"stop_pending_boundary", TestStopAtPendingBoundary},
{"direction_delay_one_ms", TestOneMillisecondDirectionDelay},
{"direction_delay_one_ms", TestOneMillisecondDirectionDelay},
{"initial_direction_prepare_stop", TestStopCancelsInitialDirectionPrepare},
{"direction_output_polarity_matrix", TestDirectionOutputPolarityMatrix},
{"direction_same_reverse_segments", TestDirectionSameAndReverseSegments},
{"direction_output_change", TestDirectionOutputCanChangeBetweenCommands},
@@ -4514,7 +4589,7 @@ static const TEST_CASE TestCases[] =
{"position_overflow", TestPositionOverflowStopsWithError}
};

int main(void)
int main(int argc, char *argv[])
{
unsigned int index;

@@ -4524,6 +4599,11 @@ int main(void)
{
unsigned int failuresBefore = FailureCount;

if ((argc > 1) && (strcmp(TestCases[index].name, argv[1]) != 0))
{
continue;
}

CurrentTest = TestCases[index].name;
(void)printf("RUN %s\n", CurrentTest);
(void)fflush(stdout);


Ladataan…
Peruuta
Tallenna