- 新增 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 覆盖不到)deepseek
| @@ -1290,6 +1290,9 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_profile.h</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_hal_f407.h</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name> | |||
| </file> | |||
| @@ -1308,6 +1311,9 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_profile.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_hal_f407.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name> | |||
| </file> | |||
| @@ -0,0 +1,64 @@ | |||
| #ifndef PLSR_HAL_F407_H | |||
| #define PLSR_HAL_F407_H | |||
| #include "plsr_types.h" | |||
| #include <stdint.h> | |||
| #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 */ | |||
| @@ -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 <string.h> | |||
| @@ -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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,516 @@ | |||
| #include "plsr_hal_f407.h" | |||
| #include "plsr_address_map.h" | |||
| #include "plsr_core.h" | |||
| #include "plsr_job.h" | |||
| #include <string.h> | |||
| #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 | |||
| @@ -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" | |||
| ) | |||
| } | |||
| ) | |||
| @@ -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 <stdio.h> | |||
| #include <string.h> | |||
| #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; | |||
| } | |||