#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 #define PLSR_CORE_SFD_AXIS_STRIDE (130U) #ifndef PLSR_HOST_TEST #include "stm32f4xx.h" #include "ucos_ii.h" #endif typedef struct { PLSR_STATE state; PLSR_OUTPUT_MODE outputMode; PLSR_ERROR error; uint16_t compatibleErrorCode; uint16_t compatibleErrorBlock; PLSR_STOP_REASON stopReason; PLSR_STATE pendingTerminal; PLSR_STATE pauseReturnState; PLSR_RESOURCE_LEASE lease; PLSR_JOB_SNAPSHOT job; PLSR_PARSE_DETAIL parseDetail; volatile uint32_t pendingEvents; uint32_t lastCommandSequence; PLSR_RESULT lastCommandResult; uint32_t illegalTransitionCount; int64_t logicalPosition; int64_t taskPulses; int64_t totalPulses; uint64_t physicalPulses; int64_t segmentAccountedPulses; int64_t equivalentCommandRemainder; PLSR_EQUIVALENT_CONFIG equivalent; PLSR_PATH_CONTEXT path; PLSR_PROFILE_STATE profile; int32_t liveFrequencyRaw; uint32_t liveTargetFrequencyHz; uint32_t pauseStopFrequencyHz; uint32_t liveFrequencyRejectCount; PLSR_RESULT lastLiveFrequencyResult; /* Publish gate shared by PlsrTask and the TIM6 100us control ISR. */ volatile uint8_t profileActive; uint8_t profileWasAccel; uint8_t hasLastCommand; uint8_t done; uint8_t directionPositive; uint8_t immediateStopPending; uint8_t positionValid; uint8_t jobValid; uint8_t positionOverflow; uint8_t segmentAccountingActive; uint8_t positiveLimitActive; uint8_t negativeLimitActive; uint8_t emergencyLatched; uint8_t segmentEventPublished; uint8_t backlashActive; uint8_t backlashBypassOnce; uint8_t lastUserDirectionValid; uint8_t lastUserDirectionPositive; uint8_t runtimeSpeedClamped; PLSR_PATH_ACTION pendingBoundaryAction; } PLSR_AXIS; typedef struct { PLSR_COMMAND command; PLSR_START_REQUEST start; PLSR_CALL call; uint32_t ticket; uint8_t hasStart; uint8_t hasCall; uint8_t occupied; } PLSR_COMMAND_SLOT; static PLSR_AXIS PlsrAxes[PLSR_AXIS_COUNT]; static PLSR_COMMAND_SLOT PlsrCommandQueue[PLSR_COMMAND_QUEUE_DEPTH]; static uint32_t PlsrNextTicket; static uint8_t PlsrInitialized; static PLSR_JOB_SNAPSHOT PlsrJobScratch; static PLSR_JOB_SNAPSHOT PlsrValidationScratch; static void (* volatile PlsrControlTickHook)(void); static volatile uint32_t PlsrMaxProcessCycles; static volatile uint32_t PlsrMaxProcessResponseCycles; static volatile uint32_t PlsrMaxProcessStageCycles[PLSR_PROCESS_STAGE_COUNT]; static uint8_t PlsrDeferHsdCheckpoint; static uint8_t PlsrHsdCheckpointPending; static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject); static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject); static void PlsrAccountHardwarePulses(uint8_t axis, PLSR_AXIS *axisObject); static void PlsrPublishSegmentEvent(uint8_t axis, PLSR_AXIS *axisObject, PLSR_STOP_REASON reason); static uint8_t PlsrAnyAxisBusy(void); static uint8_t PlsrAllAxesPositionValid(void); #ifndef PLSR_HOST_TEST static void PlsrUpdateProcessStageMax(uint8_t stage, uint32_t started, uint64_t startedIsrCycles, uint32_t finished, uint64_t finishedIsrCycles) { uint32_t responseCycles = finished - started; uint64_t preemptedCycles = finishedIsrCycles - startedIsrCycles; uint32_t processCycles = (preemptedCycles < (uint64_t)responseCycles) ? responseCycles - (uint32_t)preemptedCycles : 0UL; if ((stage < PLSR_PROCESS_STAGE_COUNT) && (processCycles > PlsrMaxProcessStageCycles[stage])) { PlsrMaxProcessStageCycles[stage] = processCycles; } } #endif static void PlsrFlushHsdCheckpoint(void) { if (PlsrHsdCheckpointPending != 0U) { (void)PlcDeviceSetHsdCheckpointMeta( PlsrAllAxesPositionValid(), PlsrAnyAxisBusy()); if (PlcDeviceCheckpointHsd() == PLC_DEVICE_OK) { PlsrHsdCheckpointPending = 0U; } } } static void PlsrCheckpointHsd(void) { PlsrHsdCheckpointPending = 1U; if (PlsrDeferHsdCheckpoint == 0U) { PlsrFlushHsdCheckpoint(); } } static void PlsrCheckpointHsdImmediate(void) { uint8_t deferHsdCheckpoint = PlsrDeferHsdCheckpoint; PlsrHsdCheckpointPending = 1U; PlsrDeferHsdCheckpoint = 0U; PlsrFlushHsdCheckpoint(); PlsrDeferHsdCheckpoint = deferHsdCheckpoint; } static uint32_t PlsrCoreEnterCritical(void) { #ifdef PLSR_HOST_TEST return 0UL; #else uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); return interruptState; #endif } static void PlsrCoreExitCritical(uint32_t interruptState) { #ifdef PLSR_HOST_TEST (void)interruptState; #else __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrSetProfileActive(PLSR_AXIS *axisObject, uint8_t active) { uint32_t interruptState = PlsrCoreEnterCritical(); /* Ownership hand-off between PlsrTask and the TIM6 100us ISR. */ axisObject->profileActive = active; PlsrCoreExitCritical(interruptState); } void PlsrSetControlTickHook(void (*hook)(void)) { uint32_t interruptState = PlsrCoreEnterCritical(); PlsrControlTickHook = hook; PlsrCoreExitCritical(interruptState); } static uint8_t PlsrStateIsBusy(PLSR_STATE state) { return ((state == PLSR_STATE_ACCEL) || (state == PLSR_STATE_RUN) || (state == PLSR_STATE_DECEL) || (state == PLSR_STATE_WAIT) || (state == PLSR_STATE_PAUSED)) ? 1U : 0U; } static uint8_t PlsrStateIsPulseActive(PLSR_STATE state) { return ((state == PLSR_STATE_ACCEL) || (state == PLSR_STATE_RUN) || (state == PLSR_STATE_DECEL)) ? 1U : 0U; } static void PlsrLoadAxisEquivalentConfig(uint8_t axis, PLSR_EQUIVALENT_CONFIG *config) { uint16_t base = (uint16_t)(PLSR_SFD_CONFIG_START + (uint16_t)axis * PLSR_CORE_SFD_AXIS_STRIDE); uint16_t word; uint16_t lowWord; uint16_t highWord; config->unitCode = 0U; config->pulsesPerRevolution = 1UL; config->movementPerRevolution = 1UL; if (PlcDeviceReadSfd(base, &word) != PLC_DEVICE_OK) { return; } config->unitCode = (uint8_t)((word >> 8U) & 0x07U); if ((PlcDeviceReadSfd((uint16_t)(base + 2U), &lowWord) != PLC_DEVICE_OK) || (PlcDeviceReadSfd((uint16_t)(base + 3U), &highWord) != PLC_DEVICE_OK)) { config->unitCode = 0U; return; } config->pulsesPerRevolution = (uint32_t)lowWord | ((uint32_t)highWord << 16U); if ((PlcDeviceReadSfd((uint16_t)(base + 4U), &lowWord) != PLC_DEVICE_OK) || (PlcDeviceReadSfd((uint16_t)(base + 5U), &highWord) != PLC_DEVICE_OK)) { config->unitCode = 0U; return; } config->movementPerRevolution = (uint32_t)lowWord | ((uint32_t)highWord << 16U); if (PlsrPositionValidateEquivalent(config) != PLSR_RESULT_OK) { config->unitCode = 0U; config->pulsesPerRevolution = 1UL; config->movementPerRevolution = 1UL; } } static void PlsrPublishSdDword(uint8_t axis, uint8_t lowItem, int32_t value) { (void)PlcDevicePublishSdDword(axis, lowItem, value); } static int32_t PlsrGetCompatibleSegmentPulses(const PLSR_AXIS *axisObject) { int64_t signedPulses; if (axisObject->backlashActive != 0U) { return 0; } signedPulses = (axisObject->directionPositive != 0U) ? axisObject->segmentAccountedPulses : -axisObject->segmentAccountedPulses; if (signedPulses > INT32_MAX) { return INT32_MAX; } if (signedPulses < INT32_MIN) { return INT32_MIN; } return (int32_t)signedPulses; } static int32_t PlsrClampCompatibleInt32(int64_t value) { if (value > INT32_MAX) return INT32_MAX; if (value < INT32_MIN) return INT32_MIN; return (int32_t)value; } static void PlsrPublishSegmentEvent(uint8_t axis, PLSR_AXIS *axisObject, PLSR_STOP_REASON reason) { uint16_t segment; if ((axisObject->jobValid == 0U) || (axisObject->segmentEventPublished != 0U)) { return; } segment = PlsrPathGetCurrentSegment(&axisObject->path); if ((segment == 0U) || (segment > PLSR_MAX_SEGMENTS)) { return; } if (PlcDevicePublishEvent(axis, segment, (uint16_t)reason) == PLC_DEVICE_OK) { axisObject->segmentEventPublished = 1U; } } static void PlsrPublishRuntime(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; uint16_t currentSegment = (axisObject->jobValid != 0U) ? PlsrPathGetCurrentSegment( &axisObject->path) : 0U; int32_t segmentPulses = PlsrGetCompatibleSegmentPulses(axisObject); int32_t frequencyHz = (int32_t)PlsrHwGetCurrentFrequencyHz(axis); int64_t segmentEquivalent64 = segmentPulses; uint32_t speed = (uint32_t)frequencyHz; uint16_t publishedError; uint16_t publishedBlock; if (PlsrPositionPulsesToUnits(&axisObject->equivalent, segmentPulses, &segmentEquivalent64) != PLSR_RESULT_OK) { axisObject->positionOverflow = 1U; segmentEquivalent64 = (segmentPulses < 0) ? INT32_MIN : INT32_MAX; } if (PlsrPositionPulseFrequencyToSpeed(&axisObject->equivalent, (uint32_t)frequencyHz, &speed) != PLSR_RESULT_OK) { speed = UINT32_MAX; } PlsrPublishSdDword(axis, PLSR_SD_ITEM_SEGMENT, (int32_t)currentSegment); PlsrPublishSdDword(axis, PLSR_SD_ITEM_SEGMENT_PULSES, segmentPulses); PlsrPublishSdDword(axis, PLSR_SD_ITEM_SEGMENT_EQUIV, PlsrClampCompatibleInt32(segmentEquivalent64)); PlsrPublishSdDword(axis, PLSR_SD_ITEM_FREQUENCY, frequencyHz); PlsrPublishSdDword(axis, PLSR_SD_ITEM_SPEED, (speed > (uint32_t)INT32_MAX) ? INT32_MAX : (int32_t)speed); /* SD(B+10/+11)只发布信捷兼容码;项目内部符号错误保留在状态API, * 不得用枚举数值占用信捷固定错误码。 */ publishedError = axisObject->compatibleErrorCode; publishedBlock = (publishedError != 0U) ? axisObject->compatibleErrorBlock : 0U; (void)PlcDevicePublishSd(axis, PLSR_SD_ITEM_ERROR_CODE, (int32_t)publishedError); (void)PlcDevicePublishSd(axis, PLSR_SD_ITEM_ERROR_BLOCK, (int32_t)publishedBlock); } static void PlsrSetCompatibleParseError(uint8_t axis, PLSR_AXIS *axisObject) { uint16_t commonBase = (uint16_t)(PLSR_SFD_CONFIG_START + (uint16_t)axis * PLSR_CORE_SFD_AXIS_STRIDE); uint16_t dynamicBase = (uint16_t)(PLSR_HSD_CONFIG_START + (uint16_t)axis * 20U); uint16_t set; axisObject->compatibleErrorCode = 0U; axisObject->compatibleErrorBlock = 0U; if ((axisObject->parseDetail.result == PLSR_RESULT_RESOURCE_CONFLICT) || (axisObject->parseDetail.result == PLSR_RESULT_INVALID_RESOURCE)) { axisObject->compatibleErrorCode = 26U; return; } if ((axisObject->parseDetail.block == PLSR_PARSE_BLOCK_S0) && (axisObject->parseDetail.segment != 0U)) { axisObject->compatibleErrorCode = 1U; axisObject->compatibleErrorBlock = axisObject->parseDetail.segment; return; } if ((axisObject->parseDetail.address == (uint32_t)(commonBase + 2U)) || (axisObject->parseDetail.address == (uint32_t)(commonBase + 4U))) { axisObject->compatibleErrorCode = 2U; return; } if ((axisObject->parseDetail.address == (uint32_t)(dynamicBase + 12U)) || (axisObject->parseDetail.address == (uint32_t)(dynamicBase + 13U))) { axisObject->compatibleErrorCode = (axisObject->parseDetail.address == (uint32_t)(dynamicBase + 12U)) ? 15U : 16U; return; } for (set = 0U; set < 4U; set++) { uint32_t setBase = (uint32_t)commonBase + 50UL + (uint32_t)set * 20UL; if (axisObject->parseDetail.address == setBase + 12UL) { axisObject->compatibleErrorCode = 15U; return; } if (axisObject->parseDetail.address == setBase + 13UL) { axisObject->compatibleErrorCode = 16U; return; } } if ((axisObject->parseDetail.address == (uint32_t)(commonBase + 30U)) || (axisObject->parseDetail.address == (uint32_t)(commonBase + 32U)) || (axisObject->parseDetail.result == PLSR_RESULT_SEGMENT_OVERFLOW) || (axisObject->parseDetail.result == PLSR_RESULT_BLOCK_OVERLAP) || (axisObject->parseDetail.result == PLSR_RESULT_ADDRESS_OVERFLOW)) { axisObject->compatibleErrorCode = 4U; return; } if (axisObject->parseDetail.block == PLSR_PARSE_BLOCK_S2) { axisObject->compatibleErrorCode = 3U; } } static void PlsrPublishAxis(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; uint8_t compatibleRun; compatibleRun = (uint8_t)((PlsrStateIsPulseActive(axisObject->state) != 0U) || (axisObject->state == PLSR_STATE_WAIT)); (void)PlcDevicePublishSm(axis, compatibleRun, axisObject->directionPositive); PlsrPublishRuntime(axis); } static void PlsrSetStopReason(PLSR_AXIS *axis, PLSR_STOP_REASON stopReason) { if (stopReason > axis->stopReason) { axis->stopReason = stopReason; } } /* 将 64 位逻辑位置发布为 HSD 的 32 位兼容值。 * 超出范围时保持最近一次合法值,只锁存诊断,禁止回绕或静默截断。 */ static void PlsrPublishPosition(uint8_t axis, PLSR_AXIS *axisObject) { int32_t compatValue; int64_t equivalentPosition; uint16_t base = (uint16_t)((uint16_t)axis * PLSR_HSD_RUNTIME_AXIS_COUNT); if ((axisObject->logicalPosition > INT32_MAX) || (axisObject->logicalPosition < INT32_MIN)) { axisObject->positionOverflow = 1U; return; } compatValue = (int32_t)axisObject->logicalPosition; (void)PlcDevicePublishHsdDword(base, compatValue); if ((PlsrPositionPulsesToUnits(&axisObject->equivalent, axisObject->logicalPosition, &equivalentPosition) != PLSR_RESULT_OK) || (equivalentPosition > INT32_MAX) || (equivalentPosition < INT32_MIN)) { axisObject->positionOverflow = 1U; return; } (void)PlcDevicePublishHsdDword((uint16_t)(base + 2U), (int32_t)equivalentPosition); } static uint8_t PlsrAddInt64Checked(int64_t left, int64_t right, int64_t *result) { if ((result == NULL) || ((right > 0) && (left > INT64_MAX - right)) || ((right < 0) && (left < INT64_MIN - right))) { return 0U; } *result = left + right; return 1U; } static PLSR_RESULT PlsrReadLimitInput(const PLSR_JOB_SNAPSHOT *job, uint8_t point, uint8_t activeLow, uint8_t *active) { uint8_t rawLevel; if ((job == NULL) || (active == NULL)) { return PLSR_RESULT_INVALID_ARGUMENT; } if (point == 0xFFU) { *active = 0U; return PLSR_RESULT_OK; } if ((job->source.readBit == NULL) || (job->source.readBit(job->source.context, PLSR_DEVICE_X, point, &rawLevel) == 0U)) { return PLSR_RESULT_DATA_ACCESS; } *active = (activeLow != 0U) ? ((rawLevel == 0U) ? 1U : 0U) : ((rawLevel != 0U) ? 1U : 0U); return PLSR_RESULT_OK; } static int64_t PlsrGetBrakingDistance(uint8_t axis, const PLSR_AXIS *axisObject) { uint64_t frequencyHz; uint64_t hardwareFrequencyHz; uint64_t denominator; uint64_t numerator; uint64_t brakingPulses; uint64_t samplingPulses; uint64_t frequencyQ32; uint32_t decelSlopeHzPerMs; uint32_t interruptState; uint8_t profileActive; /* frequencyQ32 is 64-bit and may be updated by TIM6. Snapshot it with * the related fields so the 1ms protection pass cannot observe a torn * value or a mixture of two control ticks. */ interruptState = PlsrCoreEnterCritical(); profileActive = axisObject->profileActive; decelSlopeHzPerMs = axisObject->profile.decelSlopeHzPerMs; frequencyQ32 = axisObject->profile.frequencyQ32; PlsrCoreExitCritical(interruptState); if ((profileActive == 0U) || (decelSlopeHzPerMs == 0UL)) { return 0; } frequencyHz = frequencyQ32 >> 32U; hardwareFrequencyHz = PlsrHwGetCurrentFrequencyHz(axis); if (hardwareFrequencyHz > frequencyHz) { /* Protection must follow the frequency already present at the output, * not an earlier/lower profile value waiting behind timer preload. */ frequencyHz = hardwareFrequencyHz; } numerator = frequencyHz * frequencyHz; denominator = UINT64_C(2000) * decelSlopeHzPerMs; brakingPulses = (numerator + denominator - 1UL) / denominator; /* The 1ms protection task can observe several new pulses per pass. The * inclusive position comparison already covers one of them; reserve only * the additional pulses so <=1kHz behavior is unchanged while higher * frequencies cannot cross the limit by a complete sampling window. */ samplingPulses = (frequencyHz + UINT64_C(999)) / UINT64_C(1000); if (samplingPulses > 0UL) { samplingPulses--; } if (brakingPulses > (uint64_t)INT64_MAX - samplingPulses) { return INT64_MAX; } return (int64_t)(brakingPulses + samplingPulses); } static PLSR_RESULT PlsrUpdateLimitState(PLSR_AXIS *axisObject, const PLSR_JOB_SNAPSHOT *job, uint8_t includeBrakingDistance) { int64_t brakingDistance = 0; uint8_t positiveHard; uint8_t negativeHard; uint8_t positiveSoft = 0U; uint8_t negativeSoft = 0U; PLSR_RESULT result; result = PlsrReadLimitInput(job, job->limits.positiveInputPoint, job->limits.positiveInputActiveLow, &positiveHard); if (result != PLSR_RESULT_OK) return result; result = PlsrReadLimitInput(job, job->limits.negativeInputPoint, job->limits.negativeInputActiveLow, &negativeHard); if (result != PLSR_RESULT_OK) return result; if ((job->limits.softLimitEnabled != 0U) && (axisObject->backlashActive == 0U) && (axisObject->positionValid != 0U)) { if (includeBrakingDistance != 0U) { brakingDistance = PlsrGetBrakingDistance(job->dAxis, axisObject); } if (axisObject->logicalPosition >= job->limits.positiveSoftLimitPulses) { positiveSoft = 1U; } else if ((axisObject->directionPositive != 0U) && (brakingDistance > 0) && ((axisObject->logicalPosition > INT64_MAX - brakingDistance) || (axisObject->logicalPosition + brakingDistance >= job->limits.positiveSoftLimitPulses))) { positiveSoft = 1U; } if (axisObject->logicalPosition <= job->limits.negativeSoftLimitPulses) { negativeSoft = 1U; } else if ((axisObject->directionPositive == 0U) && (brakingDistance > 0) && ((axisObject->logicalPosition < INT64_MIN + brakingDistance) || (axisObject->logicalPosition - brakingDistance <= job->limits.negativeSoftLimitPulses))) { negativeSoft = 1U; } } axisObject->positiveLimitActive = ((positiveHard != 0U) || (positiveSoft != 0U)) ? 1U : 0U; axisObject->negativeLimitActive = ((negativeHard != 0U) || (negativeSoft != 0U)) ? 1U : 0U; return PLSR_RESULT_OK; } static PLSR_RESULT PlsrCheckStartProtection(PLSR_AXIS *axisObject, const PLSR_JOB_SNAPSHOT *job) { PLSR_RESULT result; if (axisObject->emergencyLatched != 0U) { return PLSR_RESULT_EMERGENCY_LATCHED; } if ((job->limits.softLimitEnabled != 0U) && (axisObject->positionValid == 0U)) { return PLSR_RESULT_POSITION_INVALID; } result = PlsrUpdateLimitState(axisObject, job, 0U); if (result != PLSR_RESULT_OK) { return result; } if ((job->initialDirectionPositive != 0U) && (axisObject->positiveLimitActive != 0U)) { return PLSR_RESULT_LIMIT_POSITIVE; } if ((job->initialDirectionPositive == 0U) && (axisObject->negativeLimitActive != 0U)) { return PLSR_RESULT_LIMIT_NEGATIVE; } return PLSR_RESULT_OK; } /* 将 HAL 实际完成的完整脉冲/AB周期合并到64位位置。 * emittedPulses 每段从0开始,因此用 segmentAccountedPulses 做差量去重。 */ static void PlsrAccountHardwarePulses(uint8_t axis, PLSR_AXIS *axisObject) { int64_t emittedPulses; int64_t delta; int64_t signedDelta; int64_t newLogicalPosition; int64_t newTaskPulses; int64_t newTotalPulses; if (axisObject->segmentAccountingActive == 0U) { return; } emittedPulses = PlsrHwGetEmittedPulses(axis); if ((emittedPulses < 0) || (emittedPulses < axisObject->segmentAccountedPulses)) { axisObject->positionOverflow = 1U; (void)PlsrPostEvent(axis, PLSR_EVENT_COUNTER_FAULT); return; } delta = emittedPulses - axisObject->segmentAccountedPulses; if (delta == 0) { return; } if ((uint64_t)delta > UINT64_MAX - axisObject->physicalPulses) { axisObject->positionOverflow = 1U; axisObject->segmentAccountedPulses = emittedPulses; (void)PlsrPostEvent(axis, PLSR_EVENT_COUNTER_FAULT); return; } axisObject->physicalPulses += (uint64_t)delta; if (axisObject->backlashActive != 0U) { /* Backlash pulses move through mechanical clearance only. */ axisObject->segmentAccountedPulses = emittedPulses; PlsrPublishRuntime(axis); return; } signedDelta = (axisObject->directionPositive != 0U) ? delta : -delta; if ((PlsrAddInt64Checked(axisObject->logicalPosition, signedDelta, &newLogicalPosition) == 0U) || (PlsrAddInt64Checked(axisObject->taskPulses, signedDelta, &newTaskPulses) == 0U) || (PlsrAddInt64Checked(axisObject->totalPulses, delta, &newTotalPulses) == 0U)) { axisObject->positionOverflow = 1U; axisObject->segmentAccountedPulses = emittedPulses; (void)PlsrPostEvent(axis, PLSR_EVENT_COUNTER_FAULT); return; } axisObject->logicalPosition = newLogicalPosition; axisObject->taskPulses = newTaskPulses; axisObject->totalPulses = newTotalPulses; axisObject->segmentAccountedPulses = emittedPulses; PlsrPublishPosition(axis, axisObject); PlsrPublishRuntime(axis); } static uint8_t PlsrTransitionIsAllowed(PLSR_STATE current, PLSR_STATE target) { if (current == target) { return 1U; } if (target == PLSR_STATE_ERROR) { return 1U; } switch (current) { case PLSR_STATE_UNINITIALIZED: return (target == PLSR_STATE_IDLE) ? 1U : 0U; case PLSR_STATE_IDLE: case PLSR_STATE_COMPLETED: case PLSR_STATE_STOPPED: return ((target == PLSR_STATE_IDLE) || (target == PLSR_STATE_ACCEL) || (target == PLSR_STATE_WAIT) || (target == PLSR_STATE_COMPLETED)) ? 1U : 0U; case PLSR_STATE_ACCEL: case PLSR_STATE_RUN: return ((target == PLSR_STATE_ACCEL) || (target == PLSR_STATE_RUN) || (target == PLSR_STATE_DECEL) || (target == PLSR_STATE_WAIT) || (target == PLSR_STATE_COMPLETED) || (target == PLSR_STATE_STOPPED)) ? 1U : 0U; case PLSR_STATE_DECEL: return ((target == PLSR_STATE_ACCEL) || (target == PLSR_STATE_RUN) || (target == PLSR_STATE_WAIT) || (target == PLSR_STATE_PAUSED) || (target == PLSR_STATE_COMPLETED) || (target == PLSR_STATE_STOPPED)) ? 1U : 0U; case PLSR_STATE_WAIT: return ((target == PLSR_STATE_ACCEL) || (target == PLSR_STATE_RUN) || (target == PLSR_STATE_PAUSED) || (target == PLSR_STATE_COMPLETED) || (target == PLSR_STATE_STOPPED)) ? 1U : 0U; case PLSR_STATE_PAUSED: return ((target == PLSR_STATE_ACCEL) || (target == PLSR_STATE_RUN) || (target == PLSR_STATE_WAIT) || (target == PLSR_STATE_STOPPED)) ? 1U : 0U; case PLSR_STATE_ERROR: return (target == PLSR_STATE_IDLE) ? 1U : 0U; default: return 0U; } } PLSR_RESULT PlsrStateTransition(uint8_t axis, PLSR_STATE target, PLSR_TRANSITION_REASON reason) { PLSR_AXIS *axisObject; (void)reason; if (axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if ((uint32_t)target > (uint32_t)PLSR_STATE_ERROR) { return PLSR_RESULT_INVALID_ARGUMENT; } axisObject = &PlsrAxes[axis]; if (PlsrTransitionIsAllowed(axisObject->state, target) == 0U) { axisObject->illegalTransitionCount++; axisObject->error = PLSR_ERROR_ILLEGAL_TRANSITION; axisObject->stopReason = PLSR_STOP_REASON_FAULT; axisObject->done = 0U; axisObject->state = PLSR_STATE_ERROR; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->immediateStopPending = 0U; axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; PlsrStopSegmentHardware(axis, axisObject); axisObject->backlashActive = 0U; axisObject->backlashBypassOnce = 0U; PlsrResourceRelease(&axisObject->lease); PlsrPublishAxis(axis); return PLSR_RESULT_INVALID_STATE; } axisObject->state = target; if (target == PLSR_STATE_COMPLETED) { axisObject->done = 1U; } if ((target == PLSR_STATE_COMPLETED) || (target == PLSR_STATE_STOPPED) || (target == PLSR_STATE_ERROR)) { axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->immediateStopPending = 0U; axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); PlsrResourceRelease(&axisObject->lease); /* 检查点写入前按全部轴的最终状态统一计算 lastBusy。 */ PlsrCheckpointHsd(); } PlsrPublishAxis(axis); return PLSR_RESULT_OK; } static uint8_t PlsrCommandPriority(PLSR_COMMAND_OPCODE opcode) { switch (opcode) { case PLSR_CMD_STOP_IMMEDIATE: return 0U; case PLSR_CMD_STOP_DECEL: return 1U; case PLSR_CMD_PAUSE: return 2U; case PLSR_CMD_RESUME: return 3U; default: return 4U; } } static PLSR_RESULT PlsrQueueCommand(const PLSR_COMMAND *command, const PLSR_START_REQUEST *start, const PLSR_CALL *call) { uint32_t interruptState; uint8_t freeSlot = PLSR_COMMAND_QUEUE_DEPTH; uint8_t index; interruptState = PlsrCoreEnterCritical(); if ((PlsrAxes[command->axis].hasLastCommand != 0U) && (PlsrAxes[command->axis].lastCommandSequence == command->sequence)) { PLSR_RESULT result = PlsrAxes[command->axis].lastCommandResult; PlsrCoreExitCritical(interruptState); return result; } for (index = 0U; index < PLSR_COMMAND_QUEUE_DEPTH; index++) { if (PlsrCommandQueue[index].occupied != 0U) { if ((PlsrCommandQueue[index].command.axis == command->axis) && (PlsrCommandQueue[index].command.sequence == command->sequence)) { PlsrCoreExitCritical(interruptState); return PLSR_RESULT_QUEUED; } } else if (freeSlot == PLSR_COMMAND_QUEUE_DEPTH) { freeSlot = index; } } if (freeSlot == PLSR_COMMAND_QUEUE_DEPTH) { PlsrCoreExitCritical(interruptState); return PLSR_RESULT_QUEUE_FULL; } PlsrCommandQueue[freeSlot].command = *command; if (start != NULL) { PlsrCommandQueue[freeSlot].start = *start; PlsrCommandQueue[freeSlot].hasStart = 1U; } else { (void)memset(&PlsrCommandQueue[freeSlot].start, 0, sizeof(PlsrCommandQueue[freeSlot].start)); PlsrCommandQueue[freeSlot].hasStart = 0U; } if (call != NULL) { PlsrCommandQueue[freeSlot].call = *call; PlsrCommandQueue[freeSlot].hasCall = 1U; } else { (void)memset(&PlsrCommandQueue[freeSlot].call, 0, sizeof(PlsrCommandQueue[freeSlot].call)); PlsrCommandQueue[freeSlot].hasCall = 0U; } PlsrCommandQueue[freeSlot].ticket = PlsrNextTicket++; PlsrCommandQueue[freeSlot].occupied = 1U; PlsrCoreExitCritical(interruptState); return PLSR_RESULT_QUEUED; } static uint8_t PlsrPopHighestPriorityCommand(PLSR_COMMAND_SLOT *slot) { uint32_t interruptState; uint32_t selectedTicket = 0UL; uint8_t selectedPriority = 0xFFU; uint8_t selected = PLSR_COMMAND_QUEUE_DEPTH; uint8_t index; uint8_t priority; interruptState = PlsrCoreEnterCritical(); for (index = 0U; index < PLSR_COMMAND_QUEUE_DEPTH; index++) { if (PlsrCommandQueue[index].occupied == 0U) { continue; } priority = PlsrCommandPriority(PlsrCommandQueue[index].command.opcode); if ((selected == PLSR_COMMAND_QUEUE_DEPTH) || (priority < selectedPriority) || ((priority == selectedPriority) && ((int32_t)(PlsrCommandQueue[index].ticket - selectedTicket) < 0))) { selected = index; selectedPriority = priority; selectedTicket = PlsrCommandQueue[index].ticket; } } if (selected == PLSR_COMMAND_QUEUE_DEPTH) { PlsrCoreExitCritical(interruptState); return 0U; } *slot = PlsrCommandQueue[selected]; PlsrCommandQueue[selected].occupied = 0U; PlsrCoreExitCritical(interruptState); return 1U; } #ifdef PLSR_HOST_TEST uint32_t PlsrTestGetProfileRefreshHz(uint8_t axis) { return (axis < PLSR_AXIS_COUNT) ? PlsrAxes[axis].profile.refreshHz : 0UL; } uint8_t PlsrTestGetProfileActive(uint8_t axis) { return (axis < PLSR_AXIS_COUNT) ? PlsrAxes[axis].profileActive : 0U; } uint8_t PlsrTestGetJobRefreshCode(uint8_t axis) { return (axis < PLSR_AXIS_COUNT) ? PlsrAxes[axis].job.s2.refreshCode : 0U; } uint32_t PlsrTestGetProfileFrequencyHz(uint8_t axis) { return (axis < PLSR_AXIS_COUNT) ? (uint32_t)(PlsrAxes[axis].profile.frequencyQ32 >> 32U) : 0UL; } PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request) { PLSR_COMMAND command; if (request == NULL) { return PLSR_RESULT_INVALID_ARGUMENT; } if (request->axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (PlsrInitialized == 0U) { return PLSR_RESULT_INVALID_STATE; } command.sequence = request->sequence; command.axis = request->axis; command.opcode = PLSR_CMD_START; command.argument = 0; return PlsrQueueCommand(&command, request, NULL); } #endif /* PLSR_HOST_TEST */ PLSR_RESULT PlsrPostCall(const PLSR_CALL *call) { PLSR_COMMAND command; if (call == NULL) { return PLSR_RESULT_INVALID_ARGUMENT; } if (call->dAxis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (PlsrInitialized == 0U) { return PLSR_RESULT_INVALID_STATE; } command.sequence = call->sequence; command.axis = call->dAxis; command.opcode = PLSR_CMD_START; command.argument = 0; return PlsrQueueCommand(&command, NULL, call); } PLSR_RESULT PlsrValidateCall(const PLSR_CALL *call, PLSR_PARSE_DETAIL *detail) { PLSR_PARSE_CONTEXT parseContext; uint32_t interruptState; if ((call == NULL) || (detail == NULL)) { return PLSR_RESULT_INVALID_ARGUMENT; } if (call->dAxis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (PlsrInitialized == 0U) { return PLSR_RESULT_INVALID_STATE; } interruptState = PlsrCoreEnterCritical(); parseContext.logicalPosition = PlsrAxes[call->dAxis].logicalPosition; parseContext.positionValid = PlsrAxes[call->dAxis].positionValid; PlsrCoreExitCritical(interruptState); return PlsrBuildJobSnapshot(call, &parseContext, &PlsrValidationScratch, detail); } PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command) { if (command == NULL) { return PLSR_RESULT_INVALID_ARGUMENT; } if (command->axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (((uint32_t)command->opcode > (uint32_t)PLSR_CMD_SELF_TEST) || (command->opcode == PLSR_CMD_START)) { return PLSR_RESULT_INVALID_ARGUMENT; } if (PlsrInitialized == 0U) { return PLSR_RESULT_INVALID_STATE; } return PlsrQueueCommand(command, NULL, NULL); } PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask) { uint32_t interruptState; if (axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if ((eventMask == 0UL) || ((eventMask & ~PLSR_EVENT_ALL_MASK) != 0UL)) { return PLSR_RESULT_INVALID_ARGUMENT; } interruptState = PlsrCoreEnterCritical(); PlsrAxes[axis].pendingEvents |= eventMask; PlsrCoreExitCritical(interruptState); return PLSR_RESULT_OK; } static uint32_t PlsrTakeEvents(uint8_t axis, uint32_t mask) { uint32_t interruptState; uint32_t events; interruptState = PlsrCoreEnterCritical(); events = PlsrAxes[axis].pendingEvents & mask; PlsrAxes[axis].pendingEvents &= ~events; PlsrCoreExitCritical(interruptState); return events; } static uint8_t PlsrAnyAxisBusy(void) { uint8_t axis; for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { if (PlsrStateIsBusy(PlsrAxes[axis].state) != 0U) { return 1U; } } return 0U; } static uint8_t PlsrAllAxesPositionValid(void) { uint8_t axis; for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { if ((PlsrAxes[axis].positionValid == 0U) || (PlsrAxes[axis].positionOverflow != 0U)) { return 0U; } } return 1U; } static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, const PLSR_START_REQUEST *start) { PLSR_RESOURCE_REQUEST resourceRequest; PLSR_RESULT result; uint8_t axisWasBusy = PlsrAnyAxisBusy(); if ((axisObject->state != PLSR_STATE_IDLE) && (axisObject->state != PLSR_STATE_COMPLETED) && (axisObject->state != PLSR_STATE_STOPPED)) { return PLSR_RESULT_INVALID_STATE; } resourceRequest.ownerAxis = start->axis; resourceRequest.outputMode = start->outputMode; resourceRequest.dAxis = start->axis; resourceRequest.directionPoint = start->directionPoint; result = PlsrResourceReserve(&resourceRequest, &axisObject->lease); if (result != PLSR_RESULT_OK) { axisObject->error = (result == PLSR_RESULT_RESOURCE_CONFLICT) ? PLSR_ERROR_RESOURCE_CONFLICT : PLSR_ERROR_INVALID_RESOURCE; return result; } axisObject->outputMode = start->outputMode; axisObject->directionPositive = (start->directionPositive != 0U) ? 1U : 0U; axisObject->error = PLSR_ERROR_NONE; axisObject->compatibleErrorCode = 0U; axisObject->compatibleErrorBlock = 0U; axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; axisObject->immediateStopPending = 0U; axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; axisObject->done = 0U; axisObject->jobValid = 0U; axisObject->taskPulses = 0; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 0U; axisObject->backlashActive = 0U; axisObject->backlashBypassOnce = 0U; axisObject->liveFrequencyRaw = 0; axisObject->liveTargetFrequencyHz = 0UL; axisObject->pauseStopFrequencyHz = 0UL; axisObject->liveFrequencyRejectCount = 0UL; axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK; axisObject->runtimeSpeedClamped = 0U; result = PlsrStateTransition(start->axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_START); if (result != PLSR_RESULT_OK) { PlsrResourceRelease(&axisObject->lease); } else { /* 全局空闲到运行的边沿立即落盘,保持原有掉电安全窗口;后续 * 并发轴已由同一个全局 lastBusy=1 检查点覆盖。 */ if (axisWasBusy == 0U) { PlsrCheckpointHsdImmediate(); } } return result; } static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, const PLSR_CALL *call) { PLSR_RESOURCE_REQUEST resourceRequest; PLSR_PARSE_CONTEXT parseContext; PLSR_RESULT result; uint8_t axisWasBusy = PlsrAnyAxisBusy(); if ((axisObject->state != PLSR_STATE_IDLE) && (axisObject->state != PLSR_STATE_COMPLETED) && (axisObject->state != PLSR_STATE_STOPPED)) { return PLSR_RESULT_INVALID_STATE; } if (axisObject->emergencyLatched != 0U) { return PLSR_RESULT_EMERGENCY_LATCHED; } axisObject->compatibleErrorCode = 0U; axisObject->compatibleErrorBlock = 0U; parseContext.logicalPosition = axisObject->logicalPosition; parseContext.positionValid = axisObject->positionValid; result = PlsrBuildJobSnapshot(call, &parseContext, &PlsrJobScratch, &axisObject->parseDetail); if (result != PLSR_RESULT_OK) { PlsrSetCompatibleParseError(call->dAxis, axisObject); return result; } result = PlsrCheckStartProtection(axisObject, &PlsrJobScratch); if (result != PLSR_RESULT_OK) { if (result == PLSR_RESULT_LIMIT_POSITIVE) { axisObject->error = PLSR_ERROR_LIMIT_POSITIVE; axisObject->compatibleErrorCode = 5U; axisObject->stopReason = PLSR_STOP_REASON_LIMIT_POSITIVE; } else if (result == PLSR_RESULT_LIMIT_NEGATIVE) { axisObject->error = PLSR_ERROR_LIMIT_NEGATIVE; axisObject->compatibleErrorCode = 6U; axisObject->stopReason = PLSR_STOP_REASON_LIMIT_NEGATIVE; } else if (result == PLSR_RESULT_EMERGENCY_LATCHED) { axisObject->error = PLSR_ERROR_EMERGENCY; axisObject->stopReason = PLSR_STOP_REASON_SOFTWARE_EMERGENCY; } else if (result == PLSR_RESULT_DATA_ACCESS) { axisObject->error = PLSR_ERROR_INTERNAL; axisObject->compatibleErrorCode = 26U; axisObject->stopReason = PLSR_STOP_REASON_FAULT; } return result; } resourceRequest.ownerAxis = call->dAxis; resourceRequest.outputMode = (PLSR_OUTPUT_MODE)PlsrJobScratch.outputMode; resourceRequest.dAxis = call->dAxis; resourceRequest.directionPoint = PlsrJobScratch.directionPoint; result = PlsrResourceReserve(&resourceRequest, &axisObject->lease); if (result != PLSR_RESULT_OK) { axisObject->error = (result == PLSR_RESULT_RESOURCE_CONFLICT) ? PLSR_ERROR_RESOURCE_CONFLICT : PLSR_ERROR_INVALID_RESOURCE; axisObject->parseDetail.result = result; axisObject->parseDetail.block = PLSR_PARSE_BLOCK_OUTPUT; axisObject->compatibleErrorCode = 26U; axisObject->compatibleErrorBlock = 0U; return result; } axisObject->job = PlsrJobScratch; axisObject->jobValid = 1U; if ((axisObject->equivalent.unitCode != axisObject->job.equivalent.unitCode) || (axisObject->equivalent.pulsesPerRevolution != axisObject->job.equivalent.pulsesPerRevolution) || (axisObject->equivalent.movementPerRevolution != axisObject->job.equivalent.movementPerRevolution)) { axisObject->equivalentCommandRemainder = 0; } axisObject->equivalent = axisObject->job.equivalent; axisObject->outputMode = (PLSR_OUTPUT_MODE)axisObject->job.outputMode; axisObject->directionPositive = axisObject->job.initialDirectionPositive; axisObject->error = PLSR_ERROR_NONE; axisObject->compatibleErrorCode = 0U; axisObject->compatibleErrorBlock = 0U; axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; axisObject->immediateStopPending = 0U; axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; axisObject->done = 0U; axisObject->taskPulses = 0; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 0U; axisObject->segmentEventPublished = 0U; axisObject->backlashActive = 0U; axisObject->backlashBypassOnce = 0U; axisObject->liveFrequencyRaw = 0; axisObject->liveTargetFrequencyHz = 0UL; axisObject->pauseStopFrequencyHz = 0UL; axisObject->liveFrequencyRejectCount = 0UL; axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK; axisObject->runtimeSpeedClamped = 0U; PlsrPathBegin(&axisObject->path, &axisObject->job, axisObject->logicalPosition); if (axisObject->path.jobEnded != 0U) { /* 起点就是零脉冲且跳转链已结束(任务无实际脉冲)。 */ axisObject->stopReason = PLSR_STOP_REASON_NORMAL_COMPLETE; (void)PlsrStateTransition(call->dAxis, PLSR_STATE_COMPLETED, PLSR_TRANSITION_JOB_COMPLETE); axisObject->jobValid = 0U; return PLSR_RESULT_OK; } result = PlsrStateTransition(call->dAxis, PLSR_STATE_ACCEL, PLSR_TRANSITION_START); if (result != PLSR_RESULT_OK) { axisObject->jobValid = 0U; PlsrResourceRelease(&axisObject->lease); } else { /* 先持久化全局 0->1 busy 边沿,再允许硬件输出启动;后续并发 * 轴由同一个 lastBusy=1 检查点覆盖。 */ if (axisWasBusy == 0U) { PlsrCheckpointHsdImmediate(); } /* 启动当前段硬件输出与速度曲线。 */ /* 零脉冲跳转链耗尽本轮预算时没有实际运动段,等待下一次 * PlsrPathTick 找到非零段后再启动硬件。 */ if (axisObject->path.jumpChainPending == 0U) { result = PlsrStartSegmentHardware(call->dAxis, axisObject); } if (result != PLSR_RESULT_OK) { if ((result == PLSR_RESULT_LIMIT_POSITIVE) || (result == PLSR_RESULT_LIMIT_NEGATIVE)) { axisObject->error = (result == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_ERROR_LIMIT_POSITIVE : PLSR_ERROR_LIMIT_NEGATIVE; axisObject->compatibleErrorCode = (result == PLSR_RESULT_LIMIT_POSITIVE) ? 5U : 6U; PlsrSetStopReason( axisObject, (result == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_STOP_REASON_LIMIT_POSITIVE : PLSR_STOP_REASON_LIMIT_NEGATIVE); (void)PlsrStateTransition(call->dAxis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); } else { 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; } } return result; } static PLSR_RESULT PlsrStopImmediate(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; if ((axisObject->state == PLSR_STATE_IDLE) || (axisObject->state == PLSR_STATE_COMPLETED) || (axisObject->state == PLSR_STATE_STOPPED)) { return PLSR_RESULT_OK; } if (axisObject->state == PLSR_STATE_ERROR) { return PLSR_RESULT_INVALID_STATE; } if (axisObject->immediateStopPending != 0U) { return PLSR_RESULT_OK; } PlsrSetStopReason(axisObject, PLSR_STOP_REASON_STOP_IMMEDIATE); axisObject->pendingTerminal = PLSR_STATE_STOPPED; PlsrPublishSegmentEvent(axis, axisObject, PLSR_STOP_REASON_STOP_IMMEDIATE); PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); return PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); } static PLSR_RESULT PlsrRequestControlledStop(uint8_t axis, PLSR_STATE terminal, PLSR_STOP_REASON reason) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; PLSR_RESULT result; uint32_t interruptState; PlsrSetStopReason(axisObject, reason); axisObject->pendingTerminal = terminal; if (terminal != PLSR_STATE_PAUSED) { PlsrPublishSegmentEvent(axis, axisObject, reason); PlsrPathTerminate(&axisObject->path); } if ((axisObject->state == PLSR_STATE_WAIT) || (axisObject->state == PLSR_STATE_PAUSED)) { PlsrStopSegmentHardware(axis, axisObject); return PlsrStateTransition(axis, terminal, PLSR_TRANSITION_DECEL_COMPLETE); } if (axisObject->profileActive == 0U) { return (axisObject->state == PLSR_STATE_DECEL) ? PLSR_RESULT_OK : PlsrStateTransition(axis, PLSR_STATE_DECEL, PLSR_TRANSITION_DECEL_REQUEST); } interruptState = PlsrCoreEnterCritical(); result = PlsrProfileRequestStop(&axisObject->profile); PlsrCoreExitCritical(interruptState); if (result != PLSR_RESULT_OK) { return result; } if (axisObject->state == PLSR_STATE_DECEL) { return PLSR_RESULT_OK; } return PlsrStateTransition(axis, PLSR_STATE_DECEL, PLSR_TRANSITION_DECEL_REQUEST); } static PLSR_RESULT PlsrStopDecel(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; if ((axisObject->state == PLSR_STATE_IDLE) || (axisObject->state == PLSR_STATE_COMPLETED) || (axisObject->state == PLSR_STATE_STOPPED)) { return PLSR_RESULT_OK; } if (axisObject->state == PLSR_STATE_ERROR) { return PLSR_RESULT_INVALID_STATE; } if (axisObject->stopReason >= PLSR_STOP_REASON_STOP_IMMEDIATE) { return PLSR_RESULT_OK; } return PlsrRequestControlledStop(axis, PLSR_STATE_STOPPED, PLSR_STOP_REASON_STOP_DECEL); } static PLSR_RESULT PlsrPause(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; uint32_t interruptState; if (axisObject->state == PLSR_STATE_PAUSED) { return PLSR_RESULT_OK; } if (axisObject->stopReason >= PLSR_STOP_REASON_STOP_DECEL) { return PLSR_RESULT_BUSY; } if ((axisObject->state != PLSR_STATE_ACCEL) && (axisObject->state != PLSR_STATE_RUN) && (axisObject->state != PLSR_STATE_DECEL) && (axisObject->state != PLSR_STATE_WAIT)) { return PLSR_RESULT_INVALID_STATE; } axisObject->pauseReturnState = axisObject->state; if (axisObject->profileActive != 0U) { interruptState = PlsrCoreEnterCritical(); /* RequestStop temporarily replaces stopFrequencyHz with zero. Keep * the segment-specific value (including backlash profiles) so RESUME * rebuilds the same trajectory rather than assuming the user S2 one. */ axisObject->pauseStopFrequencyHz = axisObject->profile.stopFrequencyHz; PlsrCoreExitCritical(interruptState); } return PlsrRequestControlledStop(axis, PLSR_STATE_PAUSED, PLSR_STOP_REASON_PAUSE); } static PLSR_RESULT PlsrResume(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; PLSR_RESULT result; PLSR_STATE targetState; uint64_t emittedPulses; if (axisObject->state != PLSR_STATE_PAUSED) { return PLSR_RESULT_INVALID_STATE; } if (axisObject->pauseReturnState == PLSR_STATE_WAIT) { axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; return PlsrStateTransition(axis, PLSR_STATE_WAIT, PLSR_TRANSITION_WAIT_COMPLETE); } if (axisObject->jobValid == 0U) { /* The snapshot-less start entry exists only for host state-machine * tests; preserve its historical transition-only resume semantics. */ axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; return PlsrStateTransition(axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_WAIT_COMPLETE); } emittedPulses = (uint64_t)PlsrHwGetEmittedPulses(axis); PlsrProfileSyncPulses(&axisObject->profile, emittedPulses); if (emittedPulses >= (uint64_t)axisObject->profile.totalPulses) { axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; result = PlsrStateTransition(axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_WAIT_COMPLETE); if (result == PLSR_RESULT_OK) { (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } return result; } result = PlsrProfileResume(&axisObject->profile, axisObject->profile.startFrequencyHz, axisObject->liveTargetFrequencyHz, axisObject->pauseStopFrequencyHz); if (result != PLSR_RESULT_OK) { return result; } result = PlsrHwResumePulse(axis); if (result != PLSR_RESULT_OK) { return result; } axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; axisObject->pauseReturnState = PLSR_STATE_UNINITIALIZED; PlsrSetProfileActive(axisObject, 1U); axisObject->profileWasAccel = (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; targetState = (axisObject->profileWasAccel != 0U) ? PLSR_STATE_ACCEL : PLSR_STATE_RUN; return PlsrStateTransition(axis, targetState, PLSR_TRANSITION_WAIT_COMPLETE); } static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) { PLSR_AXIS *axisObject = &PlsrAxes[slot->command.axis]; PLC_DEVICE_RESULT deviceResult; PLSR_RESULT result; switch (slot->command.opcode) { case PLSR_CMD_START: if (slot->hasCall != 0U) { result = PlsrStartCall(axisObject, &slot->call); } else { result = (slot->hasStart != 0U) ? PlsrStartAxis(axisObject, &slot->start) : PLSR_RESULT_INVALID_ARGUMENT; } break; case PLSR_CMD_STOP_DECEL: result = PlsrStopDecel(slot->command.axis); break; case PLSR_CMD_STOP_IMMEDIATE: result = PlsrStopImmediate(slot->command.axis); break; case PLSR_CMD_PAUSE: result = PlsrPause(slot->command.axis); break; case PLSR_CMD_RESUME: result = PlsrResume(slot->command.axis); break; case PLSR_CMD_SET_POSITION: if (PlsrStateIsBusy(axisObject->state) != 0U) { result = PLSR_RESULT_BUSY; } else { axisObject->logicalPosition = slot->command.argument; axisObject->positionValid = 1U; axisObject->positionOverflow = 0U; PlsrPublishPosition(slot->command.axis, axisObject); PlsrCheckpointHsd(); result = PLSR_RESULT_OK; } break; case PLSR_CMD_CLEAR_POSITION: if (PlsrStateIsBusy(axisObject->state) != 0U) { result = PLSR_RESULT_BUSY; } else { axisObject->logicalPosition = 0; axisObject->positionValid = 1U; axisObject->positionOverflow = 0U; PlsrPublishPosition(slot->command.axis, axisObject); PlsrCheckpointHsd(); result = PLSR_RESULT_OK; } break; case PLSR_CMD_CLEAR_TOTAL: if (PlsrStateIsBusy(axisObject->state) != 0U) { result = PLSR_RESULT_BUSY; } else { axisObject->totalPulses = 0; PlsrCheckpointHsd(); result = PLSR_RESULT_OK; } break; case PLSR_CMD_SAVE_CONFIG: if (PlsrAnyAxisBusy() != 0U) { result = PLSR_RESULT_BUSY; } else { deviceResult = PlcDeviceSaveSfd(); result = (deviceResult == PLC_DEVICE_OK) ? PLSR_RESULT_OK : PLSR_RESULT_PERSISTENCE_ERROR; } break; case PLSR_CMD_LOAD_CONFIG: if (PlsrAnyAxisBusy() != 0U) { result = PLSR_RESULT_BUSY; } else { deviceResult = PlcDeviceLoadSfd(); result = (deviceResult == PLC_DEVICE_OK) ? PLSR_RESULT_OK : PLSR_RESULT_PERSISTENCE_ERROR; } break; case PLSR_CMD_RESET_ERROR: if (PlsrStateIsBusy(axisObject->state) != 0U) { result = PLSR_RESULT_BUSY; } else if ((axisObject->state != PLSR_STATE_ERROR) && (axisObject->error == PLSR_ERROR_NONE) && (axisObject->emergencyLatched == 0U) && (axisObject->compatibleErrorCode == 0U)) { result = PLSR_RESULT_INVALID_STATE; } else { axisObject->error = PLSR_ERROR_NONE; axisObject->compatibleErrorCode = 0U; axisObject->compatibleErrorBlock = 0U; axisObject->emergencyLatched = 0U; axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->done = 0U; result = (axisObject->state == PLSR_STATE_IDLE) ? PLSR_RESULT_OK : PlsrStateTransition( slot->command.axis, PLSR_STATE_IDLE, PLSR_TRANSITION_RESET_ERROR); } break; case PLSR_CMD_SELF_TEST: result = ((PlsrAnyAxisBusy() == 0U) && (PlsrResourceCheckInvariant() != 0U)) ? PLSR_RESULT_OK : PLSR_RESULT_BUSY; break; default: result = PLSR_RESULT_INVALID_ARGUMENT; break; } axisObject->lastCommandSequence = slot->command.sequence; axisObject->lastCommandResult = result; axisObject->hasLastCommand = 1U; PlsrPublishAxis(slot->command.axis); return result; } static void PlsrProcessCriticalEvents(uint8_t axis, uint32_t events) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; if ((events & PLSR_EVENT_SOFTWARE_EMERGENCY) != 0UL) { axisObject->emergencyLatched = 1U; axisObject->error = PLSR_ERROR_EMERGENCY; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_SOFTWARE_EMERGENCY); axisObject->done = 0U; if (PlsrStateIsBusy(axisObject->state) != 0U) { PlsrPublishSegmentEvent( axis, axisObject, PLSR_STOP_REASON_SOFTWARE_EMERGENCY); PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); (void)PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); } else { PlsrPublishAxis(axis); } return; } if ((events & PLSR_EVENT_LIMIT_POSITIVE) != 0UL) { axisObject->positiveLimitActive = 1U; if ((axisObject->directionPositive != 0U) && (PlsrStateIsBusy(axisObject->state) != 0U)) { axisObject->error = PLSR_ERROR_LIMIT_POSITIVE; axisObject->compatibleErrorCode = 5U; axisObject->compatibleErrorBlock = 0U; (void)PlsrRequestControlledStop( axis, PLSR_STATE_STOPPED, PLSR_STOP_REASON_LIMIT_POSITIVE); return; } } if ((events & PLSR_EVENT_LIMIT_NEGATIVE) != 0UL) { axisObject->negativeLimitActive = 1U; if ((axisObject->directionPositive == 0U) && (PlsrStateIsBusy(axisObject->state) != 0U)) { axisObject->error = PLSR_ERROR_LIMIT_NEGATIVE; axisObject->compatibleErrorCode = 6U; axisObject->compatibleErrorBlock = 0U; (void)PlsrRequestControlledStop( axis, PLSR_STATE_STOPPED, PLSR_STOP_REASON_LIMIT_NEGATIVE); return; } } if ((events & (PLSR_EVENT_TIMER_FAULT | PLSR_EVENT_COUNTER_FAULT)) != 0UL) { axisObject->error = ((events & PLSR_EVENT_TIMER_FAULT) != 0UL) ? PLSR_ERROR_TIMER_FAULT : PLSR_ERROR_COUNTER_FAULT; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); } } static void PlsrMonitorAxisProtection(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; PLSR_RESULT result; if ((axisObject->jobValid == 0U) || (PlsrStateIsBusy(axisObject->state) == 0U)) { return; } result = PlsrUpdateLimitState(axisObject, &axisObject->job, 1U); if (result != PLSR_RESULT_OK) { axisObject->error = PLSR_ERROR_INTERNAL; axisObject->compatibleErrorCode = 26U; axisObject->compatibleErrorBlock = 0U; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; PlsrPublishSegmentEvent(axis, axisObject, PLSR_STOP_REASON_FAULT); PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); return; } if ((axisObject->directionPositive != 0U) && (axisObject->positiveLimitActive != 0U) && !((axisObject->pendingTerminal == PLSR_STATE_STOPPED) && (axisObject->stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE))) { (void)PlsrPostEvent(axis, PLSR_EVENT_LIMIT_POSITIVE); } else if ((axisObject->directionPositive == 0U) && (axisObject->negativeLimitActive != 0U) && !((axisObject->pendingTerminal == PLSR_STATE_STOPPED) && (axisObject->stopReason == PLSR_STOP_REASON_LIMIT_NEGATIVE))) { (void)PlsrPostEvent(axis, PLSR_EVENT_LIMIT_NEGATIVE); } } /* 启动当前段的硬件输出与速度曲线(P3a:单轴 PULSE/DIR)。 * 由段进入 ACCEL 时调用(任务启动 + 段间推进)。 */ 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; int64_t signedPulses; int64_t targetPosition; int64_t nextEquivalentRemainder = axisObject->equivalentCommandRemainder; int64_t outputPulses; int32_t liveFrequencyRaw; uint32_t gapSlopeHzPerMs; uint16_t backlashPulses = 0U; uint8_t runBacklash = 0U; uint8_t positive; if (job->positioningMode == 0U) { result = PlsrPositionUnitsToPulses( &axisObject->equivalent, segment->pulseOrTarget, axisObject->equivalentCommandRemainder, &signedPulses, &nextEquivalentRemainder); if (result != PLSR_RESULT_OK) { return result; } if (signedPulses == INT64_MIN) { return PLSR_RESULT_POSITION_OVERFLOW; } pulses = (signedPulses < 0) ? -signedPulses : signedPulses; positive = (signedPulses >= 0) ? 1U : 0U; } else { /* 绝对模式使用实际硬件脉冲闭环更新后的逻辑位置计算位移。 */ int64_t delta; result = PlsrPositionAbsoluteUnitsToPulses( &axisObject->equivalent, segment->pulseOrTarget, &targetPosition); if (result != PLSR_RESULT_OK) { return result; } if (((axisObject->logicalPosition > 0) && (targetPosition < INT64_MIN + axisObject->logicalPosition)) || ((axisObject->logicalPosition < 0) && (targetPosition > INT64_MAX + axisObject->logicalPosition))) { return PLSR_RESULT_POSITION_OVERFLOW; } delta = targetPosition - axisObject->logicalPosition; if (delta == INT64_MIN) { return PLSR_RESULT_POSITION_OVERFLOW; } pulses = (delta < 0) ? -delta : delta; positive = (delta >= 0) ? 1U : 0U; } result = PlsrUpdateLimitState(axisObject, job, 0U); if (result != PLSR_RESULT_OK) { return result; } if ((positive != 0U) && (axisObject->positiveLimitActive != 0U)) { return PLSR_RESULT_LIMIT_POSITIVE; } if ((positive == 0U) && (axisObject->negativeLimitActive != 0U)) { return PLSR_RESULT_LIMIT_NEGATIVE; } axisObject->segmentEventPublished = 0U; if (pulses == 0) { /* 当量小于一个物理脉冲时保存余数并按零脉冲段推进;不启动PWM。 */ axisObject->equivalentCommandRemainder = nextEquivalentRemainder; axisObject->directionPositive = positive; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 0U; PlsrPublishAxis(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); return PLSR_RESULT_OK; } /* A future segment edited before it becomes current must still use the * COMMIT snapshot. Capture the source value only as a change-detection * baseline; a later edit, made while this segment is current, is live. */ result = PlsrReadLiveFrequencyRaw(job, axisObject->path.currentSegment, &liveFrequencyRaw); if (result == PLSR_RESULT_OK) { axisObject->liveFrequencyRaw = liveFrequencyRaw; axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK; } else { axisObject->lastLiveFrequencyResult = result; if (axisObject->liveFrequencyRejectCount != UINT32_MAX) { axisObject->liveFrequencyRejectCount++; } } axisObject->liveTargetFrequencyHz = segment->targetFrequency; if (axisObject->backlashBypassOnce != 0U) { /* The internal block has just completed; start the user segment. */ axisObject->backlashBypassOnce = 0U; } else if ((axisObject->lastUserDirectionValid != 0U) && (axisObject->lastUserDirectionPositive != positive)) { backlashPulses = (positive != 0U) ? job->positiveBacklashPulses : job->negativeBacklashPulses; runBacklash = (backlashPulses != 0U) ? 1U : 0U; } (void)memset(&profileRequest, 0, sizeof(profileRequest)); profileRequest.targetFrequencyHz = segment->targetFrequency; profileRequest.maxFrequencyHz = job->s2.maximumSpeed; profileRequest.curveMode = job->s2.curveMode; outputPulses = pulses; if (runBacklash != 0U) { outputPulses = backlashPulses; profileRequest.startFrequencyHz = (job->s2.gapAccelerationMs == 0U) ? segment->targetFrequency : 0UL; profileRequest.stopFrequencyHz = 0UL; gapSlopeHzPerMs = (job->s2.gapAccelerationMs != 0U) ? segment->targetFrequency / job->s2.gapAccelerationMs : 0UL; if ((job->s2.gapAccelerationMs != 0U) && (gapSlopeHzPerMs == 0UL)) { gapSlopeHzPerMs = 1UL; } profileRequest.accelSlopeHzPerMs = gapSlopeHzPerMs; profileRequest.decelSlopeHzPerMs = gapSlopeHzPerMs; } else { profileRequest.startFrequencyHz = job->s2.startSpeed; profileRequest.stopFrequencyHz = job->s2.stopSpeed; profileRequest.accelSlopeHzPerMs = (job->s2.accelerationMs != 0U) ? job->s2.defaultSpeed / job->s2.accelerationMs : 0UL; profileRequest.decelSlopeHzPerMs = (job->s2.decelerationMs != 0U) ? job->s2.defaultSpeed / job->s2.decelerationMs : 0UL; } result = PlsrProfileStart(&axisObject->profile, &profileRequest, outputPulses, (job->s2.refreshCode == 2U) ? 10000U : 1000U); if (result != PLSR_RESULT_OK) { return result; } params.frequencyHz = profileRequest.startFrequencyHz; params.targetPulses = outputPulses; params.outputMode = (PLSR_OUTPUT_MODE)job->outputMode; params.directionPoint = job->directionPoint; params.directionPositive = positive; params.directionNegativeLogic = job->directionNegativeLogic; params.directionDelayMs = job->s2.directionDelayMs; result = PlsrHwStartPulse(axis, ¶ms); if (result != PLSR_RESULT_OK) { PlsrSetProfileActive(axisObject, 0U); axisObject->profileWasAccel = 0U; return result; } axisObject->directionPositive = positive; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 1U; axisObject->backlashActive = runBacklash; if (runBacklash == 0U) { axisObject->equivalentCommandRemainder = nextEquivalentRemainder; axisObject->lastUserDirectionValid = 1U; axisObject->lastUserDirectionPositive = positive; } PlsrSetProfileActive(axisObject, 1U); axisObject->profileWasAccel = (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; PlsrPublishAxis(axis); if (axisObject->profileWasAccel == 0U) { /* start==target or zero acceleration enters CRUISE directly. The * axis state must not remain stuck in ACCEL for the whole segment. */ (void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE); } return PLSR_RESULT_OK; } /* 停止当前段的硬件输出与速度曲线。 */ static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) { PlsrAccountHardwarePulses(axis, axisObject); PlsrSetProfileActive(axisObject, 0U); axisObject->profileWasAccel = 0U; (void)PlsrHwStopPulse(axis); PlsrAccountHardwarePulses(axis, axisObject); axisObject->segmentAccountingActive = 0U; PlsrPublishRuntime(axis); } /* 应用路径执行器的动作:段间推进、进入等待、结束、让出、错误。 */ static void PlsrApplyPathAction(uint8_t axis, PLSR_PATH_ACTION action) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; 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, PLSR_STATE_ACCEL, PLSR_TRANSITION_WAIT_COMPLETE); } else if ((axisObject->state == PLSR_STATE_ACCEL) || (axisObject->state == PLSR_STATE_RUN) || (axisObject->state == PLSR_STATE_DECEL)) { (void)PlsrStateTransition(axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_START); } /* 进入新段:重新启动硬件输出与速度曲线。 */ if (axisObject->state == PLSR_STATE_ACCEL) { PLSR_RESULT startResult = PlsrStartSegmentHardware(axis, axisObject); if ((startResult == PLSR_RESULT_LIMIT_POSITIVE) || (startResult == PLSR_RESULT_LIMIT_NEGATIVE)) { axisObject->error = (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_ERROR_LIMIT_POSITIVE : PLSR_ERROR_LIMIT_NEGATIVE; axisObject->compatibleErrorCode = (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? 5U : 6U; PlsrSetStopReason( axisObject, (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_STOP_REASON_LIMIT_POSITIVE : PLSR_STOP_REASON_LIMIT_NEGATIVE); (void)PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); } else if (startResult != PLSR_RESULT_OK) { axisObject->error = PLSR_ERROR_TIMER_FAULT; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); } } break; case PLSR_PATH_ACTION_ENTER_WAIT: if ((axisObject->state == PLSR_STATE_ACCEL) || (axisObject->state == PLSR_STATE_RUN)) { (void)PlsrStateTransition(axis, PLSR_STATE_WAIT, PLSR_TRANSITION_WAIT_BEGIN); } break; case PLSR_PATH_ACTION_JOB_COMPLETE: if (PlsrStateIsBusy(axisObject->state) != 0U) { axisObject->stopReason = PLSR_STOP_REASON_NORMAL_COMPLETE; (void)PlsrStateTransition(axis, PLSR_STATE_COMPLETED, PLSR_TRANSITION_JOB_COMPLETE); } break; case PLSR_PATH_ACTION_YIELD: /* 预算耗尽:本轮不再推进,下个 tick 由 PlsrPathTick 恢复。 */ break; case PLSR_PATH_ACTION_ERROR: axisObject->error = PLSR_ERROR_INTERNAL; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); break; default: break; } } static void PlsrProcessNormalEvents(uint8_t axis, uint32_t events) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; if (((events & PLSR_EVENT_STOP_IMMEDIATE_DONE) != 0UL) && (axisObject->immediateStopPending != 0U)) { (void)PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); return; } if (((events & PLSR_EVENT_ACCEL_COMPLETE) != 0UL) && (axisObject->state == PLSR_STATE_ACCEL) && (axisObject->immediateStopPending == 0U)) { (void)PlsrStateTransition(axis, PLSR_STATE_RUN, PLSR_TRANSITION_ACCEL_COMPLETE); } if (((events & PLSR_EVENT_DECEL_COMPLETE) != 0UL) && (axisObject->state == PLSR_STATE_DECEL)) { if (axisObject->pendingTerminal == PLSR_STATE_PAUSED) { (void)PlsrStateTransition(axis, PLSR_STATE_PAUSED, PLSR_TRANSITION_DECEL_COMPLETE); } else if (axisObject->pendingTerminal == PLSR_STATE_STOPPED) { (void)PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_DECEL_COMPLETE); } else { (void)PlsrStateTransition(axis, PLSR_STATE_RUN, PLSR_TRANSITION_DECEL_COMPLETE); } } if (((events & PLSR_EVENT_WAIT_BEGIN) != 0UL) && ((axisObject->state == PLSR_STATE_ACCEL) || (axisObject->state == PLSR_STATE_RUN))) { (void)PlsrStateTransition(axis, PLSR_STATE_WAIT, PLSR_TRANSITION_WAIT_BEGIN); } if (((events & PLSR_EVENT_WAIT_COMPLETE) != 0UL) && (axisObject->state == PLSR_STATE_WAIT)) { (void)PlsrStateTransition(axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_WAIT_COMPLETE); } if (((events & PLSR_EVENT_SEGMENT_COMPLETE) != 0UL) && (axisObject->pendingBoundaryAction != PLSR_PATH_ACTION_NONE)) { PLSR_PATH_ACTION action = axisObject->pendingBoundaryAction; axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; /* HAL has stopped at the physical falling/update boundary. Only now * may the already-evaluated ACT/EXT path action advance the job. */ PlsrApplyPathAction(axis, action); return; } if (((events & PLSR_EVENT_SEGMENT_COMPLETE) != 0UL) && ((axisObject->state == PLSR_STATE_ACCEL) || (axisObject->state == PLSR_STATE_RUN) || (axisObject->state == PLSR_STATE_DECEL)) && (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED) && (axisObject->immediateStopPending == 0U)) { PLSR_PATH_ACTION action; if (axisObject->backlashActive != 0U) { PLSR_RESULT startResult; /* Internal compensation completion is not a user segment * completion and therefore must not publish I6000..I6399 or * advance the path. */ PlsrStopSegmentHardware(axis, axisObject); axisObject->backlashActive = 0U; axisObject->backlashBypassOnce = 1U; (void)PlsrStateTransition(axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_START); startResult = PlsrStartSegmentHardware(axis, axisObject); if ((startResult == PLSR_RESULT_LIMIT_POSITIVE) || (startResult == PLSR_RESULT_LIMIT_NEGATIVE)) { axisObject->error = (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_ERROR_LIMIT_POSITIVE : PLSR_ERROR_LIMIT_NEGATIVE; axisObject->compatibleErrorCode = (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? 5U : 6U; PlsrSetStopReason( axisObject, (startResult == PLSR_RESULT_LIMIT_POSITIVE) ? PLSR_STOP_REASON_LIMIT_POSITIVE : PLSR_STOP_REASON_LIMIT_NEGATIVE); (void)PlsrStateTransition(axis, PLSR_STATE_STOPPED, PLSR_TRANSITION_STOP); } else if (startResult != PLSR_RESULT_OK) { axisObject->error = PLSR_ERROR_TIMER_FAULT; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); } return; } PlsrPublishSegmentEvent(axis, axisObject, PLSR_STOP_REASON_NORMAL_COMPLETE); action = PlsrPathOnSegmentDone(&axisObject->path, &axisObject->job, axisObject->logicalPosition); PlsrApplyPathAction(axis, action); } if (((events & PLSR_EVENT_JOB_COMPLETE) != 0UL) && (PlsrStateIsBusy(axisObject->state) != 0U) && (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED) && (axisObject->immediateStopPending == 0U)) { axisObject->stopReason = PLSR_STOP_REASON_NORMAL_COMPLETE; (void)PlsrStateTransition(axis, PLSR_STATE_COMPLETED, PLSR_TRANSITION_JOB_COMPLETE); } } PLSR_RESULT PlsrInit(void) { int32_t restoredPosition; uint8_t restoredPositionValid; uint8_t restoredLastBusy; uint8_t axis; (void)memset(PlsrAxes, 0, sizeof(PlsrAxes)); (void)memset(PlsrCommandQueue, 0, sizeof(PlsrCommandQueue)); PlsrNextTicket = 0UL; PlsrMaxProcessCycles = 0UL; PlsrMaxProcessResponseCycles = 0UL; (void)memset((void *)PlsrMaxProcessStageCycles, 0, sizeof(PlsrMaxProcessStageCycles)); PlsrDeferHsdCheckpoint = 0U; PlsrHsdCheckpointPending = 0U; PlsrResourceInit(); (void)PlsrHwInit(); PlsrControlTickHook = NULL; PlsrInitialized = 1U; restoredPositionValid = PlcDeviceGetRestoredHsdPositionValid(); restoredLastBusy = PlcDeviceGetRestoredHsdLastBusy(); for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { PlsrAxes[axis].state = PLSR_STATE_UNINITIALIZED; PlsrAxes[axis].pendingTerminal = PLSR_STATE_UNINITIALIZED; PlsrAxes[axis].lease.directionPoint = PLSR_DIRECTION_POINT_NONE; PlsrLoadAxisEquivalentConfig(axis, &PlsrAxes[axis].equivalent); if (PlcDeviceReadHsdDword( (uint16_t)(axis * PLSR_HSD_RUNTIME_AXIS_COUNT), &restoredPosition) == PLC_DEVICE_OK) { PlsrAxes[axis].logicalPosition = restoredPosition; } /* 只有上次正常停机且保存了位置有效标志,才允许绝对定位; * 运动中掉电(lastBusy=1)时位置不可信。 */ if ((restoredPositionValid != 0U) && (restoredLastBusy == 0U)) { PlsrAxes[axis].positionValid = 1U; } if (PlsrStateTransition(axis, PLSR_STATE_IDLE, PLSR_TRANSITION_INITIALIZED) != PLSR_RESULT_OK) { return PLSR_RESULT_INTERNAL_ERROR; } } return PLSR_RESULT_OK; } static void PlsrStepProfileAxis(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; PLSR_RESULT liveResult; int32_t liveRaw; uint32_t frequencyHz; uint32_t liveFrequencyHz; uint32_t outputFrequencyHz; uint64_t hardwarePulses; uint64_t remainingPulses; uint8_t profileDone; uint8_t liveClamped; uint8_t wasAccel; if ((axisObject->profileActive == 0U) || (PlsrHwGetState(axis) == PLSR_HW_STATE_DIR_SETTLING)) { return; } /* Only the current segment frequency remains live after COMMIT. Poll the * raw dword every selected control tick; conversion/divider validation is * performed only when the raw value actually changes. */ if ((axisObject->backlashActive == 0U) && (axisObject->jobValid != 0U)) { liveResult = PlsrReadLiveFrequencyRaw( &axisObject->job, axisObject->path.currentSegment, &liveRaw); if (liveResult != PLSR_RESULT_OK) { if (axisObject->lastLiveFrequencyResult != liveResult) { if (axisObject->liveFrequencyRejectCount != UINT32_MAX) { axisObject->liveFrequencyRejectCount++; } } axisObject->lastLiveFrequencyResult = liveResult; } else if (liveRaw != axisObject->liveFrequencyRaw) { axisObject->liveFrequencyRaw = liveRaw; liveResult = PlsrResolveLiveFrequency( &axisObject->job, axisObject->path.currentSegment, &liveFrequencyHz, &liveClamped); if (liveResult == PLSR_RESULT_OK) { liveResult = PlsrProfileRetarget(&axisObject->profile, liveFrequencyHz); } if (liveResult == PLSR_RESULT_OK) { axisObject->liveTargetFrequencyHz = liveFrequencyHz; axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK; if (liveClamped != 0U) { axisObject->runtimeSpeedClamped = 1U; } } else { axisObject->lastLiveFrequencyResult = liveResult; if (axisObject->liveFrequencyRejectCount != UINT32_MAX) { axisObject->liveFrequencyRejectCount++; } } } else { axisObject->lastLiveFrequencyResult = PLSR_RESULT_OK; } } hardwarePulses = (uint64_t)PlsrHwGetEmittedPulses(axis); PlsrProfileSyncPulses(&axisObject->profile, hardwarePulses); wasAccel = axisObject->profileWasAccel; (void)PlsrProfileStep(&axisObject->profile, &frequencyHz, &profileDone); outputFrequencyHz = frequencyHz; if ((PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING) && (axisObject->profile.phase != PLSR_PROFILE_PHASE_ACCEL) && ((axisObject->profile.phase != PLSR_PROFILE_PHASE_DECEL) || (axisObject->profile.decelTargetHz == axisObject->profile.stopFrequencyHz)) && (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED) && ((uint64_t)axisObject->profile.totalPulses > hardwarePulses + 1UL)) { remainingPulses = (uint64_t)axisObject->profile.totalPulses - hardwarePulses - 1UL; outputFrequencyHz = PlsrProfileGetBrakingOutputFrequency(&axisObject->profile, remainingPulses); } if ((PlsrHwGetState(axis) == PLSR_HW_STATE_PWM_PENDING) && (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL)) { outputFrequencyHz = PlsrProfileGetInitialOutputFrequency(&axisObject->profile); } if ((profileDone == 0U) || (outputFrequencyHz != 0UL)) { (void)PlsrHwSetFrequency(axis, outputFrequencyHz); } if ((profileDone != 0U) && (axisObject->pendingTerminal != PLSR_STATE_UNINITIALIZED)) { (void)PlsrHwSetFrequency(axis, 0UL); PlsrSetProfileActive(axisObject, 0U); (void)PlsrPostEvent(axis, PLSR_EVENT_DECEL_COMPLETE); } axisObject->profileWasAccel = (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; if ((wasAccel != 0U) && (axisObject->profileWasAccel == 0U) && (axisObject->state == PLSR_STATE_ACCEL)) { (void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE); } } void PlsrControlTick100us(void) { void (*hook)(void); uint8_t axis; if (PlsrInitialized == 0U) { return; } hook = PlsrControlTickHook; if (hook != NULL) { hook(); } for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { if (PlsrAxes[axis].job.s2.refreshCode == 2U) { PlsrStepProfileAxis(axis); } } } void PlsrProcess(void) { PLSR_COMMAND_SLOT slot; uint32_t events; #ifndef PLSR_HOST_TEST uint32_t started; uint64_t startedIsrCycles; uint32_t stageStarted; uint64_t stageStartedIsrCycles; #endif uint8_t processedCommands = 0U; uint8_t criticalAxes = 0U; uint8_t axis; if (PlsrInitialized == 0U) { return; } #ifndef PLSR_HOST_TEST PlsrHwGetCycleSnapshot(&started, &startedIsrCycles); stageStarted = started; stageStartedIsrCycles = startedIsrCycles; #endif PlsrDeferHsdCheckpoint = 1U; /* 先合并 ISR 已完成的实际脉冲,确保段完成、STOP或新命令不会在 * HAL 计数清零前丢失最后一批位置增量。 */ for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { PlsrAccountHardwarePulses(axis, &PlsrAxes[axis]); PlsrMonitorAxisProtection(axis); } #ifndef PLSR_HOST_TEST { uint32_t finished; uint64_t finishedIsrCycles; PlsrHwGetCycleSnapshot(&finished, &finishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_ACCOUNT_PROTECTION, stageStarted, stageStartedIsrCycles, finished, finishedIsrCycles); stageStarted = finished; stageStartedIsrCycles = finishedIsrCycles; } #endif for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { events = PlsrTakeEvents(axis, PLSR_EVENT_CRITICAL_MASK); if (events != 0UL) { criticalAxes |= (uint8_t)(1U << axis); PlsrProcessCriticalEvents(axis, events); } } #ifndef PLSR_HOST_TEST { uint32_t finished; uint64_t finishedIsrCycles; PlsrHwGetCycleSnapshot(&finished, &finishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_CRITICAL_EVENTS, stageStarted, stageStartedIsrCycles, finished, finishedIsrCycles); stageStarted = finished; stageStartedIsrCycles = finishedIsrCycles; } #endif /* Apply related multi-axis DIR changes after all commands and segment * events, keeping cross-port GPIO writes in one short commit window. */ PlsrHwBeginDirectionBatch(); while ((processedCommands < PLSR_COMMAND_QUEUE_DEPTH) && (PlsrPopHighestPriorityCommand(&slot) != 0U)) { if ((slot.command.opcode == PLSR_CMD_RESET_ERROR) && ((criticalAxes & (uint8_t)(1U << slot.command.axis)) != 0U)) { PLSR_AXIS *axisObject = &PlsrAxes[slot.command.axis]; axisObject->lastCommandSequence = slot.command.sequence; axisObject->lastCommandResult = PLSR_RESULT_BUSY; axisObject->hasLastCommand = 1U; PlsrPublishAxis(slot.command.axis); } else { (void)PlsrExecuteCommand(&slot); } processedCommands++; } #ifndef PLSR_HOST_TEST { uint32_t finished; uint64_t finishedIsrCycles; PlsrHwGetCycleSnapshot(&finished, &finishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_COMMANDS, stageStarted, stageStartedIsrCycles, finished, finishedIsrCycles); stageStarted = finished; stageStartedIsrCycles = finishedIsrCycles; } #endif for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { events = PlsrTakeEvents(axis, PLSR_EVENT_ALL_MASK & ~PLSR_EVENT_CRITICAL_MASK); if (events != 0UL) { PlsrProcessNormalEvents(axis, events); } } PlsrHwEndDirectionBatch(); #ifndef PLSR_HOST_TEST { uint32_t finished; uint64_t finishedIsrCycles; PlsrHwGetCycleSnapshot(&finished, &finishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_NORMAL_EVENTS, stageStarted, stageStartedIsrCycles, finished, finishedIsrCycles); stageStarted = finished; stageStartedIsrCycles = finishedIsrCycles; } #endif /* 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; PlsrHwTick(axis); /* Pulses were merged at the beginning of this pass. Merging again * here republishes HSD/SD runtime data for the few pulses emitted * while PlsrProcess itself was running and nearly doubles the * four-axis cost. Those pulses are safely merged at the beginning * of the next pass or by the terminal event path. */ /* 首次 AB 内部预热周期不属于用户运动,速度曲线也必须冻结; * 否则低速起步时会在隐藏周期内提前爬升十余个刷新步。 */ if (PlsrHwIsAbStartupPriming(axis) != 0U) { continue; } if (PlsrStateIsBusy(axisObject->state) == 0U) { continue; } if (axisObject->pendingBoundaryAction != PLSR_PATH_ACTION_NONE) { /* ACT/EXT has already advanced the immutable path context, but * hardware still owns the final in-flight P/D period. */ continue; } action = (axisObject->backlashActive != 0U) ? PLSR_PATH_ACTION_NONE : PlsrPathTick(&axisObject->path, &axisObject->job, axisObject->logicalPosition); if (action != PLSR_PATH_ACTION_NONE) { if (((action == PLSR_PATH_ACTION_NEXT_SEGMENT) || (action == PLSR_PATH_ACTION_JOB_COMPLETE)) && (axisObject->outputMode == PLSR_OUTPUT_PULSE_DIR) && ((axisObject->state == PLSR_STATE_ACCEL) || (axisObject->state == PLSR_STATE_RUN) || (axisObject->state == PLSR_STATE_DECEL)) && (PlsrHwIsPulseActive(axis) != 0U)) { PLSR_RESULT stopResult; /* A running ACT/EXT exit is asynchronous. Freeze profile * writes and let HAL stop at the next physical falling edge; * the completion event applies this saved action. */ PlsrSetProfileActive(axisObject, 0U); axisObject->profileWasAccel = 0U; axisObject->pendingBoundaryAction = action; stopResult = PlsrHwStopPulseAtBoundary(axis); if (stopResult != PLSR_RESULT_OK) { axisObject->pendingBoundaryAction = PLSR_PATH_ACTION_NONE; axisObject->error = PLSR_ERROR_TIMER_FAULT; PlsrSetStopReason(axisObject, PLSR_STOP_REASON_FAULT); axisObject->done = 0U; (void)PlsrStateTransition(axis, PLSR_STATE_ERROR, PLSR_TRANSITION_FAULT); } } else { PlsrApplyPathAction(axis, action); } } if (axisObject->job.s2.refreshCode != 2U) { PlsrStepProfileAxis(axis); } } #ifndef PLSR_HOST_TEST { uint32_t tickFinished; uint32_t finished; uint32_t responseCycles; uint32_t processCycles; uint64_t tickFinishedIsrCycles; uint64_t finishedIsrCycles; uint64_t preemptedCycles; PlsrHwGetCycleSnapshot(&tickFinished, &tickFinishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_TICK_PATH_PROFILE, stageStarted, stageStartedIsrCycles, tickFinished, tickFinishedIsrCycles); PlsrDeferHsdCheckpoint = 0U; PlsrFlushHsdCheckpoint(); PlsrHwGetCycleSnapshot(&finished, &finishedIsrCycles); PlsrUpdateProcessStageMax(PLSR_PROCESS_STAGE_HSD_CHECKPOINT, tickFinished, tickFinishedIsrCycles, finished, finishedIsrCycles); responseCycles = finished - started; preemptedCycles = finishedIsrCycles - startedIsrCycles; processCycles = (preemptedCycles < (uint64_t)responseCycles) ? responseCycles - (uint32_t)preemptedCycles : 0UL; if (processCycles > PlsrMaxProcessCycles) { PlsrMaxProcessCycles = processCycles; } if (responseCycles > PlsrMaxProcessResponseCycles) { PlsrMaxProcessResponseCycles = responseCycles; } } #else PlsrDeferHsdCheckpoint = 0U; PlsrFlushHsdCheckpoint(); #endif } uint32_t PlsrGetMaxProcessCycles(void) { return PlsrMaxProcessCycles; } uint32_t PlsrGetMaxProcessResponseCycles(void) { return PlsrMaxProcessResponseCycles; } uint32_t PlsrGetMaxProcessStageCycles(uint8_t stage) { if (stage >= PLSR_PROCESS_STAGE_COUNT) { return 0UL; } return PlsrMaxProcessStageCycles[stage]; } void PlsrTask(void *argument) { (void)argument; #ifdef PLSR_HOST_TEST PlsrProcess(); #else while (1) { PlsrProcess(); OSTimeDly(1U); } #endif } PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status) { PLSR_AXIS *axisObject; uint32_t interruptState; if (axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (status == NULL) { return PLSR_RESULT_INVALID_ARGUMENT; } interruptState = PlsrCoreEnterCritical(); axisObject = &PlsrAxes[axis]; status->state = axisObject->state; status->outputMode = axisObject->outputMode; status->error = axisObject->error; status->stopReason = axisObject->stopReason; status->lastCommandResult = axisObject->lastCommandResult; status->lastCommandSequence = axisObject->lastCommandSequence; status->illegalTransitionCount = axisObject->illegalTransitionCount; status->pendingEvents = axisObject->pendingEvents; status->logicalPosition = axisObject->logicalPosition; status->taskPulses = axisObject->taskPulses; status->totalPulses = axisObject->totalPulses; status->physicalPulses = axisObject->physicalPulses; status->busy = PlsrStateIsBusy(axisObject->state); status->pulseActive = PlsrStateIsPulseActive(axisObject->state); status->done = axisObject->done; status->wait = (axisObject->state == PLSR_STATE_WAIT) ? 1U : 0U; status->directionPositive = axisObject->directionPositive; status->highResourceMask = axisObject->lease.highMask; status->hardwareCounter = PlsrHwUsesHardwareCounter(axis); status->directionPoint = (axisObject->lease.valid != 0U) ? axisObject->lease.directionPoint : PLSR_DIRECTION_POINT_NONE; status->positionValid = axisObject->positionValid; status->jobValid = axisObject->jobValid; status->positionOverflow = axisObject->positionOverflow; status->positiveLimitActive = axisObject->positiveLimitActive; status->negativeLimitActive = axisObject->negativeLimitActive; status->emergencyLatched = axisObject->emergencyLatched; status->backlashActive = axisObject->backlashActive; status->s2Set = (axisObject->jobValid != 0U) ? axisObject->job.s2Set : 0U; status->speedClamped = (axisObject->jobValid != 0U) ? (uint8_t)((axisObject->job.speedClamped != 0U) || (axisObject->runtimeSpeedClamped != 0U)) : 0U; status->segmentCount = (axisObject->jobValid != 0U) ? axisObject->job.segmentCount : 0U; status->startSegment = (axisObject->jobValid != 0U) ? axisObject->job.startSegment : 0U; status->currentSegment = (axisObject->jobValid != 0U) ? PlsrPathGetCurrentSegment( &axisObject->path) : 0U; status->currentFrequencyHz = PlsrHwGetCurrentFrequencyHz(axis); status->targetFrequencyHz = (axisObject->jobValid != 0U) ? axisObject->liveTargetFrequencyHz : 0UL; status->liveFrequencyRejectCount = axisObject->liveFrequencyRejectCount; status->lastLiveFrequencyResult = axisObject->lastLiveFrequencyResult; PlsrCoreExitCritical(interruptState); return PLSR_RESULT_OK; } PLSR_RESULT PlsrGetLastParseDetail(uint8_t axis, PLSR_PARSE_DETAIL *detail) { uint32_t interruptState; if (axis >= PLSR_AXIS_COUNT) { return PLSR_RESULT_INVALID_AXIS; } if (detail == NULL) { return PLSR_RESULT_INVALID_ARGUMENT; } interruptState = PlsrCoreEnterCritical(); *detail = PlsrAxes[axis].parseDetail; PlsrCoreExitCritical(interruptState); return PLSR_RESULT_OK; }