本批完成内容: Modbus 控制窗口(新增 plsr_modbus_control/data + modbus_data_store): - COMMIT 与 START 分离:COMMIT 校验通过后生效;COMMIT 后 再修改 S0/S1,START 返回 BUSY,要求重新提交。 - 命令支持:停止、暂停、继续、位置设置、清零、保存; 重复命令序号幂等,不重复执行。 - 四轴状态均可读取;32/64 位状态用首尾版本号保证一致性 (防撕裂读取)。 - 控制窗口基址可配置:P13 暂用 D1200~D1455(非信捷固定 地址),S0=D1600、S1=D1700。 PAUSE/RESUME 闭环修复: - PAUSE 不再终止当前 S0 路径,保留当前段与已发脉冲; RESUME 从 S2 起始速度重新激活速度曲线。 - 硬件定时器恢复时不清零脉冲计数,任务累计连续。 - 暂停回差补偿段时恢复专用速度参数。 - STOP_DECEL 正常收到减速完成事件并进入 STOPPED。 - Python 脚本增加"频率非零 + 计数增长"恢复判定,不再把 ACCEL+0Hz 误判为恢复成功。 测试: - Host:3500 项检查全部通过(含控制窗口/命令/一致性/幂等)。 - IAR:0 errors,0 warnings。 - 上板验证(逻辑分析仪,Q0): START 后 1000Hz 平滑加速至 2000Hz → 稳定段 → PAUSE 约 200ms 平滑减速停止,暂停期间 0 边沿恒低 → 重复 PAUSE 无第二次动作 → RESUME 从 ~1000Hz 重新加速 → STOP_DECEL 平滑减速至停。全程 0 个 <10µs 毛刺、无截断尾脉冲、 无意外重启。master
| @@ -26,6 +26,7 @@ | |||||
| #include "modbus_rtu_slave.h" | #include "modbus_rtu_slave.h" | ||||
| #include "plc_device.h" | #include "plc_device.h" | ||||
| #include "plsr_core.h" | #include "plsr_core.h" | ||||
| #include "plsr_modbus_control.h" | |||||
| #include "plsr_self_test.h" | #include "plsr_self_test.h" | ||||
| #include "stdio.h" | #include "stdio.h" | ||||
| /* USER CODE END Includes */ | /* USER CODE END Includes */ | ||||
| @@ -37,6 +38,13 @@ | |||||
| /* Private define ------------------------------------------------------------*/ | /* Private define ------------------------------------------------------------*/ | ||||
| /* USER CODE BEGIN PD */ | /* 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 */ | /* USER CODE END PD */ | ||||
| @@ -80,6 +88,20 @@ static void AppTaskStart(void *pArg) | |||||
| * 初始化函数会立即启动USART1的DMA空闲接收 | * 初始化函数会立即启动USART1的DMA空闲接收 | ||||
| */ | */ | ||||
| (void)ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS); | (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(); | //ModbusRetainedRegistersLoad(); | ||||
| while (1) | while (1) | ||||
| { | { | ||||
| @@ -108,6 +130,7 @@ static void AppTaskStart(void *pArg) | |||||
| * 每1ms轮询一次, | * 每1ms轮询一次, | ||||
| */ | */ | ||||
| ModbusSlavePoll(); | ModbusSlavePoll(); | ||||
| PlsrModbusControlPoll(); | |||||
| // ModbusRetainedRegistersPoll(); | // ModbusRetainedRegistersPoll(); | ||||
| if (ModbusSlaveIsConnected(MODBUS_CONNECTION_TIMEOUT_MS) != 0U) | if (ModbusSlaveIsConnected(MODBUS_CONNECTION_TIMEOUT_MS) != 0U) | ||||
| @@ -200,10 +223,19 @@ int main(void) | |||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| /* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */ | |||||
| /* 上电自测统一延时1s,当前P9配置见下方调用(验证后关闭)。 */ | |||||
| HAL_Delay(1000U); | 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(); | OSStart(); | ||||
| /* USER CODE END 2 */ | /* USER CODE END 2 */ | ||||
| @@ -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 并发/删自检)。 | |||||
| @@ -1299,6 +1299,12 @@ | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_self_test.h</name> | <name>$PROJ_DIR$\..\PLSR\Inc\plsr_self_test.h</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_modbus_data.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_modbus_control.h</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name> | <name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name> | ||||
| </file> | </file> | ||||
| @@ -1326,6 +1332,12 @@ | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_self_test.c</name> | <name>$PROJ_DIR$\..\PLSR\Src\plsr_self_test.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_modbus_data.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_modbus_control.c</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name> | <name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name> | ||||
| </file> | </file> | ||||
| @@ -1335,8 +1347,14 @@ | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c</name> | <name>$PROJ_DIR$\..\Modbus\Src\modbus_rtu_slave.c</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Modbus\Src\modbus_data_store.c</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h</name> | <name>$PROJ_DIR$\..\Modbus\Inc\modbus_rtu_slave.h</name> | ||||
| </file> | </file> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\Modbus\Inc\modbus_data_store.h</name> | |||||
| </file> | |||||
| </group> | </group> | ||||
| </project> | </project> | ||||
| @@ -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 平滑停止;不得出现窄脉冲或命令切换毛刺。 | |||||
| @@ -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`。 | |||||
| @@ -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) | |||||
| @@ -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) | |||||
| @@ -0,0 +1,51 @@ | |||||
| #ifndef MODBUS_DATA_STORE_H | |||||
| #define MODBUS_DATA_STORE_H | |||||
| #include <stdint.h> | |||||
| #ifdef __cplusplus | |||||
| extern "C" { | |||||
| #endif | |||||
| /* Logical PLC word-device spaces. These values intentionally match the | |||||
| * PLSR D/HD/FD device codes, but this module does not depend on PLSR. */ | |||||
| typedef enum | |||||
| { | |||||
| MODBUS_DATA_DEVICE_D = 0, | |||||
| MODBUS_DATA_DEVICE_HD, | |||||
| MODBUS_DATA_DEVICE_FD | |||||
| } MODBUS_DATA_DEVICE; | |||||
| #define MODBUS_DATA_D_WORD_COUNT (10000UL) | |||||
| #define MODBUS_DATA_HD_WORD_COUNT (10000UL) | |||||
| #define MODBUS_DATA_FD_WORD_COUNT (10000UL) | |||||
| uint8_t ModbusDataValidateWords(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| uint32_t wordCount); | |||||
| uint8_t ModbusDataReadWord(MODBUS_DATA_DEVICE device, | |||||
| uint32_t address, | |||||
| uint16_t *value); | |||||
| uint8_t ModbusDataReadDword(MODBUS_DATA_DEVICE device, | |||||
| uint32_t lowAddress, | |||||
| int32_t *value); | |||||
| uint8_t ModbusDataWriteWord(MODBUS_DATA_DEVICE device, | |||||
| uint32_t address, | |||||
| uint16_t value); | |||||
| uint8_t ModbusDataWriteWords(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| const uint16_t *values, | |||||
| uint32_t wordCount); | |||||
| /* Existing function 0x48 uses a sparse linear address space: | |||||
| * 0..19999 are normal SRAM and 40000..69998 are CCMRAM. */ | |||||
| uint8_t ModbusDataReadLinear(uint32_t address, uint16_t *value); | |||||
| /* Even means stable; odd means a multi-register write is in progress. */ | |||||
| uint32_t ModbusDataGetWriteSequence(void); | |||||
| #ifdef __cplusplus | |||||
| } | |||||
| #endif | |||||
| #endif /* MODBUS_DATA_STORE_H */ | |||||
| @@ -0,0 +1,349 @@ | |||||
| #include "modbus_data_store.h" | |||||
| #include <stddef.h> | |||||
| #if defined(PLSR_HOST_TEST) | |||||
| #define MODBUS_DATA_BARRIER() __sync_synchronize() | |||||
| #else | |||||
| #include "stm32f4xx.h" | |||||
| #define MODBUS_DATA_BARRIER() __DMB() | |||||
| #endif | |||||
| #define MODBUS_DATA_SRAM_WORD_COUNT (20000UL) | |||||
| #define MODBUS_DATA_CCM_WORD_COUNT (29999UL) | |||||
| #define MODBUS_DATA_HD_SRAM_OFFSET (10000UL) | |||||
| #define MODBUS_DATA_LINEAR_CCM_BASE (40000UL) | |||||
| static uint16_t ModbusDataSram[MODBUS_DATA_SRAM_WORD_COUNT]; | |||||
| #if !defined(PLSR_HOST_TEST) | |||||
| #pragma location = ".ccmram" | |||||
| #pragma data_alignment = 4 | |||||
| __root | |||||
| #endif | |||||
| static uint16_t ModbusDataCcm[MODBUS_DATA_CCM_WORD_COUNT]; | |||||
| static volatile uint32_t ModbusDataWriteSequence; | |||||
| static volatile uint32_t ModbusDataWriteFirstAddress; | |||||
| static volatile uint32_t ModbusDataWriteWordCount; | |||||
| static volatile MODBUS_DATA_DEVICE ModbusDataWriteDevice; | |||||
| static uint32_t ModbusDataEnterShortCritical(void) | |||||
| { | |||||
| #if defined(PLSR_HOST_TEST) | |||||
| return 0UL; | |||||
| #else | |||||
| uint32_t interruptState; | |||||
| interruptState = __get_PRIMASK(); | |||||
| __disable_irq(); | |||||
| return interruptState; | |||||
| #endif | |||||
| } | |||||
| static void ModbusDataExitShortCritical(uint32_t interruptState) | |||||
| { | |||||
| #if defined(PLSR_HOST_TEST) | |||||
| (void)interruptState; | |||||
| #else | |||||
| if (interruptState == 0UL) | |||||
| { | |||||
| __enable_irq(); | |||||
| } | |||||
| #endif | |||||
| } | |||||
| static uint8_t ModbusDataResolve(MODBUS_DATA_DEVICE device, | |||||
| uint32_t address, | |||||
| uint16_t **word) | |||||
| { | |||||
| if (word == NULL) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| switch (device) | |||||
| { | |||||
| case MODBUS_DATA_DEVICE_D: | |||||
| if (address >= MODBUS_DATA_D_WORD_COUNT) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *word = &ModbusDataSram[address]; | |||||
| return 1U; | |||||
| case MODBUS_DATA_DEVICE_HD: | |||||
| if (address >= MODBUS_DATA_HD_WORD_COUNT) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *word = &ModbusDataSram[MODBUS_DATA_HD_SRAM_OFFSET + address]; | |||||
| return 1U; | |||||
| case MODBUS_DATA_DEVICE_FD: | |||||
| if (address >= MODBUS_DATA_FD_WORD_COUNT) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *word = &ModbusDataCcm[address]; | |||||
| return 1U; | |||||
| default: | |||||
| return 0U; | |||||
| } | |||||
| } | |||||
| static uint32_t ModbusDataCapacity(MODBUS_DATA_DEVICE device) | |||||
| { | |||||
| switch (device) | |||||
| { | |||||
| case MODBUS_DATA_DEVICE_D: | |||||
| return MODBUS_DATA_D_WORD_COUNT; | |||||
| case MODBUS_DATA_DEVICE_HD: | |||||
| return MODBUS_DATA_HD_WORD_COUNT; | |||||
| case MODBUS_DATA_DEVICE_FD: | |||||
| return MODBUS_DATA_FD_WORD_COUNT; | |||||
| default: | |||||
| return 0UL; | |||||
| } | |||||
| } | |||||
| static uint8_t ModbusDataWriteOverlaps(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| uint32_t activeFirst; | |||||
| uint32_t activeCount; | |||||
| if (device != ModbusDataWriteDevice) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| activeFirst = ModbusDataWriteFirstAddress; | |||||
| activeCount = ModbusDataWriteWordCount; | |||||
| if ((activeCount == 0UL) || (wordCount == 0UL)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return ((firstAddress < (activeFirst + activeCount)) | |||||
| && (activeFirst < (firstAddress + wordCount))) | |||||
| ? 1U | |||||
| : 0U; | |||||
| } | |||||
| static void ModbusDataWriteBegin(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| ModbusDataWriteDevice = device; | |||||
| ModbusDataWriteFirstAddress = firstAddress; | |||||
| ModbusDataWriteWordCount = wordCount; | |||||
| MODBUS_DATA_BARRIER(); | |||||
| ModbusDataWriteSequence++; | |||||
| MODBUS_DATA_BARRIER(); | |||||
| } | |||||
| static void ModbusDataWriteEnd(void) | |||||
| { | |||||
| MODBUS_DATA_BARRIER(); | |||||
| ModbusDataWriteSequence++; | |||||
| } | |||||
| uint8_t ModbusDataValidateWords(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| uint32_t capacity; | |||||
| capacity = ModbusDataCapacity(device); | |||||
| if ((capacity == 0UL) || (wordCount == 0UL) | |||||
| || (firstAddress >= capacity)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return (wordCount <= (capacity - firstAddress)) ? 1U : 0U; | |||||
| } | |||||
| uint8_t ModbusDataReadWord(MODBUS_DATA_DEVICE device, | |||||
| uint32_t address, | |||||
| uint16_t *value) | |||||
| { | |||||
| uint16_t *word; | |||||
| uint32_t before; | |||||
| uint32_t after; | |||||
| uint16_t snapshot; | |||||
| if ((value == NULL) || (ModbusDataResolve(device, address, &word) == 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| before = ModbusDataWriteSequence; | |||||
| if (((before & 1UL) != 0UL) | |||||
| && (ModbusDataWriteOverlaps(device, address, 1UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| MODBUS_DATA_BARRIER(); | |||||
| snapshot = *word; | |||||
| MODBUS_DATA_BARRIER(); | |||||
| after = ModbusDataWriteSequence; | |||||
| if ((before != after) | |||||
| && (ModbusDataWriteOverlaps(device, address, 1UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| if (((after & 1UL) != 0UL) | |||||
| && (ModbusDataWriteOverlaps(device, address, 1UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *value = snapshot; | |||||
| return 1U; | |||||
| } | |||||
| uint8_t ModbusDataReadDword(MODBUS_DATA_DEVICE device, | |||||
| uint32_t lowAddress, | |||||
| int32_t *value) | |||||
| { | |||||
| uint16_t *lowWord; | |||||
| uint16_t *highWord; | |||||
| uint16_t lowSnapshot; | |||||
| uint16_t highSnapshot; | |||||
| uint32_t before; | |||||
| uint32_t after; | |||||
| if ((value == NULL) | |||||
| || (ModbusDataValidateWords(device, lowAddress, 2UL) == 0U) | |||||
| || (ModbusDataResolve(device, lowAddress, &lowWord) == 0U) | |||||
| || (ModbusDataResolve(device, lowAddress + 1UL, &highWord) == 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| before = ModbusDataWriteSequence; | |||||
| if (((before & 1UL) != 0UL) | |||||
| && (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| MODBUS_DATA_BARRIER(); | |||||
| lowSnapshot = *lowWord; | |||||
| highSnapshot = *highWord; | |||||
| MODBUS_DATA_BARRIER(); | |||||
| after = ModbusDataWriteSequence; | |||||
| if ((before != after) | |||||
| && (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| if (((after & 1UL) != 0UL) | |||||
| && (ModbusDataWriteOverlaps(device, lowAddress, 2UL) != 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *value = (int32_t)(((uint32_t)highSnapshot << 16U) | lowSnapshot); | |||||
| return 1U; | |||||
| } | |||||
| uint8_t ModbusDataWriteWord(MODBUS_DATA_DEVICE device, | |||||
| uint32_t address, | |||||
| uint16_t value) | |||||
| { | |||||
| uint16_t *word; | |||||
| uint32_t interruptState; | |||||
| if (ModbusDataResolve(device, address, &word) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| interruptState = ModbusDataEnterShortCritical(); | |||||
| ModbusDataWriteBegin(device, address, 1UL); | |||||
| *word = value; | |||||
| ModbusDataWriteEnd(); | |||||
| ModbusDataExitShortCritical(interruptState); | |||||
| return 1U; | |||||
| } | |||||
| uint8_t ModbusDataWriteWords(MODBUS_DATA_DEVICE device, | |||||
| uint32_t firstAddress, | |||||
| const uint16_t *values, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| uint16_t *firstWord; | |||||
| uint32_t index; | |||||
| uint32_t interruptState = 1UL; | |||||
| if ((values == NULL) | |||||
| || (ModbusDataValidateWords(device, firstAddress, wordCount) == 0U) | |||||
| || (ModbusDataResolve(device, firstAddress, &firstWord) == 0U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| /* A live INT32 update is only two words. Keep that very short commit | |||||
| * indivisible to the 100us ISR, so it sees either the old or new value. | |||||
| * Larger block writes use the non-blocking sequence protocol instead of | |||||
| * delaying pulse-related interrupts for an unbounded block copy. */ | |||||
| if (wordCount <= 2UL) | |||||
| { | |||||
| interruptState = ModbusDataEnterShortCritical(); | |||||
| } | |||||
| ModbusDataWriteBegin(device, firstAddress, wordCount); | |||||
| for (index = 0UL; index < wordCount; index++) | |||||
| { | |||||
| firstWord[index] = values[index]; | |||||
| } | |||||
| ModbusDataWriteEnd(); | |||||
| if (wordCount <= 2UL) | |||||
| { | |||||
| ModbusDataExitShortCritical(interruptState); | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| uint8_t ModbusDataReadLinear(uint32_t address, uint16_t *value) | |||||
| { | |||||
| if (address < MODBUS_DATA_SRAM_WORD_COUNT) | |||||
| { | |||||
| return ModbusDataReadWord((address < MODBUS_DATA_D_WORD_COUNT) | |||||
| ? MODBUS_DATA_DEVICE_D | |||||
| : MODBUS_DATA_DEVICE_HD, | |||||
| (address < MODBUS_DATA_D_WORD_COUNT) | |||||
| ? address | |||||
| : address - MODBUS_DATA_HD_SRAM_OFFSET, | |||||
| value); | |||||
| } | |||||
| if ((address >= MODBUS_DATA_LINEAR_CCM_BASE) | |||||
| && ((address - MODBUS_DATA_LINEAR_CCM_BASE) | |||||
| < MODBUS_DATA_CCM_WORD_COUNT)) | |||||
| { | |||||
| uint32_t before; | |||||
| uint32_t after; | |||||
| uint16_t snapshot; | |||||
| if (value == NULL) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| before = ModbusDataWriteSequence; | |||||
| if ((before & 1UL) != 0UL) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| MODBUS_DATA_BARRIER(); | |||||
| snapshot = ModbusDataCcm[address - MODBUS_DATA_LINEAR_CCM_BASE]; | |||||
| MODBUS_DATA_BARRIER(); | |||||
| after = ModbusDataWriteSequence; | |||||
| if ((before != after) || ((after & 1UL) != 0UL)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *value = snapshot; | |||||
| return 1U; | |||||
| } | |||||
| return 0U; | |||||
| } | |||||
| uint32_t ModbusDataGetWriteSequence(void) | |||||
| { | |||||
| return ModbusDataWriteSequence; | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| #include "modbus_rtu_slave.h" | #include "modbus_rtu_slave.h" | ||||
| #include "modbus_data_store.h" | |||||
| #include <string.h> | #include <string.h> | ||||
| #define MODBUS_RTU_ADU_SIZE_MAX (256U) // Modbus RTU 最大 ADU 长度,单位为字节 | #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 ModbusRxAssemblyBuffer[MODBUS_RTU_ADU_SIZE_MAX]; | ||||
| static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX]; | static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX]; | ||||
| static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX]; | static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX]; | ||||
| static uint16_t ModbusWriteRegisterScratch[MODBUS_WRITE_REGS_MAX]; | |||||
| /* D100~D120 上一次已保存的值,用于检测数据是否变化 */ | /* D100~D120 上一次已保存的值,用于检测数据是否变化 */ | ||||
| static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT]; | static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT]; | ||||
| static volatile uint16_t ModbusRxFrameLength; | static volatile uint16_t ModbusRxFrameLength; | ||||
| @@ -52,16 +54,8 @@ static volatile uint8_t ModbusHasReceivedValidFrame; | |||||
| static volatile MODBUS_BACKUP_DATA *ModbusBackupData = | static volatile MODBUS_BACKUP_DATA *ModbusBackupData = | ||||
| (volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE; | (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]; | static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U]; | ||||
| volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; | ||||
| @@ -442,7 +436,11 @@ tx: | |||||
| { | { | ||||
| address = (address - 20000U) * 2U + 1U; | 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[3U + index * 2U] = (uint8_t)(value >> 8U); | ||||
| ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); | 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) | if (isBroadcast != 0U) | ||||
| { | { | ||||
| @@ -672,9 +670,13 @@ static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request, | |||||
| for (index = 0U; index < quantity; index++) | for (index = 0U; index < quantity; index++) | ||||
| { | { | ||||
| ModbusHoldingRegisters[start + index] = | |||||
| ModbusWriteRegisterScratch[index] = | |||||
| ModbusGetU16Be(&request[7U + index * 2U]); | ModbusGetU16Be(&request[7U + index * 2U]); | ||||
| } | } | ||||
| (void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, | |||||
| start, | |||||
| ModbusWriteRegisterScratch, | |||||
| quantity); | |||||
| if (isBroadcast != 0U) | if (isBroadcast != 0U) | ||||
| { | { | ||||
| @@ -723,11 +725,12 @@ static uint16_t ModbusProcessReadBigHolding(const uint8_t *request, | |||||
| return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); | 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++; | ModbusSlaveStatistics.illegalAddressCount++; | ||||
| @@ -744,17 +747,11 @@ static uint16_t ModbusProcessReadBigHolding(const uint8_t *request, | |||||
| { | { | ||||
| currentAddress = start + (uint32_t)index; | 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; | ModbusTxBusy = 0U; | ||||
| ModbusRtuTimingInit(huart->Init.BaudRate); | ModbusRtuTimingInit(huart->Init.BaudRate); | ||||
| //ModbusHoldingRegisters[HMI_REG_DEVICE_ID] = 0xF407U; | |||||
| return ModbusStartReceive(); | return ModbusStartReceive(); | ||||
| } | } | ||||
| @@ -1019,8 +1014,7 @@ uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value) | |||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| ModbusHoldingRegisters[address] = value; | |||||
| return 1U; | |||||
| return ModbusDataWriteWord(MODBUS_DATA_DEVICE_D, address, value); | |||||
| } | } | ||||
| uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *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; | return 0U; | ||||
| } | } | ||||
| *value = ModbusHoldingRegisters[address]; | |||||
| return 1U; | |||||
| return ModbusDataReadWord(MODBUS_DATA_DEVICE_D, address, value); | |||||
| } | } | ||||
| uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state) | 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++) | 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 | else | ||||
| { | { | ||||
| for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++) | 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; | ModbusBackupData->retainedD[index] = 0U; | ||||
| } | } | ||||
| @@ -1102,7 +1099,9 @@ void ModbusRetainedRegistersPoll(void) | |||||
| for (index = 0; index < MODBUS_RETAINED_D_COUNT; index++) | 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]) | if (value != ModbusRetainedSnapshot[index]) | ||||
| { | { | ||||
| ModbusBackupData->retainedD[index] = value; | ModbusBackupData->retainedD[index] = value; | ||||
| @@ -12,8 +12,14 @@ extern "C" { | |||||
| PLSR_RESULT PlsrInit(void); | PLSR_RESULT PlsrInit(void); | ||||
| void PlsrTask(void *argument); | void PlsrTask(void *argument); | ||||
| void PlsrProcess(void); | void PlsrProcess(void); | ||||
| void PlsrControlTick100us(void); | |||||
| void PlsrSetControlTickHook(void (*hook)(void)); | |||||
| PLSR_RESULT PlsrPostCall(const PLSR_CALL *call); | 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 PlsrPostCommand(const PLSR_COMMAND *command); | ||||
| PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask); | PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask); | ||||
| PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status); | PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status); | ||||
| @@ -24,6 +30,10 @@ PLSR_RESULT PlsrGetLastParseDetail(uint8_t axis, | |||||
| /* 仅测试使用的低层启动入口:不解析 S0/S1/S2/D,直接申请资源进入 ACCEL。 | /* 仅测试使用的低层启动入口:不解析 S0/S1/S2/D,直接申请资源进入 ACCEL。 | ||||
| * 生产调用必须使用 PlsrPostCall()。 */ | * 生产调用必须使用 PlsrPostCall()。 */ | ||||
| PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request); | 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 | #endif | ||||
| PLSR_RESULT PlsrStateTransition(uint8_t axis, | PLSR_RESULT PlsrStateTransition(uint8_t axis, | ||||
| @@ -27,6 +27,7 @@ typedef struct | |||||
| PLSR_OUTPUT_MODE outputMode; /* PULSE/DIR、AB 或 CW/CCW */ | PLSR_OUTPUT_MODE outputMode; /* PULSE/DIR、AB 或 CW/CCW */ | ||||
| uint8_t directionPoint; /* DIR 输出点(Y 点号,0xFF=无) */ | uint8_t directionPoint; /* DIR 输出点(Y 点号,0xFF=无) */ | ||||
| uint8_t directionPositive; | uint8_t directionPositive; | ||||
| uint8_t directionNegativeLogic; /* SFD900 Bit1: 1 reverses DIR ON/OFF */ | |||||
| uint16_t directionDelayMs; | uint16_t directionDelayMs; | ||||
| } PLSR_HW_START_PARAMS; | } 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 PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params); | ||||
| PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz); | 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); | 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); | uint8_t PlsrHwIsPulseActive(uint8_t axis); | ||||
| PLSR_HW_STATE PlsrHwGetState(uint8_t axis); | PLSR_HW_STATE PlsrHwGetState(uint8_t axis); | ||||
| uint32_t PlsrHwGetTimerClockHz(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 PlsrHwTestGetAbPhaseB(uint8_t axis); | ||||
| uint8_t PlsrHwTestGetAbQuarter(uint8_t axis); | uint8_t PlsrHwTestGetAbQuarter(uint8_t axis); | ||||
| void PlsrHwTestTriggerUpdate(uint8_t axis); | void PlsrHwTestTriggerUpdate(uint8_t axis); | ||||
| void PlsrHwTestTriggerCompare(uint8_t axis); | |||||
| void PlsrHwTestAdvanceAbQuarter(uint8_t axis); | void PlsrHwTestAdvanceAbQuarter(uint8_t axis); | ||||
| #endif | #endif | ||||
| @@ -32,6 +32,10 @@ typedef uint8_t (*PLSR_READ_WORD_FN)(void *context, | |||||
| PLSR_DEVICE_TYPE device, | PLSR_DEVICE_TYPE device, | ||||
| uint32_t address, | uint32_t address, | ||||
| uint16_t *value); | 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, | typedef uint8_t (*PLSR_READ_BIT_FN)(void *context, | ||||
| PLSR_DEVICE_TYPE device, | PLSR_DEVICE_TYPE device, | ||||
| uint32_t address, | uint32_t address, | ||||
| @@ -42,6 +46,9 @@ typedef struct | |||||
| void *context; | void *context; | ||||
| PLSR_VALIDATE_WORDS_FN validateWords; | PLSR_VALIDATE_WORDS_FN validateWords; | ||||
| PLSR_READ_WORD_FN readWord; | 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_READ_BIT_FN readBit; | ||||
| } PLSR_DATA_SOURCE; | } PLSR_DATA_SOURCE; | ||||
| @@ -152,6 +159,8 @@ typedef struct | |||||
| PLSR_EQUIVALENT_CONFIG equivalent; | PLSR_EQUIVALENT_CONFIG equivalent; | ||||
| PLSR_LIMIT_SNAPSHOT limits; | PLSR_LIMIT_SNAPSHOT limits; | ||||
| PLSR_SEGMENT_SNAPSHOT segments[PLSR_MAX_SEGMENTS]; | PLSR_SEGMENT_SNAPSHOT segments[PLSR_MAX_SEGMENTS]; | ||||
| uint16_t positiveBacklashPulses; | |||||
| uint16_t negativeBacklashPulses; | |||||
| uint32_t inputDefaultSpeed; | uint32_t inputDefaultSpeed; | ||||
| uint32_t inputMaximumSpeed; | uint32_t inputMaximumSpeed; | ||||
| uint32_t timerClockHz; | uint32_t timerClockHz; | ||||
| @@ -163,7 +172,7 @@ typedef struct | |||||
| uint8_t positioningMode; | uint8_t positioningMode; | ||||
| uint8_t outputMode; | uint8_t outputMode; | ||||
| uint8_t directionPoint; | uint8_t directionPoint; | ||||
| uint8_t directionActiveHigh; | |||||
| uint8_t directionNegativeLogic; | |||||
| uint8_t initialDirectionPositive; | uint8_t initialDirectionPositive; | ||||
| uint8_t speedClamped; | uint8_t speedClamped; | ||||
| uint8_t hasSelfLoop; | uint8_t hasSelfLoop; | ||||
| @@ -202,6 +211,9 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot, | |||||
| uint16_t segment, | uint16_t segment, | ||||
| uint32_t *frequency, | uint32_t *frequency, | ||||
| uint8_t *clamped); | uint8_t *clamped); | ||||
| PLSR_RESULT PlsrReadLiveFrequencyRaw(const PLSR_JOB_SNAPSHOT *snapshot, | |||||
| uint16_t segment, | |||||
| int32_t *rawFrequency); | |||||
| PLSR_RESULT PlsrCalculateTimerDivider(uint32_t timerClockHz, | PLSR_RESULT PlsrCalculateTimerDivider(uint32_t timerClockHz, | ||||
| uint32_t frequencyHz, | uint32_t frequencyHz, | ||||
| uint16_t *psc, | uint16_t *psc, | ||||
| @@ -0,0 +1,34 @@ | |||||
| #ifndef PLSR_MODBUS_CONTROL_H | |||||
| #define PLSR_MODBUS_CONTROL_H | |||||
| #include "plsr_types.h" | |||||
| #include <stdint.h> | |||||
| #ifdef __cplusplus | |||||
| extern "C" { | |||||
| #endif | |||||
| #define PLSR_MODBUS_PROTOCOL_VERSION (0x0100U) | |||||
| #define PLSR_MODBUS_WINDOW_WORDS (256UL) | |||||
| #define PLSR_MODBUS_CALL_REQUEST_OFFSET (8UL) | |||||
| #define PLSR_MODBUS_CALL_RESPONSE_OFFSET (24UL) | |||||
| #define PLSR_MODBUS_COMMAND_REQUEST_OFFSET (40UL) | |||||
| #define PLSR_MODBUS_COMMAND_RESPONSE_OFFSET (48UL) | |||||
| #define PLSR_MODBUS_AXIS_STATUS_OFFSET (64UL) | |||||
| #define PLSR_MODBUS_AXIS_STATUS_WORDS (48UL) | |||||
| #define PLSR_MODBUS_CALL_NONE (0U) | |||||
| #define PLSR_MODBUS_CALL_COMMIT (1U) | |||||
| #define PLSR_MODBUS_CALL_START (2U) | |||||
| PLSR_RESULT PlsrModbusControlInit(uint16_t baseAddress); | |||||
| void PlsrModbusControlPoll(void); | |||||
| uint8_t PlsrModbusControlIsEnabled(void); | |||||
| uint16_t PlsrModbusControlGetBaseAddress(void); | |||||
| #ifdef __cplusplus | |||||
| } | |||||
| #endif | |||||
| #endif /* PLSR_MODBUS_CONTROL_H */ | |||||
| @@ -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 */ | |||||
| @@ -93,6 +93,14 @@ PLSR_RESULT PlsrProfileRetarget(PLSR_PROFILE_STATE *state, | |||||
| /* 从当前频率按既定减速斜率降到0,用于缓停、限位和暂停。 */ | /* 从当前频率按既定减速斜率降到0,用于缓停、限位和暂停。 */ | ||||
| PLSR_RESULT PlsrProfileRequestStop(PLSR_PROFILE_STATE *state); | 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 预装载滞后的累积偏差, | /* 虚拟发射计数校准到硬件实际计数(消除 ARPE 预装载滞后的累积偏差, | ||||
| * 保证 DONE 判定与硬件同步,段尾不再以冻结频率补发剩余脉冲)。 */ | * 保证 DONE 判定与硬件同步,段尾不再以冻结频率补发剩余脉冲)。 */ | ||||
| void PlsrProfileSyncPulses(PLSR_PROFILE_STATE *state, uint64_t hwPulses); | void PlsrProfileSyncPulses(PLSR_PROFILE_STATE *state, uint64_t hwPulses); | ||||
| @@ -22,6 +22,30 @@ PLSR_RESULT PlsrProtectionSelfTestQueue(void); | |||||
| /* P6 board test: start four independent PULSE/DIR axes together. */ | /* P6 board test: start four independent PULSE/DIR axes together. */ | ||||
| PLSR_RESULT PlsrFourAxisSelfTestQueue(void); | 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 | #ifdef __cplusplus | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -172,6 +172,7 @@ typedef struct | |||||
| int64_t logicalPosition; | int64_t logicalPosition; | ||||
| int64_t taskPulses; | int64_t taskPulses; | ||||
| int64_t totalPulses; | int64_t totalPulses; | ||||
| uint64_t physicalPulses; | |||||
| uint8_t busy; | uint8_t busy; | ||||
| uint8_t pulseActive; | uint8_t pulseActive; | ||||
| uint8_t done; | uint8_t done; | ||||
| @@ -187,9 +188,14 @@ typedef struct | |||||
| uint8_t positiveLimitActive; | uint8_t positiveLimitActive; | ||||
| uint8_t negativeLimitActive; | uint8_t negativeLimitActive; | ||||
| uint8_t emergencyLatched; | uint8_t emergencyLatched; | ||||
| uint8_t backlashActive; | |||||
| uint16_t segmentCount; | uint16_t segmentCount; | ||||
| uint16_t startSegment; | uint16_t startSegment; | ||||
| uint16_t currentSegment; | uint16_t currentSegment; | ||||
| uint32_t currentFrequencyHz; | |||||
| uint32_t targetFrequencyHz; | |||||
| uint32_t liveFrequencyRejectCount; | |||||
| PLSR_RESULT lastLiveFrequencyResult; | |||||
| } PLSR_STATUS; | } PLSR_STATUS; | ||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||
| @@ -23,6 +23,7 @@ typedef struct | |||||
| uint16_t compatibleErrorBlock; | uint16_t compatibleErrorBlock; | ||||
| PLSR_STOP_REASON stopReason; | PLSR_STOP_REASON stopReason; | ||||
| PLSR_STATE pendingTerminal; | PLSR_STATE pendingTerminal; | ||||
| PLSR_STATE pauseReturnState; | |||||
| PLSR_RESOURCE_LEASE lease; | PLSR_RESOURCE_LEASE lease; | ||||
| PLSR_JOB_SNAPSHOT job; | PLSR_JOB_SNAPSHOT job; | ||||
| PLSR_PARSE_DETAIL parseDetail; | PLSR_PARSE_DETAIL parseDetail; | ||||
| @@ -33,12 +34,19 @@ typedef struct | |||||
| int64_t logicalPosition; | int64_t logicalPosition; | ||||
| int64_t taskPulses; | int64_t taskPulses; | ||||
| int64_t totalPulses; | int64_t totalPulses; | ||||
| uint64_t physicalPulses; | |||||
| int64_t segmentAccountedPulses; | int64_t segmentAccountedPulses; | ||||
| int64_t equivalentCommandRemainder; | int64_t equivalentCommandRemainder; | ||||
| PLSR_EQUIVALENT_CONFIG equivalent; | PLSR_EQUIVALENT_CONFIG equivalent; | ||||
| PLSR_PATH_CONTEXT path; | PLSR_PATH_CONTEXT path; | ||||
| PLSR_PROFILE_STATE profile; | 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 profileWasAccel; | ||||
| uint8_t hasLastCommand; | uint8_t hasLastCommand; | ||||
| uint8_t done; | uint8_t done; | ||||
| @@ -52,6 +60,11 @@ typedef struct | |||||
| uint8_t negativeLimitActive; | uint8_t negativeLimitActive; | ||||
| uint8_t emergencyLatched; | uint8_t emergencyLatched; | ||||
| uint8_t segmentEventPublished; | uint8_t segmentEventPublished; | ||||
| uint8_t backlashActive; | |||||
| uint8_t backlashBypassOnce; | |||||
| uint8_t lastUserDirectionValid; | |||||
| uint8_t lastUserDirectionPositive; | |||||
| uint8_t runtimeSpeedClamped; | |||||
| } PLSR_AXIS; | } PLSR_AXIS; | ||||
| typedef struct | typedef struct | ||||
| @@ -70,6 +83,8 @@ static PLSR_COMMAND_SLOT PlsrCommandQueue[PLSR_COMMAND_QUEUE_DEPTH]; | |||||
| static uint32_t PlsrNextTicket; | static uint32_t PlsrNextTicket; | ||||
| static uint8_t PlsrInitialized; | static uint8_t PlsrInitialized; | ||||
| static PLSR_JOB_SNAPSHOT PlsrJobScratch; | 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 void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject); | ||||
| static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | ||||
| @@ -105,6 +120,23 @@ static void PlsrCoreExitCritical(uint32_t interruptState) | |||||
| #endif | #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) | static uint8_t PlsrStateIsBusy(PLSR_STATE state) | ||||
| { | { | ||||
| return ((state == PLSR_STATE_ACCEL) || (state == PLSR_STATE_RUN) | 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) | 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) | if (signedPulses > INT32_MAX) | ||||
| { | { | ||||
| @@ -452,16 +490,26 @@ static int64_t PlsrGetBrakingDistance(const PLSR_AXIS *axisObject) | |||||
| uint64_t frequencyHz; | uint64_t frequencyHz; | ||||
| uint64_t denominator; | uint64_t denominator; | ||||
| uint64_t numerator; | 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; | return 0; | ||||
| } | } | ||||
| frequencyHz = axisObject->profile.frequencyQ32 >> 32U; | |||||
| frequencyHz = frequencyQ32 >> 32U; | |||||
| numerator = frequencyHz * frequencyHz; | numerator = frequencyHz * frequencyHz; | ||||
| denominator = UINT64_C(2000) | |||||
| * axisObject->profile.decelSlopeHzPerMs; | |||||
| denominator = UINT64_C(2000) * decelSlopeHzPerMs; | |||||
| return (int64_t)((numerator + denominator - 1UL) / denominator); | 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 (result != PLSR_RESULT_OK) return result; | ||||
| if ((job->limits.softLimitEnabled != 0U) | if ((job->limits.softLimitEnabled != 0U) | ||||
| && (axisObject->backlashActive == 0U) | |||||
| && (axisObject->positionValid != 0U)) | && (axisObject->positionValid != 0U)) | ||||
| { | { | ||||
| if (includeBrakingDistance != 0U) | if (includeBrakingDistance != 0U) | ||||
| @@ -591,6 +640,21 @@ static void PlsrAccountHardwarePulses(uint8_t axis, | |||||
| { | { | ||||
| return; | 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; | signedDelta = (axisObject->directionPositive != 0U) ? delta : -delta; | ||||
| if ((PlsrAddInt64Checked(axisObject->logicalPosition, | if ((PlsrAddInt64Checked(axisObject->logicalPosition, | ||||
| signedDelta, | signedDelta, | ||||
| @@ -676,6 +740,7 @@ static uint8_t PlsrTransitionIsAllowed(PLSR_STATE current, | |||||
| case PLSR_STATE_PAUSED: | case PLSR_STATE_PAUSED: | ||||
| return ((target == PLSR_STATE_ACCEL) | return ((target == PLSR_STATE_ACCEL) | ||||
| || (target == PLSR_STATE_RUN) | || (target == PLSR_STATE_RUN) | ||||
| || (target == PLSR_STATE_WAIT) | |||||
| || (target == PLSR_STATE_STOPPED)) | || (target == PLSR_STATE_STOPPED)) | ||||
| ? 1U | ? 1U | ||||
| : 0U; | : 0U; | ||||
| @@ -715,6 +780,8 @@ PLSR_RESULT PlsrStateTransition(uint8_t axis, | |||||
| axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | ||||
| axisObject->immediateStopPending = 0U; | axisObject->immediateStopPending = 0U; | ||||
| PlsrStopSegmentHardware(axis, axisObject); | PlsrStopSegmentHardware(axis, axisObject); | ||||
| axisObject->backlashActive = 0U; | |||||
| axisObject->backlashBypassOnce = 0U; | |||||
| PlsrResourceRelease(&axisObject->lease); | PlsrResourceRelease(&axisObject->lease); | ||||
| PlsrPublishAxis(axis); | PlsrPublishAxis(axis); | ||||
| return PLSR_RESULT_INVALID_STATE; | return PLSR_RESULT_INVALID_STATE; | ||||
| @@ -873,6 +940,30 @@ static uint8_t PlsrPopHighestPriorityCommand(PLSR_COMMAND_SLOT *slot) | |||||
| } | } | ||||
| #ifdef PLSR_HOST_TEST | #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_RESULT PlsrPostStart(const PLSR_START_REQUEST *request) | ||||
| { | { | ||||
| PLSR_COMMAND command; | PLSR_COMMAND command; | ||||
| @@ -921,6 +1012,35 @@ PLSR_RESULT PlsrPostCall(const PLSR_CALL *call) | |||||
| return PlsrQueueCommand(&command, NULL, 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) | PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command) | ||||
| { | { | ||||
| if (command == NULL) | if (command == NULL) | ||||
| @@ -1021,12 +1141,21 @@ static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, | |||||
| axisObject->compatibleErrorBlock = 0U; | axisObject->compatibleErrorBlock = 0U; | ||||
| axisObject->stopReason = PLSR_STOP_REASON_NONE; | axisObject->stopReason = PLSR_STOP_REASON_NONE; | ||||
| axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | ||||
| axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; | |||||
| axisObject->immediateStopPending = 0U; | axisObject->immediateStopPending = 0U; | ||||
| axisObject->done = 0U; | axisObject->done = 0U; | ||||
| axisObject->jobValid = 0U; | axisObject->jobValid = 0U; | ||||
| axisObject->taskPulses = 0; | axisObject->taskPulses = 0; | ||||
| axisObject->segmentAccountedPulses = 0; | axisObject->segmentAccountedPulses = 0; | ||||
| axisObject->segmentAccountingActive = 0U; | 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, | result = PlsrStateTransition(start->axis, | ||||
| PLSR_STATE_ACCEL, | PLSR_STATE_ACCEL, | ||||
| PLSR_TRANSITION_START); | PLSR_TRANSITION_START); | ||||
| @@ -1141,12 +1270,21 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, | |||||
| axisObject->compatibleErrorBlock = 0U; | axisObject->compatibleErrorBlock = 0U; | ||||
| axisObject->stopReason = PLSR_STOP_REASON_NONE; | axisObject->stopReason = PLSR_STOP_REASON_NONE; | ||||
| axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | ||||
| axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; | |||||
| axisObject->immediateStopPending = 0U; | axisObject->immediateStopPending = 0U; | ||||
| axisObject->done = 0U; | axisObject->done = 0U; | ||||
| axisObject->taskPulses = 0; | axisObject->taskPulses = 0; | ||||
| axisObject->segmentAccountedPulses = 0; | axisObject->segmentAccountedPulses = 0; | ||||
| axisObject->segmentAccountingActive = 0U; | axisObject->segmentAccountingActive = 0U; | ||||
| axisObject->segmentEventPublished = 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, | PlsrPathBegin(&axisObject->path, | ||||
| &axisObject->job, | &axisObject->job, | ||||
| axisObject->logicalPosition); | axisObject->logicalPosition); | ||||
| @@ -1252,11 +1390,15 @@ static PLSR_RESULT PlsrRequestControlledStop(uint8_t axis, | |||||
| { | { | ||||
| PLSR_AXIS *axisObject = &PlsrAxes[axis]; | PLSR_AXIS *axisObject = &PlsrAxes[axis]; | ||||
| PLSR_RESULT result; | PLSR_RESULT result; | ||||
| uint32_t interruptState; | |||||
| PlsrSetStopReason(axisObject, reason); | PlsrSetStopReason(axisObject, reason); | ||||
| axisObject->pendingTerminal = terminal; | 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) | if ((axisObject->state == PLSR_STATE_WAIT) | ||||
| || (axisObject->state == PLSR_STATE_PAUSED)) | || (axisObject->state == PLSR_STATE_PAUSED)) | ||||
| @@ -1274,7 +1416,9 @@ static PLSR_RESULT PlsrRequestControlledStop(uint8_t axis, | |||||
| PLSR_STATE_DECEL, | PLSR_STATE_DECEL, | ||||
| PLSR_TRANSITION_DECEL_REQUEST); | PLSR_TRANSITION_DECEL_REQUEST); | ||||
| } | } | ||||
| interruptState = PlsrCoreEnterCritical(); | |||||
| result = PlsrProfileRequestStop(&axisObject->profile); | result = PlsrProfileRequestStop(&axisObject->profile); | ||||
| PlsrCoreExitCritical(interruptState); | |||||
| if (result != PLSR_RESULT_OK) | if (result != PLSR_RESULT_OK) | ||||
| { | { | ||||
| return result; | return result; | ||||
| @@ -1315,6 +1459,7 @@ static PLSR_RESULT PlsrStopDecel(uint8_t axis) | |||||
| static PLSR_RESULT PlsrPause(uint8_t axis) | static PLSR_RESULT PlsrPause(uint8_t axis) | ||||
| { | { | ||||
| PLSR_AXIS *axisObject = &PlsrAxes[axis]; | PLSR_AXIS *axisObject = &PlsrAxes[axis]; | ||||
| uint32_t interruptState; | |||||
| if (axisObject->state == PLSR_STATE_PAUSED) | if (axisObject->state == PLSR_STATE_PAUSED) | ||||
| { | { | ||||
| @@ -1324,24 +1469,107 @@ static PLSR_RESULT PlsrPause(uint8_t axis) | |||||
| { | { | ||||
| return PLSR_RESULT_BUSY; | 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) | if ((axisObject->state != PLSR_STATE_ACCEL) | ||||
| && (axisObject->state != PLSR_STATE_RUN) | && (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; | 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, | return PlsrRequestControlledStop(axis, | ||||
| PLSR_STATE_PAUSED, | PLSR_STATE_PAUSED, | ||||
| PLSR_STOP_REASON_PAUSE); | 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) | static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) | ||||
| { | { | ||||
| PLSR_AXIS *axisObject = &PlsrAxes[slot->command.axis]; | PLSR_AXIS *axisObject = &PlsrAxes[slot->command.axis]; | ||||
| @@ -1376,18 +1604,7 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) | |||||
| break; | break; | ||||
| case PLSR_CMD_RESUME: | 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; | break; | ||||
| case PLSR_CMD_SET_POSITION: | case PLSR_CMD_SET_POSITION: | ||||
| @@ -1657,6 +1874,11 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | |||||
| int64_t targetPosition; | int64_t targetPosition; | ||||
| int64_t nextEquivalentRemainder = | int64_t nextEquivalentRemainder = | ||||
| axisObject->equivalentCommandRemainder; | axisObject->equivalentCommandRemainder; | ||||
| int64_t outputPulses; | |||||
| int32_t liveFrequencyRaw; | |||||
| uint32_t gapSlopeHzPerMs; | |||||
| uint16_t backlashPulses = 0U; | |||||
| uint8_t runBacklash = 0U; | |||||
| uint8_t positive; | uint8_t positive; | ||||
| if (job->positioningMode == 0U) | if (job->positioningMode == 0U) | ||||
| @@ -1737,48 +1959,114 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | |||||
| return PLSR_RESULT_OK; | 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)); | (void)memset(&profileRequest, 0, sizeof(profileRequest)); | ||||
| profileRequest.targetFrequencyHz = segment->targetFrequency; | profileRequest.targetFrequencyHz = segment->targetFrequency; | ||||
| profileRequest.startFrequencyHz = job->s2.startSpeed; | |||||
| profileRequest.stopFrequencyHz = job->s2.stopSpeed; | |||||
| profileRequest.maxFrequencyHz = job->s2.maximumSpeed; | 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; | 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, | result = PlsrProfileStart(&axisObject->profile, | ||||
| &profileRequest, | &profileRequest, | ||||
| pulses, | |||||
| 1000U); | |||||
| outputPulses, | |||||
| (job->s2.refreshCode == 2U) ? 10000U : 1000U); | |||||
| if (result != PLSR_RESULT_OK) | if (result != PLSR_RESULT_OK) | ||||
| { | { | ||||
| return result; | 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.outputMode = (PLSR_OUTPUT_MODE)job->outputMode; | ||||
| params.directionPoint = job->directionPoint; | params.directionPoint = job->directionPoint; | ||||
| params.directionPositive = positive; | params.directionPositive = positive; | ||||
| params.directionNegativeLogic = job->directionNegativeLogic; | |||||
| params.directionDelayMs = job->s2.directionDelayMs; | params.directionDelayMs = job->s2.directionDelayMs; | ||||
| result = PlsrHwStartPulse(axis, ¶ms); | result = PlsrHwStartPulse(axis, ¶ms); | ||||
| if (result != PLSR_RESULT_OK) | if (result != PLSR_RESULT_OK) | ||||
| { | { | ||||
| axisObject->profileActive = 0U; | |||||
| PlsrSetProfileActive(axisObject, 0U); | |||||
| axisObject->profileWasAccel = 0U; | axisObject->profileWasAccel = 0U; | ||||
| return result; | return result; | ||||
| } | } | ||||
| axisObject->equivalentCommandRemainder = nextEquivalentRemainder; | |||||
| axisObject->directionPositive = positive; | axisObject->directionPositive = positive; | ||||
| axisObject->segmentAccountedPulses = 0; | axisObject->segmentAccountedPulses = 0; | ||||
| axisObject->segmentAccountingActive = 1U; | 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->profileWasAccel = | ||||
| (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; | (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; | ||||
| PlsrPublishAxis(axis); | PlsrPublishAxis(axis); | ||||
| @@ -1795,7 +2083,7 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | |||||
| static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) | static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) | ||||
| { | { | ||||
| PlsrAccountHardwarePulses(axis, axisObject); | PlsrAccountHardwarePulses(axis, axisObject); | ||||
| axisObject->profileActive = 0U; | |||||
| PlsrSetProfileActive(axisObject, 0U); | |||||
| axisObject->profileWasAccel = 0U; | axisObject->profileWasAccel = 0U; | ||||
| (void)PlsrHwStopPulse(axis); | (void)PlsrHwStopPulse(axis); | ||||
| PlsrAccountHardwarePulses(axis, axisObject); | PlsrAccountHardwarePulses(axis, axisObject); | ||||
| @@ -1973,6 +2261,50 @@ static void PlsrProcessNormalEvents(uint8_t axis, uint32_t events) | |||||
| { | { | ||||
| PLSR_PATH_ACTION action; | 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, | PlsrPublishSegmentEvent(axis, | ||||
| axisObject, | axisObject, | ||||
| PLSR_STOP_REASON_NORMAL_COMPLETE); | PLSR_STOP_REASON_NORMAL_COMPLETE); | ||||
| @@ -2006,6 +2338,7 @@ PLSR_RESULT PlsrInit(void) | |||||
| PlsrNextTicket = 0UL; | PlsrNextTicket = 0UL; | ||||
| PlsrResourceInit(); | PlsrResourceInit(); | ||||
| (void)PlsrHwInit(); | (void)PlsrHwInit(); | ||||
| PlsrControlTickHook = NULL; | |||||
| PlsrInitialized = 1U; | PlsrInitialized = 1U; | ||||
| restoredPositionValid = PlcDeviceGetRestoredHsdPositionValid(); | restoredPositionValid = PlcDeviceGetRestoredHsdPositionValid(); | ||||
| @@ -2039,6 +2372,156 @@ PLSR_RESULT PlsrInit(void) | |||||
| return PLSR_RESULT_OK; | 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) | void PlsrProcess(void) | ||||
| { | { | ||||
| PLSR_COMMAND_SLOT slot; | 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) | while ((processedCommands < PLSR_COMMAND_QUEUE_DEPTH) | ||||
| && (PlsrPopHighestPriorityCommand(&slot) != 0U)) | && (PlsrPopHighestPriorityCommand(&slot) != 0U)) | ||||
| { | { | ||||
| @@ -2101,18 +2587,14 @@ void PlsrProcess(void) | |||||
| } | } | ||||
| } | } | ||||
| PlsrHwEndDirectionBatch(); | |||||
| /* 1ms tick:路径执行器推进(WAIT/ACT 计时、信号/EXT 轮询、跳转链) | /* 1ms tick:路径执行器推进(WAIT/ACT 计时、信号/EXT 轮询、跳转链) | ||||
| * + 速度曲线推进(P2) + HAL 状态机(DIR 延时)。 */ | * + 速度曲线推进(P2) + HAL 状态机(DIR 延时)。 */ | ||||
| for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | ||||
| { | { | ||||
| PLSR_AXIS *axisObject = &PlsrAxes[axis]; | PLSR_AXIS *axisObject = &PlsrAxes[axis]; | ||||
| PLSR_PATH_ACTION action; | PLSR_PATH_ACTION action; | ||||
| uint32_t frequencyHz; | |||||
| uint32_t outputFrequencyHz; | |||||
| uint64_t hardwarePulses; | |||||
| uint64_t remainingPulses; | |||||
| uint8_t profileDone; | |||||
| uint8_t wasAccel; | |||||
| PlsrHwTick(axis); | PlsrHwTick(axis); | ||||
| PlsrAccountHardwarePulses(axis, axisObject); | PlsrAccountHardwarePulses(axis, axisObject); | ||||
| @@ -2126,75 +2608,19 @@ void PlsrProcess(void) | |||||
| { | { | ||||
| continue; | 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) | if (action != PLSR_PATH_ACTION_NONE) | ||||
| { | { | ||||
| PlsrApplyPathAction(axis, action); | 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->logicalPosition = axisObject->logicalPosition; | ||||
| status->taskPulses = axisObject->taskPulses; | status->taskPulses = axisObject->taskPulses; | ||||
| status->totalPulses = axisObject->totalPulses; | status->totalPulses = axisObject->totalPulses; | ||||
| status->physicalPulses = axisObject->physicalPulses; | |||||
| status->busy = PlsrStateIsBusy(axisObject->state); | status->busy = PlsrStateIsBusy(axisObject->state); | ||||
| status->pulseActive = PlsrStateIsPulseActive(axisObject->state); | status->pulseActive = PlsrStateIsPulseActive(axisObject->state); | ||||
| status->done = axisObject->done; | status->done = axisObject->done; | ||||
| @@ -2255,9 +2682,12 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status) | |||||
| status->positiveLimitActive = axisObject->positiveLimitActive; | status->positiveLimitActive = axisObject->positiveLimitActive; | ||||
| status->negativeLimitActive = axisObject->negativeLimitActive; | status->negativeLimitActive = axisObject->negativeLimitActive; | ||||
| status->emergencyLatched = axisObject->emergencyLatched; | status->emergencyLatched = axisObject->emergencyLatched; | ||||
| status->backlashActive = axisObject->backlashActive; | |||||
| status->s2Set = (axisObject->jobValid != 0U) ? axisObject->job.s2Set : 0U; | status->s2Set = (axisObject->jobValid != 0U) ? axisObject->job.s2Set : 0U; | ||||
| status->speedClamped = (axisObject->jobValid != 0U) | status->speedClamped = (axisObject->jobValid != 0U) | ||||
| ? axisObject->job.speedClamped | |||||
| ? (uint8_t)((axisObject->job.speedClamped != 0U) | |||||
| || (axisObject->runtimeSpeedClamped | |||||
| != 0U)) | |||||
| : 0U; | : 0U; | ||||
| status->segmentCount = (axisObject->jobValid != 0U) | status->segmentCount = (axisObject->jobValid != 0U) | ||||
| ? axisObject->job.segmentCount | ? axisObject->job.segmentCount | ||||
| @@ -2269,6 +2699,14 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status) | |||||
| ? PlsrPathGetCurrentSegment( | ? PlsrPathGetCurrentSegment( | ||||
| &axisObject->path) | &axisObject->path) | ||||
| : 0U; | : 0U; | ||||
| status->currentFrequencyHz = PlsrHwGetCurrentFrequencyHz(axis); | |||||
| status->targetFrequencyHz = (axisObject->jobValid != 0U) | |||||
| ? axisObject->liveTargetFrequencyHz | |||||
| : 0UL; | |||||
| status->liveFrequencyRejectCount = | |||||
| axisObject->liveFrequencyRejectCount; | |||||
| status->lastLiveFrequencyResult = | |||||
| axisObject->lastLiveFrequencyResult; | |||||
| PlsrCoreExitCritical(interruptState); | PlsrCoreExitCritical(interruptState); | ||||
| return PLSR_RESULT_OK; | return PLSR_RESULT_OK; | ||||
| } | } | ||||
| @@ -145,7 +145,11 @@ typedef struct | |||||
| int64_t emittedPulses; | int64_t emittedPulses; | ||||
| uint16_t directionDelayRemainingMs; | uint16_t directionDelayRemainingMs; | ||||
| uint8_t directionPoint; | uint8_t directionPoint; | ||||
| uint8_t configuredDirectionPoint; | |||||
| uint8_t directionPositive; | uint8_t directionPositive; | ||||
| uint8_t directionNegativeLogic; | |||||
| uint8_t directionTerminalOn; | |||||
| uint8_t directionOutputPending; | |||||
| uint8_t abQuarter; | uint8_t abQuarter; | ||||
| uint8_t abCountAxis; | uint8_t abCountAxis; | ||||
| uint8_t abStartupPriming; | uint8_t abStartupPriming; | ||||
| @@ -157,9 +161,12 @@ typedef struct | |||||
| uint16_t abPendingPairPsc; | uint16_t abPendingPairPsc; | ||||
| uint16_t abPendingArr; | uint16_t abPendingArr; | ||||
| uint8_t abFrequencyPending; | uint8_t abFrequencyPending; | ||||
| uint8_t cwActiveAxis; | |||||
| uint8_t cwStopPending; | |||||
| } PLSR_HW_AXIS_STATE; | } PLSR_HW_AXIS_STATE; | ||||
| static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT]; | static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT]; | ||||
| static uint8_t PlsrHwDirectionBatchActive; | |||||
| /* 调试快照:当前上板自测只记录 Q0 的 160 ms,避免四轴 | /* 调试快照:当前上板自测只记录 Q0 的 160 ms,避免四轴 | ||||
| * PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示段启动, | * PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示段启动, | ||||
| @@ -395,19 +402,19 @@ static uint8_t PlsrHwTimerHasCc1if(uint8_t axis) | |||||
| /* ---- DIR 输出 ---- | /* ---- DIR 输出 ---- | ||||
| * XDM 为晶体管(NPN 漏型)输出:ON(导通)= 引脚低电平。 | * 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]; | PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; | ||||
| state->directionPositive = (positive != 0U) ? 1U : 0U; | |||||
| if (state->directionPoint == PLSR_HW_DIR_POINT_NONE) | if (state->directionPoint == PLSR_HW_DIR_POINT_NONE) | ||||
| { | { | ||||
| return; | return; | ||||
| } | } | ||||
| #ifdef PLSR_HOST_TEST | #ifdef PLSR_HOST_TEST | ||||
| PlsrHwTimers[axis].dirLevel = (positive != 0U) ? 1U : 0U; | |||||
| PlsrHwTimers[axis].dirLevel = state->directionTerminalOn; | |||||
| state->configuredDirectionPoint = state->directionPoint; | |||||
| #else | #else | ||||
| if (state->directionPoint < PLSR_HW_OUTPUT_POINT_COUNT) | 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) | if (pin->port != NULL) | ||||
| { | { | ||||
| /* DIR 点按需配置为推挽输出(上电默认高阻=截止,安全)。 */ | /* 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(导通)= 低电平。 */ | /* 漏型输出:ON(导通)= 低电平。 */ | ||||
| HAL_GPIO_WritePin(pin->port, | |||||
| pin->pin, | |||||
| (positive != 0U) ? GPIO_PIN_RESET | |||||
| : GPIO_PIN_SET); | |||||
| } | } | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -438,6 +448,63 @@ static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive) | |||||
| * 避免 ARR 变小瞬间 CNT 超调提前回绕(每段加速会多出 ~ln(f1/f0) 个假脉冲)。 | * 避免 ARR 变小瞬间 CNT 超调提前回绕(每段加速会多出 ~ln(f1/f0) 个假脉冲)。 | ||||
| * 首次启动用 EGR.UG 把预装载值加载到影子寄存器,杜绝首个周期用复位值。 */ | * 首次启动用 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) | static void PlsrHwTimerSetArpe(uint8_t axis, uint32_t value) | ||||
| { | { | ||||
| #ifdef PLSR_HOST_TEST | #ifdef PLSR_HOST_TEST | ||||
| @@ -470,6 +537,9 @@ static void PlsrHwConfigurePwm(uint8_t axis, uint32_t frequencyHz) | |||||
| { | { | ||||
| uint16_t psc; | uint16_t psc; | ||||
| uint16_t arr; | uint16_t arr; | ||||
| #ifndef PLSR_HOST_TEST | |||||
| uint32_t interruptState; | |||||
| #endif | |||||
| if (PlsrCalculateTimerDivider(PlsrHwAxisMap[axis].timerClockHz, | if (PlsrCalculateTimerDivider(PlsrHwAxisMap[axis].timerClockHz, | ||||
| frequencyHz, | frequencyHz, | ||||
| @@ -478,11 +548,23 @@ static void PlsrHwConfigurePwm(uint8_t axis, uint32_t frequencyHz) | |||||
| { | { | ||||
| return; | return; | ||||
| } | } | ||||
| #ifndef PLSR_HOST_TEST | |||||
| interruptState = __get_PRIMASK(); | |||||
| __disable_irq(); | |||||
| __DMB(); | |||||
| #endif | |||||
| PlsrHwTimerSetPsc(axis, psc); | PlsrHwTimerSetPsc(axis, psc); | ||||
| PlsrHwTimerSetArr(axis, arr); | PlsrHwTimerSetArr(axis, arr); | ||||
| PlsrHwTimerSetCcr(axis, (uint32_t)arr / 2UL); /* 50% 占空比 */ | PlsrHwTimerSetCcr(axis, (uint32_t)arr / 2UL); /* 50% 占空比 */ | ||||
| PlsrHwTimerSetPwmMode1(axis); | PlsrHwTimerSetPwmMode1(axis); | ||||
| PlsrHwTimerSetArpe(axis, 1UL); | 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); | 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, | static void PlsrHwConfigureActiveOutput(uint8_t axis, | ||||
| PLSR_OUTPUT_MODE outputMode, | PLSR_OUTPUT_MODE outputMode, | ||||
| uint32_t frequencyHz) | uint32_t frequencyHz) | ||||
| @@ -767,6 +948,10 @@ static void PlsrHwConfigureActiveOutput(uint8_t axis, | |||||
| { | { | ||||
| (void)PlsrHwConfigureAbPwm(axis, frequencyHz); | (void)PlsrHwConfigureAbPwm(axis, frequencyHz); | ||||
| } | } | ||||
| else if (outputMode == PLSR_OUTPUT_CW_CCW) | |||||
| { | |||||
| PlsrHwConfigureCwCcwPwm(axis, frequencyHz); | |||||
| } | |||||
| else | else | ||||
| { | { | ||||
| PlsrHwConfigurePwm(axis, frequencyHz); | PlsrHwConfigurePwm(axis, frequencyHz); | ||||
| @@ -780,6 +965,10 @@ static void PlsrHwBeginActiveOutput(uint8_t axis, | |||||
| { | { | ||||
| PlsrHwBeginAbOutput(axis, 0U); | PlsrHwBeginAbOutput(axis, 0U); | ||||
| } | } | ||||
| else if (outputMode == PLSR_OUTPUT_CW_CCW) | |||||
| { | |||||
| PlsrHwBeginCwCcwOutput(axis); | |||||
| } | |||||
| else | else | ||||
| { | { | ||||
| PlsrHwPwmBegin(axis); | PlsrHwPwmBegin(axis); | ||||
| @@ -812,6 +1001,11 @@ static void PlsrHwStopActiveOutput(uint8_t axis, | |||||
| } | } | ||||
| #endif | #endif | ||||
| } | } | ||||
| else if ((outputMode == PLSR_OUTPUT_CW_CCW) | |||||
| && (PlsrHwIsAbBaseAxis(axis) != 0U)) | |||||
| { | |||||
| PlsrHwStopCwCcwOutput(axis); | |||||
| } | |||||
| else | else | ||||
| { | { | ||||
| PlsrHwStopPwmTimer(axis); | PlsrHwStopPwmTimer(axis); | ||||
| @@ -845,10 +1039,13 @@ PLSR_RESULT PlsrHwInit(void) | |||||
| uint8_t axis; | uint8_t axis; | ||||
| (void)memset(PlsrHwAxes, 0, sizeof(PlsrHwAxes)); | (void)memset(PlsrHwAxes, 0, sizeof(PlsrHwAxes)); | ||||
| PlsrHwDirectionBatchActive = 0U; | |||||
| for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++) | for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++) | ||||
| { | { | ||||
| PlsrHwAxes[axis].state = PLSR_HW_STATE_IDLE; | PlsrHwAxes[axis].state = PLSR_HW_STATE_IDLE; | ||||
| PlsrHwAxes[axis].directionPoint = PLSR_HW_DIR_POINT_NONE; | PlsrHwAxes[axis].directionPoint = PLSR_HW_DIR_POINT_NONE; | ||||
| PlsrHwAxes[axis].configuredDirectionPoint = | |||||
| PLSR_HW_DIR_POINT_NONE; | |||||
| #ifdef PLSR_HOST_TEST | #ifdef PLSR_HOST_TEST | ||||
| (void)memset(&PlsrHwTimers[axis], 0, sizeof(PlsrHwTimers[axis])); | (void)memset(&PlsrHwTimers[axis], 0, sizeof(PlsrHwTimers[axis])); | ||||
| #else | #else | ||||
| @@ -862,6 +1059,9 @@ PLSR_RESULT PlsrHwInit(void) | |||||
| #ifndef PLSR_HOST_TEST | #ifndef PLSR_HOST_TEST | ||||
| { | { | ||||
| GPIO_InitTypeDef gpio; | GPIO_InitTypeDef gpio; | ||||
| uint32_t tim6ClockHz; | |||||
| uint16_t tim6Psc; | |||||
| uint16_t tim6Arr; | |||||
| /* 1. 输出点 GPIO 时钟(DIR 点按需配置时使用)。 */ | /* 1. 输出点 GPIO 时钟(DIR 点按需配置时使用)。 */ | ||||
| __HAL_RCC_GPIOF_CLK_ENABLE(); | __HAL_RCC_GPIOF_CLK_ENABLE(); | ||||
| @@ -879,6 +1079,31 @@ PLSR_RESULT PlsrHwInit(void) | |||||
| __HAL_RCC_TIM11_CLK_ENABLE(); | __HAL_RCC_TIM11_CLK_ENABLE(); | ||||
| __HAL_RCC_TIM13_CLK_ENABLE(); | __HAL_RCC_TIM13_CLK_ENABLE(); | ||||
| __HAL_RCC_TIM14_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)。 | /* 4. 脉冲点切定时器复用(PF6/7=AF3、PF8/9=AF9)。 | ||||
| * 定时器通道尚未使能(CC1E=0),输出级断开,无毛刺。 */ | * 定时器通道尚未使能(CC1E=0),输出级断开,无毛刺。 */ | ||||
| @@ -923,15 +1148,12 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) | |||||
| { | { | ||||
| return PLSR_RESULT_INVALID_ARGUMENT; | 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)) | && (PlsrHwIsAbBaseAxis(axis) == 0U)) | ||||
| { | { | ||||
| return PLSR_RESULT_INVALID_AXIS; | return PLSR_RESULT_INVALID_AXIS; | ||||
| } | } | ||||
| if (params->outputMode == PLSR_OUTPUT_CW_CCW) | |||||
| { | |||||
| return PLSR_RESULT_NOT_SUPPORTED; | |||||
| } | |||||
| state = &PlsrHwAxes[axis]; | state = &PlsrHwAxes[axis]; | ||||
| if (state->state == PLSR_HW_STATE_RUNNING) | if (state->state == PLSR_HW_STATE_RUNNING) | ||||
| { | { | ||||
| @@ -945,7 +1167,9 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) | |||||
| directionChanged = | directionChanged = | ||||
| (state->directionPoint == PLSR_HW_DIR_POINT_NONE) | (state->directionPoint == PLSR_HW_DIR_POINT_NONE) | ||||
| || (state->directionPoint != params->directionPoint) | || (state->directionPoint != params->directionPoint) | ||||
| || (state->directionPositive != params->directionPositive); | |||||
| || (state->directionPositive != params->directionPositive) | |||||
| || (state->directionNegativeLogic | |||||
| != params->directionNegativeLogic); | |||||
| } | } | ||||
| state->outputMode = params->outputMode; | state->outputMode = params->outputMode; | ||||
| @@ -965,12 +1189,21 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) | |||||
| state->abFrequencyPending = 0U; | state->abFrequencyPending = 0U; | ||||
| if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) | if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) | ||||
| { | { | ||||
| PlsrHwSetDirLevel(axis, params->directionPositive); | |||||
| PlsrHwSetDirLevel(axis, | |||||
| params->directionPositive, | |||||
| params->directionNegativeLogic); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| state->directionPositive = | state->directionPositive = | ||||
| (params->directionPositive != 0U) ? 1U : 0U; | (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) | state->state = (state->directionDelayRemainingMs > 0U) | ||||
| ? PLSR_HW_STATE_DIR_SETTLING | ? PLSR_HW_STATE_DIR_SETTLING | ||||
| @@ -987,6 +1220,13 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) | |||||
| return PLSR_RESULT_INVALID_ARGUMENT; | return PLSR_RESULT_INVALID_ARGUMENT; | ||||
| } | } | ||||
| state = &PlsrHwAxes[axis]; | 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; | state->currentFrequencyHz = frequencyHz; | ||||
| if (state->state == PLSR_HW_STATE_RUNNING) | if (state->state == PLSR_HW_STATE_RUNNING) | ||||
| { | { | ||||
| @@ -1023,6 +1263,29 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) | |||||
| return PLSR_RESULT_OK; | 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_RESULT PlsrHwStopPulse(uint8_t axis) | ||||
| { | { | ||||
| PLSR_HW_AXIS_STATE *state; | PLSR_HW_AXIS_STATE *state; | ||||
| @@ -1225,6 +1488,43 @@ void PlsrHwOnTimerUpdate(uint8_t axis) | |||||
| return; | 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) | if (hasCc1 != 0U) | ||||
| { | { | ||||
| /* 非运行态/非 AB 模式的 CC1 仅作为杂散标志消费。 */ | /* 非运行态/非 AB 模式的 CC1 仅作为杂散标志消费。 */ | ||||
| @@ -1381,6 +1681,15 @@ void PlsrHwTestTriggerUpdate(uint8_t axis) | |||||
| } | } | ||||
| PlsrHwOnTimerUpdate(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 | #endif | ||||
| #ifndef PLSR_HOST_TEST | #ifndef PLSR_HOST_TEST | ||||
| @@ -1403,4 +1712,13 @@ void TIM8_TRG_COM_TIM14_IRQHandler(void) | |||||
| { | { | ||||
| PlsrHwOnTimerUpdate(3U); | PlsrHwOnTimerUpdate(3U); | ||||
| } | } | ||||
| void TIM6_DAC_IRQHandler(void) | |||||
| { | |||||
| if ((TIM6->SR & TIM_SR_UIF) != 0UL) | |||||
| { | |||||
| TIM6->SR &= ~TIM_SR_UIF; | |||||
| PlsrControlTick100us(); | |||||
| } | |||||
| } | |||||
| #endif | #endif | ||||
| @@ -137,6 +137,23 @@ static PLSR_RESULT PlsrReadInt32(const PLSR_DATA_SOURCE *source, | |||||
| segment); | segment); | ||||
| return PLSR_RESULT_ADDRESS_OVERFLOW; | 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, | result = PlsrReadWord(source, | ||||
| device, | device, | ||||
| address, | address, | ||||
| @@ -486,12 +503,23 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call, | |||||
| result = PlsrReadFixedWord(0U, commonBase, &word, detail); | result = PlsrReadFixedWord(0U, commonBase, &word, detail); | ||||
| if (result != PLSR_RESULT_OK) return result; | if (result != PLSR_RESULT_OK) return result; | ||||
| commonFlags = word; | commonFlags = word; | ||||
| snapshot->directionActiveHigh = | |||||
| snapshot->directionNegativeLogic = | |||||
| ((commonFlags & (1U << 1U)) != 0U) ? 1U : 0U; | ((commonFlags & (1U << 1U)) != 0U) ? 1U : 0U; | ||||
| snapshot->limits.softLimitEnabled = | snapshot->limits.softLimitEnabled = | ||||
| ((commonFlags & (1U << 2U)) != 0U) ? 1U : 0U; | ((commonFlags & (1U << 2U)) != 0U) ? 1U : 0U; | ||||
| snapshot->equivalent.unitCode = | snapshot->equivalent.unitCode = | ||||
| (uint8_t)((commonFlags >> 8U) & 0x07U); | (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) | if (PlsrPositionUnitCodeIsValid(snapshot->equivalent.unitCode) == 0U) | ||||
| { | { | ||||
| PlsrSetDetail(detail, | PlsrSetDetail(detail, | ||||
| @@ -681,6 +709,28 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call, | |||||
| return PLSR_RESULT_OK; | 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( | static PLSR_RESULT PlsrValidateVariableReference( | ||||
| const PLSR_CALL *call, | const PLSR_CALL *call, | ||||
| uint8_t sourceCode, | uint8_t sourceCode, | ||||
| @@ -1447,7 +1497,6 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot, | |||||
| uint8_t *clamped) | uint8_t *clamped) | ||||
| { | { | ||||
| int32_t rawFrequency; | int32_t rawFrequency; | ||||
| uint32_t segmentAddress; | |||||
| PLSR_RESULT result; | PLSR_RESULT result; | ||||
| if ((snapshot == NULL) || (frequency == NULL) || (clamped == NULL) | if ((snapshot == NULL) || (frequency == NULL) || (clamped == NULL) | ||||
| @@ -1463,15 +1512,7 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot, | |||||
| *clamped = 0U; | *clamped = 0U; | ||||
| return PLSR_RESULT_OK; | 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 (result != PLSR_RESULT_OK) return result; | ||||
| if (rawFrequency < 0) return PLSR_RESULT_INVALID_FREQUENCY; | if (rawFrequency < 0) return PLSR_RESULT_INVALID_FREQUENCY; | ||||
| *frequency = (rawFrequency == 0) ? snapshot->inputDefaultSpeed | *frequency = (rawFrequency == 0) ? snapshot->inputDefaultSpeed | ||||
| @@ -0,0 +1,570 @@ | |||||
| #include "plsr_modbus_control.h" | |||||
| #include "modbus_data_store.h" | |||||
| #include "plsr_address_map.h" | |||||
| #include "plsr_core.h" | |||||
| #include "plsr_job.h" | |||||
| #include "plsr_modbus_data.h" | |||||
| #include <stddef.h> | |||||
| #include <string.h> | |||||
| #define PLSR_MODBUS_MAGIC_LOW (0x504CU) | |||||
| #define PLSR_MODBUS_MAGIC_HIGH (0x5352U) | |||||
| #define PLSR_MODBUS_CAPABILITIES (0x0007U) | |||||
| #define PLSR_MODBUS_CALL_REQUEST_WORDS (16UL) | |||||
| #define PLSR_MODBUS_CALL_RESPONSE_WORDS (12UL) | |||||
| #define PLSR_MODBUS_COMMAND_REQUEST_WORDS (8UL) | |||||
| #define PLSR_MODBUS_COMMAND_RESPONSE_WORDS (8UL) | |||||
| #define PLSR_MODBUS_S0_HEADER_WORDS (10UL) | |||||
| #define PLSR_MODBUS_S0_SEGMENT_WORDS (10UL) | |||||
| #define PLSR_MODBUS_S1_WORDS (4UL) | |||||
| #define PLSR_MODBUS_HASH_OFFSET (2166136261UL) | |||||
| #define PLSR_MODBUS_HASH_PRIME (16777619UL) | |||||
| typedef struct | |||||
| { | |||||
| PLSR_CALL call; | |||||
| uint32_t fingerprint; | |||||
| uint8_t valid; | |||||
| } PLSR_MODBUS_COMMITTED_CALL; | |||||
| static uint16_t PlsrModbusBaseAddress; | |||||
| static uint8_t PlsrModbusEnabled; | |||||
| static uint32_t PlsrModbusLastCallRequestSequence; | |||||
| static uint32_t PlsrModbusLastCommandRequestSequence; | |||||
| static uint32_t PlsrModbusStatusGeneration[PLSR_AXIS_COUNT]; | |||||
| static PLSR_MODBUS_COMMITTED_CALL PlsrModbusCommitted[PLSR_AXIS_COUNT]; | |||||
| static uint16_t PlsrModbusStatusWords[PLSR_AXIS_COUNT] | |||||
| [PLSR_MODBUS_AXIS_STATUS_WORDS]; | |||||
| static const uint16_t PlsrModbusZeroWindow[PLSR_MODBUS_WINDOW_WORDS] = {0U}; | |||||
| static void PlsrModbusPutU32(uint16_t *words, | |||||
| uint32_t offset, | |||||
| uint32_t value) | |||||
| { | |||||
| words[offset] = (uint16_t)(value & 0xFFFFUL); | |||||
| words[offset + 1UL] = (uint16_t)(value >> 16U); | |||||
| } | |||||
| static void PlsrModbusPutU64(uint16_t *words, | |||||
| uint32_t offset, | |||||
| uint64_t value) | |||||
| { | |||||
| words[offset] = (uint16_t)(value & 0xFFFFULL); | |||||
| words[offset + 1UL] = (uint16_t)((value >> 16U) & 0xFFFFULL); | |||||
| words[offset + 2UL] = (uint16_t)((value >> 32U) & 0xFFFFULL); | |||||
| words[offset + 3UL] = (uint16_t)(value >> 48U); | |||||
| } | |||||
| static uint32_t PlsrModbusGetU32(const uint16_t *words, uint32_t offset) | |||||
| { | |||||
| return ((uint32_t)words[offset + 1UL] << 16U) | words[offset]; | |||||
| } | |||||
| static uint64_t PlsrModbusGetU64(const uint16_t *words, uint32_t offset) | |||||
| { | |||||
| return ((uint64_t)words[offset + 3UL] << 48U) | |||||
| | ((uint64_t)words[offset + 2UL] << 32U) | |||||
| | ((uint64_t)words[offset + 1UL] << 16U) | |||||
| | words[offset]; | |||||
| } | |||||
| static uint8_t PlsrModbusReadWords(uint32_t offset, | |||||
| uint16_t *words, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| uint32_t index; | |||||
| if (words == NULL) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| for (index = 0UL; index < wordCount; index++) | |||||
| { | |||||
| if (ModbusDataReadWord(MODBUS_DATA_DEVICE_D, | |||||
| (uint32_t)PlsrModbusBaseAddress + offset + index, | |||||
| &words[index]) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| static uint8_t PlsrModbusRangesOverlap(uint32_t firstA, | |||||
| uint32_t countA, | |||||
| uint32_t firstB, | |||||
| uint32_t countB) | |||||
| { | |||||
| return ((firstA < (firstB + countB)) && (firstB < (firstA + countA))) | |||||
| ? 1U | |||||
| : 0U; | |||||
| } | |||||
| static uint32_t PlsrModbusHashWord(uint32_t hash, uint16_t value) | |||||
| { | |||||
| hash ^= (uint8_t)(value & 0x00FFU); | |||||
| hash *= PLSR_MODBUS_HASH_PRIME; | |||||
| hash ^= (uint8_t)(value >> 8U); | |||||
| hash *= PLSR_MODBUS_HASH_PRIME; | |||||
| return hash; | |||||
| } | |||||
| static uint32_t PlsrModbusHashU32(uint32_t hash, uint32_t value) | |||||
| { | |||||
| hash = PlsrModbusHashWord(hash, (uint16_t)(value & 0xFFFFUL)); | |||||
| return PlsrModbusHashWord(hash, (uint16_t)(value >> 16U)); | |||||
| } | |||||
| static PLSR_RESULT PlsrModbusFingerprintCall(const PLSR_CALL *call, | |||||
| uint32_t *fingerprint) | |||||
| { | |||||
| uint16_t word; | |||||
| int32_t segmentCount; | |||||
| uint32_t s0Words; | |||||
| uint32_t index; | |||||
| uint32_t hash = PLSR_MODBUS_HASH_OFFSET; | |||||
| if ((call == NULL) || (fingerprint == NULL) | |||||
| || (call->source.readDword == NULL) | |||||
| || (call->source.readWord == NULL) | |||||
| || (call->source.validateWords == NULL)) | |||||
| { | |||||
| return PLSR_RESULT_INVALID_ARGUMENT; | |||||
| } | |||||
| if (call->source.readDword(call->source.context, | |||||
| call->s0.device, | |||||
| call->s0.address, | |||||
| &segmentCount) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| if ((segmentCount < 1) || (segmentCount > (int32_t)PLSR_MAX_SEGMENTS)) | |||||
| { | |||||
| return PLSR_RESULT_SEGMENT_OVERFLOW; | |||||
| } | |||||
| s0Words = PLSR_MODBUS_S0_HEADER_WORDS | |||||
| + (uint32_t)segmentCount * PLSR_MODBUS_S0_SEGMENT_WORDS; | |||||
| if ((call->source.validateWords(call->source.context, | |||||
| call->s0.device, | |||||
| call->s0.address, | |||||
| s0Words) == 0U) | |||||
| || (call->source.validateWords(call->source.context, | |||||
| call->s1.device, | |||||
| call->s1.address, | |||||
| PLSR_MODBUS_S1_WORDS) == 0U)) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| if (((call->s0.device == PLSR_DEVICE_D) | |||||
| && (PlsrModbusRangesOverlap(call->s0.address, | |||||
| s0Words, | |||||
| PlsrModbusBaseAddress, | |||||
| PLSR_MODBUS_WINDOW_WORDS) != 0U)) | |||||
| || ((call->s1.device == PLSR_DEVICE_D) | |||||
| && (PlsrModbusRangesOverlap(call->s1.address, | |||||
| PLSR_MODBUS_S1_WORDS, | |||||
| PlsrModbusBaseAddress, | |||||
| PLSR_MODBUS_WINDOW_WORDS) != 0U))) | |||||
| { | |||||
| return PLSR_RESULT_BLOCK_OVERLAP; | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, (uint16_t)call->s0.device); | |||||
| hash = PlsrModbusHashU32(hash, call->s0.address); | |||||
| for (index = 0UL; index < s0Words; index++) | |||||
| { | |||||
| if (call->source.readWord(call->source.context, | |||||
| call->s0.device, | |||||
| call->s0.address + index, | |||||
| &word) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, word); | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, (uint16_t)call->s1.device); | |||||
| hash = PlsrModbusHashU32(hash, call->s1.address); | |||||
| for (index = 0UL; index < PLSR_MODBUS_S1_WORDS; index++) | |||||
| { | |||||
| if (call->source.readWord(call->source.context, | |||||
| call->s1.device, | |||||
| call->s1.address + index, | |||||
| &word) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, word); | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, (uint16_t)call->s2.type); | |||||
| hash = PlsrModbusHashWord(hash, (uint16_t)call->s2.data.device); | |||||
| hash = PlsrModbusHashU32(hash, call->s2.data.address); | |||||
| hash = PlsrModbusHashU32(hash, (uint32_t)call->s2.constant); | |||||
| if (call->s2.type == PLSR_OPERAND_DATA) | |||||
| { | |||||
| for (index = 0UL; index < 2UL; index++) | |||||
| { | |||||
| if (call->source.readWord(call->source.context, | |||||
| call->s2.data.device, | |||||
| call->s2.data.address + index, | |||||
| &word) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, word); | |||||
| } | |||||
| } | |||||
| hash = PlsrModbusHashWord(hash, call->dAxis); | |||||
| hash = PlsrModbusHashWord(hash, call->outputModeOverride); | |||||
| *fingerprint = hash; | |||||
| return PLSR_RESULT_OK; | |||||
| } | |||||
| static PLSR_RESULT PlsrModbusBuildCall(const uint16_t *request, | |||||
| PLSR_CALL *call) | |||||
| { | |||||
| uint16_t s2Type; | |||||
| uint16_t outputMode; | |||||
| if ((request == NULL) || (call == NULL)) | |||||
| { | |||||
| return PLSR_RESULT_INVALID_ARGUMENT; | |||||
| } | |||||
| s2Type = request[8UL]; | |||||
| outputMode = request[13UL]; | |||||
| if ((request[2UL] > (uint16_t)PLSR_DEVICE_FD) | |||||
| || (request[5UL] > (uint16_t)PLSR_DEVICE_FD) | |||||
| || (s2Type > (uint16_t)PLSR_OPERAND_DATA) | |||||
| || ((s2Type == (uint16_t)PLSR_OPERAND_DATA) | |||||
| && (request[9UL] > (uint16_t)PLSR_DEVICE_FD)) | |||||
| || (request[12UL] >= PLSR_AXIS_COUNT) | |||||
| || ((outputMode > (uint16_t)PLSR_OUTPUT_CW_CCW) | |||||
| && (outputMode != PLSR_OUTPUT_MODE_FROM_SFD))) | |||||
| { | |||||
| return PLSR_RESULT_INVALID_ARGUMENT; | |||||
| } | |||||
| (void)memset(call, 0, sizeof(*call)); | |||||
| call->sequence = PlsrModbusGetU32(request, 0UL); | |||||
| call->s0.device = (PLSR_DEVICE_TYPE)request[2UL]; | |||||
| call->s0.address = PlsrModbusGetU32(request, 3UL); | |||||
| call->s1.device = (PLSR_DEVICE_TYPE)request[5UL]; | |||||
| call->s1.address = PlsrModbusGetU32(request, 6UL); | |||||
| call->s2.type = (PLSR_OPERAND_TYPE)s2Type; | |||||
| call->s2.data.device = (PLSR_DEVICE_TYPE)request[9UL]; | |||||
| call->s2.data.address = PlsrModbusGetU32(request, 10UL); | |||||
| call->s2.constant = (int32_t)PlsrModbusGetU32(request, 10UL); | |||||
| call->dAxis = (uint8_t)request[12UL]; | |||||
| call->outputModeOverride = (uint8_t)outputMode; | |||||
| PlsrModbusDataSourceInit(&call->source); | |||||
| return PLSR_RESULT_OK; | |||||
| } | |||||
| static void PlsrModbusPublishCallResponse(uint32_t sequence, | |||||
| uint16_t operation, | |||||
| PLSR_RESULT result, | |||||
| const PLSR_PARSE_DETAIL *detail, | |||||
| uint8_t committed) | |||||
| { | |||||
| uint16_t response[PLSR_MODBUS_CALL_RESPONSE_WORDS] = {0U}; | |||||
| PlsrModbusPutU32(response, 0UL, sequence); | |||||
| response[2UL] = operation; | |||||
| response[3UL] = (uint16_t)result; | |||||
| if (detail != NULL) | |||||
| { | |||||
| response[4UL] = (uint16_t)detail->result; | |||||
| response[5UL] = (uint16_t)detail->block; | |||||
| PlsrModbusPutU32(response, 6UL, detail->address); | |||||
| PlsrModbusPutU32(response, 8UL, (uint32_t)detail->value); | |||||
| response[10UL] = detail->segment; | |||||
| } | |||||
| response[11UL] = committed; | |||||
| (void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, | |||||
| (uint32_t)PlsrModbusBaseAddress | |||||
| + PLSR_MODBUS_CALL_RESPONSE_OFFSET, | |||||
| response, | |||||
| PLSR_MODBUS_CALL_RESPONSE_WORDS); | |||||
| } | |||||
| static void PlsrModbusHandleCallRequest(void) | |||||
| { | |||||
| uint16_t request[PLSR_MODBUS_CALL_REQUEST_WORDS]; | |||||
| PLSR_CALL call; | |||||
| PLSR_PARSE_DETAIL detail; | |||||
| PLSR_RESULT result; | |||||
| uint32_t sequence; | |||||
| uint32_t fingerprint; | |||||
| uint16_t operation; | |||||
| uint8_t axis = 0U; | |||||
| if (PlsrModbusReadWords(PLSR_MODBUS_CALL_REQUEST_OFFSET, | |||||
| request, | |||||
| PLSR_MODBUS_CALL_REQUEST_WORDS) == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| sequence = PlsrModbusGetU32(request, 0UL); | |||||
| if ((sequence == 0UL) || (sequence == PlsrModbusLastCallRequestSequence)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| PlsrModbusLastCallRequestSequence = sequence; | |||||
| operation = request[14UL]; | |||||
| (void)memset(&detail, 0, sizeof(detail)); | |||||
| result = PlsrModbusBuildCall(request, &call); | |||||
| if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| axis = call.dAxis; | |||||
| } | |||||
| if ((result == PLSR_RESULT_OK) && (operation == PLSR_MODBUS_CALL_COMMIT)) | |||||
| { | |||||
| result = PlsrValidateCall(&call, &detail); | |||||
| if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| result = PlsrModbusFingerprintCall(&call, &fingerprint); | |||||
| } | |||||
| if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| PlsrModbusCommitted[axis].call = call; | |||||
| PlsrModbusCommitted[axis].fingerprint = fingerprint; | |||||
| PlsrModbusCommitted[axis].valid = 1U; | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrModbusCommitted[axis].valid = 0U; | |||||
| } | |||||
| } | |||||
| else if ((result == PLSR_RESULT_OK) | |||||
| && (operation == PLSR_MODBUS_CALL_START)) | |||||
| { | |||||
| if (PlsrModbusCommitted[axis].valid == 0U) | |||||
| { | |||||
| result = PLSR_RESULT_INVALID_STATE; | |||||
| } | |||||
| else | |||||
| { | |||||
| result = PlsrModbusFingerprintCall( | |||||
| &PlsrModbusCommitted[axis].call, | |||||
| &fingerprint); | |||||
| if ((result == PLSR_RESULT_OK) | |||||
| && (fingerprint != PlsrModbusCommitted[axis].fingerprint)) | |||||
| { | |||||
| result = PLSR_RESULT_BUSY; | |||||
| } | |||||
| if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| result = PlsrValidateCall(&PlsrModbusCommitted[axis].call, | |||||
| &detail); | |||||
| } | |||||
| if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| PlsrModbusCommitted[axis].call.sequence = sequence; | |||||
| result = PlsrPostCall(&PlsrModbusCommitted[axis].call); | |||||
| } | |||||
| } | |||||
| } | |||||
| else if (result == PLSR_RESULT_OK) | |||||
| { | |||||
| result = PLSR_RESULT_INVALID_ARGUMENT; | |||||
| } | |||||
| PlsrModbusPublishCallResponse( | |||||
| sequence, | |||||
| operation, | |||||
| result, | |||||
| &detail, | |||||
| (axis < PLSR_AXIS_COUNT) ? PlsrModbusCommitted[axis].valid : 0U); | |||||
| } | |||||
| static void PlsrModbusPublishCommandResponse(uint32_t sequence, | |||||
| uint16_t opcode, | |||||
| uint16_t axis, | |||||
| PLSR_RESULT result) | |||||
| { | |||||
| uint16_t response[PLSR_MODBUS_COMMAND_RESPONSE_WORDS] = {0U}; | |||||
| PlsrModbusPutU32(response, 0UL, sequence); | |||||
| response[2UL] = opcode; | |||||
| response[3UL] = axis; | |||||
| response[4UL] = (uint16_t)result; | |||||
| (void)ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, | |||||
| (uint32_t)PlsrModbusBaseAddress | |||||
| + PLSR_MODBUS_COMMAND_RESPONSE_OFFSET, | |||||
| response, | |||||
| PLSR_MODBUS_COMMAND_RESPONSE_WORDS); | |||||
| } | |||||
| static void PlsrModbusHandleCommandRequest(void) | |||||
| { | |||||
| uint16_t request[PLSR_MODBUS_COMMAND_REQUEST_WORDS]; | |||||
| PLSR_COMMAND command; | |||||
| PLSR_RESULT result; | |||||
| uint32_t sequence; | |||||
| if (PlsrModbusReadWords(PLSR_MODBUS_COMMAND_REQUEST_OFFSET, | |||||
| request, | |||||
| PLSR_MODBUS_COMMAND_REQUEST_WORDS) == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| sequence = PlsrModbusGetU32(request, 0UL); | |||||
| if ((sequence == 0UL) | |||||
| || (sequence == PlsrModbusLastCommandRequestSequence)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| PlsrModbusLastCommandRequestSequence = sequence; | |||||
| (void)memset(&command, 0, sizeof(command)); | |||||
| if ((request[2UL] > (uint16_t)PLSR_CMD_SELF_TEST) | |||||
| || (request[2UL] == (uint16_t)PLSR_CMD_START) | |||||
| || (request[3UL] >= PLSR_AXIS_COUNT)) | |||||
| { | |||||
| result = PLSR_RESULT_INVALID_ARGUMENT; | |||||
| } | |||||
| else | |||||
| { | |||||
| command.sequence = sequence; | |||||
| command.opcode = (PLSR_COMMAND_OPCODE)request[2UL]; | |||||
| command.axis = (uint8_t)request[3UL]; | |||||
| command.argument = (int64_t)PlsrModbusGetU64(request, 4UL); | |||||
| result = PlsrPostCommand(&command); | |||||
| } | |||||
| PlsrModbusPublishCommandResponse(sequence, | |||||
| request[2UL], | |||||
| request[3UL], | |||||
| result); | |||||
| } | |||||
| static void PlsrModbusPublishAxisStatus(uint8_t axis) | |||||
| { | |||||
| PLSR_STATUS status; | |||||
| uint16_t *words = PlsrModbusStatusWords[axis]; | |||||
| uint32_t flags = 0UL; | |||||
| uint32_t generation; | |||||
| if (PlsrGetStatus(axis, &status) != PLSR_RESULT_OK) | |||||
| { | |||||
| return; | |||||
| } | |||||
| generation = PlsrModbusStatusGeneration[axis] + 2UL; | |||||
| if (generation == 0UL) | |||||
| { | |||||
| generation = 2UL; | |||||
| } | |||||
| PlsrModbusStatusGeneration[axis] = generation; | |||||
| (void)memset(words, 0, sizeof(PlsrModbusStatusWords[axis])); | |||||
| if (status.busy != 0U) flags |= (1UL << 0U); | |||||
| if (status.pulseActive != 0U) flags |= (1UL << 1U); | |||||
| if (status.done != 0U) flags |= (1UL << 2U); | |||||
| if (status.wait != 0U) flags |= (1UL << 3U); | |||||
| if (status.directionPositive != 0U) flags |= (1UL << 4U); | |||||
| if (status.positionValid != 0U) flags |= (1UL << 5U); | |||||
| if (status.jobValid != 0U) flags |= (1UL << 6U); | |||||
| if (status.speedClamped != 0U) flags |= (1UL << 7U); | |||||
| if (status.positionOverflow != 0U) flags |= (1UL << 8U); | |||||
| if (status.positiveLimitActive != 0U) flags |= (1UL << 9U); | |||||
| if (status.negativeLimitActive != 0U) flags |= (1UL << 10U); | |||||
| if (status.emergencyLatched != 0U) flags |= (1UL << 11U); | |||||
| if (status.backlashActive != 0U) flags |= (1UL << 12U); | |||||
| PlsrModbusPutU32(words, 0UL, generation); | |||||
| words[2UL] = (uint16_t)status.state; | |||||
| PlsrModbusPutU32(words, 3UL, flags); | |||||
| words[5UL] = (uint16_t)status.outputMode; | |||||
| words[6UL] = (uint16_t)status.error; | |||||
| words[7UL] = (uint16_t)status.stopReason; | |||||
| words[8UL] = (uint16_t)status.lastCommandResult; | |||||
| words[9UL] = status.s2Set; | |||||
| PlsrModbusPutU32(words, 10UL, status.lastCommandSequence); | |||||
| PlsrModbusPutU32(words, 12UL, status.illegalTransitionCount); | |||||
| PlsrModbusPutU32(words, 14UL, status.pendingEvents); | |||||
| PlsrModbusPutU64(words, 16UL, (uint64_t)status.logicalPosition); | |||||
| PlsrModbusPutU64(words, 20UL, (uint64_t)status.taskPulses); | |||||
| PlsrModbusPutU64(words, 24UL, (uint64_t)status.totalPulses); | |||||
| PlsrModbusPutU64(words, 28UL, status.physicalPulses); | |||||
| words[32UL] = status.segmentCount; | |||||
| words[33UL] = status.startSegment; | |||||
| words[34UL] = status.currentSegment; | |||||
| words[35UL] = status.directionPoint; | |||||
| words[36UL] = status.highResourceMask; | |||||
| PlsrModbusPutU32(words, 38UL, status.currentFrequencyHz); | |||||
| PlsrModbusPutU32(words, 40UL, status.targetFrequencyHz); | |||||
| PlsrModbusPutU32(words, 42UL, status.liveFrequencyRejectCount); | |||||
| words[44UL] = (uint16_t)status.lastLiveFrequencyResult; | |||||
| PlsrModbusPutU32(words, 46UL, generation); | |||||
| (void)ModbusDataWriteWords( | |||||
| MODBUS_DATA_DEVICE_D, | |||||
| (uint32_t)PlsrModbusBaseAddress + PLSR_MODBUS_AXIS_STATUS_OFFSET | |||||
| + (uint32_t)axis * PLSR_MODBUS_AXIS_STATUS_WORDS, | |||||
| words, | |||||
| PLSR_MODBUS_AXIS_STATUS_WORDS); | |||||
| } | |||||
| PLSR_RESULT PlsrModbusControlInit(uint16_t baseAddress) | |||||
| { | |||||
| uint16_t header[8] = {0U}; | |||||
| if (ModbusDataValidateWords(MODBUS_DATA_DEVICE_D, | |||||
| baseAddress, | |||||
| PLSR_MODBUS_WINDOW_WORDS) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| PlsrModbusBaseAddress = baseAddress; | |||||
| PlsrModbusEnabled = 0U; | |||||
| PlsrModbusLastCallRequestSequence = 0UL; | |||||
| PlsrModbusLastCommandRequestSequence = 0UL; | |||||
| (void)memset(PlsrModbusCommitted, 0, sizeof(PlsrModbusCommitted)); | |||||
| (void)memset(PlsrModbusStatusGeneration, | |||||
| 0, | |||||
| sizeof(PlsrModbusStatusGeneration)); | |||||
| if (ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, | |||||
| baseAddress, | |||||
| PlsrModbusZeroWindow, | |||||
| PLSR_MODBUS_WINDOW_WORDS) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| header[0UL] = PLSR_MODBUS_MAGIC_LOW; | |||||
| header[1UL] = PLSR_MODBUS_MAGIC_HIGH; | |||||
| header[2UL] = PLSR_MODBUS_PROTOCOL_VERSION; | |||||
| header[3UL] = (uint16_t)PLSR_MODBUS_WINDOW_WORDS; | |||||
| header[4UL] = PLSR_MODBUS_CAPABILITIES; | |||||
| if (ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, | |||||
| baseAddress, | |||||
| header, | |||||
| 8UL) == 0U) | |||||
| { | |||||
| return PLSR_RESULT_DATA_ACCESS; | |||||
| } | |||||
| PlsrModbusEnabled = 1U; | |||||
| PlsrModbusControlPoll(); | |||||
| return PLSR_RESULT_OK; | |||||
| } | |||||
| void PlsrModbusControlPoll(void) | |||||
| { | |||||
| uint8_t axis; | |||||
| if (PlsrModbusEnabled == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| PlsrModbusHandleCallRequest(); | |||||
| PlsrModbusHandleCommandRequest(); | |||||
| for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | |||||
| { | |||||
| PlsrModbusPublishAxisStatus(axis); | |||||
| } | |||||
| } | |||||
| uint8_t PlsrModbusControlIsEnabled(void) | |||||
| { | |||||
| return PlsrModbusEnabled; | |||||
| } | |||||
| uint16_t PlsrModbusControlGetBaseAddress(void) | |||||
| { | |||||
| return PlsrModbusBaseAddress; | |||||
| } | |||||
| @@ -0,0 +1,72 @@ | |||||
| #include "plsr_modbus_data.h" | |||||
| #include "modbus_data_store.h" | |||||
| #include <stddef.h> | |||||
| static uint8_t PlsrModbusDevice(PLSR_DEVICE_TYPE device, | |||||
| MODBUS_DATA_DEVICE *modbusDevice) | |||||
| { | |||||
| if ((modbusDevice == NULL) || (device > PLSR_DEVICE_FD)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| *modbusDevice = (MODBUS_DATA_DEVICE)device; | |||||
| return 1U; | |||||
| } | |||||
| static uint8_t PlsrModbusValidateWords(void *context, | |||||
| PLSR_DEVICE_TYPE device, | |||||
| uint32_t firstAddress, | |||||
| uint32_t wordCount) | |||||
| { | |||||
| MODBUS_DATA_DEVICE modbusDevice; | |||||
| (void)context; | |||||
| if (PlsrModbusDevice(device, &modbusDevice) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return ModbusDataValidateWords(modbusDevice, firstAddress, wordCount); | |||||
| } | |||||
| static uint8_t PlsrModbusReadWord(void *context, | |||||
| PLSR_DEVICE_TYPE device, | |||||
| uint32_t address, | |||||
| uint16_t *value) | |||||
| { | |||||
| MODBUS_DATA_DEVICE modbusDevice; | |||||
| (void)context; | |||||
| if (PlsrModbusDevice(device, &modbusDevice) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return ModbusDataReadWord(modbusDevice, address, value); | |||||
| } | |||||
| static uint8_t PlsrModbusReadDword(void *context, | |||||
| PLSR_DEVICE_TYPE device, | |||||
| uint32_t address, | |||||
| int32_t *value) | |||||
| { | |||||
| MODBUS_DATA_DEVICE modbusDevice; | |||||
| (void)context; | |||||
| if (PlsrModbusDevice(device, &modbusDevice) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return ModbusDataReadDword(modbusDevice, address, value); | |||||
| } | |||||
| void PlsrModbusDataSourceInit(PLSR_DATA_SOURCE *source) | |||||
| { | |||||
| if (source == NULL) | |||||
| { | |||||
| return; | |||||
| } | |||||
| source->context = NULL; | |||||
| source->validateWords = PlsrModbusValidateWords; | |||||
| source->readWord = PlsrModbusReadWord; | |||||
| source->readDword = PlsrModbusReadDword; | |||||
| source->readBit = NULL; | |||||
| } | |||||
| @@ -16,6 +16,10 @@ static uint8_t PlsrPathReadDword(const PLSR_DATA_SOURCE *source, | |||||
| uint16_t lowWord; | uint16_t lowWord; | ||||
| uint16_t highWord; | uint16_t highWord; | ||||
| if (source->readDword != NULL) | |||||
| { | |||||
| return source->readDword(source->context, device, address, value); | |||||
| } | |||||
| if ((source->readWord == NULL) | if ((source->readWord == NULL) | ||||
| || (source->readWord(source->context, | || (source->readWord(source->context, | ||||
| device, | device, | ||||
| @@ -499,6 +499,49 @@ PLSR_RESULT PlsrProfileRequestStop(PLSR_PROFILE_STATE *state) | |||||
| return PLSR_RESULT_OK; | 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, | PLSR_RESULT PlsrProfilePlan(const PLSR_PROFILE_REQUEST *request, | ||||
| int64_t pulses, | int64_t pulses, | ||||
| PLSR_PROFILE_PLAN *plan) | PLSR_PROFILE_PLAN *plan) | ||||
| @@ -1,7 +1,9 @@ | |||||
| #include "plsr_self_test.h" | #include "plsr_self_test.h" | ||||
| #include "plc_device.h" | #include "plc_device.h" | ||||
| #include "modbus_data_store.h" | |||||
| #include "plsr_core.h" | #include "plsr_core.h" | ||||
| #include "plsr_job.h" | #include "plsr_job.h" | ||||
| #include "plsr_modbus_data.h" | |||||
| #include <string.h> | #include <string.h> | ||||
| /* 上电自测(验证后可删除): | /* 上电自测(验证后可删除): | ||||
| @@ -18,8 +20,62 @@ | |||||
| #define SELF_TEST_DIR_POINT (4U) | #define SELF_TEST_DIR_POINT (4U) | ||||
| #define SELF_TEST_SFD_AXIS_STRIDE (130U) | #define SELF_TEST_SFD_AXIS_STRIDE (130U) | ||||
| #define SELF_TEST_SFD_SET_OFFSET (50U) | #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]; | 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, | static uint8_t SelfTestValidateWords(void *context, | ||||
| PLSR_DEVICE_TYPE device, | PLSR_DEVICE_TYPE device, | ||||
| @@ -52,6 +108,37 @@ static uint8_t SelfTestReadWord(void *context, | |||||
| return 1U; | 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, | static uint8_t SelfTestReadBit(void *context, | ||||
| PLSR_DEVICE_TYPE device, | PLSR_DEVICE_TYPE device, | ||||
| uint32_t address, | uint32_t address, | ||||
| @@ -341,3 +428,539 @@ PLSR_RESULT PlsrFourAxisSelfTestQueue(void) | |||||
| } | } | ||||
| return PLSR_RESULT_QUEUED; | 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; | |||||
| } | |||||
| @@ -96,8 +96,19 @@ $tests = @( | |||||
| "$workspacePath\PLSR\Src\plsr_hal_f407.c" | "$workspacePath\PLSR\Src\plsr_hal_f407.c" | ||||
| "$workspacePath\PLSR\Src\plsr_core.c" | "$workspacePath\PLSR\Src\plsr_core.c" | ||||
| "$workspacePath\PLSR\Src\plsr_self_test.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" | "$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' | '-Werror' | ||||
| '-DPLSR_HOST_TEST' | '-DPLSR_HOST_TEST' | ||||
| "-I$workspacePath\PLSR\Inc" | "-I$workspacePath\PLSR\Inc" | ||||
| "-I$workspacePath\Modbus\Inc" | |||||
| ) + $test.Sources + @('-o', $outputPath, '-lm') | ) + $test.Sources + @('-o', $outputPath, '-lm') | ||||
| try | try | ||||
| @@ -1,7 +1,9 @@ | |||||
| #include "plc_device.h" | #include "plc_device.h" | ||||
| #include "modbus_data_store.h" | |||||
| #include "plsr_core.h" | #include "plsr_core.h" | ||||
| #include "plsr_hal_f407.h" | #include "plsr_hal_f407.h" | ||||
| #include "plsr_job.h" | #include "plsr_job.h" | ||||
| #include "plsr_modbus_control.h" | |||||
| #include "plsr_persistence.h" | #include "plsr_persistence.h" | ||||
| #include "plsr_resource.h" | #include "plsr_resource.h" | ||||
| #include "plsr_self_test.h" | #include "plsr_self_test.h" | ||||
| @@ -254,6 +256,267 @@ static void TestDirDelaySequence(void) | |||||
| CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); | CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); | ||||
| CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); | CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); | ||||
| CHECK(PlsrHwTestGetDirLevel(0U) == 0U); | 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) | static void TestZeroFrequencyWaits(void) | ||||
| @@ -562,7 +825,7 @@ static void TestStopAndInvalidArgs(void) | |||||
| CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); | CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); | ||||
| params.targetPulses = 1; | params.targetPulses = 1; | ||||
| params.outputMode = PLSR_OUTPUT_CW_CCW; | 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; | params.outputMode = (PLSR_OUTPUT_MODE)99; | ||||
| CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); | CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); | ||||
| CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT); | CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT); | ||||
| @@ -1332,6 +1595,647 @@ static void TestFourAxisSelfTest(void) | |||||
| CHECK(PlsrResourceCheckInvariant() != 0U); | 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) | static void TestStopStopsHardware(void) | ||||
| { | { | ||||
| TEST_MEMORY memory; | TEST_MEMORY memory; | ||||
| @@ -1376,6 +2280,10 @@ int main(void) | |||||
| { | { | ||||
| TestMapping(); | TestMapping(); | ||||
| TestDirDelaySequence(); | TestDirDelaySequence(); | ||||
| TestDirectionBatch(); | |||||
| TestCwCcwSequence(); | |||||
| TestFastRefreshControlTick(); | |||||
| TestDynamicFrequencyRetarget(); | |||||
| TestZeroFrequencyWaits(); | TestZeroFrequencyWaits(); | ||||
| TestPulseCounting(); | TestPulseCounting(); | ||||
| TestAbPhaseAndCounting(); | TestAbPhaseAndCounting(); | ||||
| @@ -1394,6 +2302,14 @@ int main(void) | |||||
| TestEquivalentSelfTest(); | TestEquivalentSelfTest(); | ||||
| TestProtectionSelfTest(); | TestProtectionSelfTest(); | ||||
| TestFourAxisSelfTest(); | TestFourAxisSelfTest(); | ||||
| TestBacklashSelfTest(); | |||||
| TestDirectionLogicSelfTest(); | |||||
| TestCwCcwSelfTest(); | |||||
| TestFastRefreshSelfTest(); | |||||
| TestDynamicFrequencySelfTest(); | |||||
| TestDynamicFrequencySchedule(); | |||||
| TestModbusDataSelfTest(); | |||||
| TestModbusControlProtocol(); | |||||
| TestStopStopsHardware(); | TestStopStopsHardware(); | ||||
| if (TestFailures != 0) | if (TestFailures != 0) | ||||
| @@ -102,6 +102,8 @@ static void TestConfigureAxis0K1(void) | |||||
| CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK); | CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK); | ||||
| CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK); | CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK); | ||||
| CHECK(PlcDeviceWriteSfd(907U, 10) == 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); | TestWriteSfdDword(950U, 1000UL); | ||||
| CHECK(PlcDeviceWriteSfd(952U, 100) == PLC_DEVICE_OK); | CHECK(PlcDeviceWriteSfd(952U, 100) == PLC_DEVICE_OK); | ||||
| CHECK(PlcDeviceWriteSfd(953U, 120) == PLC_DEVICE_OK); | CHECK(PlcDeviceWriteSfd(953U, 120) == PLC_DEVICE_OK); | ||||
| @@ -184,6 +186,9 @@ static void TestValidSnapshotAndLiveFrequency(void) | |||||
| CHECK(snapshot.s2Set == 1U); | CHECK(snapshot.s2Set == 1U); | ||||
| CHECK(snapshot.outputMode == PLSR_OUTPUT_PULSE_DIR); | CHECK(snapshot.outputMode == PLSR_OUTPUT_PULSE_DIR); | ||||
| CHECK(snapshot.directionPoint == 4U); | CHECK(snapshot.directionPoint == 4U); | ||||
| CHECK(snapshot.directionNegativeLogic == 0U); | |||||
| CHECK(snapshot.positiveBacklashPulses == 17U); | |||||
| CHECK(snapshot.negativeBacklashPulses == 23U); | |||||
| CHECK(snapshot.s2.defaultSpeed == 1000UL); | CHECK(snapshot.s2.defaultSpeed == 1000UL); | ||||
| CHECK(snapshot.s2.maximumSpeed == 100000UL); | CHECK(snapshot.s2.maximumSpeed == 100000UL); | ||||
| CHECK(snapshot.s2.startSpeed == 100000UL); | CHECK(snapshot.s2.startSpeed == 100000UL); | ||||
| @@ -198,6 +203,13 @@ static void TestValidSnapshotAndLiveFrequency(void) | |||||
| CHECK(snapshot.hasSelfLoop == 1U); | CHECK(snapshot.hasSelfLoop == 1U); | ||||
| CHECK(snapshot.initialDirectionPositive == 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); | TestWriteDword(&memory, PLSR_DEVICE_D, 112U, 9999); | ||||
| CHECK(snapshot.segments[0].pulseOrTarget == 100); | CHECK(snapshot.segments[0].pulseOrTarget == 100); | ||||
| TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 2500); | TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 2500); | ||||
| @@ -0,0 +1,84 @@ | |||||
| #include "modbus_data_store.h" | |||||
| #include "plsr_modbus_data.h" | |||||
| #include <stdint.h> | |||||
| #include <stdio.h> | |||||
| static unsigned TestChecks; | |||||
| #define CHECK(condition) \ | |||||
| do \ | |||||
| { \ | |||||
| TestChecks++; \ | |||||
| if (!(condition)) \ | |||||
| { \ | |||||
| (void)printf("FAIL line %d: %s\n", __LINE__, #condition); \ | |||||
| return 1; \ | |||||
| } \ | |||||
| } while (0) | |||||
| int main(void) | |||||
| { | |||||
| PLSR_DATA_SOURCE source; | |||||
| uint16_t words[4] = {0x5678U, 0x1234U, 0xFFFEU, 0xFFFFU}; | |||||
| uint16_t word; | |||||
| int32_t dword; | |||||
| uint32_t sequence; | |||||
| CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_D, 0UL, 1UL) == 1U); | |||||
| CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_HD, 9998UL, 2UL) == 1U); | |||||
| CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_FD, 9999UL, 2UL) == 0U); | |||||
| CHECK(ModbusDataValidateWords(MODBUS_DATA_DEVICE_D, 0UL, 0UL) == 0U); | |||||
| CHECK(ModbusDataValidateWords((MODBUS_DATA_DEVICE)3, 0UL, 1UL) == 0U); | |||||
| sequence = ModbusDataGetWriteSequence(); | |||||
| CHECK((sequence & 1UL) == 0UL); | |||||
| CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, 1000UL, words, 4UL) | |||||
| == 1U); | |||||
| CHECK(ModbusDataGetWriteSequence() == sequence + 2UL); | |||||
| CHECK(ModbusDataReadDword(MODBUS_DATA_DEVICE_D, 1000UL, &dword) == 1U); | |||||
| CHECK(dword == (int32_t)0x12345678UL); | |||||
| CHECK(ModbusDataReadDword(MODBUS_DATA_DEVICE_D, 1002UL, &dword) == 1U); | |||||
| CHECK(dword == -2); | |||||
| CHECK(ModbusDataWriteWord(MODBUS_DATA_DEVICE_HD, 5UL, 0xA55AU) == 1U); | |||||
| CHECK(ModbusDataWriteWord(MODBUS_DATA_DEVICE_FD, 5UL, 0x5AA5U) == 1U); | |||||
| CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, 5UL, &word) == 1U); | |||||
| CHECK(word == 0U); | |||||
| CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_HD, 5UL, &word) == 1U); | |||||
| CHECK(word == 0xA55AU); | |||||
| CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_FD, 5UL, &word) == 1U); | |||||
| CHECK(word == 0x5AA5U); | |||||
| CHECK(ModbusDataReadLinear(10005UL, &word) == 1U); | |||||
| CHECK(word == 0xA55AU); | |||||
| CHECK(ModbusDataReadLinear(40005UL, &word) == 1U); | |||||
| CHECK(word == 0x5AA5U); | |||||
| CHECK(ModbusDataReadLinear(20000UL, &word) == 0U); | |||||
| CHECK(ModbusDataReadLinear(39999UL, &word) == 0U); | |||||
| CHECK(ModbusDataReadLinear(69999UL, &word) == 0U); | |||||
| PlsrModbusDataSourceInit(&source); | |||||
| CHECK(source.context == NULL); | |||||
| CHECK(source.validateWords != NULL); | |||||
| CHECK(source.readWord != NULL); | |||||
| CHECK(source.readDword != NULL); | |||||
| CHECK(source.readBit == NULL); | |||||
| CHECK(source.validateWords(source.context, PLSR_DEVICE_D, 1000UL, 4UL) | |||||
| == 1U); | |||||
| CHECK(source.validateWords(source.context, PLSR_DEVICE_X, 0UL, 1UL) | |||||
| == 0U); | |||||
| CHECK(source.readDword(source.context, | |||||
| PLSR_DEVICE_D, | |||||
| 1000UL, | |||||
| &dword) == 1U); | |||||
| CHECK(dword == (int32_t)0x12345678UL); | |||||
| CHECK(source.readWord(source.context, PLSR_DEVICE_HD, 5UL, &word) == 1U); | |||||
| CHECK(word == 0xA55AU); | |||||
| CHECK(source.readWord(source.context, PLSR_DEVICE_FD, 5UL, &word) == 1U); | |||||
| CHECK(word == 0x5AA5U); | |||||
| CHECK(source.readWord(source.context, PLSR_DEVICE_M, 5UL, &word) == 0U); | |||||
| PlsrModbusDataSourceInit(NULL); | |||||
| (void)printf("PASS: %u Modbus data-source checks\n", TestChecks); | |||||
| return 0; | |||||
| } | |||||
| @@ -348,6 +348,7 @@ static void TestControlledStop(void) | |||||
| 10U, 10U, 0U); | 10U, 10U, 0U); | ||||
| PLSR_PROFILE_STATE state; | PLSR_PROFILE_STATE state; | ||||
| uint32_t frequency = 0U; | uint32_t frequency = 0U; | ||||
| uint64_t pausedPulsesQ32; | |||||
| uint8_t completed = 0U; | uint8_t completed = 0U; | ||||
| int step; | int step; | ||||
| @@ -365,12 +366,28 @@ static void TestControlledStop(void) | |||||
| CHECK(frequency == 0U); | CHECK(frequency == 0U); | ||||
| CHECK(state.phase == PLSR_PROFILE_PHASE_DONE); | 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; | request.decelSlopeHzPerMs = 0U; | ||||
| CHECK(PlsrProfileStart(&state, &request, 10000, 1000U) | CHECK(PlsrProfileStart(&state, &request, 10000, 1000U) | ||||
| == PLSR_RESULT_OK); | == PLSR_RESULT_OK); | ||||
| CHECK(PlsrProfileRequestStop(&state) == PLSR_RESULT_OK); | CHECK(PlsrProfileRequestStop(&state) == PLSR_RESULT_OK); | ||||
| CHECK(state.phase == PLSR_PROFILE_PHASE_DONE); | CHECK(state.phase == PLSR_PROFILE_PHASE_DONE); | ||||
| CHECK(PlsrProfileRequestStop(NULL) == PLSR_RESULT_INVALID_ARGUMENT); | 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) | static void TestPlan(void) | ||||