#include "plc_device.h" #include "plsr_core.h" #include "plsr_persistence.h" #include "plsr_resource.h" #include #include #include static unsigned int TestCount; #define TEST_CHECK(condition) \ do \ { \ TestCount++; \ if (!(condition)) \ { \ (void)fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); \ exit(EXIT_FAILURE); \ } \ } while (0) static void TestReset(void) { PlsrPersistenceTestResetStorage(); TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlsrInit() == PLSR_RESULT_OK); } static PLSR_RESULT TestPostCommand(uint32_t sequence, uint8_t axis, PLSR_COMMAND_OPCODE opcode, int64_t argument) { PLSR_COMMAND command; command.sequence = sequence; command.axis = axis; command.opcode = opcode; command.argument = argument; return PlsrPostCommand(&command); } static PLSR_RESULT TestPostStart(uint32_t sequence, uint8_t axis, PLSR_OUTPUT_MODE mode, uint8_t directionPoint, uint8_t directionPositive) { PLSR_START_REQUEST request; request.sequence = sequence; request.axis = axis; request.outputMode = mode; request.directionPoint = directionPoint; request.directionPositive = directionPositive; return PlsrPostStart(&request); } static PLSR_STATUS TestGetStatus(uint8_t axis) { PLSR_STATUS status; (void)memset(&status, 0, sizeof(status)); TEST_CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); return status; } static void TestResourceManager(void) { PLSR_RESOURCE_REQUEST request; PLSR_RESOURCE_LEASE first; PLSR_RESOURCE_LEASE second; PLSR_RESOURCE_STATUS status; (void)memset(&first, 0, sizeof(first)); (void)memset(&second, 0, sizeof(second)); PlsrResourceInit(); request.ownerAxis = 0U; request.outputMode = PLSR_OUTPUT_PULSE_DIR; request.dAxis = 0U; request.directionPoint = 4U; TEST_CHECK(PlsrResourceReserve(&request, &first) == PLSR_RESULT_OK); TEST_CHECK(first.highMask == 0x01U); TEST_CHECK(first.outputMask == ((1UL << 0U) | (1UL << 4U))); request.ownerAxis = 1U; request.dAxis = 1U; request.directionPoint = 4U; TEST_CHECK(PlsrResourceReserve(&request, &second) == PLSR_RESULT_RESOURCE_CONFLICT); TEST_CHECK(second.valid == 0U); request.directionPoint = 8U; TEST_CHECK(PlsrResourceReserve(&request, &second) == PLSR_RESULT_INVALID_RESOURCE); request.directionPoint = 1U; TEST_CHECK(PlsrResourceReserve(&request, &second) == PLSR_RESULT_INVALID_RESOURCE); PlsrResourceRelease(&first); PlsrResourceRelease(&first); request.ownerAxis = 0U; request.outputMode = PLSR_OUTPUT_AB; request.dAxis = 0U; request.directionPoint = PLSR_DIRECTION_POINT_NONE; TEST_CHECK(PlsrResourceReserve(&request, &first) == PLSR_RESULT_OK); TEST_CHECK(first.highMask == 0x03U); request.ownerAxis = 2U; request.outputMode = PLSR_OUTPUT_PULSE_DIR; request.dAxis = 2U; request.directionPoint = 4U; TEST_CHECK(PlsrResourceReserve(&request, &second) == PLSR_RESULT_OK); TEST_CHECK(second.highMask == 0x04U); request.ownerAxis = 1U; request.outputMode = PLSR_OUTPUT_CW_CCW; request.dAxis = 1U; request.directionPoint = PLSR_DIRECTION_POINT_NONE; PlsrResourceRelease(&second); TEST_CHECK(PlsrResourceReserve(&request, &second) == PLSR_RESULT_INVALID_RESOURCE); PlsrResourceGetStatus(&status); TEST_CHECK(status.highMask == 0x03U); TEST_CHECK(PlsrResourceCheckInvariant() != 0U); PlsrResourceRelease(&first); PlsrResourceGetStatus(&status); TEST_CHECK(status.outputMask == 0UL); } static void TestStateMachineAndCommands(void) { PLSR_RESOURCE_STATUS resources; PLSR_STATUS status; TestReset(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_IDLE); TEST_CHECK(status.directionPoint == PLSR_DIRECTION_POINT_NONE); TEST_CHECK(TestPostStart(1U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_ACCEL); TEST_CHECK(status.busy != 0U); TEST_CHECK(status.pulseActive != 0U); TEST_CHECK(status.done == 0U); TEST_CHECK(status.highResourceMask == 0x01U); TEST_CHECK(status.directionPoint == 4U); TEST_CHECK(status.lastCommandSequence == 1U); TEST_CHECK(status.lastCommandResult == PLSR_RESULT_OK); TEST_CHECK(TestPostStart(1U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_OK); TEST_CHECK(TestPostStart(1U, 1U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(1U); TEST_CHECK(status.state == PLSR_STATE_IDLE); TEST_CHECK(status.lastCommandResult == PLSR_RESULT_RESOURCE_CONFLICT); TEST_CHECK(status.error == PLSR_ERROR_RESOURCE_CONFLICT); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_ACCEL_COMPLETE) == PLSR_RESULT_OK); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).state == PLSR_STATE_RUN); TEST_CHECK(TestPostCommand(2U, 0U, PLSR_CMD_SET_POSITION, 500) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).lastCommandResult == PLSR_RESULT_BUSY); TEST_CHECK(TestPostCommand(3U, 0U, PLSR_CMD_PAUSE, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).state == PLSR_STATE_DECEL); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_DECEL_COMPLETE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_PAUSED); TEST_CHECK(status.busy != 0U); TEST_CHECK(status.pulseActive == 0U); TEST_CHECK(status.highResourceMask == 0x01U); TEST_CHECK(TestPostCommand(4U, 0U, PLSR_CMD_RESUME, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).state == PLSR_STATE_ACCEL); TEST_CHECK(TestPostCommand(5U, 0U, PLSR_CMD_STOP_IMMEDIATE, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_STOPPED); TEST_CHECK(status.stopReason == PLSR_STOP_REASON_STOP_IMMEDIATE); TEST_CHECK(status.highResourceMask == 0U); TEST_CHECK(TestPostCommand(5U, 0U, PLSR_CMD_STOP_IMMEDIATE, 0) == PLSR_RESULT_OK); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_STOP_IMMEDIATE_DONE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_STOPPED); TEST_CHECK(status.busy == 0U); TEST_CHECK(status.done == 0U); TEST_CHECK(status.highResourceMask == 0U); PlsrResourceGetStatus(&resources); TEST_CHECK(resources.outputMask == 0UL); TEST_CHECK(TestPostStart(6U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 0U) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_JOB_COMPLETE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_COMPLETED); TEST_CHECK(status.done != 0U); TEST_CHECK(status.stopReason == PLSR_STOP_REASON_NORMAL_COMPLETE); TEST_CHECK(TestPostStart(7U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).done == 0U); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_JOB_COMPLETE | PLSR_EVENT_SOFTWARE_EMERGENCY) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_STOPPED); TEST_CHECK(status.done == 0U); TEST_CHECK(status.stopReason == PLSR_STOP_REASON_SOFTWARE_EMERGENCY); TEST_CHECK(PlsrStateTransition(0U, PLSR_STATE_RUN, PLSR_TRANSITION_ACCEL_COMPLETE) == PLSR_RESULT_INVALID_STATE); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_ERROR); TEST_CHECK(status.error == PLSR_ERROR_ILLEGAL_TRANSITION); TEST_CHECK(status.illegalTransitionCount == 1UL); TEST_CHECK(TestPostCommand(8U, 0U, PLSR_CMD_RESET_ERROR, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).state == PLSR_STATE_IDLE); } static void TestLimitWaitAndPairs(void) { PLSR_STATUS status; TestReset(); TEST_CHECK(TestPostStart(10U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_LIMIT_POSITIVE | PLSR_EVENT_DECEL_COMPLETE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(0U); TEST_CHECK(status.state == PLSR_STATE_STOPPED); TEST_CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE); TEST_CHECK(status.error == PLSR_ERROR_LIMIT_POSITIVE); TEST_CHECK(TestPostStart(11U, 2U, PLSR_OUTPUT_AB, PLSR_DIRECTION_POINT_NONE, 0U) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(2U); TEST_CHECK(status.highResourceMask == 0x0CU); TEST_CHECK(status.outputMode == PLSR_OUTPUT_AB); TEST_CHECK(PlsrPostEvent(2U, PLSR_EVENT_WAIT_BEGIN) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(2U); TEST_CHECK(status.state == PLSR_STATE_WAIT); TEST_CHECK(status.busy != 0U); TEST_CHECK(status.pulseActive == 0U); TEST_CHECK(status.highResourceMask == 0x0CU); TEST_CHECK(TestPostCommand(12U, 2U, PLSR_CMD_PAUSE, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(2U).state == PLSR_STATE_PAUSED); TEST_CHECK(TestPostCommand(13U, 2U, PLSR_CMD_STOP_DECEL, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(2U); TEST_CHECK(status.state == PLSR_STATE_STOPPED); TEST_CHECK(status.highResourceMask == 0U); TEST_CHECK(TestPostStart(14U, 1U, PLSR_OUTPUT_CW_CCW, PLSR_DIRECTION_POINT_NONE, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(1U); TEST_CHECK(status.state == PLSR_STATE_IDLE); TEST_CHECK(status.lastCommandResult == PLSR_RESULT_INVALID_RESOURCE); } static void TestQueueAndInputValidation(void) { PLSR_COMMAND command; uint32_t sequence; TestReset(); TEST_CHECK(PlsrPostEvent(4U, PLSR_EVENT_JOB_COMPLETE) == PLSR_RESULT_INVALID_AXIS); TEST_CHECK(PlsrPostEvent(0U, 0UL) == PLSR_RESULT_INVALID_ARGUMENT); TEST_CHECK(PlsrPostEvent(0U, 0x80000000UL) == PLSR_RESULT_INVALID_ARGUMENT); TEST_CHECK(PlsrGetStatus(0U, NULL) == PLSR_RESULT_INVALID_ARGUMENT); TEST_CHECK(PlsrGetStatus(4U, NULL) == PLSR_RESULT_INVALID_AXIS); command.axis = 1U; command.opcode = PLSR_CMD_CLEAR_POSITION; command.argument = 0; for (sequence = 100U; sequence < 100U + PLSR_COMMAND_QUEUE_DEPTH; sequence++) { command.sequence = sequence; TEST_CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); } command.sequence = 1000U; TEST_CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUE_FULL); PlsrProcess(); TEST_CHECK(TestGetStatus(1U).lastCommandResult == PLSR_RESULT_OK); TEST_CHECK(TestPostCommand(2000U, 0U, PLSR_CMD_SELF_TEST, 0) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(TestGetStatus(0U).lastCommandResult == PLSR_RESULT_OK); } static void TestPositionCheckpointing(void) { PLSR_STATUS status; int32_t hsdPosition; int32_t hsdEquivalent; uint8_t axis; /* 1. 全局position-valid必须保守聚合:只建立轴0坐标时,重启 * 后不得把其他三条未校准轴一起判为可信。 */ TestReset(); TEST_CHECK(TestPostCommand(1U, 0U, PLSR_CMD_SET_POSITION, 500) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceGetRestoredHsdPositionValid() == 0U); /* 2. 同一轮的四轴 SET_POSITION 合并为一个完整 HSD 检查点。 */ TestReset(); for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { TEST_CHECK(TestPostCommand((uint32_t)axis + 1UL, axis, PLSR_CMD_SET_POSITION, (axis == 0U) ? 500 : 0) == PLSR_RESULT_QUEUED); } PlsrProcess(); TEST_CHECK(PlsrPersistenceTestGetHsdSaveCount() == 1UL); status = TestGetStatus(0U); TEST_CHECK(status.lastCommandResult == PLSR_RESULT_OK); TEST_CHECK(status.positionValid != 0U); 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); /* 3. 模拟重启:位置与 positionValid 应恢复到上次正常停机的检查点。 */ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlsrInit() == PLSR_RESULT_OK); status = TestGetStatus(0U); TEST_CHECK(status.positionValid != 0U); TEST_CHECK(status.logicalPosition == 500); /* 4. 运动中掉电(lastBusy=1):恢复后位置不可信。 */ TEST_CHECK(PlcDeviceSetHsdCheckpointMeta(1U, 1U) == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceCheckpointHsd() == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlsrInit() == PLSR_RESULT_OK); status = TestGetStatus(0U); TEST_CHECK(status.positionValid == 0U); TEST_CHECK(status.logicalPosition == 500); /* 5. 先重新建立四轴有效坐标,再验证64位位置超出INT32范围: * 保留内部值、HSD保持最近合法值,但持久化有效位必须清除。 */ for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) { TEST_CHECK(TestPostCommand(10UL + (uint32_t)axis, axis, PLSR_CMD_SET_POSITION, (axis == 0U) ? 500 : 0) == PLSR_RESULT_QUEUED); } PlsrProcess(); TEST_CHECK(TestPostCommand(20U, 0U, PLSR_CMD_SET_POSITION, INT64_C(0x100000000)) == PLSR_RESULT_QUEUED); PlsrProcess(); 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); /* HSD只能保存32位位置;发生发布溢出后,旧的合法HSD值不得在 * 重启时冒充当前可信坐标。 */ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceGetRestoredHsdPositionValid() == 0U); TEST_CHECK(PlsrInit() == PLSR_RESULT_OK); status = TestGetStatus(0U); TEST_CHECK(status.positionValid == 0U); TEST_CHECK(status.logicalPosition == 500); /* 6. 清零位置重新建立有效坐标,并解除兼容发布溢出锁存。 */ TEST_CHECK(TestPostCommand(21U, 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); } static void TestMultiAxisBusyCheckpoint(void) { /* 轴0结束时轴1仍在运动,持久化lastBusy必须保持1。 */ TestReset(); TEST_CHECK(TestPostStart(100U, 0U, PLSR_OUTPUT_PULSE_DIR, 4U, 1U) == PLSR_RESULT_QUEUED); TEST_CHECK(TestPostStart(101U, 1U, PLSR_OUTPUT_PULSE_DIR, 5U, 1U) == PLSR_RESULT_QUEUED); PlsrProcess(); TEST_CHECK(PlsrPostEvent(0U, PLSR_EVENT_JOB_COMPLETE) == PLSR_RESULT_OK); PlsrProcess(); TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK); TEST_CHECK(PlcDeviceGetRestoredHsdLastBusy() != 0U); } int main(void) { TestResourceManager(); TestStateMachineAndCommands(); TestLimitWaitAndPairs(); TestQueueAndInputValidation(); TestPositionCheckpointing(); TestMultiAxisBusyCheckpoint(); (void)printf("PASS: %u PLSR core checks\n", TestCount); return EXIT_SUCCESS; }