范围:仅 PULSE/DIR 模式。AB 正交模式维持现状,作为第二阶段另行设计。
V1.1 变更:采纳审查意见,补齐四根承重梁——①多段边界语义解析层;②唯一硬件执行器
(所有权状态机);③STOP/EXT/动态改频统一的 generation 原子重规划;④硬件量化进入
规划闭环。修正脉冲守恒公式表述,修正"预算不足=错误"的文档矛盾。
配套依据:PLSR_Bug报告_2026-08-13_短轮廓与段间延迟.md、PLSR方案设计书_V1.0.md。
当前固件中"加减速"存在两套并行实现(时间域 ramp 与脉冲域短轮廓),四条输出路径,
接力点由启发式估算决定且不守恒。逻辑分析仪实测(Bug 报告)已证实四类缺陷:
| Bug | 现象 | 结构性根因 |
|---|---|---|
| 1 | 减速预算不足时跳停止速度,尾部慢爬 | 长 ramp 路径没有"按剩余脉冲重算斜坡” |
| 2 | 加速 ramp 整体消失,首脉冲即目标频率 | ramp 按挂钟计时,与首脉冲等待时间脱节 |
| 3 | 整段以启动/停止速度输出 | 同上极端版 + 0 脉冲消耗判定缺陷 |
| 4 | 有限路径段间固定 ~2.5ms 低电平间隙 | 段完成事件跨两个 1ms 周期消费 |
平台层 PSC/ARR 写入散在 5 个点,任务域与中断域两条通道并存,互斥靠临界区 +
写保护 guard + generation 计数拼凑:
| 写入点 | 域 | 触发场景 |
|---|---|---|
PlsrPlatformUpdateFinitePrepared |
任务域 | 有限轮廓运行中调频/ramp 更新 |
PlsrPlatformUpdateFiniteStep |
任务域 | 有限序列运行中改写旧 step 表 |
PlsrPlatformQueueFrequency |
任务域 | 时间域 ramp 逐 tick 调频 |
PlsrPlatformLoadPreparedFromIrq |
中断域 | profile 队列/handoff 预装载 |
PlsrFinitePrepareNextStepIrq |
中断域 | step 表切换预装载 |
PlsrCutRequested 的消费点只在 PlsrPulseTimerIrq,而有限路径由计数器中断驱动、
不走该函数——有限轮廓运行中触发 EXT 时 cut 语义丢失,要等段自然结束才被当作
普通边界处理。
AB_STREAM 占位)。原始段参数(PLSR_CONFIG + 当前段 + 实际位置)
│
▼
┌───────────────────────────────┐
│ 多段边界语义解析器(新) │ SEND_COMPLETE / SEND_SUBSEQUENT /
│ 段序列 → MotionBlock 序列 │ WAIT 类型 / 跳转 / 同向连续 / 换向
└───────────────────────────────┘
│ MotionBlock(entryHz, cruiseHz, exitHz, pulseBudget, boundaryAction)
▼
┌───────────────────────────────┐
│ 脉冲域规划器(新,纯函数,流式) │ 相位法:∫f dt = k → t_k → f[k]
│ 量化闭环:以实际定时器周期累计 │ 输出 PLSR_STREAM_ITEM(含 repeat 压缩)
└───────────────────────────────┘
│ 轨迹流
▼
┌───────────────────────────────┐
│ 唯一硬件执行器(PLSR_EXEC_MODE) │ STEP_TABLE / STREAM 两种供给方式,
│ 只有它写 PSC/ARR │ 一个执行状态机
└───────────────────────────────┘
│
▼
状态机 / 命令 / Modbus(PlsrPoll1ms 保持 1ms 节拍,不再逐 tick 调频)
规划器只管"一个 MotionBlock 内的脉冲域轨迹”。段序列语义在上层解析为块序列,
每次边界推进时用实际位置与方向解析下一块(绝对位置、换向因此天然支持)。
typedef enum
{
PLSR_BOUNDARY_STOP, /* 运动结束 */
PLSR_BOUNDARY_NEXT_FRESH, /* 完成发送:减速到停止速度,下一块从启动速度起 */
PLSR_BOUNDARY_NEXT_CARRY, /* 后续发送同向连续:exitHz = 下一块 cruiseHz */
PLSR_BOUNDARY_WAIT_TIME, /* WAIT 时间 → 状态机等待,不属执行器 */
PLSR_BOUNDARY_WAIT_SIGNAL, /* WAIT 信号 → 同上 */
PLSR_BOUNDARY_WAIT_EXT, /* EXT 信号 → 同上 */
PLSR_BOUNDARY_JUMP, /* 跳转段 */
PLSR_BOUNDARY_EXT_CUT /* EXT 提前截断(立即切断语义) */
} PLSR_BOUNDARY_ACTION;
typedef struct
{
uint32_t entryHz; /* 块起点:启动速度 或 上一块 carry */
uint32_t cruiseHz; /* 段目标频率 */
uint32_t exitHz; /* 块终点:停止速度 或 下一块 cruiseHz */
uint32_t pulseBudget; /* 本块脉冲预算(uint32_t) */
PLSR_BOUNDARY_ACTION boundary;
} PLSR_MOTION_BLOCK;
解析规则(落实 SEND 模式与边界语义,避免"完成发送/后续发送被当成同一结果”):
exitHz = stopSpeedHz(decelerationTimeMs>0 时),boundary = NEXT_FRESH;下一块 entryHz = startSpeedHz。exitHz = 下一段 cruiseHz,boundary = NEXT_CARRY;下一块 entryHz = carry。PlsrPosition 的最新值解析下一块,不允许跨段预缓存方向。BOUNDARY_EXT_CUT,走 §6 原子重规划(立即切断)。N = ∫ f(t) dt。t_k 满足 ∫[0,t_k] f(t) dt = k(k = 1..N)。Δt_k = t_k − t_{k−1},目标频率 f[k] = 1/Δt_k。f_timer[k] = BuildTimerSetting(f[k]) 的 actualHz(PSC/ARR 量化BuildTimerSetting 必须无副作用、可在宿主机纯运行。现有实现对照:PlsrRampAreaQ32 即 ∫f dt 的 Q32 积分、PlsrExactRampBoundaryQ32
即相位边界求解,二者原样迁入;新增量化反馈环。
typedef struct
{
uint16_t psc; /* 量化后定时器设定 */
uint16_t pairPsc; /* PULSE/DIR 恒 0(AB 复用) */
uint32_t arr;
uint32_t compare;
uint32_t actualHz;
} PLSR_TIMER_STEP;
typedef struct
{
PLSR_TIMER_STEP step;
uint32_t repeatCount; /* 匀速区压缩:连续同频脉冲合并 */
} PLSR_STREAM_ITEM;
typedef struct
{
PLSR_MOTION_BLOCK block;
uint32_t appliedHz; /* 起点:实际生效频率(见 §6.3) */
uint64_t phasePulses; /* 起点相位:已实际输出脉冲数 */
/* 内部:曲线积分表索引、Q32 相位累计、量化误差累计 */
uint8_t curveMode;
/* ... */
} PLSR_PLANNER_CONTEXT;
typedef enum
{
PLSR_PLANNER_OK = 0, /* 正常生成(含完整梯形稳态段) */
PLSR_PLANNER_CLIPPED, /* 预算不足,结果为可执行的截断形态(三角/纯加减速) */
PLSR_PLANNER_DONE, /* 全部脉冲已生成 */
PLSR_PLANNER_INVALID /* 参数非法 */
} PLSR_PLANNER_STATUS;
PLSR_PLANNER_STATUS PlsrPlannerBegin(PLSR_PLANNER_CONTEXT *ctx,
const PLSR_MOTION_BLOCK *block,
uint32_t appliedHz,
uint64_t phasePulses);
uint16_t PlsrPlannerGenerate(PLSR_PLANNER_CONTEXT *ctx,
PLSR_STREAM_ITEM *out,
uint16_t capacity);
要点:
pulseBudget 为 uint32_t;phasePulses 为 uint64_t(长运动相位累计)。{step, repeatCount} 压缩;ramp 区逐脉冲(或按可合并步长)输出。tests/plsr_host)|Σ 1/f_timer[k] − ∫dt| 误差有界且不随 N 增长(量化闭环验收)。Σ repeatCount = N(含 CLIPPED 形态)。f_peak² ≈ 2·N·defaultSpeedHz·1000/(t1+t2) + 端点加权(补推导注释)。typedef enum
{
PLSR_EXEC_IDLE = 0, /* 无运动 */
PLSR_EXEC_STEP_TABLE, /* 供给方式一:整体预展开 step 表(有限序列) */
PLSR_EXEC_STREAM, /* 供给方式二:队列续块流式(长段/方向切换/CLIPPED) */
PLSR_EXEC_AB_STREAM, /* AB 模式占位(第二阶段迁入) */
PLSR_EXEC_REPLANNING, /* 原子重规划过渡态(STOP/EXT/改频统一入口) */
PLSR_EXEC_STOPPING /* 受控停止排空 */
} PLSR_EXEC_MODE;
所有权规则(写死):
PlsrExecLoadNext() 写 PSC/ARR(写保护 guard、UIF 提交、PlsrPoll1ms、Modbus 写寄存器、STOP/EXT/动态改频、规划器:均不得直接写PlsrExecLoadNext() 兼容出口(或加PLSR_RAMP_LEGACY 隔离段),保证"写 PSC/ARR 的通道唯一"这一约束自 Phase 1status = PlsrPlannerBegin(&ctx, &block, appliedHz, phase);
if (status == PLSR_PLANNER_OK || status == PLSR_PLANNER_CLIPPED) {
/* 可整体预展开(总步数≤上限、无运行时变数)→ STEP_TABLE
否则 → STREAM
两种方式都由同一执行器消费,禁止第四条路径 */
} else {
fault();
}
预装载时序(ARPE/OC1PE、UI 提交、写保护窗口)、计数器块式执行(
PLSR_COUNTER_BLOCK_PULSES、CC1 提前一脉冲准备、UI 块完成)、下降沿安全停止
(PlsrFiniteStopAtFallingEdge)、停止重定向(PlsrPlatformRetargetFiniteStop)
全部保留;只是这些函数变成执行器内部实现,外部不再直接调用。
STOP、EXT、动态改频统一走同一套过程,不再分别散落在
PlsrExecuteStop / PlsrRequestCut / PlsrFrequencyUpdatePending。
事件源:STOP 命令 | EXT 边沿(CUT 或受控) | 动态改频
↓
① 冻结:临界区内读三件事:
countedPulses = 硬件已计数脉冲(含未发布块)
appliedHz = 正在实际输出的频率(PlsrTimerActiveSetting)
committed = 已预装载、不可撤销的下一脉冲数(1~2,现
PlsrStopDrainPulseCount 概念的推广)
↓
② 失效:planGeneration++(旧 step 表 / profile 队列 / handoff 计划全部原子失效)
↓
③ 重规划:pulseBudget = targetPulses − countedPulses − committed
PlsrPlannerBegin(appliedHz, phase = countedPulses)
(STOP 必须从 appliedHz 起,不能从 commanded/preloaded 起)
↓
④ 切换:在下一个安全脉冲边界(UI 提交点 / 写保护窗口)由 PlsrExecLoadNext 装载
新计划;committed 项按原计划排空后生效
↓
状态迁移:任意态 → REPLANNING → (STEP_TABLE | STREAM | STOPPING | IDLE)
| 语义 | 触发 | 行为 |
|---|---|---|
| 立即切断 | EXT_OR_COMPLETE 段中 EXT 边沿 |
重规划为空计划,最近安全下降沿停止(与信捷 PLSR"提前结束"对齐) |
| 受控停止 | (若需求扩展配置位) | 从 appliedHz 按剩余预算重规划减速,走 STOP 同一条协议 |
两者都必须经 REPLANNING,不允许只设一个逻辑标志等普通脉冲 IRQ 消费——这正是
当前 PlsrCutRequested 在有限路径无消费点的缺陷。
commandedHz /* 逻辑层已命令(规划器请求值) */
preloadedHz /* 已写 PSC/ARR 预装载、尚未经 UI 提交 */
appliedHz /* 正在实际输出(PlsrTimerActiveSetting) */
重规划的起点只能是 appliedHz,预算必须扣掉 committed(已预装载不可撤销项),
否则产生一脉冲错位或 STOP 初始频率跳变。
PlsrFrequencyUpdatePending 路径替换为:REPLANNING(新目标 Hz + 剩余预算)→
生成新轨迹 → 安全边界切换。不再局部修改旧 step 表
(PlsrPlatformUpdateFiniteStep 退役),保证已预计算的后续减速段同步更新。
有限序列运行中的段频率改写亦走同一协议。
在动架构之前,先把三组失败测试落在宿主机框架里(当前代码预期失败,记录失败模式):
| # | 用例 | 预期行为 | 当前代码失败模式 |
|---|---|---|---|
| T1 | 有限轮廓运行中触发 EXT | 最近安全边界切断,位置=已输出脉冲 | CutRequested 无消费点,段跑完,cut 丢失 |
| T2 | 有限加速过程中触发 STOP | 从 appliedHz 受控减速,总脉冲精确,无频率跳变 | 时间域 ramp 与脉冲域 retarget 混用,预算不含 committed |
| T3 | 有限加减速过程中动态修改目标频率 | 从 appliedHz 重规划到新目标,后续减速同步更新 | 局部改旧 step 表 + 时间域 ramp,旧减速段残留 |
补充用例(Phase 2 后追加):T4 stream 模式运行中 EXT/STOP;T5 段边界 NEXT_CARRY
与 NEXT_FRESH 的 exitHz 正确性(宿主机断言规划器输入块)。
三组失败测试在 Phase 1/2 的通过情况即为验收闸门。
| Phase | 内容 | 验收闸门 |
|---|---|---|
| 0 | 基线锁定(bug 报告 4 配置波形存档)+ 三组失败测试落地(预期红) | 基线入库;失败测试红且失败模式与 §7 表一致 |
| 1 | 执行状态机 PLSR_EXEC_MODE + 单一所有权:PSC/ARR 写入收敛到 PlsrExecLoadNext,AB 加 LEGACY 隔离 |
编译干净;旧功能全量回归;T1 失败模式变为"协议未实现”(不再是静默丢 cut) |
| 2 | 原子重规划:STOP/EXT/动态改频统一入口(REPLANNING),appliedHz 起点 + committed 扣减 | T1/T2/T3 转绿;板级 EXT/STOP 波形验收 |
| 3 | 流式规划器 + 边界语义解析器落地;时间域 PlsrRamp/PlsrMaybePlanBoundaryRamp/PlsrRampPulseEstimate 退役;量化闭环生效 |
宿主机全绿;Bug 1/2/3/4 配置回归通过 |
| 4 | 曲线校准:正弦/S 曲线与信捷对标数据(PLSR信捷对标追踪矩阵.md)逐点校准 |
对标矩阵通过;波形时频曲线容差达标 |
| 5 | AB 第二阶段:AB 迁入 STREAM(规划器支持 pairPsc) | AB 验收用例全量回归 |
每 Phase 的板级对比容差:总脉冲数精确、时频曲线形状一致、无速度跳变;逐 tick
频率值不作为对比基准(脉冲域规划无 1ms 台阶,理论上优于时间域)。
test_plsr_host.c 全绿 + 新增规划器/执行器/重规划单测全绿。| 配置 | 预期 |
|---|---|
| bug1:100000/100/100/100/100/1000 | 三角平滑到 100Hz,总时长 ~64ms,无 100Hz 尾巴 |
| bug2:加速 10ms/启动 100Hz/1000 脉冲 | 首脉冲后完整加速可见,总时长 ~20ms |
| bug3:启动 10Hz/1000 脉冲 | 首脉冲等待 100ms 后按剩余脉冲重新规划 |
| TC-PD-011:10 段恒速 1000Hz | 段间间隙 <1ms |
wave_viewer.py / bin_to_time_freq.py 时频曲线 vs 理论曲线。COUNT_PASS / FREQUENCY_PASS / CURVE_PASS 全置位。PlsrRampAreaQ32),量化actualHz 反推真实周期,宿主机单测必须覆盖"长期累计误差有界”。PlsrTestEmitPulseOnCriticalEntryPLSR_DEBUG_TIMING 对比改前改后PlsrProfileProducerMaxItemCycles 应显著下降)。PlsrExecLoadNext 是唯一写通道;AB 旧路径经 LEGACY