From f1babb383ac10c4b766fd82a38628214ae4b9afc Mon Sep 17 00:00:00 2001 From: ywh <2227158009@qq.com> Date: Sat, 8 Aug 2026 22:48:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=AC=E6=89=B9=E5=AE=8C=E6=88=90=E5=86=85?= =?UTF-8?q?=E5=AE=B9=EF=BC=9A=20HAL=20=E5=90=AF=E5=8A=A8=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20outputMode=EF=BC=8Ccore=20=E4=BC=9A?= =?UTF-8?q?=E6=98=8E=E7=A1=AE=E4=BC=A0=E9=80=92=20PULSE/DIR=E3=80=81AB=20?= =?UTF-8?q?=E6=88=96=20CW/CCW=20=E6=A8=A1=E5=BC=8F=E3=80=82=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20AB=20=E6=AD=A3=E5=90=91=E7=9B=B8=E5=BA=8F=EF=BC=9A0?= =?UTF-8?q?0=E2=86=9210=E2=86=9211=E2=86=9201=E2=86=9200=E3=80=82=20?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20AB=20=E5=8F=8D=E5=90=91=E7=9B=B8=E5=BA=8F?= =?UTF-8?q?=EF=BC=9A00=E2=86=9201=E2=86=9211=E2=86=9210=E2=86=9200?= =?UTF-8?q?=E3=80=82=20=E5=9B=9B=E6=AC=A1=E7=9B=B8=E4=BD=8D=E8=B7=B3?= =?UTF-8?q?=E5=8F=98=E3=80=81=E4=B8=80=E4=B8=AA=E5=AE=8C=E6=95=B4=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E5=8F=AA=E7=B4=AF=E8=AE=A1=E4=B8=80=E4=B8=AA=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E8=84=89=E5=86=B2=E3=80=82=20AB=20=E4=BB=85=E5=85=81?= =?UTF-8?q?=E8=AE=B8=20D=3DY0=20=E6=88=96=20Y2=EF=BC=8C=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=20Q0/Q1=E3=80=81Q2/Q3=E3=80=82=20AB=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=BF=BD=E7=95=A5=20DIR=20=E7=82=B9=E5=92=8C=20SFD907=20?= =?UTF-8?q?=E6=96=B9=E5=90=91=E5=BB=B6=E6=97=B6=E3=80=82=20=E8=B0=83?= =?UTF-8?q?=E9=A2=91=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0=20A=E3=80=81B=20?= =?UTF-8?q?=E4=B8=A4=E8=B7=AF=EF=BC=8C=E4=B8=94=E4=B8=8D=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=9B=B8=E4=BD=8D=E3=80=82=20=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E5=AE=8C=E6=88=90=E5=8F=AA=E5=9C=A8=E5=AE=8C=E6=95=B4?= =?UTF-8?q?=2000=20=E5=91=A8=E6=9C=9F=E8=BE=B9=E7=95=8C=E5=81=9C=E6=AD=A2?= =?UTF-8?q?=E3=80=82=20=E7=B4=A7=E6=80=A5=E5=81=9C=E6=AD=A2=E4=BC=9A?= =?UTF-8?q?=E5=B0=86=E4=B8=BB=E6=9C=BA=E6=A8=A1=E5=9E=8B=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=87=B3=E5=AE=89=E5=85=A8=2000=E3=80=82=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=A4=E7=BB=84=20AB=20=E8=BD=B4=E7=8B=AC=E7=AB=8B=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=EF=BC=8C=E5=81=9C=E6=AD=A2=E4=B8=80=E8=BD=B4=E4=B8=8D?= =?UTF-8?q?=E4=BC=9A=E5=BD=B1=E5=93=8D=E5=8F=A6=E4=B8=80=E8=BD=B4=E3=80=82?= =?UTF-8?q?=20CW/CCW=20=E7=9B=AE=E5=89=8D=E6=98=8E=E7=A1=AE=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20PLSR=5FRESULT=5FNOT=5FSUPPORTED=E3=80=82=20?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E6=9D=BF=20AB=20=E7=A1=AC=E4=BB=B6=E5=B0=9A?= =?UTF-8?q?=E6=9C=AA=E5=AE=9E=E7=8E=B0=E6=97=B6=E4=B9=9F=E6=98=8E=E7=A1=AE?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=E5=90=AF=E5=8A=A8=EF=BC=8C=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E8=AF=AF=E8=BE=93=E5=87=BA=E4=B8=BA=E5=8D=95=E8=B7=AF=20PWM?= =?UTF-8?q?=E3=80=82=20=E5=90=8C=E6=97=B6=E4=BF=AE=E5=A4=8D=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E6=97=A7=E8=BE=B9=E7=95=8C=E9=97=AE=E9=A2=98=EF=BC=9A?= =?UTF-8?q?=20=E9=9B=B6=E8=84=89=E5=86=B2=E8=B7=B3=E8=BD=AC=E9=93=BE?= =?UTF-8?q?=E8=80=97=E5=B0=BD=E6=9C=AC=E8=BD=AE=E9=A2=84=E7=AE=97=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E8=AF=AF=E5=90=AF=E5=8A=A8=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=200=20=E8=84=89=E5=86=B2=E7=A1=AC=E4=BB=B6=E6=AE=B5?= =?UTF-8?q?=E3=80=82=20=E6=9E=81=E7=9F=AD=E6=AE=B5=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E5=9C=A8=20ACCEL=20=E6=88=96=20DECEL=20=E9=98=B6=E6=AE=B5?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=EF=BC=9B=E7=8E=B0=E5=9C=A8=20ACCEL/RUN/DECEL?= =?UTF-8?q?=20=E5=9D=87=E5=8F=AF=E6=AD=A3=E7=A1=AE=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=AE=B5=E5=AE=8C=E6=88=90=E4=BA=8B=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLSR/Inc/plsr_hal_f407.h | 5 + PLSR/Src/plsr_core.c | 70 +++++++++--- PLSR/Src/plsr_hal_f407.c | 219 +++++++++++++++++++++++++++++++++++--- PLSR/Test/test_plsr_hal.c | 193 +++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+), 30 deletions(-) diff --git a/PLSR/Inc/plsr_hal_f407.h b/PLSR/Inc/plsr_hal_f407.h index 216c4c5..37fbb61 100644 --- a/PLSR/Inc/plsr_hal_f407.h +++ b/PLSR/Inc/plsr_hal_f407.h @@ -24,6 +24,7 @@ typedef struct { uint32_t frequencyHz; /* 初始频率(起始速度,可为 0) */ int64_t targetPulses; /* 本段目标脉冲数 */ + PLSR_OUTPUT_MODE outputMode; /* PULSE/DIR、AB 或 CW/CCW */ uint8_t directionPoint; /* DIR 输出点(Y 点号,0xFF=无) */ uint8_t directionPositive; uint16_t directionDelayMs; @@ -57,7 +58,11 @@ uint32_t PlsrHwTestGetCr1(uint8_t axis); uint32_t PlsrHwTestGetPsc(uint8_t axis); uint8_t PlsrHwTestGetPwmEnabled(uint8_t axis); uint8_t PlsrHwTestGetDirLevel(uint8_t axis); +uint8_t PlsrHwTestGetAbPhaseA(uint8_t axis); +uint8_t PlsrHwTestGetAbPhaseB(uint8_t axis); +uint8_t PlsrHwTestGetAbQuarter(uint8_t axis); void PlsrHwTestTriggerUpdate(uint8_t axis); +void PlsrHwTestAdvanceAbQuarter(uint8_t axis); #endif #ifdef __cplusplus diff --git a/PLSR/Src/plsr_core.c b/PLSR/Src/plsr_core.c index 17ef149..ce7b748 100644 --- a/PLSR/Src/plsr_core.c +++ b/PLSR/Src/plsr_core.c @@ -59,7 +59,8 @@ 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 PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, + PLSR_AXIS *axisObject); static uint32_t PlsrCoreEnterCritical(void) { @@ -636,7 +637,22 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, else { /* 启动当前段硬件输出与速度曲线。 */ - PlsrStartSegmentHardware(call->dAxis, axisObject); + /* 零脉冲跳转链耗尽本轮预算时没有实际运动段,等待下一次 + * PlsrPathTick 找到非零段后再启动硬件。 */ + if (axisObject->path.jumpChainPending == 0U) + { + result = PlsrStartSegmentHardware(call->dAxis, axisObject); + } + if (result != PLSR_RESULT_OK) + { + axisObject->error = PLSR_ERROR_TIMER_FAULT; + PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); + axisObject->done = 0U; + (void)PlsrStateTransition(call->dAxis, + PLSR_STATE_ERROR, + PLSR_TRANSITION_FAULT); + return result; + } /* 运动开始:掉电恢复时据此判定"断电时在运动中"。 */ (void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 1U); (void)PlcDeviceCheckpointHsd(); @@ -1008,13 +1024,15 @@ static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events) /* 启动当前段的硬件输出与速度曲线(P3a:单轴 PULSE/DIR)。 * 由段进入 ACCEL 时调用(任务启动 + 段间推进)。 */ -static void PlsrStartSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) +static PLSR_RESULT 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; + PLSR_RESULT result; int64_t pulses; uint8_t positive; @@ -1050,24 +1068,32 @@ static void PlsrStartSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) : 0UL; profileRequest.curveMode = job->s2.curveMode; - if (PlsrProfileStart(&axisObject->profile, - &profileRequest, - pulses, - 1000U) == PLSR_RESULT_OK) + result = PlsrProfileStart(&axisObject->profile, + &profileRequest, + pulses, + 1000U); + if (result != PLSR_RESULT_OK) { - axisObject->profileActive = 1U; - axisObject->profileWasAccel = - (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) - ? 1U - : 0U; + return result; } params.frequencyHz = job->s2.startSpeed; params.targetPulses = pulses; + params.outputMode = (PLSR_OUTPUT_MODE)job->outputMode; params.directionPoint = job->directionPoint; params.directionPositive = positive; params.directionDelayMs = job->s2.directionDelayMs; - (void)PlsrHwStartPulse(axis, ¶ms); + result = PlsrHwStartPulse(axis, ¶ms); + if (result != PLSR_RESULT_OK) + { + axisObject->profileActive = 0U; + axisObject->profileWasAccel = 0U; + return result; + } + axisObject->profileActive = 1U; + axisObject->profileWasAccel = + (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; + return PLSR_RESULT_OK; } /* 停止当前段的硬件输出与速度曲线。 */ @@ -1086,6 +1112,9 @@ static void PlsrApplyPathAction(uint8_t axis, PLSR_PATH_ACTION action) switch (action) { case PLSR_PATH_ACTION_NEXT_SEGMENT: + /* SEGMENT_COMPLETE 表示上一段输出边界已经结束。统一收口旧段 + * profile/HAL 后再启动新段;真实 IRQ 与测试注入事件均一致。 */ + PlsrStopSegmentHardware(axis, axisObject); if (axisObject->state == PLSR_STATE_WAIT) { (void)PlsrStateTransition(axis, @@ -1103,7 +1132,16 @@ static void PlsrApplyPathAction(uint8_t axis, PLSR_PATH_ACTION action) /* 进入新段:重新启动硬件输出与速度曲线。 */ if (axisObject->state == PLSR_STATE_ACCEL) { - PlsrStartSegmentHardware(axis, axisObject); + if (PlsrStartSegmentHardware(axis, axisObject) + != 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); + } } break; @@ -1208,7 +1246,9 @@ static void PlsrProcessNormalEvents(uint8_t axis, uint32_t events) } if (((events & PLSR_EVENT_SEGMENT_COMPLETE) != 0UL) - && (axisObject->state == PLSR_STATE_RUN) + && ((axisObject->state == PLSR_STATE_ACCEL) + || (axisObject->state == PLSR_STATE_RUN) + || (axisObject->state == PLSR_STATE_DECEL)) && (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED) && (axisObject->immediateStopPending == 0U)) { diff --git a/PLSR/Src/plsr_hal_f407.c b/PLSR/Src/plsr_hal_f407.c index f5dc705..907a08e 100644 --- a/PLSR/Src/plsr_hal_f407.c +++ b/PLSR/Src/plsr_hal_f407.c @@ -13,6 +13,7 @@ #define PLSR_HW_TIMER_UPDATE_BIT (0x0001U) #define PLSR_HW_OUTPUT_POINT_COUNT (21U) #define PLSR_HW_DBG_SNAPSHOT_COUNT (160U) +#define PLSR_HW_AB_QUARTER_COUNT (4U) typedef struct { @@ -101,12 +102,14 @@ static PLSR_HW_TIMER_REGS PlsrHwTimers[PLSR_HW_AXIS_COUNT]; typedef struct { PLSR_HW_STATE state; + PLSR_OUTPUT_MODE outputMode; uint32_t currentFrequencyHz; int64_t targetPulses; int64_t emittedPulses; uint16_t directionDelayRemainingMs; uint8_t directionPoint; uint8_t directionPositive; + uint8_t abQuarter; } PLSR_HW_AXIS_STATE; static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT]; @@ -375,6 +378,63 @@ static void PlsrHwStopPwmTimer(uint8_t axis) PlsrHwTimerSetCen(axis, 0UL); } +static uint8_t PlsrHwIsAbBaseAxis(uint8_t axis) +{ + return ((axis == 0U) || (axis == 2U)) ? 1U : 0U; +} + +#ifdef PLSR_HOST_TEST +static uint8_t PlsrHwGetPairedAxis(uint8_t axis) +{ + return (uint8_t)(axis + 1U); +} +#endif + +/* P3b-1:AB 正交相序先在 host 模型中闭环。真实 STM32 双定时器的 + * 同步启动、相位偏置和安全停止将在 P3b-2 接入。 */ +static void PlsrHwConfigureActiveOutput(uint8_t axis, + PLSR_OUTPUT_MODE outputMode, + uint32_t frequencyHz) +{ + PlsrHwConfigurePwm(axis, frequencyHz); +#ifdef PLSR_HOST_TEST + if (outputMode == PLSR_OUTPUT_AB) + { + PlsrHwConfigurePwm(PlsrHwGetPairedAxis(axis), frequencyHz); + } +#else + (void)outputMode; +#endif +} + +static void PlsrHwBeginActiveOutput(uint8_t axis, + PLSR_OUTPUT_MODE outputMode) +{ + PlsrHwPwmBegin(axis); +#ifdef PLSR_HOST_TEST + if (outputMode == PLSR_OUTPUT_AB) + { + PlsrHwPwmBegin(PlsrHwGetPairedAxis(axis)); + } +#else + (void)outputMode; +#endif +} + +static void PlsrHwStopActiveOutput(uint8_t axis, + PLSR_OUTPUT_MODE outputMode) +{ + PlsrHwStopPwmTimer(axis); +#ifdef PLSR_HOST_TEST + if ((outputMode == PLSR_OUTPUT_AB) && (PlsrHwIsAbBaseAxis(axis) != 0U)) + { + PlsrHwStopPwmTimer(PlsrHwGetPairedAxis(axis)); + } +#else + (void)outputMode; +#endif +} + uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber) { /* 与资源层一致的合法输出点掩码(Q0~Q7、Q10~Q17、Q20)。 */ @@ -465,7 +525,7 @@ PLSR_RESULT PlsrHwInit(void) PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) { PLSR_HW_AXIS_STATE *state; - uint8_t directionChanged; + uint8_t directionChanged = 0U; if ((axis >= PLSR_HW_AXIS_COUNT) || (params == NULL)) { @@ -475,6 +535,27 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) { return PLSR_RESULT_INVALID_ARGUMENT; } + if ((uint32_t)params->outputMode > (uint32_t)PLSR_OUTPUT_CW_CCW) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + if ((params->outputMode == PLSR_OUTPUT_AB) + && (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return PLSR_RESULT_INVALID_AXIS; + } + if (params->outputMode == PLSR_OUTPUT_CW_CCW) + { + return PLSR_RESULT_NOT_SUPPORTED; + } +#ifndef PLSR_HOST_TEST + if (params->outputMode == PLSR_OUTPUT_AB) + { + /* P3b-1 只交付可验证的 host 相序模型;禁止目标板误输出成 + * 单路 PULSE/DIR。P3b-2 接入双定时器后移除此保护。 */ + return PLSR_RESULT_NOT_SUPPORTED; + } +#endif state = &PlsrHwAxes[axis]; if (state->state == PLSR_HW_STATE_RUNNING) { @@ -483,17 +564,37 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) /* 方向延时只在方向发生变化时生效(首次启动/换向/换方向点): * 段间同向衔接不再等待 10ms,直接进入 PWM 待启动。 */ - directionChanged = (state->directionPoint == PLSR_HW_DIR_POINT_NONE) - || (state->directionPoint != params->directionPoint) - || (state->directionPositive != params->directionPositive); + if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) + { + directionChanged = + (state->directionPoint == PLSR_HW_DIR_POINT_NONE) + || (state->directionPoint != params->directionPoint) + || (state->directionPositive != params->directionPositive); + } + state->outputMode = params->outputMode; state->targetPulses = params->targetPulses; state->emittedPulses = 0; state->currentFrequencyHz = params->frequencyHz; - state->directionPoint = params->directionPoint; + state->directionPoint = + (params->outputMode == PLSR_OUTPUT_PULSE_DIR) + ? params->directionPoint + : PLSR_HW_DIR_POINT_NONE; state->directionDelayRemainingMs = - (directionChanged != 0U) ? params->directionDelayMs : 0U; - PlsrHwSetDirLevel(axis, params->directionPositive); + ((params->outputMode == PLSR_OUTPUT_PULSE_DIR) + && (directionChanged != 0U)) + ? params->directionDelayMs + : 0U; + state->abQuarter = 0U; + if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) + { + PlsrHwSetDirLevel(axis, params->directionPositive); + } + else + { + state->directionPositive = + (params->directionPositive != 0U) ? 1U : 0U; + } state->state = (state->directionDelayRemainingMs > 0U) ? PLSR_HW_STATE_DIR_SETTLING : PLSR_HW_STATE_PWM_PENDING; @@ -515,18 +616,18 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) if (frequencyHz > 0UL) { /* 运行中调频:预装载写入,更新事件时生效,不触碰使能位。 */ - PlsrHwConfigurePwm(axis, frequencyHz); + PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); } else { - PlsrHwStopPwmTimer(axis); + PlsrHwStopActiveOutput(axis, state->outputMode); } } else if ((state->state == PLSR_HW_STATE_PWM_PENDING) && (frequencyHz > 0UL)) { - PlsrHwConfigurePwm(axis, frequencyHz); - PlsrHwPwmBegin(axis); + PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); + PlsrHwBeginActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_RUNNING; } return PLSR_RESULT_OK; @@ -543,7 +644,8 @@ PLSR_RESULT PlsrHwStopPulse(uint8_t axis) state = &PlsrHwAxes[axis]; if (state->state != PLSR_HW_STATE_IDLE) { - PlsrHwStopPwmTimer(axis); + PlsrHwStopActiveOutput(axis, state->outputMode); + state->abQuarter = 0U; state->state = PLSR_HW_STATE_IDLE; } return PLSR_RESULT_OK; @@ -631,8 +733,10 @@ void PlsrHwTick(uint8_t axis) case PLSR_HW_STATE_PWM_PENDING: if (state->currentFrequencyHz > 0UL) { - PlsrHwConfigurePwm(axis, state->currentFrequencyHz); - PlsrHwPwmBegin(axis); + PlsrHwConfigureActiveOutput(axis, + state->outputMode, + state->currentFrequencyHz); + PlsrHwBeginActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_RUNNING; } break; @@ -661,12 +765,18 @@ void PlsrHwOnTimerUpdate(uint8_t axis) { return; } + if (state->outputMode != PLSR_OUTPUT_PULSE_DIR) + { + /* AB host 模型按四分之一周期推进,不能把任一物理定时器的 + * update 直接当成完整 AB 指令脉冲。 */ + return; + } state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { /* 更新时刻 = 周期结束:关通道即完整下降沿后停止,无额外脉冲。 */ - PlsrHwStopPwmTimer(axis); + PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } @@ -710,6 +820,85 @@ uint8_t PlsrHwTestGetDirLevel(uint8_t axis) return PlsrHwTimers[axis].dirLevel; } +uint8_t PlsrHwTestGetAbPhaseA(uint8_t axis) +{ + const PLSR_HW_AXIS_STATE *state; + + if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return 0U; + } + state = &PlsrHwAxes[axis]; + if (state->directionPositive != 0U) + { + return ((state->abQuarter == 1U) || (state->abQuarter == 2U)) + ? 1U + : 0U; + } + return ((state->abQuarter == 2U) || (state->abQuarter == 3U)) + ? 1U + : 0U; +} + +uint8_t PlsrHwTestGetAbPhaseB(uint8_t axis) +{ + const PLSR_HW_AXIS_STATE *state; + + if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return 0U; + } + state = &PlsrHwAxes[axis]; + if (state->directionPositive != 0U) + { + return ((state->abQuarter == 2U) || (state->abQuarter == 3U)) + ? 1U + : 0U; + } + return ((state->abQuarter == 1U) || (state->abQuarter == 2U)) + ? 1U + : 0U; +} + +uint8_t PlsrHwTestGetAbQuarter(uint8_t axis) +{ + if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return 0U; + } + return PlsrHwAxes[axis].abQuarter; +} + +void PlsrHwTestAdvanceAbQuarter(uint8_t axis) +{ + PLSR_HW_AXIS_STATE *state; + + if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return; + } + state = &PlsrHwAxes[axis]; + if ((state->state != PLSR_HW_STATE_RUNNING) + || (state->outputMode != PLSR_OUTPUT_AB)) + { + return; + } + + state->abQuarter = (uint8_t)((state->abQuarter + 1U) + % PLSR_HW_AB_QUARTER_COUNT); + if (state->abQuarter == 0U) + { + state->emittedPulses++; + if (state->emittedPulses >= state->targetPulses) + { + /* 仅在完整 00 边界停机,禁止留下半个正交周期。 */ + PlsrHwStopActiveOutput(axis, state->outputMode); + state->state = PLSR_HW_STATE_DONE; + (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); + } + } +} + void PlsrHwTestTriggerUpdate(uint8_t axis) { if (axis < PLSR_HW_AXIS_COUNT) diff --git a/PLSR/Test/test_plsr_hal.c b/PLSR/Test/test_plsr_hal.c index 42ba31d..d15d3af 100644 --- a/PLSR/Test/test_plsr_hal.c +++ b/PLSR/Test/test_plsr_hal.c @@ -274,6 +274,147 @@ static void TestPulseCounting(void) CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); } +static void TestAbPhaseAndCounting(void) +{ + PLSR_HW_START_PARAMS params; + uint16_t psc; + uint16_t arr; + static const uint8_t positiveA[4] = {1U, 1U, 0U, 0U}; + static const uint8_t positiveB[4] = {0U, 1U, 1U, 0U}; + static const uint8_t negativeA[4] = {0U, 1U, 1U, 0U}; + static const uint8_t negativeB[4] = {1U, 1U, 0U, 0U}; + int quarter; + + (void)PlsrHwInit(); + (void)memset(¶ms, 0, sizeof(params)); + params.frequencyHz = 1000UL; + params.targetPulses = 2; + params.outputMode = PLSR_OUTPUT_AB; + params.directionPoint = 4U; /* AB 模式必须忽略独立 DIR 点。 */ + params.directionPositive = 1U; + params.directionDelayMs = 10U; /* AB 模式不得执行方向延时。 */ + + CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_INVALID_AXIS); + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); + CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); + CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); + CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); + CHECK(PlsrCalculateTimerDivider(168000000UL, 1000UL, &psc, &arr) + == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetPsc(0U) == psc); + CHECK(PlsrHwTestGetArr(0U) == arr); + CHECK(PlsrCalculateTimerDivider(84000000UL, 1000UL, &psc, &arr) + == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetPsc(1U) == psc); + CHECK(PlsrHwTestGetArr(1U) == arr); + + /* 任一物理 timer update 不能直接计作完整 AB 周期。 */ + PlsrHwTestTriggerUpdate(0U); + CHECK(PlsrHwGetEmittedPulses(0U) == 0); + + /* 正向:00→10→11→01→00;四次相位跳变只计一个脉冲。 */ + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwTestGetAbPhaseA(0U) == positiveA[quarter]); + CHECK(PlsrHwTestGetAbPhaseB(0U) == positiveB[quarter]); + } + CHECK(PlsrHwGetEmittedPulses(0U) == 1); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + + /* 调频同时更新 A/B 两路,且不得重置正交相位。 */ + CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetAbQuarter(0U) == 0U); + CHECK(PlsrCalculateTimerDivider(168000000UL, 2000UL, &psc, &arr) + == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetArr(0U) == arr); + CHECK(PlsrCalculateTimerDivider(84000000UL, 2000UL, &psc, &arr) + == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetArr(1U) == arr); + + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + } + CHECK(PlsrHwGetEmittedPulses(0U) == 2); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); + CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); + CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); + CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); + + /* 反向:00→01→11→10→00。 */ + params.targetPulses = 1; + params.directionPositive = 0U; + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwTestGetAbPhaseA(0U) == negativeA[quarter]); + CHECK(PlsrHwTestGetAbPhaseB(0U) == negativeB[quarter]); + } + CHECK(PlsrHwGetEmittedPulses(0U) == 1); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); + + /* 紧急停止即使发生在周期中间,也必须回到安全 00。 */ + params.targetPulses = 10; + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwTestGetAbQuarter(0U) == 1U); + CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetAbQuarter(0U) == 0U); + CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); + CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); +} + +static void TestTwoAbAxesIndependent(void) +{ + PLSR_HW_START_PARAMS params; + int quarter; + + (void)PlsrHwInit(); + (void)memset(¶ms, 0, sizeof(params)); + params.frequencyHz = 1000UL; + params.targetPulses = 1; + params.outputMode = PLSR_OUTPUT_AB; + params.directionPoint = PLSR_HW_DIR_POINT_NONE; + params.directionPositive = 1U; + + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(2U, 2000UL) == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(2U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(3U) != 0U); + + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + } + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); + CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_RUNNING); + CHECK(PlsrHwGetEmittedPulses(2U) == 0); + CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); + CHECK(PlsrHwTestGetPwmEnabled(2U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(3U) != 0U); + + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(2U); + } + CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_DONE); + CHECK(PlsrHwGetEmittedPulses(2U) == 1); +} + static void TestStopAndInvalidArgs(void) { (void)PlsrHwInit(); @@ -298,6 +439,11 @@ static void TestStopAndInvalidArgs(void) CHECK(PlsrHwStartPulse(0U, NULL) == PLSR_RESULT_INVALID_ARGUMENT); params.targetPulses = 0; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); + params.targetPulses = 1; + params.outputMode = PLSR_OUTPUT_CW_CCW; + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_NOT_SUPPORTED); + params.outputMode = (PLSR_OUTPUT_MODE)99; + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT); CHECK(PlsrHwStopPulse(4U) == PLSR_RESULT_INVALID_ARGUMENT); } @@ -392,6 +538,50 @@ static void TestEndToEndTwoSegments(void) CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); } +static void TestEndToEndAbSegment(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_STATUS status; + int quarter; + + TestResetEnvironment(); + (void)memset(&memory, 0, sizeof(memory)); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); + TestSetSegment(&memory, 1U, 1000U, -2); + + call = TestMakeCall(&memory); + call.sequence = 15UL; + call.outputModeOverride = PLSR_OUTPUT_AB; + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_ACCEL); + CHECK(status.outputMode == PLSR_OUTPUT_AB); + CHECK(status.directionPoint == PLSR_DIRECTION_POINT_NONE); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); + + /* 负脉冲选择反向相序,完整两个周期后由同一事件链结束任务。 */ + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); + CHECK(PlsrHwTestGetAbPhaseB(0U) == 1U); + for (quarter = 1; quarter < 8; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + } + CHECK(PlsrHwGetEmittedPulses(0U) == 2); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_COMPLETED); + CHECK(status.done != 0U); + CHECK(status.highResourceMask == 0U); + CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); +} + static void TestStopStopsHardware(void) { TEST_MEMORY memory; @@ -438,8 +628,11 @@ int main(void) TestDirDelaySequence(); TestZeroFrequencyWaits(); TestPulseCounting(); + TestAbPhaseAndCounting(); + TestTwoAbAxesIndependent(); TestStopAndInvalidArgs(); TestEndToEndTwoSegments(); + TestEndToEndAbSegment(); TestStopStopsHardware(); if (TestFailures != 0)