From 83803085ec3fbce67071f65611ae254651d8a386 Mon Sep 17 00:00:00 2001
From: ywh <2227158009@qq.com>
Date: Fri, 7 Aug 2026 10:45:22 +0800
Subject: [PATCH] =?UTF-8?q?PLSR=20=E6=96=B0=E5=A2=9E=20P3a=20=E7=A1=AC?=
=?UTF-8?q?=E4=BB=B6=E5=B1=82=EF=BC=9A=E5=8D=95=E8=BD=B4=20PULSE/DIR=20?=
=?UTF-8?q?=E8=BE=93=E5=87=BA=E6=89=93=E9=80=9A=EF=BC=88host=20=E6=A8=A1?=
=?UTF-8?q?=E6=8B=9F=20+=20core=20=E6=8E=A5=E7=BA=BF=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 plsr_hal_f407.c/.h:Q0~Q3 定时器/引脚映射(PF6-9 + TIM10/11/13/14)、
DIR 输出与方向建立延时状态机、PWM 启停/动态改频(50%占空比、预装载无毛刺)、
更新中断软件计数 + 段完成事件注入、精确停止(周期末关通道,无额外脉冲)
- 定时器时钟按 RCC 实际配置计算(修正 TIM13/14=84MHz 疑点,APB2 分频2 时实际 168MHz)
- core 接线:段进入自动启动硬件+速度曲线、段间自动重启、tick 驱动 profile→改频、
加速完成自动注入 ACCEL_COMPLETE、STOP/急停/限位/故障/终态统一停止硬件
- PLSR_HOST_TEST 下模拟定时器寄存器,HAL 逻辑可单测
- 新增 test_plsr_hal 380 项(含端到端:START→DIR延时→PWM→加速→计数→段间→完成),
六套 host 测试共 1151 项全绿,IAR 0 错误 0 警告
- 待办:上板前补 GPIO 复用与 NVIC 使能(host 覆盖不到)
---
EWARM/Modbus.ewp | 6 +
PLSR/Inc/plsr_hal_f407.h | 64 +++++
PLSR/Src/plsr_core.c | 127 ++++++++-
PLSR/Src/plsr_hal_f407.c | 516 +++++++++++++++++++++++++++++++++++
PLSR/Test/run_host_tests.ps1 | 20 ++
PLSR/Test/test_plsr_hal.c | 421 ++++++++++++++++++++++++++++
6 files changed, 1153 insertions(+), 1 deletion(-)
create mode 100644 PLSR/Inc/plsr_hal_f407.h
create mode 100644 PLSR/Src/plsr_hal_f407.c
create mode 100644 PLSR/Test/test_plsr_hal.c
diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp
index fe594b9..84d63a6 100644
--- a/EWARM/Modbus.ewp
+++ b/EWARM/Modbus.ewp
@@ -1290,6 +1290,9 @@
$PROJ_DIR$\..\PLSR\Inc\plsr_profile.h
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_hal_f407.h
+
$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c
@@ -1308,6 +1311,9 @@
$PROJ_DIR$\..\PLSR\Src\plsr_profile.c
+
+ $PROJ_DIR$\..\PLSR\Src\plsr_hal_f407.c
+
$PROJ_DIR$\..\PLSR\Src\plsr_core.c
diff --git a/PLSR/Inc/plsr_hal_f407.h b/PLSR/Inc/plsr_hal_f407.h
new file mode 100644
index 0000000..d5581ca
--- /dev/null
+++ b/PLSR/Inc/plsr_hal_f407.h
@@ -0,0 +1,64 @@
+#ifndef PLSR_HAL_F407_H
+#define PLSR_HAL_F407_H
+
+#include "plsr_types.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PLSR_HW_AXIS_COUNT (PLSR_AXIS_COUNT)
+#define PLSR_HW_DIR_POINT_NONE (0xFFU)
+
+typedef enum
+{
+ PLSR_HW_STATE_IDLE = 0,
+ PLSR_HW_STATE_DIR_SETTLING, /* DIR 已置位,等待方向建立延时 */
+ PLSR_HW_STATE_PWM_PENDING, /* 延时已到,等待首个非零频率启动 PWM */
+ PLSR_HW_STATE_RUNNING, /* PWM 输出中,更新中断计数 */
+ PLSR_HW_STATE_DONE
+} PLSR_HW_STATE;
+
+typedef struct
+{
+ uint32_t frequencyHz; /* 初始频率(起始速度,可为 0) */
+ int64_t targetPulses; /* 本段目标脉冲数 */
+ uint8_t directionPoint; /* DIR 输出点(Y 点号,0xFF=无) */
+ uint8_t directionPositive;
+ uint16_t directionDelayMs;
+} PLSR_HW_START_PARAMS;
+
+PLSR_RESULT PlsrHwInit(void);
+
+PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params);
+PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz);
+PLSR_RESULT PlsrHwStopPulse(uint8_t axis);
+uint8_t PlsrHwIsPulseActive(uint8_t axis);
+PLSR_HW_STATE PlsrHwGetState(uint8_t axis);
+uint32_t PlsrHwGetTimerClockHz(uint8_t axis);
+
+/* 每 1ms tick 推进 HAL 状态机(DIR 延时等)。 */
+void PlsrHwTick(uint8_t axis);
+
+/* 输出定时器更新中断入口(生产由 IRQHandler 调用,host 由测试调用)。 */
+void PlsrHwOnTimerUpdate(uint8_t axis);
+
+/* 输出点(Y0~Y20)引脚映射表:DIR 点解析用。 */
+uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber);
+
+#ifdef PLSR_HOST_TEST
+/* 模拟寄存器访问与中断触发(测试用)。 */
+uint32_t PlsrHwTestGetArr(uint8_t axis);
+uint32_t PlsrHwTestGetCcr(uint8_t axis);
+uint32_t PlsrHwTestGetPsc(uint8_t axis);
+uint8_t PlsrHwTestGetPwmEnabled(uint8_t axis);
+uint8_t PlsrHwTestGetDirLevel(uint8_t axis);
+void PlsrHwTestTriggerUpdate(uint8_t axis);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PLSR_HAL_F407_H */
diff --git a/PLSR/Src/plsr_core.c b/PLSR/Src/plsr_core.c
index e86d080..7372c5e 100644
--- a/PLSR/Src/plsr_core.c
+++ b/PLSR/Src/plsr_core.c
@@ -1,7 +1,9 @@
#include "plsr_core.h"
#include "plc_device.h"
#include "plsr_address_map.h"
+#include "plsr_hal_f407.h"
#include "plsr_path.h"
+#include "plsr_profile.h"
#include "plsr_resource.h"
#include
@@ -27,6 +29,9 @@ typedef struct
int64_t logicalPosition;
int64_t totalPulses;
PLSR_PATH_CONTEXT path;
+ PLSR_PROFILE_STATE profile;
+ uint8_t profileActive;
+ uint8_t profileWasAccel;
uint8_t hasLastCommand;
uint8_t done;
uint8_t directionPositive;
@@ -53,6 +58,9 @@ static uint32_t PlsrNextTicket;
static uint8_t PlsrInitialized;
static PLSR_JOB_SNAPSHOT PlsrJobScratch;
+static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject);
+static void PlsrStartSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject);
+
static uint32_t PlsrCoreEnterCritical(void)
{
#ifdef PLSR_HOST_TEST
@@ -230,6 +238,7 @@ PLSR_RESULT PlsrStateTransition(uint8_t axis,
axisObject->state = PLSR_STATE_ERROR;
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->immediateStopPending = 0U;
+ PlsrStopSegmentHardware(axis, axisObject);
PlsrResourceRelease(&axisObject->lease);
PlsrPublishAxis(axis);
return PLSR_RESULT_INVALID_STATE;
@@ -246,6 +255,7 @@ PLSR_RESULT PlsrStateTransition(uint8_t axis,
axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED;
axisObject->immediateStopPending = 0U;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
PlsrResourceRelease(&axisObject->lease);
/* 运动已结束:记录 lastBusy=0,保证掉电后恢复时位置仍可信。 */
(void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 0U);
@@ -625,6 +635,8 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject,
}
else
{
+ /* 启动当前段硬件输出与速度曲线。 */
+ PlsrStartSegmentHardware(call->dAxis, axisObject);
/* 运动开始:掉电恢复时据此判定"断电时在运动中"。 */
(void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 1U);
(void)PlcDeviceCheckpointHsd();
@@ -654,6 +666,7 @@ static PLSR_RESULT PlsrStopImmediate(uint8_t axis)
PlsrSetStopReason(axisObject, PLSR_STOP_REASON_STOP_IMMEDIATE);
axisObject->pendingTerminal = PLSR_STATE_STOPPED;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
if ((axisObject->state == PLSR_STATE_WAIT)
|| (axisObject->state == PLSR_STATE_PAUSED))
{
@@ -688,6 +701,7 @@ static PLSR_RESULT PlsrStopDecel(uint8_t axis)
PlsrSetStopReason(axisObject, PLSR_STOP_REASON_STOP_DECEL);
axisObject->pendingTerminal = PLSR_STATE_STOPPED;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
if ((axisObject->state == PLSR_STATE_WAIT)
|| (axisObject->state == PLSR_STATE_PAUSED))
{
@@ -720,6 +734,7 @@ static PLSR_RESULT PlsrPause(uint8_t axis)
{
PlsrSetStopReason(axisObject, PLSR_STOP_REASON_PAUSE);
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
return PlsrStateTransition(axis,
PLSR_STATE_PAUSED,
PLSR_TRANSITION_DECEL_COMPLETE);
@@ -912,6 +927,7 @@ static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events)
PLSR_STOP_REASON_SOFTWARE_EMERGENCY);
axisObject->done = 0U;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
(void)PlsrStateTransition(axis,
PLSR_STATE_STOPPED,
PLSR_TRANSITION_STOP);
@@ -929,6 +945,7 @@ static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events)
PLSR_STOP_REASON_LIMIT_POSITIVE);
axisObject->pendingTerminal = PLSR_STATE_STOPPED;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
if ((axisObject->state == PLSR_STATE_WAIT)
|| (axisObject->state == PLSR_STATE_PAUSED))
{
@@ -956,6 +973,7 @@ static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events)
PLSR_STOP_REASON_LIMIT_NEGATIVE);
axisObject->pendingTerminal = PLSR_STATE_STOPPED;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
if ((axisObject->state == PLSR_STATE_WAIT)
|| (axisObject->state == PLSR_STATE_PAUSED))
{
@@ -981,12 +999,85 @@ static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events)
PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT);
axisObject->done = 0U;
PlsrPathTerminate(&axisObject->path);
+ PlsrStopSegmentHardware(axis, axisObject);
(void)PlsrStateTransition(axis,
PLSR_STATE_ERROR,
PLSR_TRANSITION_FAULT);
}
}
+/* 启动当前段的硬件输出与速度曲线(P3a:单轴 PULSE/DIR)。
+ * 由段进入 ACCEL 时调用(任务启动 + 段间推进)。 */
+static void PlsrStartSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject)
+{
+ const PLSR_JOB_SNAPSHOT *job = &axisObject->job;
+ const PLSR_SEGMENT_SNAPSHOT *segment =
+ &job->segments[axisObject->path.currentSegment - 1U];
+ PLSR_PROFILE_REQUEST profileRequest;
+ PLSR_HW_START_PARAMS params;
+ int64_t pulses;
+ uint8_t positive;
+
+ if (job->positioningMode == 0U)
+ {
+ pulses = (segment->pulseOrTarget < 0)
+ ? -(int64_t)segment->pulseOrTarget
+ : (int64_t)segment->pulseOrTarget;
+ positive = (segment->pulseOrTarget >= 0) ? 1U : 0U;
+ }
+ else
+ {
+ /* 绝对模式:P4 位置闭环后完善;P3a 按当前位置计算。 */
+ int64_t delta = (int64_t)segment->pulseOrTarget
+ - axisObject->logicalPosition;
+
+ pulses = (delta < 0) ? -delta : delta;
+ positive = (delta >= 0) ? 1U : 0U;
+ }
+
+ (void)memset(&profileRequest, 0, sizeof(profileRequest));
+ profileRequest.targetFrequencyHz = segment->targetFrequency;
+ profileRequest.startFrequencyHz = job->s2.startSpeed;
+ profileRequest.stopFrequencyHz = job->s2.stopSpeed;
+ profileRequest.maxFrequencyHz = job->s2.maximumSpeed;
+ profileRequest.accelSlopeHzPerMs =
+ (job->s2.accelerationMs != 0U)
+ ? job->s2.defaultSpeed / job->s2.accelerationMs
+ : 0UL;
+ profileRequest.decelSlopeHzPerMs =
+ (job->s2.decelerationMs != 0U)
+ ? job->s2.defaultSpeed / job->s2.decelerationMs
+ : 0UL;
+ profileRequest.curveMode = job->s2.curveMode;
+
+ if (PlsrProfileStart(&axisObject->profile,
+ &profileRequest,
+ pulses,
+ 1000U) == PLSR_RESULT_OK)
+ {
+ axisObject->profileActive = 1U;
+ axisObject->profileWasAccel =
+ (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL)
+ ? 1U
+ : 0U;
+ }
+
+ params.frequencyHz = job->s2.startSpeed;
+ params.targetPulses = pulses;
+ params.directionPoint = job->directionPoint;
+ params.directionPositive = positive;
+ params.directionDelayMs = job->s2.directionDelayMs;
+ (void)PlsrHwStartPulse(axis, ¶ms);
+}
+
+/* 停止当前段的硬件输出与速度曲线。 */
+static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject)
+{
+ axisObject->profileActive = 0U;
+ axisObject->profileWasAccel = 0U;
+ (void)PlsrHwStopPulse(axis);
+}
+
/* 应用路径执行器的动作:段间推进、进入等待、结束、让出、错误。 */
static void PlsrApplyPathAction(uint8_t axis, PLSR_PATH_ACTION action)
{
@@ -1009,6 +1100,11 @@ static void PlsrApplyPathAction(uint8_t axis, PLSR_PATH_ACTION action)
PLSR_STATE_ACCEL,
PLSR_TRANSITION_START);
}
+ /* 进入新段:重新启动硬件输出与速度曲线。 */
+ if (axisObject->state == PLSR_STATE_ACCEL)
+ {
+ PlsrStartSegmentHardware(axis, axisObject);
+ }
break;
case PLSR_PATH_ACTION_ENTER_WAIT:
@@ -1147,6 +1243,7 @@ PLSR_RESULT PlsrInit(void)
(void)memset(PlsrCommandQueue, 0, sizeof(PlsrCommandQueue));
PlsrNextTicket = 0UL;
PlsrResourceInit();
+ (void)PlsrHwInit();
PlsrInitialized = 1U;
restoredPositionValid = PlcDeviceGetRestoredHsdPositionValid();
@@ -1218,12 +1315,17 @@ void PlsrProcess(void)
}
}
- /* 1ms tick:路径执行器推进(WAIT/ACT 计时、信号/EXT 轮询、跳转链)。 */
+ /* 1ms tick:路径执行器推进(WAIT/ACT 计时、信号/EXT 轮询、跳转链)
+ * + 速度曲线推进(P2) + HAL 状态机(DIR 延时)。 */
for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++)
{
PLSR_AXIS *axisObject = &PlsrAxes[axis];
PLSR_PATH_ACTION action;
+ uint32_t frequencyHz;
+ uint8_t profileDone;
+ uint8_t wasAccel;
+ PlsrHwTick(axis);
if (PlsrStateIsBusy(axisObject->state) == 0U)
{
continue;
@@ -1235,6 +1337,29 @@ void PlsrProcess(void)
{
PlsrApplyPathAction(axis, action);
}
+
+ if (axisObject->profileActive != 0U)
+ {
+ wasAccel = axisObject->profileWasAccel;
+ (void)PlsrProfileStep(&axisObject->profile,
+ &frequencyHz,
+ &profileDone);
+ if (profileDone == 0U)
+ {
+ (void)PlsrHwSetFrequency(axis, frequencyHz);
+ }
+ 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);
+ }
+ }
}
}
diff --git a/PLSR/Src/plsr_hal_f407.c b/PLSR/Src/plsr_hal_f407.c
new file mode 100644
index 0000000..8f89e9d
--- /dev/null
+++ b/PLSR/Src/plsr_hal_f407.c
@@ -0,0 +1,516 @@
+#include "plsr_hal_f407.h"
+#include "plsr_address_map.h"
+#include "plsr_core.h"
+#include "plsr_job.h"
+#include
+
+#ifndef PLSR_HOST_TEST
+#include "stm32f4xx.h"
+#include "stm32f4xx_hal.h"
+#endif
+
+#define PLSR_HW_TIMER_CHANNEL1_BIT (0x0001U)
+#define PLSR_HW_TIMER_UPDATE_BIT (0x0001U)
+#define PLSR_HW_OUTPUT_POINT_COUNT (21U)
+
+typedef struct
+{
+ uint32_t timerClockHz;
+ uint8_t directionPoint; /* 0xFF = 无 */
+#ifndef PLSR_HOST_TEST
+ TIM_TypeDef *timer;
+ GPIO_TypeDef *gpioPort;
+ uint16_t gpioPin;
+ uint8_t afMode;
+ IRQn_Type irq;
+#endif
+} PLSR_HW_AXIS_MAP;
+
+#ifndef PLSR_HOST_TEST
+/* 输出点(Y 点号)→ GPIO 引脚:XDM-60T4-E 原理图。
+ * 点号 8/9/18/19 不存在(资源层掩码 0x0013FCFF 已约束)。 */
+typedef struct
+{
+ GPIO_TypeDef *port;
+ uint16_t pin;
+} PLSR_HW_OUTPUT_PIN;
+
+static const PLSR_HW_OUTPUT_PIN PlsrHwOutputPins[PLSR_HW_OUTPUT_POINT_COUNT] =
+{
+ {GPIOF, GPIO_PIN_6}, /* Y0 */
+ {GPIOF, GPIO_PIN_8}, /* Y1 */
+ {GPIOF, GPIO_PIN_7}, /* Y2 */
+ {GPIOF, GPIO_PIN_9}, /* Y3 */
+ {GPIOI, GPIO_PIN_8}, /* Y4 */
+ {GPIOE, GPIO_PIN_6}, /* Y5 */
+ {GPIOE, GPIO_PIN_5}, /* Y6 */
+ {GPIOE, GPIO_PIN_4}, /* Y7 */
+ {NULL, 0U}, /* Y8 */
+ {NULL, 0U}, /* Y9 */
+ {GPIOG, GPIO_PIN_7}, /* Y10 */
+ {GPIOG, GPIO_PIN_6}, /* Y11 */
+ {GPIOH, GPIO_PIN_9}, /* Y12 */
+ {GPIOH, GPIO_PIN_8}, /* Y13 */
+ {GPIOH, GPIO_PIN_7}, /* Y14 */
+ {GPIOH, GPIO_PIN_6}, /* Y15 */
+ {GPIOF, GPIO_PIN_11}, /* Y16 */
+ {GPIOB, GPIO_PIN_0}, /* Y17 */
+ {NULL, 0U}, /* Y18 */
+ {NULL, 0U}, /* Y19 */
+ {GPIOH, GPIO_PIN_5} /* Y20 */
+};
+#endif
+
+/* Q0~Q3 定时器:XDM-60T4-E。
+ * PF6=TIM10_CH1(AF3)、PF7=TIM11_CH1(AF3)、PF8=TIM13_CH1(AF9)、PF9=TIM14_CH1(AF9)。
+ * 定时器时钟由 RCC 实际配置计算(APB2 分频≠1 时定时器时钟×2)。 */
+static const PLSR_HW_AXIS_MAP PlsrHwAxisMap[PLSR_HW_AXIS_COUNT] =
+{
+#ifndef PLSR_HOST_TEST
+ {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM10, GPIOF, GPIO_PIN_6, 3U, TIM1_UP_TIM10_IRQn},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM13, GPIOF, GPIO_PIN_8, 9U, TIM8_UP_TIM13_IRQn},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM11, GPIOF, GPIO_PIN_7, 3U, TIM1_TRG_COM_TIM11_IRQn},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM14, GPIOF, GPIO_PIN_9, 9U, TIM8_TRG_COM_TIM14_IRQn}
+#else
+ {168000000UL, PLSR_HW_DIR_POINT_NONE},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE},
+ {168000000UL, PLSR_HW_DIR_POINT_NONE}
+#endif
+};
+
+/* host 测试:模拟定时器寄存器。 */
+#ifdef PLSR_HOST_TEST
+typedef struct
+{
+ uint32_t cr1;
+ uint32_t dier;
+ uint32_t sr;
+ uint32_t psc;
+ uint32_t arr;
+ uint32_t ccr1;
+ uint32_t ccer;
+ uint8_t dirLevel;
+} PLSR_HW_TIMER_REGS;
+
+static PLSR_HW_TIMER_REGS PlsrHwTimers[PLSR_HW_AXIS_COUNT];
+#endif
+
+typedef struct
+{
+ PLSR_HW_STATE state;
+ uint32_t currentFrequencyHz;
+ int64_t targetPulses;
+ int64_t emittedPulses;
+ uint16_t directionDelayRemainingMs;
+ uint8_t directionPoint;
+ uint8_t directionPositive;
+} PLSR_HW_AXIS_STATE;
+
+static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT];
+
+/* ---- 定时器寄存器访问抽象(host 模拟 / 生产真实) ---- */
+
+static void PlsrHwTimerSetArr(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].arr = value;
+#else
+ PlsrHwAxisMap[axis].timer->ARR = value;
+#endif
+}
+
+static void PlsrHwTimerSetPsc(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].psc = value;
+#else
+ PlsrHwAxisMap[axis].timer->PSC = value;
+#endif
+}
+
+static void PlsrHwTimerSetCcr(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].ccr1 = value;
+#else
+ PlsrHwAxisMap[axis].timer->CCR1 = value;
+#endif
+}
+
+static void PlsrHwTimerSetCen(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].cr1 = (PlsrHwTimers[axis].cr1 & ~0x0001UL) | value;
+#else
+ if (value != 0UL)
+ {
+ PlsrHwAxisMap[axis].timer->CR1 |= TIM_CR1_CEN;
+ }
+ else
+ {
+ PlsrHwAxisMap[axis].timer->CR1 &= ~TIM_CR1_CEN;
+ }
+#endif
+}
+
+static void PlsrHwTimerSetCc1e(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].ccer = (PlsrHwTimers[axis].ccer & ~0x0001UL) | value;
+#else
+ if (value != 0UL)
+ {
+ PlsrHwAxisMap[axis].timer->CCER |= TIM_CCER_CC1E;
+ }
+ else
+ {
+ PlsrHwAxisMap[axis].timer->CCER &= ~TIM_CCER_CC1E;
+ }
+#endif
+}
+
+static void PlsrHwTimerSetUie(uint8_t axis, uint32_t value)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].dier = (PlsrHwTimers[axis].dier & ~0x0001UL) | value;
+#else
+ if (value != 0UL)
+ {
+ PlsrHwAxisMap[axis].timer->DIER |= TIM_DIER_UIE;
+ }
+ else
+ {
+ PlsrHwAxisMap[axis].timer->DIER &= ~TIM_DIER_UIE;
+ }
+#endif
+}
+
+static void PlsrHwTimerClearUif(uint8_t axis)
+{
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].sr &= ~PLSR_HW_TIMER_UPDATE_BIT;
+#else
+ PlsrHwAxisMap[axis].timer->SR &= ~TIM_SR_UIF;
+#endif
+}
+
+/* ---- DIR 输出 ---- */
+
+static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive)
+{
+ PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis];
+
+ state->directionPositive = (positive != 0U) ? 1U : 0U;
+ if (state->directionPoint == PLSR_HW_DIR_POINT_NONE)
+ {
+ return;
+ }
+#ifdef PLSR_HOST_TEST
+ PlsrHwTimers[axis].dirLevel = (positive != 0U) ? 1U : 0U;
+#else
+ if (state->directionPoint < PLSR_HW_OUTPUT_POINT_COUNT)
+ {
+ const PLSR_HW_OUTPUT_PIN *pin =
+ &PlsrHwOutputPins[state->directionPoint];
+
+ if (pin->port != NULL)
+ {
+ HAL_GPIO_WritePin(pin->port, pin->pin,
+ (positive != 0U) ? GPIO_PIN_SET : GPIO_PIN_RESET);
+ }
+ }
+#endif
+}
+
+/* ---- PWM 启停 ---- */
+
+static void PlsrHwStartPwmTimer(uint8_t axis, uint32_t frequencyHz)
+{
+ uint16_t psc;
+ uint16_t arr;
+
+ if (PlsrCalculateTimerDivider(PlsrHwAxisMap[axis].timerClockHz,
+ frequencyHz,
+ &psc,
+ &arr) != PLSR_RESULT_OK)
+ {
+ return;
+ }
+ PlsrHwTimerSetPsc(axis, psc);
+ PlsrHwTimerSetArr(axis, arr);
+ PlsrHwTimerSetCcr(axis, (uint32_t)arr / 2UL); /* 50% 占空比 */
+ PlsrHwTimerSetUie(axis, 1UL);
+ PlsrHwTimerSetCc1e(axis, 1UL);
+ PlsrHwTimerSetCen(axis, 1UL);
+}
+
+static void PlsrHwStopPwmTimer(uint8_t axis)
+{
+ PlsrHwTimerSetCc1e(axis, 0UL);
+ PlsrHwTimerSetUie(axis, 0UL);
+ PlsrHwTimerSetCen(axis, 0UL);
+}
+
+uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber)
+{
+ /* 与资源层一致的合法输出点掩码(Q0~Q7、Q10~Q17、Q20)。 */
+ const uint32_t validOutputMask = 0x0013FCFFUL;
+
+ if (pointNumber >= PLSR_HW_OUTPUT_POINT_COUNT)
+ {
+ return 0U;
+ }
+ if ((validOutputMask & (1UL << pointNumber)) == 0UL)
+ {
+ return 0U;
+ }
+#ifndef PLSR_HOST_TEST
+ if (PlsrHwOutputPins[pointNumber].port == NULL)
+ {
+ return 0U;
+ }
+#endif
+ return 1U;
+}
+
+PLSR_RESULT PlsrHwInit(void)
+{
+ uint8_t axis;
+
+ (void)memset(PlsrHwAxes, 0, sizeof(PlsrHwAxes));
+ for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++)
+ {
+ PlsrHwAxes[axis].state = PLSR_HW_STATE_IDLE;
+ PlsrHwAxes[axis].directionPoint = PLSR_HW_DIR_POINT_NONE;
+#ifdef PLSR_HOST_TEST
+ (void)memset(&PlsrHwTimers[axis], 0, sizeof(PlsrHwTimers[axis]));
+#else
+ PlsrHwTimerSetCc1e(axis, 0UL);
+ PlsrHwTimerSetUie(axis, 0UL);
+ PlsrHwTimerSetCen(axis, 0UL);
+#endif
+ }
+ return PLSR_RESULT_OK;
+}
+
+PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params)
+{
+ PLSR_HW_AXIS_STATE *state;
+
+ if ((axis >= PLSR_HW_AXIS_COUNT) || (params == NULL))
+ {
+ return PLSR_RESULT_INVALID_ARGUMENT;
+ }
+ if (params->targetPulses <= 0)
+ {
+ return PLSR_RESULT_INVALID_ARGUMENT;
+ }
+ state = &PlsrHwAxes[axis];
+ if (state->state == PLSR_HW_STATE_RUNNING)
+ {
+ return PLSR_RESULT_BUSY;
+ }
+
+ state->targetPulses = params->targetPulses;
+ state->emittedPulses = 0;
+ state->currentFrequencyHz = params->frequencyHz;
+ state->directionPoint = params->directionPoint;
+ state->directionDelayRemainingMs = params->directionDelayMs;
+ PlsrHwSetDirLevel(axis, params->directionPositive);
+ state->state = (params->directionDelayMs > 0U)
+ ? PLSR_HW_STATE_DIR_SETTLING
+ : PLSR_HW_STATE_PWM_PENDING;
+ return PLSR_RESULT_OK;
+}
+
+PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz)
+{
+ PLSR_HW_AXIS_STATE *state;
+
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return PLSR_RESULT_INVALID_ARGUMENT;
+ }
+ state = &PlsrHwAxes[axis];
+ state->currentFrequencyHz = frequencyHz;
+ if (state->state == PLSR_HW_STATE_RUNNING)
+ {
+ if (frequencyHz > 0UL)
+ {
+ PlsrHwStartPwmTimer(axis, frequencyHz);
+ }
+ else
+ {
+ PlsrHwStopPwmTimer(axis);
+ }
+ }
+ else if ((state->state == PLSR_HW_STATE_PWM_PENDING)
+ && (frequencyHz > 0UL))
+ {
+ PlsrHwStartPwmTimer(axis, frequencyHz);
+ state->state = PLSR_HW_STATE_RUNNING;
+ }
+ return PLSR_RESULT_OK;
+}
+
+PLSR_RESULT PlsrHwStopPulse(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_IDLE)
+ {
+ PlsrHwStopPwmTimer(axis);
+ state->state = PLSR_HW_STATE_IDLE;
+ }
+ return PLSR_RESULT_OK;
+}
+
+uint8_t PlsrHwIsPulseActive(uint8_t axis)
+{
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return 0U;
+ }
+ return (PlsrHwAxes[axis].state == PLSR_HW_STATE_RUNNING) ? 1U : 0U;
+}
+
+PLSR_HW_STATE PlsrHwGetState(uint8_t axis)
+{
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return PLSR_HW_STATE_IDLE;
+ }
+ return PlsrHwAxes[axis].state;
+}
+
+uint32_t PlsrHwGetTimerClockHz(uint8_t axis)
+{
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return 0UL;
+ }
+ return PlsrHwAxisMap[axis].timerClockHz;
+}
+
+void PlsrHwTick(uint8_t axis)
+{
+ PLSR_HW_AXIS_STATE *state;
+
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return;
+ }
+ state = &PlsrHwAxes[axis];
+ switch (state->state)
+ {
+ case PLSR_HW_STATE_DIR_SETTLING:
+ if (state->directionDelayRemainingMs > 0U)
+ {
+ state->directionDelayRemainingMs--;
+ }
+ if (state->directionDelayRemainingMs == 0U)
+ {
+ state->state = PLSR_HW_STATE_PWM_PENDING;
+ }
+ break;
+
+ case PLSR_HW_STATE_PWM_PENDING:
+ if (state->currentFrequencyHz > 0UL)
+ {
+ PlsrHwStartPwmTimer(axis, state->currentFrequencyHz);
+ state->state = PLSR_HW_STATE_RUNNING;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* 输出定时器更新中断:每周期末触发一次(=1 个脉冲)。 */
+void PlsrHwOnTimerUpdate(uint8_t axis)
+{
+ PLSR_HW_AXIS_STATE *state;
+
+ if (axis >= PLSR_HW_AXIS_COUNT)
+ {
+ return;
+ }
+ state = &PlsrHwAxes[axis];
+ PlsrHwTimerClearUif(axis);
+ if (state->state != PLSR_HW_STATE_RUNNING)
+ {
+ return;
+ }
+
+ state->emittedPulses++;
+ if (state->emittedPulses >= state->targetPulses)
+ {
+ /* 更新时刻 = 周期结束:关通道即完整下降沿后停止,无额外脉冲。 */
+ PlsrHwStopPwmTimer(axis);
+ state->state = PLSR_HW_STATE_DONE;
+ (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE);
+ }
+}
+
+#ifdef PLSR_HOST_TEST
+uint32_t PlsrHwTestGetArr(uint8_t axis)
+{
+ return PlsrHwTimers[axis].arr;
+}
+
+uint32_t PlsrHwTestGetCcr(uint8_t axis)
+{
+ return PlsrHwTimers[axis].ccr1;
+}
+
+uint32_t PlsrHwTestGetPsc(uint8_t axis)
+{
+ return PlsrHwTimers[axis].psc;
+}
+
+uint8_t PlsrHwTestGetPwmEnabled(uint8_t axis)
+{
+ return ((PlsrHwTimers[axis].ccer & PLSR_HW_TIMER_CHANNEL1_BIT) != 0UL)
+ ? 1U
+ : 0U;
+}
+
+uint8_t PlsrHwTestGetDirLevel(uint8_t axis)
+{
+ return PlsrHwTimers[axis].dirLevel;
+}
+
+void PlsrHwTestTriggerUpdate(uint8_t axis)
+{
+ PlsrHwOnTimerUpdate(axis);
+}
+#endif
+
+#ifndef PLSR_HOST_TEST
+void TIM1_UP_TIM10_IRQHandler(void)
+{
+ PlsrHwOnTimerUpdate(0U);
+}
+
+void TIM8_UP_TIM13_IRQHandler(void)
+{
+ PlsrHwOnTimerUpdate(1U);
+}
+
+void TIM1_TRG_COM_TIM11_IRQHandler(void)
+{
+ PlsrHwOnTimerUpdate(2U);
+}
+
+void TIM8_TRG_COM_TIM14_IRQHandler(void)
+{
+ PlsrHwOnTimerUpdate(3U);
+}
+#endif
diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1
index ebf55ee..8f7195f 100644
--- a/PLSR/Test/run_host_tests.ps1
+++ b/PLSR/Test/run_host_tests.ps1
@@ -30,6 +30,8 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_resource.c"
"$workspacePath\PLSR\Src\plsr_job.c"
"$workspacePath\PLSR\Src\plsr_path.c"
+ "$workspacePath\PLSR\Src\plsr_profile.c"
+ "$workspacePath\PLSR\Src\plsr_hal_f407.c"
"$workspacePath\PLSR\Src\plsr_core.c"
"$workspacePath\PLSR\Test\test_plsr_core.c"
)
@@ -42,6 +44,8 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_resource.c"
"$workspacePath\PLSR\Src\plsr_job.c"
"$workspacePath\PLSR\Src\plsr_path.c"
+ "$workspacePath\PLSR\Src\plsr_profile.c"
+ "$workspacePath\PLSR\Src\plsr_hal_f407.c"
"$workspacePath\PLSR\Src\plsr_core.c"
"$workspacePath\PLSR\Test\test_plsr_job.c"
)
@@ -54,6 +58,8 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_resource.c"
"$workspacePath\PLSR\Src\plsr_job.c"
"$workspacePath\PLSR\Src\plsr_path.c"
+ "$workspacePath\PLSR\Src\plsr_profile.c"
+ "$workspacePath\PLSR\Src\plsr_hal_f407.c"
"$workspacePath\PLSR\Src\plsr_core.c"
"$workspacePath\PLSR\Test\test_plsr_path.c"
)
@@ -64,6 +70,20 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_profile.c"
"$workspacePath\PLSR\Test\test_plsr_profile.c"
)
+ },
+ @{
+ Name = 'test_plsr_hal'
+ Sources = @(
+ "$workspacePath\PLSR\Src\plc_device.c"
+ "$workspacePath\PLSR\Src\plsr_persistence.c"
+ "$workspacePath\PLSR\Src\plsr_resource.c"
+ "$workspacePath\PLSR\Src\plsr_job.c"
+ "$workspacePath\PLSR\Src\plsr_path.c"
+ "$workspacePath\PLSR\Src\plsr_profile.c"
+ "$workspacePath\PLSR\Src\plsr_hal_f407.c"
+ "$workspacePath\PLSR\Src\plsr_core.c"
+ "$workspacePath\PLSR\Test\test_plsr_hal.c"
+ )
}
)
diff --git a/PLSR/Test/test_plsr_hal.c b/PLSR/Test/test_plsr_hal.c
new file mode 100644
index 0000000..93686f2
--- /dev/null
+++ b/PLSR/Test/test_plsr_hal.c
@@ -0,0 +1,421 @@
+#include "plc_device.h"
+#include "plsr_core.h"
+#include "plsr_hal_f407.h"
+#include "plsr_job.h"
+#include "plsr_persistence.h"
+#include
+#include
+
+#define TEST_WORD_CAPACITY (3000U)
+#define TEST_S0_BASE (100U)
+#define TEST_S1_BASE (200U)
+
+typedef struct
+{
+ uint16_t words[3][TEST_WORD_CAPACITY];
+} TEST_MEMORY;
+
+static int TestFailures;
+static int TestChecks;
+
+#define CHECK(condition) \
+ do \
+ { \
+ TestChecks++; \
+ if (!(condition)) \
+ { \
+ TestFailures++; \
+ (void)printf("FAIL line %d: %s\n", __LINE__, #condition); \
+ } \
+ } while (0)
+
+static uint8_t TestValidateWords(void *context,
+ PLSR_DEVICE_TYPE device,
+ uint32_t firstAddress,
+ uint32_t wordCount)
+{
+ (void)context;
+ (void)device;
+ return (((uint64_t)firstAddress + wordCount) <= TEST_WORD_CAPACITY)
+ ? 1U
+ : 0U;
+}
+
+static uint8_t TestReadWord(void *context,
+ PLSR_DEVICE_TYPE device,
+ uint32_t address,
+ uint16_t *value)
+{
+ TEST_MEMORY *memory = (TEST_MEMORY *)context;
+
+ if ((memory == NULL) || (value == NULL) || (device > PLSR_DEVICE_FD)
+ || (address >= TEST_WORD_CAPACITY))
+ {
+ return 0U;
+ }
+ *value = memory->words[device][address];
+ return 1U;
+}
+
+static uint8_t TestReadBit(void *context,
+ PLSR_DEVICE_TYPE device,
+ uint32_t address,
+ uint8_t *value)
+{
+ (void)context;
+ (void)device;
+ (void)address;
+ *value = 0U;
+ return 1U;
+}
+
+static void TestWriteDword(TEST_MEMORY *memory,
+ PLSR_DEVICE_TYPE device,
+ uint32_t address,
+ int32_t value)
+{
+ uint32_t raw = (uint32_t)value;
+
+ memory->words[device][address] = (uint16_t)(raw & 0xFFFFUL);
+ memory->words[device][address + 1UL] = (uint16_t)(raw >> 16U);
+}
+
+static void TestSetSegment(TEST_MEMORY *memory,
+ uint16_t number,
+ uint32_t frequency,
+ int32_t pulses)
+{
+ uint32_t base = TEST_S0_BASE + (uint32_t)number * 10UL;
+
+ TestWriteDword(memory, PLSR_DEVICE_D, base, (int32_t)frequency);
+ TestWriteDword(memory, PLSR_DEVICE_D, base + 2UL, pulses);
+ memory->words[PLSR_DEVICE_D][base + 4UL] = 0U;
+ TestWriteDword(memory, PLSR_DEVICE_D, base + 5UL, 0);
+ memory->words[PLSR_DEVICE_D][base + 7UL] = 0U;
+ TestWriteDword(memory, PLSR_DEVICE_D, base + 8UL, 0);
+}
+
+static void TestResetEnvironment(void)
+{
+ PlsrPersistenceTestResetStorage();
+ CHECK(PlcDeviceInit() == PLC_DEVICE_OK);
+ CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK);
+ CHECK(PlsrInit() == PLSR_RESULT_OK);
+}
+
+static PLSR_CALL TestMakeCall(TEST_MEMORY *memory)
+{
+ PLSR_CALL call;
+
+ (void)memset(&call, 0, sizeof(call));
+ call.sequence = 10UL;
+ call.source.context = memory;
+ call.source.validateWords = TestValidateWords;
+ call.source.readWord = TestReadWord;
+ call.source.readBit = TestReadBit;
+ call.s0.device = PLSR_DEVICE_D;
+ call.s0.address = TEST_S0_BASE;
+ call.s1.device = PLSR_DEVICE_D;
+ call.s1.address = TEST_S1_BASE;
+ call.s2.type = PLSR_OPERAND_CONSTANT;
+ call.s2.constant = 1;
+ call.dAxis = 0U;
+ call.outputModeOverride = PLSR_OUTPUT_MODE_FROM_SFD;
+ return call;
+}
+
+static PLSR_STATUS TestGetStatus(void)
+{
+ PLSR_STATUS status;
+
+ (void)memset(&status, 0, sizeof(status));
+ CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK);
+ return status;
+}
+
+/* ---- HAL 单测 ---- */
+
+static void TestMapping(void)
+{
+ (void)PlsrHwInit();
+ CHECK(PlsrHwGetTimerClockHz(0U) == 168000000UL);
+ CHECK(PlsrHwGetTimerClockHz(1U) == 168000000UL);
+ CHECK(PlsrHwGetTimerClockHz(2U) == 168000000UL);
+ CHECK(PlsrHwGetTimerClockHz(3U) == 168000000UL);
+ CHECK(PlsrHwGetTimerClockHz(4U) == 0UL);
+ CHECK(PlsrHwResolveDirectionPoint(4U) != 0U);
+ CHECK(PlsrHwResolveDirectionPoint(8U) == 0U);
+ CHECK(PlsrHwResolveDirectionPoint(20U) != 0U);
+ CHECK(PlsrHwResolveDirectionPoint(21U) == 0U);
+}
+
+static void TestDirDelaySequence(void)
+{
+ (void)PlsrHwInit();
+ PLSR_HW_START_PARAMS params;
+ uint16_t psc;
+ uint16_t arr;
+ int ticks;
+
+ (void)memset(¶ms, 0, sizeof(params));
+ params.frequencyHz = 1000UL;
+ params.targetPulses = 100;
+ params.directionPoint = 4U;
+ params.directionPositive = 1U;
+ params.directionDelayMs = 10U;
+
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
+ CHECK(PlsrHwTestGetDirLevel(0U) == 1U);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
+ CHECK(PlsrHwIsPulseActive(0U) == 0U);
+
+ for (ticks = 0; ticks < 9; ticks++)
+ {
+ PlsrHwTick(0U);
+ }
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
+
+ PlsrHwTick(0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
+
+ /* 首个非零频率启动 PWM,ARR/CCR 与分频计算一致。 */
+ CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
+ CHECK(PlsrHwIsPulseActive(0U) == 1U);
+ CHECK(PlsrCalculateTimerDivider(168000000UL, 1000UL, &psc, &arr)
+ == PLSR_RESULT_OK);
+ CHECK(PlsrHwTestGetArr(0U) == arr);
+ CHECK(PlsrHwTestGetPsc(0U) == psc);
+ CHECK(PlsrHwTestGetCcr(0U) == arr / 2UL);
+}
+
+static void TestZeroFrequencyWaits(void)
+{
+ (void)PlsrHwInit();
+ PLSR_HW_START_PARAMS params;
+
+ (void)memset(¶ms, 0, sizeof(params));
+ params.frequencyHz = 0UL;
+ params.targetPulses = 50;
+ params.directionPoint = PLSR_HW_DIR_POINT_NONE;
+ params.directionPositive = 1U;
+ params.directionDelayMs = 0U;
+
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
+ PlsrHwTick(0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
+
+ /* 起始速度为 0:profile 升频后首个非零频率才启动 PWM。 */
+ CHECK(PlsrHwSetFrequency(0U, 10UL) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
+}
+
+static void TestPulseCounting(void)
+{
+ (void)PlsrHwInit();
+ PLSR_HW_START_PARAMS params;
+ int pulse;
+
+ (void)memset(¶ms, 0, sizeof(params));
+ params.frequencyHz = 1000UL;
+ params.targetPulses = 5;
+ params.directionPoint = PLSR_HW_DIR_POINT_NONE;
+ params.directionPositive = 1U;
+ params.directionDelayMs = 0U;
+
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
+
+ for (pulse = 0; pulse < 4; pulse++)
+ {
+ PlsrHwTestTriggerUpdate(0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
+ }
+ /* 第 5 个脉冲:到目标,停止 + 段完成事件。 */
+ PlsrHwTestTriggerUpdate(0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
+ CHECK(PlsrHwIsPulseActive(0U) == 0U);
+
+ /* 停止后再触发更新中断无动作。 */
+ PlsrHwTestTriggerUpdate(0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE);
+}
+
+static void TestStopAndInvalidArgs(void)
+{
+ (void)PlsrHwInit();
+ PLSR_HW_START_PARAMS params;
+
+ (void)memset(¶ms, 0, sizeof(params));
+ params.frequencyHz = 1000UL;
+ params.targetPulses = 100;
+ params.directionPoint = PLSR_HW_DIR_POINT_NONE;
+ params.directionPositive = 1U;
+ params.directionDelayMs = 0U;
+
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK);
+ CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK);
+ CHECK(PlsrHwIsPulseActive(0U) == 1U);
+ CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK);
+ CHECK(PlsrHwIsPulseActive(0U) == 0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
+
+ CHECK(PlsrHwStartPulse(4U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT);
+ CHECK(PlsrHwStartPulse(0U, NULL) == PLSR_RESULT_INVALID_ARGUMENT);
+ params.targetPulses = 0;
+ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT);
+ CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT);
+ CHECK(PlsrHwStopPulse(4U) == PLSR_RESULT_INVALID_ARGUMENT);
+}
+
+/* ---- 端到端集成:START → 硬件 → 计数 → 事件 → 段间 → 完成 ---- */
+
+static void TestEndToEndTwoSegments(void)
+{
+ TEST_MEMORY memory;
+ PLSR_CALL call;
+ PLSR_STATUS status;
+ int ticks;
+ int pulse;
+
+ TestResetEnvironment();
+ (void)memset(&memory, 0, sizeof(memory));
+ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 2);
+ TestSetSegment(&memory, 1U, 1000U, 100);
+ TestSetSegment(&memory, 2U, 2000U, 200);
+
+ call = TestMakeCall(&memory);
+ CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED);
+ PlsrProcess();
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_ACCEL);
+ CHECK(status.currentSegment == 1U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING);
+
+ /* DIR 延时 10ms → PWM 启动(段1 起始速度 0,profile 升频后启动)。 */
+ for (ticks = 0; ticks < 10; ticks++)
+ {
+ PlsrProcess();
+ }
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U);
+
+ /* 加速完成 → 状态机进入 RUN。 */
+ for (ticks = 0; ticks < 500; ticks++)
+ {
+ PlsrProcess();
+ if (TestGetStatus().state == PLSR_STATE_RUN)
+ {
+ break;
+ }
+ }
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_RUN);
+ CHECK(status.currentSegment == 1U);
+
+ /* 段1 脉冲完成:100 次更新中断 → SEGMENT_COMPLETE → 段2 启动。 */
+ for (pulse = 0; pulse < 100; pulse++)
+ {
+ PlsrHwTestTriggerUpdate(0U);
+ }
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE);
+ PlsrProcess();
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_ACCEL);
+ CHECK(status.currentSegment == 2U);
+
+ /* 段2:DIR 延时 → PWM → 加速 → RUN。 */
+ for (ticks = 0; ticks < 600; ticks++)
+ {
+ PlsrProcess();
+ if (TestGetStatus().state == PLSR_STATE_RUN)
+ {
+ break;
+ }
+ }
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_RUN);
+ CHECK(status.currentSegment == 2U);
+
+ /* 段2 脉冲完成 → 任务结束。 */
+ for (pulse = 0; pulse < 200; pulse++)
+ {
+ PlsrHwTestTriggerUpdate(0U);
+ }
+ PlsrProcess();
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_COMPLETED);
+ CHECK(status.done != 0U);
+ /* 终态转换后 HAL 回 IDLE(允许重新启动),脉冲已停止。 */
+ CHECK(PlsrHwIsPulseActive(0U) == 0U);
+ CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U);
+}
+
+static void TestStopStopsHardware(void)
+{
+ TEST_MEMORY memory;
+ PLSR_CALL call;
+ PLSR_COMMAND command;
+ PLSR_STATUS status;
+ int ticks;
+
+ TestResetEnvironment();
+ (void)memset(&memory, 0, sizeof(memory));
+ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1);
+ TestSetSegment(&memory, 1U, 1000U, 10000);
+
+ call = TestMakeCall(&memory);
+ call.sequence = 20UL;
+ CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED);
+ PlsrProcess();
+ for (ticks = 0; ticks < 10; ticks++)
+ {
+ PlsrProcess();
+ }
+ CHECK(PlsrHwIsPulseActive(0U) == 1U);
+
+ /* STOP_IMMEDIATE:硬件立即停止。 */
+ command.sequence = 21UL;
+ command.axis = 0U;
+ command.opcode = PLSR_CMD_STOP_IMMEDIATE;
+ command.argument = 0;
+ CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED);
+ PlsrProcess();
+ CHECK(PlsrHwIsPulseActive(0U) == 0U);
+ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE);
+
+ CHECK(PlsrPostEvent(0U, PLSR_EVENT_STOP_IMMEDIATE_DONE)
+ == PLSR_RESULT_OK);
+ PlsrProcess();
+ status = TestGetStatus();
+ CHECK(status.state == PLSR_STATE_STOPPED);
+}
+
+int main(void)
+{
+ TestMapping();
+ TestDirDelaySequence();
+ TestZeroFrequencyWaits();
+ TestPulseCounting();
+ TestStopAndInvalidArgs();
+ TestEndToEndTwoSegments();
+ TestStopStopsHardware();
+
+ if (TestFailures != 0)
+ {
+ (void)printf("FAIL: %d of %d PLSR HAL checks failed\n",
+ TestFailures,
+ TestChecks);
+ return 1;
+ }
+ (void)printf("PASS: %d PLSR HAL checks\n", TestChecks);
+ return 0;
+}