diff --git a/Core/Src/main.c b/Core/Src/main.c
index c299f0c..5c7b0a6 100644
--- a/Core/Src/main.c
+++ b/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 */
diff --git a/Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.md b/Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.md
new file mode 100644
index 0000000..66e370d
--- /dev/null
+++ b/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 并发/删自检)。
+
diff --git a/Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.zip b/Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.zip
new file mode 100644
index 0000000..3a056ca
Binary files /dev/null and b/Document/PLSR_document/ai问询记录/AI问询会话记录_2026.8.3-2026.8.9.zip differ
diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp
index 9c07e0e..2e9e62f 100644
--- a/EWARM/Modbus.ewp
+++ b/EWARM/Modbus.ewp
@@ -1299,6 +1299,12 @@
$PROJ_DIR$\..\PLSR\Inc\plsr_self_test.h
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_modbus_data.h
+
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_modbus_control.h
+
$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c
@@ -1326,6 +1332,12 @@
$PROJ_DIR$\..\PLSR\Src\plsr_self_test.c
+
+ $PROJ_DIR$\..\PLSR\Src\plsr_modbus_data.c
+
+
+ $PROJ_DIR$\..\PLSR\Src\plsr_modbus_control.c
+
$PROJ_DIR$\..\PLSR\Src\plsr_core.c
@@ -1335,8 +1347,14 @@
$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c
+
+ $PROJ_DIR$\..\Modbus\Src\modbus_data_store.c
+
$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h
+
+ $PROJ_DIR$\..\Modbus\Inc\modbus_data_store.h
+
diff --git a/HostComputer/PLSR_MODBUS_CONTROL_TEST.md b/HostComputer/PLSR_MODBUS_CONTROL_TEST.md
new file mode 100644
index 0000000..eb0c868
--- /dev/null
+++ b/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 平滑停止;不得出现窄脉冲或命令切换毛刺。
diff --git a/HostComputer/PLSR_MODBUS_TEST.md b/HostComputer/PLSR_MODBUS_TEST.md
new file mode 100644
index 0000000..162f870
--- /dev/null
+++ b/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`。
diff --git a/HostComputer/__pycache__/plsr_modbus_frequency_test.cpython-39.pyc b/HostComputer/__pycache__/plsr_modbus_frequency_test.cpython-39.pyc
new file mode 100644
index 0000000..54765b5
Binary files /dev/null and b/HostComputer/__pycache__/plsr_modbus_frequency_test.cpython-39.pyc differ
diff --git a/HostComputer/plsr_modbus_control_test.py b/HostComputer/plsr_modbus_control_test.py
new file mode 100644
index 0000000..b5931bb
--- /dev/null
+++ b/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)
diff --git a/HostComputer/plsr_modbus_frequency_test.py b/HostComputer/plsr_modbus_frequency_test.py
new file mode 100644
index 0000000..968bb63
--- /dev/null
+++ b/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)
diff --git a/Modbus/Inc/modbus_data_store.h b/Modbus/Inc/modbus_data_store.h
new file mode 100644
index 0000000..a8700fa
--- /dev/null
+++ b/Modbus/Inc/modbus_data_store.h
@@ -0,0 +1,51 @@
+#ifndef MODBUS_DATA_STORE_H
+#define MODBUS_DATA_STORE_H
+
+#include
+
+#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 */
diff --git a/Modbus/Src/modbus_data_store.c b/Modbus/Src/modbus_data_store.c
new file mode 100644
index 0000000..e850c36
--- /dev/null
+++ b/Modbus/Src/modbus_data_store.c
@@ -0,0 +1,349 @@
+#include "modbus_data_store.h"
+#include
+
+#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;
+}
diff --git a/Modbus/Src/modbus_rtu_slave.c b/Modbus/Src/modbus_rtu_slave.c
index b4756f1..fa6478a 100644
--- a/Modbus/Src/modbus_rtu_slave.c
+++ b/Modbus/Src/modbus_rtu_slave.c
@@ -1,4 +1,5 @@
#include "modbus_rtu_slave.h"
+#include "modbus_data_store.h"
#include
#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;
diff --git a/PLSR/Inc/plsr_core.h b/PLSR/Inc/plsr_core.h
index eba1778..8f8bf6d 100644
--- a/PLSR/Inc/plsr_core.h
+++ b/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,
diff --git a/PLSR/Inc/plsr_hal_f407.h b/PLSR/Inc/plsr_hal_f407.h
index 86fc4c3..6947bf0 100644
--- a/PLSR/Inc/plsr_hal_f407.h
+++ b/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
diff --git a/PLSR/Inc/plsr_job.h b/PLSR/Inc/plsr_job.h
index e88335f..8a70fed 100644
--- a/PLSR/Inc/plsr_job.h
+++ b/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,
diff --git a/PLSR/Inc/plsr_modbus_control.h b/PLSR/Inc/plsr_modbus_control.h
new file mode 100644
index 0000000..c97275d
--- /dev/null
+++ b/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
+
+#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 */
diff --git a/PLSR/Inc/plsr_modbus_data.h b/PLSR/Inc/plsr_modbus_data.h
new file mode 100644
index 0000000..f32d60d
--- /dev/null
+++ b/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 */
diff --git a/PLSR/Inc/plsr_profile.h b/PLSR/Inc/plsr_profile.h
index ec3deed..88a15c2 100644
--- a/PLSR/Inc/plsr_profile.h
+++ b/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);
diff --git a/PLSR/Inc/plsr_self_test.h b/PLSR/Inc/plsr_self_test.h
index ec7492c..de766b8 100644
--- a/PLSR/Inc/plsr_self_test.h
+++ b/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
diff --git a/PLSR/Inc/plsr_types.h b/PLSR/Inc/plsr_types.h
index 40ee5d3..b7bbd4b 100644
--- a/PLSR/Inc/plsr_types.h
+++ b/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
diff --git a/PLSR/Src/plsr_core.c b/PLSR/Src/plsr_core.c
index 1ed30a4..258ec74 100644
--- a/PLSR/Src/plsr_core.c
+++ b/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, ¶ms);
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;
}
diff --git a/PLSR/Src/plsr_hal_f407.c b/PLSR/Src/plsr_hal_f407.c
index 5f4cced..23a51c8 100644
--- a/PLSR/Src/plsr_hal_f407.c
+++ b/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
diff --git a/PLSR/Src/plsr_job.c b/PLSR/Src/plsr_job.c
index 586eed9..ba271ed 100644
--- a/PLSR/Src/plsr_job.c
+++ b/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
diff --git a/PLSR/Src/plsr_modbus_control.c b/PLSR/Src/plsr_modbus_control.c
new file mode 100644
index 0000000..8a8b581
--- /dev/null
+++ b/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
+#include
+
+#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;
+}
diff --git a/PLSR/Src/plsr_modbus_data.c b/PLSR/Src/plsr_modbus_data.c
new file mode 100644
index 0000000..650aa7d
--- /dev/null
+++ b/PLSR/Src/plsr_modbus_data.c
@@ -0,0 +1,72 @@
+#include "plsr_modbus_data.h"
+#include "modbus_data_store.h"
+#include
+
+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;
+}
diff --git a/PLSR/Src/plsr_path.c b/PLSR/Src/plsr_path.c
index cbb3015..05bad02 100644
--- a/PLSR/Src/plsr_path.c
+++ b/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,
diff --git a/PLSR/Src/plsr_profile.c b/PLSR/Src/plsr_profile.c
index 8dd8ec2..17dec90 100644
--- a/PLSR/Src/plsr_profile.c
+++ b/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)
diff --git a/PLSR/Src/plsr_self_test.c b/PLSR/Src/plsr_self_test.c
index 0e24eaf..764f292 100644
--- a/PLSR/Src/plsr_self_test.c
+++ b/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
/* 上电自测(验证后可删除):
@@ -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;
+}
diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1
index 95ec94c..44510ce 100644
--- a/PLSR/Test/run_host_tests.ps1
+++ b/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
diff --git a/PLSR/Test/test_plsr_hal.c b/PLSR/Test/test_plsr_hal.c
index 7cf7575..b381de6 100644
--- a/PLSR/Test/test_plsr_hal.c
+++ b/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, ¶ms) == 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, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
+ CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
+ params.directionPositive = 1U;
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwTestGetDirLevel(0U) == 0U);
+}
+
+static void TestDirectionBatch(void)
+{
+ PLSR_HW_START_PARAMS params;
+
+ (void)PlsrHwInit();
+ (void)memset(¶ms, 0, sizeof(params));
+ params.frequencyHz = 0UL;
+ params.targetPulses = 10;
+ params.outputMode = PLSR_OUTPUT_PULSE_DIR;
+ params.directionPoint = 4U;
+ params.directionPositive = 1U;
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ params.directionPoint = 3U;
+ params.directionNegativeLogic = 1U;
+ CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
+ CHECK(PlsrHwTestGetDirLevel(1U) == 0U);
+
+ PlsrHwBeginDirectionBatch();
+ params.directionPoint = 4U;
+ params.directionPositive = 0U;
+ params.directionNegativeLogic = 0U;
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ params.directionPoint = 3U;
+ params.directionNegativeLogic = 1U;
+ CHECK(PlsrHwStartPulse(1U, ¶ms) == 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(¶ms, 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, ¶ms) == PLSR_RESULT_INVALID_AXIS);
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == 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, ¶ms) == 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, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT);
params.targetPulses = 1;
params.outputMode = PLSR_OUTPUT_CW_CCW;
- CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_NOT_SUPPORTED);
+ CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_INVALID_AXIS);
params.outputMode = (PLSR_OUTPUT_MODE)99;
CHECK(PlsrHwStartPulse(0U, ¶ms) == 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)
diff --git a/PLSR/Test/test_plsr_job.c b/PLSR/Test/test_plsr_job.c
index 33c5a06..5cf9c90 100644
--- a/PLSR/Test/test_plsr_job.c
+++ b/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);
diff --git a/PLSR/Test/test_plsr_modbus_data.c b/PLSR/Test/test_plsr_modbus_data.c
new file mode 100644
index 0000000..afc0998
--- /dev/null
+++ b/PLSR/Test/test_plsr_modbus_data.c
@@ -0,0 +1,84 @@
+#include "modbus_data_store.h"
+#include "plsr_modbus_data.h"
+#include
+#include
+
+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;
+}
diff --git a/PLSR/Test/test_plsr_profile.c b/PLSR/Test/test_plsr_profile.c
index 4a72a6d..11c47c5 100644
--- a/PLSR/Test/test_plsr_profile.c
+++ b/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)