| @@ -64,6 +64,8 @@ | |||
| #define PLSR_PROFILE_REFILL_BUDGET (200U) | |||
| #define PLSR_PROFILE_LOW_WATER (100U) | |||
| #define PLSR_PROFILE_LOW_WATER_CLEAR (200U) | |||
| #define PLSR_UNDERRUN_HOLD_TIME_US (2000UL) | |||
| #define PLSR_UNDERRUN_HOLD_MAX_PULSES (200UL) | |||
| #define PLSR_HANDOFF_WARMUP_ITEMS (8U) | |||
| #define PLSR_REPLAN_PREFIX_ITEMS (512U) | |||
| #define PLSR_REPLAN_PREFIX_TIME_US (5000ULL) | |||
| @@ -130,8 +132,11 @@ typedef struct | |||
| volatile uint32_t generation; | |||
| uint32_t producerEpoch; | |||
| volatile uint32_t repeatRemaining; | |||
| volatile uint32_t underrunDebtPulses; | |||
| PLSR_PLATFORM_TIMER_SETTING lastSetting; | |||
| uint8_t producerSegment; | |||
| uint8_t preparedHandoffBank; | |||
| volatile uint8_t haveLastSetting; | |||
| volatile uint8_t active; | |||
| volatile uint8_t generatorComplete; | |||
| } PLSR_PROFILE_QUEUE; | |||
| @@ -357,10 +362,15 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||
| uint64_t pulseCount); | |||
| static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | |||
| PLSR_PROFILE_ENTRY *entry); | |||
| static uint8_t PlsrShortProfileTakeRunLimited( | |||
| PLSR_SHORT_PROFILE *profile, | |||
| PLSR_PROFILE_ENTRY *entry, | |||
| uint32_t maximumRepeats); | |||
| static void PlsrProfileRecordPlannerStatus( | |||
| const PLSR_SHORT_PROFILE *profile); | |||
| static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| uint32_t totalPulses); | |||
| static PLSR_PLATFORM_SERVICE_RESULT PlsrReplanPulseDir( | |||
| uint32_t targetHz, | |||
| uint32_t totalPulses); | |||
| static void PlsrProfileQueueReset(void); | |||
| static void PlsrProfileQueueInvalidateGeneration(void); | |||
| static uint8_t PlsrProfileQueueBegin( | |||
| @@ -370,6 +380,12 @@ static uint8_t PlsrProfileQueueBegin( | |||
| uint8_t preparedHandoffBank); | |||
| static uint8_t PlsrProfileQueueFill(uint16_t targetCount, | |||
| uint16_t *itemBudget); | |||
| static uint8_t PlsrTimerSettingsEqual( | |||
| const PLSR_PLATFORM_TIMER_SETTING *first, | |||
| const PLSR_PLATFORM_TIMER_SETTING *second); | |||
| static uint8_t PlsrProfileQueueAppendLocked( | |||
| PLSR_PROFILE_QUEUE *queue, | |||
| const PLSR_PROFILE_ENTRY *entry); | |||
| static uint8_t PlsrStageCountedHandoff(void); | |||
| static PLSR_PLATFORM_QUEUE_RESULT PlsrProfileQueueCommitNext( | |||
| uint8_t pulseOutput); | |||
| @@ -1770,11 +1786,19 @@ static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, | |||
| static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | |||
| PLSR_PROFILE_ENTRY *entry) | |||
| { | |||
| return PlsrShortProfileTakeRunLimited(profile, entry, 0xFFFFFFFFUL); | |||
| } | |||
| static uint8_t PlsrShortProfileTakeRunLimited( | |||
| PLSR_SHORT_PROFILE *profile, | |||
| PLSR_PROFILE_ENTRY *entry, | |||
| uint32_t maximumRepeats) | |||
| { | |||
| uint32_t repeatCount; | |||
| if ((profile->nextPeriod >= profile->pulseCount) | |||
| || (entry == NULL)) | |||
| || (entry == NULL) || (maximumRepeats == 0UL)) | |||
| { | |||
| return 0U; | |||
| } | |||
| @@ -1793,6 +1817,10 @@ static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | |||
| { | |||
| repeatCount = profile->pulseCount - profile->nextPeriod; | |||
| } | |||
| if (repeatCount > maximumRepeats) | |||
| { | |||
| repeatCount = maximumRepeats; | |||
| } | |||
| if (repeatCount == 0UL) | |||
| { | |||
| profile->active = 0U; | |||
| @@ -1816,8 +1844,9 @@ static uint8_t PlsrShortProfileTakeRun(PLSR_SHORT_PROFILE *profile, | |||
| /* Build the replacement stream in the inactive queue bank while the IRQ keeps | |||
| consuming the published bank. A bounded prefix from the old queue bridges | |||
| the construction interval; the final bank flip is the only critical part. */ | |||
| static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| uint32_t totalPulses) | |||
| static PLSR_PLATFORM_SERVICE_RESULT PlsrReplanPulseDir( | |||
| uint32_t targetHz, | |||
| uint32_t totalPulses) | |||
| { | |||
| PLSR_PROFILE_QUEUE *source; | |||
| PLSR_PROFILE_QUEUE *destination; | |||
| @@ -1835,6 +1864,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| uint32_t availablePlanPulses; | |||
| uint32_t replacementPulses; | |||
| uint32_t prefixCount = 0UL; | |||
| uint32_t replacementStartIndex; | |||
| uint32_t consumedPrefix; | |||
| uint32_t sourceIndex; | |||
| uint32_t criticalState; | |||
| @@ -1844,7 +1874,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| uint8_t sourceBank; | |||
| uint8_t destinationBank; | |||
| uint8_t boundaryWriteIndex; | |||
| uint8_t pipelineStartsNextSegment; | |||
| uint8_t pipelineStartsNextSegment = 0U; | |||
| uint8_t tailWasClipped = 0U; | |||
| uint16_t fillBudget = PLSR_REPLAN_BUILD_ITEMS; | |||
| @@ -1862,16 +1892,28 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| boundaryWriteIndex = PlsrCountedBoundaryWriteIndex; | |||
| if ((PlsrPulseActive == 0U) || (segmentNumber == 0U) | |||
| || (segmentNumber > PlsrActiveConfig.segmentCount) | |||
| || (boundaryWriteIndex != PlsrCountedBoundaryReadIndex) | |||
| || (PlsrCountedFaultEvent != 0U) | |||
| || (PlsrPlatformFinitePipelineSnapshot( | |||
| (uint8_t)PlsrActiveConfig.pulseOutput, | |||
| &committedPulses, &tailFrequencyHz, | |||
| &pipelineStartsNextSegment) == 0U) | |||
| || (pipelineStartsNextSegment != 0U)) | |||
| || (PlsrCountedFaultEvent != 0U)) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return 0U; | |||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||
| } | |||
| if (boundaryWriteIndex != PlsrCountedBoundaryReadIndex) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return PLSR_PLATFORM_SERVICE_DEFERRED; | |||
| } | |||
| if (PlsrPlatformFinitePipelineSnapshot( | |||
| (uint8_t)PlsrActiveConfig.pulseOutput, | |||
| &committedPulses, &tailFrequencyHz, | |||
| &pipelineStartsNextSegment) == 0U) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||
| } | |||
| if (pipelineStartsNextSegment != 0U) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return PLSR_PLATFORM_SERVICE_DEFERRED; | |||
| } | |||
| PlsrPlatformExitCritical(criticalState); | |||
| @@ -1884,6 +1926,8 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| destination->readIndex = 0UL; | |||
| destination->writeIndex = 0UL; | |||
| destination->repeatRemaining = 0UL; | |||
| destination->underrunDebtPulses = 0UL; | |||
| destination->haveLastSetting = 0U; | |||
| destination->producerEpoch = epoch; | |||
| destination->producerSegment = segmentNumber; | |||
| destination->preparedHandoffBank = handoffBank; | |||
| @@ -1912,7 +1956,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| frequencyHz = entry.setting.actualFrequencyHz; | |||
| if (frequencyHz == 0UL) | |||
| { | |||
| return 0U; | |||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||
| } | |||
| remainingPrefixTimeUs = | |||
| PLSR_REPLAN_PREFIX_TIME_US - prefixTimeUs; | |||
| @@ -1943,6 +1987,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| } | |||
| replacementPulses = availablePlanPulses - (uint32_t)prefixPulses; | |||
| replacementStartIndex = prefixCount; | |||
| if (replacementPulses != 0UL) | |||
| { | |||
| if ((tailFrequencyHz == 0UL) | |||
| @@ -1950,7 +1995,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| tailFrequencyHz, targetHz, | |||
| replacementPulses) == 0U)) | |||
| { | |||
| return 0U; | |||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||
| } | |||
| PlsrCopyShortProfile(&destination->producerProfile, &replacement); | |||
| destination->active = 1U; | |||
| @@ -1962,9 +2007,24 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| fillBudget--; | |||
| if (PlsrShortProfileTakeRun(&candidate, &entry) == 0U) | |||
| { | |||
| return 0U; | |||
| return PLSR_PLATFORM_SERVICE_FAILED; | |||
| } | |||
| if ((prefixCount > replacementStartIndex) | |||
| && (destination->entries[prefixCount - 1UL] | |||
| .startsNextSegment == 0U) | |||
| && (PlsrTimerSettingsEqual( | |||
| &destination->entries[prefixCount - 1UL].setting, | |||
| &entry.setting) != 0U) | |||
| && (destination->entries[prefixCount - 1UL].repeatCount | |||
| <= (0xFFFFFFFFUL - entry.repeatCount))) | |||
| { | |||
| destination->entries[prefixCount - 1UL].repeatCount += | |||
| entry.repeatCount; | |||
| } | |||
| else | |||
| { | |||
| destination->entries[prefixCount++] = entry; | |||
| } | |||
| destination->entries[prefixCount++] = entry; | |||
| } | |||
| PlsrCopyShortProfile(&destination->producerProfile, &candidate); | |||
| destination->generatorComplete = | |||
| @@ -1996,7 +2056,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| && ((currentReadIndex - snapshotReadIndex) >= prefixCount))) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return 0U; | |||
| return PLSR_PLATFORM_SERVICE_DEFERRED; | |||
| } | |||
| consumedPrefix = currentReadIndex - snapshotReadIndex; | |||
| destination->readIndex = consumedPrefix; | |||
| @@ -2018,7 +2078,7 @@ static uint8_t PlsrReplanPulseDir(uint32_t targetHz, | |||
| PlsrProfileRecordPlannerStatus(&replacement); | |||
| PlsrRefreshCurrentHandoffPlan(replacement.endHz); | |||
| return 1U; | |||
| return PLSR_PLATFORM_SERVICE_READY; | |||
| } | |||
| static void PlsrCopyShortProfile(PLSR_SHORT_PROFILE *destination, | |||
| @@ -2099,8 +2159,10 @@ static void PlsrProfileQueueReset(void) | |||
| 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; | |||
| } | |||
| PlsrProfileQueueBank = 0U; | |||
| @@ -2126,6 +2188,8 @@ static uint8_t PlsrProfileQueueBegin( | |||
| queue->preparedHandoffBank = preparedHandoffBank; | |||
| queue->generatorComplete = 0U; | |||
| queue->repeatRemaining = 0UL; | |||
| queue->underrunDebtPulses = 0UL; | |||
| queue->haveLastSetting = 0U; | |||
| PlsrProfileQueueMinimumDepth = PLSR_PROFILE_QUEUE_CAPACITY; | |||
| PlsrProfileQueueLowWaterLatched = 0U; | |||
| PlsrCopyShortProfile(&queue->producerProfile, producerProfile); | |||
| @@ -2154,9 +2218,61 @@ static void PlsrProfileQueueInvalidateGeneration(void) | |||
| queue->readIndex = 0U; | |||
| queue->writeIndex = 0U; | |||
| queue->repeatRemaining = 0UL; | |||
| queue->underrunDebtPulses = 0UL; | |||
| queue->haveLastSetting = 0U; | |||
| PlsrPlatformExitCritical(criticalState); | |||
| } | |||
| /* Called with pulse IRQs masked. Quantized-equal adjacent settings form one | |||
| hardware run even if their requested ramp frequencies differ. Segment | |||
| markers remain separate because their completion owns the boundary event. */ | |||
| static uint8_t PlsrTimerSettingsEqual( | |||
| const PLSR_PLATFORM_TIMER_SETTING *first, | |||
| const PLSR_PLATFORM_TIMER_SETTING *second) | |||
| { | |||
| return ((first->actualFrequencyHz == second->actualFrequencyHz) | |||
| && (first->prescaler == second->prescaler) | |||
| && (first->pairPrescaler == second->pairPrescaler) | |||
| && (first->period == second->period) | |||
| && (first->compare == second->compare)) ? 1U : 0U; | |||
| } | |||
| static uint8_t PlsrProfileQueueAppendLocked( | |||
| PLSR_PROFILE_QUEUE *queue, | |||
| const PLSR_PROFILE_ENTRY *entry) | |||
| { | |||
| PLSR_PROFILE_ENTRY *previous; | |||
| uint32_t writeIndex; | |||
| if ((queue == NULL) || (entry == NULL) || (entry->repeatCount == 0UL)) | |||
| { | |||
| return 0U; | |||
| } | |||
| writeIndex = queue->writeIndex; | |||
| if (writeIndex != queue->readIndex) | |||
| { | |||
| previous = &queue->entries[ | |||
| (writeIndex - 1UL) & PLSR_PROFILE_QUEUE_MASK]; | |||
| if ((previous->startsNextSegment == 0U) | |||
| && (entry->startsNextSegment == 0U) | |||
| && (PlsrTimerSettingsEqual( | |||
| &previous->setting, &entry->setting) != 0U) | |||
| && (previous->repeatCount | |||
| <= (0xFFFFFFFFUL - entry->repeatCount))) | |||
| { | |||
| previous->repeatCount += entry->repeatCount; | |||
| return 1U; | |||
| } | |||
| } | |||
| if ((writeIndex - queue->readIndex) >= PLSR_PROFILE_QUEUE_CAPACITY) | |||
| { | |||
| return 0U; | |||
| } | |||
| queue->entries[writeIndex & PLSR_PROFILE_QUEUE_MASK] = *entry; | |||
| queue->writeIndex = writeIndex + 1UL; | |||
| return 1U; | |||
| } | |||
| #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | |||
| && !defined(PLSR_HOST_TEST) | |||
| static uint32_t PlsrProfileTimingNow(void) | |||
| @@ -2191,7 +2307,18 @@ static uint8_t PlsrProfileQueueFill(uint16_t targetCount, | |||
| uint32_t generation; | |||
| uint32_t producerEpoch; | |||
| uint32_t criticalState; | |||
| uint32_t writeIndex; | |||
| uint32_t underrunDebt; | |||
| uint32_t queueReadIndex; | |||
| uint32_t queueWriteIndex; | |||
| uint32_t queueCount; | |||
| uint32_t currentGeneration; | |||
| uint32_t currentProducerEpoch; | |||
| PLSR_PROFILE_QUEUE *queue; | |||
| uint8_t queueBank; | |||
| uint8_t currentBank; | |||
| uint8_t queueActive; | |||
| uint8_t generatorComplete; | |||
| uint8_t payingUnderrunDebt; | |||
| #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | |||
| && !defined(PLSR_HOST_TEST) | |||
| uint32_t startCycles; | |||
| @@ -2209,51 +2336,88 @@ static uint8_t PlsrProfileQueueFill(uint16_t targetCount, | |||
| while (1) | |||
| { | |||
| criticalState = PlsrPlatformEnterCritical(); | |||
| if ((PlsrProfileQueue.active == 0U) | |||
| || (PlsrProfileQueue.generatorComplete != 0U) | |||
| || (PlsrProfileQueueCount() >= targetCount) | |||
| queueBank = PlsrProfileQueueBank; | |||
| queue = &PlsrProfileQueues[queueBank]; | |||
| queueActive = queue->active; | |||
| generatorComplete = queue->generatorComplete; | |||
| queueWriteIndex = queue->writeIndex; | |||
| queueReadIndex = queue->readIndex; | |||
| queueCount = queueWriteIndex - queueReadIndex; | |||
| if ((queueActive == 0U) | |||
| || (generatorComplete != 0U) | |||
| || (queueCount >= targetCount) | |||
| || (*itemBudget == 0U)) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| return 1U; | |||
| } | |||
| (*itemBudget)--; | |||
| generation = PlsrProfileQueue.generation; | |||
| producerEpoch = PlsrProfileQueue.producerEpoch; | |||
| PlsrCopyShortProfile(&candidate, | |||
| &PlsrProfileQueue.producerProfile); | |||
| generation = queue->generation; | |||
| producerEpoch = queue->producerEpoch; | |||
| underrunDebt = queue->underrunDebtPulses; | |||
| payingUnderrunDebt = (underrunDebt != 0UL) ? 1U : 0U; | |||
| PlsrCopyShortProfile(&candidate, &queue->producerProfile); | |||
| PlsrPlatformExitCritical(criticalState); | |||
| #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | |||
| && !defined(PLSR_HOST_TEST) | |||
| startCycles = PlsrProfileTimingNow(); | |||
| #endif | |||
| if (PlsrShortProfileTakeRun(&candidate, &entry) == 0U) | |||
| if (PlsrShortProfileTakeRunLimited( | |||
| &candidate, &entry, | |||
| (payingUnderrunDebt != 0U) | |||
| ? underrunDebt : 0xFFFFFFFFUL) == 0U) | |||
| { | |||
| return 0U; | |||
| } | |||
| criticalState = PlsrPlatformEnterCritical(); | |||
| if ((PlsrProfileQueue.active == 0U) | |||
| || (PlsrProfileQueue.generation != generation) | |||
| || (PlsrProfileQueue.producerEpoch != producerEpoch) | |||
| || (PlsrProfileQueueCount() >= PLSR_PROFILE_QUEUE_CAPACITY)) | |||
| currentBank = PlsrProfileQueueBank; | |||
| queueActive = queue->active; | |||
| currentGeneration = queue->generation; | |||
| currentProducerEpoch = queue->producerEpoch; | |||
| queueWriteIndex = queue->writeIndex; | |||
| queueReadIndex = queue->readIndex; | |||
| queueCount = queueWriteIndex - queueReadIndex; | |||
| if ((currentBank != queueBank) | |||
| || (queueActive == 0U) | |||
| || (currentGeneration != generation) | |||
| || (currentProducerEpoch != producerEpoch) | |||
| || (queueCount >= PLSR_PROFILE_QUEUE_CAPACITY)) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| continue; | |||
| } | |||
| writeIndex = PlsrProfileQueue.writeIndex; | |||
| PlsrProfileQueue.entries[writeIndex & PLSR_PROFILE_QUEUE_MASK] = entry; | |||
| PlsrCopyShortProfile(&PlsrProfileQueue.producerProfile, &candidate); | |||
| PlsrProfileQueue.writeIndex = writeIndex + 1UL; | |||
| if (PlsrProfileQueueCount() >= PLSR_PROFILE_LOW_WATER_CLEAR) | |||
| PlsrCopyShortProfile(&queue->producerProfile, &candidate); | |||
| if (payingUnderrunDebt != 0U) | |||
| { | |||
| underrunDebt = queue->underrunDebtPulses; | |||
| if (underrunDebt < entry.repeatCount) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| continue; | |||
| } | |||
| queue->underrunDebtPulses = | |||
| underrunDebt - entry.repeatCount; | |||
| } | |||
| else if (PlsrProfileQueueAppendLocked( | |||
| queue, &entry) == 0U) | |||
| { | |||
| PlsrPlatformExitCritical(criticalState); | |||
| continue; | |||
| } | |||
| queueWriteIndex = queue->writeIndex; | |||
| queueReadIndex = queue->readIndex; | |||
| queueCount = queueWriteIndex - queueReadIndex; | |||
| if (queueCount >= PLSR_PROFILE_LOW_WATER_CLEAR) | |||
| { | |||
| PlsrProfileQueueLowWaterLatched = 0U; | |||
| } | |||
| if ((candidate.active == 0U) | |||
| || (candidate.nextPeriod >= candidate.pulseCount)) | |||
| if (((candidate.active == 0U) | |||
| || (candidate.nextPeriod >= candidate.pulseCount)) | |||
| && (queue->underrunDebtPulses == 0UL)) | |||
| { | |||
| PlsrProfileQueue.generatorComplete = 1U; | |||
| queue->generatorComplete = 1U; | |||
| } | |||
| PlsrPlatformExitCritical(criticalState); | |||
| #if defined(PLSR_DEBUG_TIMING) && (PLSR_DEBUG_TIMING != 0) \ | |||
| @@ -2559,6 +2723,8 @@ static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) | |||
| } | |||
| PlsrCurrentFrequencyHz = firstFrequencyHz; | |||
| PlsrQueuedFrequencyHz = secondFrequencyHz; | |||
| PlsrProfileQueue.lastSetting = firstRun.setting; | |||
| PlsrProfileQueue.haveLastSetting = 1U; | |||
| PlsrDiagnosticQueuedExpectedHz = firstRun.setting.actualFrequencyHz; | |||
| PlsrCountedObservedPublished = PlsrPlatformObservedPulses( | |||
| (uint8_t)PlsrActiveConfig.pulseOutput); | |||
| @@ -3265,6 +3431,7 @@ uint8_t PlsrExecTakeCountedRunIrq( | |||
| uint8_t *startsNextSegment) | |||
| { | |||
| const PLSR_PROFILE_ENTRY *entry; | |||
| PLSR_PROFILE_QUEUE *queue; | |||
| uint32_t readIndex; | |||
| uint32_t nextReadIndex; | |||
| @@ -3275,17 +3442,68 @@ uint8_t PlsrExecTakeCountedRunIrq( | |||
| { | |||
| return PLSR_EXEC_RUN_FAILED; | |||
| } | |||
| readIndex = PlsrProfileQueue.readIndex; | |||
| if (readIndex == PlsrProfileQueue.writeIndex) | |||
| queue = &PlsrProfileQueues[PlsrProfileQueueBank]; | |||
| readIndex = queue->readIndex; | |||
| if (readIndex == queue->writeIndex) | |||
| { | |||
| if (PlsrProfileQueue.generatorComplete != 0U) | |||
| uint32_t availablePulses; | |||
| uint32_t holdPulses; | |||
| uint32_t holdFrequencyHz; | |||
| if (queue->generatorComplete != 0U) | |||
| { | |||
| return PLSR_EXEC_RUN_DONE; | |||
| } | |||
| PlsrProfileQueueRecordUnderrun(); | |||
| return PLSR_EXEC_RUN_FAILED; | |||
| } | |||
| entry = &PlsrProfileQueue.entries[readIndex & PLSR_PROFILE_QUEUE_MASK]; | |||
| /* A producer miss must not immediately turn the last scheduled edge | |||
| into an abrupt stop. Borrow at most 2 ms of still-unplanned logical | |||
| pulses and hold the last applied timer setting. The task repays | |||
| this debt by advancing (without enqueueing) the same number of | |||
| planner pulses, so segment pulse count remains exact. */ | |||
| availablePulses = | |||
| (queue->producerProfile.pulseCount | |||
| > queue->producerProfile.nextPeriod) | |||
| ? queue->producerProfile.pulseCount | |||
| - queue->producerProfile.nextPeriod | |||
| : 0UL; | |||
| if (availablePulses > queue->underrunDebtPulses) | |||
| { | |||
| availablePulses -= queue->underrunDebtPulses; | |||
| } | |||
| else | |||
| { | |||
| availablePulses = 0UL; | |||
| } | |||
| holdFrequencyHz = queue->lastSetting.actualFrequencyHz; | |||
| holdPulses = (uint32_t)( | |||
| ((uint64_t)holdFrequencyHz * PLSR_UNDERRUN_HOLD_TIME_US | |||
| + 999999ULL) / 1000000ULL); | |||
| if (holdPulses == 0UL) | |||
| { | |||
| holdPulses = 1UL; | |||
| } | |||
| if (holdPulses > PLSR_UNDERRUN_HOLD_MAX_PULSES) | |||
| { | |||
| holdPulses = PLSR_UNDERRUN_HOLD_MAX_PULSES; | |||
| } | |||
| if (holdPulses > availablePulses) | |||
| { | |||
| holdPulses = availablePulses; | |||
| } | |||
| if ((queue->active == 0U) || (queue->haveLastSetting == 0U) | |||
| || (holdFrequencyHz == 0UL) || (holdPulses == 0UL)) | |||
| { | |||
| return PLSR_EXEC_RUN_FAILED; | |||
| } | |||
| queue->underrunDebtPulses += holdPulses; | |||
| queue->generation++; | |||
| *setting = queue->lastSetting; | |||
| *pulseCount = holdPulses; | |||
| *startsNextSegment = 0U; | |||
| PlsrQueuedFrequencyHz = holdFrequencyHz; | |||
| return PLSR_EXEC_RUN_READY; | |||
| } | |||
| entry = &queue->entries[readIndex & PLSR_PROFILE_QUEUE_MASK]; | |||
| if (entry->repeatCount == 0UL) | |||
| { | |||
| return PLSR_EXEC_RUN_FAILED; | |||
| @@ -3304,11 +3522,13 @@ uint8_t PlsrExecTakeCountedRunIrq( | |||
| *pulseCount = entry->repeatCount; | |||
| *startsNextSegment = entry->startsNextSegment; | |||
| nextReadIndex = readIndex + 1UL; | |||
| PlsrProfileQueue.readIndex = nextReadIndex; | |||
| if (PlsrProfileQueue.generatorComplete == 0U) | |||
| queue->readIndex = nextReadIndex; | |||
| queue->lastSetting = entry->setting; | |||
| queue->haveLastSetting = 1U; | |||
| if (queue->generatorComplete == 0U) | |||
| { | |||
| PlsrProfileQueueRecordDepth( | |||
| (uint16_t)(PlsrProfileQueue.writeIndex - nextReadIndex)); | |||
| (uint16_t)(queue->writeIndex - nextReadIndex)); | |||
| } | |||
| /* 记录硬件即将生效的频率(事实镜像),不做任何状态转换。 */ | |||
| PlsrQueuedFrequencyHz = entry->setting.actualFrequencyHz; | |||
| @@ -3461,7 +3681,15 @@ static void PlsrExecServiceCountedBoundaryEvent(void) | |||
| PlsrBoundaryPending = 0U; | |||
| PlsrBoundaryWasCut = 0U; | |||
| PlsrCutRequested = 0U; | |||
| PlsrFrequencyUpdatePending = 0U; | |||
| if (PlsrFrequencyUpdatePending != 0U) | |||
| { | |||
| /* An online frequency transaction that met a latched segment marker | |||
| follows the hardware into the new segment instead of invalidating | |||
| the old handoff metadata. */ | |||
| PlsrFrequencyUpdateSegment = nextSegment; | |||
| PlsrFrequencyUpdateTargetHz = | |||
| PlsrActiveConfig.segments[nextSegment - 1U].frequencyHz; | |||
| } | |||
| PlsrDeferredFrequencyPending = 0U; | |||
| PlsrExtEdgePending = 0U; | |||
| PlsrExtPreviousLevel = | |||
| @@ -4206,7 +4434,8 @@ static uint8_t PlsrExecuteStop(void) | |||
| PlsrRemainingPulses = drainPulses; | |||
| PlsrPlatformExitCritical(criticalState); | |||
| if (PlsrReplanPulseDir(stopTargetHz, | |||
| (uint32_t)drainPulses) == 0U) | |||
| (uint32_t)drainPulses) | |||
| != PLSR_PLATFORM_SERVICE_READY) | |||
| { | |||
| PlsrEnterError(PLSR_ERROR_TIMER); | |||
| return 1U; | |||
| @@ -4401,6 +4630,8 @@ void PlsrPoll1ms(void) | |||
| uint32_t pulseDirReplanPulses = 0UL; | |||
| uint32_t pollEpoch; | |||
| uint16_t fillBudget = PLSR_PROFILE_REFILL_BUDGET; | |||
| PLSR_PLATFORM_SERVICE_RESULT pulseDirReplanResult = | |||
| PLSR_PLATFORM_SERVICE_READY; | |||
| if (PlsrInitialized == 0U) | |||
| { | |||
| @@ -4558,25 +4789,33 @@ void PlsrPoll1ms(void) | |||
| && (PlsrAbStopArmed == 0U) | |||
| && (PlsrFrequencyUpdateSegment == activeSegmentNumber)) | |||
| { | |||
| PlsrFrequencyUpdatePending = 0U; | |||
| activeSegment->frequencyHz = newTargetHz; | |||
| PlsrBoundaryRampStarted = 0U; | |||
| if (PlsrActiveConfig.outputMode == PLSR_OUTPUT_PULSE_DIR) | |||
| { | |||
| uint64_t remaining = PlsrRemainingPulses; | |||
| if ((remaining == 0ULL) || (remaining > 0xFFFFFFFFULL)) | |||
| if (PlsrCountedHandoffStaged != 0U) | |||
| { | |||
| /* The marker and its metadata are one published transaction. | |||
| Leave both untouched; the boundary consumer retargets this | |||
| pending update to the segment that hardware enters. */ | |||
| } | |||
| else if ((remaining == 0ULL) || (remaining > 0xFFFFFFFFULL)) | |||
| { | |||
| PlsrFrequencyUpdatePending = 0U; | |||
| pulseDirReplanFailed = 1U; | |||
| } | |||
| else | |||
| { | |||
| PlsrFrequencyUpdatePending = 0U; | |||
| pulseDirReplanPulses = (uint32_t)remaining; | |||
| pulseDirReplanRequested = 1U; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| PlsrFrequencyUpdatePending = 0U; | |||
| PlsrShortProfile.active = 0U; | |||
| PlsrProfileQueueInvalidateGeneration(); | |||
| PlsrRampStart(PlsrCurrentFrequencyHz, newTargetHz); | |||
| @@ -4588,11 +4827,29 @@ void PlsrPoll1ms(void) | |||
| } | |||
| PlsrPlatformExitCritical(criticalState); | |||
| if ((pulseDirReplanRequested != 0U) | |||
| && (PlsrReplanPulseDir(newTargetHz, | |||
| pulseDirReplanPulses) == 0U)) | |||
| if (pulseDirReplanRequested != 0U) | |||
| { | |||
| pulseDirReplanFailed = 1U; | |||
| pulseDirReplanResult = PlsrReplanPulseDir( | |||
| newTargetHz, pulseDirReplanPulses); | |||
| if (pulseDirReplanResult == PLSR_PLATFORM_SERVICE_FAILED) | |||
| { | |||
| pulseDirReplanFailed = 1U; | |||
| } | |||
| else if (pulseDirReplanResult == PLSR_PLATFORM_SERVICE_DEFERRED) | |||
| { | |||
| /* A segment-marker run may already be latched. Keep the old | |||
| published stream intact and retry against whichever segment is | |||
| active after the boundary event is settled. */ | |||
| criticalState = PlsrPlatformEnterCritical(); | |||
| if ((PlsrSegmentEpoch == pollEpoch) | |||
| && (PlsrStopRequested == 0U)) | |||
| { | |||
| PlsrFrequencyUpdateTargetHz = newTargetHz; | |||
| PlsrFrequencyUpdateSegment = activeSegmentNumber; | |||
| PlsrFrequencyUpdatePending = 1U; | |||
| } | |||
| PlsrPlatformExitCritical(criticalState); | |||
| } | |||
| } | |||
| if (pulseDirReplanFailed != 0U) | |||
| @@ -5133,7 +5390,8 @@ PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, | |||
| criticalState = PlsrPlatformEnterCritical(); | |||
| segmentBeforeDrain = PlsrCurrentSegment; | |||
| if ((updateActiveFrequencies != 0U) && (PlsrPulseActive != 0U)) | |||
| if ((updateActiveFrequencies != 0U) && (PlsrPulseActive != 0U) | |||
| && (PlsrActiveConfig.outputMode != PLSR_OUTPUT_PULSE_DIR)) | |||
| { | |||
| PlsrPlatformDrainPendingPulse( | |||
| (uint8_t)PlsrActiveConfig.pulseOutput); | |||
| @@ -5173,15 +5431,40 @@ PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, | |||
| } | |||
| } | |||
| PlsrPreparedHandoffBank = handoffBank; | |||
| PlsrHandoffPlan.valid = 0U; | |||
| PlsrProfileQueueInvalidateGeneration(); | |||
| if ((drainedToDifferentSegment == 0U) | |||
| && (PlsrPulseActive != 0U) | |||
| && (PlsrAbStopArmed == 0U) | |||
| && (PlsrShortProfile.active == 0U) | |||
| && (PlsrRemainingPulses == 1UL)) | |||
| if (PlsrActiveConfig.outputMode == PLSR_OUTPUT_PULSE_DIR) | |||
| { | |||
| (void)PlsrPrimeHandoff(); | |||
| /* Publish the new prepared-plan bank without touching the stream | |||
| or handoff metadata already visible to the counter IRQ. A | |||
| task-context replan replaces the current tail atomically. If | |||
| the segment marker was already latched, the request remains | |||
| pending and is retargeted by the boundary event. */ | |||
| if ((PlsrPulseActive != 0U) | |||
| && (PlsrStopRequested == 0U) | |||
| && (PlsrAbStopArmed == 0U) | |||
| && (PlsrRunStatus != PLSR_STATUS_WAITING) | |||
| && (PlsrCurrentSegment != 0U) | |||
| && (PlsrCurrentSegment | |||
| <= PlsrActiveConfig.segmentCount)) | |||
| { | |||
| PlsrFrequencyUpdateSegment = PlsrCurrentSegment; | |||
| PlsrFrequencyUpdateTargetHz = | |||
| PlsrActiveConfig.segments[ | |||
| PlsrCurrentSegment - 1U].frequencyHz; | |||
| PlsrFrequencyUpdatePending = 1U; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| PlsrHandoffPlan.valid = 0U; | |||
| PlsrProfileQueueInvalidateGeneration(); | |||
| if ((drainedToDifferentSegment == 0U) | |||
| && (PlsrPulseActive != 0U) | |||
| && (PlsrAbStopArmed == 0U) | |||
| && (PlsrShortProfile.active == 0U) | |||
| && (PlsrRemainingPulses == 1UL)) | |||
| { | |||
| (void)PlsrPrimeHandoff(); | |||
| } | |||
| } | |||
| if ((drainedToDifferentSegment != 0U) | |||
| && (PlsrPulseActive != 0U) | |||
| @@ -5202,6 +5485,7 @@ PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, | |||
| if ((updateActiveFrequencies != 0U) | |||
| && (PlsrPulseActive != 0U) | |||
| && (PlsrActiveConfig.outputMode != PLSR_OUTPUT_PULSE_DIR) | |||
| && (PlsrAbStopArmed == 0U) | |||
| && (PlsrFrequencyUpdatePending == 0U) | |||
| && (PlsrPrepareFutureHandoffQueue() == 0U)) | |||