| @@ -62,6 +62,8 @@ | |||||
| #define PLSR_PROFILE_STARTUP_BUDGET (400U) | #define PLSR_PROFILE_STARTUP_BUDGET (400U) | ||||
| #define PLSR_PROFILE_REFILL_TARGET (800U) | #define PLSR_PROFILE_REFILL_TARGET (800U) | ||||
| #define PLSR_PROFILE_REFILL_BUDGET (200U) | #define PLSR_PROFILE_REFILL_BUDGET (200U) | ||||
| #define PLSR_TIMED_START_BUILD_BUDGET (32U) | |||||
| #define PLSR_TIMED_START_READY_ITEMS PLSR_PROFILE_STARTUP_BUDGET | |||||
| #define PLSR_PROFILE_LOW_WATER (100U) | #define PLSR_PROFILE_LOW_WATER (100U) | ||||
| #define PLSR_PROFILE_LOW_WATER_CLEAR (200U) | #define PLSR_PROFILE_LOW_WATER_CLEAR (200U) | ||||
| #define PLSR_UNDERRUN_HOLD_TIME_US (2000UL) | #define PLSR_UNDERRUN_HOLD_TIME_US (2000UL) | ||||
| @@ -141,6 +143,28 @@ typedef struct | |||||
| volatile uint8_t generatorComplete; | volatile uint8_t generatorComplete; | ||||
| } PLSR_PROFILE_QUEUE; | } PLSR_PROFILE_QUEUE; | ||||
| /* A WAIT_TIME boundary is a cold start, but its planner work is predictable | |||||
| while the source segment is still running. Build that next stream in the | |||||
| inactive queue bank and publish it only after the startup horizon is ready. | |||||
| The boundary path then contains no planner loop. */ | |||||
| typedef struct | |||||
| { | |||||
| PLSR_PROFILE_ENTRY firstRun; | |||||
| uint64_t magnitude; | |||||
| uint32_t sourceEpoch; | |||||
| uint32_t queueGeneration; | |||||
| uint16_t generatedItems; | |||||
| uint8_t sourceSegment; | |||||
| uint8_t nextSegment; | |||||
| uint8_t positive; | |||||
| uint8_t directionLevel; | |||||
| uint8_t directionChanged; | |||||
| uint8_t queueBank; | |||||
| volatile uint8_t building; | |||||
| volatile uint8_t valid; | |||||
| volatile uint8_t pendingActivation; | |||||
| } PLSR_TIMED_START; | |||||
| typedef struct | typedef struct | ||||
| { | { | ||||
| uint64_t magnitude; | uint64_t magnitude; | ||||
| @@ -260,6 +284,7 @@ static PLSR_SHORT_PROFILE PlsrShortProfile; | |||||
| static PLSR_PROFILE_QUEUE PlsrProfileQueues[2]; | static PLSR_PROFILE_QUEUE PlsrProfileQueues[2]; | ||||
| static volatile uint8_t PlsrProfileQueueBank; | static volatile uint8_t PlsrProfileQueueBank; | ||||
| #define PlsrProfileQueue (PlsrProfileQueues[PlsrProfileQueueBank]) | #define PlsrProfileQueue (PlsrProfileQueues[PlsrProfileQueueBank]) | ||||
| static PLSR_TIMED_START PlsrTimedStart; | |||||
| static PLSR_HANDOFF_PLAN PlsrHandoffPlan; | static PLSR_HANDOFF_PLAN PlsrHandoffPlan; | ||||
| static volatile uint64_t PlsrCountedObservedPublished; | static volatile uint64_t PlsrCountedObservedPublished; | ||||
| static volatile uint8_t PlsrCountedHandoffStaged; | static volatile uint8_t PlsrCountedHandoffStaged; | ||||
| @@ -359,7 +384,8 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||||
| uint8_t segmentNumber, | uint8_t segmentNumber, | ||||
| uint32_t startFrequencyHz, | uint32_t startFrequencyHz, | ||||
| uint32_t targetFrequencyHz, | uint32_t targetFrequencyHz, | ||||
| uint64_t pulseCount); | |||||
| uint64_t pulseCount, | |||||
| uint8_t currentPositive); | |||||
| static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | ||||
| PLSR_PROFILE_ENTRY *entry); | PLSR_PROFILE_ENTRY *entry); | ||||
| static uint8_t PlsrShortProfileTakeRunLimited( | static uint8_t PlsrShortProfileTakeRunLimited( | ||||
| @@ -372,6 +398,12 @@ static PLSR_PLATFORM_SERVICE_RESULT PlsrReplanPulseDir( | |||||
| uint32_t targetHz, | uint32_t targetHz, | ||||
| uint32_t totalPulses); | uint32_t totalPulses); | ||||
| static void PlsrProfileQueueReset(void); | static void PlsrProfileQueueReset(void); | ||||
| static void PlsrProfileQueueResetBankLocked(uint8_t bank); | |||||
| static void PlsrInvalidateTimedStartLocked(void); | |||||
| static void PlsrServiceTimedStartPreparation(void); | |||||
| static PLSR_PLATFORM_SERVICE_RESULT PlsrTryStartPreparedTimedSegment( | |||||
| uint8_t segmentNumber); | |||||
| static uint8_t PlsrStartPreparedTimedOutput(void); | |||||
| static void PlsrProfileQueueInvalidateGeneration(void); | static void PlsrProfileQueueInvalidateGeneration(void); | ||||
| static uint8_t PlsrProfileQueueBegin( | static uint8_t PlsrProfileQueueBegin( | ||||
| const PLSR_SHORT_PROFILE *producerProfile, | const PLSR_SHORT_PROFILE *producerProfile, | ||||
| @@ -1702,13 +1734,13 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||||
| uint8_t segmentNumber, | uint8_t segmentNumber, | ||||
| uint32_t startFrequencyHz, | uint32_t startFrequencyHz, | ||||
| uint32_t targetFrequencyHz, | uint32_t targetFrequencyHz, | ||||
| uint64_t pulseCount) | |||||
| uint64_t pulseCount, | |||||
| uint8_t currentPositive) | |||||
| { | { | ||||
| PLSR_MOTION_BLOCK block; | PLSR_MOTION_BLOCK block; | ||||
| PLSR_PLANNER_STATUS status; | PLSR_PLANNER_STATUS status; | ||||
| const PLSR_SEGMENT_CONFIG *segment; | const PLSR_SEGMENT_CONFIG *segment; | ||||
| uint8_t nextSegment = 0U; | uint8_t nextSegment = 0U; | ||||
| uint8_t currentPositive; | |||||
| uint8_t nextPositive; | uint8_t nextPositive; | ||||
| (void)memset(profile, 0, sizeof(*profile)); | (void)memset(profile, 0, sizeof(*profile)); | ||||
| @@ -1741,7 +1773,6 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||||
| } | } | ||||
| if (nextSegment != 0U) | if (nextSegment != 0U) | ||||
| { | { | ||||
| currentPositive = PlsrCountPositive; | |||||
| if (PlsrActiveConfig.positionMode == PLSR_POSITION_RELATIVE) | if (PlsrActiveConfig.positionMode == PLSR_POSITION_RELATIVE) | ||||
| { | { | ||||
| currentPositive = (segment->pulses >= 0L) ? 1U : 0U; | currentPositive = (segment->pulses >= 0L) ? 1U : 0U; | ||||
| @@ -1749,11 +1780,12 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||||
| (PlsrActiveConfig.segments[nextSegment - 1U].pulses >= 0L) | (PlsrActiveConfig.segments[nextSegment - 1U].pulses >= 0L) | ||||
| ? 1U : 0U; | ? 1U : 0U; | ||||
| } | } | ||||
| else if ((segmentNumber == PlsrCurrentSegment) | |||||
| && (PlsrPredictNextDirection(nextSegment, | |||||
| &nextPositive) != 0U)) | |||||
| else if ((int64_t)PlsrActiveConfig.segments[nextSegment - 1U].pulses | |||||
| != (int64_t)segment->pulses) | |||||
| { | { | ||||
| /* direction resolved from the live absolute position */ | |||||
| nextPositive = | |||||
| ((int64_t)PlsrActiveConfig.segments[nextSegment - 1U].pulses | |||||
| > (int64_t)segment->pulses) ? 1U : 0U; | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1915,6 +1947,8 @@ static PLSR_PLATFORM_SERVICE_RESULT PlsrReplanPulseDir( | |||||
| PlsrPlatformExitCritical(criticalState); | PlsrPlatformExitCritical(criticalState); | ||||
| return PLSR_PLATFORM_SERVICE_DEFERRED; | return PLSR_PLATFORM_SERVICE_DEFERRED; | ||||
| } | } | ||||
| /* The replacement stream owns the inactive bank from this point. */ | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | PlsrPlatformExitCritical(criticalState); | ||||
| if (totalPulses < committedPulses) | if (totalPulses < committedPulses) | ||||
| @@ -1993,7 +2027,8 @@ static PLSR_PLATFORM_SERVICE_RESULT PlsrReplanPulseDir( | |||||
| if ((tailFrequencyHz == 0UL) | if ((tailFrequencyHz == 0UL) | ||||
| || (PlsrPrepareShortProfile(&replacement, segmentNumber, | || (PlsrPrepareShortProfile(&replacement, segmentNumber, | ||||
| tailFrequencyHz, targetHz, | tailFrequencyHz, targetHz, | ||||
| replacementPulses) == 0U)) | |||||
| replacementPulses, | |||||
| PlsrCountPositive) == 0U)) | |||||
| { | { | ||||
| return PLSR_PLATFORM_SERVICE_FAILED; | return PLSR_PLATFORM_SERVICE_FAILED; | ||||
| } | } | ||||
| @@ -2145,25 +2180,40 @@ static void PlsrProfileRecordPlannerStatus( | |||||
| } | } | ||||
| } | } | ||||
| static void PlsrInvalidateTimedStartLocked(void) | |||||
| { | |||||
| PlsrTimedStart.building = 0U; | |||||
| PlsrTimedStart.valid = 0U; | |||||
| PlsrTimedStart.pendingActivation = 0U; | |||||
| } | |||||
| static void PlsrProfileQueueResetBankLocked(uint8_t bank) | |||||
| { | |||||
| PLSR_PROFILE_QUEUE *queue = &PlsrProfileQueues[bank & 1U]; | |||||
| queue->generation++; | |||||
| queue->readIndex = 0U; | |||||
| queue->writeIndex = 0U; | |||||
| queue->active = 0U; | |||||
| queue->generatorComplete = 0U; | |||||
| queue->producerEpoch = 0UL; | |||||
| queue->repeatRemaining = 0UL; | |||||
| queue->underrunDebtPulses = 0UL; | |||||
| queue->producerSegment = 0U; | |||||
| queue->preparedHandoffBank = 0U; | |||||
| queue->haveLastSetting = 0U; | |||||
| queue->producerProfile.active = 0U; | |||||
| } | |||||
| static void PlsrProfileQueueReset(void) | static void PlsrProfileQueueReset(void) | ||||
| { | { | ||||
| uint32_t criticalState = PlsrPlatformEnterCritical(); | uint32_t criticalState = PlsrPlatformEnterCritical(); | ||||
| uint8_t bank; | uint8_t bank; | ||||
| PlsrInvalidateTimedStartLocked(); | |||||
| for (bank = 0U; bank < 2U; bank++) | for (bank = 0U; bank < 2U; bank++) | ||||
| { | { | ||||
| PlsrProfileQueues[bank].generation++; | |||||
| PlsrProfileQueues[bank].readIndex = 0U; | |||||
| PlsrProfileQueues[bank].writeIndex = 0U; | |||||
| PlsrProfileQueues[bank].active = 0U; | |||||
| PlsrProfileQueues[bank].generatorComplete = 0U; | |||||
| PlsrProfileQueues[bank].producerEpoch = 0UL; | |||||
| PlsrProfileQueues[bank].repeatRemaining = 0UL; | |||||
| PlsrProfileQueues[bank].underrunDebtPulses = 0UL; | |||||
| PlsrProfileQueues[bank].producerSegment = 0U; | |||||
| PlsrProfileQueues[bank].preparedHandoffBank = 0U; | |||||
| PlsrProfileQueues[bank].haveLastSetting = 0U; | |||||
| PlsrProfileQueues[bank].producerProfile.active = 0U; | |||||
| PlsrProfileQueueResetBankLocked(bank); | |||||
| } | } | ||||
| PlsrProfileQueueBank = 0U; | PlsrProfileQueueBank = 0U; | ||||
| PlsrProfileQueueLowWaterLatched = 0U; | PlsrProfileQueueLowWaterLatched = 0U; | ||||
| @@ -2273,6 +2323,251 @@ static uint8_t PlsrProfileQueueAppendLocked( | |||||
| return 1U; | return 1U; | ||||
| } | } | ||||
| static void PlsrServiceTimedStartPreparation(void) | |||||
| { | |||||
| PLSR_PROFILE_QUEUE *queue; | |||||
| PLSR_SHORT_PROFILE profile; | |||||
| PLSR_PROFILE_ENTRY entry; | |||||
| PLSR_HANDOFF_PLAN *handoff; | |||||
| const PLSR_SEGMENT_CONFIG *source; | |||||
| uint32_t criticalState; | |||||
| uint32_t sourceEpoch; | |||||
| uint32_t queueGeneration; | |||||
| uint32_t targetFrequencyHz; | |||||
| uint32_t startFrequencyHz; | |||||
| uint64_t remaining; | |||||
| int64_t predictedPosition; | |||||
| int64_t displacement; | |||||
| uint64_t magnitude; | |||||
| int32_t position; | |||||
| uint16_t budget; | |||||
| uint8_t sourceSegment; | |||||
| uint8_t nextSegment; | |||||
| uint8_t positive; | |||||
| uint8_t directionLevel; | |||||
| uint8_t directionChanged; | |||||
| uint8_t queueBank; | |||||
| if ((PlsrActiveConfig.outputMode != PLSR_OUTPUT_PULSE_DIR) | |||||
| || (PlsrStopRequested != 0U) | |||||
| || (PlsrTimedStart.pendingActivation != 0U)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| sourceEpoch = PlsrSegmentEpoch; | |||||
| sourceSegment = PlsrCurrentSegment; | |||||
| if ((sourceSegment == 0U) | |||||
| || (sourceSegment > PlsrActiveConfig.segmentCount) | |||||
| || (PlsrActiveConfig.segments[sourceSegment - 1U].waitType | |||||
| != PLSR_WAIT_TIME)) | |||||
| { | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| if ((PlsrTimedStart.building != 0U) | |||||
| || (PlsrTimedStart.valid != 0U)) | |||||
| { | |||||
| if ((PlsrTimedStart.sourceEpoch != sourceEpoch) | |||||
| || (PlsrTimedStart.sourceSegment != sourceSegment) | |||||
| || (PlsrTimedStart.queueBank == PlsrProfileQueueBank) | |||||
| || (PlsrProfileQueues[PlsrTimedStart.queueBank].generation | |||||
| != PlsrTimedStart.queueGeneration)) | |||||
| { | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| if (PlsrTimedStart.valid != 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| goto fill_timed_start; | |||||
| } | |||||
| } | |||||
| position = PlsrPosition; | |||||
| remaining = PlsrRemainingPulses; | |||||
| positive = PlsrCountPositive; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| if (PlsrGetNextSegment(&nextSegment) == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| source = &PlsrActiveConfig.segments[sourceSegment - 1U]; | |||||
| if (PlsrActiveConfig.positionMode == PLSR_POSITION_ABSOLUTE) | |||||
| { | |||||
| predictedPosition = source->pulses; | |||||
| } | |||||
| else if (positive != 0U) | |||||
| { | |||||
| predictedPosition = (int64_t)position + (int64_t)remaining; | |||||
| } | |||||
| else | |||||
| { | |||||
| predictedPosition = (int64_t)position - (int64_t)remaining; | |||||
| } | |||||
| if ((predictedPosition > (int64_t)INT32_MAX) | |||||
| || (predictedPosition < (int64_t)INT32_MIN)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| displacement = PlsrSegmentDisplacement( | |||||
| nextSegment, (int32_t)predictedPosition); | |||||
| if (displacement == 0) | |||||
| { | |||||
| return; | |||||
| } | |||||
| positive = (displacement > 0) ? 1U : 0U; | |||||
| magnitude = (displacement < 0) ? (uint64_t)(-displacement) | |||||
| : (uint64_t)displacement; | |||||
| if (magnitude > 0xFFFFFFFFULL) | |||||
| { | |||||
| return; | |||||
| } | |||||
| directionLevel = positive; | |||||
| if (PlsrActiveConfig.directionNegativeLogic != 0U) | |||||
| { | |||||
| directionLevel ^= 1U; | |||||
| } | |||||
| directionChanged = ((PlsrLastDirectionValid == 0U) | |||||
| || (PlsrLastDirectionOutput | |||||
| != (uint8_t)PlsrActiveConfig.directionOutput) | |||||
| || (PlsrLastDirectionLevel != directionLevel)) ? 1U : 0U; | |||||
| targetFrequencyHz = | |||||
| PlsrActiveConfig.segments[nextSegment - 1U].frequencyHz; | |||||
| startFrequencyHz = PlsrEffectiveStartFrequency( | |||||
| targetFrequencyHz, 0U, directionChanged, 0UL); | |||||
| if ((PlsrPrepareShortProfile(&profile, nextSegment, | |||||
| startFrequencyHz, targetFrequencyHz, | |||||
| magnitude, positive) == 0U) | |||||
| || (PlsrShortProfileTakeRun(&profile, &entry) == 0U)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| /* Keep the seamless plan for this future segment consistent with its | |||||
| possibly clipped terminal frequency. */ | |||||
| handoff = &PlsrPreparedHandoffPlans[PlsrPreparedHandoffBank] | |||||
| [nextSegment - 1U]; | |||||
| (void)PlsrBuildHandoffPlan(nextSegment, &PlsrActiveConfig, | |||||
| profile.endHz, handoff); | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| if ((PlsrSegmentEpoch != sourceEpoch) | |||||
| || (PlsrCurrentSegment != sourceSegment)) | |||||
| { | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| queueBank = (uint8_t)(PlsrProfileQueueBank ^ 1U); | |||||
| PlsrProfileQueueResetBankLocked(queueBank); | |||||
| queue = &PlsrProfileQueues[queueBank]; | |||||
| queue->producerEpoch = sourceEpoch + 1UL; | |||||
| queue->producerSegment = nextSegment; | |||||
| queue->preparedHandoffBank = PlsrPreparedHandoffBank; | |||||
| queue->repeatRemaining = 0UL; | |||||
| queue->underrunDebtPulses = 0UL; | |||||
| queue->haveLastSetting = 0U; | |||||
| PlsrCopyShortProfile(&queue->producerProfile, &profile); | |||||
| queue->active = 1U; | |||||
| queue->generatorComplete = (profile.active == 0U) ? 1U : 0U; | |||||
| PlsrTimedStart.firstRun = entry; | |||||
| PlsrTimedStart.magnitude = magnitude; | |||||
| PlsrTimedStart.sourceEpoch = sourceEpoch; | |||||
| PlsrTimedStart.queueGeneration = queue->generation; | |||||
| PlsrTimedStart.generatedItems = 0U; | |||||
| PlsrTimedStart.sourceSegment = sourceSegment; | |||||
| PlsrTimedStart.nextSegment = nextSegment; | |||||
| PlsrTimedStart.positive = positive; | |||||
| PlsrTimedStart.directionLevel = directionLevel; | |||||
| PlsrTimedStart.directionChanged = directionChanged; | |||||
| PlsrTimedStart.queueBank = queueBank; | |||||
| PlsrTimedStart.valid = (queue->generatorComplete != 0U) ? 1U : 0U; | |||||
| PlsrTimedStart.building = (PlsrTimedStart.valid == 0U) ? 1U : 0U; | |||||
| PlsrTimedStart.pendingActivation = 0U; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| PlsrProfileRecordPlannerStatus(&profile); | |||||
| fill_timed_start: | |||||
| budget = PLSR_TIMED_START_BUILD_BUDGET; | |||||
| while (budget != 0U) | |||||
| { | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| queueBank = PlsrTimedStart.queueBank; | |||||
| queue = &PlsrProfileQueues[queueBank]; | |||||
| queueGeneration = PlsrTimedStart.queueGeneration; | |||||
| if ((PlsrTimedStart.building == 0U) | |||||
| || (PlsrTimedStart.sourceEpoch != PlsrSegmentEpoch) | |||||
| || (PlsrTimedStart.sourceSegment != PlsrCurrentSegment) | |||||
| || (queueBank == PlsrProfileQueueBank) | |||||
| || (queue->generation != queueGeneration)) | |||||
| { | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| PlsrCopyShortProfile(&profile, &queue->producerProfile); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| if (PlsrShortProfileTakeRun(&profile, &entry) == 0U) | |||||
| { | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| if ((PlsrTimedStart.building != 0U) | |||||
| && (queue->generation == queueGeneration)) | |||||
| { | |||||
| queue->generatorComplete = 1U; | |||||
| PlsrTimedStart.building = 0U; | |||||
| PlsrTimedStart.valid = 1U; | |||||
| } | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| if ((PlsrTimedStart.building == 0U) | |||||
| || (PlsrTimedStart.sourceEpoch != PlsrSegmentEpoch) | |||||
| || (queueBank == PlsrProfileQueueBank) | |||||
| || (queue->generation != queueGeneration)) | |||||
| { | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| if (PlsrProfileQueueAppendLocked(queue, &entry) == 0U) | |||||
| { | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| PlsrCopyShortProfile(&queue->producerProfile, &profile); | |||||
| if (PlsrTimedStart.generatedItems != 0xFFFFU) | |||||
| { | |||||
| PlsrTimedStart.generatedItems++; | |||||
| } | |||||
| if ((profile.active == 0U) | |||||
| || (profile.nextPeriod >= profile.pulseCount)) | |||||
| { | |||||
| queue->generatorComplete = 1U; | |||||
| } | |||||
| if ((PlsrTimedStart.generatedItems | |||||
| >= PLSR_TIMED_START_READY_ITEMS) | |||||
| || (queue->generatorComplete != 0U)) | |||||
| { | |||||
| PlsrTimedStart.building = 0U; | |||||
| PlsrTimedStart.valid = 1U; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return; | |||||
| } | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| budget--; | |||||
| } | |||||
| } | |||||
| #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | ||||
| && !defined(PLSR_HOST_TEST) | && !defined(PLSR_HOST_TEST) | ||||
| static uint32_t PlsrProfileTimingNow(void) | static uint32_t PlsrProfileTimingNow(void) | ||||
| @@ -2646,6 +2941,195 @@ static void PlsrMaybePlanBoundaryRamp(uint32_t expectedEpoch) | |||||
| } | } | ||||
| } | } | ||||
| static uint8_t PlsrStartPreparedTimedOutput(void) | |||||
| { | |||||
| PLSR_PROFILE_QUEUE *queue; | |||||
| PLSR_PROFILE_ENTRY firstRun; | |||||
| uint32_t criticalState; | |||||
| uint32_t firstFrequencyHz; | |||||
| uint32_t secondFrequencyHz; | |||||
| uint32_t actualFrequencyHz; | |||||
| uint32_t queueReadIndex; | |||||
| uint32_t queueWriteIndex; | |||||
| uint8_t queueBank; | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| queueBank = PlsrTimedStart.queueBank; | |||||
| queue = &PlsrProfileQueues[queueBank]; | |||||
| if ((PlsrTimedStart.pendingActivation == 0U) | |||||
| || (PlsrTimedStart.valid == 0U) | |||||
| || (PlsrCurrentSegment != PlsrTimedStart.nextSegment) | |||||
| || (PlsrSegmentEpoch != PlsrTimedStart.sourceEpoch + 1UL) | |||||
| || (queueBank == PlsrProfileQueueBank) | |||||
| || (queue->generation != PlsrTimedStart.queueGeneration) | |||||
| || (queue->producerEpoch != PlsrSegmentEpoch) | |||||
| || (queue->producerSegment != PlsrCurrentSegment)) | |||||
| { | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return 0U; | |||||
| } | |||||
| firstRun = PlsrTimedStart.firstRun; | |||||
| PlsrProfileQueueResetBankLocked(PlsrProfileQueueBank); | |||||
| PlsrProfileQueueBank = queueBank; | |||||
| PlsrCopyShortProfile(&PlsrShortProfile, &queue->producerProfile); | |||||
| PlsrTimedStart.pendingActivation = 0U; | |||||
| PlsrTimedStart.valid = 0U; | |||||
| PlsrTimedStart.building = 0U; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| PlsrSegmentClockStarted = 1U; | |||||
| PlsrSegmentElapsedMs = 0UL; | |||||
| PlsrRamp.active = 0U; | |||||
| PlsrCountedHandoffStaged = 0U; | |||||
| if (PlsrStageCountedHandoff() == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| firstFrequencyHz = firstRun.setting.actualFrequencyHz; | |||||
| secondFrequencyHz = firstFrequencyHz; | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| queueReadIndex = queue->readIndex; | |||||
| queueWriteIndex = queue->writeIndex; | |||||
| if (queueReadIndex != queueWriteIndex) | |||||
| { | |||||
| secondFrequencyHz = queue->entries[ | |||||
| queueReadIndex & PLSR_PROFILE_QUEUE_MASK] | |||||
| .setting.actualFrequencyHz; | |||||
| } | |||||
| queue->lastSetting = firstRun.setting; | |||||
| queue->haveLastSetting = 1U; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| if (secondFrequencyHz > firstFrequencyHz) | |||||
| { | |||||
| PlsrRunStatus = PLSR_STATUS_ACCELERATING; | |||||
| } | |||||
| else if (secondFrequencyHz < firstFrequencyHz) | |||||
| { | |||||
| PlsrRunStatus = PLSR_STATUS_DECELERATING; | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrRunStatus = PLSR_STATUS_RUNNING; | |||||
| } | |||||
| PlsrCurrentFrequencyHz = firstFrequencyHz; | |||||
| PlsrQueuedFrequencyHz = secondFrequencyHz; | |||||
| PlsrDiagnosticQueuedExpectedHz = firstRun.setting.actualFrequencyHz; | |||||
| PlsrCountedObservedPublished = PlsrPlatformObservedPulses( | |||||
| (uint8_t)PlsrActiveConfig.pulseOutput); | |||||
| PlsrProfileQueueMinimumDepth = PlsrProfileQueueCount(); | |||||
| PlsrProfileQueueLowWaterLatched = 0U; | |||||
| PlsrPulseActive = 1U; | |||||
| PlsrExecutor.generation++; | |||||
| PlsrExecutor.mode = (queue->generatorComplete != 0U) | |||||
| ? PLSR_EXEC_STEP_TABLE | |||||
| : PLSR_EXEC_STREAM; | |||||
| if (PlsrPlatformStartCountedStreamPrepared( | |||||
| (uint8_t)PlsrActiveConfig.pulseOutput, | |||||
| &firstRun.setting, firstRun.repeatCount, | |||||
| &actualFrequencyHz) == 0U) | |||||
| { | |||||
| PlsrPulseActive = 0U; | |||||
| PlsrExecutor.mode = PLSR_EXEC_IDLE; | |||||
| PlsrProfileQueueReset(); | |||||
| PlsrShortProfile.active = 0U; | |||||
| return 0U; | |||||
| } | |||||
| PlsrCurrentFrequencyHz = actualFrequencyHz; | |||||
| PlsrDiagnosticRecordFrequency(firstRun.requestedFrequencyHz, | |||||
| firstRun.setting.actualFrequencyHz, | |||||
| actualFrequencyHz); | |||||
| return 1U; | |||||
| } | |||||
| static PLSR_PLATFORM_SERVICE_RESULT PlsrTryStartPreparedTimedSegment( | |||||
| uint8_t segmentNumber) | |||||
| { | |||||
| PLSR_PROFILE_QUEUE *queue; | |||||
| uint32_t criticalState; | |||||
| uint64_t magnitude; | |||||
| uint8_t positive; | |||||
| uint8_t directionLevel; | |||||
| uint8_t directionChanged; | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| queue = &PlsrProfileQueues[PlsrTimedStart.queueBank]; | |||||
| if ((PlsrTimedStart.valid == 0U) | |||||
| || (PlsrTimedStart.pendingActivation != 0U) | |||||
| || (PlsrTimedStart.sourceEpoch != PlsrSegmentEpoch) | |||||
| || (PlsrTimedStart.sourceSegment != PlsrCurrentSegment) | |||||
| || (PlsrTimedStart.nextSegment != segmentNumber) | |||||
| || (PlsrTimedStart.queueBank == PlsrProfileQueueBank) | |||||
| || (queue->generation != PlsrTimedStart.queueGeneration)) | |||||
| { | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return PLSR_PLATFORM_SERVICE_DEFERRED; | |||||
| } | |||||
| magnitude = PlsrTimedStart.magnitude; | |||||
| positive = PlsrTimedStart.positive; | |||||
| directionLevel = PlsrTimedStart.directionLevel; | |||||
| directionChanged = PlsrTimedStart.directionChanged; | |||||
| PlsrSegmentEpoch++; | |||||
| PlsrCurrentSegment = segmentNumber; | |||||
| PlsrSegmentClockStarted = 0U; | |||||
| PlsrSegmentElapsedMs = 0UL; | |||||
| PlsrWaitElapsedMs = 0UL; | |||||
| PlsrBoundaryRampStarted = 0U; | |||||
| PlsrBoundaryPending = 0U; | |||||
| PlsrBoundaryWasCut = 0U; | |||||
| PlsrCutRequested = 0U; | |||||
| PlsrStopPulsesRemaining = 0U; | |||||
| PlsrAbStopArmed = 0U; | |||||
| PlsrFrequencyUpdatePending = 0U; | |||||
| PlsrDeferredFrequencyPending = 0U; | |||||
| PlsrCurrentFrequencyHz = 0UL; | |||||
| PlsrQueuedFrequencyHz = 0UL; | |||||
| PlsrHandoffPlan.valid = 0U; | |||||
| PlsrShortProfile.active = 0U; | |||||
| PlsrShortProfile.nextPeriod = 0U; | |||||
| PlsrDirectionDelayActive = 0U; | |||||
| PlsrDirectionDelayRemainingMs = 0U; | |||||
| PlsrExtEdgePending = 0U; | |||||
| PlsrRemainingPulses = magnitude; | |||||
| PlsrCountPositive = positive; | |||||
| PlsrTimedStart.pendingActivation = 1U; | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| PlsrExtPreviousLevel = | |||||
| PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); | |||||
| if (PlsrPlatformPrepare((uint8_t)PlsrActiveConfig.pulseOutput, | |||||
| (uint8_t)PlsrActiveConfig.directionOutput, | |||||
| directionLevel, | |||||
| (uint8_t)PlsrActiveConfig.outputMode, | |||||
| positive) == 0U) | |||||
| { | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| PlsrInvalidateTimedStartLocked(); | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||||
| } | |||||
| PlsrDiagnosticBeginSegment(segmentNumber, magnitude, positive); | |||||
| PlsrLastDirectionValid = 1U; | |||||
| PlsrLastDirectionOutput = (uint8_t)PlsrActiveConfig.directionOutput; | |||||
| PlsrLastDirectionLevel = directionLevel; | |||||
| if ((directionChanged != 0U) | |||||
| && (PlsrActiveConfig.directionDelayMs != 0U)) | |||||
| { | |||||
| PlsrDirectionDelayActive = 1U; | |||||
| PlsrDirectionDelayRemainingMs = PlsrActiveConfig.directionDelayMs; | |||||
| PlsrRunStatus = PLSR_STATUS_ACCELERATING; | |||||
| return PLSR_PLATFORM_SERVICE_READY; | |||||
| } | |||||
| if (PlsrStartPreparedTimedOutput() == 0U) | |||||
| { | |||||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||||
| } | |||||
| return PLSR_PLATFORM_SERVICE_READY; | |||||
| } | |||||
| static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) | static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) | ||||
| { | { | ||||
| @@ -2655,17 +3139,23 @@ static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) | |||||
| uint32_t secondFrequencyHz; | uint32_t secondFrequencyHz; | ||||
| uint32_t actualFrequencyHz; | uint32_t actualFrequencyHz; | ||||
| uint32_t profileEpoch; | uint32_t profileEpoch; | ||||
| uint64_t remainingPulses; | |||||
| uint8_t profileSegment; | uint8_t profileSegment; | ||||
| uint8_t profileHandoffBank; | uint8_t profileHandoffBank; | ||||
| uint8_t countPositive; | |||||
| uint16_t fillBudget = PLSR_PROFILE_STARTUP_BUDGET; | uint16_t fillBudget = PLSR_PROFILE_STARTUP_BUDGET; | ||||
| PLSR_PROFILE_ENTRY firstRun; | PLSR_PROFILE_ENTRY firstRun; | ||||
| PlsrSegmentClockStarted = 1U; | PlsrSegmentClockStarted = 1U; | ||||
| PlsrSegmentElapsedMs = 0UL; | PlsrSegmentElapsedMs = 0UL; | ||||
| profileSegment = PlsrCurrentSegment; | |||||
| countPositive = PlsrCountPositive; | |||||
| remainingPulses = PlsrRemainingSnapshot(); | |||||
| if (PlsrPrepareShortProfile(&PlsrShortProfile, | if (PlsrPrepareShortProfile(&PlsrShortProfile, | ||||
| PlsrCurrentSegment, startFrequencyHz, | |||||
| profileSegment, startFrequencyHz, | |||||
| targetFrequencyHz, | targetFrequencyHz, | ||||
| PlsrRemainingSnapshot()) != 0U) | |||||
| remainingPulses, | |||||
| countPositive) != 0U) | |||||
| { | { | ||||
| PlsrRamp.active = 0U; | PlsrRamp.active = 0U; | ||||
| PlsrRefreshCurrentHandoffPlan(PlsrShortProfile.endHz); | PlsrRefreshCurrentHandoffPlan(PlsrShortProfile.endHz); | ||||
| @@ -2676,7 +3166,6 @@ static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) | |||||
| firstFrequencyHz = firstRun.setting.actualFrequencyHz; | firstFrequencyHz = firstRun.setting.actualFrequencyHz; | ||||
| secondFrequencyHz = firstFrequencyHz; | secondFrequencyHz = firstFrequencyHz; | ||||
| profileEpoch = PlsrSegmentEpoch; | profileEpoch = PlsrSegmentEpoch; | ||||
| profileSegment = PlsrCurrentSegment; | |||||
| profileHandoffBank = PlsrPreparedHandoffBank; | profileHandoffBank = PlsrPreparedHandoffBank; | ||||
| (void)PlsrProfileQueueBegin(&PlsrShortProfile, | (void)PlsrProfileQueueBegin(&PlsrShortProfile, | ||||
| profileEpoch, | profileEpoch, | ||||
| @@ -3071,6 +3560,7 @@ static void PlsrTransitionToNext(uint8_t allowCarry) | |||||
| { | { | ||||
| uint8_t nextSegment; | uint8_t nextSegment; | ||||
| uint32_t carryFrequencyHz = PlsrBoundaryFrequencyHz; | uint32_t carryFrequencyHz = PlsrBoundaryFrequencyHz; | ||||
| PLSR_PLATFORM_SERVICE_RESULT preparedResult; | |||||
| if (PlsrGetNextSegment(&nextSegment) == 0U) | if (PlsrGetNextSegment(&nextSegment) == 0U) | ||||
| { | { | ||||
| @@ -3078,6 +3568,20 @@ static void PlsrTransitionToNext(uint8_t allowCarry) | |||||
| return; | return; | ||||
| } | } | ||||
| if (allowCarry == 0U) | |||||
| { | |||||
| preparedResult = PlsrTryStartPreparedTimedSegment(nextSegment); | |||||
| if (preparedResult == PLSR_PLATFORM_SERVICE_READY) | |||||
| { | |||||
| return; | |||||
| } | |||||
| if (preparedResult == PLSR_PLATFORM_SERVICE_FAILED) | |||||
| { | |||||
| PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); | |||||
| return; | |||||
| } | |||||
| } | |||||
| if (PlsrStartSegment(nextSegment, allowCarry, carryFrequencyHz) == 0U) | if (PlsrStartSegment(nextSegment, allowCarry, carryFrequencyHz) == 0U) | ||||
| { | { | ||||
| PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); | PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); | ||||
| @@ -3147,7 +3651,7 @@ static uint8_t PlsrBuildHandoffPlan(uint8_t sourceSegment, | |||||
| PlsrResolvedSegmentFrequency(frequencyConfig, nextSegment - 1U); | PlsrResolvedSegmentFrequency(frequencyConfig, nextSegment - 1U); | ||||
| if (PlsrPrepareShortProfile(&plan->profile, nextSegment, | if (PlsrPrepareShortProfile(&plan->profile, nextSegment, | ||||
| carryFrequencyHz, nextFrequencyHz, | carryFrequencyHz, nextFrequencyHz, | ||||
| plan->magnitude) == 0U) | |||||
| plan->magnitude, positive) == 0U) | |||||
| { | { | ||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| @@ -3960,7 +4464,9 @@ static PLSR_PLATFORM_QUEUE_RESULT PlsrTryContinuousHandoff(void) | |||||
| static void PlsrHandleBoundary(uint8_t extEdge) | static void PlsrHandleBoundary(uint8_t extEdge) | ||||
| { | { | ||||
| const PLSR_SEGMENT_CONFIG *segment; | const PLSR_SEGMENT_CONFIG *segment; | ||||
| uint32_t criticalState; | |||||
| uint8_t wasCut = PlsrBoundaryWasCut; | uint8_t wasCut = PlsrBoundaryWasCut; | ||||
| uint8_t preserveTimedStart; | |||||
| PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); | PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); | ||||
| PlsrBoundaryPending = 0U; | PlsrBoundaryPending = 0U; | ||||
| @@ -3969,7 +4475,23 @@ static void PlsrHandleBoundary(uint8_t extEdge) | |||||
| PlsrCurrentFrequencyHz = 0UL; | PlsrCurrentFrequencyHz = 0UL; | ||||
| PlsrQueuedFrequencyHz = 0UL; | PlsrQueuedFrequencyHz = 0UL; | ||||
| PlsrShortProfile.active = 0U; | PlsrShortProfile.active = 0U; | ||||
| PlsrProfileQueueReset(); | |||||
| criticalState = PlsrPlatformEnterCritical(); | |||||
| preserveTimedStart = | |||||
| (((PlsrTimedStart.valid != 0U) | |||||
| || (PlsrTimedStart.building != 0U)) | |||||
| && (PlsrTimedStart.sourceEpoch == PlsrSegmentEpoch) | |||||
| && (PlsrTimedStart.sourceSegment == PlsrCurrentSegment) | |||||
| && (PlsrTimedStart.queueBank != PlsrProfileQueueBank)) ? 1U : 0U; | |||||
| if (preserveTimedStart != 0U) | |||||
| { | |||||
| PlsrProfileQueueResetBankLocked(PlsrProfileQueueBank); | |||||
| PlsrProfileQueueLowWaterLatched = 0U; | |||||
| } | |||||
| PlsrPlatformExitCritical(criticalState); | |||||
| if (preserveTimedStart == 0U) | |||||
| { | |||||
| PlsrProfileQueueReset(); | |||||
| } | |||||
| PlsrHandoffPlan.valid = 0U; | PlsrHandoffPlan.valid = 0U; | ||||
| PlsrDeferredFrequencyPending = 0U; | PlsrDeferredFrequencyPending = 0U; | ||||
| PlsrAbStopArmed = 0U; | PlsrAbStopArmed = 0U; | ||||
| @@ -4017,7 +4539,14 @@ static void PlsrHandleBoundary(uint8_t extEdge) | |||||
| { | { | ||||
| case PLSR_WAIT_TIME: | case PLSR_WAIT_TIME: | ||||
| PlsrWaitElapsedMs = 0UL; | PlsrWaitElapsedMs = 0UL; | ||||
| PlsrRunStatus = PLSR_STATUS_WAITING; | |||||
| if (segment->waitTimeMs == 0UL) | |||||
| { | |||||
| PlsrTransitionToNext(0U); | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrRunStatus = PLSR_STATUS_WAITING; | |||||
| } | |||||
| break; | break; | ||||
| case PLSR_WAIT_SIGNAL: | case PLSR_WAIT_SIGNAL: | ||||
| @@ -4690,6 +5219,10 @@ void PlsrPoll1ms(void) | |||||
| return; | return; | ||||
| } | } | ||||
| /* This work is bounded and targets only the inactive bank. It is done | |||||
| after servicing the live queue so preparation cannot starve output. */ | |||||
| PlsrServiceTimedStartPreparation(); | |||||
| criticalState = PlsrPlatformEnterCritical(); | criticalState = PlsrPlatformEnterCritical(); | ||||
| pollEpoch = PlsrSegmentEpoch; | pollEpoch = PlsrSegmentEpoch; | ||||
| extLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); | extLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); | ||||
| @@ -4734,10 +5267,17 @@ void PlsrPoll1ms(void) | |||||
| if (PlsrDirectionDelayRemainingMs == 0U) | if (PlsrDirectionDelayRemainingMs == 0U) | ||||
| { | { | ||||
| PlsrDirectionDelayActive = 0U; | PlsrDirectionDelayActive = 0U; | ||||
| if (PlsrBeginSegmentOutput(PlsrEffectiveStartFrequency( | |||||
| PlsrActiveConfig.segments[PlsrCurrentSegment - 1U] | |||||
| .frequencyHz, | |||||
| 0U, 1U, 0UL)) == 0U) | |||||
| if (PlsrTimedStart.pendingActivation != 0U) | |||||
| { | |||||
| if (PlsrStartPreparedTimedOutput() == 0U) | |||||
| { | |||||
| PlsrEnterError(PLSR_ERROR_TIMER); | |||||
| } | |||||
| } | |||||
| else if (PlsrBeginSegmentOutput(PlsrEffectiveStartFrequency( | |||||
| PlsrActiveConfig.segments[PlsrCurrentSegment - 1U] | |||||
| .frequencyHz, | |||||
| 0U, 1U, 0UL)) == 0U) | |||||
| { | { | ||||
| PlsrEnterError(PLSR_ERROR_TIMER); | PlsrEnterError(PLSR_ERROR_TIMER); | ||||
| } | } | ||||
| @@ -5408,6 +5948,7 @@ PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, | |||||
| PlsrShadowConfig = PlsrCandidateConfig; | PlsrShadowConfig = PlsrCandidateConfig; | ||||
| if (updateActiveFrequencies != 0U) | if (updateActiveFrequencies != 0U) | ||||
| { | { | ||||
| PlsrInvalidateTimedStartLocked(); | |||||
| for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) | for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) | ||||
| { | { | ||||
| uint8_t frequencyChanged = | uint8_t frequencyChanged = | ||||