| @@ -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}\)。 | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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; | |||
| } | |||
| @@ -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 | |||
| } | |||
| } | |||
| @@ -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); | |||