#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_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; int64_t segmentAccountedPulses; int64_t equivalentCommandRemainder; PLSR_EQUIVALENT_CONFIG equivalent; PLSR_PATH_CONTEXT path; PLSR_PROFILE_STATE profile; 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; } 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 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 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 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 = (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(const PLSR_AXIS *axisObject) { uint64_t frequencyHz; uint64_t denominator; uint64_t numerator; if ((axisObject->profileActive == 0U) || (axisObject->profile.decelSlopeHzPerMs == 0UL)) { return 0; } frequencyHz = axisObject->profile.frequencyQ32 >> 32U; numerator = frequencyHz * frequencyHz; denominator = UINT64_C(2000) * axisObject->profile.decelSlopeHzPerMs; return (int64_t)((numerator + denominator - 1UL) / denominator); } 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->positionValid != 0U)) { if (includeBrakingDistance != 0U) { brakingDistance = PlsrGetBrakingDistance(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; } 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_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; PlsrStopSegmentHardware(axis, axisObject); 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; PlsrPathTerminate(&axisObject->path); PlsrStopSegmentHardware(axis, axisObject); PlsrResourceRelease(&axisObject->lease); /* 运动已结束:记录 lastBusy=0,保证掉电后恢复时位置仍可信。 */ (void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 0U); (void)PlcDeviceCheckpointHsd(); } 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 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 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 PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, const PLSR_START_REQUEST *start) { PLSR_RESOURCE_REQUEST resourceRequest; PLSR_RESULT result; 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->immediateStopPending = 0U; axisObject->done = 0U; axisObject->jobValid = 0U; axisObject->taskPulses = 0; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 0U; result = PlsrStateTransition(start->axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_START); if (result != PLSR_RESULT_OK) { PlsrResourceRelease(&axisObject->lease); } else { /* 运动开始:掉电恢复时据此判定"断电时在运动中"。 */ (void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 1U); (void)PlcDeviceCheckpointHsd(); } return result; } static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, const PLSR_CALL *call) { PLSR_RESOURCE_REQUEST resourceRequest; PLSR_PARSE_CONTEXT parseContext; PLSR_RESULT result; 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->immediateStopPending = 0U; axisObject->done = 0U; axisObject->taskPulses = 0; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 0U; axisObject->segmentEventPublished = 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 { /* 启动当前段硬件输出与速度曲线。 */ /* 零脉冲跳转链耗尽本轮预算时没有实际运动段,等待下一次 * 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; } /* 运动开始:掉电恢复时据此判定"断电时在运动中"。 */ (void)PlcDeviceSetHsdCheckpointMeta(axisObject->positionValid, 1U); (void)PlcDeviceCheckpointHsd(); } 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; PlsrSetStopReason(axisObject, reason); axisObject->pendingTerminal = terminal; 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); } result = PlsrProfileRequestStop(&axisObject->profile); 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]; 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_WAIT) { return PlsrRequestControlledStop(axis, PLSR_STATE_PAUSED, PLSR_STOP_REASON_PAUSE); } if ((axisObject->state != PLSR_STATE_ACCEL) && (axisObject->state != PLSR_STATE_RUN) && (axisObject->state != PLSR_STATE_DECEL)) { return PLSR_RESULT_INVALID_STATE; } return PlsrRequestControlledStop(axis, PLSR_STATE_PAUSED, PLSR_STOP_REASON_PAUSE); } 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: if (axisObject->state != PLSR_STATE_PAUSED) { result = PLSR_RESULT_INVALID_STATE; } else { axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; result = PlsrStateTransition(slot->command.axis, PLSR_STATE_ACCEL, PLSR_TRANSITION_WAIT_COMPLETE); } 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); (void)PlcDeviceSetHsdCheckpointMeta(1U, 0U); (void)PlcDeviceCheckpointHsd(); 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); (void)PlcDeviceSetHsdCheckpointMeta(1U, 0U); (void)PlcDeviceCheckpointHsd(); result = PLSR_RESULT_OK; } break; case PLSR_CMD_CLEAR_TOTAL: if (PlsrStateIsBusy(axisObject->state) != 0U) { result = PLSR_RESULT_BUSY; } else { axisObject->totalPulses = 0; (void)PlcDeviceSetHsdCheckpointMeta( axisObject->positionValid, 0U); (void)PlcDeviceCheckpointHsd(); 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; 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; } (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; result = PlsrProfileStart(&axisObject->profile, &profileRequest, pulses, 1000U); if (result != PLSR_RESULT_OK) { return result; } params.frequencyHz = job->s2.startSpeed; params.targetPulses = pulses; params.outputMode = (PLSR_OUTPUT_MODE)job->outputMode; params.directionPoint = job->directionPoint; params.directionPositive = positive; params.directionDelayMs = job->s2.directionDelayMs; result = PlsrHwStartPulse(axis, ¶ms); if (result != PLSR_RESULT_OK) { axisObject->profileActive = 0U; axisObject->profileWasAccel = 0U; return result; } axisObject->equivalentCommandRemainder = nextEquivalentRemainder; axisObject->directionPositive = positive; axisObject->segmentAccountedPulses = 0; axisObject->segmentAccountingActive = 1U; axisObject->profileActive = 1U; axisObject->profileWasAccel = (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; PlsrPublishAxis(axis); return PLSR_RESULT_OK; } /* 停止当前段的硬件输出与速度曲线。 */ static void PlsrStopSegmentHardware(uint8_t axis, PLSR_AXIS *axisObject) { PlsrAccountHardwarePulses(axis, axisObject); axisObject->profileActive = 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->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; 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; PlsrResourceInit(); (void)PlsrHwInit(); 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; } void PlsrProcess(void) { PLSR_COMMAND_SLOT slot; uint32_t events; uint8_t processedCommands = 0U; uint8_t criticalAxes = 0U; uint8_t axis; if (PlsrInitialized == 0U) { return; } /* 先合并 ISR 已完成的实际脉冲,确保段完成、STOP或新命令不会在 * HAL 计数清零前丢失最后一批位置增量。 */ for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { PlsrAccountHardwarePulses(axis, &PlsrAxes[axis]); PlsrMonitorAxisProtection(axis); } 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); } } 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++; } 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); } } /* 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; uint32_t outputFrequencyHz; uint64_t hardwarePulses; uint64_t remainingPulses; uint8_t profileDone; uint8_t wasAccel; PlsrHwTick(axis); PlsrAccountHardwarePulses(axis, axisObject); /* 首次 AB 内部预热周期不属于用户运动,速度曲线也必须冻结; * 否则低速起步时会在隐藏周期内提前爬升十余个刷新步。 */ if (PlsrHwIsAbStartupPriming(axis) != 0U) { continue; } if (PlsrStateIsBusy(axisObject->state) == 0U) { continue; } action = PlsrPathTick(&axisObject->path, &axisObject->job, axisObject->logicalPosition); if (action != PLSR_PATH_ACTION_NONE) { PlsrApplyPathAction(axis, action); } if ((axisObject->profileActive != 0U) && (PlsrHwGetState(axis) != PLSR_HW_STATE_DIR_SETTLING)) { /* 虚拟计数校准到硬件实际计数:ARPE 预装载滞后的偏差不累积, * 保证 profile DONE 与硬件计数同步(消除段尾冻结频率收尾)。 */ hardwarePulses = (uint64_t)PlsrHwGetEmittedPulses(axis); PlsrProfileSyncPulses(&axisObject->profile, hardwarePulses); wasAccel = axisObject->profileWasAccel; (void)PlsrProfileStep(&axisObject->profile, &frequencyHz, &profileDone); outputFrequencyHz = frequencyHz; if ((PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING) && (wasAccel == 0U) && (axisObject->pendingTerminal == PLSR_STATE_UNINITIALIZED) && ((uint64_t)axisObject->profile.totalPulses > hardwarePulses + 1UL)) { /* ARR 预装载在当前脉冲结束后生效,因此计算的是 * “当前脉冲之后”剩余的脉冲。用位置反推减速频率, * 避免 1 ms 时间曲线与预装载滞后造成段尾突停。 */ remainingPulses = (uint64_t)axisObject->profile.totalPulses - hardwarePulses - 1UL; outputFrequencyHz = PlsrProfileGetBrakingOutputFrequency( &axisObject->profile, remainingPulses); } if ((PlsrHwGetState(axis) == PLSR_HW_STATE_PWM_PENDING) && (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL)) { outputFrequencyHz = PlsrProfileGetInitialOutputFrequency( &axisObject->profile); } if ((profileDone == 0U) || (outputFrequencyHz != 0UL)) { (void)PlsrHwSetFrequency(axis, outputFrequencyHz); } if ((profileDone != 0U) && (axisObject->pendingTerminal != PLSR_STATE_UNINITIALIZED)) { (void)PlsrHwSetFrequency(axis, 0UL); axisObject->profileActive = 0U; (void)PlsrPostEvent(axis, PLSR_EVENT_DECEL_COMPLETE); } axisObject->profileWasAccel = (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; if ((wasAccel != 0U) && (axisObject->profileWasAccel == 0U) && (axisObject->state == PLSR_STATE_ACCEL)) { /* 加速完成(曲线进入匀速/减速)→ 状态机推进到 RUN。 */ (void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE); } } } } 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->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->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->s2Set = (axisObject->jobValid != 0U) ? axisObject->job.s2Set : 0U; status->speedClamped = (axisObject->jobValid != 0U) ? axisObject->job.speedClamped : 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; 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; }