diff --git a/Core/Src/main.c b/Core/Src/main.c index 07b5ff5..16c8ae1 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -202,7 +202,8 @@ int main(void) /* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */ HAL_Delay(1000U); - (void)PlsrSelfTestQueue(); + // (void)PlsrSelfTestQueue(); + (void)PlsrEquivalentSelfTestQueue(); OSStart(); /* USER CODE END 2 */ diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp index 870b464..9c07e0e 100644 --- a/EWARM/Modbus.ewp +++ b/EWARM/Modbus.ewp @@ -1290,6 +1290,9 @@ $PROJ_DIR$\..\PLSR\Inc\plsr_profile.h + + $PROJ_DIR$\..\PLSR\Inc\plsr_position.h + $PROJ_DIR$\..\PLSR\Inc\plsr_hal_f407.h @@ -1314,6 +1317,9 @@ $PROJ_DIR$\..\PLSR\Src\plsr_profile.c + + $PROJ_DIR$\..\PLSR\Src\plsr_position.c + $PROJ_DIR$\..\PLSR\Src\plsr_hal_f407.c diff --git a/PLSR/Inc/plc_device.h b/PLSR/Inc/plc_device.h index 13a0485..8bfd422 100644 --- a/PLSR/Inc/plc_device.h +++ b/PLSR/Inc/plc_device.h @@ -49,9 +49,13 @@ PLC_DEVICE_RESULT PlcDevicePublishSm(uint8_t axis, PLC_DEVICE_RESULT PlcDeviceReadSd(uint16_t address, int32_t *value); PLC_DEVICE_RESULT PlcDeviceWriteSd(uint16_t address, int32_t value); +PLC_DEVICE_RESULT PlcDeviceReadSdDword(uint16_t lowAddress, int32_t *value); PLC_DEVICE_RESULT PlcDevicePublishSd(uint8_t axis, uint8_t item, int32_t value); +PLC_DEVICE_RESULT PlcDevicePublishSdDword(uint8_t axis, + uint8_t lowItem, + int32_t value); PLC_DEVICE_RESULT PlcDeviceGetEventAddress(uint8_t axis, uint16_t segmentNumber, diff --git a/PLSR/Inc/plsr_address_map.h b/PLSR/Inc/plsr_address_map.h index 5580be3..8d23252 100644 --- a/PLSR/Inc/plsr_address_map.h +++ b/PLSR/Inc/plsr_address_map.h @@ -22,6 +22,14 @@ extern "C" { #define PLSR_SD_AXIS_ITEM_COUNT (12U) #define PLSR_EVENT_AXIS_ITEM_COUNT (100U) +#define PLSR_SD_ITEM_SEGMENT (0U) +#define PLSR_SD_ITEM_SEGMENT_PULSES (2U) +#define PLSR_SD_ITEM_SEGMENT_EQUIV (4U) +#define PLSR_SD_ITEM_FREQUENCY (6U) +#define PLSR_SD_ITEM_SPEED (8U) +#define PLSR_SD_ITEM_ERROR_CODE (10U) +#define PLSR_SD_ITEM_ERROR_BLOCK (11U) + typedef struct { uint16_t hsdRuntimeBase; diff --git a/PLSR/Inc/plsr_hal_f407.h b/PLSR/Inc/plsr_hal_f407.h index b01e5ed..86fc4c3 100644 --- a/PLSR/Inc/plsr_hal_f407.h +++ b/PLSR/Inc/plsr_hal_f407.h @@ -38,7 +38,9 @@ PLSR_RESULT PlsrHwStopPulse(uint8_t axis); uint8_t PlsrHwIsPulseActive(uint8_t axis); PLSR_HW_STATE PlsrHwGetState(uint8_t axis); uint32_t PlsrHwGetTimerClockHz(uint8_t axis); +uint32_t PlsrHwGetCurrentFrequencyHz(uint8_t axis); int64_t PlsrHwGetEmittedPulses(uint8_t axis); +uint8_t PlsrHwIsAbStartupPriming(uint8_t axis); /* 每 1ms tick 推进 HAL 状态机(DIR 延时等)。 */ void PlsrHwTick(uint8_t axis); diff --git a/PLSR/Inc/plsr_job.h b/PLSR/Inc/plsr_job.h index 685f6a1..379e7f4 100644 --- a/PLSR/Inc/plsr_job.h +++ b/PLSR/Inc/plsr_job.h @@ -1,6 +1,7 @@ #ifndef PLSR_JOB_H #define PLSR_JOB_H +#include "plsr_position.h" #include "plsr_types.h" #include @@ -137,7 +138,10 @@ typedef struct PLSR_DATA_REF s0; PLSR_DATA_REF s1; PLSR_S2_SNAPSHOT s2; + PLSR_EQUIVALENT_CONFIG equivalent; PLSR_SEGMENT_SNAPSHOT segments[PLSR_MAX_SEGMENTS]; + uint32_t inputDefaultSpeed; + uint32_t inputMaximumSpeed; uint32_t timerClockHz; uint32_t pairedTimerClockHz; uint16_t segmentCount; diff --git a/PLSR/Inc/plsr_position.h b/PLSR/Inc/plsr_position.h new file mode 100644 index 0000000..410f8e8 --- /dev/null +++ b/PLSR/Inc/plsr_position.h @@ -0,0 +1,56 @@ +#ifndef PLSR_POSITION_H +#define PLSR_POSITION_H + +#include "plsr_types.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + uint32_t pulsesPerRevolution; + uint32_t movementPerRevolution; + uint8_t unitCode; +} PLSR_EQUIVALENT_CONFIG; + +uint8_t PlsrPositionUnitCodeIsValid(uint8_t unitCode); +uint8_t PlsrPositionUsesEquivalent(const PLSR_EQUIVALENT_CONFIG *config); +PLSR_RESULT PlsrPositionValidateEquivalent( + const PLSR_EQUIVALENT_CONFIG *config); + +/* 带余数的相对距离换算。remainder/newRemainder 的分母恒为 + * movementPerRevolution,允许负值以保证正反向运动可相互抵消。 */ +PLSR_RESULT PlsrPositionUnitsToPulses( + const PLSR_EQUIVALENT_CONFIG *config, + int32_t units, + int64_t remainder, + int64_t *pulses, + int64_t *newRemainder); + +PLSR_RESULT PlsrPositionAbsoluteUnitsToPulses( + const PLSR_EQUIVALENT_CONFIG *config, + int32_t units, + int64_t *pulses); + +PLSR_RESULT PlsrPositionPulsesToUnits( + const PLSR_EQUIVALENT_CONFIG *config, + int64_t pulses, + int64_t *units); + +PLSR_RESULT PlsrPositionSpeedToPulseFrequency( + const PLSR_EQUIVALENT_CONFIG *config, + uint32_t speed, + uint32_t *frequencyHz); + +PLSR_RESULT PlsrPositionPulseFrequencyToSpeed( + const PLSR_EQUIVALENT_CONFIG *config, + uint32_t frequencyHz, + uint32_t *speed); + +#ifdef __cplusplus +} +#endif + +#endif /* PLSR_POSITION_H */ diff --git a/PLSR/Inc/plsr_self_test.h b/PLSR/Inc/plsr_self_test.h index 2781265..00b2870 100644 --- a/PLSR/Inc/plsr_self_test.h +++ b/PLSR/Inc/plsr_self_test.h @@ -11,6 +11,10 @@ extern "C" { * 用于 P3b-2 上板验证,验证完成后应关闭。 */ PLSR_RESULT PlsrSelfTestQueue(void); +/* P4-2 当量换算上板自测:Q0=PULSE、Y4=DIR,两段各1工程单位; + * 3脉冲/2单位比例下应依次输出1、2个脉冲。 */ +PLSR_RESULT PlsrEquivalentSelfTestQueue(void); + #ifdef __cplusplus } #endif diff --git a/PLSR/Inc/plsr_types.h b/PLSR/Inc/plsr_types.h index 838f038..759da73 100644 --- a/PLSR/Inc/plsr_types.h +++ b/PLSR/Inc/plsr_types.h @@ -166,6 +166,7 @@ typedef struct uint32_t illegalTransitionCount; uint32_t pendingEvents; int64_t logicalPosition; + int64_t taskPulses; int64_t totalPulses; uint8_t busy; uint8_t pulseActive; diff --git a/PLSR/Src/plc_device.c b/PLSR/Src/plc_device.c index 5f2c844..5f37c9a 100644 --- a/PLSR/Src/plc_device.c +++ b/PLSR/Src/plc_device.c @@ -549,6 +549,7 @@ PLC_DEVICE_RESULT PlcDevicePublishSm(uint8_t axis, PLC_DEVICE_RESULT PlcDeviceReadSd(uint16_t address, int32_t *value) { int32_t *source; + uint32_t interruptState; if (value == NULL) { @@ -560,7 +561,9 @@ PLC_DEVICE_RESULT PlcDeviceReadSd(uint16_t address, int32_t *value) return PLC_DEVICE_INVALID_ADDRESS; } + interruptState = PlcDeviceEnterCritical(); *value = *source; + PlcDeviceExitCritical(interruptState); return PLC_DEVICE_OK; } @@ -576,12 +579,63 @@ PLC_DEVICE_RESULT PlcDevicePublishSd(uint8_t axis, uint8_t item, int32_t value) { + uint32_t interruptState; + if ((axis >= PLSR_AXIS_COUNT) || (item >= PLSR_SD_AXIS_ITEM_COUNT)) { return PLC_DEVICE_INVALID_ARGUMENT; } + interruptState = PlcDeviceEnterCritical(); PlcSdRuntime[axis][item] = value; + PlcDeviceExitCritical(interruptState); + return PLC_DEVICE_OK; +} + +PLC_DEVICE_RESULT PlcDeviceReadSdDword(uint16_t lowAddress, int32_t *value) +{ + int32_t *source; + uint32_t rawValue; + uint32_t interruptState; + uint8_t axis; + uint8_t item; + + if (value == NULL) + { + return PLC_DEVICE_NULL_POINTER; + } + source = PlcDeviceResolveSd(lowAddress, &axis, &item); + if ((source == NULL) || ((item & 1U) != 0U) + || (item > PLSR_SD_ITEM_SPEED)) + { + return PLC_DEVICE_INVALID_ADDRESS; + } + interruptState = PlcDeviceEnterCritical(); + rawValue = (uint32_t)PlcSdRuntime[axis][item] & 0xFFFFUL; + rawValue |= ((uint32_t)PlcSdRuntime[axis][item + 1U] & 0xFFFFUL) + << 16U; + PlcDeviceExitCritical(interruptState); + *value = (int32_t)rawValue; + return PLC_DEVICE_OK; +} + +PLC_DEVICE_RESULT PlcDevicePublishSdDword(uint8_t axis, + uint8_t lowItem, + int32_t value) +{ + uint32_t rawValue; + uint32_t interruptState; + + if ((axis >= PLSR_AXIS_COUNT) || ((lowItem & 1U) != 0U) + || (lowItem > PLSR_SD_ITEM_SPEED)) + { + return PLC_DEVICE_INVALID_ARGUMENT; + } + rawValue = (uint32_t)value; + interruptState = PlcDeviceEnterCritical(); + PlcSdRuntime[axis][lowItem] = (int32_t)(rawValue & 0xFFFFUL); + PlcSdRuntime[axis][lowItem + 1U] = (int32_t)(rawValue >> 16U); + PlcDeviceExitCritical(interruptState); return PLC_DEVICE_OK; } diff --git a/PLSR/Src/plsr_core.c b/PLSR/Src/plsr_core.c index ce7b748..00cd3bc 100644 --- a/PLSR/Src/plsr_core.c +++ b/PLSR/Src/plsr_core.c @@ -7,6 +7,8 @@ #include "plsr_resource.h" #include +#define PLSR_CORE_SFD_AXIS_STRIDE (130U) + #ifndef PLSR_HOST_TEST #include "stm32f4xx.h" #include "ucos_ii.h" @@ -17,6 +19,8 @@ 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; @@ -27,7 +31,11 @@ typedef struct 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; @@ -39,6 +47,7 @@ typedef struct uint8_t positionValid; uint8_t jobValid; uint8_t positionOverflow; + uint8_t segmentAccountingActive; } PLSR_AXIS; typedef struct @@ -61,6 +70,8 @@ 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 uint32_t PlsrCoreEnterCritical(void) { @@ -104,6 +115,161 @@ static uint8_t PlsrStateIsPulseActive(PLSR_STATE state) : 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 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); + publishedError = (axisObject->compatibleErrorCode != 0U) + ? axisObject->compatibleErrorCode + : (uint16_t)axisObject->error; + publishedBlock = (axisObject->compatibleErrorCode != 0U) + ? axisObject->compatibleErrorBlock + : (((axisObject->error != PLSR_ERROR_NONE) + && (axisObject->jobValid != 0U)) + ? currentSegment + : 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 PlsrSetCompatibleEquivalentError(uint8_t axis, + PLSR_AXIS *axisObject) +{ + uint16_t commonBase = (uint16_t)(PLSR_SFD_CONFIG_START + + (uint16_t)axis + * PLSR_CORE_SFD_AXIS_STRIDE); + + if ((axisObject->parseDetail.result == PLSR_RESULT_INVALID_S2) + && (axisObject->parseDetail.block == PLSR_PARSE_BLOCK_S2) + && ((axisObject->parseDetail.address + == (uint32_t)(commonBase + 2U)) + || (axisObject->parseDetail.address + == (uint32_t)(commonBase + 4U)))) + { + axisObject->compatibleErrorCode = 2U; + axisObject->compatibleErrorBlock = 0U; + } +} + static void PlsrPublishAxis(uint8_t axis) { PLSR_AXIS *axisObject = &PlsrAxes[axis]; @@ -114,6 +280,7 @@ static void PlsrPublishAxis(uint8_t axis) (void)PlcDevicePublishSm(axis, compatibleRun, axisObject->directionPositive); + PlsrPublishRuntime(axis); } static void PlsrSetStopReason(PLSR_AXIS *axis, @@ -125,21 +292,102 @@ static void PlsrSetStopReason(PLSR_AXIS *axis, } } -/* 将 64 位逻辑位置发布为 HSD 的 32 位兼容值: - * 超出 INT32 范围时回绕(与信捷 32 位寄存器一致)并置溢出诊断标志。 */ +/* 将 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; +} + +/* 将 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; } - compatValue = (int32_t)(uint32_t)axisObject->logicalPosition; - (void)PlcDevicePublishHsdDword( - (uint16_t)((uint16_t)axis * PLSR_HSD_RUNTIME_AXIS_COUNT), - compatValue); + + axisObject->logicalPosition = newLogicalPosition; + axisObject->taskPulses = newTaskPulses; + axisObject->totalPulses = newTotalPulses; + axisObject->segmentAccountedPulses = emittedPulses; + PlsrPublishPosition(axis, axisObject); + PlsrPublishRuntime(axis); } static uint8_t PlsrTransitionIsAllowed(PLSR_STATE current, @@ -542,11 +790,16 @@ static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, 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); @@ -577,6 +830,9 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, return PLSR_RESULT_INVALID_STATE; } + axisObject->compatibleErrorCode = 0U; + axisObject->compatibleErrorBlock = 0U; + parseContext.logicalPosition = axisObject->logicalPosition; parseContext.positionValid = axisObject->positionValid; result = PlsrBuildJobSnapshot(call, @@ -585,6 +841,7 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, &axisObject->parseDetail); if (result != PLSR_RESULT_OK) { + PlsrSetCompatibleEquivalentError(call->dAxis, axisObject); return result; } @@ -606,13 +863,28 @@ static PLSR_RESULT PlsrStartCall(PLSR_AXIS *axisObject, 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; PlsrPathBegin(&axisObject->path, &axisObject->job, axisObject->logicalPosition); @@ -864,6 +1136,10 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) else { axisObject->totalPulses = 0; + (void)PlcDeviceSetHsdCheckpointMeta( + axisObject->positionValid, + 0U); + (void)PlcDeviceCheckpointHsd(); result = PLSR_RESULT_OK; } break; @@ -904,6 +1180,8 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) else { axisObject->error = PLSR_ERROR_NONE; + axisObject->compatibleErrorCode = 0U; + axisObject->compatibleErrorBlock = 0U; axisObject->stopReason = PLSR_STOP_REASON_NONE; axisObject->done = 0U; result = PlsrStateTransition(slot->command.axis, @@ -1034,25 +1312,75 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, 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) { - pulses = (segment->pulseOrTarget < 0) - ? -(int64_t)segment->pulseOrTarget - : (int64_t)segment->pulseOrTarget; - positive = (segment->pulseOrTarget >= 0) ? 1U : 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 { - /* 绝对模式:P4 位置闭环后完善;P3a 按当前位置计算。 */ - int64_t delta = (int64_t)segment->pulseOrTarget - - axisObject->logicalPosition; + /* 绝对模式使用实际硬件脉冲闭环更新后的逻辑位置计算位移。 */ + 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; } + 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; @@ -1090,18 +1418,27 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, 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); } /* 应用路径执行器的动作:段间推进、进入等待、结束、让出、错误。 */ @@ -1293,6 +1630,7 @@ PLSR_RESULT PlsrInit(void) 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) @@ -1328,6 +1666,13 @@ void PlsrProcess(void) return; } + /* 先合并 ISR 已完成的实际脉冲,确保段完成、STOP或新命令不会在 + * HAL 计数清零前丢失最后一批位置增量。 */ + for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) + { + PlsrAccountHardwarePulses(axis, &PlsrAxes[axis]); + } + for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { events = PlsrTakeEvents(axis, PLSR_EVENT_CRITICAL_MASK); @@ -1369,6 +1714,13 @@ void PlsrProcess(void) uint8_t wasAccel; PlsrHwTick(axis); + PlsrAccountHardwarePulses(axis, axisObject); + /* 首次 AB 内部预热周期不属于用户运动,速度曲线也必须冻结; + * 否则低速起步时会在隐藏周期内提前爬升十余个刷新步。 */ + if (PlsrHwIsAbStartupPriming(axis) != 0U) + { + continue; + } if (PlsrStateIsBusy(axisObject->state) == 0U) { continue; @@ -1475,6 +1827,7 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status) 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); diff --git a/PLSR/Src/plsr_hal_f407.c b/PLSR/Src/plsr_hal_f407.c index 970cd28..5f4cced 100644 --- a/PLSR/Src/plsr_hal_f407.c +++ b/PLSR/Src/plsr_hal_f407.c @@ -148,6 +148,8 @@ typedef struct uint8_t directionPositive; uint8_t abQuarter; uint8_t abCountAxis; + uint8_t abStartupPriming; + uint8_t abOutputPrimed; uint16_t abActiveBasePsc; uint16_t abActivePairPsc; uint16_t abActiveArr; @@ -688,6 +690,7 @@ static void PlsrHwBeginAbOutput(uint8_t axis, uint8_t debugReason) lagStart = periodTicks / 2UL; state->abCountAxis = lagAxis; state->abQuarter = 0U; + state->abStartupPriming = (state->abOutputPrimed == 0U) ? 1U : 0U; #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); @@ -733,14 +736,17 @@ static void PlsrHwBeginAbOutput(uint8_t axis, uint8_t debugReason) PlsrHwTimerSetCc1e(pairAxis, 1UL); PlsrHwTimerSetPwmMode1(axis); PlsrHwTimerSetPwmMode1(pairAxis); - /* OCREF 已在低电平位置稳定后再把物理引脚交还定时器。 */ PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); - PlsrHwReleasePulsePin(axis); - PlsrHwReleasePulsePin(pairAxis); + if (state->abStartupPriming == 0U) + { + /* 完成过首次预热后,段间/调频重定相均从已验证的 00 边界 + * 直接交还 AF,不额外吞掉用户周期。 */ + PlsrHwReleasePulsePin(axis); + PlsrHwReleasePulsePin(pairAxis); + } PlsrHwTimerSetCen(axis, 1UL); PlsrHwTimerSetCen(pairAxis, 1UL); - /* 最后才允许落后相计数中断。 */ PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwTimerSetCc1ie(lagAxis, 1UL); @@ -797,6 +803,7 @@ static void PlsrHwStopActiveOutput(uint8_t axis, #endif PlsrHwStopPwmTimer(axis); PlsrHwStopPwmTimer(pairAxis); + PlsrHwAxes[axis].abStartupPriming = 0U; #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) @@ -1062,6 +1069,16 @@ uint32_t PlsrHwGetTimerClockHz(uint8_t axis) return PlsrHwAxisMap[axis].timerClockHz; } +uint32_t PlsrHwGetCurrentFrequencyHz(uint8_t axis) +{ + if ((axis >= PLSR_HW_AXIS_COUNT) + || (PlsrHwAxes[axis].state != PLSR_HW_STATE_RUNNING)) + { + return 0UL; + } + return PlsrHwAxes[axis].currentFrequencyHz; +} + /* 硬件已发出的脉冲数(profile 虚拟计数校准用,中断内递增)。 */ int64_t PlsrHwGetEmittedPulses(uint8_t axis) { @@ -1090,6 +1107,15 @@ int64_t PlsrHwGetEmittedPulses(uint8_t axis) return emittedPulses; } +uint8_t PlsrHwIsAbStartupPriming(uint8_t axis) +{ + if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) + { + return 0U; + } + return PlsrHwAxes[axis].abStartupPriming; +} + void PlsrHwTick(uint8_t axis) { PLSR_HW_AXIS_STATE *state; @@ -1163,6 +1189,19 @@ void PlsrHwOnTimerUpdate(uint8_t axis) { return; } + if (state->abStartupPriming != 0U) + { + /* F407 首次切换 OC 模式时 OC1REF 初态不可直接作为物理AB相。 + * GPIO 保持 00 隐藏首个内部周期;落后相下降沿是真实 00 + * 边界,此时再交还 AF,且该隐藏周期绝不能计入 emitted。 */ + state->abStartupPriming = 0U; + state->abOutputPrimed = 1U; +#ifndef PLSR_HOST_TEST + PlsrHwReleasePulsePin(ownerAxis); + PlsrHwReleasePulsePin(PlsrHwGetPairedAxis(ownerAxis)); +#endif + return; + } state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { diff --git a/PLSR/Src/plsr_job.c b/PLSR/Src/plsr_job.c index bb97b22..45e17e3 100644 --- a/PLSR/Src/plsr_job.c +++ b/PLSR/Src/plsr_job.c @@ -482,6 +482,43 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call, result = PlsrReadFixedWord(0U, commonBase, &word, detail); if (result != PLSR_RESULT_OK) return result; snapshot->directionActiveHigh = ((word & (1U << 1U)) != 0U) ? 1U : 0U; + snapshot->equivalent.unitCode = (uint8_t)((word >> 8U) & 0x07U); + if (PlsrPositionUnitCodeIsValid(snapshot->equivalent.unitCode) == 0U) + { + PlsrSetDetail(detail, + PLSR_RESULT_INVALID_S2, + PLSR_PARSE_BLOCK_S2, + commonBase, + snapshot->equivalent.unitCode, + 0U); + return PLSR_RESULT_INVALID_S2; + } + result = PlsrReadFixedDword(0U, + (uint16_t)(commonBase + 2U), + &snapshot->equivalent.pulsesPerRevolution, + detail); + if (result != PLSR_RESULT_OK) return result; + result = PlsrReadFixedDword(0U, + (uint16_t)(commonBase + 4U), + &snapshot->equivalent.movementPerRevolution, + detail); + if (result != PLSR_RESULT_OK) return result; + if (PlsrPositionValidateEquivalent(&snapshot->equivalent) + != PLSR_RESULT_OK) + { + uint16_t invalidAddress = + (snapshot->equivalent.pulsesPerRevolution == 0UL) + ? (uint16_t)(commonBase + 2U) + : (uint16_t)(commonBase + 4U); + + PlsrSetDetail(detail, + PLSR_RESULT_INVALID_S2, + PLSR_PARSE_BLOCK_S2, + invalidAddress, + 0, + 0U); + return PLSR_RESULT_INVALID_S2; + } if (call->outputModeOverride == PLSR_OUTPUT_MODE_FROM_SFD) { snapshot->outputMode = ((word & (1U << 13U)) != 0U) @@ -550,6 +587,8 @@ static PLSR_RESULT PlsrLoadS2(const PLSR_CALL *call, snapshot->s2.stopSpeed = snapshot->s2.maximumSpeed; snapshot->speedClamped = 1U; } + snapshot->inputDefaultSpeed = snapshot->s2.defaultSpeed; + snapshot->inputMaximumSpeed = snapshot->s2.maximumSpeed; return PLSR_RESULT_OK; } @@ -835,9 +874,12 @@ static PLSR_RESULT PlsrCheckInitialPosition( { uint8_t visited[PLSR_MAX_SEGMENTS]; int64_t position = context->logicalPosition; - int32_t movement; + int64_t movement; + int64_t targetPosition; + int64_t remainder = 0; int32_t next; uint16_t segment = snapshot->startSegment; + PLSR_RESULT result; if ((snapshot->positioningMode != 0U) && (context->positionValid == 0U)) { @@ -858,7 +900,24 @@ static PLSR_RESULT PlsrCheckInitialPosition( visited[segment - 1U] = 1U; if (snapshot->positioningMode == 0U) { - movement = segmentData->pulseOrTarget; + result = PlsrPositionUnitsToPulses(&snapshot->equivalent, + segmentData->pulseOrTarget, + remainder, + &movement, + &remainder); + if (result != PLSR_RESULT_OK) + { + PlsrSetDetail(detail, + PLSR_RESULT_POSITION_OVERFLOW, + PLSR_PARSE_BLOCK_POSITION, + snapshot->s0.address + + (uint32_t)segment + * PLSR_S0_SEGMENT_WORDS + + 2UL, + segmentData->pulseOrTarget, + segment); + return PLSR_RESULT_POSITION_OVERFLOW; + } if (((movement > 0) && (position > INT64_MAX - movement)) || ((movement < 0) && (position < INT64_MIN - movement))) { @@ -877,10 +936,18 @@ static PLSR_RESULT PlsrCheckInitialPosition( } else { - movement = (segmentData->pulseOrTarget > position) + result = PlsrPositionAbsoluteUnitsToPulses( + &snapshot->equivalent, + segmentData->pulseOrTarget, + &targetPosition); + if (result != PLSR_RESULT_OK) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + movement = (targetPosition > position) ? 1 - : ((segmentData->pulseOrTarget < position) ? -1 : 0); - position = segmentData->pulseOrTarget; + : ((targetPosition < position) ? -1 : 0); + position = targetPosition; } if ((movement != 0) && (snapshot->initialDirectionPositive == 0U)) { @@ -910,6 +977,84 @@ static PLSR_RESULT PlsrCheckInitialPosition( return PLSR_RESULT_OK; } +static PLSR_RESULT PlsrConvertSnapshotSpeeds( + PLSR_JOB_SNAPSHOT *snapshot, + PLSR_PARSE_DETAIL *detail) +{ + uint16_t segment; + PLSR_RESULT result; + + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.defaultSpeed, + &snapshot->s2.defaultSpeed); + if (result != PLSR_RESULT_OK) return result; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.maximumSpeed, + &snapshot->s2.maximumSpeed); + if (result != PLSR_RESULT_OK) return result; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.startSpeed, + &snapshot->s2.startSpeed); + if (result != PLSR_RESULT_OK) return result; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.stopSpeed, + &snapshot->s2.stopSpeed); + if (result != PLSR_RESULT_OK) return result; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.zrnHighSpeed, + &snapshot->s2.zrnHighSpeed); + if (result != PLSR_RESULT_OK) return result; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + snapshot->s2.zrnCrawlSpeed, + &snapshot->s2.zrnCrawlSpeed); + if (result != PLSR_RESULT_OK) return result; + + if ((snapshot->s2.maximumSpeed < PLSR_FREQUENCY_MIN_HZ) + || (snapshot->s2.maximumSpeed > PLSR_FREQUENCY_MAX_HZ)) + { + PlsrSetDetail(detail, + PLSR_RESULT_INVALID_FREQUENCY, + PLSR_PARSE_BLOCK_S2, + 0UL, + (int32_t)snapshot->s2.maximumSpeed, + 0U); + return PLSR_RESULT_INVALID_FREQUENCY; + } + for (segment = 1U; segment <= snapshot->segmentCount; segment++) + { + PLSR_SEGMENT_SNAPSHOT *segmentData = + &snapshot->segments[segment - 1U]; + + if (segmentData->targetFrequency == 0UL) + { + continue; + } + result = PlsrPositionSpeedToPulseFrequency( + &snapshot->equivalent, + segmentData->targetFrequency, + &segmentData->targetFrequency); + if ((result != PLSR_RESULT_OK) + || (segmentData->targetFrequency < PLSR_FREQUENCY_MIN_HZ) + || (segmentData->targetFrequency > PLSR_FREQUENCY_MAX_HZ)) + { + PlsrSetDetail(detail, + PLSR_RESULT_INVALID_FREQUENCY, + PLSR_PARSE_BLOCK_S0, + snapshot->s0.address + + (uint32_t)segment * PLSR_S0_SEGMENT_WORDS, + (int32_t)segmentData->targetFrequency, + segment); + return PLSR_RESULT_INVALID_FREQUENCY; + } + result = PlsrValidateFrequencyDivider(snapshot, + segmentData->targetFrequency, + detail, + segment); + if (result != PLSR_RESULT_OK) return result; + } + return PLSR_RESULT_OK; +} + PLSR_RESULT PlsrBuildJobSnapshot(const PLSR_CALL *call, const PLSR_PARSE_CONTEXT *context, PLSR_JOB_SNAPSHOT *snapshot, @@ -1186,14 +1331,6 @@ PLSR_RESULT PlsrBuildJobSnapshot(const PLSR_CALL *call, segmentData->flags |= PLSR_SEGMENT_FLAG_SPEED_CLAMPED; snapshot->speedClamped = 1U; } - if (segmentData->targetFrequency != 0UL) - { - result = PlsrValidateFrequencyDivider(snapshot, - segmentData->targetFrequency, - detail, - segment); - if (result != PLSR_RESULT_OK) return result; - } result = PlsrValidateWait(call, segmentData, segment, detail); if (result != PLSR_RESULT_OK) return result; result = PlsrValidateJump(call, @@ -1205,6 +1342,8 @@ PLSR_RESULT PlsrBuildJobSnapshot(const PLSR_CALL *call, if (result != PLSR_RESULT_OK) return result; } + result = PlsrConvertSnapshotSpeeds(snapshot, detail); + if (result != PLSR_RESULT_OK) return result; result = PlsrCheckConstantPath(snapshot, detail); if (result != PLSR_RESULT_OK) return result; result = PlsrCheckInitialPosition(snapshot, context, detail); @@ -1246,14 +1385,22 @@ PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot, NULL); if (result != PLSR_RESULT_OK) return result; if (rawFrequency < 0) return PLSR_RESULT_INVALID_FREQUENCY; - *frequency = (rawFrequency == 0) ? snapshot->s2.defaultSpeed + *frequency = (rawFrequency == 0) ? snapshot->inputDefaultSpeed : (uint32_t)rawFrequency; *clamped = 0U; - if (*frequency > snapshot->s2.maximumSpeed) + if (*frequency > snapshot->inputMaximumSpeed) { - *frequency = snapshot->s2.maximumSpeed; + *frequency = snapshot->inputMaximumSpeed; *clamped = 1U; } if (*frequency == 0UL) return PLSR_RESULT_INVALID_FREQUENCY; + result = PlsrPositionSpeedToPulseFrequency(&snapshot->equivalent, + *frequency, + frequency); + if ((result != PLSR_RESULT_OK) || (*frequency == 0UL) + || (*frequency > PLSR_FREQUENCY_MAX_HZ)) + { + return PLSR_RESULT_INVALID_FREQUENCY; + } return PlsrValidateFrequencyDivider(snapshot, *frequency, NULL, segment); } diff --git a/PLSR/Src/plsr_path.c b/PLSR/Src/plsr_path.c index 39d832f..cbb3015 100644 --- a/PLSR/Src/plsr_path.c +++ b/PLSR/Src/plsr_path.c @@ -88,13 +88,18 @@ static uint8_t PlsrPathSegmentIsZeroPulse(const PLSR_JOB_SNAPSHOT *job, { const PLSR_SEGMENT_SNAPSHOT *segmentSnapshot = &job->segments[segment - 1U]; + int64_t targetPosition; if ((segmentSnapshot->flags & PLSR_SEGMENT_FLAG_ZERO_PULSE) != 0U) { return 1U; } if ((job->positioningMode == 1U) - && (segmentSnapshot->pulseOrTarget == (int32_t)logicalPosition)) + && (PlsrPositionAbsoluteUnitsToPulses( + &job->equivalent, + segmentSnapshot->pulseOrTarget, + &targetPosition) == PLSR_RESULT_OK) + && (targetPosition == logicalPosition)) { return 1U; } diff --git a/PLSR/Src/plsr_position.c b/PLSR/Src/plsr_position.c new file mode 100644 index 0000000..0f50faf --- /dev/null +++ b/PLSR/Src/plsr_position.c @@ -0,0 +1,227 @@ +#include "plsr_position.h" +#include +#include + +uint8_t PlsrPositionUnitCodeIsValid(uint8_t unitCode) +{ + return ((unitCode == 0U) || (unitCode == 1U) || (unitCode == 3U) + || (unitCode == 5U) || (unitCode == 7U)) + ? 1U + : 0U; +} + +uint8_t PlsrPositionUsesEquivalent(const PLSR_EQUIVALENT_CONFIG *config) +{ + return ((config != NULL) && (config->unitCode != 0U)) ? 1U : 0U; +} + +PLSR_RESULT PlsrPositionValidateEquivalent( + const PLSR_EQUIVALENT_CONFIG *config) +{ + if ((config == NULL) + || (PlsrPositionUnitCodeIsValid(config->unitCode) == 0U)) + { + return PLSR_RESULT_INVALID_S2; + } + if ((config->unitCode != 0U) + && ((config->pulsesPerRevolution == 0UL) + || (config->movementPerRevolution == 0UL))) + { + return PLSR_RESULT_INVALID_S2; + } + return PLSR_RESULT_OK; +} + +PLSR_RESULT PlsrPositionUnitsToPulses( + const PLSR_EQUIVALENT_CONFIG *config, + int32_t units, + int64_t remainder, + int64_t *pulses, + int64_t *newRemainder) +{ + int64_t numerator; + int64_t pulseValue; + uint32_t denominator; + + if ((pulses == NULL) || (newRemainder == NULL) + || (PlsrPositionValidateEquivalent(config) != PLSR_RESULT_OK)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + if (config->unitCode == 0U) + { + *pulses = units; + *newRemainder = 0; + return PLSR_RESULT_OK; + } + denominator = config->movementPerRevolution; + if ((remainder <= -(int64_t)denominator) + || (remainder >= (int64_t)denominator)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + + /* int32 * uint32 本身落在 int64 范围内;仅余数相加需要边界检查。 */ + numerator = (int64_t)units + * (int64_t)config->pulsesPerRevolution; + if (((remainder > 0) && (numerator > INT64_MAX - remainder)) + || ((remainder < 0) && (numerator < INT64_MIN - remainder))) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + numerator += remainder; + pulseValue = numerator / (int64_t)denominator; + *newRemainder = numerator % (int64_t)denominator; + *pulses = pulseValue; + return PLSR_RESULT_OK; +} + +PLSR_RESULT PlsrPositionAbsoluteUnitsToPulses( + const PLSR_EQUIVALENT_CONFIG *config, + int32_t units, + int64_t *pulses) +{ + int64_t ignoredRemainder; + + return PlsrPositionUnitsToPulses(config, + units, + 0, + pulses, + &ignoredRemainder); +} + +static uint64_t PlsrPositionMagnitude(int64_t value) +{ + return (value < 0) ? (uint64_t)(-(value + 1)) + 1ULL + : (uint64_t)value; +} + +PLSR_RESULT PlsrPositionPulsesToUnits( + const PLSR_EQUIVALENT_CONFIG *config, + int64_t pulses, + int64_t *units) +{ + uint64_t magnitude; + uint64_t quotient; + uint64_t remainder; + uint64_t whole; + uint64_t fraction; + uint64_t scaled; + uint64_t numerator; + uint64_t denominator; + + if ((units == NULL) + || (PlsrPositionValidateEquivalent(config) != PLSR_RESULT_OK)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + if (config->unitCode == 0U) + { + *units = pulses; + return PLSR_RESULT_OK; + } + + magnitude = PlsrPositionMagnitude(pulses); + numerator = config->movementPerRevolution; + denominator = config->pulsesPerRevolution; + quotient = magnitude / denominator; + remainder = magnitude % denominator; + if ((quotient != 0ULL) && (numerator > UINT64_MAX / quotient)) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + whole = quotient * numerator; + /* remainder 与 numerator 均为 uint32 范围,乘积不会超过 uint64。 */ + fraction = (remainder * numerator) / denominator; + if (whole > UINT64_MAX - fraction) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + scaled = whole + fraction; + if ((pulses >= 0) && (scaled > (uint64_t)INT64_MAX)) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + if ((pulses < 0) && (scaled > (UINT64_C(1) << 63U))) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + if ((pulses < 0) && (scaled == (UINT64_C(1) << 63U))) + { + *units = INT64_MIN; + } + else + { + *units = (pulses < 0) ? -(int64_t)scaled : (int64_t)scaled; + } + return PLSR_RESULT_OK; +} + +static PLSR_RESULT PlsrPositionScaleUnsigned(uint32_t value, + uint32_t numerator, + uint32_t denominator, + uint32_t *result) +{ + uint64_t product; + uint64_t scaled; + + if ((result == NULL) || (denominator == 0UL)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + product = (uint64_t)value * (uint64_t)numerator; + /* 速度采用四舍五入;累计位置仍采用截断并由绝对累计避免漂移。 */ + scaled = product / denominator; + if ((product % denominator) >= ((uint64_t)denominator + 1ULL) / 2ULL) + { + scaled++; + } + if (scaled > UINT32_MAX) + { + return PLSR_RESULT_POSITION_OVERFLOW; + } + *result = (uint32_t)scaled; + return PLSR_RESULT_OK; +} + +PLSR_RESULT PlsrPositionSpeedToPulseFrequency( + const PLSR_EQUIVALENT_CONFIG *config, + uint32_t speed, + uint32_t *frequencyHz) +{ + if ((frequencyHz == NULL) + || (PlsrPositionValidateEquivalent(config) != PLSR_RESULT_OK)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + if (config->unitCode == 0U) + { + *frequencyHz = speed; + return PLSR_RESULT_OK; + } + return PlsrPositionScaleUnsigned(speed, + config->pulsesPerRevolution, + config->movementPerRevolution, + frequencyHz); +} + +PLSR_RESULT PlsrPositionPulseFrequencyToSpeed( + const PLSR_EQUIVALENT_CONFIG *config, + uint32_t frequencyHz, + uint32_t *speed) +{ + if ((speed == NULL) + || (PlsrPositionValidateEquivalent(config) != PLSR_RESULT_OK)) + { + return PLSR_RESULT_INVALID_ARGUMENT; + } + if (config->unitCode == 0U) + { + *speed = frequencyHz; + return PLSR_RESULT_OK; + } + return PlsrPositionScaleUnsigned(frequencyHz, + config->movementPerRevolution, + config->pulsesPerRevolution, + speed); +} diff --git a/PLSR/Src/plsr_self_test.c b/PLSR/Src/plsr_self_test.c index 3544009..2403142 100644 --- a/PLSR/Src/plsr_self_test.c +++ b/PLSR/Src/plsr_self_test.c @@ -70,6 +70,13 @@ static void SelfTestWriteDword(PLSR_DEVICE_TYPE device, SelfTestWords[device][address + 1UL] = (uint16_t)(value >> 16U); } +static void SelfTestWriteSfdDword(uint16_t address, uint32_t value) +{ + (void)PlcDeviceWriteSfd(address, (uint16_t)(value & 0xFFFFUL)); + (void)PlcDeviceWriteSfd((uint16_t)(address + 1U), + (uint16_t)(value >> 16U)); +} + PLSR_RESULT PlsrSelfTestQueue(void) { PLSR_CALL call; @@ -109,3 +116,45 @@ PLSR_RESULT PlsrSelfTestQueue(void) call.outputModeOverride = PLSR_OUTPUT_AB; return PlsrPostCall(&call); } + +PLSR_RESULT PlsrEquivalentSelfTestQueue(void) +{ + PLSR_CALL call; + + (void)memset(SelfTestWords, 0, sizeof(SelfTestWords)); + + /* SFD900 Bit10~8=001(1um当量);3脉冲/2单位。 */ + (void)PlcDeviceWriteSfd(900U, (1U << 8U)); + SelfTestWriteSfdDword(902U, 3UL); + SelfTestWriteSfdDword(904U, 2UL); + (void)PlcDeviceWriteSfd(906U, SELF_TEST_DIR_POINT); + (void)PlcDeviceWriteSfd(907U, 10U); + /* 当量换算后物理最高速度=90000Hz,不超过硬件100kHz。 */ + SelfTestWriteSfdDword(956U, 60000UL); + + /* 板端可观察版本:两段各1001工程单位。 + * 3脉冲/2单位带余数换算后分别输出1501、1502脉冲,累计3003脉冲; + * 1500Hz附近持续约2s,避免原3脉冲自检在逻辑分析仪启动前已经结束。 */ + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE, 2U); + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 10U, 1000UL); + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 12U, 1001UL); + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 20U, 1000UL); + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 22U, 1001UL); + SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S1_BASE, 0U); + + (void)memset(&call, 0, sizeof(call)); + call.sequence = 0xA5A6UL; + call.source.context = NULL; + call.source.validateWords = SelfTestValidateWords; + call.source.readWord = SelfTestReadWord; + call.source.readBit = SelfTestReadBit; + call.s0.device = PLSR_DEVICE_D; + call.s0.address = SELF_TEST_S0_BASE; + call.s1.device = PLSR_DEVICE_D; + call.s1.address = SELF_TEST_S1_BASE; + call.s2.type = PLSR_OPERAND_CONSTANT; + call.s2.constant = 1; + call.dAxis = 0U; + call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; + return PlsrPostCall(&call); +} diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1 index 571a63c..95ec94c 100644 --- a/PLSR/Test/run_host_tests.ps1 +++ b/PLSR/Test/run_host_tests.ps1 @@ -14,9 +14,17 @@ if (-not (Test-Path -LiteralPath $outputDirectory)) } $tests = @( + @{ + Name = 'test_plsr_position' + Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" + "$workspacePath\PLSR\Test\test_plsr_position.c" + ) + }, @{ Name = 'test_plc_device' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plc_device.c" "$workspacePath\PLSR\Src\plsr_persistence.c" "$workspacePath\PLSR\Test\test_plc_device.c" @@ -25,6 +33,7 @@ $tests = @( @{ Name = 'test_plsr_core' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plc_device.c" "$workspacePath\PLSR\Src\plsr_persistence.c" "$workspacePath\PLSR\Src\plsr_resource.c" @@ -39,6 +48,7 @@ $tests = @( @{ Name = 'test_plsr_job' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plc_device.c" "$workspacePath\PLSR\Src\plsr_persistence.c" "$workspacePath\PLSR\Src\plsr_resource.c" @@ -53,6 +63,7 @@ $tests = @( @{ Name = 'test_plsr_path' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plc_device.c" "$workspacePath\PLSR\Src\plsr_persistence.c" "$workspacePath\PLSR\Src\plsr_resource.c" @@ -67,6 +78,7 @@ $tests = @( @{ Name = 'test_plsr_profile' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plsr_profile.c" "$workspacePath\PLSR\Test\test_plsr_profile.c" ) @@ -74,6 +86,7 @@ $tests = @( @{ Name = 'test_plsr_hal' Sources = @( + "$workspacePath\PLSR\Src\plsr_position.c" "$workspacePath\PLSR\Src\plc_device.c" "$workspacePath\PLSR\Src\plsr_persistence.c" "$workspacePath\PLSR\Src\plsr_resource.c" diff --git a/PLSR/Test/test_plc_device.c b/PLSR/Test/test_plc_device.c index 83c443c..304ed84 100644 --- a/PLSR/Test/test_plc_device.c +++ b/PLSR/Test/test_plc_device.c @@ -269,6 +269,16 @@ static void TestSmAndSd(void) TEST_CHECK(PlcDeviceWriteSd(1071U, 0) == PLC_DEVICE_READ_ONLY); TEST_CHECK(PlcDeviceReadSd(1012U, &value) == PLC_DEVICE_INVALID_ADDRESS); TEST_CHECK(PlcDevicePublishSd(4U, 0U, 0) == PLC_DEVICE_INVALID_ARGUMENT); + TEST_CHECK(PlcDevicePublishSdDword(2U, 2U, -123456) + == PLC_DEVICE_OK); + TEST_CHECK(PlcDeviceReadSdDword(1042U, &value) == PLC_DEVICE_OK); + TEST_CHECK(value == -123456); + TEST_CHECK(PlcDeviceReadSdDword(1043U, &value) + == PLC_DEVICE_INVALID_ADDRESS); + TEST_CHECK(PlcDevicePublishSdDword(2U, 9U, 0) + == PLC_DEVICE_INVALID_ARGUMENT); + TEST_CHECK(PlcDeviceReadSdDword(1070U, &value) + == PLC_DEVICE_INVALID_ADDRESS); TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceReadSm(1040U, &state) == PLC_DEVICE_OK); diff --git a/PLSR/Test/test_plsr_core.c b/PLSR/Test/test_plsr_core.c index a2084d0..89c92ef 100644 --- a/PLSR/Test/test_plsr_core.c +++ b/PLSR/Test/test_plsr_core.c @@ -348,6 +348,7 @@ static void TestPositionCheckpointing(void) { PLSR_STATUS status; int32_t hsdPosition; + int32_t hsdEquivalent; /* 1. SET_POSITION 后立即写入 HSD 检查点。 */ TestReset(); @@ -360,6 +361,8 @@ static void TestPositionCheckpointing(void) TEST_CHECK(PlcDeviceIsHsdDirty() == 0U); TEST_CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); TEST_CHECK(hsdPosition == 500); + TEST_CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + TEST_CHECK(hsdEquivalent == 500); /* 2. 模拟重启:位置与 positionValid 应恢复到上次正常停机的检查点。 */ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); @@ -377,7 +380,7 @@ static void TestPositionCheckpointing(void) TEST_CHECK(status.positionValid == 0U); TEST_CHECK(status.logicalPosition == 500); - /* 4. 64位位置超出 INT32 范围:对外回绕并置溢出诊断。 */ + /* 4. 64位位置超出 INT32 范围:保留内部值、HSD保持最近合法值。 */ TEST_CHECK(TestPostCommand(2U, 0U, PLSR_CMD_SET_POSITION, @@ -387,8 +390,24 @@ static void TestPositionCheckpointing(void) status = TestGetStatus(0U); TEST_CHECK(status.lastCommandResult == PLSR_RESULT_OK); TEST_CHECK(status.positionOverflow != 0U); + TEST_CHECK(status.logicalPosition == INT64_C(0x100000000)); + TEST_CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); + TEST_CHECK(hsdPosition == 500); + TEST_CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + TEST_CHECK(hsdEquivalent == 500); + + /* 5. 清零位置重新建立有效坐标,并解除兼容发布溢出锁存。 */ + TEST_CHECK(TestPostCommand(3U, 0U, PLSR_CMD_CLEAR_POSITION, 0) + == PLSR_RESULT_QUEUED); + PlsrProcess(); + status = TestGetStatus(0U); + TEST_CHECK(status.logicalPosition == 0); + TEST_CHECK(status.positionValid != 0U); + TEST_CHECK(status.positionOverflow == 0U); TEST_CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); TEST_CHECK(hsdPosition == 0); + TEST_CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + TEST_CHECK(hsdEquivalent == 0); } int main(void) diff --git a/PLSR/Test/test_plsr_hal.c b/PLSR/Test/test_plsr_hal.c index f644a69..f40fec1 100644 --- a/PLSR/Test/test_plsr_hal.c +++ b/PLSR/Test/test_plsr_hal.c @@ -81,6 +81,14 @@ static void TestWriteDword(TEST_MEMORY *memory, memory->words[device][address + 1UL] = (uint16_t)(raw >> 16U); } +static void TestWriteSfdDword(uint16_t address, uint32_t value) +{ + CHECK(PlcDeviceWriteSfd(address, (uint16_t)(value & 0xFFFFUL)) + == PLC_DEVICE_OK); + CHECK(PlcDeviceWriteSfd((uint16_t)(address + 1U), + (uint16_t)(value >> 16U)) == PLC_DEVICE_OK); +} + static void TestSetSegment(TEST_MEMORY *memory, uint16_t number, uint32_t frequency, @@ -104,6 +112,20 @@ static void TestResetEnvironment(void) CHECK(PlsrInit() == PLSR_RESULT_OK); } +static void TestCompleteFirstAbPrime(uint8_t axis) +{ + int quarter; + + CHECK(PlsrHwIsAbStartupPriming(axis) != 0U); + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(axis); + } + CHECK(PlsrHwIsAbStartupPriming(axis) == 0U); + CHECK(PlsrHwGetEmittedPulses(axis) == 0); + CHECK(PlsrHwTestGetAbQuarter(axis) == 0U); +} + static PLSR_CALL TestMakeCall(TEST_MEMORY *memory) { PLSR_CALL call; @@ -134,6 +156,20 @@ static PLSR_STATUS TestGetStatus(void) return status; } +static int32_t TestReadSdDword(uint16_t lowAddress) +{ + int32_t lowWord = 0; + int32_t highWord = 0; + uint32_t rawValue; + + CHECK(PlcDeviceReadSd(lowAddress, &lowWord) == PLC_DEVICE_OK); + CHECK(PlcDeviceReadSd((uint16_t)(lowAddress + 1U), &highWord) + == PLC_DEVICE_OK); + rawValue = ((uint32_t)lowWord & 0xFFFFUL) + | (((uint32_t)highWord & 0xFFFFUL) << 16U); + return (int32_t)rawValue; +} + /* ---- HAL 单测 ---- */ static void TestMapping(void) @@ -316,6 +352,7 @@ static void TestAbPhaseAndCounting(void) CHECK(PlsrHwTestGetCnt(0U) == ((PlsrHwTestGetArr(0U) + 1UL) * 3UL) / 4UL); CHECK(PlsrHwTestGetCnt(1U) == PlsrHwTestGetCcr(1U)); + TestCompleteFirstAbPrime(0U); /* 任一物理 timer update 不能直接计作完整 AB 周期。 */ PlsrHwTestTriggerUpdate(0U); @@ -433,6 +470,9 @@ static void TestTwoAbAxesIndependent(void) CHECK(PlsrHwTestGetPwmEnabled(2U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(3U) != 0U); + TestCompleteFirstAbPrime(0U); + TestCompleteFirstAbPrime(2U); + for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); @@ -473,6 +513,8 @@ static void TestAbFrequencyLimits(void) == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); CHECK(PlsrHwTestGetArr(0U) <= 65535UL); + TestCompleteFirstAbPrime(0U); + CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); for (quarter = 0; quarter < 4; quarter++) { @@ -580,6 +622,11 @@ static void TestEndToEndTwoSegments(void) status = TestGetStatus(); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.currentSegment == 2U); + CHECK(status.logicalPosition == 100); + CHECK(status.taskPulses == 100); + CHECK(status.totalPulses == 100); + CHECK(TestReadSdDword(1000U) == 2); + CHECK(TestReadSdDword(1002U) == 0); /* 段2:DIR 延时 → PWM → 加速 → RUN。 */ for (ticks = 0; ticks < 600; ticks++) @@ -603,6 +650,23 @@ static void TestEndToEndTwoSegments(void) status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); + CHECK(status.logicalPosition == 300); + CHECK(status.taskPulses == 300); + CHECK(status.totalPulses == 300); + CHECK(TestReadSdDword(1000U) == 2); + CHECK(TestReadSdDword(1002U) == 200); + CHECK(TestReadSdDword(1004U) == 200); + CHECK(TestReadSdDword(1006U) == 0); + { + int32_t hsdPulses; + int32_t hsdEquivalent; + + CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); + CHECK(hsdPulses == 300); + CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) + == PLC_DEVICE_OK); + CHECK(hsdEquivalent == 300); + } /* 终态转换后 HAL 回 IDLE(允许重新启动),脉冲已停止。 */ CHECK(PlsrHwIsPulseActive(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); @@ -633,6 +697,8 @@ static void TestEndToEndAbSegment(void) CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); + TestCompleteFirstAbPrime(0U); + /* 负脉冲选择反向相序,完整两个周期后由同一事件链结束任务。 */ PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); @@ -647,11 +713,222 @@ static void TestEndToEndAbSegment(void) status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); + CHECK(status.directionPositive == 0U); + CHECK(status.logicalPosition == -2); + CHECK(status.taskPulses == -2); + CHECK(status.totalPulses == 2); + CHECK(TestReadSdDword(1002U) == -2); + CHECK(TestReadSdDword(1004U) == -2); CHECK(status.highResourceMask == 0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); } +static void TestPositionOnImmediateStop(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_COMMAND command; + PLSR_STATUS status; + int pulse; + int tick; + + TestResetEnvironment(); + (void)memset(&memory, 0, sizeof(memory)); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); + TestSetSegment(&memory, 1U, 1000U, 100); + call = TestMakeCall(&memory); + call.sequence = 30UL; + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + for (tick = 0; tick < 10; tick++) + { + PlsrProcess(); + } + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + + for (pulse = 0; pulse < 37; pulse++) + { + PlsrHwTestTriggerUpdate(0U); + } + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.logicalPosition == 37); + CHECK(status.taskPulses == 37); + CHECK(status.totalPulses == 37); + + command.sequence = 31UL; + command.axis = 0U; + command.opcode = PLSR_CMD_STOP_IMMEDIATE; + command.argument = 0; + CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); + PlsrProcess(); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); + CHECK(PlsrPostEvent(0U, PLSR_EVENT_STOP_IMMEDIATE_DONE) + == PLSR_RESULT_OK); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_STOPPED); + CHECK(status.logicalPosition == 37); + CHECK(status.taskPulses == 37); + CHECK(status.totalPulses == 37); + CHECK(TestReadSdDword(1002U) == 37); + + command.sequence = 32UL; + command.opcode = PLSR_CMD_CLEAR_TOTAL; + CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.logicalPosition == 37); + CHECK(status.totalPulses == 0); +} + +static void TestAbsolutePositionAccounting(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_COMMAND command; + PLSR_STATUS status; + int32_t hsdPosition; + int pulse; + int tick; + + TestResetEnvironment(); + command.sequence = 40UL; + command.axis = 0U; + command.opcode = PLSR_CMD_SET_POSITION; + command.argument = 100; + CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); + PlsrProcess(); + + (void)memset(&memory, 0, sizeof(memory)); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); + TestSetSegment(&memory, 1U, 1000U, 130); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 1); + call = TestMakeCall(&memory); + call.sequence = 41UL; + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + for (tick = 0; tick < 10; tick++) + { + PlsrProcess(); + } + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + for (pulse = 0; pulse < 30; pulse++) + { + PlsrHwTestTriggerUpdate(0U); + } + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_COMPLETED); + CHECK(status.logicalPosition == 130); + CHECK(status.taskPulses == 30); + CHECK(status.totalPulses == 30); + CHECK(status.positionValid != 0U); + CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); + CHECK(hsdPosition == 130); +} + +static void TestEquivalentRemainderAccounting(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_STATUS status; + int32_t hsdPulses; + int32_t hsdEquivalent; + + TestResetEnvironment(); + CHECK(PlcDeviceWriteSfd(900U, (1U << 8U)) == PLC_DEVICE_OK); + TestWriteSfdDword(902U, 3UL); + TestWriteSfdDword(904U, 2UL); + TestWriteSfdDword(956U, 60000UL); + CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); + + (void)memset(&memory, 0, sizeof(memory)); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); + TestSetSegment(&memory, 1U, 1000U, 1); + call = TestMakeCall(&memory); + call.sequence = 30UL; + call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; + + /* 3脉冲/2单位:第一次1单位只输出1脉冲并保存1/2余数。 */ + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + PlsrHwTestTriggerUpdate(0U); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_COMPLETED); + CHECK(status.logicalPosition == 1); + CHECK(status.taskPulses == 1); + CHECK(status.totalPulses == 1); + CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); + CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + CHECK(hsdPulses == 1); + CHECK(hsdEquivalent == 0); + + /* 第二次1单位合并余数后输出2脉冲;两次合计精确为3脉冲/2单位。 */ + call.sequence = 31UL; + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + PlsrHwTestTriggerUpdate(0U); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + PlsrHwTestTriggerUpdate(0U); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.state == PLSR_STATE_COMPLETED); + CHECK(status.logicalPosition == 3); + CHECK(status.taskPulses == 2); + CHECK(status.totalPulses == 3); + CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); + CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + CHECK(hsdPulses == 3); + CHECK(hsdEquivalent == 2); + CHECK(TestReadSdDword(1002U) == 2); + CHECK(TestReadSdDword(1004U) == 1); +} + +static void TestEquivalentCompatibleError(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_STATUS status; + int32_t errorCode = -1; + int32_t errorBlock = -1; + + TestResetEnvironment(); + CHECK(PlcDeviceWriteSfd(900U, (1U << 8U)) == PLC_DEVICE_OK); + TestWriteSfdDword(902U, 0UL); + TestWriteSfdDword(904U, 2UL); + TestWriteSfdDword(956U, 60000UL); + + (void)memset(&memory, 0, sizeof(memory)); + TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); + TestSetSegment(&memory, 1U, 1000U, 1); + call = TestMakeCall(&memory); + call.sequence = 32UL; + call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; + + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.lastCommandResult == PLSR_RESULT_INVALID_S2); + CHECK(status.state == PLSR_STATE_IDLE); + CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); + CHECK(PlcDeviceReadSd(1011U, &errorBlock) == PLC_DEVICE_OK); + CHECK(errorCode == 2); + CHECK(errorBlock == 0); + + /* A valid retry clears the compatible parameter error. */ + TestWriteSfdDword(902U, 3UL); + call.sequence = 33UL; + CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); + PlsrProcess(); + CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); + CHECK(errorCode == 0); + CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); +} + static void TestProductionSelfTestStartsAb(void) { PLSR_STATUS status; @@ -670,6 +947,55 @@ static void TestProductionSelfTestStartsAb(void) CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); } +static void TestEquivalentSelfTest(void) +{ + PLSR_STATUS status; + int32_t hsdPulses; + int32_t hsdEquivalent; + int ticks; + + TestResetEnvironment(); + CHECK(PlsrEquivalentSelfTestQueue() == PLSR_RESULT_QUEUED); + PlsrProcess(); + + /* SFD907=10ms. Advance the simulated hardware delay before segment 1. */ + for (ticks = 0; ticks < 10; ticks++) + { + PlsrProcess(); + } + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + + /* 3 pulses / 2 units: 1001 units emit 1501 pulses and keep 1/2 remainder. */ + for (ticks = 0; ticks < 1501; ticks++) + { + PlsrHwTestTriggerUpdate(0U); + } + PlsrProcess(); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + + /* Segment 2 consumes the remainder and therefore emits 1502 pulses. */ + for (ticks = 0; ticks < 1502; ticks++) + { + PlsrHwTestTriggerUpdate(0U); + } + PlsrProcess(); + + status = TestGetStatus(); + CHECK(status.lastCommandResult == PLSR_RESULT_OK); + CHECK(status.outputMode == PLSR_OUTPUT_PULSE_DIR); + CHECK(status.state == PLSR_STATE_COMPLETED); + CHECK(status.currentSegment == 2U); + CHECK(status.logicalPosition == 3003); + CHECK(status.taskPulses == 3003); + CHECK(status.totalPulses == 3003); + CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); + CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); + CHECK(hsdPulses == 3003); + CHECK(hsdEquivalent == 2002); + CHECK(TestReadSdDword(1002U) == 1502); + CHECK(TestReadSdDword(1004U) == 1001); +} + static void TestStopStopsHardware(void) { TEST_MEMORY memory; @@ -722,7 +1048,12 @@ int main(void) TestStopAndInvalidArgs(); TestEndToEndTwoSegments(); TestEndToEndAbSegment(); + TestPositionOnImmediateStop(); + TestAbsolutePositionAccounting(); + TestEquivalentRemainderAccounting(); + TestEquivalentCompatibleError(); TestProductionSelfTestStartsAb(); + TestEquivalentSelfTest(); TestStopStopsHardware(); if (TestFailures != 0) diff --git a/PLSR/Test/test_plsr_job.c b/PLSR/Test/test_plsr_job.c index 545f80b..ad6876c 100644 --- a/PLSR/Test/test_plsr_job.c +++ b/PLSR/Test/test_plsr_job.c @@ -362,6 +362,53 @@ static void TestOutputAndDivider(void) CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK); } +static void TestEquivalentSnapshot(void) +{ + TEST_MEMORY memory; + PLSR_CALL call; + PLSR_PARSE_CONTEXT context = {0, 1U}; + PLSR_JOB_SNAPSHOT snapshot; + PLSR_PARSE_DETAIL detail; + uint32_t liveFrequency; + uint8_t clamped; + + TestBuildValidBlocks(&memory); + TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 40000); + call = TestMakeCall(&memory); + CHECK(PlcDeviceWriteSfd(900U, (1U << 8U)) == PLC_DEVICE_OK); + TestWriteSfdDword(902U, 3UL); + TestWriteSfdDword(904U, 2UL); + TestWriteSfdDword(956U, 60000UL); + TestWriteSfdDword(958U, 0UL); + + CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) + == PLSR_RESULT_OK); + CHECK(snapshot.equivalent.unitCode == 1U); + CHECK(snapshot.equivalent.pulsesPerRevolution == 3UL); + CHECK(snapshot.equivalent.movementPerRevolution == 2UL); + CHECK(snapshot.inputDefaultSpeed == 1000UL); + CHECK(snapshot.inputMaximumSpeed == 60000UL); + CHECK(snapshot.s2.defaultSpeed == 1500UL); + CHECK(snapshot.s2.maximumSpeed == 90000UL); + CHECK(snapshot.segments[0].targetFrequency == 60000UL); + CHECK(snapshot.segments[1].targetFrequency == 1500UL); + + TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 20000); + CHECK(PlsrResolveLiveFrequency(&snapshot, + 1U, + &liveFrequency, + &clamped) == PLSR_RESULT_OK); + CHECK(liveFrequency == 30000UL); + CHECK(clamped == 0U); + + TestWriteSfdDword(902U, 0UL); + CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) + == PLSR_RESULT_INVALID_S2); + CHECK(detail.address == 902U); + + TestConfigureAxis0K1(); +} + static void TestCoreSubmission(void) { TEST_MEMORY memory; @@ -443,6 +490,7 @@ int main(void) TestValidationFailures(); TestDynamicReferences(); TestOutputAndDivider(); + TestEquivalentSnapshot(); TestDefaultSfdStartable(); TestCoreSubmission(); diff --git a/PLSR/Test/test_plsr_position.c b/PLSR/Test/test_plsr_position.c new file mode 100644 index 0000000..c208086 --- /dev/null +++ b/PLSR/Test/test_plsr_position.c @@ -0,0 +1,93 @@ +#include "plsr_position.h" +#include +#include + +static int TestChecks; +static int TestFailures; + +#define CHECK(condition) \ + do \ + { \ + TestChecks++; \ + if (!(condition)) \ + { \ + TestFailures++; \ + (void)printf("FAIL line %d: %s\n", __LINE__, #condition); \ + } \ + } while (0) + +int main(void) +{ + PLSR_EQUIVALENT_CONFIG config = {3UL, 2UL, 1U}; + PLSR_EQUIVALENT_CONFIG pulseConfig = {0UL, 0UL, 0U}; + int64_t pulses; + int64_t units; + int64_t remainder; + uint32_t speed; + + CHECK(PlsrPositionUnitCodeIsValid(0U) != 0U); + CHECK(PlsrPositionUnitCodeIsValid(1U) != 0U); + CHECK(PlsrPositionUnitCodeIsValid(3U) != 0U); + CHECK(PlsrPositionUnitCodeIsValid(5U) != 0U); + CHECK(PlsrPositionUnitCodeIsValid(7U) != 0U); + CHECK(PlsrPositionUnitCodeIsValid(2U) == 0U); + CHECK(PlsrPositionValidateEquivalent(&config) == PLSR_RESULT_OK); + + CHECK(PlsrPositionUnitsToPulses(&config, 1, 0, &pulses, &remainder) + == PLSR_RESULT_OK); + CHECK(pulses == 1); + CHECK(remainder == 1); + CHECK(PlsrPositionUnitsToPulses(&config, + 1, + remainder, + &pulses, + &remainder) == PLSR_RESULT_OK); + CHECK(pulses == 2); + CHECK(remainder == 0); + + CHECK(PlsrPositionUnitsToPulses(&config, -1, 0, &pulses, &remainder) + == PLSR_RESULT_OK); + CHECK(pulses == -1); + CHECK(remainder == -1); + CHECK(PlsrPositionUnitsToPulses(&config, + -1, + remainder, + &pulses, + &remainder) == PLSR_RESULT_OK); + CHECK(pulses == -2); + CHECK(remainder == 0); + + CHECK(PlsrPositionAbsoluteUnitsToPulses(&config, 10, &pulses) + == PLSR_RESULT_OK); + CHECK(pulses == 15); + CHECK(PlsrPositionPulsesToUnits(&config, 3, &units) == PLSR_RESULT_OK); + CHECK(units == 2); + CHECK(PlsrPositionPulsesToUnits(&config, -3, &units) == PLSR_RESULT_OK); + CHECK(units == -2); + CHECK(PlsrPositionSpeedToPulseFrequency(&config, 200U, &speed) + == PLSR_RESULT_OK); + CHECK(speed == 300U); + CHECK(PlsrPositionPulseFrequencyToSpeed(&config, 300U, &speed) + == PLSR_RESULT_OK); + CHECK(speed == 200U); + + CHECK(PlsrPositionUnitsToPulses(&pulseConfig, + INT32_MIN, + 99, + &pulses, + &remainder) == PLSR_RESULT_OK); + CHECK(pulses == INT32_MIN); + CHECK(remainder == 0); + config.pulsesPerRevolution = 0UL; + CHECK(PlsrPositionValidateEquivalent(&config) == PLSR_RESULT_INVALID_S2); + + if (TestFailures != 0) + { + (void)printf("FAIL: %d of %d PLSR position checks failed\n", + TestFailures, + TestChecks); + return 1; + } + (void)printf("PASS: %d PLSR position checks\n", TestChecks); + return 0; +} diff --git a/tmp/pdfs/xinjie_page_30.png b/tmp/pdfs/xinjie_page_30.png new file mode 100644 index 0000000..7a6ee1f Binary files /dev/null and b/tmp/pdfs/xinjie_page_30.png differ diff --git a/tmp/pdfs/xinjie_page_31.png b/tmp/pdfs/xinjie_page_31.png new file mode 100644 index 0000000..ac66b07 Binary files /dev/null and b/tmp/pdfs/xinjie_page_31.png differ