| @@ -1281,6 +1281,9 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_core.h</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Inc\plsr_job.h</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name> | |||
| </file> | |||
| @@ -1290,6 +1293,9 @@ | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_resource.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_job.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name> | |||
| </file> | |||
| @@ -1,6 +1,7 @@ | |||
| #ifndef PLSR_CORE_H | |||
| #define PLSR_CORE_H | |||
| #include "plsr_job.h" | |||
| #include "plsr_types.h" | |||
| #include <stdint.h> | |||
| @@ -13,9 +14,12 @@ void PlsrTask(void *argument); | |||
| void PlsrProcess(void); | |||
| PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request); | |||
| PLSR_RESULT PlsrPostCall(const PLSR_CALL *call); | |||
| PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command); | |||
| PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask); | |||
| PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status); | |||
| PLSR_RESULT PlsrGetLastParseDetail(uint8_t axis, | |||
| PLSR_PARSE_DETAIL *detail); | |||
| PLSR_RESULT PlsrStateTransition(uint8_t axis, | |||
| PLSR_STATE target, | |||
| @@ -0,0 +1,198 @@ | |||
| #ifndef PLSR_JOB_H | |||
| #define PLSR_JOB_H | |||
| #include "plsr_types.h" | |||
| #include <stdint.h> | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #endif | |||
| #define PLSR_MAX_SEGMENTS (100U) | |||
| #define PLSR_OUTPUT_MODE_FROM_SFD (0xFFU) | |||
| #define PLSR_FREQUENCY_MIN_HZ (1UL) | |||
| #define PLSR_FREQUENCY_MAX_HZ (100000UL) | |||
| typedef enum | |||
| { | |||
| PLSR_DEVICE_D = 0, | |||
| PLSR_DEVICE_HD, | |||
| PLSR_DEVICE_FD, | |||
| PLSR_DEVICE_X, | |||
| PLSR_DEVICE_M, | |||
| PLSR_DEVICE_HM | |||
| } PLSR_DEVICE_TYPE; | |||
| typedef uint8_t (*PLSR_VALIDATE_WORDS_FN)(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t firstAddress, | |||
| uint32_t wordCount); | |||
| typedef uint8_t (*PLSR_READ_WORD_FN)(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t address, | |||
| uint16_t *value); | |||
| typedef uint8_t (*PLSR_READ_BIT_FN)(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t address, | |||
| uint8_t *value); | |||
| typedef struct | |||
| { | |||
| void *context; | |||
| PLSR_VALIDATE_WORDS_FN validateWords; | |||
| PLSR_READ_WORD_FN readWord; | |||
| PLSR_READ_BIT_FN readBit; | |||
| } PLSR_DATA_SOURCE; | |||
| typedef struct | |||
| { | |||
| PLSR_DEVICE_TYPE device; | |||
| uint32_t address; | |||
| } PLSR_DATA_REF; | |||
| typedef enum | |||
| { | |||
| PLSR_OPERAND_CONSTANT = 0, | |||
| PLSR_OPERAND_DATA | |||
| } PLSR_OPERAND_TYPE; | |||
| typedef struct | |||
| { | |||
| PLSR_OPERAND_TYPE type; | |||
| PLSR_DATA_REF data; | |||
| int32_t constant; | |||
| } PLSR_VALUE_OPERAND; | |||
| typedef struct | |||
| { | |||
| uint32_t sequence; | |||
| /* The source context must remain valid while the queued/running job exists. */ | |||
| PLSR_DATA_SOURCE source; | |||
| PLSR_DATA_REF s0; | |||
| PLSR_DATA_REF s1; | |||
| PLSR_VALUE_OPERAND s2; | |||
| uint8_t dAxis; | |||
| uint8_t outputModeOverride; | |||
| } PLSR_CALL; | |||
| typedef enum | |||
| { | |||
| PLSR_WAIT_PULSE_COMPLETE = 0, | |||
| PLSR_WAIT_TIME, | |||
| PLSR_WAIT_SIGNAL, | |||
| PLSR_WAIT_ACT_TIME, | |||
| PLSR_WAIT_EXT, | |||
| PLSR_WAIT_EXT_OR_COMPLETE | |||
| } PLSR_WAIT_CONDITION; | |||
| typedef enum | |||
| { | |||
| PLSR_VALUE_CONSTANT = 0, | |||
| PLSR_VALUE_D, | |||
| PLSR_VALUE_HD, | |||
| PLSR_VALUE_FD, | |||
| PLSR_VALUE_X, | |||
| PLSR_VALUE_M, | |||
| PLSR_VALUE_HM | |||
| } PLSR_VALUE_SOURCE; | |||
| #define PLSR_SEGMENT_FLAG_ZERO_PULSE (1U << 0U) | |||
| #define PLSR_SEGMENT_FLAG_DEFAULT_SPEED (1U << 1U) | |||
| #define PLSR_SEGMENT_FLAG_SPEED_CLAMPED (1U << 2U) | |||
| #define PLSR_SEGMENT_FLAG_SELF_LOOP (1U << 3U) | |||
| typedef struct | |||
| { | |||
| uint32_t targetFrequency; | |||
| int32_t pulseOrTarget; | |||
| int32_t waitValueOrAddress; | |||
| int32_t jumpValueOrAddress; | |||
| uint8_t waitCondition; | |||
| uint8_t waitSource; | |||
| uint8_t jumpSource; | |||
| uint8_t flags; | |||
| } PLSR_SEGMENT_SNAPSHOT; | |||
| typedef struct | |||
| { | |||
| uint32_t defaultSpeed; | |||
| uint32_t maximumSpeed; | |||
| uint32_t startSpeed; | |||
| uint32_t stopSpeed; | |||
| uint32_t zrnHighSpeed; | |||
| uint32_t zrnCrawlSpeed; | |||
| uint16_t accelerationMs; | |||
| uint16_t decelerationMs; | |||
| uint16_t gapAccelerationMs; | |||
| uint16_t directionDelayUs; | |||
| uint8_t curveMode; | |||
| uint8_t follow; | |||
| uint8_t feedforwardPercent; | |||
| uint8_t refreshCode; | |||
| } PLSR_S2_SNAPSHOT; | |||
| typedef struct | |||
| { | |||
| PLSR_DATA_SOURCE source; | |||
| PLSR_DATA_REF s0; | |||
| PLSR_DATA_REF s1; | |||
| PLSR_S2_SNAPSHOT s2; | |||
| PLSR_SEGMENT_SNAPSHOT segments[PLSR_MAX_SEGMENTS]; | |||
| uint32_t timerClockHz; | |||
| uint32_t pairedTimerClockHz; | |||
| uint16_t segmentCount; | |||
| uint16_t startSegment; | |||
| uint8_t dAxis; | |||
| uint8_t s2Set; | |||
| uint8_t positioningMode; | |||
| uint8_t outputMode; | |||
| uint8_t directionPoint; | |||
| uint8_t directionActiveHigh; | |||
| uint8_t initialDirectionPositive; | |||
| uint8_t speedClamped; | |||
| uint8_t hasSelfLoop; | |||
| } PLSR_JOB_SNAPSHOT; | |||
| typedef enum | |||
| { | |||
| PLSR_PARSE_BLOCK_NONE = 0, | |||
| PLSR_PARSE_BLOCK_S0, | |||
| PLSR_PARSE_BLOCK_S1, | |||
| PLSR_PARSE_BLOCK_S2, | |||
| PLSR_PARSE_BLOCK_OUTPUT, | |||
| PLSR_PARSE_BLOCK_POSITION | |||
| } PLSR_PARSE_BLOCK; | |||
| typedef struct | |||
| { | |||
| PLSR_RESULT result; | |||
| PLSR_PARSE_BLOCK block; | |||
| uint32_t address; | |||
| int32_t value; | |||
| uint16_t segment; | |||
| } PLSR_PARSE_DETAIL; | |||
| typedef struct | |||
| { | |||
| int64_t logicalPosition; | |||
| uint8_t positionValid; | |||
| } PLSR_PARSE_CONTEXT; | |||
| PLSR_RESULT PlsrBuildJobSnapshot(const PLSR_CALL *call, | |||
| const PLSR_PARSE_CONTEXT *context, | |||
| PLSR_JOB_SNAPSHOT *snapshot, | |||
| PLSR_PARSE_DETAIL *detail); | |||
| PLSR_RESULT PlsrResolveLiveFrequency(const PLSR_JOB_SNAPSHOT *snapshot, | |||
| uint16_t segment, | |||
| uint32_t *frequency, | |||
| uint8_t *clamped); | |||
| PLSR_RESULT PlsrCalculateTimerDivider(uint32_t timerClockHz, | |||
| uint32_t frequencyHz, | |||
| uint16_t *psc, | |||
| uint16_t *arr); | |||
| #ifdef __cplusplus | |||
| } | |||
| #endif | |||
| #endif /* PLSR_JOB_H */ | |||
| @@ -60,7 +60,21 @@ typedef enum | |||
| PLSR_RESULT_BUSY, | |||
| PLSR_RESULT_PERSISTENCE_ERROR, | |||
| PLSR_RESULT_NOT_SUPPORTED, | |||
| PLSR_RESULT_INTERNAL_ERROR | |||
| PLSR_RESULT_INTERNAL_ERROR, | |||
| PLSR_RESULT_DATA_ACCESS, | |||
| PLSR_RESULT_ADDRESS_OVERFLOW, | |||
| PLSR_RESULT_BLOCK_OVERLAP, | |||
| PLSR_RESULT_SEGMENT_OVERFLOW, | |||
| PLSR_RESULT_RESERVED_NOT_ZERO, | |||
| PLSR_RESULT_INVALID_POSITION_MODE, | |||
| PLSR_RESULT_INVALID_S2, | |||
| PLSR_RESULT_INVALID_FREQUENCY, | |||
| PLSR_RESULT_INVALID_WAIT, | |||
| PLSR_RESULT_INVALID_JUMP, | |||
| PLSR_RESULT_PATH_CYCLE, | |||
| PLSR_RESULT_POSITION_INVALID, | |||
| PLSR_RESULT_POSITION_OVERFLOW, | |||
| PLSR_RESULT_DIVIDER_UNREPRESENTABLE | |||
| } PLSR_RESULT; | |||
| typedef enum | |||
| @@ -159,6 +173,12 @@ typedef struct | |||
| uint8_t directionPositive; | |||
| uint8_t highResourceMask; | |||
| uint8_t directionPoint; | |||
| uint8_t positionValid; | |||
| uint8_t jobValid; | |||
| uint8_t s2Set; | |||
| uint8_t speedClamped; | |||
| uint16_t segmentCount; | |||
| uint16_t startSegment; | |||
| } PLSR_STATUS; | |||
| #ifdef __cplusplus | |||
| @@ -17,6 +17,8 @@ typedef struct | |||
| 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; | |||
| @@ -27,14 +29,18 @@ typedef struct | |||
| uint8_t done; | |||
| uint8_t directionPositive; | |||
| uint8_t immediateStopPending; | |||
| uint8_t positionValid; | |||
| uint8_t jobValid; | |||
| } 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; | |||
| @@ -42,6 +48,7 @@ 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 uint32_t PlsrCoreEnterCritical(void) | |||
| { | |||
| @@ -240,7 +247,8 @@ static uint8_t PlsrCommandPriority(PLSR_COMMAND_OPCODE opcode) | |||
| } | |||
| static PLSR_RESULT PlsrQueueCommand(const PLSR_COMMAND *command, | |||
| const PLSR_START_REQUEST *start) | |||
| const PLSR_START_REQUEST *start, | |||
| const PLSR_CALL *call) | |||
| { | |||
| uint32_t interruptState; | |||
| uint8_t freeSlot = PLSR_COMMAND_QUEUE_DEPTH; | |||
| @@ -292,6 +300,18 @@ static PLSR_RESULT PlsrQueueCommand(const PLSR_COMMAND *command, | |||
| 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); | |||
| @@ -361,7 +381,30 @@ PLSR_RESULT PlsrPostStart(const PLSR_START_REQUEST *request) | |||
| command.axis = request->axis; | |||
| command.opcode = PLSR_CMD_START; | |||
| command.argument = 0; | |||
| return PlsrQueueCommand(&command, request); | |||
| return PlsrQueueCommand(&command, request, NULL); | |||
| } | |||
| 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) | |||
| @@ -383,7 +426,7 @@ PLSR_RESULT PlsrPostCommand(const PLSR_COMMAND *command) | |||
| { | |||
| return PLSR_RESULT_INVALID_STATE; | |||
| } | |||
| return PlsrQueueCommand(command, NULL); | |||
| return PlsrQueueCommand(command, NULL, NULL); | |||
| } | |||
| PLSR_RESULT PlsrPostEvent(uint8_t axis, uint32_t eventMask) | |||
| @@ -464,6 +507,7 @@ static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, | |||
| axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | |||
| axisObject->immediateStopPending = 0U; | |||
| axisObject->done = 0U; | |||
| axisObject->jobValid = 0U; | |||
| result = PlsrStateTransition(start->axis, | |||
| PLSR_STATE_ACCEL, | |||
| PLSR_TRANSITION_START); | |||
| @@ -474,6 +518,67 @@ static PLSR_RESULT PlsrStartAxis(PLSR_AXIS *axisObject, | |||
| 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; | |||
| } | |||
| parseContext.logicalPosition = axisObject->logicalPosition; | |||
| parseContext.positionValid = axisObject->positionValid; | |||
| result = PlsrBuildJobSnapshot(call, | |||
| &parseContext, | |||
| &PlsrJobScratch, | |||
| &axisObject->parseDetail); | |||
| if (result != PLSR_RESULT_OK) | |||
| { | |||
| 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; | |||
| return result; | |||
| } | |||
| axisObject->job = PlsrJobScratch; | |||
| axisObject->jobValid = 1U; | |||
| axisObject->outputMode = (PLSR_OUTPUT_MODE)axisObject->job.outputMode; | |||
| axisObject->directionPositive = axisObject->job.initialDirectionPositive; | |||
| axisObject->error = PLSR_ERROR_NONE; | |||
| axisObject->stopReason = PLSR_STOP_REASON_NONE; | |||
| axisObject->pendingTerminal = PLSR_STATE_UNINITIALIZED; | |||
| axisObject->immediateStopPending = 0U; | |||
| axisObject->done = 0U; | |||
| result = PlsrStateTransition(call->dAxis, | |||
| PLSR_STATE_ACCEL, | |||
| PLSR_TRANSITION_START); | |||
| if (result != PLSR_RESULT_OK) | |||
| { | |||
| axisObject->jobValid = 0U; | |||
| PlsrResourceRelease(&axisObject->lease); | |||
| } | |||
| return result; | |||
| } | |||
| static PLSR_RESULT PlsrStopImmediate(uint8_t axis) | |||
| { | |||
| PLSR_AXIS *axisObject = &PlsrAxes[axis]; | |||
| @@ -590,9 +695,16 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) | |||
| switch (slot->command.opcode) | |||
| { | |||
| case PLSR_CMD_START: | |||
| result = (slot->hasStart != 0U) | |||
| ? PlsrStartAxis(axisObject, &slot->start) | |||
| : PLSR_RESULT_INVALID_ARGUMENT; | |||
| 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: | |||
| @@ -630,6 +742,7 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) | |||
| else | |||
| { | |||
| axisObject->logicalPosition = slot->command.argument; | |||
| axisObject->positionValid = 1U; | |||
| (void)PlcDevicePublishHsdPair( | |||
| (uint16_t)(slot->command.axis | |||
| * PLSR_HSD_RUNTIME_AXIS_COUNT), | |||
| @@ -646,6 +759,7 @@ static PLSR_RESULT PlsrExecuteCommand(const PLSR_COMMAND_SLOT *slot) | |||
| else | |||
| { | |||
| axisObject->logicalPosition = 0; | |||
| axisObject->positionValid = 1U; | |||
| (void)PlcDevicePublishHsdPair( | |||
| (uint16_t)(slot->command.axis | |||
| * PLSR_HSD_RUNTIME_AXIS_COUNT), | |||
| @@ -888,6 +1002,7 @@ static void PlsrProcessNormalEvents(uint8_t axis, uint32_t events) | |||
| PLSR_RESULT PlsrInit(void) | |||
| { | |||
| int64_t restoredPosition; | |||
| uint8_t axis; | |||
| (void)memset(PlsrAxes, 0, sizeof(PlsrAxes)); | |||
| @@ -901,6 +1016,15 @@ PLSR_RESULT PlsrInit(void) | |||
| PlsrAxes[axis].state = PLSR_STATE_UNINITIALIZED; | |||
| PlsrAxes[axis].pendingTerminal = PLSR_STATE_UNINITIALIZED; | |||
| PlsrAxes[axis].lease.directionPoint = PLSR_DIRECTION_POINT_NONE; | |||
| if ((PlcDeviceGetLastHsdLoadResult() == PLSR_PERSISTENCE_OK) | |||
| && (PlcDeviceReadHsdPair( | |||
| (uint16_t)(axis * PLSR_HSD_RUNTIME_AXIS_COUNT), | |||
| &restoredPosition) | |||
| == PLC_DEVICE_OK)) | |||
| { | |||
| PlsrAxes[axis].logicalPosition = restoredPosition; | |||
| PlsrAxes[axis].positionValid = 1U; | |||
| } | |||
| if (PlsrStateTransition(axis, | |||
| PLSR_STATE_IDLE, | |||
| PLSR_TRANSITION_INITIALIZED) | |||
| @@ -1001,6 +1125,37 @@ PLSR_RESULT PlsrGetStatus(uint8_t axis, PLSR_STATUS *status) | |||
| status->directionPoint = (axisObject->lease.valid != 0U) | |||
| ? axisObject->lease.directionPoint | |||
| : PLSR_DIRECTION_POINT_NONE; | |||
| status->positionValid = axisObject->positionValid; | |||
| status->jobValid = axisObject->jobValid; | |||
| 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; | |||
| 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; | |||
| } | |||
| @@ -28,9 +28,21 @@ $tests = @( | |||
| "$workspacePath\PLSR\Src\plc_device.c" | |||
| "$workspacePath\PLSR\Src\plsr_persistence.c" | |||
| "$workspacePath\PLSR\Src\plsr_resource.c" | |||
| "$workspacePath\PLSR\Src\plsr_job.c" | |||
| "$workspacePath\PLSR\Src\plsr_core.c" | |||
| "$workspacePath\PLSR\Test\test_plsr_core.c" | |||
| ) | |||
| }, | |||
| @{ | |||
| Name = 'test_plsr_job' | |||
| Sources = @( | |||
| "$workspacePath\PLSR\Src\plc_device.c" | |||
| "$workspacePath\PLSR\Src\plsr_persistence.c" | |||
| "$workspacePath\PLSR\Src\plsr_resource.c" | |||
| "$workspacePath\PLSR\Src\plsr_job.c" | |||
| "$workspacePath\PLSR\Src\plsr_core.c" | |||
| "$workspacePath\PLSR\Test\test_plsr_job.c" | |||
| ) | |||
| } | |||
| ) | |||
| @@ -0,0 +1,410 @@ | |||
| #include "plc_device.h" | |||
| #include "plsr_core.h" | |||
| #include "plsr_job.h" | |||
| #include "plsr_persistence.h" | |||
| #include <stdio.h> | |||
| #include <string.h> | |||
| #define TEST_WORD_CAPACITY (3000U) | |||
| #define TEST_BIT_CAPACITY (128U) | |||
| typedef struct | |||
| { | |||
| uint16_t words[3][TEST_WORD_CAPACITY]; | |||
| uint8_t bits[3][TEST_BIT_CAPACITY]; | |||
| } TEST_MEMORY; | |||
| static int TestFailures; | |||
| static int TestChecks; | |||
| #define CHECK(condition) \ | |||
| do \ | |||
| { \ | |||
| TestChecks++; \ | |||
| if (!(condition)) \ | |||
| { \ | |||
| TestFailures++; \ | |||
| (void)printf("FAIL line %d: %s\n", __LINE__, #condition); \ | |||
| } \ | |||
| } while (0) | |||
| static uint8_t TestValidateWords(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t firstAddress, | |||
| uint32_t wordCount) | |||
| { | |||
| (void)context; | |||
| if ((device > PLSR_DEVICE_FD) || (wordCount == 0UL)) | |||
| { | |||
| return 0U; | |||
| } | |||
| return (((uint64_t)firstAddress + wordCount) <= TEST_WORD_CAPACITY) | |||
| ? 1U | |||
| : 0U; | |||
| } | |||
| static uint8_t TestReadWord(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t address, | |||
| uint16_t *value) | |||
| { | |||
| TEST_MEMORY *memory = (TEST_MEMORY *)context; | |||
| if ((memory == NULL) || (value == NULL) || (device > PLSR_DEVICE_FD) | |||
| || (address >= TEST_WORD_CAPACITY)) | |||
| { | |||
| return 0U; | |||
| } | |||
| *value = memory->words[device][address]; | |||
| return 1U; | |||
| } | |||
| static uint8_t TestReadBit(void *context, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t address, | |||
| uint8_t *value) | |||
| { | |||
| TEST_MEMORY *memory = (TEST_MEMORY *)context; | |||
| uint8_t index; | |||
| if ((memory == NULL) || (value == NULL) || (device < PLSR_DEVICE_X) | |||
| || (device > PLSR_DEVICE_HM) || (address >= TEST_BIT_CAPACITY)) | |||
| { | |||
| return 0U; | |||
| } | |||
| index = (uint8_t)(device - PLSR_DEVICE_X); | |||
| *value = memory->bits[index][address]; | |||
| return 1U; | |||
| } | |||
| static void TestWriteDword(TEST_MEMORY *memory, | |||
| PLSR_DEVICE_TYPE device, | |||
| uint32_t address, | |||
| int32_t value) | |||
| { | |||
| uint32_t raw = (uint32_t)value; | |||
| memory->words[device][address] = (uint16_t)(raw & 0xFFFFUL); | |||
| memory->words[device][address + 1UL] = (uint16_t)(raw >> 16U); | |||
| } | |||
| static void TestWriteSfdDword(uint16_t address, uint32_t value) | |||
| { | |||
| CHECK(PlcDeviceWriteSfd(address, (int32_t)(value & 0xFFFFUL)) | |||
| == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd((uint16_t)(address + 1U), | |||
| (int32_t)(value >> 16U)) | |||
| == PLC_DEVICE_OK); | |||
| } | |||
| static void TestConfigureAxis0K1(void) | |||
| { | |||
| CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(907U, 10) == PLC_DEVICE_OK); | |||
| TestWriteSfdDword(950U, 1000UL); | |||
| CHECK(PlcDeviceWriteSfd(952U, 100) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(953U, 120) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(954U, 10) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(955U, 0) == PLC_DEVICE_OK); | |||
| TestWriteSfdDword(956U, 100000UL); | |||
| TestWriteSfdDword(958U, 120000UL); | |||
| TestWriteSfdDword(960U, 500UL); | |||
| CHECK(PlcDeviceWriteSfd(962U, 50) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(963U, 20) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(964U, 0) == PLC_DEVICE_OK); | |||
| TestWriteSfdDword(966U, 2000UL); | |||
| TestWriteSfdDword(968U, 200UL); | |||
| } | |||
| static void TestBuildValidBlocks(TEST_MEMORY *memory) | |||
| { | |||
| (void)memset(memory, 0, sizeof(*memory)); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 100U, 2); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 110U, 150000); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 112U, 100); | |||
| memory->words[PLSR_DEVICE_D][114U] = 0U; | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 115U, 0); | |||
| memory->words[PLSR_DEVICE_D][117U] = 0U; | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 118U, 0); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 120U, 0); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 122U, -50); | |||
| memory->words[PLSR_DEVICE_D][124U] = 0x0100U; | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 125U, 0); | |||
| memory->words[PLSR_DEVICE_D][127U] = 0U; | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 128U, 2); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 200U, 0); | |||
| TestWriteDword(memory, PLSR_DEVICE_D, 202U, 0); | |||
| TestWriteDword(memory, PLSR_DEVICE_HD, 10U, 1); | |||
| } | |||
| static PLSR_CALL TestMakeCall(TEST_MEMORY *memory) | |||
| { | |||
| PLSR_CALL call; | |||
| (void)memset(&call, 0, sizeof(call)); | |||
| call.sequence = 77UL; | |||
| call.source.context = memory; | |||
| call.source.validateWords = TestValidateWords; | |||
| call.source.readWord = TestReadWord; | |||
| call.source.readBit = TestReadBit; | |||
| call.s0.device = PLSR_DEVICE_D; | |||
| call.s0.address = 100UL; | |||
| call.s1.device = PLSR_DEVICE_D; | |||
| call.s1.address = 200UL; | |||
| call.s2.type = PLSR_OPERAND_CONSTANT; | |||
| call.s2.constant = 1; | |||
| call.dAxis = 0U; | |||
| call.outputModeOverride = PLSR_OUTPUT_MODE_FROM_SFD; | |||
| return call; | |||
| } | |||
| static void TestValidSnapshotAndLiveFrequency(void) | |||
| { | |||
| TEST_MEMORY memory; | |||
| PLSR_CALL call; | |||
| PLSR_PARSE_CONTEXT context; | |||
| PLSR_JOB_SNAPSHOT snapshot; | |||
| PLSR_PARSE_DETAIL detail; | |||
| uint32_t frequency; | |||
| uint8_t clamped; | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| context.logicalPosition = 0; | |||
| context.positionValid = 0U; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(detail.result == PLSR_RESULT_OK); | |||
| CHECK(snapshot.segmentCount == 2U); | |||
| CHECK(snapshot.startSegment == 1U); | |||
| CHECK(snapshot.s2Set == 1U); | |||
| CHECK(snapshot.outputMode == PLSR_OUTPUT_PULSE_DIR); | |||
| CHECK(snapshot.directionPoint == 4U); | |||
| CHECK(snapshot.s2.defaultSpeed == 1000UL); | |||
| CHECK(snapshot.s2.maximumSpeed == 100000UL); | |||
| CHECK(snapshot.s2.startSpeed == 100000UL); | |||
| CHECK(snapshot.speedClamped == 1U); | |||
| CHECK(snapshot.segments[0].targetFrequency == 100000UL); | |||
| CHECK((snapshot.segments[0].flags & PLSR_SEGMENT_FLAG_SPEED_CLAMPED) | |||
| != 0U); | |||
| CHECK(snapshot.segments[1].targetFrequency == 1000UL); | |||
| CHECK((snapshot.segments[1].flags & PLSR_SEGMENT_FLAG_DEFAULT_SPEED) | |||
| != 0U); | |||
| CHECK((snapshot.segments[1].flags & PLSR_SEGMENT_FLAG_SELF_LOOP) != 0U); | |||
| CHECK(snapshot.hasSelfLoop == 1U); | |||
| CHECK(snapshot.initialDirectionPositive == 1U); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 112U, 9999); | |||
| CHECK(snapshot.segments[0].pulseOrTarget == 100); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 2500); | |||
| CHECK(PlsrResolveLiveFrequency(&snapshot, 1U, &frequency, &clamped) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(frequency == 2500UL); | |||
| CHECK(clamped == 0U); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 110U, 200000); | |||
| CHECK(PlsrResolveLiveFrequency(&snapshot, 1U, &frequency, &clamped) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(frequency == 100000UL); | |||
| CHECK(clamped == 1U); | |||
| call.s2.type = PLSR_OPERAND_DATA; | |||
| call.s2.data.device = PLSR_DEVICE_HD; | |||
| call.s2.data.address = 10UL; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(snapshot.s2Set == 1U); | |||
| } | |||
| static void TestValidationFailures(void) | |||
| { | |||
| TEST_MEMORY memory; | |||
| PLSR_CALL call; | |||
| PLSR_PARSE_CONTEXT context = {0, 0U}; | |||
| PLSR_JOB_SNAPSHOT snapshot; | |||
| PLSR_PARSE_DETAIL detail; | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| call.s1.address = 120UL; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_BLOCK_OVERLAP); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| memory.words[PLSR_DEVICE_D][103U] = 1U; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_RESERVED_NOT_ZERO); | |||
| CHECK(detail.address == 103UL); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 100U, 101); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_SEGMENT_OVERFLOW); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 110U, -1); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_FREQUENCY); | |||
| CHECK(detail.segment == 1U); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| memory.words[PLSR_DEVICE_D][124U] = 0x0405U; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_WAIT); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 128U, 3); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_JUMP); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 118U, 2); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 128U, 1); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_PATH_CYCLE); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 200U, 1); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_POSITION_INVALID); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| call.s0.address = UINT32_MAX - 5UL; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_ADDRESS_OVERFLOW); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| call.s2.constant = 5; | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_S2); | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| CHECK(PlcDeviceWriteSfd(962U, 356) == PLC_DEVICE_OK); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_S2); | |||
| CHECK(PlcDeviceWriteSfd(962U, 50) == PLC_DEVICE_OK); | |||
| CHECK(PlcDeviceWriteSfd(964U, 1) == PLC_DEVICE_OK); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_INVALID_S2); | |||
| CHECK(PlcDeviceWriteSfd(964U, 0) == PLC_DEVICE_OK); | |||
| } | |||
| static void TestDynamicReferences(void) | |||
| { | |||
| TEST_MEMORY memory; | |||
| PLSR_CALL call; | |||
| PLSR_PARSE_CONTEXT context = {0, 0U}; | |||
| PLSR_JOB_SNAPSHOT snapshot; | |||
| PLSR_PARSE_DETAIL detail; | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| memory.words[PLSR_DEVICE_D][124U] = 0x0101U; | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 125U, 300); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 300U, 25); | |||
| memory.words[PLSR_DEVICE_D][127U] = 1U; | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 128U, 302); | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 302U, 2); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(snapshot.segments[1].waitSource == PLSR_VALUE_D); | |||
| CHECK(snapshot.segments[1].waitValueOrAddress == 300); | |||
| CHECK(snapshot.segments[1].jumpSource == PLSR_VALUE_D); | |||
| CHECK(snapshot.segments[1].jumpValueOrAddress == 302); | |||
| memory.words[PLSR_DEVICE_D][124U] = 0x0404U; | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 125U, 7); | |||
| memory.bits[0][7U] = 1U; | |||
| memory.words[PLSR_DEVICE_D][127U] = 0U; | |||
| TestWriteDword(&memory, PLSR_DEVICE_D, 128U, 2); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_OK); | |||
| } | |||
| static void TestOutputAndDivider(void) | |||
| { | |||
| TEST_MEMORY memory; | |||
| PLSR_CALL call; | |||
| PLSR_PARSE_CONTEXT context = {0, 0U}; | |||
| PLSR_JOB_SNAPSHOT snapshot; | |||
| PLSR_PARSE_DETAIL detail; | |||
| uint16_t psc; | |||
| uint16_t arr; | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| CHECK(PlcDeviceWriteSfd(900U, (1U << 13U)) == PLC_DEVICE_OK); | |||
| CHECK(PlsrBuildJobSnapshot(&call, &context, &snapshot, &detail) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(snapshot.outputMode == PLSR_OUTPUT_AB); | |||
| CHECK(snapshot.pairedTimerClockHz == 84000000UL); | |||
| CHECK(PlsrCalculateTimerDivider(168000000UL, 1UL, &psc, &arr) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(PlsrCalculateTimerDivider(84000000UL, 100000UL, &psc, &arr) | |||
| == PLSR_RESULT_OK); | |||
| CHECK(PlsrCalculateTimerDivider(84000000UL, 0UL, &psc, &arr) | |||
| == PLSR_RESULT_INVALID_ARGUMENT); | |||
| CHECK(PlsrCalculateTimerDivider(1UL, 100000UL, &psc, &arr) | |||
| == PLSR_RESULT_DIVIDER_UNREPRESENTABLE); | |||
| CHECK(PlcDeviceWriteSfd(900U, 0) == PLC_DEVICE_OK); | |||
| } | |||
| static void TestCoreSubmission(void) | |||
| { | |||
| TEST_MEMORY memory; | |||
| PLSR_CALL call; | |||
| PLSR_STATUS status; | |||
| PLSR_PARSE_DETAIL detail; | |||
| TestBuildValidBlocks(&memory); | |||
| call = TestMakeCall(&memory); | |||
| call.sequence = 500UL; | |||
| CHECK(PlsrInit() == PLSR_RESULT_OK); | |||
| CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); | |||
| PlsrProcess(); | |||
| CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); | |||
| CHECK(status.lastCommandResult == PLSR_RESULT_OK); | |||
| CHECK(status.state == PLSR_STATE_ACCEL); | |||
| CHECK(status.jobValid == 1U); | |||
| CHECK(status.segmentCount == 2U); | |||
| CHECK(status.startSegment == 1U); | |||
| CHECK(status.s2Set == 1U); | |||
| CHECK(status.speedClamped == 1U); | |||
| CHECK(PlsrGetLastParseDetail(0U, &detail) == PLSR_RESULT_OK); | |||
| CHECK(detail.result == PLSR_RESULT_OK); | |||
| } | |||
| int main(void) | |||
| { | |||
| PlsrPersistenceTestResetStorage(); | |||
| CHECK(PlcDeviceInit() == PLC_DEVICE_OK); | |||
| TestConfigureAxis0K1(); | |||
| TestValidSnapshotAndLiveFrequency(); | |||
| TestValidationFailures(); | |||
| TestDynamicReferences(); | |||
| TestOutputAndDivider(); | |||
| TestCoreSubmission(); | |||
| if (TestFailures != 0) | |||
| { | |||
| (void)printf("FAIL: %d of %d PLSR job checks failed\n", | |||
| TestFailures, | |||
| TestChecks); | |||
| return 1; | |||
| } | |||
| (void)printf("PASS: %d PLSR job checks\n", TestChecks); | |||
| return 0; | |||
| } | |||