瀏覽代碼

P13 Modbus 控制窗口打通:COMMIT/START 分离、命令应答与一致性状态读取 + PAUSE/RESUME 修复

本批完成内容:

Modbus 控制窗口(新增 plsr_modbus_control/data + modbus_data_store):
- COMMIT 与 START 分离:COMMIT 校验通过后生效;COMMIT 后
  再修改 S0/S1,START 返回 BUSY,要求重新提交。
- 命令支持:停止、暂停、继续、位置设置、清零、保存;
  重复命令序号幂等,不重复执行。
- 四轴状态均可读取;32/64 位状态用首尾版本号保证一致性
  (防撕裂读取)。
- 控制窗口基址可配置:P13 暂用 D1200~D1455(非信捷固定
  地址),S0=D1600、S1=D1700。

PAUSE/RESUME 闭环修复:
- PAUSE 不再终止当前 S0 路径,保留当前段与已发脉冲;
  RESUME 从 S2 起始速度重新激活速度曲线。
- 硬件定时器恢复时不清零脉冲计数,任务累计连续。
- 暂停回差补偿段时恢复专用速度参数。
- STOP_DECEL 正常收到减速完成事件并进入 STOPPED。
- Python 脚本增加"频率非零 + 计数增长"恢复判定,不再把
  ACCEL+0Hz 误判为恢复成功。

测试:
- Host:3500 项检查全部通过(含控制窗口/命令/一致性/幂等)。
- IAR:0 errors,0 warnings。
- 上板验证(逻辑分析仪,Q0):
  START 后 1000Hz 平滑加速至 2000Hz → 稳定段 → PAUSE 约
  200ms 平滑减速停止,暂停期间 0 边沿恒低 → 重复 PAUSE
  无第二次动作 → RESUME 从 ~1000Hz 重新加速 → STOP_DECEL
  平滑减速至停。全程 0 个 <10µs 毛刺、无截断尾脉冲、
  无意外重启。
master
ywh 1 月之前
父節點
當前提交
fdbbe71aea
共有 33 個文件被更改,包括 4731 次插入193 次删除
  1. +35
    -3
      Core/Src/main.c
  2. +309
    -0
      Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.md
  3. 二進制
      Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.zip
  4. +18
    -0
      EWARM/Modbus.ewp
  5. +23
    -0
      HostComputer/PLSR_MODBUS_CONTROL_TEST.md
  6. +43
    -0
      HostComputer/PLSR_MODBUS_TEST.md
  7. 二進制
      HostComputer/__pycache__/plsr_modbus_frequency_test.cpython-39.pyc
  8. +270
    -0
      HostComputer/plsr_modbus_control_test.py
  9. +173
    -0
      HostComputer/plsr_modbus_frequency_test.py
  10. +51
    -0
      Modbus/Inc/modbus_data_store.h
  11. +349
    -0
      Modbus/Src/modbus_data_store.c
  12. +37
    -38
      Modbus/Src/modbus_rtu_slave.c
  13. +10
    -0
      PLSR/Inc/plsr_core.h
  14. +9
    -0
      PLSR/Inc/plsr_hal_f407.h
  15. +13
    -1
      PLSR/Inc/plsr_job.h
  16. +34
    -0
      PLSR/Inc/plsr_modbus_control.h
  17. +18
    -0
      PLSR/Inc/plsr_modbus_data.h
  18. +8
    -0
      PLSR/Inc/plsr_profile.h
  19. +24
    -0
      PLSR/Inc/plsr_self_test.h
  20. +6
    -0
      PLSR/Inc/plsr_types.h
  21. +556
    -118
      PLSR/Src/plsr_core.c
  22. +339
    -21
      PLSR/Src/plsr_hal_f407.c
  23. +52
    -11
      PLSR/Src/plsr_job.c
  24. +570
    -0
      PLSR/Src/plsr_modbus_control.c
  25. +72
    -0
      PLSR/Src/plsr_modbus_data.c
  26. +4
    -0
      PLSR/Src/plsr_path.c
  27. +43
    -0
      PLSR/Src/plsr_profile.c
  28. +623
    -0
      PLSR/Src/plsr_self_test.c
  29. +12
    -0
      PLSR/Test/run_host_tests.ps1
  30. +917
    -1
      PLSR/Test/test_plsr_hal.c
  31. +12
    -0
      PLSR/Test/test_plsr_job.c
  32. +84
    -0
      PLSR/Test/test_plsr_modbus_data.c
  33. +17
    -0
      PLSR/Test/test_plsr_profile.c

+ 35
- 3
Core/Src/main.c 查看文件

@@ -26,6 +26,7 @@
#include "modbus_rtu_slave.h"
#include "plc_device.h"
#include "plsr_core.h"
#include "plsr_modbus_control.h"
#include "plsr_self_test.h"
#include "stdio.h"
/* USER CODE END Includes */
@@ -37,6 +38,13 @@

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PLSR_BOARD_TEST_CW_CCW (9U)
#define PLSR_BOARD_TEST_FAST_REFRESH (10U)
#define PLSR_BOARD_TEST_DYNAMIC_FREQ (11U)
#define PLSR_BOARD_TEST_MODBUS_DATA (12U)
#define PLSR_BOARD_TEST_MODBUS_CONTROL (13U)
#define PLSR_BOARD_TEST_SELECT PLSR_BOARD_TEST_MODBUS_CONTROL
#define PLSR_MODBUS_CONTROL_TEST_BASE (1200U)

/* USER CODE END PD */

@@ -80,6 +88,20 @@ static void AppTaskStart(void *pArg)
* 初始化函数会立即启动USART1的DMA空闲接收
*/
(void)ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS);
#if PLSR_BOARD_TEST_SELECT == PLSR_BOARD_TEST_MODBUS_DATA
/* P12 must be queued after the Modbus register store is ready. */
if (PlsrModbusDataSelfTestQueue() != PLSR_RESULT_QUEUED)
{
Error_Handler();
}
#elif PLSR_BOARD_TEST_SELECT == PLSR_BOARD_TEST_MODBUS_CONTROL
if ((PlsrModbusControlSelfTestPrepare() != PLSR_RESULT_OK)
|| (PlsrModbusControlInit(PLSR_MODBUS_CONTROL_TEST_BASE)
!= PLSR_RESULT_OK))
{
Error_Handler();
}
#endif
//ModbusRetainedRegistersLoad();
while (1)
{
@@ -108,6 +130,7 @@ static void AppTaskStart(void *pArg)
* 每1ms轮询一次,
*/
ModbusSlavePoll();
PlsrModbusControlPoll();
// ModbusRetainedRegistersPoll();

if (ModbusSlaveIsConnected(MODBUS_CONNECTION_TIMEOUT_MS) != 0U)
@@ -200,10 +223,19 @@ int main(void)
Error_Handler();
}

/* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */
/* 上电自测统一延时1s,当前P9配置见下方调用(验证后关闭)。 */
HAL_Delay(1000U);
/* P6 board test: Q0..Q3 run four independent PULSE/DIR jobs. */
(void)PlsrFourAxisSelfTestQueue();
#if PLSR_BOARD_TEST_SELECT == PLSR_BOARD_TEST_CW_CCW
/* P9: Q0=CW, Q1=CCW; only one channel may pulse. */
(void)PlsrCwCcwSelfTestQueue();
#elif PLSR_BOARD_TEST_SELECT == PLSR_BOARD_TEST_FAST_REFRESH
/* P10: Q0=1ms refresh, Q1=0.1ms refresh. */
(void)PlsrFastRefreshSelfTestQueue();
#elif PLSR_BOARD_TEST_SELECT == PLSR_BOARD_TEST_DYNAMIC_FREQ
/* P11: automatic live-frequency schedule without Watch edits. */
(void)PlsrDynamicFrequencySelfTestQueue();
#endif
//(void)PlsrDirectionLogicSelfTestQueue();
OSStart();
/* USER CODE END 2 */



+ 309
- 0
Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.md 查看文件

@@ -0,0 +1,309 @@
# AI 问询会话记录(2026-08-03 ~ 2026-08-09)

---

## 周一(2026-08-03):需求理解与地址映射设计

**问:PLSR 指令的 S0/S1/S2/D 操作数分别是什么含义?我需要一份完整的语义说明来做解析层的接口设计。**
答:S0 是段表起始地址,每段占 10 个字,字段依次为:目标频率(双字)、脉冲数或目标位置(双字,符号位表示方向)、加速时间、减速时间等;S1 是模式与起始段号,bit0 决定相对/绝对定位;S2 是参数组号,K0 对应 HSD460-539 固定区,K1~K4 对应 SFD950+ 的参数组(默认速度、加减速时间、最高速度、起始/终止速度、FOLLOW、前馈百分比、刷新代码等);D 是输出轴号(Y0~Y3)。解析层要做的事:读取 S0/S1/S2 指向的软元件区 → 校验(段数上限、频率范围 1~100kHz、参数组合法性)→ 生成不可变任务快照 → 异步提交给核心执行。快照必须是"不可变"的:提交后不允许任何代码修改它,否则上位机在任务执行中改写 D 区数据会破坏执行一致性。

**问:输出端口和定时器怎么对应?我查了手册,PF 口复用功能比较多,容易配错。**
答:四路脉冲输出:Q0=PF6/TIM10_CH1(AF3)、Q1=PF8/TIM13_CH1(AF9)、Q2=PF7/TIM11_CH1(AF3)、Q3=PF9/TIM14_CH1(AF9)。时钟树:APB1 分频 4、APB2 分频 2,所以 TIM10/11 走 168MHz、TIM13/14 走 84MHz——这个时钟值必须写进轴映射表,不能四路都写 168MHz,否则 Q1/Q3 的实际频率会减半。GPIO 初始化用 AF_PP + 对应 AF 号,通道使能前输出级是断开的,不会有毛刺。

**问:软元件存储怎么安排比较紧凑?HSD/SFD/SD/SM 都要有地址映射。**
答:按四轴布局:HSD 384 字节(运行态 16 字/轴 + 配置区),SFD RAM 缓存 2080 字节覆盖 900~1419(每轴公共参数 130 字 + 4 套参数组各 20 字),SD 192 字节,SM 4 字节;轴间用宏常量 PLSR_SFD_AXIS_STRIDE=130 做偏移,杜绝魔法数。SFD 需要掉电保存——单独做持久化层:Flash 分页存储、写前擦除、加载失败自动回退出厂默认并置 PLSR_PERSISTENCE_DEFAULTED 标志,损坏数据自愈(保留最旧有效副本)。

**问:S2 参数组有 K0~K4 五个,K0 和 K1~K4 的存储位置不一样?**
答:对。K0 对应 HSD460-539(配置区,也是快照里默认参数组的来源);K1~K4 对应 SFD 参数区(每套 20 字偏移)。解析时按 S2 操作数是常数还是寄存器地址来选:常数 0 取 HSD 区,常数 1~4 取 SFD 对应套,寄存器地址则运行时读取目标软元件。校验规则:默认速度 1~100000Hz、加减速时间非零、最高速度不低于默认速度、FOLLOW 1~100、前馈 0~100,非法则返回带定位信息的错误(错误块号 + 偏移)。

---

## 周二(2026-08-04):状态机与命令体系设计

**问:单轴运动状态机应该分几个状态?我之前设计的四态不够用,加减速和暂停分不开。**
答:建议十态:IDLE / WAIT / ACCEL / RUN / DECEL / COMPLETED / STOPPED / PAUSED / ERROR / UNINITIALIZED。WAIT 是段间等待(ACT 计时/EXT 信号),ACCEL/RUN/DECEL 是运动三阶段,STOPPED 与 COMPLETED 分开(停止原因可查),PAUSED 从运动态进入、RESUME 恢复。所有转换走统一的 PlsrStateTransition 入口,用显式迁移表约束:比如 ACCEL 只允许去 RUN/DECEL/STOPPED/ERROR,PAUSED 只能去 RUN/DECEL/STOPPED;非法迁移直接返回错误,避免状态散落在各处 if 里改。

**问:STOP、PAUSE、RESUME 这些命令怎么设计优先级?多个命令同时来怎么办?**
答:命令队列固定深度,按优先级排序取最高者执行:STOP_IMMEDIATE(0)> STOP_DECEL(1)> PAUSE(2)> RESUME(3)> 其他(4)。同序号命令去重——记录 lastCommandSequence,重复序号直接返回上次结果,防止上位机重发造成二次执行。每个命令响应都带命令序号 + 轴状态 + 错误码,上位机可以确认"我发的命令到底执行了没有"。STOP_IMMEDIATE 直接关 PWM 停硬件并产生 STOP_IMMEDIATE_DONE 事件;STOP_DECEL 走受控减速(进入 DECEL 阶段平滑停)。

**问:段间跳转(ACT/EXT/跳转链)在状态机里怎么表达?路径执行器每 tick 要做什么?**
答:路径执行器(plsr_path)每 1ms tick 推进一次,内部维护当前段号、跳转链、WAIT/ACT 计时、EXT 信号轮询与完成标志,对外只返回 PATH_ACTION(NEXT_SEGMENT / WAIT / TERMINATE / ERROR),core 拿到 action 后统一应用到状态机——状态机本身不关心路径细节。两个边界必须处理:一是零脉冲跳转链,本轮预算耗尽时不能误启动一个 0 脉冲硬件段;二是极短段可能在 ACCEL 或 DECEL 阶段就完成,ACCEL/RUN/DECEL 三种状态都要能正确处理段完成事件,否则轴会卡在加速态整段。

**问:事件体系怎么设计?段完成、任务完成这些怎么通知状态机?**
答:每轴一个事件队列(位掩码),PlsrPostEvent 可从中断里调用;事件分两级:CRITICAL(软急停、限位、故障)每轮 PlsrProcess 最先处理,普通事件(ACCEL_COMPLETE、DECEL_COMPLETE、WAIT_BEGIN/COMPLETE、SEGMENT_COMPLETE、JOB_COMPLETE、STOP_IMMEDIATE_DONE)按序消费。中断里只 PostEvent,不做状态迁移——状态迁移全部在任务上下文完成,避免中断与任务的竞态。

---

## 周三(2026-08-05):速度规划与测试框架

**问:加减速曲线为什么用定点实现,不用浮点?F407 我记得是有 FPU 的。**
答:F407 是 Cortex-M4F,有单精度 FPU——用定点不是硬件限制,是工程选择:定点运算在编译目标间行为完全确定(浮点舍入依赖编译器/库版本,同样的公式换个编译选项结果可能不同),且避免浮点库的代码与栈开销。实现:频率用 Q32.32 定点(uint64,整数部分 32 位),斜率步进 = slopeHzPerMs × 1000 / refreshHz 转 Q32.32;正弦曲线用 256 项 Q16 查表,注意 π/2 面积补偿(正弦段峰值增量 = 直线段增量 × π/2,否则面积不匹配会少发脉冲);短距离自动退化为三角曲线(峰值频率用 f1²=f0²+2a 求根)。

**问:1ms 刷新下低速起步会有什么问题?我用模拟器跑了一下,10Hz 起步好像有异常。**
答:问题在于"ARR/PSC 预装载只在周期更新事件生效":10Hz 起步首周期 100ms,这 100ms 内 1ms 速度规划已经推到 ~1000Hz,但中间所有调频都积压在预装载里,第一个周期结束后一次性加载最新参数——中间的 20~900Hz 根本不会真实出现在波形上。解法:按脉冲内频率积分计算首周期等效频率 f_eff=(f0+f1)/2,f1²=f0²+2a,把首周期从 100ms 压到 ~13ms;之后每个周期按同样的等效原则输出,加速曲线就平滑了(实测首周期 10.2ms,后续 168→232→279→314Hz…平滑递增)。

**问:host 测试怎么组织?真机不方便每轮都烧录。**
答:PLSR/Test 下 6 个套件(解析/状态机/路径/规划/HAL/设备)约 1400 项断言,编译参数 -std=c11 -Wall -Wextra -Werror -DPLSR_HOST_TEST(警告即错误),run_host_tests.ps1 一键全跑。HAL 层用寄存器模拟(PLSR_HW_TIMER_REGS:cr1/dier/sr/psc/arr/ccr1/cnt/ccmr1/ccer),把 F407 的实测语义也建模进去——比如"CNT 写到活动 CCR1 比较值会置 CC1IF"这条就是上板实测后补进 host 模型的,否则 host 测试会漏掉这类硬件行为。

**问:正弦曲线的 π/2 补偿具体怎么算?**
答:正弦段目标是在同样的加速时间内"面积"(脉冲数)与直线段一致。直线段频率线性上升,正弦段频率按 sin 变化——如果直接 f=f0+Δf·sin(π/2·t/T),总面积比直线段小(正弦积分=1/π·2 vs 直线=1/2 的关系),所以要乘 π/2 系数把峰值增量放大到直线增量的 π/2 倍,面积才匹配。代码里 PlsrProfileSlopePerStepQ32 之后的正弦分支直接处理这个缩放。

---

## 周四(2026-08-06):硬件层与上板准备

**问:PWM 输出没方波,只有恒定电平,为什么?我配置了定时器但看不到波形。**
答:CCMR1 复位值是冻结模式(OC1M=000)——输出比较不驱动通道,输出恒定。必须显式写 PWM 模式 1(OC1M=110)+ OC1PE(CCR 预装载),并且 CR1.ARPE=1 开 ARR 预装载。启动顺序很重要:先写 PSC/ARR/CCR 预装载 → EGR.UG 生成更新事件把预装载一次性加载进影子寄存器 → 再清 UIF → 最后 CC1E+CEN。如果不开 ARPE,运行中调频写入会在回绕瞬间生效,ARR 变小瞬间 CNT 超调提前回绕——每段加速会多出约 ln(f1/f0) 个假脉冲。

**问:方向延时(SFD907)应该怎么生效?手册里"脉冲方向延时时间"这一行 PLSR 是"可不设"。**
答:SFD907 出厂默认 10ms,解析进 S2 快照的 directionDelayMs。生效条件:只在方向发生变化时(首次启动 / 换向 / 方向点变化)进入 DIR_SETTLING 等待 10ms;段间同向衔接跳过等待直接进 PWM_PENDING——这样段间静默从 14.9ms 优化到约 2ms。手册 1-4-6 节表格里 PLSR 的"脉冲方向延时时间"确实标"可不设"(PLSF 才"必须设定"),与"同向不等待"的设计一致。

**问:上电自测怎么安排?我想第一次上板就能看到波形。**
答:上电 HAL_Delay(1000) 等电源/外设稳定,然后入队自测任务:Q0 发三段脉冲(2000Hz/1000 个、5000Hz/6000 个、1000Hz/500 个,H00 顺序衔接),同时验证完成事件、段间跳转、加减速三段曲线;方向点配 Y4(SFD906=4)。自测用静态数组实现 D 设备的读写回调(validateWords/readWord/readBit),任务源先写死在 main.c,正式 Modbus D 设备适配器留到联调阶段替换。

**问:NVIC 优先级怎么分?中断服务和 OS 的优先级关系?**
答:定时器更新/比较中断优先级设 1(抢占)——比 uC/OS-II 的 SysTick(15)高,保证脉冲计数不丢;OS 里 PlsrTask 用 OSTimeDly(1) 做 1ms 节拍。共享中断(如 TIM8_UP_TIM13_IRQn 同时服务 update 和 CC1)在 ISR 里必须分别查 UIF/CC1IF 的来源再处理,不能假设每次进入都是同一个事件。

---

## 周五(2026-08-07):上板验证与波形分析

**问:Y0~Y20都正常吗**
答:Y 输出点本身是正常的——但要先确认你测的是什么。XDM 的 Y 输出是晶体管输出,测量方式和普通推挽输出不一样,我先确认一下你的测量方法。

**问:现在的现象是,PE6置高Y5的指示灯不亮万用表测Y5物理脚0v,PE置低Y5的指示灯亮万用表测Y5物理脚0v**
答:这个现象正好说明 Y5 是集电极开路(NPN 漏型)输出:PE6 置高时内部晶体管导通,Y5 物理脚被拉到 ≈0V,指示灯(如果接在 Y5 和 +24V 之间)应该亮——你说灯不亮,要查指示灯接法;PE6 置低时晶体管截止,Y5 悬空,万用表测 0V 是因为悬空没有驱动。关键点:**这种输出没有主动高电平**,只有导通(≈0V)和截止(悬空)两态。

**问:告诉我你的依据,看的哪个文档里有写**
答:信捷《XD/XL 系列 PLC 定位控制手册》1-4-4 节(PDF 页 166):XDM 脉冲输出为集电极开路(OC)方式,晶体管导通时输出约 0V、截止时悬空——手册原文明确写了"集电极开路"字样。这是硬件特性,不是故障。

**问:你的意思是他的输出口是开漏输出**
答:对,准确说是集电极开路(OC),和开漏(OD)同类——NPN 漏型:只能拉低,不能主动拉高。所以万用表永远测不到"高电平",要测高电平必须外部上拉(比如 1~10kΩ 到 +24V 或 +5V)。

**问:那我逻辑分析仪接哪个口呢**
答:逻辑分析仪不能直接测 Y 端子(OC 输出无上拉时测到的电平不确定)。两个选择:① 飞线到 MCU 引脚直接测(比如 Q0 就是 PF6,波形最干净);② 在 Y 端子上加外部上拉电阻再测。建议先飞线 PF6。

**问:我接不了mcu脚,只能接Y端子,我该怎么做**
答:那就在 Y 端子和 +24V 之间接一个上拉电阻(1k~10kΩ),这样导通时测到 ≈0V、截止时被上拉拉高到 24V,逻辑分析仪就能看到完整方波。注意 24V 上拉的话逻辑分析仪要用高压探头或分压,安全起见也可以用 5V 上拉。

**问:我现在飞线飞到PF6上了**
答:好,PF6 就是 Q0 的 MCU 引脚(TIM10_CH1),信号最干净。采一段波形发我,我帮你分析有没有脉冲、频率对不对。

**问:只有一段672ms的高电平,其余全是低电平**
答:672ms 恒定高电平 + 全低——这说明 PWM 没起来,只有一个电平状态。两种可能:① 定时器没启动(CEN 没置位);② 输出了但 CCMR1 是冻结模式——你用的 HAL 初始化可能没写 PWM 模式。先检查 CCMR1 的值,冻结(OC1M=000)时输出就是恒定的。

**问:有脉冲了,加速段180ms,减速段180ms,这也在你意料之内吗**
答:在。这是梯形速度曲线:加速 180ms + 匀速 + 减速 180ms,正好对应你配置的加减速时间。脉冲已经出来了,说明 PWM 模式和预装载都对,接下来验证脉冲数和段间衔接。

**问:上电后延时一秒再PlsrSelfTestQueue();**
答:好,改成 HAL_Delay(1000) 后入队自测——等电源和外设稳定再发脉冲,避免上电瞬间时序问题干扰判断。

**问:这个加速段第一个脉冲不正常啊**
答:看到了——加速段第一个脉冲之前有个异常的高电平(约 50ms)加一个 0.5ms 的低电平,然后才是正常加速。这个现象我先记下来(后面定义为"②段首压缩"问题),需要抓寄存器快照定位。

**问:再测几个脉冲**
答:好,多发几个文件我对比——最好把触发点放在加速段开始,方便看第一个周期的完整结构。

**问:每段和段之间的延时是多少**
答:从波形量:段间静默约 14.9ms。拆解:方向延时 10ms(首次启动)+ DONE 检测的 tick 边界(≤1ms)+ PWM 启动的 tick 边界(≤1ms)+ 段切换处理。这个 14.9ms 主要被方向延时占掉了。

**问:显然差5ms太严重了,能优化吗**
答:能。方向延时只在方向真正变化时才需要——段间同向衔接时跳过 DIR_SETTLING,直接进 PWM_PENDING。改动后段间应该能压到 2ms 以内(只剩 DONE 检测 + 启动两个 tick 边界)。改完重新烧录验证。

**问:扫描一下这个文件...bin 帮我拟合出时频曲线,你写个py程序画图**
答:写了个 plot_waveform.py:解析逻辑分析仪 bin(16 通道每采样 2 字节小端,信号在 bit0),自动探测采样率(100MS/s,用 2000/5000Hz 匀速段打分验证),提取上升沿算瞬时频率,输出两段图(原始波形 + 时频曲线)。分析结果:三段结构 2000/5000/1000Hz,总脉冲数 7517。

**问:这样的时频图有问题吧**
答:有问题——加速段在时频图上不是一条平滑斜线,而是折线:段2 起步先是 50ms 恒高 + 0.5ms 低,然后直接跳到 1000Hz 附近。这就是"②段首压缩":第一个周期 100ms 内规划已经推到 ~1000Hz,但 ARR 预装载只在周期末生效,中间频率全被吞掉了。

**问:你可以直接看2026-08-07_16-43-23.bin分析图像啊,图片只是给我看的**
答:明白,我直接分析 bin 数据本身,不依赖截图。用脚本逐边沿提取:第一段下降沿到第二段上升沿间隔 14.9ms、总脉冲 7517、三段频率正确——和截图结论一致。

**问:第一段的下降沿到第二段的上升沿的时间是14.9ms...**
答:对,这就是段间静默:方向延时 10ms + 段切换处理约 4.9ms。优化方向不变:同向跳过方向延时,目标压到 2ms 内。

**问:实际的脉冲个数是7517个**
答:7517 vs 配置 7500,多了 17 个。根因:ARR 没开预装载时,调频写入在回绕瞬间生效,ARR 变小瞬间 CNT 超调提前回绕——每段加速多出约 ln(f1/f0) 个假脉冲。修复:ARPE=1 + 启动时 UG 加载影子,改完应回到精确 7500。

**问:2026-08-07_17-19-03.bin这是我用新代码的波形,用那个py生个图我看看**
答:生成好了。结果:总脉冲 7500 精确(修复生效),三段 1000/6000/500 结构正确,段间静默也降下来了。

**问:现在的波形有什么问题**
答:①段1→段2 衔接还有 14.9ms 静默(方向延时,待优化);②段2 起步加速段仍是折线(首周期压缩未解决);③段尾收尾有低频平台。我把这三个列成清单,逐个修。

**问:把现在的bug修了**
答:这次修了两个:①段间同向跳过方向延时(14.9ms→2ms);②段尾收尾用硬件计数校准 profile 虚拟计数(SyncPulses),消除冻结频率补发平台。编译烧录后你测一下。

**问:2026-08-08_19-14-34.bin**
答:验证①:总脉冲 7500 精确;段间静默 2ms 达标;段尾收尾平滑(520ms 减速到停,无冻结平台)。①修复确认。

**问:你有没有发现第二段的一开始加速度很大...频谱上表现出来的加速段不是一条斜线是一条折线**
答:确认——②段首压缩还在:段2 起步"高 50ms + 低 0.5ms"后直接 1000Hz,时频图是折线不是斜线。这个问题的本质是"低速起步 + 周期末加载"的架构问题,我需要抓 tick 级快照定位。

---

## 周六(2026-08-08):段首压缩排查与交接

**问:生成一个git提交的描述**
答:标题"P3a 硬件层上板验证:修复 PWM 输出两个硬件缺陷,优化段间衔接,扩展三段自测";正文列:①CCMR1 冻结→PWM1+OC1PE;②ARPE+UG 影子加载(7517→7500);③段间同向跳过方向延时(14.9ms→2ms);④段尾硬件计数校准(SyncPulses);⑤三段自测扩展;上板实测数据。

**问:2026-08-08_19-23-37.bin**
答:对比 19-14:段2 起步还是"高 50ms+低 0.5ms"结构——②未修复,确认与 19-14 同源。段1 起步正常(首周期 10.2ms 平滑),只有段2 起步异常——段1 有"上电后 1s 延时"缓冲,段2 是运动中直接切换,路径不同。

**问:你的意思是bug还没修复**
答:对,②段首压缩在段2/段3 起步仍然存在。现象、位置、结构都和之前完全一致——不是新问题,是同一个问题没修掉。

**问:我该怎么帮你**
答:最快的定位方式是抓寄存器快照:在 HAL 里加一个调试快照数组,每个 1ms tick 记录 reason/psc/arr/ccr/cnt 五字段,段2 起步时连续记录 64 条,然后从 IAR Watch 里读出来——快照能直接告诉我们周期结束时 ARR 到底加载了什么值,是不是"一次性加载了 1000Hz"。

**问:(IAR Watch 截图)**
答:截图里看到快照数组已经在跑了。reason=3 的条目(1ms tick)里 arr 有 55999——这是 divider(1000Hz) 的 ARR 值,说明周期结束瞬间加载的就是 1000Hz 参数——和"第一个周期吞掉加速"的模型一致。

**问:Snipaste_2026-08-08_20-13-35.png**
答:这张图里快照字段能对上:reason 交替 0/3,psc/arr/ccr 随段变化,cnt 在 653/ms 附近正常递增。但有几条 reason 值异常(256 之类),我怀疑是识图读错字段了。

**问:我怀疑识图错了,你和我讲下 PlsrHwDbgSnap 数组(每条含 reason/psc/arr/ccr/cnt 五个字段)**
答:字段布局:reason(uint8:0=PwmBegin、3=PlsrHwTick)、psc、arr、ccr、cnt(都是 uint32,按序排列),共 64 条。你在 IAR Watch 里按这个布局读,注意 Watch 的十六进制显示要转十进制——之前 GLM 识图把字段错位了(把高 16 位当成另一个字段),数字全不可信。

**问:我不是叫你调用的是豆包吗**
答:你说得对——之前用的 GLM 识图(glm_vision.py),数字字段错位不可靠。我换成豆包视觉模型重新读这些截图。

**问:ark-fc26924a-b80c-49f2-867d-3d47ad79b09b-75f42这个apikey呢**
答:用这个 key 配豆包(doubao-seed-2-1-pro),端点用火山引擎 v3 的 OpenAI 兼容格式:https://ark.cn-beijing.volces.com/api/v3/chat/completions。compatible 端点会 404,必须用 v3。

**问:现在能看到了吗**
答:能了——豆包读出来的字段对齐了:reason 0/3 交替正常,arr=55999 那条确认是 1000Hz 的 divider,cnt 653/ms 正常。识图通道已经可靠。

**问:你现在能调用豆包识图了吗,我又重新配置了一下**
答:可以了,v3 端点 + 你的 key 正常返回。以后截图直接发我,我用豆包读。

**问:2026-08-08_21-02-32.bin;Snipaste_2026-08-08_21-02-45.png**
答:波形:段2 起步还是 50ms+0.5ms 结构(②未修);快照:周期末加载 arr=55999(1000Hz)实锤——"第一个周期结束后一次性加载最新 1000Hz 参数"的证据链完整。

---

## 周日(2026-08-09):AB 验证、保护功能与多轴测试

**问:2026-08-08_22-03-54.bin,看看吧**
答:**②段首压缩修复了**!段2 起步首周期 10.2ms(~98Hz)→ 168→232→279→314→344→374→400→424→450→470→490→510→530Hz 平滑递增,无折线;总脉冲 7500 精确;三段 1000/6000/500;段间静默 ~2ms。新代码用"首周期等效频率"(f1²=f0²+2a 积分)解决了低速起步问题——这正是 Codex 说的"重新设计控制周期"的落地。

**问:现在理论上段与段之间的延时时间是多少**
答:理论构成:DONE 检测(≤1ms)+ 段切换(同 tick)+ PWM 启动(≤1ms)≈ 0~2ms;方向改变时 +directionDelayMs(SFD907,默认 10ms)。实测 22-03 段间 ~2ms 与理论吻合。

**问:我是说现在plsr指令上的延时**
答:你问的是 PLSR 指令配置的延时——SFD907 方向延时,出厂默认 10ms,Modbus 可改。

**问:SFD907 方向延时 10ms,根据这个参数,段与段之间应该有10ms延时吧,为什么现在没有,是因为没应用吗**
答:不是没应用,是**有条件应用**:directionChanged(首次启动/换向/方向点变化)才等 10ms,段间同向衔接跳过(plsr_hal_f407.c:486-488 的注释就是这个设计)。手册 1-4-6 表格里 PLSR 的方向延时本来标"可不设"(PLSF 才必须设)。自测三段同向所以看不到 10ms——把段2 改反向就能看到。

**问:2026-08-08_22-42-03.bin;Snipaste_2026-08-08_22-42-40.png看一下波形是否符合图片的描述**
答:对照截图 7 项结论:①Q0 波形正确 ✅;②7500 脉冲、分段 1000/6000/500 ✅;③首脉冲+减速尾段正确 ✅;④段间同向不额外等待 10ms ✅;⑤Y4 方向正向——bin 里 CH1-15 全低,Y4 通道没接到,无法从数据验证 ⚠️;⑥无毛刺无丢脉冲 ✅。结论:6/6 可验证项通过。

**问:Q1的脉冲个数不是只有7500个吗**
答:实测 Q1=7501(Q0 也是 7501)——多出的 1 个是段1 起步瞬间 A-R 与 B-R 同刻上升(0.37µs)的启动边沿,A/B 各多 1 个。去掉这个启动同升,Q1=7500 正好。全波形 A/B 同刻(<1µs)的边沿对只有这一对。

**问:2026-08-08_23-20-24.bin;稳定频率处预计相位差:段1 2000Hz 90°=125µs、段2 5000Hz 50µs、段3 1000Hz -250µs...**
答:实测:稳定段相位 +98.3°/+138.9°/-135.0°——**方向对但角度不对**(应 ±90°)。根因:起步 CNT 差 0.25T(90°)正确,但调频只写预装载不重置 CNT,两路独立定时器各自回绕时刻差 0.25T,1ms 调频期间两路加载的 ARR 不同步,相位随频率漂移。另外:段2 起步 A/B 上升沿重合(1µs)、B 相有 4µs 微毛刺、段间静默 23ms、Q1=7501。

**问:你这个分析的原因可信度有多少**
答:分三层:①实测数据(相位/毛刺/段间)100% 可信;②根因推理(调频↔相位漂移相关)50~70%——方向大概率对,但定量模型对不上(按 CNT 差保持算 seg2 应 198µs,实测 77µs),说明机制比我描述的复杂;③未知项(段间 23ms 原因、4µs 毛刺来源)<40% 纯推测。验证方法:恒定频率直启(无加速)看相位是否 90°——如果稳定 90° 则漂移 100% 锁定为调频引起。

**问:2026-08-08_23-51-27.bin;三个稳定段的 A→B 相位:应持续接近 +90°、+90°、-90°,不能再逐段漂移...**
答:**相位修复成功**:+90.2°/+90.6°/-89.9°(误差<1°),方向正确;但上升沿 8804/8804(每段多 300/900/100 个=各段调频次数)——"重写 CNT 保相位"方案引入边沿对;4µs 微毛刺仍在(5213 个 <10µs 间隔);段间 26.8ms。结论:核心指标(相位)达标,但重写 CNT 引入新问题。

**问:生成一个git提交描述**
答:标题"P3b-2 AB 正交硬件层上板验证:双定时器同步分频 + 无毛刺相位建立,三段 AB 自测通过";正文列:配对定时器 PSC 2 倍关系同步分频、CNT 初值 3/4T 与 1/2T 建立 90°、落后相 CC1 下降沿计完整 00 周期、运行中调频只写预装载、GPIO 保持低→update 分时释放引脚;上板实测:相位 +90.2/+90.6/-89.9、段间 3.57ms、调频毛刺 0、Q0/Q1 各 7501(启动边沿待消除)。

**问:信捷手册里说的运行调频是什么意思**
答:手册原文没有"调频"这个词——对应的官方概念是"脉冲频率刷新时间"(HSD474/SFD964:1ms 或 0.1ms,参数表里标"可不设"):运行中每隔一个刷新周期按加减速斜率重新计算并更新输出频率,加减速就是靠这个周期刷新实现的。对应我们代码:refreshHz=1000(1ms)→ PlsrProfileStep 每 tick 算新频率 → PlsrHwSetFrequency(预装载写入)。

**问:IAR编辑器界面怎么打开显示行数**
答:Tools → Options → Editor → 勾选 Show line numbers。全局生效,所有文件左侧显示行号;也可以 Ctrl+G 跳行、看状态栏 Ln/Col。

**问:2026-08-09_13-41-48.bin;是否符合Q0上升沿:7500、Q1上升沿:7500...**
答:还是 7501/7501——启动同升(@0.110669,0.37µs)与 00:06 版同源未修。其余全过:相位 +90.2/+90.6/-89.9、分段 1000/6000/500、调频毛刺 0。

**问:Q1的脉冲个数不是只有7500个吗**
答:确认:Q1=7501(Q0=7501),多出的就是启动同升那一对(全波形唯一 <1µs 的同刻边沿对)。去掉它 Q1=7500 正好。Q1 不是单独问题——是 A/B 一起在启动时多跳了一次。

**问:2026-08-09_13-57-30.bin;重点确认:Q0上升沿 = 7500...第一个周期应为:00→A-R:10→B-R:11→A-F:01→B-F:00...**
答:仍 7501/7501;首周期 A-R 与 B-R 同刻(0.33µs,从 11 开始而非 00→10→11)。第 6 版同一现象。按你的标准(7500 才关闭)——不能关闭。定位建议:两个 ISR 打点验证 update 先后 / SetPwmMode1 移到 SetCc1e 之前。

**问:2026-08-09_13-59-20.txt**
答:与 13:57 同波形(txt 时间轴 +0.5147s 偏移),7501/7501、启动同升 0.34µs 仍在。四版(00:06/13:41/13:57/13:59)完全同一现象。

**问:让你分析txt还是bin消耗的token少一点**
答:差不多(都是写脚本算摘要再读结果),txt 略省(脚本更简单、不用 memmap 和采样率探测)。真正省 token 的是把分析脚本固化,每次只换文件名。

**问:不用,你先扫一下代码吧,分析一下导致波形出问题的原因**
答:扫描结果:启动序列 737-740 行先 SetCc1e(1) 再 SetPwmMode1——RM0090 明确"OC 模式从冻结切 PWM 时 OCREF 电平会跳变",且落后相 CNT 初值=CCR(写入 CNT=CCR 会置 CC1IF,代码自己的 host 注释写的)。强嫌疑:①+②组合——B 相 CNT=CCR 写入触发比较事件,叠加 CC1E 先于模式切换的次序,使 OCREF 在启动序列中就变高。建议先试"SetPwmMode1 移到 SetCc1e 之前"。

**问:这个bug其实也不是什么大bug是吧**
答:影响确实小(1 个脉冲、仅启动瞬间、其余全达标),但 6 版没修好说明根因未明,可能掩盖更深时序问题。建议:降优先级不阻塞主流程,但别"关闭"——留待实验定位(ISR 打点 10 分钟能出结论)。

**问:后面还要写多少批代码**
答:按交接路线估 7~9 批:AB 收尾 → 四轴并发 → TIM6/NVIC → 运动语义闭环(STOP/PAUSE/ACT/EXT)→ 位置链路 → Modbus → 性能项 → 清理。

**问:2026-08-09_15-05-32.bin;两段各移动1001工程单位,第一段输出1501脉冲,第二段输出1502脉冲,总计3003脉冲,稳定频率约1500Hz...**
答:**全部符合**:Q0=3003(rise0..1500 段1=1501、rise1501..3002 段2=1502);两段 median 1500Hz;时长 2.19s;**余数累计验证通过**(1001×1.5=1501.5 → 段1 出 1501 余 0.5;段2 再 +0.5 → 1502 余 0);起步 120→200→282Hz 平滑、收尾 82Hz、毛刺 0。这是 plsr_position 单位换算模块的真机验证。

**问:生成一次git提交描述**
答:标题"工程单位/脉冲当量换算落地(余数累计真机验证)+ 位置链路闭环 + AB 启动预热";正文:plsr_position 模块(单位校验/等效配置/双向换算/溢出检查)、job 解析(等效配置 + 工程单位段目标 + POSITION_OVERFLOW)、core 位置链路(logicalPosition/taskPulses/totalPulses 累加 + HSD 检查点 + 软急停/限位事件)、HAL AB 启动预热;上板实测 3003/1501/1502。

**问:现在的代码还剩哪些部分没写**
答:扫描结果:Modbus(D 设备适配器)、CW/CCW、0.1ms 刷新、硬件计数 TIM9/12、动态调频(无调用点)、SD 发布、限位接线、STOP/PAUSE 真机验证、自测/调试清理、TIM6——约 6 批。

**问:我现在测试的全是发送低频,你觉得代码有没有可能在输出100k频率的信号会出问题,分析完代码再回答,别瞎猜**
答:分析完:基础能力支持 100kHz(上限 100000Hz、PSC=0/ARR=1679 可表示、中断级计数、Q32.32 不溢出);三个真实风险:①AB 模式高频加速段每 1ms 重定相吞 ~1µs(占 10% 周期,PULSE/DIR 无此问题);②四轴 100kHz 逐脉冲中断 40 万次/秒(CPU 12~40%,Codex 早列为风险);③100kHz 附近 ARR 每计数 60Hz 调频台阶。建议各测一次确认。

**问:2026-08-09_16-04-17.bin;测试任务为:Q0脉冲、Q4方向、+10000目标、正软限位+500、1000Hz、减速100ms、方向延时10ms...**
答:实测 502 个脉冲(验收 499~501,超 1);恒定段 460 个@1000Hz → 减速段 42 个(84.5ms,535→134Hz 平滑)→ 停止后零边沿无毛刺;Q4 方向 CH1 全低测不到(接线问题)。触发点比理论(450)晚 9 个脉冲、超停 1~2 个——已列入已知问题清单。

**问:这个不是什么严重bug吧**
答:不是严重 bug(超停 1~2 个脉冲、提前减速逻辑生效、停止干净),但比 AB 启动边沿"重一点点":软限位是安全功能,且偏差是系统性的(判定位置源/提前量公式),不会自己消失。建议不阻塞主流程但列入待办。

**问:把之前和现在这两个小问题统计下来**
答:已生成《已知问题清单_2026-08-09.md》:问题 1 AB 启动边沿(7501,6 版复现,根因方向+3 条验证方法);问题 2 软限位边界精度(502,波形结构+根因方向+验证方法);共同结论:小影响、非安全级、卡验收、实验定位后再修。

**问:生成一次git提交描述**
答:标题"P5 软限位保护 + 受控停止落地:限位提前减速真机验证 + 已知问题清单";正文:PlsrProfileRequestStop 受控停止入口、软限位判定接入运动循环、PlsrProtectionSelfTestQueue 自测、main.c 切换、已知问题清单文档;上板实测 502(超停 1~2 已记录)。

**问:2026-08-09_16-30-26.bin;预期:Q0 1000Hz/1000沿、Q1 2000Hz/2000沿、Q2 3000Hz/3000沿、Q3 4000Hz/4000沿,四路约1秒...**
答:**四轴并发全过**:Q0~Q3 上升沿 1000/2000/3000/4000 全部精确、占空比 50.0%、时长 ~1s、四路独立无干扰;同时验证 84MHz 时钟修正有效(Q1=TIM13 精确 2000Hz、Q3=TIM14 精确 4000Hz 未减半)。这项可以直接关闭。

**问:生成一次git提交的描述**
答:标题"P6 四轴并发自测落地:Q0~Q3 四路独立 PULSE/DIR 真机验证 + 零加速段状态机修复";正文:PlsrFourAxisSelfTestQueue(四轴独立 S0/S1/SFD 配置)、main.c 切换、零加速段补发 ACCEL_COMPLETE 修复、host 测试 +90 项;上板实测四路精确。

**问:现在的代码还剩哪些部分没写**
答:重扫后:Modbus(D 设备适配器)、CW/CCW、0.1ms 刷新+动态调频、硬件计数、四轴验证、限位接线、STOP/PAUSE 验证、清理——约 5~6 批。

**问:我现在测试的全是发送低频,你觉得代码有没有可能在输出100k频率的信号会出问题,分析完代码再回答,别瞎猜**(重复确认)
答:(同上)结论不变:能出 100kHz,PULSE/DIR 大概率没问题;AB 高频加速段重定相干扰是真正需要上板验证的点。

**问:2026-08-09_16-55-57.bin;正反向齿隙补偿...预期:段1正向200、负向补偿20、段2负向200、正向补偿10、段3正向100、总计530...**
答:**齿隙补偿全过**:530 脉冲精确(200/20/200/10/100);负向补偿 20 脉冲呈三角曲线(229→987→220Hz)、正向补偿 10 脉冲(229→974→524Hz);换向点只有 2 个(间隔 13.2ms = 末脉冲周期 + 10ms 延时 + 段处理);补偿与正式段同向无缝(无第二个 10ms);毛刺 0。Q4 方向信号 CH1 测不到(接线问题)。

**问:Q4的信号在CH4**
答:收到——重新提取 CH4:方向变化 3 次(0.0374 低→高初始建立、0.2477 高→低换向1、0.4987 低→高换向2);段1 起步方向建立→首脉冲 9.8ms≈10ms 延时 ✅;两次换向各 11.7ms(含 10ms)✅;补偿→正式段 1.9ms 无第二个 10ms ✅;方向电平与验收表格完全一致。**齿隙补偿验收全过**(之前 CH1=0 只是接线,不是代码问题)。

**问:软限位、硬限位和软件急停,补齐兼容错误码5/6。正反向齿隙补偿...I6000~I6399段事件及完整兼容错误码发布。独立定时器实现0.1ms控制刷新...FOLLOW和前馈补偿的实际运算。CW/CCW双脉冲输出及资源冲突检查。Modbus调用、原子提交、命令序号和状态读取的真实联调。收尾修复与压力测试...现在还剩哪些没完成了**
答:逐项扫描:错误码 5/6(core:1115/1121 启动保护、1589/1606 运行中限位)✅;齿隙补偿 ✅(真机验证);I 事件+完整错误码(plc_device:662-728,1/2/3/4/5/6/15/16/26)✅;0.1ms+动态调频 ❌(core:1833 固定 1000U、ResolveLiveFrequency 无调用);FOLLOW/前馈 ❌(仅解析);CW/CCW ✅(18:13 真机验证);Modbus ❌(从站框架在、D 设备适配器未接);收尾 ❌。**还剩 4 项**。

**问:错误码 5/6是啥**
答:5=正限位触发后停止(减速停止并禁止继续正向启动)、6=负限位触发后停止(减速停止并禁止继续反向启动)——需求规格 2.13.1 错误码表;同表还有 1(S0 段配置)、2(每转参数 0)、3(S2 块号)、4(参数块超限)、15/16(FOLLOW 参数)、26(资源分配失败)。

**问:你重新扫一下代码,确定没写吗**
答:**我上次判断错了,5/6 已经写了**——core:1115/1121(启动保护路径:PlsrCheckStartProtection 返回 LIMIT 时设 5/6 + stopReason)、1589/1606(运行中限位事件:设 5/6 + PlsrRequestControlledStop);3/4 在 356/361。第一次扫描只看 300-345 行片段就下结论,漏了后面——向你更正。

**问:2026-08-09_17-20-14.bin;复位运行,程序会自动执行:第一段:正向200个脉冲1000Hz,换向等待10ms,第二段:反向200个脉冲1000Hz...Q4正逻辑、Q5负逻辑...CH3是Q5,CH4是Q4**
答:Q0/Q1 各 400 上升沿 ✅、换向静默 9.9995ms ✅;但方向电平与"NPN 低导通"预期相反(正向段 Q4 高、Q5 低)——在"高=ON"映射下代码完全正确(轴0 正逻辑正向 ON、轴1 负逻辑正向 OFF),两路一致地反说明是测量点极性/接线问题;Q5 启动有 1µs 毛刺、换向 55µs 过渡(非互补段仅此三处)。

**问:2026-08-09_17-51-24.bin;预期波形:第一段Q0输出2000Hz周期500µs高250µs上升沿300个,Q1始终低;换向Q0停Q1开始不重叠;第二段Q1输出1000Hz周期1ms高500µs上升沿200个,Q0始终低;结束全低...**
答:第一次分析误判"Q1 只出 4 个脉冲就停"——实际是我脚本的窗口裁剪 bug(按 Q0 范围裁掉了段2 的 Q1 边沿)。修正后:Q0=300/300、Q1=200/200 精确;段1 Q1 恒高问题已修复(0.000%);段切换 0.9ms 不重叠;启动无同升。**CW/CCW 验收通过**,仅剩段尾截断窄脉冲。

**问:位置在哪**
答:两个 <10µs 间隔都在段尾:①段1 最后脉冲 0.167797→0.167800(2.5µs,第 300 个脉冲上升沿后即拉低);②段2 最后脉冲 0.367690→0.367695(5.0µs)。性质:停止时机比 CC1 下降沿早半个周期,最后一个脉冲被截断——不是噪声毛刺,计数不受影响(300/200 精确),但严格说"微秒级间隔 0 个"不达标,需把停止对齐到完整周期末。

**问:重新扫描一下代码,看一下这些功能还还有哪些没实现 软限位、硬限位和软件急停,补齐兼容错误码5/6。正反向齿隙补偿...(同前清单)**
答:重扫确认:错误码 5/6 ✅、齿隙 ✅、I 事件+完整错误码 ✅、CW/CCW ✅(均真机验证);**还剩 4 项**:0.1ms 控制刷新+动态调频、FOLLOW/前馈运算、Modbus 真实联调、收尾压力测试(AB 首沿/100kHz/双 AB 并发/删自检)。


二進制
Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.zip 查看文件


+ 18
- 0
EWARM/Modbus.ewp 查看文件

@@ -1299,6 +1299,12 @@
<file>
<name>$PROJ_DIR$\..\PLSR\Inc\plsr_self_test.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Inc\plsr_modbus_data.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Inc\plsr_modbus_control.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name>
</file>
@@ -1326,6 +1332,12 @@
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_self_test.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_modbus_data.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_modbus_control.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name>
</file>
@@ -1335,8 +1347,14 @@
<file>
<name>$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\Modbus\Src\modbus_data_store.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\Modbus\Inc\modbus_data_store.h</name>
</file>
</group>
</project>

+ 23
- 0
HostComputer/PLSR_MODBUS_CONTROL_TEST.md 查看文件

@@ -0,0 +1,23 @@
# PLSR P13 Modbus 命令与状态测试

P13 使用可配置控制窗口,当前板测选择 D1200~D1455。该范围不属于信捷固定 PLSR 地址,仅是本工程上位机测试使用的动态通信窗口。

运行:

```powershell
python HostComputer\plsr_modbus_control_test.py --port COM5
```

默认串口为 9600、8E1、从站地址 1。测试脚本会自动:

1. 使用 0x10 写入 S0=D1600、S1=D1700。
2. COMMIT 并检查完整参数校验结果。
3. 修改 COMMIT 后的 S0,验证 START 被拒绝。
4. 恢复参数、重新 COMMIT,然后 START Q0。
5. 下发 PAUSE,等待减速至 PAUSED。
6. 重发相同 PAUSE 序号,验证不会重复执行。
7. 下发 RESUME,恢复脉冲输出。
8. 下发 STOP_DECEL,等待减速进入 STOPPED。
9. 检查状态首尾版本、命令序号、结果、逻辑位置和脉冲累计。

逻辑分析仪接 Q0 和 GND,建议在运行脚本前开始采集至少 8 秒。预期只有一段有效 Q0 输出:启动并加速到 2000Hz,PAUSE 减速停止,静默至少 250ms(另加重复序号和状态回读的 RTU 耗时),RESUME 后重新加速,最后 STOP_DECEL 平滑停止;不得出现窄脉冲或命令切换毛刺。

+ 43
- 0
HostComputer/PLSR_MODBUS_TEST.md 查看文件

@@ -0,0 +1,43 @@
# PLSR P12 Modbus 数据源测试

本测试验证 PLSR 从真实 Modbus 寄存器读取 S0/S1,并在运行中通过 Modbus 原子更新当前段频率。

## 固件自测数据

- S0:D1000,单段。
- S1:D1100,相对定位,从第 1 段开始。
- 当前段频率:D1010(低 16 位)、D1011(高 16 位)。
- 脉冲数:100000,Q0 输出,Q4 为方向。
- S2:K1,默认 1000Hz,最高 5000Hz,刷新周期 0.1ms。

这些地址只是 P12 自测选择的动态 S0/S1 地址,不是新增的固定 PLSR 地址。

## 运行

安装依赖:

```powershell
py -m pip install -r HostComputer\requirements.txt
```

连接当前工程使用的 Modbus 串口后运行:

```powershell
py HostComputer\plsr_modbus_frequency_test.py --port COM5
```

串口参数默认与固件一致:9600、8E1、从站地址 1。若电脑只有一个串口,可以省略 `--port`。

脚本使用功能码 0x10 一次写入 D1010/D1011,再用 0x03 回读。不要用两次 0x06 更新一个 32 位值,否则两次 Modbus 事务之间必然存在半新半旧的中间值。

## 预期结果

脚本依次写入:1000、4000、500、0、8000、-1、2000Hz。逻辑分析仪观察 Q0:

- 1000→4000、4000→500、1000→5000、5000→2000 平滑变频,无窄脉冲。
- 写入 0 后目标变为 S2 默认的 1000Hz。
- 写入 8000 后目标被钳位为 5000Hz。
- 写入 -1 后保持 5000Hz,随后可恢复到 2000Hz。
- 全程脉冲连续,不因 Modbus 请求结束或主站断开而停止。

IAR Watch 可辅助观察 `PlsrHwAxes[0].currentFrequencyHz`、`PlsrAxes[0].liveTargetFrequencyHz`、`PlsrAxes[0].liveFrequencyRejectCount` 和 `PlsrAxes[0].lastLiveFrequencyResult`。

二進制
HostComputer/__pycache__/plsr_modbus_frequency_test.cpython-39.pyc 查看文件


+ 270
- 0
HostComputer/plsr_modbus_control_test.py 查看文件

@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""PLSR P13 Modbus COMMIT/START/command/status integration test."""

from __future__ import annotations

import argparse
import time

import serial

from plsr_modbus_frequency_test import RtuClient, choose_port, signed_dword_words


CONTROL_BASE = 1200
S0_BASE = 1600
S1_BASE = 1700
CALL_REQUEST = CONTROL_BASE + 8
CALL_RESPONSE = CONTROL_BASE + 24
COMMAND_REQUEST = CONTROL_BASE + 40
COMMAND_RESPONSE = CONTROL_BASE + 48
AXIS0_STATUS = CONTROL_BASE + 64

RESULT_OK = 0
RESULT_QUEUED = 1
RESULT_BUSY = 8
STATE_ACCEL = 2
STATE_RUN = 3
STATE_DECEL = 4
STATE_PAUSED = 6
STATE_STOPPED = 8

CALL_COMMIT = 1
CALL_START = 2
CMD_STOP_DECEL = 1
CMD_PAUSE = 3
CMD_RESUME = 4


def put_u32(words: list[int], offset: int, value: int) -> None:
words[offset : offset + 2] = signed_dword_words(value)


def put_u64(words: list[int], offset: int, value: int) -> None:
raw = value & 0xFFFFFFFFFFFFFFFF
words[offset : offset + 4] = [(raw >> shift) & 0xFFFF for shift in (0, 16, 32, 48)]


def get_u32(words: list[int], offset: int) -> int:
return words[offset] | (words[offset + 1] << 16)


def get_u64(words: list[int], offset: int, signed: bool = False) -> int:
raw = sum(words[offset + index] << (index * 16) for index in range(4))
if signed and raw & (1 << 63):
return raw - (1 << 64)
return raw


def wait_call_response(client: RtuClient, sequence: int, timeout: float = 2.0) -> list[int]:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
response = client.read_holding(CALL_RESPONSE, 12)
if get_u32(response, 0) == sequence:
return response
raise RuntimeError(f"等待调用应答序号 {sequence} 超时")


def send_call(client: RtuClient, sequence: int, operation: int) -> list[int]:
request = [0] * 16
put_u32(request, 0, sequence)
request[2] = 0 # S0 device D
put_u32(request, 3, S0_BASE)
request[5] = 0 # S1 device D
put_u32(request, 6, S1_BASE)
request[8] = 0 # S2 constant
request[9] = 0
put_u32(request, 10, 1) # K1
request[12] = 0 # axis Y0/Q0
request[13] = 0 # PULSE/DIR
request[14] = operation
client.write_multiple(CALL_REQUEST, request)
return wait_call_response(client, sequence)


def wait_command_response(client: RtuClient, sequence: int, timeout: float = 2.0) -> list[int]:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
response = client.read_holding(COMMAND_RESPONSE, 8)
if get_u32(response, 0) == sequence:
return response
raise RuntimeError(f"等待命令应答序号 {sequence} 超时")


def send_command(
client: RtuClient, sequence: int, opcode: int, argument: int = 0
) -> list[int]:
request = [0] * 8
put_u32(request, 0, sequence)
request[2] = opcode
request[3] = 0
put_u64(request, 4, argument)
client.write_multiple(COMMAND_REQUEST, request)
return wait_command_response(client, sequence)


def read_axis_status(client: RtuClient) -> dict[str, int]:
words = client.read_holding(AXIS0_STATUS, 48)
generation_begin = get_u32(words, 0)
generation_end = get_u32(words, 46)
if generation_begin != generation_end or generation_begin & 1:
raise RuntimeError(
f"状态快照版本不一致:begin={generation_begin}, end={generation_end}"
)
return {
"generation": generation_begin,
"state": words[2],
"flags": get_u32(words, 3),
"error": words[6],
"stop_reason": words[7],
"last_result": words[8],
"last_sequence": get_u32(words, 10),
"logical_position": get_u64(words, 16, signed=True),
"task_pulses": get_u64(words, 20, signed=True),
"physical_pulses": get_u64(words, 28),
"current_frequency": get_u32(words, 38),
"target_frequency": get_u32(words, 40),
}


def wait_status(
client: RtuClient,
states: set[int],
sequence: int | None = None,
timeout: float = 5.0,
) -> dict[str, int]:
deadline = time.monotonic() + timeout
latest: dict[str, int] | None = None
while time.monotonic() < deadline:
latest = read_axis_status(client)
sequence_ok = sequence is None or latest["last_sequence"] == sequence
if latest["state"] in states and sequence_ok:
return latest
raise RuntimeError(f"等待状态 {sorted(states)} 超时,最后状态:{latest}")


def wait_running_output(
client: RtuClient, sequence: int, timeout: float = 5.0
) -> dict[str, int]:
"""等待真实脉冲恢复,不能只依据 ACCEL/RUN 状态标签。"""
deadline = time.monotonic() + timeout
latest: dict[str, int] | None = None
while time.monotonic() < deadline:
latest = read_axis_status(client)
pulse_active = (latest["flags"] & (1 << 1)) != 0
if (
latest["last_sequence"] == sequence
and latest["state"] in {STATE_ACCEL, STATE_RUN}
and pulse_active
and latest["current_frequency"] > 0
):
return latest
raise RuntimeError(f"等待实际脉冲恢复超时,最后状态:{latest}")


def check_result(response: list[int], expected: int, label: str) -> None:
result = response[3] if len(response) == 12 else response[4]
if result != expected:
raise RuntimeError(f"{label} 返回 {result},期望 {expected};应答={response}")


def main() -> int:
parser = argparse.ArgumentParser(description="PLSR P13 Modbus 控制接口自动测试")
parser.add_argument("--port", help="串口,例如 COM5;只有一个串口时可省略")
parser.add_argument("--baud", type=int, default=9600)
parser.add_argument("--slave", type=int, default=1)
args = parser.parse_args()

with serial.Serial(
port=choose_port(args.port),
baudrate=args.baud,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
timeout=1.0,
write_timeout=1.0,
) as uart:
client = RtuClient(uart, args.slave)
header = client.read_holding(CONTROL_BASE, 8)
if header[:5] != [0x504C, 0x5352, 0x0100, 256, 0x0007]:
raise RuntimeError(f"P13 控制窗口未就绪:{header}")
print("P13 控制窗口就绪:D1200~D1455,协议 V1.0")

s0 = [0] * 20
put_u32(s0, 0, 1)
put_u32(s0, 10, 2000)
put_u32(s0, 12, 50000)
s1 = [0] * 4
client.write_multiple(S0_BASE, s0)
client.write_multiple(S1_BASE, s1)
print("S0=D1600、S1=D1700 已用 0x10 原子写入")

response = send_call(client, 1, CALL_COMMIT)
check_result(response, RESULT_OK, "COMMIT#1")
if response[11] != 1:
raise RuntimeError("COMMIT#1 未建立有效提交")
print("COMMIT#1:完整校验通过")

client.write_multiple(S0_BASE + 12, signed_dword_words(50001))
response = send_call(client, 2, CALL_START)
check_result(response, RESULT_BUSY, "篡改后的 START#2")
print("START#2:正确拒绝 COMMIT 后被修改的 S0")

client.write_multiple(S0_BASE + 12, signed_dword_words(50000))
response = send_call(client, 3, CALL_COMMIT)
check_result(response, RESULT_OK, "COMMIT#3")
response = send_call(client, 4, CALL_START)
check_result(response, RESULT_QUEUED, "START#4")
status = wait_status(client, {STATE_ACCEL, STATE_RUN}, sequence=4)
if status["last_result"] != RESULT_OK:
raise RuntimeError(f"START#4 内核执行失败:{status}")
print(
f"START#4:Q0 已启动,当前 {status['current_frequency']}Hz,"
f"目标 {status['target_frequency']}Hz"
)

time.sleep(0.5)
response = send_command(client, 100, CMD_PAUSE)
check_result(response, RESULT_QUEUED, "PAUSE#100")
status = wait_status(client, {STATE_PAUSED}, sequence=100)
print(f"PAUSE#100:已暂停,任务累计 {status['task_pulses']} 脉冲")

# Resending the exact sequence must only replay the existing response.
response = send_command(client, 100, CMD_PAUSE)
check_result(response, RESULT_QUEUED, "重复 PAUSE#100")
status = read_axis_status(client)
if status["state"] != STATE_PAUSED or status["last_sequence"] != 100:
raise RuntimeError(f"重复序号导致状态变化:{status}")
print("重复 PAUSE#100:未重复执行")

time.sleep(0.25)
response = send_command(client, 101, CMD_RESUME)
check_result(response, RESULT_QUEUED, "RESUME#101")
status = wait_running_output(client, sequence=101)
print(f"RESUME#101:恢复输出,当前 {status['current_frequency']}Hz")

resumed_pulses = status["task_pulses"]
time.sleep(0.5)
status = read_axis_status(client)
if status["task_pulses"] <= resumed_pulses:
raise RuntimeError(f"RESUME#101 后脉冲计数未增长:{status}")
response = send_command(client, 102, CMD_STOP_DECEL)
check_result(response, RESULT_QUEUED, "STOP_DECEL#102")
status = wait_status(client, {STATE_STOPPED}, sequence=102)
print(
f"STOP_DECEL#102:减速停止完成,逻辑位置={status['logical_position']},"
f"任务脉冲={status['task_pulses']},物理脉冲={status['physical_pulses']}"
)
if status["error"] != 0:
raise RuntimeError(f"最终状态存在错误:{status}")

print("P13 全部自动测试 PASS,请核对 Q0 的启动/暂停/恢复/减速停止波形。")
return 0


if __name__ == "__main__":
try:
raise SystemExit(main())
except (RuntimeError, serial.SerialException) as error:
print(f"测试失败:{error}")
raise SystemExit(1)

+ 173
- 0
HostComputer/plsr_modbus_frequency_test.py 查看文件

@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""PLSR P12 Modbus RTU live-frequency test.

The firmware starts a long Q0 move from S0=D1000 and S1=D1100. This tool
updates the current-segment frequency at D1010/D1011 with function 0x10, so
the two 16-bit words are committed as one Modbus transaction.
"""

from __future__ import annotations

import argparse
import struct
import time
from dataclasses import dataclass

import serial
from serial.tools import list_ports


S0_BASE = 1000
S1_BASE = 1100
LIVE_FREQUENCY_ADDRESS = S0_BASE + 10
SLAVE_DEFAULT = 1


def crc16(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
return crc & 0xFFFF


def add_crc(payload: bytes) -> bytes:
crc = crc16(payload)
return payload + bytes((crc & 0xFF, crc >> 8))


def signed_dword_words(value: int) -> list[int]:
raw = value & 0xFFFFFFFF
return [raw & 0xFFFF, (raw >> 16) & 0xFFFF]


@dataclass
class RtuClient:
port: serial.Serial
slave: int

def exchange(self, request_pdu: bytes, response_size: int) -> bytes:
request = add_crc(bytes((self.slave,)) + request_pdu)
self.port.reset_input_buffer()
self.port.write(request)
self.port.flush()
response = self.port.read(response_size)
if len(response) != response_size:
raise RuntimeError(
f"响应超时:期望 {response_size} 字节,收到 {len(response)} 字节"
)
if crc16(response[:-2]) != int.from_bytes(response[-2:], "little"):
raise RuntimeError(f"响应 CRC 错误:{response.hex(' ')}")
if response[0] != self.slave:
raise RuntimeError(f"站号错误:收到 {response[0]},期望 {self.slave}")
if response[1] & 0x80:
raise RuntimeError(
f"Modbus 异常:功能码 0x{response[1]:02X},异常码 0x{response[2]:02X}"
)
return response

def read_holding(self, address: int, quantity: int) -> list[int]:
pdu = bytes((0x03,)) + struct.pack(">HH", address, quantity)
response = self.exchange(pdu, 5 + quantity * 2)
if response[1] != 0x03 or response[2] != quantity * 2:
raise RuntimeError(f"0x03 响应格式错误:{response.hex(' ')}")
return list(struct.unpack(f">{quantity}H", response[3:-2]))

def write_multiple(self, address: int, values: list[int]) -> None:
encoded = struct.pack(f">{len(values)}H", *values)
pdu = (
bytes((0x10,))
+ struct.pack(">HHB", address, len(values), len(encoded))
+ encoded
)
response = self.exchange(pdu, 8)
expected = bytes((self.slave, 0x10)) + struct.pack(">HH", address, len(values))
if response[:6] != expected:
raise RuntimeError(f"0x10 响应回显错误:{response.hex(' ')}")

def write_dword(self, address: int, value: int) -> None:
self.write_multiple(address, signed_dword_words(value))
words = self.read_holding(address, 2)
if words != signed_dword_words(value):
raise RuntimeError(
f"D{address} 回读不一致:写入 {signed_dword_words(value)},回读 {words}"
)


def choose_port(requested: str | None) -> str:
if requested:
return requested
ports = [item.device for item in list_ports.comports()]
if len(ports) == 1:
print(f"自动选择串口 {ports[0]}")
return ports[0]
available = ", ".join(ports) if ports else "未发现串口"
raise RuntimeError(f"请用 --port 指定串口。当前串口:{available}")


def wait_until(deadline: float) -> None:
remaining = deadline - time.monotonic()
if remaining > 0:
time.sleep(remaining)


def main() -> int:
parser = argparse.ArgumentParser(description="PLSR P12 Modbus 动态频率自动测试")
parser.add_argument("--port", help="串口,例如 COM5;只有一个串口时可省略")
parser.add_argument("--baud", type=int, default=9600)
parser.add_argument("--slave", type=int, default=SLAVE_DEFAULT)
args = parser.parse_args()

port_name = choose_port(args.port)
with serial.Serial(
port=port_name,
baudrate=args.baud,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
timeout=1.0,
write_timeout=1.0,
) as uart:
client = RtuClient(uart, args.slave)
header = client.read_holding(S0_BASE, 20)
s1 = client.read_holding(S1_BASE, 4)
if header[0] != 1 or header[12:14] != signed_dword_words(100000):
raise RuntimeError(
"P12 数据未就绪:请确认已烧录当前固件并复位开发板"
)
if any(s1):
raise RuntimeError(f"S1 数据异常:{s1}")

print("P12 已就绪:S0=D1000,S1=D1100,Q0 正在输出")
print("所有 32 位频率均使用 0x10 一次写入两个寄存器。")
schedule = [
(0.0, 1000, "初始目标"),
(1.0, 4000, "升至 4000Hz"),
(1.5, 500, "降至 500Hz"),
(2.0, 0, "0 使用 S2 默认 1000Hz"),
(2.2, 8000, "超过上限,固件应钳位到 5000Hz"),
(2.7, -1, "非法值,固件应保持上一次安全目标"),
(2.9, 2000, "恢复到 2000Hz"),
]
started = time.monotonic()
for offset, frequency, description in schedule:
wait_until(started + offset)
before = time.monotonic()
client.write_dword(LIVE_FREQUENCY_ADDRESS, frequency)
latency_ms = (time.monotonic() - before) * 1000.0
print(
f"T+{time.monotonic() - started:6.3f}s "
f"D1010={frequency:6d} {description} RTU往返={latency_ms:6.1f}ms"
)

print("脚本测试完成。请按测试说明核对 Q0 波形与 IAR 状态变量。")
return 0


if __name__ == "__main__":
try:
raise SystemExit(main())
except (RuntimeError, serial.SerialException) as error:
print(f"测试失败:{error}")
raise SystemExit(1)

+ 51
- 0
Modbus/Inc/modbus_data_store.h 查看文件

@@ -0,0 +1,51 @@
#ifndef MODBUS_DATA_STORE_H
#define MODBUS_DATA_STORE_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Logical PLC word-device spaces. These values intentionally match the
* PLSR D/HD/FD device codes, but this module does not depend on PLSR. */
typedef enum
{
MODBUS_DATA_DEVICE_D = 0,
MODBUS_DATA_DEVICE_HD,
MODBUS_DATA_DEVICE_FD
} MODBUS_DATA_DEVICE;

#define MODBUS_DATA_D_WORD_COUNT (10000UL)
#define MODBUS_DATA_HD_WORD_COUNT (10000UL)
#define MODBUS_DATA_FD_WORD_COUNT (10000UL)

uint8_t ModbusDataValidateWords(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
uint32_t wordCount);
uint8_t ModbusDataReadWord(MODBUS_DATA_DEVICE device,
uint32_t address,
uint16_t *value);
uint8_t ModbusDataReadDword(MODBUS_DATA_DEVICE device,
uint32_t lowAddress,
int32_t *value);
uint8_t ModbusDataWriteWord(MODBUS_DATA_DEVICE device,
uint32_t address,
uint16_t value);
uint8_t ModbusDataWriteWords(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
const uint16_t *values,
uint32_t wordCount);

/* Existing function 0x48 uses a sparse linear address space:
* 0..19999 are normal SRAM and 40000..69998 are CCMRAM. */
uint8_t ModbusDataReadLinear(uint32_t address, uint16_t *value);

/* Even means stable; odd means a multi-register write is in progress. */
uint32_t ModbusDataGetWriteSequence(void);

#ifdef __cplusplus
}
#endif

#endif /* MODBUS_DATA_STORE_H */

+ 349
- 0
Modbus/Src/modbus_data_store.c 查看文件

@@ -0,0 +1,349 @@
#include "modbus_data_store.h"
#include <stddef.h>

#if defined(PLSR_HOST_TEST)
#define MODBUS_DATA_BARRIER() __sync_synchronize()
#else
#include "stm32f4xx.h"
#define MODBUS_DATA_BARRIER() __DMB()
#endif

#define MODBUS_DATA_SRAM_WORD_COUNT (20000UL)
#define MODBUS_DATA_CCM_WORD_COUNT (29999UL)
#define MODBUS_DATA_HD_SRAM_OFFSET (10000UL)
#define MODBUS_DATA_LINEAR_CCM_BASE (40000UL)

static uint16_t ModbusDataSram[MODBUS_DATA_SRAM_WORD_COUNT];

#if !defined(PLSR_HOST_TEST)
#pragma location = ".ccmram"
#pragma data_alignment = 4
__root
#endif
static uint16_t ModbusDataCcm[MODBUS_DATA_CCM_WORD_COUNT];

static volatile uint32_t ModbusDataWriteSequence;
static volatile uint32_t ModbusDataWriteFirstAddress;
static volatile uint32_t ModbusDataWriteWordCount;
static volatile MODBUS_DATA_DEVICE ModbusDataWriteDevice;

static uint32_t ModbusDataEnterShortCritical(void)
{
#if defined(PLSR_HOST_TEST)
return 0UL;
#else
uint32_t interruptState;

interruptState = __get_PRIMASK();
__disable_irq();
return interruptState;
#endif
}

static void ModbusDataExitShortCritical(uint32_t interruptState)
{
#if defined(PLSR_HOST_TEST)
(void)interruptState;
#else
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

static uint8_t ModbusDataResolve(MODBUS_DATA_DEVICE device,
uint32_t address,
uint16_t **word)
{
if (word == NULL)
{
return 0U;
}
switch (device)
{
case MODBUS_DATA_DEVICE_D:
if (address >= MODBUS_DATA_D_WORD_COUNT)
{
return 0U;
}
*word = &ModbusDataSram[address];
return 1U;

case MODBUS_DATA_DEVICE_HD:
if (address >= MODBUS_DATA_HD_WORD_COUNT)
{
return 0U;
}
*word = &ModbusDataSram[MODBUS_DATA_HD_SRAM_OFFSET + address];
return 1U;

case MODBUS_DATA_DEVICE_FD:
if (address >= MODBUS_DATA_FD_WORD_COUNT)
{
return 0U;
}
*word = &ModbusDataCcm[address];
return 1U;

default:
return 0U;
}
}

static uint32_t ModbusDataCapacity(MODBUS_DATA_DEVICE device)
{
switch (device)
{
case MODBUS_DATA_DEVICE_D:
return MODBUS_DATA_D_WORD_COUNT;

case MODBUS_DATA_DEVICE_HD:
return MODBUS_DATA_HD_WORD_COUNT;

case MODBUS_DATA_DEVICE_FD:
return MODBUS_DATA_FD_WORD_COUNT;

default:
return 0UL;
}
}

static uint8_t ModbusDataWriteOverlaps(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
uint32_t wordCount)
{
uint32_t activeFirst;
uint32_t activeCount;

if (device != ModbusDataWriteDevice)
{
return 0U;
}
activeFirst = ModbusDataWriteFirstAddress;
activeCount = ModbusDataWriteWordCount;
if ((activeCount == 0UL) || (wordCount == 0UL))
{
return 0U;
}
return ((firstAddress < (activeFirst + activeCount))
&& (activeFirst < (firstAddress + wordCount)))
? 1U
: 0U;
}

static void ModbusDataWriteBegin(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
uint32_t wordCount)
{
ModbusDataWriteDevice = device;
ModbusDataWriteFirstAddress = firstAddress;
ModbusDataWriteWordCount = wordCount;
MODBUS_DATA_BARRIER();
ModbusDataWriteSequence++;
MODBUS_DATA_BARRIER();
}

static void ModbusDataWriteEnd(void)
{
MODBUS_DATA_BARRIER();
ModbusDataWriteSequence++;
}

uint8_t ModbusDataValidateWords(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
uint32_t wordCount)
{
uint32_t capacity;

capacity = ModbusDataCapacity(device);
if ((capacity == 0UL) || (wordCount == 0UL)
|| (firstAddress >= capacity))
{
return 0U;
}
return (wordCount <= (capacity - firstAddress)) ? 1U : 0U;
}

uint8_t ModbusDataReadWord(MODBUS_DATA_DEVICE device,
uint32_t address,
uint16_t *value)
{
uint16_t *word;
uint32_t before;
uint32_t after;
uint16_t snapshot;

if ((value == NULL) || (ModbusDataResolve(device, address, &word) == 0U))
{
return 0U;
}
before = ModbusDataWriteSequence;
if (((before & 1UL) != 0UL)
&& (ModbusDataWriteOverlaps(device, address, 1UL) != 0U))
{
return 0U;
}
MODBUS_DATA_BARRIER();
snapshot = *word;
MODBUS_DATA_BARRIER();
after = ModbusDataWriteSequence;
if ((before != after)
&& (ModbusDataWriteOverlaps(device, address, 1UL) != 0U))
{
return 0U;
}
if (((after & 1UL) != 0UL)
&& (ModbusDataWriteOverlaps(device, address, 1UL) != 0U))
{
return 0U;
}
*value = snapshot;
return 1U;
}

uint8_t ModbusDataReadDword(MODBUS_DATA_DEVICE device,
uint32_t lowAddress,
int32_t *value)
{
uint16_t *lowWord;
uint16_t *highWord;
uint16_t lowSnapshot;
uint16_t highSnapshot;
uint32_t before;
uint32_t after;

if ((value == NULL)
|| (ModbusDataValidateWords(device, lowAddress, 2UL) == 0U)
|| (ModbusDataResolve(device, lowAddress, &lowWord) == 0U)
|| (ModbusDataResolve(device, lowAddress + 1UL, &highWord) == 0U))
{
return 0U;
}
before = ModbusDataWriteSequence;
if (((before & 1UL) != 0UL)
&& (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U))
{
return 0U;
}
MODBUS_DATA_BARRIER();
lowSnapshot = *lowWord;
highSnapshot = *highWord;
MODBUS_DATA_BARRIER();
after = ModbusDataWriteSequence;
if ((before != after)
&& (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U))
{
return 0U;
}
if (((after & 1UL) != 0UL)
&& (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U))
{
return 0U;
}
*value = (int32_t)(((uint32_t)highSnapshot << 16U) | lowSnapshot);
return 1U;
}

uint8_t ModbusDataWriteWord(MODBUS_DATA_DEVICE device,
uint32_t address,
uint16_t value)
{
uint16_t *word;
uint32_t interruptState;

if (ModbusDataResolve(device, address, &word) == 0U)
{
return 0U;
}
interruptState = ModbusDataEnterShortCritical();
ModbusDataWriteBegin(device, address, 1UL);
*word = value;
ModbusDataWriteEnd();
ModbusDataExitShortCritical(interruptState);
return 1U;
}

uint8_t ModbusDataWriteWords(MODBUS_DATA_DEVICE device,
uint32_t firstAddress,
const uint16_t *values,
uint32_t wordCount)
{
uint16_t *firstWord;
uint32_t index;
uint32_t interruptState = 1UL;

if ((values == NULL)
|| (ModbusDataValidateWords(device, firstAddress, wordCount) == 0U)
|| (ModbusDataResolve(device, firstAddress, &firstWord) == 0U))
{
return 0U;
}
/* A live INT32 update is only two words. Keep that very short commit
* indivisible to the 100us ISR, so it sees either the old or new value.
* Larger block writes use the non-blocking sequence protocol instead of
* delaying pulse-related interrupts for an unbounded block copy. */
if (wordCount <= 2UL)
{
interruptState = ModbusDataEnterShortCritical();
}
ModbusDataWriteBegin(device, firstAddress, wordCount);
for (index = 0UL; index < wordCount; index++)
{
firstWord[index] = values[index];
}
ModbusDataWriteEnd();
if (wordCount <= 2UL)
{
ModbusDataExitShortCritical(interruptState);
}
return 1U;
}

uint8_t ModbusDataReadLinear(uint32_t address, uint16_t *value)
{
if (address < MODBUS_DATA_SRAM_WORD_COUNT)
{
return ModbusDataReadWord((address < MODBUS_DATA_D_WORD_COUNT)
? MODBUS_DATA_DEVICE_D
: MODBUS_DATA_DEVICE_HD,
(address < MODBUS_DATA_D_WORD_COUNT)
? address
: address - MODBUS_DATA_HD_SRAM_OFFSET,
value);
}
if ((address >= MODBUS_DATA_LINEAR_CCM_BASE)
&& ((address - MODBUS_DATA_LINEAR_CCM_BASE)
< MODBUS_DATA_CCM_WORD_COUNT))
{
uint32_t before;
uint32_t after;
uint16_t snapshot;

if (value == NULL)
{
return 0U;
}
before = ModbusDataWriteSequence;
if ((before & 1UL) != 0UL)
{
return 0U;
}
MODBUS_DATA_BARRIER();
snapshot = ModbusDataCcm[address - MODBUS_DATA_LINEAR_CCM_BASE];
MODBUS_DATA_BARRIER();
after = ModbusDataWriteSequence;
if ((before != after) || ((after & 1UL) != 0UL))
{
return 0U;
}
*value = snapshot;
return 1U;
}
return 0U;
}

uint32_t ModbusDataGetWriteSequence(void)
{
return ModbusDataWriteSequence;
}

+ 37
- 38
Modbus/Src/modbus_rtu_slave.c 查看文件

@@ -1,4 +1,5 @@
#include "modbus_rtu_slave.h"
#include "modbus_data_store.h"
#include <string.h>

#define MODBUS_RTU_ADU_SIZE_MAX (256U) // Modbus RTU 最大 ADU 长度,单位为字节
@@ -36,6 +37,7 @@ static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX];
static uint8_t ModbusRxAssemblyBuffer[MODBUS_RTU_ADU_SIZE_MAX];
static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX];
static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX];
static uint16_t ModbusWriteRegisterScratch[MODBUS_WRITE_REGS_MAX];
/* D100~D120 上一次已保存的值,用于检测数据是否变化 */
static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT];
static volatile uint16_t ModbusRxFrameLength;
@@ -52,16 +54,8 @@ static volatile uint8_t ModbusHasReceivedValidFrame;
static volatile MODBUS_BACKUP_DATA *ModbusBackupData =
(volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE;

/**
* 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储,
* 占用 1250 字节
*/
static uint16_t ModbusHoldingRegisters[20000];

#pragma location = ".ccmram"
#pragma data_alignment = 4
__root static uint16_t ModbusRegistersCcm[29999];

/* Word data is owned by modbus_data_store.c. This file keeps only the
* protocol-facing coil space and RTU buffers. */
static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U];

volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics;
@@ -442,7 +436,11 @@ tx:
{
address = (address - 20000U) * 2U + 1U;
}
value = ModbusHoldingRegisters[address];
if (ModbusDataReadWord(MODBUS_DATA_DEVICE_D, address, &value) == 0U)
{
ModbusSlaveStatistics.illegalAddressCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
}
ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U);
ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU);
}
@@ -484,7 +482,7 @@ static uint16_t ModbusProcessWriteSingleRegister(const uint8_t *request,
}

ModbusHoldingRegisters[address] = value;
(void)ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, address, value);

if (isBroadcast != 0U)
{
@@ -672,9 +670,13 @@ static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request,

for (index = 0U; index < quantity; index++)
{
ModbusHoldingRegisters[start + index] =
ModbusWriteRegisterScratch[index] =
ModbusGetU16Be(&request[7U + index * 2U]);
}
(void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
start,
ModbusWriteRegisterScratch,
quantity);

if (isBroadcast != 0U)
{
@@ -723,11 +725,12 @@ static uint16_t ModbusProcessReadBigHolding(const uint8_t *request,
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE);
}

/*
* 普通SRAM有40000个寄存器,CCMRAM有29999个寄存器,
* 总地址范围为0~69998
*/
if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start)))
/* Function 0x48 retains the existing sparse range: SRAM 0..19999 and
* CCMRAM 40000..69998. The unallocated 20000..39999 gap is rejected. */
if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start))
|| ((start < 40000UL)
&& ((start >= 20000UL)
|| ((start + quantity) > 20000UL))))

{
ModbusSlaveStatistics.illegalAddressCount++;
@@ -744,17 +747,11 @@ static uint16_t ModbusProcessReadBigHolding(const uint8_t *request,
{
currentAddress = start + (uint32_t)index;

/*
* 地址0~39999位于普通SRAM;
* 地址40000~69998位于CCMRAM
*/
if (currentAddress < 40000UL)
{
value = ModbusHoldingRegisters[currentAddress];
}
else
/* The data-store layer validates the sparse physical range. */
if (ModbusDataReadLinear(currentAddress, &value) == 0U)
{
value = ModbusRegistersCcm[currentAddress - 40000UL];
ModbusSlaveStatistics.illegalAddressCount++;
return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS);
}

/* 每个寄存器按照高字节、低字节装入响应帧 */
@@ -864,8 +861,6 @@ HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart,
ModbusTxBusy = 0U;
ModbusRtuTimingInit(huart->Init.BaudRate);

//ModbusHoldingRegisters[HMI_REG_DEVICE_ID] = 0xF407U;

return ModbusStartReceive();
}

@@ -1019,8 +1014,7 @@ uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value)
return 0U;
}

ModbusHoldingRegisters[address] = value;
return 1U;
return ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, address, value);
}

uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value)
@@ -1030,8 +1024,7 @@ uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value)
return 0U;
}

*value = ModbusHoldingRegisters[address];
return 1U;
return ModbusDataReadWord(MODBUS_DATA_DEVICE_D, address, value);
}

uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state)
@@ -1075,15 +1068,19 @@ void ModbusRetainedRegistersLoad(void)
{
for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++)
{
ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] =
ModbusBackupData->retainedD[index];
(void)ModbusDataWriteWord(
MODBUS_DATA_DEVICE_D,
MODBUS_RETAINED_D_START + index,
ModbusBackupData->retainedD[index]);
}
}
else
{
for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++)
{
ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] = 0U;
(void)ModbusDataWriteWord(MODBUS_DATA_DEVICE_D,
MODBUS_RETAINED_D_START + index,
0U);

ModbusBackupData->retainedD[index] = 0U;
}
@@ -1102,7 +1099,9 @@ void ModbusRetainedRegistersPoll(void)

for (index = 0; index < MODBUS_RETAINED_D_COUNT; index++)
{
value = ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index];
(void)ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
MODBUS_RETAINED_D_START + index,
&value);
if (value != ModbusRetainedSnapshot[index])
{
ModbusBackupData->retainedD[index] = value;


+ 10
- 0
PLSR/Inc/plsr_core.h 查看文件

@@ -12,8 +12,14 @@ extern "C" {
PLSR_RESULT PlsrInit(void);
void PlsrTask(void *argument);
void PlsrProcess(void);
void PlsrControlTick100us(void);
void PlsrSetControlTickHook(void (*hook)(void));

PLSR_RESULT PlsrPostCall(const PLSR_CALL *call);
/* Side-effect-free COMMIT validation. It parses the complete S0/S1/S2/D
* model but does not reserve resources or start an axis. */
PLSR_RESULT PlsrValidateCall(const PLSR_CALL *call,
PLSR_PARSE_DETAIL *detail);
PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command);
PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask);
PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status);
@@ -24,6 +30,10 @@ PLSR_RESULT PlsrGetLastParseDetail(uint8_t axis,
/* 仅测试使用的低层启动入口:不解析 S0/S1/S2/D,直接申请资源进入 ACCEL。
* 生产调用必须使用 PlsrPostCall()。 */
PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request);
uint32_t PlsrTestGetProfileRefreshHz(uint8_t axis);
uint8_t PlsrTestGetProfileActive(uint8_t axis);
uint8_t PlsrTestGetJobRefreshCode(uint8_t axis);
uint32_t PlsrTestGetProfileFrequencyHz(uint8_t axis);
#endif

PLSR_RESULT PlsrStateTransition(uint8_t axis,


+ 9
- 0
PLSR/Inc/plsr_hal_f407.h 查看文件

@@ -27,6 +27,7 @@ typedef struct
PLSR_OUTPUT_MODE outputMode; /* PULSE/DIR、AB 或 CW/CCW */
uint8_t directionPoint; /* DIR 输出点(Y 点号,0xFF=无) */
uint8_t directionPositive;
uint8_t directionNegativeLogic; /* SFD900 Bit1: 1 reverses DIR ON/OFF */
uint16_t directionDelayMs;
} PLSR_HW_START_PARAMS;

@@ -34,7 +35,14 @@ PLSR_RESULT PlsrHwInit(void);

PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params);
PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz);
/* Re-arm a PAUSE-stopped timer without clearing its emitted/target counters. */
PLSR_RESULT PlsrHwResumePulse(uint8_t axis);
PLSR_RESULT PlsrHwStopPulse(uint8_t axis);

/* Batch related multi-axis DIR changes into one short GPIO commit window. */
void PlsrHwBeginDirectionBatch(void);
void PlsrHwEndDirectionBatch(void);

uint8_t PlsrHwIsPulseActive(uint8_t axis);
PLSR_HW_STATE PlsrHwGetState(uint8_t axis);
uint32_t PlsrHwGetTimerClockHz(uint8_t axis);
@@ -65,6 +73,7 @@ uint8_t PlsrHwTestGetAbPhaseA(uint8_t axis);
uint8_t PlsrHwTestGetAbPhaseB(uint8_t axis);
uint8_t PlsrHwTestGetAbQuarter(uint8_t axis);
void PlsrHwTestTriggerUpdate(uint8_t axis);
void PlsrHwTestTriggerCompare(uint8_t axis);
void PlsrHwTestAdvanceAbQuarter(uint8_t axis);
#endif



+ 13
- 1
PLSR/Inc/plsr_job.h 查看文件

@@ -32,6 +32,10 @@ typedef uint8_t (*PLSR_READ_WORD_FN)(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
uint16_t *value);
typedef uint8_t (*PLSR_READ_DWORD_FN)(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
int32_t *value);
typedef uint8_t (*PLSR_READ_BIT_FN)(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
@@ -42,6 +46,9 @@ typedef struct
void *context;
PLSR_VALIDATE_WORDS_FN validateWords;
PLSR_READ_WORD_FN readWord;
/* Optional atomic low-word/high-word snapshot. A 0.1ms live-frequency
* source should provide this callback to prevent torn 32-bit reads. */
PLSR_READ_DWORD_FN readDword;
PLSR_READ_BIT_FN readBit;
} PLSR_DATA_SOURCE;

@@ -152,6 +159,8 @@ typedef struct
PLSR_EQUIVALENT_CONFIG equivalent;
PLSR_LIMIT_SNAPSHOT limits;
PLSR_SEGMENT_SNAPSHOT segments[PLSR_MAX_SEGMENTS];
uint16_t positiveBacklashPulses;
uint16_t negativeBacklashPulses;
uint32_t inputDefaultSpeed;
uint32_t inputMaximumSpeed;
uint32_t timerClockHz;
@@ -163,7 +172,7 @@ typedef struct
uint8_t positioningMode;
uint8_t outputMode;
uint8_t directionPoint;
uint8_t directionActiveHigh;
uint8_t directionNegativeLogic;
uint8_t initialDirectionPositive;
uint8_t speedClamped;
uint8_t hasSelfLoop;
@@ -202,6 +211,9 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot,
uint16_t segment,
uint32_t *frequency,
uint8_t *clamped);
PLSR_RESULT PlsrReadLiveFrequencyRaw(const PLSR_JOB_SNAPSHOT *snapshot,
uint16_t segment,
int32_t *rawFrequency);
PLSR_RESULT PlsrCalculateTimerDivider(uint32_t timerClockHz,
uint32_t frequencyHz,
uint16_t *psc,


+ 34
- 0
PLSR/Inc/plsr_modbus_control.h 查看文件

@@ -0,0 +1,34 @@
#ifndef PLSR_MODBUS_CONTROL_H
#define PLSR_MODBUS_CONTROL_H

#include "plsr_types.h"
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#define PLSR_MODBUS_PROTOCOL_VERSION (0x0100U)
#define PLSR_MODBUS_WINDOW_WORDS (256UL)

#define PLSR_MODBUS_CALL_REQUEST_OFFSET (8UL)
#define PLSR_MODBUS_CALL_RESPONSE_OFFSET (24UL)
#define PLSR_MODBUS_COMMAND_REQUEST_OFFSET (40UL)
#define PLSR_MODBUS_COMMAND_RESPONSE_OFFSET (48UL)
#define PLSR_MODBUS_AXIS_STATUS_OFFSET (64UL)
#define PLSR_MODBUS_AXIS_STATUS_WORDS (48UL)

#define PLSR_MODBUS_CALL_NONE (0U)
#define PLSR_MODBUS_CALL_COMMIT (1U)
#define PLSR_MODBUS_CALL_START (2U)

PLSR_RESULT PlsrModbusControlInit(uint16_t baseAddress);
void PlsrModbusControlPoll(void);
uint8_t PlsrModbusControlIsEnabled(void);
uint16_t PlsrModbusControlGetBaseAddress(void);

#ifdef __cplusplus
}
#endif

#endif /* PLSR_MODBUS_CONTROL_H */

+ 18
- 0
PLSR/Inc/plsr_modbus_data.h 查看文件

@@ -0,0 +1,18 @@
#ifndef PLSR_MODBUS_DATA_H
#define PLSR_MODBUS_DATA_H

#include "plsr_job.h"

#ifdef __cplusplus
extern "C" {
#endif

/* Build a PLSR data source backed by the Modbus register store. D, HD and FD
* are separate logical spaces; standard Modbus holding registers expose D. */
void PlsrModbusDataSourceInit(PLSR_DATA_SOURCE *source);

#ifdef __cplusplus
}
#endif

#endif /* PLSR_MODBUS_DATA_H */

+ 8
- 0
PLSR/Inc/plsr_profile.h 查看文件

@@ -93,6 +93,14 @@ PLSR_RESULT PlsrProfileRetarget(PLSR_PROFILE_STATE *state,
/* 从当前频率按既定减速斜率降到0,用于缓停、限位和暂停。 */
PLSR_RESULT PlsrProfileRequestStop(PLSR_PROFILE_STATE *state);

/* Resume a profile that was decelerated to zero by PAUSE. Pulse progress and
* the original segment total are retained; only the velocity trajectory is
* rebuilt for the unexecuted remainder. */
PLSR_RESULT PlsrProfileResume(PLSR_PROFILE_STATE *state,
uint32_t startFrequencyHz,
uint32_t targetFrequencyHz,
uint32_t stopFrequencyHz);

/* 虚拟发射计数校准到硬件实际计数(消除 ARPE 预装载滞后的累积偏差,
* 保证 DONE 判定与硬件同步,段尾不再以冻结频率补发剩余脉冲)。 */
void PlsrProfileSyncPulses(PLSR_PROFILE_STATE *state, uint64_t hwPulses);


+ 24
- 0
PLSR/Inc/plsr_self_test.h 查看文件

@@ -22,6 +22,30 @@ PLSR_RESULT PlsrProtectionSelfTestQueue(void);
/* P6 board test: start four independent PULSE/DIR axes together. */
PLSR_RESULT PlsrFourAxisSelfTestQueue(void);

/* P7 board test: three segments exercise negative and positive backlash. */
PLSR_RESULT PlsrBacklashSelfTestQueue(void);

/* P8 board test: Q4/Q3 show positive/negative DIR logic side by side. */
PLSR_RESULT PlsrDirectionLogicSelfTestQueue(void);

/* P9 board test: Q0=CW and Q1=CCW, with strict channel interlock. */
PLSR_RESULT PlsrCwCcwSelfTestQueue(void);

/* P10 board test: Q0 uses 1ms refresh, Q1 uses 0.1ms refresh. */
PLSR_RESULT PlsrFastRefreshSelfTestQueue(void);
PLSR_RESULT PlsrDynamicFrequencySelfTestQueue(void);
/* P12: S0=D1000, S1=D1100; D1010/D1011 is changed by a Modbus master. */
PLSR_RESULT PlsrModbusDataSelfTestQueue(void);
/* P13 prepares deterministic SFD K1 data; motion is commanded via Modbus. */
PLSR_RESULT PlsrModbusControlSelfTestPrepare(void);
void PlsrSelfTestControlTick100us(void);

/* P11 board-test control. Write this signed dword from IAR Watch while the
* test is running; it represents the live S0 current-segment frequency. */
extern volatile int32_t PlsrSelfTestLiveFrequencyHz;
extern volatile uint32_t PlsrSelfTestDynamicTick100us;
extern volatile uint8_t PlsrSelfTestDynamicPhase;

#ifdef __cplusplus
}
#endif


+ 6
- 0
PLSR/Inc/plsr_types.h 查看文件

@@ -172,6 +172,7 @@ typedef struct
int64_t logicalPosition;
int64_t taskPulses;
int64_t totalPulses;
uint64_t physicalPulses;
uint8_t busy;
uint8_t pulseActive;
uint8_t done;
@@ -187,9 +188,14 @@ typedef struct
uint8_t positiveLimitActive;
uint8_t negativeLimitActive;
uint8_t emergencyLatched;
uint8_t backlashActive;
uint16_t segmentCount;
uint16_t startSegment;
uint16_t currentSegment;
uint32_t currentFrequencyHz;
uint32_t targetFrequencyHz;
uint32_t liveFrequencyRejectCount;
PLSR_RESULT lastLiveFrequencyResult;
} PLSR_STATUS;

#ifdef __cplusplus


+ 556
- 118
PLSR/Src/plsr_core.c 查看文件

@@ -23,6 +23,7 @@ typedef struct
uint16_t compatibleErrorBlock;
PLSR_STOP_REASON stopReason;
PLSR_STATE pendingTerminal;
PLSR_STATE pauseReturnState;
PLSR_RESOURCE_LEASE lease;
PLSR_JOB_SNAPSHOT job;
PLSR_PARSE_DETAIL parseDetail;
@@ -33,12 +34,19 @@ typedef struct
int64_t logicalPosition;
int64_t taskPulses;
int64_t totalPulses;
uint64_t physicalPulses;
int64_t segmentAccountedPulses;
int64_t equivalentCommandRemainder;
PLSR_EQUIVALENT_CONFIG equivalent;
PLSR_PATH_CONTEXT path;
PLSR_PROFILE_STATE profile;
uint8_t profileActive;
int32_t liveFrequencyRaw;
uint32_t liveTargetFrequencyHz;
uint32_t pauseStopFrequencyHz;
uint32_t liveFrequencyRejectCount;
PLSR_RESULT lastLiveFrequencyResult;
/* Publish gate shared by PlsrTask and the TIM6 100us control ISR. */
volatile uint8_t profileActive;
uint8_t profileWasAccel;
uint8_t hasLastCommand;
uint8_t done;
@@ -52,6 +60,11 @@ typedef struct
uint8_t negativeLimitActive;
uint8_t emergencyLatched;
uint8_t segmentEventPublished;
uint8_t backlashActive;
uint8_t backlashBypassOnce;
uint8_t lastUserDirectionValid;
uint8_t lastUserDirectionPositive;
uint8_t runtimeSpeedClamped;
} PLSR_AXIS;

typedef struct
@@ -70,6 +83,8 @@ static PLSR_COMMAND_SLOT PlsrCommandQueue[PLSR_COMMAND_QUEUE_DEPTH];
static uint32_t PlsrNextTicket;
static uint8_t PlsrInitialized;
static PLSR_JOB_SNAPSHOT PlsrJobScratch;
static PLSR_JOB_SNAPSHOT PlsrValidationScratch;
static void (* volatile PlsrControlTickHook)(void);

static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject);
static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis,
@@ -105,6 +120,23 @@ static void PlsrCoreExitCritical(uint32_t interruptState)
#endif
}

static void PlsrSetProfileActive(PLSR_AXIS *axisObject, uint8_t active)
{
uint32_t interruptState = PlsrCoreEnterCritical();

/* Ownership hand-off between PlsrTask and the TIM6 100us ISR. */
axisObject->profileActive = active;
PlsrCoreExitCritical(interruptState);
}

void PlsrSetControlTickHook(void (*hook)(void))
{
uint32_t interruptState = PlsrCoreEnterCritical();

PlsrControlTickHook = hook;
PlsrCoreExitCritical(interruptState);
}

static uint8_t PlsrStateIsBusy(PLSR_STATE state)
{
return ((state == PLSR_STATE_ACCEL) || (state == PLSR_STATE_RUN)
@@ -177,9 +209,15 @@ static void PlsrPublishSdDword(uint8_t axis,

static int32_t PlsrGetCompatibleSegmentPulses(const PLSR_AXIS *axisObject)
{
int64_t signedPulses = (axisObject->directionPositive != 0U)
? axisObject->segmentAccountedPulses
: -axisObject->segmentAccountedPulses;
int64_t signedPulses;

if (axisObject->backlashActive != 0U)
{
return 0;
}
signedPulses = (axisObject->directionPositive != 0U)
? axisObject->segmentAccountedPulses
: -axisObject->segmentAccountedPulses;

if (signedPulses > INT32_MAX)
{
@@ -452,16 +490,26 @@ static int64_t PlsrGetBrakingDistance(const PLSR_AXIS *axisObject)
uint64_t frequencyHz;
uint64_t denominator;
uint64_t numerator;
uint64_t frequencyQ32;
uint32_t decelSlopeHzPerMs;
uint32_t interruptState;
uint8_t profileActive;

if ((axisObject->profileActive == 0U)
|| (axisObject->profile.decelSlopeHzPerMs == 0UL))
/* frequencyQ32 is 64-bit and may be updated by TIM6. Snapshot it with
* the related fields so the 1ms protection pass cannot observe a torn
* value or a mixture of two control ticks. */
interruptState = PlsrCoreEnterCritical();
profileActive = axisObject->profileActive;
decelSlopeHzPerMs = axisObject->profile.decelSlopeHzPerMs;
frequencyQ32 = axisObject->profile.frequencyQ32;
PlsrCoreExitCritical(interruptState);
if ((profileActive == 0U) || (decelSlopeHzPerMs == 0UL))
{
return 0;
}
frequencyHz = axisObject->profile.frequencyQ32 >> 32U;
frequencyHz = frequencyQ32 >> 32U;
numerator = frequencyHz * frequencyHz;
denominator = UINT64_C(2000)
* axisObject->profile.decelSlopeHzPerMs;
denominator = UINT64_C(2000) * decelSlopeHzPerMs;
return (int64_t)((numerator + denominator - 1UL) / denominator);
}

@@ -488,6 +536,7 @@ static PLSR_RESULT PlsrUpdateLimitState(PLSR_AXIS *axisObject,
if (result != PLSR_RESULT_OK) return result;

if ((job->limits.softLimitEnabled != 0U)
&& (axisObject->backlashActive == 0U)
&& (axisObject->positionValid != 0U))
{
if (includeBrakingDistance != 0U)
@@ -591,6 +640,21 @@ static void PlsrAccountHardwarePulses(uint8_t axis,
{
return;
}
if ((uint64_t)delta > UINT64_MAX - axisObject->physicalPulses)
{
axisObject->positionOverflow = 1U;
axisObject->segmentAccountedPulses = emittedPulses;
(void)PlsrPostEvent(axis, PLSR_EVENT_COUNTER_FAULT);
return;
}
axisObject->physicalPulses += (uint64_t)delta;
if (axisObject->backlashActive != 0U)
{
/* Backlash pulses move through mechanical clearance only. */
axisObject->segmentAccountedPulses = emittedPulses;
PlsrPublishRuntime(axis);
return;
}
signedDelta = (axisObject->directionPositive != 0U) ? delta : -delta;
if ((PlsrAddInt64Checked(axisObject->logicalPosition,
signedDelta,
@@ -676,6 +740,7 @@ static uint8_t PlsrTransitionIsAllowed(PLSR_STATE current,
case PLSR_STATE_PAUSED:
return ((target == PLSR_STATE_ACCEL)
|| (target == PLSR_STATE_RUN)
|| (target == PLSR_STATE_WAIT)
|| (target == PLSR_STATE_STOPPED))
? 1U
: 0U;
@@ -715,6 +780,8 @@ PLSR_RESULT PlsrStateTransition(uint8_t axis,
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->immediateStopPending = 0U;
PlsrStopSegmentHardware(axis, axisObject);
axisObject->backlashActive = 0U;
axisObject->backlashBypassOnce = 0U;
PlsrResourceRelease(&axisObject->lease);
PlsrPublishAxis(axis);
return PLSR_RESULT_INVALID_STATE;
@@ -873,6 +940,30 @@ static uint8_t PlsrPopHighestPriorityCommand(PLSR_COMMAND_SLOT *slot)
}

#ifdef PLSR_HOST_TEST
uint32_t PlsrTestGetProfileRefreshHz(uint8_t axis)
{
return (axis < PLSR_AXIS_COUNT) ? PlsrAxes[axis].profile.refreshHz : 0UL;
}

uint8_t PlsrTestGetProfileActive(uint8_t axis)
{
return (axis < PLSR_AXIS_COUNT) ? PlsrAxes[axis].profileActive : 0U;
}

uint8_t PlsrTestGetJobRefreshCode(uint8_t axis)
{
return (axis < PLSR_AXIS_COUNT)
? PlsrAxes[axis].job.s2.refreshCode
: 0U;
}

uint32_t PlsrTestGetProfileFrequencyHz(uint8_t axis)
{
return (axis < PLSR_AXIS_COUNT)
? (uint32_t)(PlsrAxes[axis].profile.frequencyQ32 >> 32U)
: 0UL;
}

PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request)
{
PLSR_COMMAND command;
@@ -921,6 +1012,35 @@ PLSR_RESULT PlsrPostCall(const PLSR_CALL *call)
return PlsrQueueCommand(&command, NULL, call);
}

PLSR_RESULT PlsrValidateCall(const PLSR_CALL *call,
PLSR_PARSE_DETAIL *detail)
{
PLSR_PARSE_CONTEXT parseContext;
uint32_t interruptState;

if ((call == NULL) || (detail == NULL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
if (call->dAxis >= PLSR_AXIS_COUNT)
{
return PLSR_RESULT_INVALID_AXIS;
}
if (PlsrInitialized == 0U)
{
return PLSR_RESULT_INVALID_STATE;
}

interruptState = PlsrCoreEnterCritical();
parseContext.logicalPosition = PlsrAxes[call->dAxis].logicalPosition;
parseContext.positionValid = PlsrAxes[call->dAxis].positionValid;
PlsrCoreExitCritical(interruptState);
return PlsrBuildJobSnapshot(call,
&parseContext,
&PlsrValidationScratch,
detail);
}

PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command)
{
if (command == NULL)
@@ -1021,12 +1141,21 @@ static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject,
axisObject->compatibleErrorBlock = 0U;
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
axisObject->immediateStopPending = 0U;
axisObject->done = 0U;
axisObject->jobValid = 0U;
axisObject->taskPulses = 0;
axisObject->segmentAccountedPulses = 0;
axisObject->segmentAccountingActive = 0U;
axisObject->backlashActive = 0U;
axisObject->backlashBypassOnce = 0U;
axisObject->liveFrequencyRaw = 0;
axisObject->liveTargetFrequencyHz = 0UL;
axisObject->pauseStopFrequencyHz = 0UL;
axisObject->liveFrequencyRejectCount = 0UL;
axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK;
axisObject->runtimeSpeedClamped = 0U;
result = PlsrStateTransition(start->axis,
PLSR_STATE_ACCEL,
PLSR_TRANSITION_START);
@@ -1141,12 +1270,21 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject,
axisObject->compatibleErrorBlock = 0U;
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
axisObject->immediateStopPending = 0U;
axisObject->done = 0U;
axisObject->taskPulses = 0;
axisObject->segmentAccountedPulses = 0;
axisObject->segmentAccountingActive = 0U;
axisObject->segmentEventPublished = 0U;
axisObject->backlashActive = 0U;
axisObject->backlashBypassOnce = 0U;
axisObject->liveFrequencyRaw = 0;
axisObject->liveTargetFrequencyHz = 0UL;
axisObject->pauseStopFrequencyHz = 0UL;
axisObject->liveFrequencyRejectCount = 0UL;
axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK;
axisObject->runtimeSpeedClamped = 0U;
PlsrPathBegin(&axisObject->path,
&axisObject->job,
axisObject->logicalPosition);
@@ -1252,11 +1390,15 @@ static PLSR_RESULT PlsrRequestControlledStop(uint8_t axis,
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
PLSR_RESULT result;
uint32_t interruptState;

PlsrSetStopReason(axisObject, reason);
axisObject->pendingTerminal = terminal;
PlsrPublishSegmentEvent(axis, axisObject, reason);
PlsrPathTerminate(&axisObject->path);
if (terminal != PLSR_STATE_PAUSED)
{
PlsrPublishSegmentEvent(axis, axisObject, reason);
PlsrPathTerminate(&axisObject->path);
}

if ((axisObject->state == PLSR_STATE_WAIT)
|| (axisObject->state == PLSR_STATE_PAUSED))
@@ -1274,7 +1416,9 @@ static PLSR_RESULT PlsrRequestControlledStop(uint8_t axis,
PLSR_STATE_DECEL,
PLSR_TRANSITION_DECEL_REQUEST);
}
interruptState = PlsrCoreEnterCritical();
result = PlsrProfileRequestStop(&axisObject->profile);
PlsrCoreExitCritical(interruptState);
if (result != PLSR_RESULT_OK)
{
return result;
@@ -1315,6 +1459,7 @@ static PLSR_RESULT PlsrStopDecel(uint8_t axis)
static PLSR_RESULT PlsrPause(uint8_t axis)
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
uint32_t interruptState;

if (axisObject->state == PLSR_STATE_PAUSED)
{
@@ -1324,24 +1469,107 @@ static PLSR_RESULT PlsrPause(uint8_t axis)
{
return PLSR_RESULT_BUSY;
}
if (axisObject->state == PLSR_STATE_WAIT)
{
return PlsrRequestControlledStop(axis,
PLSR_STATE_PAUSED,
PLSR_STOP_REASON_PAUSE);
}
if ((axisObject->state != PLSR_STATE_ACCEL)
&& (axisObject->state != PLSR_STATE_RUN)
&& (axisObject->state != PLSR_STATE_DECEL))
&& (axisObject->state != PLSR_STATE_DECEL)
&& (axisObject->state != PLSR_STATE_WAIT))
{
return PLSR_RESULT_INVALID_STATE;
}

axisObject->pauseReturnState = axisObject->state;
if (axisObject->profileActive != 0U)
{
interruptState = PlsrCoreEnterCritical();
/* RequestStop temporarily replaces stopFrequencyHz with zero. Keep
* the segment-specific value (including backlash profiles) so RESUME
* rebuilds the same trajectory rather than assuming the user S2 one. */
axisObject->pauseStopFrequencyHz =
axisObject->profile.stopFrequencyHz;
PlsrCoreExitCritical(interruptState);
}
return PlsrRequestControlledStop(axis,
PLSR_STATE_PAUSED,
PLSR_STOP_REASON_PAUSE);
}

static PLSR_RESULT PlsrResume(uint8_t axis)
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
PLSR_RESULT result;
PLSR_STATE targetState;
uint64_t emittedPulses;

if (axisObject->state != PLSR_STATE_PAUSED)
{
return PLSR_RESULT_INVALID_STATE;
}
if (axisObject->pauseReturnState == PLSR_STATE_WAIT)
{
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
return PlsrStateTransition(axis,
PLSR_STATE_WAIT,
PLSR_TRANSITION_WAIT_COMPLETE);
}
if (axisObject->jobValid == 0U)
{
/* The snapshot-less start entry exists only for host state-machine
* tests; preserve its historical transition-only resume semantics. */
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
return PlsrStateTransition(axis,
PLSR_STATE_ACCEL,
PLSR_TRANSITION_WAIT_COMPLETE);
}

emittedPulses = (uint64_t)PlsrHwGetEmittedPulses(axis);
PlsrProfileSyncPulses(&axisObject->profile, emittedPulses);
if (emittedPulses >= (uint64_t)axisObject->profile.totalPulses)
{
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
result = PlsrStateTransition(axis,
PLSR_STATE_ACCEL,
PLSR_TRANSITION_WAIT_COMPLETE);
if (result == PLSR_RESULT_OK)
{
(void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE);
}
return result;
}

result = PlsrProfileResume(&axisObject->profile,
axisObject->profile.startFrequencyHz,
axisObject->liveTargetFrequencyHz,
axisObject->pauseStopFrequencyHz);
if (result != PLSR_RESULT_OK)
{
return result;
}
result = PlsrHwResumePulse(axis);
if (result != PLSR_RESULT_OK)
{
return result;
}

axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED;
PlsrSetProfileActive(axisObject, 1U);
axisObject->profileWasAccel =
(axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U;
targetState = (axisObject->profileWasAccel != 0U)
? PLSR_STATE_ACCEL
: PLSR_STATE_RUN;
return PlsrStateTransition(axis,
targetState,
PLSR_TRANSITION_WAIT_COMPLETE);
}

static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot)
{
PLSR_AXIS *axisObject = &PlsrAxes[slot->command.axis];
@@ -1376,18 +1604,7 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot)
break;

case PLSR_CMD_RESUME:
if (axisObject->state != PLSR_STATE_PAUSED)
{
result = PLSR_RESULT_INVALID_STATE;
}
else
{
axisObject->stopReason = PLSR_STOP_REASON_NONE;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
result = PlsrStateTransition(slot->command.axis,
PLSR_STATE_ACCEL,
PLSR_TRANSITION_WAIT_COMPLETE);
}
result = PlsrResume(slot->command.axis);
break;

case PLSR_CMD_SET_POSITION:
@@ -1657,6 +1874,11 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis,
int64_t targetPosition;
int64_t nextEquivalentRemainder =
axisObject->equivalentCommandRemainder;
int64_t outputPulses;
int32_t liveFrequencyRaw;
uint32_t gapSlopeHzPerMs;
uint16_t backlashPulses = 0U;
uint8_t runBacklash = 0U;
uint8_t positive;

if (job->positioningMode == 0U)
@@ -1737,48 +1959,114 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis,
return PLSR_RESULT_OK;
}

/* A future segment edited before it becomes current must still use the
* COMMIT snapshot. Capture the source value only as a change-detection
* baseline; a later edit, made while this segment is current, is live. */
result = PlsrReadLiveFrequencyRaw(job,
axisObject->path.currentSegment,
&liveFrequencyRaw);
if (result == PLSR_RESULT_OK)
{
axisObject->liveFrequencyRaw = liveFrequencyRaw;
axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK;
}
else
{
axisObject->lastLiveFrequencyResult = result;
if (axisObject->liveFrequencyRejectCount != UINT32_MAX)
{
axisObject->liveFrequencyRejectCount++;
}
}
axisObject->liveTargetFrequencyHz = segment->targetFrequency;

if (axisObject->backlashBypassOnce != 0U)
{
/* The internal block has just completed; start the user segment. */
axisObject->backlashBypassOnce = 0U;
}
else if ((axisObject->lastUserDirectionValid != 0U)
&& (axisObject->lastUserDirectionPositive != positive))
{
backlashPulses = (positive != 0U)
? job->positiveBacklashPulses
: job->negativeBacklashPulses;
runBacklash = (backlashPulses != 0U) ? 1U : 0U;
}

(void)memset(&profileRequest, 0, sizeof(profileRequest));
profileRequest.targetFrequencyHz = segment->targetFrequency;
profileRequest.startFrequencyHz = job->s2.startSpeed;
profileRequest.stopFrequencyHz = job->s2.stopSpeed;
profileRequest.maxFrequencyHz = job->s2.maximumSpeed;
profileRequest.accelSlopeHzPerMs =
(job->s2.accelerationMs != 0U)
? job->s2.defaultSpeed / job->s2.accelerationMs
: 0UL;
profileRequest.decelSlopeHzPerMs =
(job->s2.decelerationMs != 0U)
? job->s2.defaultSpeed / job->s2.decelerationMs
: 0UL;
profileRequest.curveMode = job->s2.curveMode;
outputPulses = pulses;
if (runBacklash != 0U)
{
outputPulses = backlashPulses;
profileRequest.startFrequencyHz =
(job->s2.gapAccelerationMs == 0U)
? segment->targetFrequency
: 0UL;
profileRequest.stopFrequencyHz = 0UL;
gapSlopeHzPerMs =
(job->s2.gapAccelerationMs != 0U)
? segment->targetFrequency / job->s2.gapAccelerationMs
: 0UL;
if ((job->s2.gapAccelerationMs != 0U)
&& (gapSlopeHzPerMs == 0UL))
{
gapSlopeHzPerMs = 1UL;
}
profileRequest.accelSlopeHzPerMs = gapSlopeHzPerMs;
profileRequest.decelSlopeHzPerMs = gapSlopeHzPerMs;
}
else
{
profileRequest.startFrequencyHz = job->s2.startSpeed;
profileRequest.stopFrequencyHz = job->s2.stopSpeed;
profileRequest.accelSlopeHzPerMs =
(job->s2.accelerationMs != 0U)
? job->s2.defaultSpeed / job->s2.accelerationMs
: 0UL;
profileRequest.decelSlopeHzPerMs =
(job->s2.decelerationMs != 0U)
? job->s2.defaultSpeed / job->s2.decelerationMs
: 0UL;
}

result = PlsrProfileStart(&axisObject->profile,
&profileRequest,
pulses,
1000U);
outputPulses,
(job->s2.refreshCode == 2U) ? 10000U : 1000U);
if (result != PLSR_RESULT_OK)
{
return result;
}

params.frequencyHz = job->s2.startSpeed;
params.targetPulses = pulses;
params.frequencyHz = profileRequest.startFrequencyHz;
params.targetPulses = outputPulses;
params.outputMode = (PLSR_OUTPUT_MODE)job->outputMode;
params.directionPoint = job->directionPoint;
params.directionPositive = positive;
params.directionNegativeLogic = job->directionNegativeLogic;
params.directionDelayMs = job->s2.directionDelayMs;
result = PlsrHwStartPulse(axis, &params);
if (result != PLSR_RESULT_OK)
{
axisObject->profileActive = 0U;
PlsrSetProfileActive(axisObject, 0U);
axisObject->profileWasAccel = 0U;
return result;
}
axisObject->equivalentCommandRemainder = nextEquivalentRemainder;
axisObject->directionPositive = positive;
axisObject->segmentAccountedPulses = 0;
axisObject->segmentAccountingActive = 1U;
axisObject->profileActive = 1U;
axisObject->backlashActive = runBacklash;
if (runBacklash == 0U)
{
axisObject->equivalentCommandRemainder = nextEquivalentRemainder;
axisObject->lastUserDirectionValid = 1U;
axisObject->lastUserDirectionPositive = positive;
}
PlsrSetProfileActive(axisObject, 1U);
axisObject->profileWasAccel =
(axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U;
PlsrPublishAxis(axis);
@@ -1795,7 +2083,7 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis,
static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject)
{
PlsrAccountHardwarePulses(axis, axisObject);
axisObject->profileActive = 0U;
PlsrSetProfileActive(axisObject, 0U);
axisObject->profileWasAccel = 0U;
(void)PlsrHwStopPulse(axis);
PlsrAccountHardwarePulses(axis, axisObject);
@@ -1973,6 +2261,50 @@ static void PlsrProcessNormalEvents(uint8_t axis, uint32_t events)
{
PLSR_PATH_ACTION action;

if (axisObject->backlashActive != 0U)
{
PLSR_RESULT startResult;

/* Internal compensation completion is not a user segment
* completion and therefore must not publish I6000..I6399 or
* advance the path. */
PlsrStopSegmentHardware(axis, axisObject);
axisObject->backlashActive = 0U;
axisObject->backlashBypassOnce = 1U;
(void)PlsrStateTransition(axis,
PLSR_STATE_ACCEL,
PLSR_TRANSITION_START);
startResult = PlsrStartSegmentHardware(axis, axisObject);
if ((startResult == PLSR_RESULT_LIMIT_POSITIVE)
|| (startResult == PLSR_RESULT_LIMIT_NEGATIVE))
{
axisObject->error =
(startResult == PLSR_RESULT_LIMIT_POSITIVE)
? PLSR_ERROR_LIMIT_POSITIVE
: PLSR_ERROR_LIMIT_NEGATIVE;
axisObject->compatibleErrorCode =
(startResult == PLSR_RESULT_LIMIT_POSITIVE) ? 5U : 6U;
PlsrSetStopReason(
axisObject,
(startResult == PLSR_RESULT_LIMIT_POSITIVE)
? PLSR_STOP_REASON_LIMIT_POSITIVE
: PLSR_STOP_REASON_LIMIT_NEGATIVE);
(void)PlsrStateTransition(axis,
PLSR_STATE_STOPPED,
PLSR_TRANSITION_STOP);
}
else if (startResult != PLSR_RESULT_OK)
{
axisObject->error = PLSR_ERROR_TIMER_FAULT;
PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT);
axisObject->done = 0U;
(void)PlsrStateTransition(axis,
PLSR_STATE_ERROR,
PLSR_TRANSITION_FAULT);
}
return;
}

PlsrPublishSegmentEvent(axis,
axisObject,
PLSR_STOP_REASON_NORMAL_COMPLETE);
@@ -2006,6 +2338,7 @@ PLSR_RESULT PlsrInit(void)
PlsrNextTicket = 0UL;
PlsrResourceInit();
(void)PlsrHwInit();
PlsrControlTickHook = NULL;
PlsrInitialized = 1U;

restoredPositionValid = PlcDeviceGetRestoredHsdPositionValid();
@@ -2039,6 +2372,156 @@ PLSR_RESULT PlsrInit(void)
return PLSR_RESULT_OK;
}

static void PlsrStepProfileAxis(uint8_t axis)
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
PLSR_RESULT liveResult;
int32_t liveRaw;
uint32_t frequencyHz;
uint32_t liveFrequencyHz;
uint32_t outputFrequencyHz;
uint64_t hardwarePulses;
uint64_t remainingPulses;
uint8_t profileDone;
uint8_t liveClamped;
uint8_t wasAccel;

if ((axisObject->profileActive == 0U)
|| (PlsrHwGetState(axis) == PLSR_HW_STATE_DIR_SETTLING))
{
return;
}

/* Only the current segment frequency remains live after COMMIT. Poll the
* raw dword every selected control tick; conversion/divider validation is
* performed only when the raw value actually changes. */
if ((axisObject->backlashActive == 0U)
&& (axisObject->jobValid != 0U))
{
liveResult = PlsrReadLiveFrequencyRaw(
&axisObject->job,
axisObject->path.currentSegment,
&liveRaw);
if (liveResult != PLSR_RESULT_OK)
{
if (axisObject->lastLiveFrequencyResult != liveResult)
{
if (axisObject->liveFrequencyRejectCount != UINT32_MAX)
{
axisObject->liveFrequencyRejectCount++;
}
}
axisObject->lastLiveFrequencyResult = liveResult;
}
else if (liveRaw != axisObject->liveFrequencyRaw)
{
axisObject->liveFrequencyRaw = liveRaw;
liveResult = PlsrResolveLiveFrequency(
&axisObject->job,
axisObject->path.currentSegment,
&liveFrequencyHz,
&liveClamped);
if (liveResult == PLSR_RESULT_OK)
{
liveResult = PlsrProfileRetarget(&axisObject->profile,
liveFrequencyHz);
}
if (liveResult == PLSR_RESULT_OK)
{
axisObject->liveTargetFrequencyHz = liveFrequencyHz;
axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK;
if (liveClamped != 0U)
{
axisObject->runtimeSpeedClamped = 1U;
}
}
else
{
axisObject->lastLiveFrequencyResult = liveResult;
if (axisObject->liveFrequencyRejectCount != UINT32_MAX)
{
axisObject->liveFrequencyRejectCount++;
}
}
}
else
{
axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK;
}
}

hardwarePulses = (uint64_t)PlsrHwGetEmittedPulses(axis);
PlsrProfileSyncPulses(&axisObject->profile, hardwarePulses);
wasAccel = axisObject->profileWasAccel;
(void)PlsrProfileStep(&axisObject->profile,
&frequencyHz,
&profileDone);
outputFrequencyHz = frequencyHz;
if ((PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING)
&& (axisObject->profile.phase != PLSR_PROFILE_PHASE_ACCEL)
&& ((axisObject->profile.phase != PLSR_PROFILE_PHASE_DECEL)
|| (axisObject->profile.decelTargetHz
== axisObject->profile.stopFrequencyHz))
&& (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED)
&& ((uint64_t)axisObject->profile.totalPulses
> hardwarePulses + 1UL))
{
remainingPulses = (uint64_t)axisObject->profile.totalPulses
- hardwarePulses - 1UL;
outputFrequencyHz =
PlsrProfileGetBrakingOutputFrequency(&axisObject->profile,
remainingPulses);
}
if ((PlsrHwGetState(axis) == PLSR_HW_STATE_PWM_PENDING)
&& (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL))
{
outputFrequencyHz =
PlsrProfileGetInitialOutputFrequency(&axisObject->profile);
}
if ((profileDone == 0U) || (outputFrequencyHz != 0UL))
{
(void)PlsrHwSetFrequency(axis, outputFrequencyHz);
}
if ((profileDone != 0U)
&& (axisObject->pendingTerminal != PLSR_STATE_UNINITIALIZED))
{
(void)PlsrHwSetFrequency(axis, 0UL);
PlsrSetProfileActive(axisObject, 0U);
(void)PlsrPostEvent(axis, PLSR_EVENT_DECEL_COMPLETE);
}
axisObject->profileWasAccel =
(axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U;
if ((wasAccel != 0U)
&& (axisObject->profileWasAccel == 0U)
&& (axisObject->state == PLSR_STATE_ACCEL))
{
(void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE);
}
}

void PlsrControlTick100us(void)
{
void (*hook)(void);
uint8_t axis;

if (PlsrInitialized == 0U)
{
return;
}
hook = PlsrControlTickHook;
if (hook != NULL)
{
hook();
}
for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++)
{
if (PlsrAxes[axis].job.s2.refreshCode == 2U)
{
PlsrStepProfileAxis(axis);
}
}
}

void PlsrProcess(void)
{
PLSR_COMMAND_SLOT slot;
@@ -2070,6 +2553,9 @@ void PlsrProcess(void)
}
}

/* Apply related multi-axis DIR changes after all commands and segment
* events, keeping cross-port GPIO writes in one short commit window. */
PlsrHwBeginDirectionBatch();
while ((processedCommands < PLSR_COMMAND_QUEUE_DEPTH)
&& (PlsrPopHighestPriorityCommand(&slot) != 0U))
{
@@ -2101,18 +2587,14 @@ void PlsrProcess(void)
}
}

PlsrHwEndDirectionBatch();

/* 1ms tick:路径执行器推进(WAIT/ACT 计时、信号/EXT 轮询、跳转链)
* + 速度曲线推进(P2) + HAL 状态机(DIR 延时)。 */
for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++)
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
PLSR_PATH_ACTION action;
uint32_t frequencyHz;
uint32_t outputFrequencyHz;
uint64_t hardwarePulses;
uint64_t remainingPulses;
uint8_t profileDone;
uint8_t wasAccel;

PlsrHwTick(axis);
PlsrAccountHardwarePulses(axis, axisObject);
@@ -2126,75 +2608,19 @@ void PlsrProcess(void)
{
continue;
}
action = PlsrPathTick(&axisObject->path,
&axisObject->job,
axisObject->logicalPosition);
action = (axisObject->backlashActive != 0U)
? PLSR_PATH_ACTION_NONE
: PlsrPathTick(&axisObject->path,
&axisObject->job,
axisObject->logicalPosition);
if (action != PLSR_PATH_ACTION_NONE)
{
PlsrApplyPathAction(axis, action);
}

if ((axisObject->profileActive != 0U)
&& (PlsrHwGetState(axis) != PLSR_HW_STATE_DIR_SETTLING))
if (axisObject->job.s2.refreshCode != 2U)
{
/* 虚拟计数校准到硬件实际计数:ARPE 预装载滞后的偏差不累积,
* 保证 profile DONE 与硬件计数同步(消除段尾冻结频率收尾)。 */
hardwarePulses = (uint64_t)PlsrHwGetEmittedPulses(axis);
PlsrProfileSyncPulses(&axisObject->profile, hardwarePulses);
wasAccel = axisObject->profileWasAccel;
(void)PlsrProfileStep(&axisObject->profile,
&frequencyHz,
&profileDone);
outputFrequencyHz = frequencyHz;
if ((PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING)
&& (wasAccel == 0U)
&& (axisObject->pendingTerminal
== PLSR_STATE_UNINITIALIZED)
&& ((uint64_t)axisObject->profile.totalPulses
> hardwarePulses + 1UL))
{
/* ARR 预装载在当前脉冲结束后生效,因此计算的是
* “当前脉冲之后”剩余的脉冲。用位置反推减速频率,
* 避免 1 ms 时间曲线与预装载滞后造成段尾突停。 */
remainingPulses =
(uint64_t)axisObject->profile.totalPulses
- hardwarePulses - 1UL;
outputFrequencyHz =
PlsrProfileGetBrakingOutputFrequency(
&axisObject->profile,
remainingPulses);
}
if ((PlsrHwGetState(axis) == PLSR_HW_STATE_PWM_PENDING)
&& (axisObject->profile.phase
== PLSR_PROFILE_PHASE_ACCEL))
{
outputFrequencyHz =
PlsrProfileGetInitialOutputFrequency(
&axisObject->profile);
}
if ((profileDone == 0U) || (outputFrequencyHz != 0UL))
{
(void)PlsrHwSetFrequency(axis, outputFrequencyHz);
}
if ((profileDone != 0U)
&& (axisObject->pendingTerminal
!= PLSR_STATE_UNINITIALIZED))
{
(void)PlsrHwSetFrequency(axis, 0UL);
axisObject->profileActive = 0U;
(void)PlsrPostEvent(axis, PLSR_EVENT_DECEL_COMPLETE);
}
axisObject->profileWasAccel =
(axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL)
? 1U
: 0U;
if ((wasAccel != 0U)
&& (axisObject->profileWasAccel == 0U)
&& (axisObject->state == PLSR_STATE_ACCEL))
{
/* 加速完成(曲线进入匀速/减速)→ 状态机推进到 RUN。 */
(void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE);
}
PlsrStepProfileAxis(axis);
}
}
}
@@ -2240,6 +2666,7 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status)
status->logicalPosition = axisObject->logicalPosition;
status->taskPulses = axisObject->taskPulses;
status->totalPulses = axisObject->totalPulses;
status->physicalPulses = axisObject->physicalPulses;
status->busy = PlsrStateIsBusy(axisObject->state);
status->pulseActive = PlsrStateIsPulseActive(axisObject->state);
status->done = axisObject->done;
@@ -2255,9 +2682,12 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status)
status->positiveLimitActive = axisObject->positiveLimitActive;
status->negativeLimitActive = axisObject->negativeLimitActive;
status->emergencyLatched = axisObject->emergencyLatched;
status->backlashActive = axisObject->backlashActive;
status->s2Set = (axisObject->jobValid != 0U) ? axisObject->job.s2Set : 0U;
status->speedClamped = (axisObject->jobValid != 0U)
? axisObject->job.speedClamped
? (uint8_t)((axisObject->job.speedClamped != 0U)
|| (axisObject->runtimeSpeedClamped
!= 0U))
: 0U;
status->segmentCount = (axisObject->jobValid != 0U)
? axisObject->job.segmentCount
@@ -2269,6 +2699,14 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status)
? PlsrPathGetCurrentSegment(
&axisObject->path)
: 0U;
status->currentFrequencyHz = PlsrHwGetCurrentFrequencyHz(axis);
status->targetFrequencyHz = (axisObject->jobValid != 0U)
? axisObject->liveTargetFrequencyHz
: 0UL;
status->liveFrequencyRejectCount =
axisObject->liveFrequencyRejectCount;
status->lastLiveFrequencyResult =
axisObject->lastLiveFrequencyResult;
PlsrCoreExitCritical(interruptState);
return PLSR_RESULT_OK;
}


+ 339
- 21
PLSR/Src/plsr_hal_f407.c 查看文件

@@ -145,7 +145,11 @@ typedef struct
int64_t emittedPulses;
uint16_t directionDelayRemainingMs;
uint8_t directionPoint;
uint8_t configuredDirectionPoint;
uint8_t directionPositive;
uint8_t directionNegativeLogic;
uint8_t directionTerminalOn;
uint8_t directionOutputPending;
uint8_t abQuarter;
uint8_t abCountAxis;
uint8_t abStartupPriming;
@@ -157,9 +161,12 @@ typedef struct
uint16_t abPendingPairPsc;
uint16_t abPendingArr;
uint8_t abFrequencyPending;
uint8_t cwActiveAxis;
uint8_t cwStopPending;
} PLSR_HW_AXIS_STATE;

static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT];
static uint8_t PlsrHwDirectionBatchActive;

/* 调试快照:当前上板自测只记录 Q0 的 160 ms,避免四轴
* PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示段启动,
@@ -395,19 +402,19 @@ static uint8_t PlsrHwTimerHasCc1if(uint8_t axis)

/* ---- DIR 输出 ----
* XDM 为晶体管(NPN 漏型)输出:ON(导通)= 引脚低电平。
* 信捷正逻辑:正向发脉冲时方向端子置 ON(低)。 */
* 正逻辑:正向=ON;负逻辑:正向=OFF。逻辑运动方向始终单独保存,
* 不能因电气极性反转而改变位置符号、AB相序或SM方向标志。 */

static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive)
static void PlsrHwApplyDirLevel(uint8_t axis)
{
PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis];

state->directionPositive = (positive != 0U) ? 1U : 0U;
if (state->directionPoint == PLSR_HW_DIR_POINT_NONE)
{
return;
}
#ifdef PLSR_HOST_TEST
PlsrHwTimers[axis].dirLevel = (positive != 0U) ? 1U : 0U;
PlsrHwTimers[axis].dirLevel = state->directionTerminalOn;
state->configuredDirectionPoint = state->directionPoint;
#else
if (state->directionPoint < PLSR_HW_OUTPUT_POINT_COUNT)
{
@@ -418,16 +425,19 @@ static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive)
if (pin->port != NULL)
{
/* DIR 点按需配置为推挽输出(上电默认高阻=截止,安全)。 */
gpio.Pin = pin->pin;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(pin->port, &gpio);
pin->port->BSRR = (state->directionTerminalOn != 0U)
? ((uint32_t)pin->pin << 16U)
: (uint32_t)pin->pin;
if (state->configuredDirectionPoint != state->directionPoint)
{
gpio.Pin = pin->pin;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(pin->port, &gpio);
state->configuredDirectionPoint = state->directionPoint;
}
/* 漏型输出:ON(导通)= 低电平。 */
HAL_GPIO_WritePin(pin->port,
pin->pin,
(positive != 0U) ? GPIO_PIN_RESET
: GPIO_PIN_SET);
}
}
#endif
@@ -438,6 +448,63 @@ static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive)
* 避免 ARR 变小瞬间 CNT 超调提前回绕(每段加速会多出 ~ln(f1/f0) 个假脉冲)。
* 首次启动用 EGR.UG 把预装载值加载到影子寄存器,杜绝首个周期用复位值。 */

static void PlsrHwSetDirLevel(uint8_t axis,
uint8_t positive,
uint8_t negativeLogic)
{
PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis];

state->directionPositive = (positive != 0U) ? 1U : 0U;
state->directionNegativeLogic =
(negativeLogic != 0U) ? 1U : 0U;
state->directionTerminalOn =
(uint8_t)(state->directionPositive
^ state->directionNegativeLogic);
if (state->directionPoint == PLSR_HW_DIR_POINT_NONE)
{
return;
}
if ((PlsrHwDirectionBatchActive != 0U)
&& (state->configuredDirectionPoint == state->directionPoint))
{
state->directionOutputPending = 1U;
return;
}
PlsrHwApplyDirLevel(axis);
}

void PlsrHwBeginDirectionBatch(void)
{
PlsrHwDirectionBatchActive = 1U;
}

void PlsrHwEndDirectionBatch(void)
{
uint8_t axis;
#ifndef PLSR_HOST_TEST
uint32_t interruptState = __get_PRIMASK();

__disable_irq();
__DMB();
#endif
PlsrHwDirectionBatchActive = 0U;
for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++)
{
if (PlsrHwAxes[axis].directionOutputPending != 0U)
{
PlsrHwAxes[axis].directionOutputPending = 0U;
PlsrHwApplyDirLevel(axis);
}
}
#ifndef PLSR_HOST_TEST
__DMB();
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

static void PlsrHwTimerSetArpe(uint8_t axis, uint32_t value)
{
#ifdef PLSR_HOST_TEST
@@ -470,6 +537,9 @@ static void PlsrHwConfigurePwm(uint8_t axis, uint32_t frequencyHz)
{
uint16_t psc;
uint16_t arr;
#ifndef PLSR_HOST_TEST
uint32_t interruptState;
#endif

if (PlsrCalculateTimerDivider(PlsrHwAxisMap[axis].timerClockHz,
frequencyHz,
@@ -478,11 +548,23 @@ static void PlsrHwConfigurePwm(uint8_t axis, uint32_t frequencyHz)
{
return;
}
#ifndef PLSR_HOST_TEST
interruptState = __get_PRIMASK();
__disable_irq();
__DMB();
#endif
PlsrHwTimerSetPsc(axis, psc);
PlsrHwTimerSetArr(axis, arr);
PlsrHwTimerSetCcr(axis, (uint32_t)arr / 2UL); /* 50% 占空比 */
PlsrHwTimerSetPwmMode1(axis);
PlsrHwTimerSetArpe(axis, 1UL);
#ifndef PLSR_HOST_TEST
__DMB();
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

/* 首次启动输出:加载影子寄存器后使能更新中断、通道与计数。 */
@@ -759,6 +841,105 @@ static void PlsrHwBeginAbOutput(uint8_t axis, uint8_t debugReason)
PlsrHwDbgCapture(axis, debugReason);
}

static void PlsrHwConfigureCwCcwPwm(uint8_t axis, uint32_t frequencyHz)
{
PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis];
uint8_t activeAxis = state->cwActiveAxis;
uint16_t psc;
uint16_t arr;
#ifndef PLSR_HOST_TEST
uint32_t interruptState;
#endif

if (PlsrCalculateTimerDivider(PlsrHwAxisMap[activeAxis].timerClockHz,
frequencyHz,
&psc,
&arr) != PLSR_RESULT_OK)
{
return;
}
#ifndef PLSR_HOST_TEST
interruptState = __get_PRIMASK();
__disable_irq();
__DMB();
#endif
/* The compare ISR may arm final-pulse shutdown while TIM6 is calculating
* a new divider. Recheck under the same short critical section as the
* preload writes so the tail period can no longer be changed afterwards. */
if (state->cwStopPending == 0U)
{
PlsrHwTimerSetPsc(activeAxis, psc);
PlsrHwTimerSetArr(activeAxis, arr);
PlsrHwTimerSetCcr(activeAxis, (uint32_t)arr / 2UL);
PlsrHwTimerSetPwmMode1(activeAxis);
PlsrHwTimerSetArpe(activeAxis, 1UL);
}
#ifndef PLSR_HOST_TEST
__DMB();
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

static void PlsrHwBeginCwCcwOutput(uint8_t axis)
{
PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis];
uint8_t pairAxis = PlsrHwGetPairedAxis(axis);
uint8_t activeAxis = state->cwActiveAxis;
#ifndef PLSR_HOST_TEST
uint32_t interruptState = __get_PRIMASK();

__disable_irq();
__DMB();
#endif
/* Keep both pins in timer AF. On this output chain, switching a channel
* to GPIO-low is observable as an asserted Q edge. CC1E=0 is the tested
* inactive level and avoids the extra start/end edge. */
PlsrHwStopPwmTimer(axis);
PlsrHwStopPwmTimer(pairAxis);
state->cwStopPending = 0U;
PlsrHwTimerSetUg(activeAxis);
PlsrHwTimerClearUif(activeAxis);
PlsrHwTimerClearCc1if(activeAxis);
PlsrHwTimerSetCnt(activeAxis, 0UL);
PlsrHwTimerSetUie(activeAxis, 0UL);
PlsrHwTimerSetCc1ie(activeAxis, 1UL);
PlsrHwTimerSetCc1e(activeAxis, 1UL);
PlsrHwTimerSetCen(activeAxis, 1UL);
#ifndef PLSR_HOST_TEST
__DMB();
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

static void PlsrHwStopCwCcwOutput(uint8_t axis)
{
uint8_t pairAxis = PlsrHwGetPairedAxis(axis);
#ifndef PLSR_HOST_TEST
uint32_t interruptState = __get_PRIMASK();

__disable_irq();
__DMB();
#endif
/* Disable both compare outputs while retaining AF mode; do not force
* either pin through GPIO during the direction handover. */
PlsrHwStopPwmTimer(axis);
PlsrHwStopPwmTimer(pairAxis);
PlsrHwAxes[axis].cwStopPending = 0U;
#ifndef PLSR_HOST_TEST
__DMB();
if (interruptState == 0UL)
{
__enable_irq();
}
#endif
}

static void PlsrHwConfigureActiveOutput(uint8_t axis,
PLSR_OUTPUT_MODE outputMode,
uint32_t frequencyHz)
@@ -767,6 +948,10 @@ static void PlsrHwConfigureActiveOutput(uint8_t axis,
{
(void)PlsrHwConfigureAbPwm(axis, frequencyHz);
}
else if (outputMode == PLSR_OUTPUT_CW_CCW)
{
PlsrHwConfigureCwCcwPwm(axis, frequencyHz);
}
else
{
PlsrHwConfigurePwm(axis, frequencyHz);
@@ -780,6 +965,10 @@ static void PlsrHwBeginActiveOutput(uint8_t axis,
{
PlsrHwBeginAbOutput(axis, 0U);
}
else if (outputMode == PLSR_OUTPUT_CW_CCW)
{
PlsrHwBeginCwCcwOutput(axis);
}
else
{
PlsrHwPwmBegin(axis);
@@ -812,6 +1001,11 @@ static void PlsrHwStopActiveOutput(uint8_t axis,
}
#endif
}
else if ((outputMode == PLSR_OUTPUT_CW_CCW)
&& (PlsrHwIsAbBaseAxis(axis) != 0U))
{
PlsrHwStopCwCcwOutput(axis);
}
else
{
PlsrHwStopPwmTimer(axis);
@@ -845,10 +1039,13 @@ PLSR_RESULT PlsrHwInit(void)
uint8_t axis;

(void)memset(PlsrHwAxes, 0, sizeof(PlsrHwAxes));
PlsrHwDirectionBatchActive = 0U;
for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++)
{
PlsrHwAxes[axis].state = PLSR_HW_STATE_IDLE;
PlsrHwAxes[axis].directionPoint = PLSR_HW_DIR_POINT_NONE;
PlsrHwAxes[axis].configuredDirectionPoint =
PLSR_HW_DIR_POINT_NONE;
#ifdef PLSR_HOST_TEST
(void)memset(&PlsrHwTimers[axis], 0, sizeof(PlsrHwTimers[axis]));
#else
@@ -862,6 +1059,9 @@ PLSR_RESULT PlsrHwInit(void)
#ifndef PLSR_HOST_TEST
{
GPIO_InitTypeDef gpio;
uint32_t tim6ClockHz;
uint16_t tim6Psc;
uint16_t tim6Arr;

/* 1. 输出点 GPIO 时钟(DIR 点按需配置时使用)。 */
__HAL_RCC_GPIOF_CLK_ENABLE();
@@ -879,6 +1079,31 @@ PLSR_RESULT PlsrHwInit(void)
__HAL_RCC_TIM11_CLK_ENABLE();
__HAL_RCC_TIM13_CLK_ENABLE();
__HAL_RCC_TIM14_CLK_ENABLE();
__HAL_RCC_TIM6_CLK_ENABLE();

/* Independent 10kHz control clock for S2 refreshCode=2. */
tim6ClockHz = HAL_RCC_GetPCLK1Freq();
if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1)
{
tim6ClockHz *= 2UL;
}
if (PlsrCalculateTimerDivider(tim6ClockHz,
10000UL,
&tim6Psc,
&tim6Arr) != PLSR_RESULT_OK)
{
return PLSR_RESULT_DIVIDER_UNREPRESENTABLE;
}
TIM6->CR1 = 0UL;
TIM6->DIER = 0UL;
TIM6->PSC = tim6Psc;
TIM6->ARR = tim6Arr;
TIM6->EGR = TIM_EGR_UG;
TIM6->SR = 0UL;
TIM6->DIER = TIM_DIER_UIE;
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 2U, 0U);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
TIM6->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN;

/* 4. 脉冲点切定时器复用(PF6/7=AF3、PF8/9=AF9)。
* 定时器通道尚未使能(CC1E=0),输出级断开,无毛刺。 */
@@ -923,15 +1148,12 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params)
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
if ((params->outputMode == PLSR_OUTPUT_AB)
if (((params->outputMode == PLSR_OUTPUT_AB)
|| (params->outputMode == PLSR_OUTPUT_CW_CCW))
&& (PlsrHwIsAbBaseAxis(axis) == 0U))
{
return PLSR_RESULT_INVALID_AXIS;
}
if (params->outputMode == PLSR_OUTPUT_CW_CCW)
{
return PLSR_RESULT_NOT_SUPPORTED;
}
state = &PlsrHwAxes[axis];
if (state->state == PLSR_HW_STATE_RUNNING)
{
@@ -945,7 +1167,9 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params)
directionChanged =
(state->directionPoint == PLSR_HW_DIR_POINT_NONE)
|| (state->directionPoint != params->directionPoint)
|| (state->directionPositive != params->directionPositive);
|| (state->directionPositive != params->directionPositive)
|| (state->directionNegativeLogic
!= params->directionNegativeLogic);
}

state->outputMode = params->outputMode;
@@ -965,12 +1189,21 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params)
state->abFrequencyPending = 0U;
if (params->outputMode == PLSR_OUTPUT_PULSE_DIR)
{
PlsrHwSetDirLevel(axis, params->directionPositive);
PlsrHwSetDirLevel(axis,
params->directionPositive,
params->directionNegativeLogic);
}
else
{
state->directionPositive =
(params->directionPositive != 0U) ? 1U : 0U;
state->directionNegativeLogic = 0U;
}
if (params->outputMode == PLSR_OUTPUT_CW_CCW)
{
state->cwActiveAxis = (state->directionPositive != 0U)
? axis
: PlsrHwGetPairedAxis(axis);
}
state->state = (state->directionDelayRemainingMs > 0U)
? PLSR_HW_STATE_DIR_SETTLING
@@ -987,6 +1220,13 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz)
return PLSR_RESULT_INVALID_ARGUMENT;
}
state = &PlsrHwAxes[axis];
if ((state->outputMode == PLSR_OUTPUT_CW_CCW)
&& (state->cwStopPending != 0U))
{
/* Preserve the final physical high width until its natural update
* boundary; no later profile write may move that boundary. */
return PLSR_RESULT_OK;
}
state->currentFrequencyHz = frequencyHz;
if (state->state == PLSR_HW_STATE_RUNNING)
{
@@ -1023,6 +1263,29 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz)
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrHwResumePulse(uint8_t axis)
{
PLSR_HW_AXIS_STATE *state;

if (axis >= PLSR_HW_AXIS_COUNT)
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
state = &PlsrHwAxes[axis];
if ((state->state != PLSR_HW_STATE_RUNNING)
|| (state->currentFrequencyHz != 0UL)
|| (state->emittedPulses >= state->targetPulses))
{
return PLSR_RESULT_INVALID_STATE;
}

/* PlsrHwSetFrequency(0) stopped the physical timer but deliberately kept
* the segment counters. PWM_PENDING makes the next non-zero control-tick
* update take the normal clean-start path without resetting those counts. */
state->state = PLSR_HW_STATE_PWM_PENDING;
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrHwStopPulse(uint8_t axis)
{
PLSR_HW_AXIS_STATE *state;
@@ -1225,6 +1488,43 @@ void PlsrHwOnTimerUpdate(uint8_t axis)
return;
}

if ((state->state == PLSR_HW_STATE_RUNNING)
&& (state->outputMode == PLSR_OUTPUT_CW_CCW))
{
uint8_t hasUif = PlsrHwTimerHasUif(axis);

if (hasUif != 0U)
{
PlsrHwTimerClearUif(axis);
}
if (axis != state->cwActiveAxis)
{
return;
}
if (hasCc1 != 0U)
{
state->emittedPulses++;
if (state->emittedPulses >= state->targetPulses)
{
/* The board output is inverted relative to OC1REF: CC1 is
* the physical rising edge. Arm the tail here, then stop on
* the following update (physical falling edge). */
state->cwStopPending = 1U;
PlsrHwTimerSetCc1ie(axis, 0UL);
PlsrHwTimerClearUif(axis);
PlsrHwTimerSetUie(axis, 1UL);
}
return;
}
if ((hasUif != 0U) && (state->cwStopPending != 0U))
{
PlsrHwStopActiveOutput(ownerAxis, state->outputMode);
state->state = PLSR_HW_STATE_DONE;
(void)PlsrPostEvent(ownerAxis, PLSR_EVENT_SEGMENT_COMPLETE);
}
return;
}

if (hasCc1 != 0U)
{
/* 非运行态/非 AB 模式的 CC1 仅作为杂散标志消费。 */
@@ -1381,6 +1681,15 @@ void PlsrHwTestTriggerUpdate(uint8_t axis)
}
PlsrHwOnTimerUpdate(axis);
}

void PlsrHwTestTriggerCompare(uint8_t axis)
{
if (axis < PLSR_HW_AXIS_COUNT)
{
PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_CC1_BIT;
}
PlsrHwOnTimerUpdate(axis);
}
#endif

#ifndef PLSR_HOST_TEST
@@ -1403,4 +1712,13 @@ void TIM8_TRG_COM_TIM14_IRQHandler(void)
{
PlsrHwOnTimerUpdate(3U);
}

void TIM6_DAC_IRQHandler(void)
{
if ((TIM6->SR & TIM_SR_UIF) != 0UL)
{
TIM6->SR &= ~TIM_SR_UIF;
PlsrControlTick100us();
}
}
#endif

+ 52
- 11
PLSR/Src/plsr_job.c 查看文件

@@ -137,6 +137,23 @@ static PLSR_RESULT PlsrReadInt32(const PLSR_DATA_SOURCE *source,
segment);
return PLSR_RESULT_ADDRESS_OVERFLOW;
}
if (source->readDword != NULL)
{
if (source->readDword(source->context,
device,
address,
value) == 0U)
{
PlsrSetDetail(detail,
PLSR_RESULT_DATA_ACCESS,
block,
address,
0,
segment);
return PLSR_RESULT_DATA_ACCESS;
}
return PLSR_RESULT_OK;
}
result = PlsrReadWord(source,
device,
address,
@@ -486,12 +503,23 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call,
result = PlsrReadFixedWord(0U, commonBase, &word, detail);
if (result != PLSR_RESULT_OK) return result;
commonFlags = word;
snapshot->directionActiveHigh =
snapshot->directionNegativeLogic =
((commonFlags & (1U << 1U)) != 0U) ? 1U : 0U;
snapshot->limits.softLimitEnabled =
((commonFlags & (1U << 2U)) != 0U) ? 1U : 0U;
snapshot->equivalent.unitCode =
(uint8_t)((commonFlags >> 8U) & 0x07U);

result = PlsrReadFixedWord(0U,
(uint16_t)(commonBase + 8U),
&snapshot->positiveBacklashPulses,
detail);
if (result != PLSR_RESULT_OK) return result;
result = PlsrReadFixedWord(0U,
(uint16_t)(commonBase + 9U),
&snapshot->negativeBacklashPulses,
detail);
if (result != PLSR_RESULT_OK) return result;
if (PlsrPositionUnitCodeIsValid(snapshot->equivalent.unitCode) == 0U)
{
PlsrSetDetail(detail,
@@ -681,6 +709,28 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call,
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrReadLiveFrequencyRaw(const PLSR_JOB_SNAPSHOT *snapshot,
uint16_t segment,
int32_t *rawFrequency)
{
uint32_t segmentAddress;

if ((snapshot == NULL) || (rawFrequency == NULL)
|| (segment < 1U) || (segment > snapshot->segmentCount))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
segmentAddress = snapshot->s0.address
+ (uint32_t)segment * PLSR_S0_SEGMENT_WORDS;
return PlsrReadInt32(&snapshot->source,
snapshot->s0.device,
segmentAddress,
rawFrequency,
PLSR_PARSE_BLOCK_S0,
segment,
NULL);
}

static PLSR_RESULT PlsrValidateVariableReference(
const PLSR_CALL *call,
uint8_t sourceCode,
@@ -1447,7 +1497,6 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot,
uint8_t *clamped)
{
int32_t rawFrequency;
uint32_t segmentAddress;
PLSR_RESULT result;

if ((snapshot == NULL) || (frequency == NULL) || (clamped == NULL)
@@ -1463,15 +1512,7 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot,
*clamped = 0U;
return PLSR_RESULT_OK;
}
segmentAddress = snapshot->s0.address
+ (uint32_t)segment * PLSR_S0_SEGMENT_WORDS;
result = PlsrReadInt32(&snapshot->source,
snapshot->s0.device,
segmentAddress,
&rawFrequency,
PLSR_PARSE_BLOCK_S0,
segment,
NULL);
result = PlsrReadLiveFrequencyRaw(snapshot, segment, &rawFrequency);
if (result != PLSR_RESULT_OK) return result;
if (rawFrequency < 0) return PLSR_RESULT_INVALID_FREQUENCY;
*frequency = (rawFrequency == 0) ? snapshot->inputDefaultSpeed


+ 570
- 0
PLSR/Src/plsr_modbus_control.c 查看文件

@@ -0,0 +1,570 @@
#include "plsr_modbus_control.h"
#include "modbus_data_store.h"
#include "plsr_address_map.h"
#include "plsr_core.h"
#include "plsr_job.h"
#include "plsr_modbus_data.h"
#include <stddef.h>
#include <string.h>

#define PLSR_MODBUS_MAGIC_LOW (0x504CU)
#define PLSR_MODBUS_MAGIC_HIGH (0x5352U)
#define PLSR_MODBUS_CAPABILITIES (0x0007U)
#define PLSR_MODBUS_CALL_REQUEST_WORDS (16UL)
#define PLSR_MODBUS_CALL_RESPONSE_WORDS (12UL)
#define PLSR_MODBUS_COMMAND_REQUEST_WORDS (8UL)
#define PLSR_MODBUS_COMMAND_RESPONSE_WORDS (8UL)
#define PLSR_MODBUS_S0_HEADER_WORDS (10UL)
#define PLSR_MODBUS_S0_SEGMENT_WORDS (10UL)
#define PLSR_MODBUS_S1_WORDS (4UL)
#define PLSR_MODBUS_HASH_OFFSET (2166136261UL)
#define PLSR_MODBUS_HASH_PRIME (16777619UL)

typedef struct
{
PLSR_CALL call;
uint32_t fingerprint;
uint8_t valid;
} PLSR_MODBUS_COMMITTED_CALL;

static uint16_t PlsrModbusBaseAddress;
static uint8_t PlsrModbusEnabled;
static uint32_t PlsrModbusLastCallRequestSequence;
static uint32_t PlsrModbusLastCommandRequestSequence;
static uint32_t PlsrModbusStatusGeneration[PLSR_AXIS_COUNT];
static PLSR_MODBUS_COMMITTED_CALL PlsrModbusCommitted[PLSR_AXIS_COUNT];
static uint16_t PlsrModbusStatusWords[PLSR_AXIS_COUNT]
[PLSR_MODBUS_AXIS_STATUS_WORDS];
static const uint16_t PlsrModbusZeroWindow[PLSR_MODBUS_WINDOW_WORDS] = {0U};

static void PlsrModbusPutU32(uint16_t *words,
uint32_t offset,
uint32_t value)
{
words[offset] = (uint16_t)(value & 0xFFFFUL);
words[offset + 1UL] = (uint16_t)(value >> 16U);
}

static void PlsrModbusPutU64(uint16_t *words,
uint32_t offset,
uint64_t value)
{
words[offset] = (uint16_t)(value & 0xFFFFULL);
words[offset + 1UL] = (uint16_t)((value >> 16U) & 0xFFFFULL);
words[offset + 2UL] = (uint16_t)((value >> 32U) & 0xFFFFULL);
words[offset + 3UL] = (uint16_t)(value >> 48U);
}

static uint32_t PlsrModbusGetU32(const uint16_t *words, uint32_t offset)
{
return ((uint32_t)words[offset + 1UL] << 16U) | words[offset];
}

static uint64_t PlsrModbusGetU64(const uint16_t *words, uint32_t offset)
{
return ((uint64_t)words[offset + 3UL] << 48U)
| ((uint64_t)words[offset + 2UL] << 32U)
| ((uint64_t)words[offset + 1UL] << 16U)
| words[offset];
}

static uint8_t PlsrModbusReadWords(uint32_t offset,
uint16_t *words,
uint32_t wordCount)
{
uint32_t index;

if (words == NULL)
{
return 0U;
}
for (index = 0UL; index < wordCount; index++)
{
if (ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
(uint32_t)PlsrModbusBaseAddress + offset + index,
&words[index]) == 0U)
{
return 0U;
}
}
return 1U;
}

static uint8_t PlsrModbusRangesOverlap(uint32_t firstA,
uint32_t countA,
uint32_t firstB,
uint32_t countB)
{
return ((firstA < (firstB + countB)) && (firstB < (firstA + countA)))
? 1U
: 0U;
}

static uint32_t PlsrModbusHashWord(uint32_t hash, uint16_t value)
{
hash ^= (uint8_t)(value & 0x00FFU);
hash *= PLSR_MODBUS_HASH_PRIME;
hash ^= (uint8_t)(value >> 8U);
hash *= PLSR_MODBUS_HASH_PRIME;
return hash;
}

static uint32_t PlsrModbusHashU32(uint32_t hash, uint32_t value)
{
hash = PlsrModbusHashWord(hash, (uint16_t)(value & 0xFFFFUL));
return PlsrModbusHashWord(hash, (uint16_t)(value >> 16U));
}

static PLSR_RESULT PlsrModbusFingerprintCall(const PLSR_CALL *call,
uint32_t *fingerprint)
{
uint16_t word;
int32_t segmentCount;
uint32_t s0Words;
uint32_t index;
uint32_t hash = PLSR_MODBUS_HASH_OFFSET;

if ((call == NULL) || (fingerprint == NULL)
|| (call->source.readDword == NULL)
|| (call->source.readWord == NULL)
|| (call->source.validateWords == NULL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
if (call->source.readDword(call->source.context,
call->s0.device,
call->s0.address,
&segmentCount) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
if ((segmentCount < 1) || (segmentCount > (int32_t)PLSR_MAX_SEGMENTS))
{
return PLSR_RESULT_SEGMENT_OVERFLOW;
}
s0Words = PLSR_MODBUS_S0_HEADER_WORDS
+ (uint32_t)segmentCount * PLSR_MODBUS_S0_SEGMENT_WORDS;
if ((call->source.validateWords(call->source.context,
call->s0.device,
call->s0.address,
s0Words) == 0U)
|| (call->source.validateWords(call->source.context,
call->s1.device,
call->s1.address,
PLSR_MODBUS_S1_WORDS) == 0U))
{
return PLSR_RESULT_DATA_ACCESS;
}
if (((call->s0.device == PLSR_DEVICE_D)
&& (PlsrModbusRangesOverlap(call->s0.address,
s0Words,
PlsrModbusBaseAddress,
PLSR_MODBUS_WINDOW_WORDS) != 0U))
|| ((call->s1.device == PLSR_DEVICE_D)
&& (PlsrModbusRangesOverlap(call->s1.address,
PLSR_MODBUS_S1_WORDS,
PlsrModbusBaseAddress,
PLSR_MODBUS_WINDOW_WORDS) != 0U)))
{
return PLSR_RESULT_BLOCK_OVERLAP;
}

hash = PlsrModbusHashWord(hash, (uint16_t)call->s0.device);
hash = PlsrModbusHashU32(hash, call->s0.address);
for (index = 0UL; index < s0Words; index++)
{
if (call->source.readWord(call->source.context,
call->s0.device,
call->s0.address + index,
&word) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
hash = PlsrModbusHashWord(hash, word);
}
hash = PlsrModbusHashWord(hash, (uint16_t)call->s1.device);
hash = PlsrModbusHashU32(hash, call->s1.address);
for (index = 0UL; index < PLSR_MODBUS_S1_WORDS; index++)
{
if (call->source.readWord(call->source.context,
call->s1.device,
call->s1.address + index,
&word) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
hash = PlsrModbusHashWord(hash, word);
}
hash = PlsrModbusHashWord(hash, (uint16_t)call->s2.type);
hash = PlsrModbusHashWord(hash, (uint16_t)call->s2.data.device);
hash = PlsrModbusHashU32(hash, call->s2.data.address);
hash = PlsrModbusHashU32(hash, (uint32_t)call->s2.constant);
if (call->s2.type == PLSR_OPERAND_DATA)
{
for (index = 0UL; index < 2UL; index++)
{
if (call->source.readWord(call->source.context,
call->s2.data.device,
call->s2.data.address + index,
&word) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
hash = PlsrModbusHashWord(hash, word);
}
}
hash = PlsrModbusHashWord(hash, call->dAxis);
hash = PlsrModbusHashWord(hash, call->outputModeOverride);
*fingerprint = hash;
return PLSR_RESULT_OK;
}

static PLSR_RESULT PlsrModbusBuildCall(const uint16_t *request,
PLSR_CALL *call)
{
uint16_t s2Type;
uint16_t outputMode;

if ((request == NULL) || (call == NULL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
s2Type = request[8UL];
outputMode = request[13UL];
if ((request[2UL] > (uint16_t)PLSR_DEVICE_FD)
|| (request[5UL] > (uint16_t)PLSR_DEVICE_FD)
|| (s2Type > (uint16_t)PLSR_OPERAND_DATA)
|| ((s2Type == (uint16_t)PLSR_OPERAND_DATA)
&& (request[9UL] > (uint16_t)PLSR_DEVICE_FD))
|| (request[12UL] >= PLSR_AXIS_COUNT)
|| ((outputMode > (uint16_t)PLSR_OUTPUT_CW_CCW)
&& (outputMode != PLSR_OUTPUT_MODE_FROM_SFD)))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}

(void)memset(call, 0, sizeof(*call));
call->sequence = PlsrModbusGetU32(request, 0UL);
call->s0.device = (PLSR_DEVICE_TYPE)request[2UL];
call->s0.address = PlsrModbusGetU32(request, 3UL);
call->s1.device = (PLSR_DEVICE_TYPE)request[5UL];
call->s1.address = PlsrModbusGetU32(request, 6UL);
call->s2.type = (PLSR_OPERAND_TYPE)s2Type;
call->s2.data.device = (PLSR_DEVICE_TYPE)request[9UL];
call->s2.data.address = PlsrModbusGetU32(request, 10UL);
call->s2.constant = (int32_t)PlsrModbusGetU32(request, 10UL);
call->dAxis = (uint8_t)request[12UL];
call->outputModeOverride = (uint8_t)outputMode;
PlsrModbusDataSourceInit(&call->source);
return PLSR_RESULT_OK;
}

static void PlsrModbusPublishCallResponse(uint32_t sequence,
uint16_t operation,
PLSR_RESULT result,
const PLSR_PARSE_DETAIL *detail,
uint8_t committed)
{
uint16_t response[PLSR_MODBUS_CALL_RESPONSE_WORDS] = {0U};

PlsrModbusPutU32(response, 0UL, sequence);
response[2UL] = operation;
response[3UL] = (uint16_t)result;
if (detail != NULL)
{
response[4UL] = (uint16_t)detail->result;
response[5UL] = (uint16_t)detail->block;
PlsrModbusPutU32(response, 6UL, detail->address);
PlsrModbusPutU32(response, 8UL, (uint32_t)detail->value);
response[10UL] = detail->segment;
}
response[11UL] = committed;
(void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
(uint32_t)PlsrModbusBaseAddress
+ PLSR_MODBUS_CALL_RESPONSE_OFFSET,
response,
PLSR_MODBUS_CALL_RESPONSE_WORDS);
}

static void PlsrModbusHandleCallRequest(void)
{
uint16_t request[PLSR_MODBUS_CALL_REQUEST_WORDS];
PLSR_CALL call;
PLSR_PARSE_DETAIL detail;
PLSR_RESULT result;
uint32_t sequence;
uint32_t fingerprint;
uint16_t operation;
uint8_t axis = 0U;

if (PlsrModbusReadWords(PLSR_MODBUS_CALL_REQUEST_OFFSET,
request,
PLSR_MODBUS_CALL_REQUEST_WORDS) == 0U)
{
return;
}
sequence = PlsrModbusGetU32(request, 0UL);
if ((sequence == 0UL) || (sequence == PlsrModbusLastCallRequestSequence))
{
return;
}
PlsrModbusLastCallRequestSequence = sequence;
operation = request[14UL];
(void)memset(&detail, 0, sizeof(detail));
result = PlsrModbusBuildCall(request, &call);
if (result == PLSR_RESULT_OK)
{
axis = call.dAxis;
}

if ((result == PLSR_RESULT_OK) && (operation == PLSR_MODBUS_CALL_COMMIT))
{
result = PlsrValidateCall(&call, &detail);
if (result == PLSR_RESULT_OK)
{
result = PlsrModbusFingerprintCall(&call, &fingerprint);
}
if (result == PLSR_RESULT_OK)
{
PlsrModbusCommitted[axis].call = call;
PlsrModbusCommitted[axis].fingerprint = fingerprint;
PlsrModbusCommitted[axis].valid = 1U;
}
else
{
PlsrModbusCommitted[axis].valid = 0U;
}
}
else if ((result == PLSR_RESULT_OK)
&& (operation == PLSR_MODBUS_CALL_START))
{
if (PlsrModbusCommitted[axis].valid == 0U)
{
result = PLSR_RESULT_INVALID_STATE;
}
else
{
result = PlsrModbusFingerprintCall(
&PlsrModbusCommitted[axis].call,
&fingerprint);
if ((result == PLSR_RESULT_OK)
&& (fingerprint != PlsrModbusCommitted[axis].fingerprint))
{
result = PLSR_RESULT_BUSY;
}
if (result == PLSR_RESULT_OK)
{
result = PlsrValidateCall(&PlsrModbusCommitted[axis].call,
&detail);
}
if (result == PLSR_RESULT_OK)
{
PlsrModbusCommitted[axis].call.sequence = sequence;
result = PlsrPostCall(&PlsrModbusCommitted[axis].call);
}
}
}
else if (result == PLSR_RESULT_OK)
{
result = PLSR_RESULT_INVALID_ARGUMENT;
}

PlsrModbusPublishCallResponse(
sequence,
operation,
result,
&detail,
(axis < PLSR_AXIS_COUNT) ? PlsrModbusCommitted[axis].valid : 0U);
}

static void PlsrModbusPublishCommandResponse(uint32_t sequence,
uint16_t opcode,
uint16_t axis,
PLSR_RESULT result)
{
uint16_t response[PLSR_MODBUS_COMMAND_RESPONSE_WORDS] = {0U};

PlsrModbusPutU32(response, 0UL, sequence);
response[2UL] = opcode;
response[3UL] = axis;
response[4UL] = (uint16_t)result;
(void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
(uint32_t)PlsrModbusBaseAddress
+ PLSR_MODBUS_COMMAND_RESPONSE_OFFSET,
response,
PLSR_MODBUS_COMMAND_RESPONSE_WORDS);
}

static void PlsrModbusHandleCommandRequest(void)
{
uint16_t request[PLSR_MODBUS_COMMAND_REQUEST_WORDS];
PLSR_COMMAND command;
PLSR_RESULT result;
uint32_t sequence;

if (PlsrModbusReadWords(PLSR_MODBUS_COMMAND_REQUEST_OFFSET,
request,
PLSR_MODBUS_COMMAND_REQUEST_WORDS) == 0U)
{
return;
}
sequence = PlsrModbusGetU32(request, 0UL);
if ((sequence == 0UL)
|| (sequence == PlsrModbusLastCommandRequestSequence))
{
return;
}
PlsrModbusLastCommandRequestSequence = sequence;
(void)memset(&command, 0, sizeof(command));
if ((request[2UL] > (uint16_t)PLSR_CMD_SELF_TEST)
|| (request[2UL] == (uint16_t)PLSR_CMD_START)
|| (request[3UL] >= PLSR_AXIS_COUNT))
{
result = PLSR_RESULT_INVALID_ARGUMENT;
}
else
{
command.sequence = sequence;
command.opcode = (PLSR_COMMAND_OPCODE)request[2UL];
command.axis = (uint8_t)request[3UL];
command.argument = (int64_t)PlsrModbusGetU64(request, 4UL);
result = PlsrPostCommand(&command);
}
PlsrModbusPublishCommandResponse(sequence,
request[2UL],
request[3UL],
result);
}

static void PlsrModbusPublishAxisStatus(uint8_t axis)
{
PLSR_STATUS status;
uint16_t *words = PlsrModbusStatusWords[axis];
uint32_t flags = 0UL;
uint32_t generation;

if (PlsrGetStatus(axis, &status) != PLSR_RESULT_OK)
{
return;
}
generation = PlsrModbusStatusGeneration[axis] + 2UL;
if (generation == 0UL)
{
generation = 2UL;
}
PlsrModbusStatusGeneration[axis] = generation;
(void)memset(words, 0, sizeof(PlsrModbusStatusWords[axis]));
if (status.busy != 0U) flags |= (1UL << 0U);
if (status.pulseActive != 0U) flags |= (1UL << 1U);
if (status.done != 0U) flags |= (1UL << 2U);
if (status.wait != 0U) flags |= (1UL << 3U);
if (status.directionPositive != 0U) flags |= (1UL << 4U);
if (status.positionValid != 0U) flags |= (1UL << 5U);
if (status.jobValid != 0U) flags |= (1UL << 6U);
if (status.speedClamped != 0U) flags |= (1UL << 7U);
if (status.positionOverflow != 0U) flags |= (1UL << 8U);
if (status.positiveLimitActive != 0U) flags |= (1UL << 9U);
if (status.negativeLimitActive != 0U) flags |= (1UL << 10U);
if (status.emergencyLatched != 0U) flags |= (1UL << 11U);
if (status.backlashActive != 0U) flags |= (1UL << 12U);

PlsrModbusPutU32(words, 0UL, generation);
words[2UL] = (uint16_t)status.state;
PlsrModbusPutU32(words, 3UL, flags);
words[5UL] = (uint16_t)status.outputMode;
words[6UL] = (uint16_t)status.error;
words[7UL] = (uint16_t)status.stopReason;
words[8UL] = (uint16_t)status.lastCommandResult;
words[9UL] = status.s2Set;
PlsrModbusPutU32(words, 10UL, status.lastCommandSequence);
PlsrModbusPutU32(words, 12UL, status.illegalTransitionCount);
PlsrModbusPutU32(words, 14UL, status.pendingEvents);
PlsrModbusPutU64(words, 16UL, (uint64_t)status.logicalPosition);
PlsrModbusPutU64(words, 20UL, (uint64_t)status.taskPulses);
PlsrModbusPutU64(words, 24UL, (uint64_t)status.totalPulses);
PlsrModbusPutU64(words, 28UL, status.physicalPulses);
words[32UL] = status.segmentCount;
words[33UL] = status.startSegment;
words[34UL] = status.currentSegment;
words[35UL] = status.directionPoint;
words[36UL] = status.highResourceMask;
PlsrModbusPutU32(words, 38UL, status.currentFrequencyHz);
PlsrModbusPutU32(words, 40UL, status.targetFrequencyHz);
PlsrModbusPutU32(words, 42UL, status.liveFrequencyRejectCount);
words[44UL] = (uint16_t)status.lastLiveFrequencyResult;
PlsrModbusPutU32(words, 46UL, generation);
(void)ModbusDataWriteWords(
MODBUS_DATA_DEVICE_D,
(uint32_t)PlsrModbusBaseAddress + PLSR_MODBUS_AXIS_STATUS_OFFSET
+ (uint32_t)axis * PLSR_MODBUS_AXIS_STATUS_WORDS,
words,
PLSR_MODBUS_AXIS_STATUS_WORDS);
}

PLSR_RESULT PlsrModbusControlInit(uint16_t baseAddress)
{
uint16_t header[8] = {0U};

if (ModbusDataValidateWords(MODBUS_DATA_DEVICE_D,
baseAddress,
PLSR_MODBUS_WINDOW_WORDS) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
PlsrModbusBaseAddress = baseAddress;
PlsrModbusEnabled = 0U;
PlsrModbusLastCallRequestSequence = 0UL;
PlsrModbusLastCommandRequestSequence = 0UL;
(void)memset(PlsrModbusCommitted, 0, sizeof(PlsrModbusCommitted));
(void)memset(PlsrModbusStatusGeneration,
0,
sizeof(PlsrModbusStatusGeneration));
if (ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
baseAddress,
PlsrModbusZeroWindow,
PLSR_MODBUS_WINDOW_WORDS) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
header[0UL] = PLSR_MODBUS_MAGIC_LOW;
header[1UL] = PLSR_MODBUS_MAGIC_HIGH;
header[2UL] = PLSR_MODBUS_PROTOCOL_VERSION;
header[3UL] = (uint16_t)PLSR_MODBUS_WINDOW_WORDS;
header[4UL] = PLSR_MODBUS_CAPABILITIES;
if (ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
baseAddress,
header,
8UL) == 0U)
{
return PLSR_RESULT_DATA_ACCESS;
}
PlsrModbusEnabled = 1U;
PlsrModbusControlPoll();
return PLSR_RESULT_OK;
}

void PlsrModbusControlPoll(void)
{
uint8_t axis;

if (PlsrModbusEnabled == 0U)
{
return;
}
PlsrModbusHandleCallRequest();
PlsrModbusHandleCommandRequest();
for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++)
{
PlsrModbusPublishAxisStatus(axis);
}
}

uint8_t PlsrModbusControlIsEnabled(void)
{
return PlsrModbusEnabled;
}

uint16_t PlsrModbusControlGetBaseAddress(void)
{
return PlsrModbusBaseAddress;
}

+ 72
- 0
PLSR/Src/plsr_modbus_data.c 查看文件

@@ -0,0 +1,72 @@
#include "plsr_modbus_data.h"
#include "modbus_data_store.h"
#include <stddef.h>

static uint8_t PlsrModbusDevice(PLSR_DEVICE_TYPE device,
MODBUS_DATA_DEVICE *modbusDevice)
{
if ((modbusDevice == NULL) || (device > PLSR_DEVICE_FD))
{
return 0U;
}
*modbusDevice = (MODBUS_DATA_DEVICE)device;
return 1U;
}

static uint8_t PlsrModbusValidateWords(void *context,
PLSR_DEVICE_TYPE device,
uint32_t firstAddress,
uint32_t wordCount)
{
MODBUS_DATA_DEVICE modbusDevice;

(void)context;
if (PlsrModbusDevice(device, &modbusDevice) == 0U)
{
return 0U;
}
return ModbusDataValidateWords(modbusDevice, firstAddress, wordCount);
}

static uint8_t PlsrModbusReadWord(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
uint16_t *value)
{
MODBUS_DATA_DEVICE modbusDevice;

(void)context;
if (PlsrModbusDevice(device, &modbusDevice) == 0U)
{
return 0U;
}
return ModbusDataReadWord(modbusDevice, address, value);
}

static uint8_t PlsrModbusReadDword(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
int32_t *value)
{
MODBUS_DATA_DEVICE modbusDevice;

(void)context;
if (PlsrModbusDevice(device, &modbusDevice) == 0U)
{
return 0U;
}
return ModbusDataReadDword(modbusDevice, address, value);
}

void PlsrModbusDataSourceInit(PLSR_DATA_SOURCE *source)
{
if (source == NULL)
{
return;
}
source->context = NULL;
source->validateWords = PlsrModbusValidateWords;
source->readWord = PlsrModbusReadWord;
source->readDword = PlsrModbusReadDword;
source->readBit = NULL;
}

+ 4
- 0
PLSR/Src/plsr_path.c 查看文件

@@ -16,6 +16,10 @@ static uint8_t PlsrPathReadDword(const PLSR_DATA_SOURCE *source,
uint16_t lowWord;
uint16_t highWord;

if (source->readDword != NULL)
{
return source->readDword(source->context, device, address, value);
}
if ((source->readWord == NULL)
|| (source->readWord(source->context,
device,


+ 43
- 0
PLSR/Src/plsr_profile.c 查看文件

@@ -499,6 +499,49 @@ PLSR_RESULT PlsrProfileRequestStop(PLSR_PROFILE_STATE *state)
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrProfileResume(PLSR_PROFILE_STATE *state,
uint32_t startFrequencyHz,
uint32_t targetFrequencyHz,
uint32_t stopFrequencyHz)
{
uint64_t totalPulsesQ32;

if ((state == NULL) || (state->started == 0U)
|| (state->phase != PLSR_PROFILE_PHASE_DONE)
|| (state->totalPulses <= 0) || (targetFrequencyHz == 0UL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
totalPulsesQ32 = (uint64_t)state->totalPulses
* PLSR_PROFILE_Q32_ONE;
if (state->emittedPulsesQ32 >= totalPulsesQ32)
{
return PLSR_RESULT_INVALID_STATE;
}

state->targetFrequencyHz = targetFrequencyHz;
state->startFrequencyHz = startFrequencyHz;
state->stopFrequencyHz = stopFrequencyHz;
state->decelTargetHz = stopFrequencyHz;
if (startFrequencyHz > targetFrequencyHz)
{
startFrequencyHz = targetFrequencyHz;
}
state->frequencyQ32 = PlsrProfileHzToQ32(startFrequencyHz);
state->phase = PLSR_PROFILE_PHASE_ACCEL;
if ((startFrequencyHz >= targetFrequencyHz)
|| (state->accelSlopeHzPerMs == 0UL))
{
state->frequencyQ32 = PlsrProfileHzToQ32(targetFrequencyHz);
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
else
{
PlsrProfileBeginAccel(state);
}
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrProfilePlan(const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
PLSR_PROFILE_PLAN *plan)


+ 623
- 0
PLSR/Src/plsr_self_test.c 查看文件

@@ -1,7 +1,9 @@
#include "plsr_self_test.h"
#include "plc_device.h"
#include "modbus_data_store.h"
#include "plsr_core.h"
#include "plsr_job.h"
#include "plsr_modbus_data.h"
#include <string.h>

/* 上电自测(验证后可删除):
@@ -18,8 +20,62 @@
#define SELF_TEST_DIR_POINT (4U)
#define SELF_TEST_SFD_AXIS_STRIDE (130U)
#define SELF_TEST_SFD_SET_OFFSET (50U)
#define SELF_TEST_MODBUS_S0_BASE (1000UL)
#define SELF_TEST_MODBUS_S1_BASE (1100UL)

static uint16_t SelfTestWords[3][SELF_TEST_WORD_CAPACITY];
volatile int32_t PlsrSelfTestLiveFrequencyHz;
volatile uint32_t PlsrSelfTestDynamicTick100us;
volatile uint8_t PlsrSelfTestDynamicPhase;
static volatile uint8_t PlsrSelfTestDynamicEnabled;

void PlsrSelfTestControlTick100us(void)
{
if (PlsrSelfTestDynamicEnabled == 0U)
{
return;
}
if (PlsrSelfTestDynamicTick100us != UINT32_MAX)
{
PlsrSelfTestDynamicTick100us++;
}
switch (PlsrSelfTestDynamicTick100us)
{
case 10000UL: /* 1.0s: 1000 -> 4000Hz. */
PlsrSelfTestLiveFrequencyHz = 4000;
PlsrSelfTestDynamicPhase = 1U;
break;

case 15000UL: /* 1.5s: 4000 -> 500Hz. */
PlsrSelfTestLiveFrequencyHz = 500;
PlsrSelfTestDynamicPhase = 2U;
break;

case 20000UL: /* 2.0s: zero selects the 1000Hz S2 default. */
PlsrSelfTestLiveFrequencyHz = 0;
PlsrSelfTestDynamicPhase = 3U;
break;

case 22000UL: /* 2.2s: 8000 is clamped to the 5000Hz maximum. */
PlsrSelfTestLiveFrequencyHz = 8000;
PlsrSelfTestDynamicPhase = 4U;
break;

case 27000UL: /* 2.7s: invalid value must retain the safe target. */
PlsrSelfTestLiveFrequencyHz = -1;
PlsrSelfTestDynamicPhase = 5U;
break;

case 29000UL: /* 2.9s: recover and hold 2000Hz. */
PlsrSelfTestLiveFrequencyHz = 2000;
PlsrSelfTestDynamicPhase = 6U;
PlsrSelfTestDynamicEnabled = 0U;
break;

default:
break;
}
}

static uint8_t SelfTestValidateWords(void *context,
PLSR_DEVICE_TYPE device,
@@ -52,6 +108,37 @@ static uint8_t SelfTestReadWord(void *context,
return 1U;
}

static uint8_t SelfTestReadDwordLive(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
int32_t *value)
{
uint16_t lowWord;
uint16_t highWord;

if (value == NULL)
{
return 0U;
}
if ((device == PLSR_DEVICE_D)
&& (address == SELF_TEST_S0_BASE + 10UL))
{
/* Aligned Cortex-M4 dword load: atomic source for the TIM6 ISR. */
*value = PlsrSelfTestLiveFrequencyHz;
return 1U;
}
if ((SelfTestReadWord(context, device, address, &lowWord) == 0U)
|| (SelfTestReadWord(context,
device,
address + 1UL,
&highWord) == 0U))
{
return 0U;
}
*value = (int32_t)(((uint32_t)highWord << 16U) | lowWord);
return 1U;
}

static uint8_t SelfTestReadBit(void *context,
PLSR_DEVICE_TYPE device,
uint32_t address,
@@ -341,3 +428,539 @@ PLSR_RESULT PlsrFourAxisSelfTestQueue(void)
}
return PLSR_RESULT_QUEUED;
}

PLSR_RESULT PlsrBacklashSelfTestQueue(void)
{
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;

(void)memset(SelfTestWords, 0, sizeof(SelfTestWords));

/* Pulse unit, Q4 direction, +10/-20 pulse backlash. */
(void)PlcDeviceWriteSfd(900U, 0U);
SelfTestWriteSfdDword(902U, 1UL);
SelfTestWriteSfdDword(904U, 1UL);
(void)PlcDeviceWriteSfd(906U, SELF_TEST_DIR_POINT);
(void)PlcDeviceWriteSfd(907U, 10U);
(void)PlcDeviceWriteSfd(908U, 10U);
(void)PlcDeviceWriteSfd(909U, 20U);
(void)PlcDeviceWriteSfd(912U, 0U);
(void)PlcDeviceWriteSfd(915U, 0xFFFFU);

/* K1 user segments are fixed 1kHz. Backlash blocks use a 20ms
* acceleration/deceleration parameter. */
SelfTestWriteSfdDword(950U, 1000UL);
(void)PlcDeviceWriteSfd(952U, 0U);
(void)PlcDeviceWriteSfd(953U, 0U);
(void)PlcDeviceWriteSfd(954U, 20U);
(void)PlcDeviceWriteSfd(955U, 0U);
SelfTestWriteSfdDword(956U, 100000UL);
SelfTestWriteSfdDword(958U, 1000UL);
SelfTestWriteSfdDword(960U, 0UL);
(void)PlcDeviceWriteSfd(962U, 50U);
(void)PlcDeviceWriteSfd(963U, 0U);
(void)PlcDeviceWriteSfd(964U, 0U);
SelfTestWriteSfdDword(966U, 2000UL);
SelfTestWriteSfdDword(968U, 200UL);

SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE, 3U);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 10U,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 12U,
200UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 20U,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 22U,
(uint32_t)(int32_t)-200);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 30U,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 32U,
100UL);
SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S1_BASE, 0U);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xA700UL;
command.axis = 0U;
command.opcode = PLSR_CMD_SET_POSITION;
command.argument = 0;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xA701UL;
call.source.context = NULL;
call.source.validateWords = SelfTestValidateWords;
call.source.readWord = SelfTestReadWord;
call.source.readBit = SelfTestReadBit;
call.s0.device = PLSR_DEVICE_D;
call.s0.address = SELF_TEST_S0_BASE;
call.s1.device = PLSR_DEVICE_D;
call.s1.address = SELF_TEST_S1_BASE;
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = 0U;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
return PlsrPostCall(&call);
}

PLSR_RESULT PlsrDirectionLogicSelfTestQueue(void)
{
static const uint16_t s0Base[2] = {10U, 40U};
static const uint16_t s1Base[2] = {160U, 164U};
static const uint8_t directionPoint[2] = {4U, 3U};
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;
uint16_t commonBase;
uint16_t setBase;
uint8_t axis;

(void)memset(SelfTestWords, 0, sizeof(SelfTestWords));

for (axis = 0U; axis < 2U; axis++)
{
commonBase = (uint16_t)(900U
+ (uint16_t)axis
* SELF_TEST_SFD_AXIS_STRIDE);
setBase = (uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);

/* Axis 0 uses positive logic; axis 1 uses negative logic. */
(void)PlcDeviceWriteSfd(commonBase,
(axis == 0U) ? 0U : (1U << 1U));
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U),
directionPoint[axis]);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

SelfTestWriteSfdDword(setBase, 1000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 100000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 1000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);

SelfTestWriteDword(PLSR_DEVICE_D, s0Base[axis], 2U);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 10UL,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 12UL,
200UL);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 20UL,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 22UL,
(uint32_t)(int32_t)-200);
SelfTestWriteDword(PLSR_DEVICE_D, s1Base[axis], 0U);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xA800UL + axis;
command.axis = axis;
command.opcode = PLSR_CMD_SET_POSITION;
command.argument = 0;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xA810UL + axis;
call.source.context = NULL;
call.source.validateWords = SelfTestValidateWords;
call.source.readWord = SelfTestReadWord;
call.source.readBit = SelfTestReadBit;
call.s0.device = PLSR_DEVICE_D;
call.s0.address = s0Base[axis];
call.s1.device = PLSR_DEVICE_D;
call.s1.address = s1Base[axis];
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = axis;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
result = PlsrPostCall(&call);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}
}
return PLSR_RESULT_QUEUED;
}

PLSR_RESULT PlsrCwCcwSelfTestQueue(void)
{
const uint16_t commonBase = 900U;
const uint16_t setBase =
(uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;

(void)memset(SelfTestWords, 0, sizeof(SelfTestWords));
(void)PlcDeviceWriteSfd(commonBase, 0U);
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U),
SELF_TEST_DIR_POINT);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

SelfTestWriteSfdDword(setBase, 2000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 100000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 1000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);

SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE, 2U);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 10UL,
2000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 12UL,
300UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 20UL,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 22UL,
(uint32_t)(int32_t)-200);
SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S1_BASE, 0U);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xA900UL;
command.axis = 0U;
command.opcode = PLSR_CMD_SET_POSITION;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xA901UL;
call.source.context = NULL;
call.source.validateWords = SelfTestValidateWords;
call.source.readWord = SelfTestReadWord;
call.source.readBit = SelfTestReadBit;
call.s0.device = PLSR_DEVICE_D;
call.s0.address = SELF_TEST_S0_BASE;
call.s1.device = PLSR_DEVICE_D;
call.s1.address = SELF_TEST_S1_BASE;
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = 0U;
call.outputModeOverride = PLSR_OUTPUT_CW_CCW;
return PlsrPostCall(&call);
}

PLSR_RESULT PlsrFastRefreshSelfTestQueue(void)
{
static const uint16_t s0Base[2] = {10U, 40U};
static const uint16_t s1Base[2] = {160U, 164U};
static const uint8_t directionPoint[2] = {4U, 3U};
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;
uint16_t commonBase;
uint16_t setBase;
uint8_t axis;

(void)memset(SelfTestWords, 0, sizeof(SelfTestWords));
for (axis = 0U; axis < 2U; axis++)
{
commonBase = (uint16_t)(900U
+ (uint16_t)axis
* SELF_TEST_SFD_AXIS_STRIDE);
setBase = (uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);
(void)PlcDeviceWriteSfd(commonBase, 0U);
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U),
directionPoint[axis]);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

SelfTestWriteSfdDword(setBase, 5000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
/* Linear curve keeps the 1ms/0.1ms update granularity visible. */
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 100000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 100UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 100UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U),
(axis == 0U) ? 0U : 2U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);

SelfTestWriteDword(PLSR_DEVICE_D, s0Base[axis], 1U);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 10UL,
5000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
(uint32_t)s0Base[axis] + 12UL,
2000UL);
SelfTestWriteDword(PLSR_DEVICE_D, s1Base[axis], 0U);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xAA00UL + axis;
command.axis = axis;
command.opcode = PLSR_CMD_SET_POSITION;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xAA10UL + axis;
call.source.context = NULL;
call.source.validateWords = SelfTestValidateWords;
call.source.readWord = SelfTestReadWord;
call.source.readBit = SelfTestReadBit;
call.s0.device = PLSR_DEVICE_D;
call.s0.address = s0Base[axis];
call.s1.device = PLSR_DEVICE_D;
call.s1.address = s1Base[axis];
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = axis;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
result = PlsrPostCall(&call);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}
}
return PLSR_RESULT_QUEUED;
}

PLSR_RESULT PlsrDynamicFrequencySelfTestQueue(void)
{
const uint16_t commonBase = 900U;
const uint16_t setBase =
(uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;

(void)memset(SelfTestWords, 0, sizeof(SelfTestWords));
PlsrSelfTestLiveFrequencyHz = 1000;
PlsrSelfTestDynamicTick100us = 0UL;
PlsrSelfTestDynamicPhase = 0U;
PlsrSelfTestDynamicEnabled = 1U;
PlsrSetControlTickHook(PlsrSelfTestControlTick100us);
(void)PlcDeviceWriteSfd(commonBase, 0U);
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U), 4U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

/* 1000Hz default, 5000Hz maximum, 10Hz/ms slope, 0.1ms refresh. */
SelfTestWriteSfdDword(setBase, 1000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 5000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 1000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 2U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);

SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE, 1U);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 10UL,
1000UL);
SelfTestWriteDword(PLSR_DEVICE_D,
SELF_TEST_S0_BASE + 12UL,
100000UL);
SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S1_BASE, 0U);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xAB00UL;
command.axis = 0U;
command.opcode = PLSR_CMD_SET_POSITION;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xAB01UL;
call.source.context = NULL;
call.source.validateWords = SelfTestValidateWords;
call.source.readWord = SelfTestReadWord;
call.source.readDword = SelfTestReadDwordLive;
call.source.readBit = SelfTestReadBit;
call.s0.device = PLSR_DEVICE_D;
call.s0.address = SELF_TEST_S0_BASE;
call.s1.device = PLSR_DEVICE_D;
call.s1.address = SELF_TEST_S1_BASE;
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = 0U;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
return PlsrPostCall(&call);
}

PLSR_RESULT PlsrModbusDataSelfTestQueue(void)
{
const uint16_t commonBase = 900U;
const uint16_t setBase =
(uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);
uint16_t s0Words[20] = {0U};
uint16_t s1Words[4] = {0U};
PLSR_CALL call;
PLSR_COMMAND command;
PLSR_RESULT result;

/* P12 uses D1000 as S0 and D1100 as S1. D1010/D1011 is the live
* current-segment frequency written atomically by Modbus function 0x10. */
s0Words[0] = 1U;
s0Words[10] = 1000U;
s0Words[11] = 0U;
s0Words[12] = (uint16_t)(100000UL & 0xFFFFUL);
s0Words[13] = (uint16_t)(100000UL >> 16U);
if ((ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
SELF_TEST_MODBUS_S0_BASE,
s0Words,
20UL) == 0U)
|| (ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
SELF_TEST_MODBUS_S1_BASE,
s1Words,
4UL) == 0U))
{
return PLSR_RESULT_DATA_ACCESS;
}

(void)PlcDeviceWriteSfd(commonBase, 0U);
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U), 4U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

/* Same limits as P11: 1000Hz default, 5000Hz maximum, 10Hz/ms ramp,
* and a 0.1ms live-frequency refresh. */
SelfTestWriteSfdDword(setBase, 1000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 5000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 1000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 2U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);

(void)memset(&command, 0, sizeof(command));
command.sequence = 0xAC00UL;
command.axis = 0U;
command.opcode = PLSR_CMD_SET_POSITION;
result = PlsrPostCommand(&command);
if (result != PLSR_RESULT_QUEUED)
{
return result;
}

(void)memset(&call, 0, sizeof(call));
call.sequence = 0xAC01UL;
PlsrModbusDataSourceInit(&call.source);
call.s0.device = PLSR_DEVICE_D;
call.s0.address = SELF_TEST_MODBUS_S0_BASE;
call.s1.device = PLSR_DEVICE_D;
call.s1.address = SELF_TEST_MODBUS_S1_BASE;
call.s2.type = PLSR_OPERAND_CONSTANT;
call.s2.constant = 1;
call.dAxis = 0U;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
return PlsrPostCall(&call);
}

PLSR_RESULT PlsrModbusControlSelfTestPrepare(void)
{
const uint16_t commonBase = 900U;
const uint16_t setBase =
(uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET);

(void)PlcDeviceWriteSfd(commonBase, 0U);
SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL);
SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U), 4U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 8U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 9U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU);

/* K1: 1000Hz default/start, 5000Hz maximum, 100ms ramps, 1ms refresh. */
SelfTestWriteSfdDword(setBase, 1000UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 100U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 5000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 8U), 1000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U);
(void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 0U);
SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL);
SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL);
return PLSR_RESULT_OK;
}

+ 12
- 0
PLSR/Test/run_host_tests.ps1 查看文件

@@ -96,8 +96,19 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_hal_f407.c"
"$workspacePath\PLSR\Src\plsr_core.c"
"$workspacePath\PLSR\Src\plsr_self_test.c"
"$workspacePath\PLSR\Src\plsr_modbus_data.c"
"$workspacePath\PLSR\Src\plsr_modbus_control.c"
"$workspacePath\Modbus\Src\modbus_data_store.c"
"$workspacePath\PLSR\Test\test_plsr_hal.c"
)
},
@{
Name = 'test_plsr_modbus_data'
Sources = @(
"$workspacePath\Modbus\Src\modbus_data_store.c"
"$workspacePath\PLSR\Src\plsr_modbus_data.c"
"$workspacePath\PLSR\Test\test_plsr_modbus_data.c"
)
}
)

@@ -111,6 +122,7 @@ foreach ($test in $tests)
'-Werror'
'-DPLSR_HOST_TEST'
"-I$workspacePath\PLSR\Inc"
"-I$workspacePath\Modbus\Inc"
) + $test.Sources + @('-o', $outputPath, '-lm')

try


+ 917
- 1
PLSR/Test/test_plsr_hal.c 查看文件

@@ -1,7 +1,9 @@
#include "plc_device.h"
#include "modbus_data_store.h"
#include "plsr_core.h"
#include "plsr_hal_f407.h"
#include "plsr_job.h"
#include "plsr_modbus_control.h"
#include "plsr_persistence.h"
#include "plsr_resource.h"
#include "plsr_self_test.h"
@@ -254,6 +256,267 @@ static void TestDirDelaySequence(void)
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
CHECK(PlsrHwTestGetDirLevel(0U) == 0U);

/* Bit1 negative logic reverses only the electrical DIR terminal. */
params.directionNegativeLogic = 1U;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
params.directionPositive = 1U;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwTestGetDirLevel(0U) == 0U);
}

static void TestDirectionBatch(void)
{
PLSR_HW_START_PARAMS params;

(void)PlsrHwInit();
(void)memset(&params, 0, sizeof(params));
params.frequencyHz = 0UL;
params.targetPulses = 10;
params.outputMode = PLSR_OUTPUT_PULSE_DIR;
params.directionPoint = 4U;
params.directionPositive = 1U;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
params.directionPoint = 3U;
params.directionNegativeLogic = 1U;
CHECK(PlsrHwStartPulse(1U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
CHECK(PlsrHwTestGetDirLevel(1U) == 0U);

PlsrHwBeginDirectionBatch();
params.directionPoint = 4U;
params.directionPositive = 0U;
params.directionNegativeLogic = 0U;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
params.directionPoint = 3U;
params.directionNegativeLogic = 1U;
CHECK(PlsrHwStartPulse(1U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
CHECK(PlsrHwTestGetDirLevel(1U) == 0U);
PlsrHwEndDirectionBatch();
CHECK(PlsrHwTestGetDirLevel(0U) == 0U);
CHECK(PlsrHwTestGetDirLevel(1U) == 1U);
}

static void TestCwCcwSequence(void)
{
PLSR_HW_START_PARAMS params;
uint16_t psc;
uint16_t arr;

(void)PlsrHwInit();
(void)memset(&params, 0, sizeof(params));
params.frequencyHz = 2000UL;
params.targetPulses = 3;
params.outputMode = PLSR_OUTPUT_CW_CCW;
params.directionPoint = PLSR_HW_DIR_POINT_NONE;
params.directionPositive = 1U;
params.directionDelayMs = 10U;

CHECK(PlsrHwStartPulse(1U, &params) == PLSR_RESULT_INVALID_AXIS);
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U);
PlsrHwTestTriggerCompare(1U);
CHECK(PlsrHwGetEmittedPulses(0U) == 0);
PlsrHwTestTriggerCompare(0U);
PlsrHwTestTriggerCompare(0U);
PlsrHwTestTriggerCompare(0U);
CHECK(PlsrHwGetEmittedPulses(0U) == 3);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
PlsrHwTestTriggerUpdate(0U);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U);

params.frequencyHz = 1000UL;
params.targetPulses = 2;
params.directionPositive = 0U;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_OK);
CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U);
CHECK(PlsrCalculateTimerDivider(84000000UL, 1000UL, &psc, &arr)
== PLSR_RESULT_OK);
CHECK(PlsrHwTestGetPsc(1U) == psc);
CHECK(PlsrHwTestGetArr(1U) == arr);
PlsrHwTestTriggerCompare(1U);
PlsrHwTestTriggerCompare(1U);
CHECK(PlsrHwGetEmittedPulses(0U) == 2);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U);
PlsrHwTestTriggerUpdate(1U);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U);
}

static void TestFastRefreshControlTick(void)
{
TEST_MEMORY memory;
PLSR_CALL call;
PLSR_STATUS status;
int tick;

TestResetEnvironment();
(void)memset(&memory, 0, sizeof(memory));
CHECK(PlcDeviceWriteSfd(900U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(902U, 1UL);
TestWriteSfdDword(904U, 1UL);
CHECK(PlcDeviceWriteSfd(906U, 4U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(950U, 1000UL);
CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(954U, 0U) == PLC_DEVICE_OK);
/* Linear curve: 10Hz/ms becomes exactly 1Hz per 0.1ms tick. */
CHECK(PlcDeviceWriteSfd(955U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(956U, 100000UL);
TestWriteSfdDword(958U, 0UL);
TestWriteSfdDword(960U, 0UL);
CHECK(PlcDeviceWriteSfd(962U, 50U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(963U, 0U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(964U, 2U) == PLC_DEVICE_OK);
TestWriteSfdDword(966U, 2000UL);
TestWriteSfdDword(968U, 200UL);

TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1);
TestSetSegment(&memory, 1U, 1000U, 10000);
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 0);
call = TestMakeCall(&memory);
call.sequence = 0xB000UL;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED);
PlsrProcess();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_ACCEL);
CHECK(status.jobValid != 0U);
CHECK(PlsrTestGetJobRefreshCode(0U) == 2U);
CHECK(PlsrTestGetProfileRefreshHz(0U) == 10000UL);
CHECK(PlsrTestGetProfileActive(0U) != 0U);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 0UL);

/* A normal 1ms process pass must not advance a 0.1ms profile. */
PlsrProcess();
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 0UL);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
CHECK(PlsrTestGetJobRefreshCode(0U) == 2U);
CHECK(PlsrTestGetProfileActive(0U) != 0U);
PlsrControlTick100us();
CHECK(PlsrTestGetProfileFrequencyHz(0U) == 1UL);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
CHECK(PlsrHwGetCurrentFrequencyHz(0U) > 0UL);
for (tick = 1; tick < 10; tick++)
{
PlsrControlTick100us();
}
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 10UL);
PlsrProcess();
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 10UL);
for (tick = 0; tick < 10; tick++)
{
PlsrControlTick100us();
}
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 20UL);
}

static void TestDynamicFrequencyRetarget(void)
{
TEST_MEMORY memory;
PLSR_CALL call;
PLSR_STATUS status;

TestResetEnvironment();
(void)memset(&memory, 0, sizeof(memory));
CHECK(PlcDeviceWriteSfd(900U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(902U, 1UL);
TestWriteSfdDword(904U, 1UL);
CHECK(PlcDeviceWriteSfd(906U, 4U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(950U, 1000UL);
CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(954U, 0U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(955U, 0U) == PLC_DEVICE_OK);
TestWriteSfdDword(956U, 5000UL);
TestWriteSfdDword(958U, 1000UL);
TestWriteSfdDword(960U, 0UL);
CHECK(PlcDeviceWriteSfd(962U, 50U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(963U, 0U) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(964U, 2U) == PLC_DEVICE_OK);
TestWriteSfdDword(966U, 2000UL);
TestWriteSfdDword(968U, 200UL);

TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1);
TestSetSegment(&memory, 1U, 1000U, 100000);
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 0);
call = TestMakeCall(&memory);
call.sequence = 0xB100UL;
call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR;
CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED);
PlsrProcess();
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentFrequencyHz == 1000UL);
CHECK(status.targetFrequencyHz == 1000UL);

/* Only the 100us control tick may observe/apply a refreshCode=2 edit. */
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 4000);
PlsrProcess();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 1000UL);
CHECK(status.currentFrequencyHz == 1000UL);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 4000UL);
CHECK(status.currentFrequencyHz == 1001UL);
PlsrControlTick100us();
CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 1002UL);

/* Down-retarget follows the configured slope instead of jumping. */
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 500);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 500UL);
CHECK(status.currentFrequencyHz == 1001UL);

/* Raw zero means the immutable S2 default speed. */
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 0);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 1000UL);
CHECK(status.currentFrequencyHz == 1000UL);

/* Above-maximum values clamp; invalid negatives retain the last safe
* target and produce one sticky rejection for that observed value. */
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 8000);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 5000UL);
CHECK(status.currentFrequencyHz == 1001UL);
CHECK(status.speedClamped != 0U);
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, -1);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 5000UL);
CHECK(status.currentFrequencyHz == 1002UL);
CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_INVALID_FREQUENCY);
CHECK(status.liveFrequencyRejectCount == 1UL);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.liveFrequencyRejectCount == 1UL);
TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 2000);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 2000UL);
CHECK(status.currentFrequencyHz == 1004UL);
CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_OK);
}

static void TestZeroFrequencyWaits(void)
@@ -562,7 +825,7 @@ static void TestStopAndInvalidArgs(void)
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_INVALID_ARGUMENT);
params.targetPulses = 1;
params.outputMode = PLSR_OUTPUT_CW_CCW;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_NOT_SUPPORTED);
CHECK(PlsrHwStartPulse(1U, &params) == PLSR_RESULT_INVALID_AXIS);
params.outputMode = (PLSR_OUTPUT_MODE)99;
CHECK(PlsrHwStartPulse(0U, &params) == PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT);
@@ -1332,6 +1595,647 @@ static void TestFourAxisSelfTest(void)
CHECK(PlsrResourceCheckInvariant() != 0U);
}

static void TestBacklashSelfTest(void)
{
PLC_DEVICE_EVENT_RECORD eventRecord;
PLSR_STATUS status;
int32_t hsdPosition;
int ticks;

TestResetEnvironment();
CHECK(PlsrBacklashSelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
for (ticks = 0; ticks < 10; ticks++)
{
PlsrProcess();
}

/* Segment 1: +200 user pulses, with no compensation on first motion. */
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
for (ticks = 0; ticks < 200; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
status = TestGetStatus();
CHECK(status.currentSegment == 2U);
CHECK(status.backlashActive != 0U);
CHECK(status.directionPositive == 0U);
CHECK(status.logicalPosition == 200);
CHECK(status.taskPulses == 200);
CHECK(status.totalPulses == 200);
CHECK(status.physicalPulses == 200UL);
CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 0UL);

/* Direction change to negative: 20 physical compensation pulses first. */
for (ticks = 0; ticks < 10; ticks++)
{
PlsrProcess();
}
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
for (ticks = 0; ticks < 20; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
status = TestGetStatus();
CHECK(status.backlashActive == 0U);
CHECK(status.currentSegment == 2U);
CHECK(status.logicalPosition == 200);
CHECK(status.taskPulses == 200);
CHECK(status.totalPulses == 200);
CHECK(status.physicalPulses == 220UL);
CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 0UL);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);

/* The real segment 2 contributes -200 to user position/counting. */
for (ticks = 0; ticks < 200; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
status = TestGetStatus();
CHECK(status.currentSegment == 3U);
CHECK(status.backlashActive != 0U);
CHECK(status.directionPositive != 0U);
CHECK(status.logicalPosition == 0);
CHECK(status.taskPulses == 0);
CHECK(status.totalPulses == 400);
CHECK(status.physicalPulses == 420UL);
CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 0UL);

/* Direction change back to positive: 10 compensation, then +100 user. */
for (ticks = 0; ticks < 10; ticks++)
{
PlsrProcess();
}
for (ticks = 0; ticks < 10; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
status = TestGetStatus();
CHECK(status.backlashActive == 0U);
CHECK(status.logicalPosition == 0);
CHECK(status.totalPulses == 400);
CHECK(status.physicalPulses == 430UL);
CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 0UL);

for (ticks = 0; ticks < 100; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
status = TestGetStatus();
CHECK(status.state == PLSR_STATE_COMPLETED);
CHECK(status.done != 0U);
CHECK(status.logicalPosition == 100);
CHECK(status.taskPulses == 100);
CHECK(status.totalPulses == 500);
CHECK(status.physicalPulses == 530UL);
CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK);
CHECK(hsdPosition == 100);
CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
CHECK(eventRecord.lastReason == PLSR_STOP_REASON_NORMAL_COMPLETE);
}

static void TestDirectionLogicSelfTest(void)
{
static const uint8_t expectedDirectionPoint[2] = {4U, 3U};
PLC_DEVICE_EVENT_RECORD eventRecord;
PLSR_STATUS status;
uint8_t smDirection;
uint8_t axis;
int ticks;

TestResetEnvironment();
CHECK(PlsrDirectionLogicSelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
for (ticks = 0; ticks < 10; ticks++)
{
PlsrProcess();
}

/* Both axes move logically positive. Electrical DIR terminals are
* complementary because axis 1 has SFD1030 Bit1 set. */
CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
CHECK(PlsrHwTestGetDirLevel(1U) == 0U);
for (axis = 0U; axis < 2U; axis++)
{
CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_RUN);
CHECK(status.directionPoint == expectedDirectionPoint[axis]);
CHECK(status.directionPositive != 0U);
CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING);
}

for (ticks = 0; ticks < 200; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
PlsrHwTestTriggerUpdate(1U);
}
PlsrProcess();

/* Both reverse logically; only the physical terminal mapping differs. */
CHECK(PlsrHwTestGetDirLevel(0U) == 0U);
CHECK(PlsrHwTestGetDirLevel(1U) == 1U);
for (axis = 0U; axis < 2U; axis++)
{
CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK);
CHECK(status.currentSegment == 2U);
CHECK(status.directionPositive == 0U);
CHECK(status.logicalPosition == 200);
CHECK(status.totalPulses == 200);
CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_DIR_SETTLING);
}

for (ticks = 0; ticks < 10; ticks++)
{
PlsrProcess();
}
for (ticks = 0; ticks < 200; ticks++)
{
PlsrHwTestTriggerUpdate(0U);
PlsrHwTestTriggerUpdate(1U);
}
PlsrProcess();

for (axis = 0U; axis < 2U; axis++)
{
CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_COMPLETED);
CHECK(status.done != 0U);
CHECK(status.directionPositive == 0U);
CHECK(status.logicalPosition == 0);
CHECK(status.taskPulses == 0);
CHECK(status.totalPulses == 400);
CHECK(status.physicalPulses == 400UL);
CHECK(PlcDeviceReadSm((uint16_t)(1001U
+ (uint16_t)axis * 20U),
&smDirection) == PLC_DEVICE_OK);
CHECK(smDirection == 0U);
CHECK(PlcDeviceReadEvent((uint16_t)(6000U
+ (uint16_t)axis * 100U),
&eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
CHECK(PlcDeviceReadEvent((uint16_t)(6001U
+ (uint16_t)axis * 100U),
&eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
}
CHECK(PlsrHwTestGetDirLevel(0U) == 0U);
CHECK(PlsrHwTestGetDirLevel(1U) == 1U);
}

static void TestCwCcwSelfTest(void)
{
PLC_DEVICE_EVENT_RECORD eventRecord;
PLSR_STATUS status;
int pulse;

TestResetEnvironment();
CHECK(PlsrCwCcwSelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_RUN);
CHECK(status.outputMode == PLSR_OUTPUT_CW_CCW);
CHECK(status.directionPositive != 0U);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U);

for (pulse = 0; pulse < 300; pulse++)
{
PlsrHwTestTriggerCompare(0U);
}
PlsrHwTestTriggerUpdate(0U);
PlsrProcess();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentSegment == 2U);
CHECK(status.directionPositive == 0U);
CHECK(status.logicalPosition == 300);
CHECK(status.totalPulses == 300);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U);

for (pulse = 0; pulse < 200; pulse++)
{
PlsrHwTestTriggerCompare(1U);
}
PlsrHwTestTriggerUpdate(1U);
PlsrProcess();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_COMPLETED);
CHECK(status.done != 0U);
CHECK(status.logicalPosition == 100);
CHECK(status.taskPulses == 100);
CHECK(status.totalPulses == 500);
CHECK(status.physicalPulses == 500UL);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U);
CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK);
CHECK(eventRecord.count == 1UL);
}

static void TestFastRefreshSelfTest(void)
{
PLSR_STATUS status;
uint32_t beforeFastHz;
uint16_t refreshCode;
int tick;

TestResetEnvironment();
CHECK(PlsrFastRefreshSelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
for (tick = 0; tick < 10; tick++)
{
PlsrProcess();
}
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_ACCEL);
CHECK(status.directionPoint == 4U);
CHECK(PlsrHwGetCurrentFrequencyHz(0U) > 0UL);
CHECK(PlsrGetStatus(1U, &status) == PLSR_RESULT_OK);
CHECK(status.state == PLSR_STATE_ACCEL);
CHECK(status.directionPoint == 3U);
CHECK(PlsrTestGetJobRefreshCode(1U) == 2U);
CHECK(PlsrTestGetProfileRefreshHz(1U) == 10000UL);
beforeFastHz = PlsrTestGetProfileFrequencyHz(1U);
PlsrControlTick100us();
CHECK(PlsrTestGetProfileFrequencyHz(1U) > beforeFastHz);
CHECK(PlcDeviceReadSfd(964U, &refreshCode) == PLC_DEVICE_OK);
CHECK(refreshCode == 0U);
CHECK(PlcDeviceReadSfd(1094U, &refreshCode) == PLC_DEVICE_OK);
CHECK(refreshCode == 2U);
}

static void TestDynamicFrequencySelfTest(void)
{
PLSR_STATUS status;
int tick;

TestResetEnvironment();
CHECK(PlsrDynamicFrequencySelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
for (tick = 0; tick < 10; tick++)
{
PlsrProcess();
}
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentFrequencyHz == 1000UL);
CHECK(status.targetFrequencyHz == 1000UL);

PlsrSelfTestLiveFrequencyHz = 4000;
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentFrequencyHz == 1001UL);
CHECK(status.targetFrequencyHz == 4000UL);

PlsrSelfTestLiveFrequencyHz = 500;
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentFrequencyHz == 1000UL);
CHECK(status.targetFrequencyHz == 500UL);
CHECK(status.liveFrequencyRejectCount == 0UL);
}

static void TestDynamicFrequencySchedule(void)
{
int tick;

TestResetEnvironment();
CHECK(PlsrDynamicFrequencySelfTestQueue() == PLSR_RESULT_QUEUED);
CHECK(PlsrSelfTestLiveFrequencyHz == 1000);
CHECK(PlsrSelfTestDynamicPhase == 0U);
for (tick = 0; tick < 9999; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == 1000);
PlsrSelfTestControlTick100us();
CHECK(PlsrSelfTestLiveFrequencyHz == 4000);
CHECK(PlsrSelfTestDynamicPhase == 1U);
for (tick = 0; tick < 5000; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == 500);
CHECK(PlsrSelfTestDynamicPhase == 2U);
for (tick = 0; tick < 5000; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == 0);
CHECK(PlsrSelfTestDynamicPhase == 3U);
for (tick = 0; tick < 2000; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == 8000);
CHECK(PlsrSelfTestDynamicPhase == 4U);
for (tick = 0; tick < 5000; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == -1);
CHECK(PlsrSelfTestDynamicPhase == 5U);
for (tick = 0; tick < 2000; tick++)
{
PlsrSelfTestControlTick100us();
}
CHECK(PlsrSelfTestLiveFrequencyHz == 2000);
CHECK(PlsrSelfTestDynamicPhase == 6U);
CHECK(PlsrSelfTestDynamicTick100us == 29000UL);
PlsrSelfTestControlTick100us();
CHECK(PlsrSelfTestDynamicTick100us == 29000UL);
}

static void TestModbusDataSelfTest(void)
{
PLSR_STATUS status;
uint16_t frequencyWords[2];
uint16_t word;
int tick;

TestResetEnvironment();
CHECK(PlsrModbusDataSelfTestQueue() == PLSR_RESULT_QUEUED);
PlsrProcess();
for (tick = 0; tick < 10; tick++)
{
PlsrProcess();
}
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, 1000UL, &word) == 1U);
CHECK(word == 1U);
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK((status.state == PLSR_STATE_ACCEL) || (status.state == PLSR_STATE_RUN));
CHECK(PlsrHwIsPulseActive(0U) == 1U);
CHECK(status.currentFrequencyHz == 1000UL);
CHECK(status.targetFrequencyHz == 1000UL);

frequencyWords[0] = 4000U;
frequencyWords[1] = 0U;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
1010UL,
frequencyWords,
2UL) == 1U);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.currentFrequencyHz == 1001UL);
CHECK(status.targetFrequencyHz == 4000UL);
CHECK(status.liveFrequencyRejectCount == 0UL);

frequencyWords[0] = 0xFFFFU;
frequencyWords[1] = 0xFFFFU;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
1010UL,
frequencyWords,
2UL) == 1U);
PlsrControlTick100us();
CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
CHECK(status.targetFrequencyHz == 4000UL);
CHECK(status.liveFrequencyRejectCount == 1UL);
CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_INVALID_FREQUENCY);
}

static void TestModbusControlProtocol(void)
{
const uint32_t controlBase = 1200UL;
const uint32_t s0Base = 1600UL;
const uint32_t s1Base = 1700UL;
uint16_t s0Words[20] = {0U};
uint16_t s1Words[4] = {0U};
uint16_t callRequest[16] = {0U};
uint16_t callResponse[12];
uint16_t commandRequest[8] = {0U};
uint16_t commandResponse[8];
uint16_t axisStatus[48];
uint16_t pulseWords[2];
uint32_t generationBegin;
uint32_t generationEnd;
PLSR_STATUS coreStatus;
int64_t pausedPulses;
int tick;

TestResetEnvironment();
CHECK(PlsrModbusControlSelfTestPrepare() == PLSR_RESULT_OK);
CHECK(PlsrModbusControlInit((uint16_t)controlBase) == PLSR_RESULT_OK);
CHECK(PlsrModbusControlIsEnabled() == 1U);
CHECK(PlsrModbusControlGetBaseAddress() == controlBase);

s0Words[0] = 1U;
s0Words[10] = 2000U;
s0Words[12] = (uint16_t)(50000UL & 0xFFFFUL);
s0Words[13] = (uint16_t)(50000UL >> 16U);
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
s0Base,
s0Words,
20UL) == 1U);
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
s1Base,
s1Words,
4UL) == 1U);

callRequest[0] = 1U;
callRequest[2] = PLSR_DEVICE_D;
callRequest[3] = (uint16_t)s0Base;
callRequest[5] = PLSR_DEVICE_D;
callRequest[6] = (uint16_t)s1Base;
callRequest[8] = PLSR_OPERAND_CONSTANT;
callRequest[10] = 1U;
callRequest[12] = 0U;
callRequest[13] = PLSR_OUTPUT_PULSE_DIR;
callRequest[14] = PLSR_MODBUS_CALL_COMMIT;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET,
callRequest,
16UL) == 1U);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET,
&callResponse[0]) == 1U);
for (tick = 1; tick < 12; tick++)
{
CHECK(ModbusDataReadWord(
MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET
+ (uint32_t)tick,
&callResponse[tick]) == 1U);
}
CHECK(callResponse[0] == 1U);
CHECK(callResponse[2] == PLSR_MODBUS_CALL_COMMIT);
CHECK(callResponse[3] == PLSR_RESULT_OK);
CHECK(callResponse[11] == 1U);

/* Any S0 edit after COMMIT invalidates START until a new COMMIT. */
pulseWords[0] = (uint16_t)(50001UL & 0xFFFFUL);
pulseWords[1] = (uint16_t)(50001UL >> 16U);
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
s0Base + 12UL,
pulseWords,
2UL) == 1U);
callRequest[0] = 2U;
callRequest[14] = PLSR_MODBUS_CALL_START;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET,
callRequest,
16UL) == 1U);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET
+ 3UL,
&callResponse[3]) == 1U);
CHECK(callResponse[3] == PLSR_RESULT_BUSY);

pulseWords[0] = (uint16_t)(50000UL & 0xFFFFUL);
pulseWords[1] = (uint16_t)(50000UL >> 16U);
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
s0Base + 12UL,
pulseWords,
2UL) == 1U);
callRequest[0] = 3U;
callRequest[14] = PLSR_MODBUS_CALL_COMMIT;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET,
callRequest,
16UL) == 1U);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET
+ 3UL,
&callResponse[3]) == 1U);
CHECK(callResponse[3] == PLSR_RESULT_OK);

callRequest[0] = 4U;
callRequest[14] = PLSR_MODBUS_CALL_START;
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET,
callRequest,
16UL) == 1U);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET
+ 3UL,
&callResponse[3]) == 1U);
CHECK(callResponse[3] == PLSR_RESULT_QUEUED);
for (tick = 0; tick < 10; tick++)
{
PlsrProcess();
}
PlsrModbusControlPoll();
for (tick = 0; tick < 48; tick++)
{
CHECK(ModbusDataReadWord(
MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET
+ (uint32_t)tick,
&axisStatus[tick]) == 1U);
}
generationBegin = (uint32_t)axisStatus[0]
| ((uint32_t)axisStatus[1] << 16U);
generationEnd = (uint32_t)axisStatus[46]
| ((uint32_t)axisStatus[47] << 16U);
CHECK(generationBegin == generationEnd);
CHECK((generationBegin & 1UL) == 0UL);
CHECK((axisStatus[2] == PLSR_STATE_ACCEL)
|| (axisStatus[2] == PLSR_STATE_RUN));
CHECK(axisStatus[10] == 4U);
CHECK(axisStatus[11] == 0U);
CHECK(axisStatus[8] == PLSR_RESULT_OK);

commandRequest[0] = 10U;
commandRequest[2] = PLSR_CMD_PAUSE;
commandRequest[3] = 0U;
CHECK(ModbusDataWriteWords(
MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET,
commandRequest,
8UL) == 1U);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_COMMAND_RESPONSE_OFFSET
+ 4UL,
&commandResponse[4]) == 1U);
CHECK(commandResponse[4] == PLSR_RESULT_QUEUED);
for (tick = 0; tick < 400; tick++)
{
PlsrProcess();
CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK);
if (coreStatus.state == PLSR_STATE_PAUSED)
{
break;
}
}
CHECK(coreStatus.state == PLSR_STATE_PAUSED);
CHECK(coreStatus.currentFrequencyHz == 0UL);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
pausedPulses = coreStatus.taskPulses;
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET + 2UL,
&axisStatus[2]) == 1U);
CHECK(axisStatus[2] == PLSR_STATE_PAUSED);

/* Polling an unchanged request sequence must not execute PAUSE twice. */
PlsrModbusControlPoll();
commandRequest[0] = 11U;
commandRequest[2] = PLSR_CMD_RESUME;
CHECK(ModbusDataWriteWords(
MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET,
commandRequest,
8UL) == 1U);
PlsrModbusControlPoll();
PlsrProcess();
CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK);
CHECK(coreStatus.currentFrequencyHz > 0UL);
CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
for (tick = 0; tick < 10; tick++)
{
PlsrHwTestTriggerUpdate(0U);
}
PlsrProcess();
CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK);
CHECK(coreStatus.taskPulses == pausedPulses + 10);
PlsrModbusControlPoll();
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET + 2UL,
&axisStatus[2]) == 1U);
CHECK((axisStatus[2] == PLSR_STATE_ACCEL)
|| (axisStatus[2] == PLSR_STATE_RUN));

commandRequest[0] = 12U;
commandRequest[2] = PLSR_CMD_STOP_DECEL;
CHECK(ModbusDataWriteWords(
MODBUS_DATA_DEVICE_D,
controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET,
commandRequest,
8UL) == 1U);
PlsrModbusControlPoll();
for (tick = 0; tick < 400; tick++)
{
PlsrProcess();
CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK);
if (coreStatus.state == PLSR_STATE_STOPPED)
{
break;
}
}
CHECK(coreStatus.state == PLSR_STATE_STOPPED);
CHECK(coreStatus.currentFrequencyHz == 0UL);
CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE);
}

static void TestStopStopsHardware(void)
{
TEST_MEMORY memory;
@@ -1376,6 +2280,10 @@ int main(void)
{
TestMapping();
TestDirDelaySequence();
TestDirectionBatch();
TestCwCcwSequence();
TestFastRefreshControlTick();
TestDynamicFrequencyRetarget();
TestZeroFrequencyWaits();
TestPulseCounting();
TestAbPhaseAndCounting();
@@ -1394,6 +2302,14 @@ int main(void)
TestEquivalentSelfTest();
TestProtectionSelfTest();
TestFourAxisSelfTest();
TestBacklashSelfTest();
TestDirectionLogicSelfTest();
TestCwCcwSelfTest();
TestFastRefreshSelfTest();
TestDynamicFrequencySelfTest();
TestDynamicFrequencySchedule();
TestModbusDataSelfTest();
TestModbusControlProtocol();
TestStopStopsHardware();

if (TestFailures != 0)


+ 12
- 0
PLSR/Test/test_plsr_job.c 查看文件

@@ -102,6 +102,8 @@ static void TestConfigureAxis0K1(void)
CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(907U, 10) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(908U, 17) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(909U, 23) == PLC_DEVICE_OK);
TestWriteSfdDword(950U, 1000UL);
CHECK(PlcDeviceWriteSfd(952U, 100) == PLC_DEVICE_OK);
CHECK(PlcDeviceWriteSfd(953U, 120) == PLC_DEVICE_OK);
@@ -184,6 +186,9 @@ static void TestValidSnapshotAndLiveFrequency(void)
CHECK(snapshot.s2Set == 1U);
CHECK(snapshot.outputMode == PLSR_OUTPUT_PULSE_DIR);
CHECK(snapshot.directionPoint == 4U);
CHECK(snapshot.directionNegativeLogic == 0U);
CHECK(snapshot.positiveBacklashPulses == 17U);
CHECK(snapshot.negativeBacklashPulses == 23U);
CHECK(snapshot.s2.defaultSpeed == 1000UL);
CHECK(snapshot.s2.maximumSpeed == 100000UL);
CHECK(snapshot.s2.startSpeed == 100000UL);
@@ -198,6 +203,13 @@ static void TestValidSnapshotAndLiveFrequency(void)
CHECK(snapshot.hasSelfLoop == 1U);
CHECK(snapshot.initialDirectionPositive == 1U);

CHECK(PlcDeviceWriteSfd(900U, (1U << 1U)) == PLC_DEVICE_OK);
CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail)
== PLSR_RESULT_OK);
CHECK(snapshot.directionNegativeLogic == 1U);
CHECK(snapshot.initialDirectionPositive == 1U);
CHECK(PlcDeviceWriteSfd(900U, 0U) == PLC_DEVICE_OK);

TestWriteDword(&memory, PLSR_DEVICE_D, 112U, 9999);
CHECK(snapshot.segments[0].pulseOrTarget == 100);
TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 2500);


+ 84
- 0
PLSR/Test/test_plsr_modbus_data.c 查看文件

@@ -0,0 +1,84 @@
#include "modbus_data_store.h"
#include "plsr_modbus_data.h"
#include <stdint.h>
#include <stdio.h>

static unsigned TestChecks;

#define CHECK(condition) \
do \
{ \
TestChecks++; \
if (!(condition)) \
{ \
(void)printf("FAIL line %d: %s\n", __LINE__, #condition); \
return 1; \
} \
} while (0)

int main(void)
{
PLSR_DATA_SOURCE source;
uint16_t words[4] = {0x5678U, 0x1234U, 0xFFFEU, 0xFFFFU};
uint16_t word;
int32_t dword;
uint32_t sequence;

CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_D, 0UL, 1UL) == 1U);
CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_HD, 9998UL, 2UL) == 1U);
CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_FD, 9999UL, 2UL) == 0U);
CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_D, 0UL, 0UL) == 0U);
CHECK(ModbusDataValidateWords((MODBUS_DATA_DEVICE)3, 0UL, 1UL) == 0U);

sequence = ModbusDataGetWriteSequence();
CHECK((sequence & 1UL) == 0UL);
CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, 1000UL, words, 4UL)
== 1U);
CHECK(ModbusDataGetWriteSequence() == sequence + 2UL);
CHECK(ModbusDataReadDword(MODBUS_DATA_DEVICE_D, 1000UL, &dword) == 1U);
CHECK(dword == (int32_t)0x12345678UL);
CHECK(ModbusDataReadDword(MODBUS_DATA_DEVICE_D, 1002UL, &dword) == 1U);
CHECK(dword == -2);

CHECK(ModbusDataWriteWord(MODBUS_DATA_DEVICE_HD, 5UL, 0xA55AU) == 1U);
CHECK(ModbusDataWriteWord(MODBUS_DATA_DEVICE_FD, 5UL, 0x5AA5U) == 1U);
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, 5UL, &word) == 1U);
CHECK(word == 0U);
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_HD, 5UL, &word) == 1U);
CHECK(word == 0xA55AU);
CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_FD, 5UL, &word) == 1U);
CHECK(word == 0x5AA5U);

CHECK(ModbusDataReadLinear(10005UL, &word) == 1U);
CHECK(word == 0xA55AU);
CHECK(ModbusDataReadLinear(40005UL, &word) == 1U);
CHECK(word == 0x5AA5U);
CHECK(ModbusDataReadLinear(20000UL, &word) == 0U);
CHECK(ModbusDataReadLinear(39999UL, &word) == 0U);
CHECK(ModbusDataReadLinear(69999UL, &word) == 0U);

PlsrModbusDataSourceInit(&source);
CHECK(source.context == NULL);
CHECK(source.validateWords != NULL);
CHECK(source.readWord != NULL);
CHECK(source.readDword != NULL);
CHECK(source.readBit == NULL);
CHECK(source.validateWords(source.context, PLSR_DEVICE_D, 1000UL, 4UL)
== 1U);
CHECK(source.validateWords(source.context, PLSR_DEVICE_X, 0UL, 1UL)
== 0U);
CHECK(source.readDword(source.context,
PLSR_DEVICE_D,
1000UL,
&dword) == 1U);
CHECK(dword == (int32_t)0x12345678UL);
CHECK(source.readWord(source.context, PLSR_DEVICE_HD, 5UL, &word) == 1U);
CHECK(word == 0xA55AU);
CHECK(source.readWord(source.context, PLSR_DEVICE_FD, 5UL, &word) == 1U);
CHECK(word == 0x5AA5U);
CHECK(source.readWord(source.context, PLSR_DEVICE_M, 5UL, &word) == 0U);

PlsrModbusDataSourceInit(NULL);
(void)printf("PASS: %u Modbus data-source checks\n", TestChecks);
return 0;
}

+ 17
- 0
PLSR/Test/test_plsr_profile.c 查看文件

@@ -348,6 +348,7 @@ static void TestControlledStop(void)
10U, 10U, 0U);
PLSR_PROFILE_STATE state;
uint32_t frequency = 0U;
uint64_t pausedPulsesQ32;
uint8_t completed = 0U;
int step;

@@ -365,12 +366,28 @@ static void TestControlledStop(void)
CHECK(frequency == 0U);
CHECK(state.phase == PLSR_PROFILE_PHASE_DONE);

pausedPulsesQ32 = state.emittedPulsesQ32;
CHECK(PlsrProfileResume(&state, 500U, 2000U, 0U)
== PLSR_RESULT_OK);
CHECK(state.phase == PLSR_PROFILE_PHASE_ACCEL);
CHECK(state.emittedPulsesQ32 == pausedPulsesQ32);
CHECK(PlsrProfileStep(&state, &frequency, &completed)
== PLSR_RESULT_OK);
CHECK(completed == 0U);
CHECK(frequency > 500U);

request.decelSlopeHzPerMs = 0U;
CHECK(PlsrProfileStart(&state, &request, 10000, 1000U)
== PLSR_RESULT_OK);
CHECK(PlsrProfileRequestStop(&state) == PLSR_RESULT_OK);
CHECK(state.phase == PLSR_PROFILE_PHASE_DONE);
CHECK(PlsrProfileRequestStop(NULL) == PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileResume(NULL, 500U, 2000U, 0U)
== PLSR_RESULT_INVALID_ARGUMENT);
state.emittedPulsesQ32 = (uint64_t)state.totalPulses
* PLSR_PROFILE_Q32_ONE;
CHECK(PlsrProfileResume(&state, 500U, 2000U, 0U)
== PLSR_RESULT_INVALID_STATE);
}

static void TestPlan(void)


Loading…
取消
儲存