#include "plsr.h" #include "plsr_internal.h" #include "plsr_platform.h" #include #include #if defined(__ICCARM__) #include #endif #define PLSR_COMMON_FIRST_ADDRESS (0x1000U) #define PLSR_COMMON_LAST_ADDRESS (0x10FFU) #define PLSR_SEGMENT_FIRST_ADDRESS (0x1100U) #define PLSR_SEGMENT_STRIDE (0x0010U) #define PLSR_SEGMENT_DEFINED_WORDS (8U) #define PLSR_WAIT_TIME (0U) #define PLSR_WAIT_SIGNAL (1U) #define PLSR_ACT_TIME (2U) #define PLSR_EXT_SIGNAL (3U) #define PLSR_EXT_OR_COMPLETE (4U) #define PLSR_SEND_COMPLETE (0U) #define PLSR_SEND_SUBSEQUENT (1U) #define PLSR_POSITION_RELATIVE (0U) #define PLSR_POSITION_ABSOLUTE (1U) #define PLSR_COMMAND_START (0x0001U) #define PLSR_COMMAND_STOP (0x0002U) #define PLSR_COMMAND_CLEAR (0x0004U) #define PLSR_CONFIG_SAVE_DELAY_MS (1000U) #define PLSR_POSITION_CHECKPOINT_MS (10U) #define PLSR_SHORT_PROFILE_MAX_PULSES (65535UL) #define PLSR_Q32_ONE (4294967296ULL) typedef struct { uint32_t fromHz; uint32_t toHz; uint32_t durationMs; uint32_t elapsedMs; uint8_t active; } PLSR_RAMP; typedef struct { uint32_t startHz; uint32_t peakHz; uint32_t endHz; uint16_t pulseCount; uint16_t entryPulses; uint16_t steadyPulses; uint16_t exitPulses; volatile uint16_t nextPeriod; uint64_t rampBoundaryQ32; uint64_t rampTotalAreaQ32; uint64_t rampTargetAreaQ32; uint64_t rampAreaStepQ32; uint32_t rampAreaRemainder; uint32_t rampRemainderAccumulator; uint64_t lastRampPhaseStepQ32; uint64_t entryFirstBoundaryQ32; uint64_t entrySecondBoundaryQ32; uint64_t exitFirstBoundaryQ32; uint64_t exitSecondBoundaryQ32; uint32_t lastRampFrequencyHz; volatile uint8_t active; } PLSR_SHORT_PROFILE; typedef struct { uint64_t magnitude; uint32_t firstFrequencyHz; PLSR_SHORT_PROFILE profile; uint8_t nextSegment; uint8_t positive; volatile uint8_t valid; } PLSR_HANDOFF_PLAN; typedef enum { PLSR_WORD_OK = 0, PLSR_WORD_ILLEGAL_ADDRESS, PLSR_WORD_ILLEGAL_VALUE } PLSR_WORD_RESULT; typedef enum { PLSR_COMMAND_MAILBOX_EMPTY = 0, PLSR_COMMAND_MAILBOX_PENDING, PLSR_COMMAND_MAILBOX_EXECUTING } PLSR_COMMAND_MAILBOX_STATE; typedef struct { PLSR_CONFIG startConfig; uint16_t command; volatile uint8_t state; } PLSR_COMMAND_MAILBOX; static const uint16_t PlsrSineProgressQ16[65] = { 0U, 39U, 158U, 355U, 630U, 982U, 1411U, 1915U, 2494U, 3146U, 3869U, 4662U, 5522U, 6448U, 7438U, 8488U, 9597U, 10762U, 11980U, 13248U, 14563U, 15922U, 17321U, 18758U, 20228U, 21728U, 23256U, 24806U, 26375U, 27960U, 29556U, 31160U, 32767U, 34375U, 35979U, 37575U, 39160U, 40729U, 42279U, 43807U, 45307U, 46777U, 48214U, 49613U, 50972U, 52287U, 53555U, 54773U, 55938U, 57047U, 58097U, 59087U, 60013U, 60873U, 61666U, 62389U, 63041U, 63620U, 64124U, 64553U, 64905U, 65180U, 65377U, 65496U, 65535U }; static const uint32_t PlsrSmoothIntegralQ24[65] = { 0UL, 64UL, 504UL, 1688UL, 3968UL, 7688UL, 13176UL, 20752UL, 30720UL, 43376UL, 59000UL, 77864UL, 100224UL, 126328UL, 156408UL, 190688UL, 229376UL, 272672UL, 320760UL, 373816UL, 432000UL, 495464UL, 564344UL, 638768UL, 718848UL, 804688UL, 896376UL, 993992UL, 1097600UL, 1207256UL, 1323000UL, 1444864UL, 1572864UL, 1707008UL, 1847288UL, 1993688UL, 2146176UL, 2304712UL, 2469240UL, 2639696UL, 2816000UL, 2998064UL, 3185784UL, 3379048UL, 3577728UL, 3781688UL, 3990776UL, 4204832UL, 4423680UL, 4647136UL, 4875000UL, 5107064UL, 5343104UL, 5582888UL, 5826168UL, 6072688UL, 6322176UL, 6574352UL, 6828920UL, 7085576UL, 7344000UL, 7603864UL, 7864824UL, 8126528UL, 8388608UL }; static const uint32_t PlsrSineIntegralQ24[65] = { 0UL, 53UL, 421UL, 1420UL, 3362UL, 6560UL, 11321UL, 17949UL, 26744UL, 38000UL, 52007UL, 69047UL, 89393UL, 113314UL, 141066UL, 172899UL, 209052UL, 249753UL, 295221UL, 345662UL, 401269UL, 462225UL, 528698UL, 600845UL, 678806UL, 762711UL, 852672UL, 948789UL, 1051146UL, 1159812UL, 1274841UL, 1396271UL, 1524127UL, 1658415UL, 1799129UL, 1946244UL, 2099722UL, 2259509UL, 2425536UL, 2597719UL, 2775958UL, 2960141UL, 3150138UL, 3345809UL, 3546997UL, 3753534UL, 3965237UL, 4181913UL, 4403356UL, 4629347UL, 4859658UL, 5094050UL, 5332273UL, 5574071UL, 5819175UL, 6067312UL, 6318200UL, 6571549UL, 6827065UL, 7084448UL, 7343394UL, 7603596UL, 7864741UL, 8126517UL, 8388608UL }; static PLSR_CONFIG PlsrShadowConfig; static PLSR_CONFIG PlsrActiveConfig; static PLSR_CONFIG PlsrCandidateConfig; static volatile int32_t PlsrPosition; static volatile uint64_t PlsrRemainingPulses; static volatile uint8_t PlsrPulseActive; static volatile uint8_t PlsrCutRequested; static volatile uint8_t PlsrBoundaryPending; static volatile uint8_t PlsrBoundaryWasCut; static volatile uint8_t PlsrCountPositive; static volatile uint8_t PlsrCountOverflowPending; static volatile uint8_t PlsrPositionValid; static volatile uint8_t PlsrPositionCheckpointDirty; static volatile uint8_t PlsrFrequencyUpdatePending; static volatile uint8_t PlsrFrequencyUpdateSegment; static volatile uint8_t PlsrSeamlessHandoffPending; static volatile uint8_t PlsrTimerErrorPending; static volatile uint8_t PlsrDeferredFrequencyPending; static volatile uint32_t PlsrCurrentFrequencyHz; static volatile uint32_t PlsrQueuedFrequencyHz; static volatile uint32_t PlsrBoundaryFrequencyHz; static volatile uint32_t PlsrFrequencyUpdateTargetHz; static volatile uint32_t PlsrDeferredFrequencyHz; static volatile uint32_t PlsrSegmentEpoch; static volatile PLSR_STATUS PlsrRunStatus = PLSR_STATUS_UNINITIALIZED; static PLSR_ERROR PlsrError = PLSR_ERROR_NONE; static PLSR_RAMP PlsrRamp; static PLSR_SHORT_PROFILE PlsrShortProfile; static PLSR_HANDOFF_PLAN PlsrHandoffPlan; static PLSR_HANDOFF_PLAN PlsrPreparedHandoffPlans[2][PLSR_SEGMENT_COUNT_MAX]; static volatile uint8_t PlsrPreparedHandoffBank; static uint8_t PlsrInitialized; static volatile uint8_t PlsrCurrentSegment; static uint8_t PlsrDirectionDelayActive; static uint16_t PlsrDirectionDelayRemainingMs; static volatile uint8_t PlsrSegmentClockStarted; static volatile uint32_t PlsrSegmentElapsedMs; static volatile uint32_t PlsrWaitElapsedMs; static volatile uint8_t PlsrExtPreviousLevel; static volatile uint8_t PlsrExtEdgePending; static volatile uint8_t PlsrStopRequested; static volatile uint8_t PlsrStopPulsesRemaining; static volatile uint8_t PlsrBoundaryRampStarted; static uint8_t PlsrLastDirectionValid; static uint8_t PlsrLastDirectionOutput; static uint8_t PlsrLastDirectionLevel; static uint8_t PlsrPersistenceDirty; static uint16_t PlsrPersistenceDelayMs; static uint8_t PlsrPositionCheckpointElapsedMs; static PLSR_COMMAND_MAILBOX PlsrCommandMailbox; static uint8_t PlsrIsBusy(void); static void PlsrSetDefaults(PLSR_CONFIG *config); static uint8_t PlsrConfigIsValid(const PLSR_CONFIG *config, uint8_t validateActivePath); static uint16_t PlsrReadConfigWord(const PLSR_CONFIG *config, uint16_t address); static PLSR_WORD_RESULT PlsrWriteConfigWord(PLSR_CONFIG *config, uint16_t address, uint16_t value); static uint8_t PlsrAddressIsDwordHalf(uint16_t address, uint16_t *pairedAddress); static PLSR_MB_RESULT PlsrQueueCommand(uint16_t command); static uint8_t PlsrPollCommandMailbox(void); static void PlsrExecuteStart(void); static uint8_t PlsrExecuteStop(void); static void PlsrExecuteClear(void); static uint8_t PlsrStartSegment(uint8_t segmentNumber, uint8_t allowCarry, uint32_t carryFrequencyHz); static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz); static void PlsrHandleBoundary(uint8_t extEdge); static void PlsrTransitionToNext(uint8_t allowCarry); static void PlsrFinishCompleted(void); static void PlsrFinishStopped(void); static void PlsrEnterError(PLSR_ERROR error); static void PlsrMarkPersistenceDirty(uint16_t delayMs); static void PlsrCheckpointPosition(uint8_t wasBusy); static void PlsrPollPositionCheckpoint(void); static uint8_t PlsrTrySubsequentHandoff(void); static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, uint8_t segmentNumber, uint32_t startFrequencyHz, uint32_t targetFrequencyHz, uint64_t pulseCount); static uint32_t PlsrShortProfileTakeFrequency(PLSR_SHORT_PROFILE *profile); static uint8_t PlsrAdvanceShortProfile(uint8_t pulseOutput); static void PlsrCopyShortProfile(PLSR_SHORT_PROFILE *destination, const PLSR_SHORT_PROFILE *source); static void PlsrInvalidateHandoffPlans(void); static uint8_t PlsrBuildHandoffPlanBank( const PLSR_CONFIG *frequencyConfig); static uint8_t PlsrSelectPreparedHandoffPlan(PLSR_HANDOFF_PLAN *plan); static uint8_t PlsrPrimeHandoff(void); static uint8_t PlsrBuildHandoffPlan(uint8_t sourceSegment, const PLSR_CONFIG *frequencyConfig, PLSR_HANDOFF_PLAN *plan); static uint8_t PlsrIsBusy(void) { return ((PlsrRunStatus == PLSR_STATUS_ACCELERATING) || (PlsrRunStatus == PLSR_STATUS_RUNNING) || (PlsrRunStatus == PLSR_STATUS_DECELERATING) || (PlsrRunStatus == PLSR_STATUS_WAITING) || (PlsrRunStatus == PLSR_STATUS_PAUSED)) ? 1U : 0U; } static uint32_t PlsrJoinU32(uint16_t lowWord, uint16_t highWord) { return (uint32_t)lowWord | ((uint32_t)highWord << 16U); } static uint16_t PlsrLowWord(uint32_t value) { return (uint16_t)(value & 0xFFFFUL); } static uint16_t PlsrHighWord(uint32_t value) { return (uint16_t)(value >> 16U); } static void PlsrSetDefaults(PLSR_CONFIG *config) { uint8_t index; (void)memset(config, 0, sizeof(*config)); config->pulseOutput = 0U; config->directionOutput = 0U; config->waitInput = 0U; config->extInput = 0U; config->sendMode = PLSR_SEND_COMPLETE; config->directionDelayMs = 10U; config->directionNegativeLogic = 0U; config->curveMode = 0U; config->positionMode = PLSR_POSITION_RELATIVE; config->segmentCount = 1U; config->startSegment = 1U; config->defaultSpeedHz = 1000UL; config->startSpeedHz = 100UL; config->stopSpeedHz = 100UL; config->accelerationTimeMs = 100U; config->decelerationTimeMs = 100U; for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) { config->segments[index].frequencyHz = 1000UL; config->segments[index].pulses = (index == 0U) ? 1000L : 0L; config->segments[index].waitType = PLSR_EXT_OR_COMPLETE; config->segments[index].waitTimeMs = 0U; config->segments[index].actTimeMs = 0U; config->segments[index].jumpSegment = 0U; } } static uint8_t PlsrConfigIsValid(const PLSR_CONFIG *config, uint8_t validateActivePath) { uint8_t index; if ((config->pulseOutput > 3U) || (config->directionOutput > 3U) || (config->waitInput > 1U) || (config->extInput > 1U) || (config->sendMode > PLSR_SEND_SUBSEQUENT) || (config->directionNegativeLogic > 1U) || (config->curveMode > 2U) || (config->positionMode > PLSR_POSITION_ABSOLUTE) || (config->segmentCount == 0U) || (config->segmentCount > PLSR_SEGMENT_COUNT_MAX) || (config->startSegment == 0U) || (config->startSegment > PLSR_SEGMENT_COUNT_MAX) || (config->defaultSpeedHz == 0UL) || (config->defaultSpeedHz > PLSR_FREQUENCY_MAX_HZ) || (config->startSpeedHz > PLSR_FREQUENCY_MAX_HZ) || (config->stopSpeedHz > PLSR_FREQUENCY_MAX_HZ)) { return 0U; } if ((validateActivePath != 0U) && (config->startSegment > config->segmentCount)) { return 0U; } for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) { const PLSR_SEGMENT_CONFIG *segment = &config->segments[index]; if ((segment->frequencyHz == 0UL) || (segment->frequencyHz > PLSR_FREQUENCY_MAX_HZ) || (segment->waitType > PLSR_EXT_OR_COMPLETE) || (segment->jumpSegment > PLSR_SEGMENT_COUNT_MAX)) { return 0U; } if ((validateActivePath != 0U) && (index < config->segmentCount) && (segment->jumpSegment > config->segmentCount)) { return 0U; } } return 1U; } static uint16_t PlsrReadConfigWord(const PLSR_CONFIG *config, uint16_t address) { uint16_t offset; uint8_t segmentIndex; const PLSR_SEGMENT_CONFIG *segment; switch (address) { case 0x1000U: return config->pulseOutput; case 0x1001U: return config->directionOutput; case 0x1002U: return config->waitInput; case 0x1003U: return config->extInput; case 0x1004U: return config->sendMode; case 0x1005U: return config->directionDelayMs; case 0x1006U: return config->directionNegativeLogic; case 0x1007U: return config->curveMode; case 0x1008U: return config->positionMode; case 0x1009U: return config->segmentCount; case 0x100AU: return config->startSegment; case 0x100BU: return PlsrLowWord(config->defaultSpeedHz); case 0x100CU: return PlsrHighWord(config->defaultSpeedHz); case 0x100DU: return PlsrLowWord(config->startSpeedHz); case 0x100EU: return PlsrHighWord(config->startSpeedHz); case 0x1010U: return PlsrLowWord(config->stopSpeedHz); case 0x1011U: return PlsrHighWord(config->stopSpeedHz); case 0x1012U: return config->accelerationTimeMs; case 0x1013U: return config->decelerationTimeMs; default: break; } if ((address >= PLSR_SEGMENT_FIRST_ADDRESS) && (address <= PLSR_CONFIG_LAST_ADDRESS)) { offset = (uint16_t)(address - PLSR_SEGMENT_FIRST_ADDRESS); segmentIndex = (uint8_t)(offset / PLSR_SEGMENT_STRIDE); offset = (uint16_t)(offset % PLSR_SEGMENT_STRIDE); segment = &config->segments[segmentIndex]; switch (offset) { case 0U: return PlsrLowWord(segment->frequencyHz); case 1U: return PlsrHighWord(segment->frequencyHz); case 2U: return PlsrLowWord((uint32_t)segment->pulses); case 3U: return PlsrHighWord((uint32_t)segment->pulses); case 4U: return segment->waitType; case 5U: return segment->waitTimeMs; case 6U: return segment->actTimeMs; case 7U: return segment->jumpSegment; default: return 0U; } } /* 0x100F, 0x1014..0x10FF, and segment padding read as zero. */ return 0U; } static PLSR_WORD_RESULT PlsrWriteConfigWord(PLSR_CONFIG *config, uint16_t address, uint16_t value) { uint16_t offset; uint8_t segmentIndex; PLSR_SEGMENT_CONFIG *segment; switch (address) { case 0x1000U: config->pulseOutput = value; return PLSR_WORD_OK; case 0x1001U: config->directionOutput = value; return PLSR_WORD_OK; case 0x1002U: config->waitInput = value; return PLSR_WORD_OK; case 0x1003U: config->extInput = value; return PLSR_WORD_OK; case 0x1004U: config->sendMode = value; return PLSR_WORD_OK; case 0x1005U: config->directionDelayMs = value; return PLSR_WORD_OK; case 0x1006U: config->directionNegativeLogic = value; return PLSR_WORD_OK; case 0x1007U: config->curveMode = value; return PLSR_WORD_OK; case 0x1008U: config->positionMode = value; return PLSR_WORD_OK; case 0x1009U: config->segmentCount = value; return PLSR_WORD_OK; case 0x100AU: config->startSegment = value; return PLSR_WORD_OK; case 0x100BU: config->defaultSpeedHz = PlsrJoinU32(value, PlsrHighWord(config->defaultSpeedHz)); return PLSR_WORD_OK; case 0x100CU: config->defaultSpeedHz = PlsrJoinU32(PlsrLowWord(config->defaultSpeedHz), value); return PLSR_WORD_OK; case 0x100DU: config->startSpeedHz = PlsrJoinU32(value, PlsrHighWord(config->startSpeedHz)); return PLSR_WORD_OK; case 0x100EU: config->startSpeedHz = PlsrJoinU32(PlsrLowWord(config->startSpeedHz), value); return PLSR_WORD_OK; case 0x100FU: return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; case 0x1010U: config->stopSpeedHz = PlsrJoinU32(value, PlsrHighWord(config->stopSpeedHz)); return PLSR_WORD_OK; case 0x1011U: config->stopSpeedHz = PlsrJoinU32(PlsrLowWord(config->stopSpeedHz), value); return PLSR_WORD_OK; case 0x1012U: config->accelerationTimeMs = value; return PLSR_WORD_OK; case 0x1013U: config->decelerationTimeMs = value; return PLSR_WORD_OK; default: break; } if ((address >= 0x1014U) && (address <= PLSR_COMMON_LAST_ADDRESS)) { return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; } if ((address < PLSR_SEGMENT_FIRST_ADDRESS) || (address > PLSR_CONFIG_LAST_ADDRESS)) { return PLSR_WORD_ILLEGAL_ADDRESS; } offset = (uint16_t)(address - PLSR_SEGMENT_FIRST_ADDRESS); segmentIndex = (uint8_t)(offset / PLSR_SEGMENT_STRIDE); offset = (uint16_t)(offset % PLSR_SEGMENT_STRIDE); segment = &config->segments[segmentIndex]; switch (offset) { case 0U: segment->frequencyHz = PlsrJoinU32(value, PlsrHighWord(segment->frequencyHz)); return PLSR_WORD_OK; case 1U: segment->frequencyHz = PlsrJoinU32(PlsrLowWord(segment->frequencyHz), value); return PLSR_WORD_OK; case 2U: segment->pulses = (int32_t)PlsrJoinU32( value, PlsrHighWord((uint32_t)segment->pulses)); return PLSR_WORD_OK; case 3U: segment->pulses = (int32_t)PlsrJoinU32( PlsrLowWord((uint32_t)segment->pulses), value); return PLSR_WORD_OK; case 4U: segment->waitType = value; return PLSR_WORD_OK; case 5U: segment->waitTimeMs = value; return PLSR_WORD_OK; case 6U: segment->actTimeMs = value; return PLSR_WORD_OK; case 7U: segment->jumpSegment = value; return PLSR_WORD_OK; default: return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; } } static uint8_t PlsrAddressIsDwordHalf(uint16_t address, uint16_t *pairedAddress) { uint16_t offset; switch (address) { case 0x100BU: case 0x100DU: case 0x1010U: *pairedAddress = (uint16_t)(address + 1U); return 1U; case 0x100CU: case 0x100EU: case 0x1011U: *pairedAddress = (uint16_t)(address - 1U); return 1U; default: break; } if ((address >= PLSR_SEGMENT_FIRST_ADDRESS) && (address <= PLSR_CONFIG_LAST_ADDRESS)) { offset = (uint16_t)((address - PLSR_SEGMENT_FIRST_ADDRESS) % PLSR_SEGMENT_STRIDE); if ((offset == 0U) || (offset == 2U)) { *pairedAddress = (uint16_t)(address + 1U); return 1U; } if ((offset == 1U) || (offset == 3U)) { *pairedAddress = (uint16_t)(address - 1U); return 1U; } } return 0U; } static uint8_t PlsrAddressIsProduct(uint16_t address) { return (((address >= PLSR_CONFIG_FIRST_ADDRESS) && (address <= PLSR_CONFIG_LAST_ADDRESS)) || ((address >= PLSR_STATUS_FIRST_ADDRESS) && (address <= PLSR_STATUS_LAST_ADDRESS)) || (address == PLSR_CONTROL_ADDRESS)) ? 1U : 0U; } static PLSR_MB_RESULT PlsrClassifyRange(uint16_t startAddress, uint16_t quantity) { uint32_t address; uint32_t endAddress; uint8_t foundProduct = 0U; uint8_t foundOther = 0U; if (quantity == 0U) { return PLSR_MB_ILLEGAL_VALUE; } endAddress = (uint32_t)startAddress + (uint32_t)quantity - 1UL; if (endAddress > 0xFFFFUL) { return PLSR_MB_ILLEGAL_ADDRESS; } for (address = startAddress; address <= endAddress; address++) { if (PlsrAddressIsProduct((uint16_t)address) != 0U) { foundProduct = 1U; } else { foundOther = 1U; } } if (foundProduct == 0U) { return PLSR_MB_NOT_HANDLED; } return (foundOther != 0U) ? PLSR_MB_ILLEGAL_ADDRESS : PLSR_MB_OK; } static uint32_t PlsrCurveProgressQ16(uint32_t elapsed, uint32_t duration, uint16_t curveMode) { uint32_t linear; if ((duration == 0UL) || (elapsed >= duration)) { return 65535UL; } linear = (uint32_t)(((uint64_t)elapsed * 65535UL) / duration); if (curveMode == 1U) { uint64_t x = linear; uint64_t x2 = (x * x) / 65535UL; return (uint32_t)((x2 * (196605UL - 2UL * x)) / 65535UL); } if (curveMode == 2U) { uint32_t scaled = linear * 64UL; uint32_t index = scaled / 65535UL; uint32_t fraction = scaled % 65535UL; uint32_t first; uint32_t second; if (index >= 64UL) { return 65535UL; } first = PlsrSineProgressQ16[index]; second = PlsrSineProgressQ16[index + 1UL]; return first + (uint32_t)(((uint64_t)(second - first) * fraction) / 65535UL); } return linear; } static uint32_t PlsrRampDurationMs(uint32_t fromHz, uint32_t toHz) { uint32_t gap; uint32_t baseTimeMs; uint64_t duration; if (fromHz == toHz) { return 0UL; } gap = (fromHz > toHz) ? (fromHz - toHz) : (toHz - fromHz); baseTimeMs = (toHz > fromHz) ? PlsrActiveConfig.accelerationTimeMs : PlsrActiveConfig.decelerationTimeMs; if (baseTimeMs == 0UL) { return 0UL; } duration = ((uint64_t)gap * baseTimeMs + PlsrActiveConfig.defaultSpeedHz - 1UL) / PlsrActiveConfig.defaultSpeedHz; if (duration > 0xFFFFFFFFUL) { return 0xFFFFFFFFUL; } return (uint32_t)duration; } static void PlsrRampStart(uint32_t fromHz, uint32_t toHz) { PlsrRamp.fromHz = fromHz; PlsrRamp.toHz = toHz; PlsrRamp.durationMs = PlsrRampDurationMs(fromHz, toHz); PlsrRamp.elapsedMs = 0UL; PlsrRamp.active = (PlsrRamp.durationMs != 0UL) ? 1U : 0U; if (toHz > fromHz) { PlsrRunStatus = PLSR_STATUS_ACCELERATING; } else if (toHz < fromHz) { PlsrRunStatus = PLSR_STATUS_DECELERATING; } else { PlsrRunStatus = PLSR_STATUS_RUNNING; } } static uint8_t PlsrApplyFrequencyPair(uint32_t requestedFirstFrequencyHz, uint32_t requestedQueuedFrequencyHz, uint32_t expectedEpoch) { uint32_t criticalState; uint32_t actualFirstFrequencyHz; uint32_t actualQueuedFrequencyHz; uint32_t hardwareFirstFrequencyHz = requestedFirstFrequencyHz; uint32_t hardwareQueuedFrequencyHz = requestedQueuedFrequencyHz; PLSR_HANDOFF_PLAN candidatePlan; uint8_t haveCandidatePlan = 0U; criticalState = PlsrPlatformEnterCritical(); if ((PlsrSegmentEpoch != expectedEpoch) || (PlsrBoundaryPending != 0U) || (PlsrRemainingPulses == 0UL)) { PlsrPlatformExitCritical(criticalState); return 1U; } if (hardwareFirstFrequencyHz == 0UL) { if (PlsrPulseActive == 0U) { PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrPlatformExitCritical(criticalState); return 1U; } hardwareFirstFrequencyHz = 1UL; } if (hardwareQueuedFrequencyHz == 0UL) { hardwareQueuedFrequencyHz = 1UL; } if (PlsrPulseActive != 0U) { if (PlsrHandoffPlan.valid != 0U) { PlsrPlatformExitCritical(criticalState); return 1U; } PlsrDeferredFrequencyHz = hardwareQueuedFrequencyHz; PlsrDeferredFrequencyPending = 1U; PlsrPlatformExitCritical(criticalState); return 1U; } else { if (PlsrSelectPreparedHandoffPlan(&candidatePlan) != 0U) { hardwareQueuedFrequencyHz = candidatePlan.firstFrequencyHz; haveCandidatePlan = 1U; } if (PlsrPlatformStartPulse((uint8_t)PlsrActiveConfig.pulseOutput, hardwareFirstFrequencyHz, hardwareQueuedFrequencyHz, &actualFirstFrequencyHz, &actualQueuedFrequencyHz) == 0U) { PlsrPlatformExitCritical(criticalState); return 0U; } PlsrPulseActive = 1U; PlsrCurrentFrequencyHz = actualFirstFrequencyHz; PlsrHandoffPlan.valid = 0U; if (haveCandidatePlan != 0U) { PlsrHandoffPlan.magnitude = candidatePlan.magnitude; PlsrHandoffPlan.firstFrequencyHz = actualQueuedFrequencyHz; PlsrCopyShortProfile(&PlsrHandoffPlan.profile, &candidatePlan.profile); PlsrHandoffPlan.nextSegment = candidatePlan.nextSegment; PlsrHandoffPlan.positive = candidatePlan.positive; PlsrHandoffPlan.valid = 1U; } } PlsrQueuedFrequencyHz = actualQueuedFrequencyHz; PlsrPlatformExitCritical(criticalState); return 1U; } static uint8_t PlsrApplyFrequency(uint32_t requestedFrequencyHz, uint32_t expectedEpoch) { return PlsrApplyFrequencyPair(requestedFrequencyHz, requestedFrequencyHz, expectedEpoch); } static uint8_t PlsrStopDrainPulseCount(void) { uint8_t deferredPending = PlsrDeferredFrequencyPending; uint32_t deferredFrequencyHz = PlsrDeferredFrequencyHz; uint32_t queuedFrequencyHz = PlsrQueuedFrequencyHz; uint32_t currentFrequencyHz = PlsrCurrentFrequencyHz; if ((deferredPending != 0U) && (deferredFrequencyHz != queuedFrequencyHz)) { return 3U; } PlsrDeferredFrequencyPending = 0U; return (currentFrequencyHz == queuedFrequencyHz) ? 1U : 2U; } static uint8_t PlsrCommitDeferredFrequency(uint8_t pulseOutput) { uint32_t requestedFrequencyHz; uint32_t actualFrequencyHz; if (PlsrDeferredFrequencyPending == 0U) { return 1U; } requestedFrequencyHz = PlsrDeferredFrequencyHz; PlsrDeferredFrequencyPending = 0U; if (PlsrPlatformQueueFrequency(pulseOutput, requestedFrequencyHz, &actualFrequencyHz) == 0U) { return 0U; } PlsrQueuedFrequencyHz = actualFrequencyHz; return 1U; } static uint8_t PlsrRampAdvance(uint32_t expectedEpoch) { uint32_t progress; uint32_t frequency; uint32_t gap; uint32_t criticalState; uint32_t fromHz; uint32_t toHz; uint32_t durationMs; uint32_t elapsedMs; criticalState = PlsrPlatformEnterCritical(); if ((PlsrSegmentEpoch != expectedEpoch) || (PlsrRamp.active == 0U)) { PlsrPlatformExitCritical(criticalState); return 1U; } PlsrRamp.elapsedMs++; fromHz = PlsrRamp.fromHz; toHz = PlsrRamp.toHz; durationMs = PlsrRamp.durationMs; elapsedMs = PlsrRamp.elapsedMs; PlsrPlatformExitCritical(criticalState); progress = PlsrCurveProgressQ16(elapsedMs, durationMs, PlsrActiveConfig.curveMode); if (toHz >= fromHz) { gap = toHz - fromHz; frequency = fromHz + (uint32_t)(((uint64_t)gap * progress) / 65535UL); } else { gap = fromHz - toHz; frequency = fromHz - (uint32_t)(((uint64_t)gap * progress) / 65535UL); } if (PlsrApplyFrequency(frequency, expectedEpoch) == 0U) { return 0U; } criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch != expectedEpoch) { PlsrPlatformExitCritical(criticalState); return 1U; } if (elapsedMs >= durationMs) { PlsrRamp.active = 0U; if (PlsrStopRequested == 0U) { PlsrRunStatus = PLSR_STATUS_RUNNING; } else { PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); } } PlsrPlatformExitCritical(criticalState); return 1U; } static uint8_t PlsrGetNextSegment(uint8_t *nextSegment) { const PLSR_SEGMENT_CONFIG *segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; if (segment->jumpSegment != 0U) { *nextSegment = (uint8_t)segment->jumpSegment; return 1U; } if (PlsrCurrentSegment < PlsrActiveConfig.segmentCount) { *nextSegment = (uint8_t)(PlsrCurrentSegment + 1U); return 1U; } return 0U; } static int64_t PlsrSegmentDisplacement(uint8_t segmentNumber, int32_t referencePosition) { int32_t configured = PlsrActiveConfig.segments[segmentNumber - 1U].pulses; if (PlsrActiveConfig.positionMode == PLSR_POSITION_ABSOLUTE) { return (int64_t)configured - (int64_t)referencePosition; } return configured; } static uint8_t PlsrPredictNextDirection(uint8_t nextSegment, uint8_t *positive) { uint32_t criticalState; uint64_t remaining; int32_t position; uint32_t predictedBits; int32_t predictedPosition; int64_t displacement; criticalState = PlsrPlatformEnterCritical(); remaining = PlsrRemainingPulses; position = PlsrPosition; PlsrPlatformExitCritical(criticalState); predictedBits = (uint32_t)position; if (PlsrCountPositive != 0U) { predictedBits += (uint32_t)remaining; } else { predictedBits -= (uint32_t)remaining; } predictedPosition = (int32_t)predictedBits; displacement = PlsrSegmentDisplacement(nextSegment, predictedPosition); if (displacement == 0) { return 0U; } *positive = (displacement > 0) ? 1U : 0U; return 1U; } static uint64_t PlsrRemainingSnapshot(void) { uint32_t criticalState = PlsrPlatformEnterCritical(); uint64_t remaining = PlsrRemainingPulses; PlsrPlatformExitCritical(criticalState); return remaining; } static uint64_t PlsrRampPulseEstimate(uint32_t fromHz, uint32_t toHz) { uint32_t duration = PlsrRampDurationMs(fromHz, toHz); uint64_t sum = (uint64_t)fromHz + toHz; return (sum * duration + 1999UL) / 2000UL + 2UL; } static uint16_t PlsrShortProfileRampTime(uint32_t fromHz, uint32_t toHz) { if (toHz > fromHz) { return PlsrActiveConfig.accelerationTimeMs; } if (toHz < fromHz) { return PlsrActiveConfig.decelerationTimeMs; } return 0U; } static uint64_t PlsrShortProfileRampWeight(uint32_t fromHz, uint32_t toHz, uint16_t timeMs) { uint64_t fromSquared = (uint64_t)fromHz * fromHz; uint64_t toSquared = (uint64_t)toHz * toHz; uint64_t difference = (fromSquared > toSquared) ? (fromSquared - toSquared) : (toSquared - fromSquared); return difference * timeMs; } static uint32_t PlsrIntegerSquareRoot(uint64_t value) { uint64_t bit = (uint64_t)1U << 62U; uint64_t root = 0UL; while (bit > value) { bit >>= 2U; } while (bit != 0UL) { if (value >= (root + bit)) { value -= root + bit; root = (root >> 1U) + bit; } else { root >>= 1U; } bit >>= 2U; } return (uint32_t)root; } static uint32_t PlsrShortProfilePeak(uint32_t startHz, uint32_t targetHz, uint32_t endHz, uint16_t pulseCount) { uint32_t upperEndpoint = (startHz > endHz) ? startHz : endHz; uint32_t lowerEndpoint = (startHz < endHz) ? startHz : endHz; uint16_t entryTime; uint16_t exitTime; uint32_t timeSum; uint64_t weightedEndpoints; uint64_t availableArea; uint64_t peakSquared; uint32_t peakHz; if (targetHz > upperEndpoint) { entryTime = PlsrShortProfileRampTime(startHz, targetHz); exitTime = PlsrShortProfileRampTime(targetHz, endHz); timeSum = (uint32_t)entryTime + exitTime; if (timeSum == 0UL) { return targetHz; } weightedEndpoints = ((uint64_t)startHz * startHz * entryTime) + ((uint64_t)endHz * endHz * exitTime); availableArea = (uint64_t)2U * pulseCount * PlsrActiveConfig.defaultSpeedHz * 1000UL; peakSquared = (availableArea + weightedEndpoints) / timeSum; peakHz = PlsrIntegerSquareRoot(peakSquared); if (peakHz < upperEndpoint) { peakHz = upperEndpoint; } if (peakHz > targetHz) { peakHz = targetHz; } return peakHz; } if (targetHz < lowerEndpoint) { entryTime = PlsrShortProfileRampTime(startHz, targetHz); exitTime = PlsrShortProfileRampTime(targetHz, endHz); timeSum = (uint32_t)entryTime + exitTime; if (timeSum == 0UL) { return targetHz; } weightedEndpoints = ((uint64_t)startHz * startHz * entryTime) + ((uint64_t)endHz * endHz * exitTime); availableArea = (uint64_t)2U * pulseCount * PlsrActiveConfig.defaultSpeedHz * 1000UL; if (availableArea >= weightedEndpoints) { return targetHz; } peakSquared = (weightedEndpoints - availableArea) / timeSum; peakHz = PlsrIntegerSquareRoot(peakSquared); if (peakHz < targetHz) { peakHz = targetHz; } if (peakHz > lowerEndpoint) { peakHz = lowerEndpoint; } return peakHz; } return targetHz; } static uint64_t PlsrShortProfileRequiredSteps(uint64_t rampWeight) { uint64_t denominator = (uint64_t)2U * PlsrActiveConfig.defaultSpeedHz * 1000UL; if (rampWeight == 0UL) { return 0UL; } return (rampWeight + denominator - 1UL) / denominator; } static uint32_t PlsrShortProfileReachableFrequency(uint32_t fromHz, uint32_t towardHz, uint16_t pulseCount) { uint16_t baseTime = PlsrShortProfileRampTime(fromHz, towardHz); uint64_t frequencySquared = (uint64_t)fromHz * fromHz; uint64_t changeSquared; uint32_t reachableHz; if ((baseTime == 0U) || (fromHz == towardHz)) { return towardHz; } changeSquared = (uint64_t)2U * pulseCount * PlsrActiveConfig.defaultSpeedHz * 1000UL / baseTime; if (towardHz > fromHz) { reachableHz = PlsrIntegerSquareRoot(frequencySquared + changeSquared); return (reachableHz > towardHz) ? towardHz : reachableHz; } frequencySquared = (changeSquared >= frequencySquared) ? 0UL : (frequencySquared - changeSquared); reachableHz = PlsrIntegerSquareRoot(frequencySquared); if (((uint64_t)reachableHz * reachableHz) < frequencySquared) { reachableHz++; } return (reachableHz < towardHz) ? towardHz : reachableHz; } static uint64_t PlsrCurveIntegralQ32(uint64_t progressQ32, uint16_t curveMode) { uint64_t scaled; uint32_t index; uint32_t fraction; uint64_t first; uint64_t second; const uint32_t *table; if (progressQ32 >= PLSR_Q32_ONE) { return PLSR_Q32_ONE / 2ULL; } if (curveMode == 0U) { return (progressQ32 * progressQ32) >> 33U; } table = (curveMode == 1U) ? PlsrSmoothIntegralQ24 : PlsrSineIntegralQ24; scaled = progressQ32 * 64ULL; index = (uint32_t)(scaled >> 32U); fraction = (uint32_t)scaled; first = (uint64_t)table[index] << 8U; second = (uint64_t)table[index + 1UL] << 8U; return first + (((second - first) * fraction) >> 32U); } static uint64_t PlsrRampAreaQ32(uint32_t fromHz, uint32_t toHz, uint64_t progressQ32) { int64_t delta = (int64_t)toHz - (int64_t)fromHz; int64_t area = (int64_t)((uint64_t)fromHz * progressQ32) + delta * (int64_t)PlsrCurveIntegralQ32( progressQ32, PlsrActiveConfig.curveMode); return (uint64_t)area; } static uint32_t PlsrRampInstantFrequency(uint32_t fromHz, uint32_t toHz, uint64_t progressQ32) { uint64_t curveProgressQ32; uint64_t scaled; uint32_t index; uint32_t tableSlopeQ32; uint32_t gap; const uint32_t *integralTable; if (progressQ32 >= PLSR_Q32_ONE) { return toHz; } if (PlsrActiveConfig.curveMode != 0U) { scaled = progressQ32 * 64ULL; index = (uint32_t)(scaled >> 32U); integralTable = (PlsrActiveConfig.curveMode == 1U) ? PlsrSmoothIntegralQ24 : PlsrSineIntegralQ24; tableSlopeQ32 = (integralTable[index + 1UL] - integralTable[index]) << 14U; curveProgressQ32 = tableSlopeQ32; } else { curveProgressQ32 = progressQ32; } if (toHz >= fromHz) { gap = toHz - fromHz; return fromHz + (uint32_t)(((uint64_t)gap * curveProgressQ32) >> 32U); } gap = fromHz - toHz; return fromHz - (uint32_t)(((uint64_t)gap * curveProgressQ32) >> 32U); } static uint64_t PlsrExactRampBoundaryQ32(uint32_t fromHz, uint32_t toHz, uint64_t previousBoundaryQ32, uint64_t targetAreaQ32) { uint64_t lowerQ32 = previousBoundaryQ32; uint64_t upperQ32 = PLSR_Q32_ONE; uint64_t middleQ32; uint32_t iteration; /* The Q32 domain is 2^32 units wide, so 32 fixed bisections are exact. */ for (iteration = 0UL; iteration < 32UL; iteration++) { middleQ32 = lowerQ32 + ((upperQ32 - lowerQ32) >> 1U); if (PlsrRampAreaQ32(fromHz, toHz, middleQ32) < targetAreaQ32) { lowerQ32 = middleQ32; } else { upperQ32 = middleQ32; } } return upperQ32; } static uint32_t PlsrCountLeadingZeros32(uint32_t value) { #if defined(__ICCARM__) return __CLZ(value); #else uint32_t count = 0U; if ((value & 0xFFFF0000UL) == 0UL) { count += 16U; value <<= 16U; } if ((value & 0xFF000000UL) == 0UL) { count += 8U; value <<= 8U; } if ((value & 0xF0000000UL) == 0UL) { count += 4U; value <<= 4U; } if ((value & 0xC0000000UL) == 0UL) { count += 2U; value <<= 2U; } if ((value & 0x80000000UL) == 0UL) { count++; } return count; #endif } static uint32_t PlsrDivideU64Low(uint32_t highWord, uint32_t lowWord, uint32_t divisor, uint32_t *remainder) { const uint32_t halfBase = 0x10000UL; uint32_t shift = PlsrCountLeadingZeros32(divisor); uint32_t normalizedDivisor = divisor << shift; uint32_t divisorHigh = normalizedDivisor >> 16U; uint32_t divisorLow = normalizedDivisor & 0xFFFFUL; uint32_t normalizedHigh; uint32_t normalizedLow = lowWord << shift; uint32_t lowHigh = normalizedLow >> 16U; uint32_t lowLow = normalizedLow & 0xFFFFUL; uint32_t quotientHigh; uint32_t quotientLow; uint32_t partialRemainder; uint32_t middle; uint32_t normalizedRemainder; if (shift == 0U) { normalizedHigh = highWord; } else { normalizedHigh = (highWord << shift) | (lowWord >> (32U - shift)); } quotientHigh = normalizedHigh / divisorHigh; partialRemainder = normalizedHigh - quotientHigh * divisorHigh; while ((quotientHigh >= halfBase) || (quotientHigh * divisorLow > (partialRemainder << 16U) + lowHigh)) { quotientHigh--; partialRemainder += divisorHigh; if (partialRemainder >= halfBase) { break; } } middle = normalizedHigh * halfBase + lowHigh - quotientHigh * normalizedDivisor; quotientLow = middle / divisorHigh; partialRemainder = middle - quotientLow * divisorHigh; while ((quotientLow >= halfBase) || (quotientLow * divisorLow > (partialRemainder << 16U) + lowLow)) { quotientLow--; partialRemainder += divisorHigh; if (partialRemainder >= halfBase) { break; } } normalizedRemainder = middle * halfBase + lowLow - quotientLow * normalizedDivisor; if (remainder != NULL) { *remainder = normalizedRemainder >> shift; } return quotientHigh * halfBase + quotientLow; } static uint64_t PlsrDivideU64ByU32(uint64_t dividend, uint32_t divisor, uint32_t *remainder) { uint32_t highWord = (uint32_t)(dividend >> 32U); uint32_t lowWord = (uint32_t)dividend; uint32_t quotientHigh = highWord / divisor; uint32_t highRemainder = highWord - quotientHigh * divisor; uint32_t quotientLow = PlsrDivideU64Low(highRemainder, lowWord, divisor, remainder); return ((uint64_t)quotientHigh << 32U) | quotientLow; } static void PlsrPrepareShortProfileBoundaries(PLSR_SHORT_PROFILE *profile) { uint64_t totalAreaQ32; uint64_t areaStepQ32; uint64_t targetAreaQ32; uint32_t areaRemainder; if (profile->entryPulses != 0U) { totalAreaQ32 = PlsrRampAreaQ32( profile->startHz, profile->peakHz, PLSR_Q32_ONE); areaStepQ32 = totalAreaQ32 / profile->entryPulses; areaRemainder = (uint32_t)(totalAreaQ32 % profile->entryPulses); profile->entryFirstBoundaryQ32 = PlsrExactRampBoundaryQ32( profile->startHz, profile->peakHz, 0ULL, areaStepQ32); if (profile->entryPulses > 1U) { targetAreaQ32 = areaStepQ32 * 2ULL + (((uint64_t)areaRemainder * 2ULL) / profile->entryPulses); profile->entrySecondBoundaryQ32 = PlsrExactRampBoundaryQ32( profile->startHz, profile->peakHz, profile->entryFirstBoundaryQ32, targetAreaQ32); } } if (profile->exitPulses != 0U) { totalAreaQ32 = PlsrRampAreaQ32( profile->peakHz, profile->endHz, PLSR_Q32_ONE); areaStepQ32 = totalAreaQ32 / profile->exitPulses; areaRemainder = (uint32_t)(totalAreaQ32 % profile->exitPulses); profile->exitFirstBoundaryQ32 = PlsrExactRampBoundaryQ32( profile->peakHz, profile->endHz, 0ULL, areaStepQ32); if (profile->exitPulses > 1U) { targetAreaQ32 = areaStepQ32 * 2ULL + (((uint64_t)areaRemainder * 2ULL) / profile->exitPulses); profile->exitSecondBoundaryQ32 = PlsrExactRampBoundaryQ32( profile->peakHz, profile->endHz, profile->exitFirstBoundaryQ32, targetAreaQ32); } } } static uint64_t PlsrRampBoundaryQ32(uint32_t fromHz, uint32_t toHz, uint64_t previousBoundaryQ32, uint64_t targetAreaQ32, uint64_t predictedStepQ32) { uint64_t currentAreaQ32; uint64_t candidateQ32; uint64_t candidateAreaQ32; uint64_t differenceQ32; uint64_t correctionQ32; uint32_t derivativeHz; currentAreaQ32 = (previousBoundaryQ32 == 0ULL) ? 0ULL : PlsrRampAreaQ32(fromHz, toHz, previousBoundaryQ32); if (targetAreaQ32 <= currentAreaQ32) { return previousBoundaryQ32 + 1ULL; } if (predictedStepQ32 != 0ULL) { if (predictedStepQ32 >= PLSR_Q32_ONE - previousBoundaryQ32) { candidateQ32 = PLSR_Q32_ONE; } else { candidateQ32 = previousBoundaryQ32 + predictedStepQ32; } } else { derivativeHz = PlsrRampInstantFrequency(fromHz, toHz, previousBoundaryQ32); if (derivativeHz == 0UL) { derivativeHz = 1UL; } differenceQ32 = targetAreaQ32 - currentAreaQ32; correctionQ32 = PlsrDivideU64ByU32(differenceQ32 + derivativeHz - 1UL, derivativeHz, NULL); if (correctionQ32 >= PLSR_Q32_ONE - previousBoundaryQ32) { candidateQ32 = PLSR_Q32_ONE; } else { candidateQ32 = previousBoundaryQ32 + correctionQ32; } } candidateAreaQ32 = PlsrRampAreaQ32(fromHz, toHz, candidateQ32); derivativeHz = PlsrRampInstantFrequency(fromHz, toHz, candidateQ32); if (derivativeHz == 0UL) { derivativeHz = 1UL; } if (candidateAreaQ32 < targetAreaQ32) { differenceQ32 = targetAreaQ32 - candidateAreaQ32; correctionQ32 = PlsrDivideU64ByU32(differenceQ32 + derivativeHz - 1UL, derivativeHz, NULL); if (correctionQ32 >= PLSR_Q32_ONE - candidateQ32) { candidateQ32 = PLSR_Q32_ONE; } else { candidateQ32 += correctionQ32; } } else if (candidateAreaQ32 > targetAreaQ32) { differenceQ32 = candidateAreaQ32 - targetAreaQ32; correctionQ32 = PlsrDivideU64ByU32(differenceQ32, derivativeHz, NULL); if (correctionQ32 == 0ULL) { correctionQ32 = 1ULL; } if (correctionQ32 >= candidateQ32 - previousBoundaryQ32) { candidateQ32 = previousBoundaryQ32 + 1ULL; } else { candidateQ32 -= correctionQ32; } } /* The corrected phase step seeds the next pulse and keeps ISR work fixed. */ return candidateQ32; } static uint32_t PlsrRampAverageFrequency(uint64_t areaIncrementQ32, uint64_t firstBoundaryQ32, uint64_t secondBoundaryQ32) { uint64_t denominator; uint64_t frequencyHz; if (secondBoundaryQ32 <= firstBoundaryQ32) { return 1UL; } denominator = secondBoundaryQ32 - firstBoundaryQ32; if (denominator == PLSR_Q32_ONE) { frequencyHz = (areaIncrementQ32 + denominator / 2ULL) >> 32U; } else { frequencyHz = PlsrDivideU64ByU32( areaIncrementQ32 + denominator / 2ULL, (uint32_t)denominator, NULL); } if (frequencyHz == 0UL) { return 1UL; } if (frequencyHz > PLSR_FREQUENCY_MAX_HZ) { return PLSR_FREQUENCY_MAX_HZ; } return (uint32_t)frequencyHz; } static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, uint8_t segmentNumber, uint32_t startFrequencyHz, uint32_t targetFrequencyHz, uint64_t pulseCount) { const PLSR_SEGMENT_CONFIG *segment; uint32_t endFrequencyHz; uint32_t peakFrequencyHz; uint16_t entryTime; uint16_t exitTime; uint16_t totalPulses; uint64_t entryWeight; uint64_t exitWeight; uint64_t entryRequired; uint64_t exitRequired; uint64_t totalWeight; uint64_t scaledEntry; uint64_t durationWeight; uint64_t singleFrequencyHz; uint16_t directTime; uint64_t directWeight; uint64_t directRequired; (void)memset(profile, 0, sizeof(*profile)); if ((pulseCount == 0UL) || (pulseCount > PLSR_SHORT_PROFILE_MAX_PULSES) || (segmentNumber != PlsrActiveConfig.segmentCount)) { return 0U; } segment = &PlsrActiveConfig.segments[segmentNumber - 1U]; if (segment->jumpSegment != 0U) { return 0U; } endFrequencyHz = PlsrActiveConfig.stopSpeedHz; totalPulses = (uint16_t)pulseCount; directTime = PlsrShortProfileRampTime(startFrequencyHz, endFrequencyHz); directWeight = PlsrShortProfileRampWeight(startFrequencyHz, endFrequencyHz, directTime); directRequired = PlsrShortProfileRequiredSteps(directWeight); if (directRequired > totalPulses) { profile->startHz = startFrequencyHz; profile->peakHz = PlsrShortProfileReachableFrequency( startFrequencyHz, endFrequencyHz, totalPulses); profile->endHz = profile->peakHz; profile->pulseCount = totalPulses; profile->entryPulses = totalPulses; profile->active = 1U; PlsrPrepareShortProfileBoundaries(profile); return 1U; } peakFrequencyHz = PlsrShortProfilePeak(startFrequencyHz, targetFrequencyHz, endFrequencyHz, (uint16_t)pulseCount); entryTime = PlsrShortProfileRampTime(startFrequencyHz, peakFrequencyHz); exitTime = PlsrShortProfileRampTime(peakFrequencyHz, endFrequencyHz); entryWeight = PlsrShortProfileRampWeight(startFrequencyHz, peakFrequencyHz, entryTime); exitWeight = PlsrShortProfileRampWeight(peakFrequencyHz, endFrequencyHz, exitTime); entryRequired = PlsrShortProfileRequiredSteps(entryWeight); exitRequired = PlsrShortProfileRequiredSteps(exitWeight); if ((entryRequired == 0UL) && (exitRequired == 0UL)) { return 0U; } profile->startHz = startFrequencyHz; profile->peakHz = peakFrequencyHz; profile->endHz = endFrequencyHz; profile->pulseCount = totalPulses; if (totalPulses == 1U) { durationWeight = (uint64_t)((peakFrequencyHz > startFrequencyHz) ? (peakFrequencyHz - startFrequencyHz) : (startFrequencyHz - peakFrequencyHz)) * entryTime + (uint64_t)((peakFrequencyHz > endFrequencyHz) ? (peakFrequencyHz - endFrequencyHz) : (endFrequencyHz - peakFrequencyHz)) * exitTime; if (durationWeight == 0UL) { return 0U; } singleFrequencyHz = ((uint64_t)PlsrActiveConfig.defaultSpeedHz * 1000UL + durationWeight / 2UL) / durationWeight; if (singleFrequencyHz == 0UL) { singleFrequencyHz = 1UL; } if (singleFrequencyHz > PLSR_FREQUENCY_MAX_HZ) { singleFrequencyHz = PLSR_FREQUENCY_MAX_HZ; } profile->peakHz = (uint32_t)singleFrequencyHz; profile->steadyPulses = 1U; profile->active = 1U; return 1U; } if ((exitRequired == 0UL) && (peakFrequencyHz != endFrequencyHz)) { exitRequired = 1UL; exitWeight = (uint64_t)2U * PlsrActiveConfig.defaultSpeedHz * 1000UL; } if ((entryRequired + exitRequired) <= totalPulses) { profile->entryPulses = (uint16_t)entryRequired; profile->exitPulses = (uint16_t)exitRequired; profile->steadyPulses = (uint16_t)(totalPulses - profile->entryPulses - profile->exitPulses); } else if (entryRequired == 0UL) { profile->exitPulses = totalPulses; } else if (exitRequired == 0UL) { profile->entryPulses = totalPulses; } else { totalWeight = entryWeight + exitWeight; /* Peak planning bounds both weights by this move's pulse budget. */ scaledEntry = ((uint64_t)totalPulses * entryWeight + totalWeight / 2UL) / totalWeight; if (scaledEntry == 0UL) { scaledEntry = 1UL; } if (scaledEntry >= totalPulses) { scaledEntry = totalPulses - 1U; } profile->entryPulses = (uint16_t)scaledEntry; profile->exitPulses = (uint16_t)(totalPulses - profile->entryPulses); } profile->active = 1U; PlsrPrepareShortProfileBoundaries(profile); return 1U; } static uint32_t PlsrShortProfileTakeFrequency(PLSR_SHORT_PROFILE *profile) { uint16_t period = profile->nextPeriod; uint16_t relativePeriod; uint16_t rampPulseCount; uint32_t fromHz; uint32_t toHz; uint64_t firstBoundaryQ32; uint64_t secondBoundaryQ32; uint64_t previousTargetAreaQ32; uint64_t areaIncrementQ32; uint64_t firstCachedBoundaryQ32; uint64_t secondCachedBoundaryQ32; uint32_t frequencyHz; if ((profile->active == 0U) || (period >= profile->pulseCount)) { return (profile->endHz == 0UL) ? 1UL : profile->endHz; } profile->nextPeriod = (uint16_t)(period + 1U); if (period < profile->entryPulses) { relativePeriod = period; rampPulseCount = profile->entryPulses; fromHz = profile->startHz; toHz = profile->peakHz; firstCachedBoundaryQ32 = profile->entryFirstBoundaryQ32; secondCachedBoundaryQ32 = profile->entrySecondBoundaryQ32; firstBoundaryQ32 = (relativePeriod == 0U) ? 0ULL : profile->rampBoundaryQ32; } else if (period < (uint16_t)(profile->entryPulses + profile->steadyPulses)) { profile->lastRampFrequencyHz = 0UL; return (profile->peakHz == 0UL) ? 1UL : profile->peakHz; } else { relativePeriod = (uint16_t)(period - profile->entryPulses - profile->steadyPulses); rampPulseCount = profile->exitPulses; fromHz = profile->peakHz; toHz = profile->endHz; firstCachedBoundaryQ32 = profile->exitFirstBoundaryQ32; secondCachedBoundaryQ32 = profile->exitSecondBoundaryQ32; firstBoundaryQ32 = (relativePeriod == 0U) ? 0ULL : profile->rampBoundaryQ32; } if (relativePeriod == 0U) { profile->rampTotalAreaQ32 = PlsrRampAreaQ32(fromHz, toHz, PLSR_Q32_ONE); profile->rampAreaStepQ32 = PlsrDivideU64ByU32(profile->rampTotalAreaQ32, rampPulseCount, &profile->rampAreaRemainder); profile->rampRemainderAccumulator = 0UL; profile->rampTargetAreaQ32 = 0ULL; profile->rampBoundaryQ32 = 0ULL; profile->lastRampPhaseStepQ32 = 0ULL; profile->lastRampFrequencyHz = 0UL; } previousTargetAreaQ32 = profile->rampTargetAreaQ32; profile->rampTargetAreaQ32 += profile->rampAreaStepQ32; profile->rampRemainderAccumulator += profile->rampAreaRemainder; if (profile->rampRemainderAccumulator >= rampPulseCount) { profile->rampTargetAreaQ32++; profile->rampRemainderAccumulator -= rampPulseCount; } if ((uint16_t)(relativePeriod + 1U) >= rampPulseCount) { profile->rampTargetAreaQ32 = profile->rampTotalAreaQ32; secondBoundaryQ32 = PLSR_Q32_ONE; } else if (relativePeriod == 0U) { secondBoundaryQ32 = firstCachedBoundaryQ32; } else if (relativePeriod == 1U) { secondBoundaryQ32 = secondCachedBoundaryQ32; } else { secondBoundaryQ32 = PlsrRampBoundaryQ32( fromHz, toHz, firstBoundaryQ32, profile->rampTargetAreaQ32, (relativePeriod < 2U) ? 0ULL : profile->lastRampPhaseStepQ32); } areaIncrementQ32 = profile->rampTargetAreaQ32 - previousTargetAreaQ32; profile->rampBoundaryQ32 = secondBoundaryQ32; profile->lastRampPhaseStepQ32 = secondBoundaryQ32 - firstBoundaryQ32; frequencyHz = PlsrRampAverageFrequency(areaIncrementQ32, firstBoundaryQ32, secondBoundaryQ32); if ((profile->lastRampFrequencyHz != 0UL) && (((toHz > fromHz) && (frequencyHz < profile->lastRampFrequencyHz)) || ((toHz < fromHz) && (frequencyHz > profile->lastRampFrequencyHz)))) { frequencyHz = profile->lastRampFrequencyHz; } profile->lastRampFrequencyHz = frequencyHz; return frequencyHz; } static void PlsrCopyShortProfile(PLSR_SHORT_PROFILE *destination, const PLSR_SHORT_PROFILE *source) { destination->active = 0U; destination->startHz = source->startHz; destination->peakHz = source->peakHz; destination->endHz = source->endHz; destination->pulseCount = source->pulseCount; destination->entryPulses = source->entryPulses; destination->steadyPulses = source->steadyPulses; destination->exitPulses = source->exitPulses; destination->nextPeriod = source->nextPeriod; destination->rampBoundaryQ32 = source->rampBoundaryQ32; destination->rampTotalAreaQ32 = source->rampTotalAreaQ32; destination->rampTargetAreaQ32 = source->rampTargetAreaQ32; destination->rampAreaStepQ32 = source->rampAreaStepQ32; destination->rampAreaRemainder = source->rampAreaRemainder; destination->rampRemainderAccumulator = source->rampRemainderAccumulator; destination->lastRampPhaseStepQ32 = source->lastRampPhaseStepQ32; destination->entryFirstBoundaryQ32 = source->entryFirstBoundaryQ32; destination->entrySecondBoundaryQ32 = source->entrySecondBoundaryQ32; destination->exitFirstBoundaryQ32 = source->exitFirstBoundaryQ32; destination->exitSecondBoundaryQ32 = source->exitSecondBoundaryQ32; destination->lastRampFrequencyHz = source->lastRampFrequencyHz; destination->active = source->active; } static void PlsrInvalidateHandoffPlans(void) { uint8_t bank; uint8_t index; uint32_t criticalState = PlsrPlatformEnterCritical(); PlsrHandoffPlan.valid = 0U; for (bank = 0U; bank < 2U; bank++) { for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) { PlsrPreparedHandoffPlans[bank][index].valid = 0U; } } PlsrPlatformExitCritical(criticalState); } static uint8_t PlsrAdvanceShortProfile(uint8_t pulseOutput) { uint32_t requestedFrequencyHz; uint32_t actualFrequencyHz; if ((PlsrShortProfile.active == 0U) || (PlsrShortProfile.nextPeriod >= PlsrShortProfile.pulseCount)) { return 1U; } requestedFrequencyHz = PlsrShortProfileTakeFrequency(&PlsrShortProfile); PlsrDeferredFrequencyPending = 0U; if (PlsrPlatformQueueFrequency(pulseOutput, requestedFrequencyHz, &actualFrequencyHz) == 0U) { return 0U; } PlsrQueuedFrequencyHz = actualFrequencyHz; if (actualFrequencyHz > PlsrCurrentFrequencyHz) { PlsrRunStatus = PLSR_STATUS_ACCELERATING; } else if (actualFrequencyHz < PlsrCurrentFrequencyHz) { PlsrRunStatus = PLSR_STATUS_DECELERATING; } else { PlsrRunStatus = PLSR_STATUS_RUNNING; } return 1U; } static void PlsrMaybePlanBoundaryRamp(uint32_t expectedEpoch) { const PLSR_SEGMENT_CONFIG *segment; uint8_t nextSegment; uint8_t nextPositive; uint8_t hasNext; uint32_t targetHz; uint32_t criticalState; uint64_t estimate; uint64_t remaining; if ((PlsrPulseActive == 0U) || (PlsrBoundaryRampStarted != 0U) || (PlsrShortProfile.active != 0U) || (PlsrHandoffPlan.valid != 0U) || (PlsrStopRequested != 0U) || (PlsrCurrentSegment == 0U)) { return; } segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; hasNext = PlsrGetNextSegment(&nextSegment); targetHz = PlsrActiveConfig.stopSpeedHz; if ((PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) && (hasNext != 0U) && (segment->waitType == PLSR_EXT_OR_COMPLETE)) { if ((PlsrPredictNextDirection(nextSegment, &nextPositive) != 0U) && (nextPositive == PlsrCountPositive)) { targetHz = PlsrActiveConfig.segments[nextSegment - 1U].frequencyHz; } } estimate = PlsrRampPulseEstimate(PlsrCurrentFrequencyHz, targetHz); criticalState = PlsrPlatformEnterCritical(); remaining = PlsrRemainingPulses; if ((PlsrSegmentEpoch != expectedEpoch) || (PlsrBoundaryPending != 0U) || (PlsrPulseActive == 0U) || (PlsrBoundaryRampStarted != 0U) || (PlsrShortProfile.active != 0U) || (PlsrHandoffPlan.valid != 0U) || (PlsrStopRequested != 0U)) { PlsrPlatformExitCritical(criticalState); return; } if (remaining > estimate) { PlsrPlatformExitCritical(criticalState); return; } PlsrBoundaryRampStarted = 1U; PlsrRampStart(PlsrCurrentFrequencyHz, targetHz); PlsrPlatformExitCritical(criticalState); if (PlsrRamp.active == 0U) { (void)PlsrApplyFrequency(targetHz, expectedEpoch); } } static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) { uint32_t targetFrequencyHz = PlsrActiveConfig.segments[PlsrCurrentSegment - 1U].frequencyHz; uint32_t firstFrequencyHz; uint32_t secondFrequencyHz; PlsrSegmentClockStarted = 1U; PlsrSegmentElapsedMs = 0UL; if (PlsrPrepareShortProfile(&PlsrShortProfile, PlsrCurrentSegment, startFrequencyHz, targetFrequencyHz, PlsrRemainingSnapshot()) != 0U) { PlsrRamp.active = 0U; firstFrequencyHz = PlsrShortProfileTakeFrequency(&PlsrShortProfile); secondFrequencyHz = (PlsrShortProfile.nextPeriod < PlsrShortProfile.pulseCount) ? PlsrShortProfileTakeFrequency(&PlsrShortProfile) : firstFrequencyHz; if (secondFrequencyHz > firstFrequencyHz) { PlsrRunStatus = PLSR_STATUS_ACCELERATING; } else if (secondFrequencyHz < firstFrequencyHz) { PlsrRunStatus = PLSR_STATUS_DECELERATING; } else { PlsrRunStatus = PLSR_STATUS_RUNNING; } if (PlsrApplyFrequencyPair(firstFrequencyHz, secondFrequencyHz, PlsrSegmentEpoch) == 0U) { PlsrShortProfile.active = 0U; return 0U; } return 1U; } PlsrRampStart(startFrequencyHz, targetFrequencyHz); if (PlsrRamp.active == 0U) { if (PlsrApplyFrequency(targetFrequencyHz, PlsrSegmentEpoch) == 0U) { return 0U; } PlsrRunStatus = PLSR_STATUS_RUNNING; } else if ((startFrequencyHz != 0UL) && (PlsrApplyFrequency(startFrequencyHz, PlsrSegmentEpoch) == 0U)) { return 0U; } return 1U; } static uint8_t PlsrStartSegment(uint8_t segmentNumber, uint8_t allowCarry, uint32_t carryFrequencyHz) { uint32_t criticalState; int32_t position; int64_t displacement; uint64_t magnitude; uint8_t positive; uint8_t directionLevel; uint8_t directionChanged; uint32_t startFrequencyHz; if ((segmentNumber == 0U) || (segmentNumber > PlsrActiveConfig.segmentCount)) { return 0U; } criticalState = PlsrPlatformEnterCritical(); position = PlsrPosition; PlsrPlatformExitCritical(criticalState); displacement = PlsrSegmentDisplacement(segmentNumber, position); positive = (displacement >= 0) ? 1U : 0U; magnitude = (displacement < 0) ? (uint64_t)(-displacement) : (uint64_t)displacement; PlsrSegmentEpoch++; PlsrCurrentSegment = segmentNumber; PlsrSegmentClockStarted = 0U; PlsrSegmentElapsedMs = 0UL; PlsrWaitElapsedMs = 0UL; PlsrBoundaryRampStarted = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCutRequested = 0U; PlsrStopPulsesRemaining = 0U; PlsrFrequencyUpdatePending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrHandoffPlan.valid = 0U; PlsrShortProfile.active = 0U; PlsrShortProfile.nextPeriod = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrExtEdgePending = 0U; PlsrExtPreviousLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); criticalState = PlsrPlatformEnterCritical(); PlsrRemainingPulses = magnitude; PlsrCountPositive = positive; PlsrPlatformExitCritical(criticalState); if ((PlsrActiveConfig.segments[segmentNumber - 1U].waitType == PLSR_ACT_TIME) && (PlsrActiveConfig.segments[segmentNumber - 1U].actTimeMs == 0U)) { PlsrSegmentClockStarted = 1U; PlsrRunStatus = PLSR_STATUS_RUNNING; PlsrBoundaryFrequencyHz = (allowCarry != 0U) ? carryFrequencyHz : 0UL; PlsrBoundaryWasCut = 1U; PlsrBoundaryPending = 1U; return 1U; } if (magnitude == 0UL) { PlsrSegmentClockStarted = 1U; PlsrRunStatus = PLSR_STATUS_RUNNING; PlsrBoundaryFrequencyHz = 0UL; PlsrBoundaryPending = 1U; return 1U; } directionLevel = positive; if (PlsrActiveConfig.directionNegativeLogic != 0U) { directionLevel ^= 1U; } directionChanged = ((PlsrLastDirectionValid == 0U) || (PlsrLastDirectionOutput != (uint8_t)PlsrActiveConfig.directionOutput) || (PlsrLastDirectionLevel != directionLevel)) ? 1U : 0U; if (PlsrPlatformPrepare((uint8_t)PlsrActiveConfig.pulseOutput, (uint8_t)PlsrActiveConfig.directionOutput, directionLevel) == 0U) { return 0U; } PlsrLastDirectionValid = 1U; PlsrLastDirectionOutput = (uint8_t)PlsrActiveConfig.directionOutput; PlsrLastDirectionLevel = directionLevel; if ((allowCarry != 0U) && (directionChanged == 0U) && (carryFrequencyHz != 0UL)) { startFrequencyHz = carryFrequencyHz; } else { startFrequencyHz = PlsrActiveConfig.startSpeedHz; } if ((directionChanged != 0U) && (PlsrActiveConfig.directionDelayMs != 0U)) { PlsrDirectionDelayActive = 1U; PlsrDirectionDelayRemainingMs = PlsrActiveConfig.directionDelayMs; PlsrRunStatus = PLSR_STATUS_ACCELERATING; return 1U; } PlsrDirectionDelayActive = 0U; return PlsrBeginSegmentOutput(startFrequencyHz); } static void PlsrMarkPersistenceDirty(uint16_t delayMs) { PlsrPersistenceDirty = 1U; PlsrPersistenceDelayMs = delayMs; } static void PlsrCheckpointPosition(uint8_t wasBusy) { uint32_t criticalState; int32_t position; uint8_t positionValid; criticalState = PlsrPlatformEnterCritical(); position = PlsrPosition; positionValid = PlsrPositionValid; PlsrPositionCheckpointDirty = 0U; PlsrPlatformExitCritical(criticalState); PlsrPlatformCheckpointPosition(position, positionValid, wasBusy); PlsrPositionCheckpointElapsedMs = 0U; } static void PlsrPollPositionCheckpoint(void) { if (PlsrPositionCheckpointDirty == 0U) { PlsrPositionCheckpointElapsedMs = 0U; return; } if (PlsrPositionCheckpointElapsedMs < PLSR_POSITION_CHECKPOINT_MS) { PlsrPositionCheckpointElapsedMs++; } if (PlsrPositionCheckpointElapsedMs >= PLSR_POSITION_CHECKPOINT_MS) { PlsrCheckpointPosition(1U); } } static void PlsrFinishCompleted(void) { PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); PlsrRemainingPulses = 0UL; PlsrPulseActive = 0U; PlsrCutRequested = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrCurrentSegment = 0U; PlsrSegmentClockStarted = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrExtEdgePending = 0U; PlsrStopRequested = 0U; PlsrStopPulsesRemaining = 0U; PlsrSeamlessHandoffPending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrTimerErrorPending = 0U; PlsrRamp.active = 0U; PlsrInvalidateHandoffPlans(); PlsrRunStatus = PLSR_STATUS_COMPLETED; PlsrError = PLSR_ERROR_NONE; PlsrCheckpointPosition(0U); PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); } static void PlsrFinishStopped(void) { PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); PlsrRemainingPulses = 0UL; PlsrPulseActive = 0U; PlsrCutRequested = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrCurrentSegment = 0U; PlsrSegmentClockStarted = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrExtEdgePending = 0U; PlsrStopRequested = 0U; PlsrStopPulsesRemaining = 0U; PlsrSeamlessHandoffPending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrTimerErrorPending = 0U; PlsrRamp.active = 0U; PlsrInvalidateHandoffPlans(); PlsrRunStatus = PLSR_STATUS_STOPPED; PlsrError = PLSR_ERROR_NONE; PlsrCheckpointPosition(0U); PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); } static void PlsrEnterError(PLSR_ERROR error) { PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); PlsrRemainingPulses = 0UL; PlsrPulseActive = 0U; PlsrCutRequested = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrCurrentSegment = 0U; PlsrSegmentClockStarted = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrExtEdgePending = 0U; PlsrStopRequested = 0U; PlsrStopPulsesRemaining = 0U; PlsrSeamlessHandoffPending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrTimerErrorPending = 0U; PlsrRamp.active = 0U; PlsrInvalidateHandoffPlans(); PlsrRunStatus = PLSR_STATUS_ERROR; PlsrError = error; PlsrCheckpointPosition(0U); PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); } static void PlsrTransitionToNext(uint8_t allowCarry) { uint8_t nextSegment; uint32_t carryFrequencyHz = PlsrBoundaryFrequencyHz; if (PlsrGetNextSegment(&nextSegment) == 0U) { PlsrFinishCompleted(); return; } if (PlsrStartSegment(nextSegment, allowCarry, carryFrequencyHz) == 0U) { PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); } } static uint8_t PlsrBuildHandoffPlan(uint8_t sourceSegment, const PLSR_CONFIG *frequencyConfig, PLSR_HANDOFF_PLAN *plan) { const PLSR_SEGMENT_CONFIG *segment; uint8_t nextSegment; int64_t displacement; uint8_t positive; plan->valid = 0U; if ((PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) || (sourceSegment == 0U) || (sourceSegment > PlsrActiveConfig.segmentCount)) { return 0U; } segment = &PlsrActiveConfig.segments[sourceSegment - 1U]; if (segment->waitType != PLSR_EXT_OR_COMPLETE) { return 0U; } if (segment->jumpSegment != 0U) { nextSegment = (uint8_t)segment->jumpSegment; } else if (sourceSegment < PlsrActiveConfig.segmentCount) { nextSegment = (uint8_t)(sourceSegment + 1U); } else { return 0U; } if (PlsrActiveConfig.positionMode == PLSR_POSITION_ABSOLUTE) { displacement = (int64_t)PlsrActiveConfig.segments[nextSegment - 1U].pulses - (int64_t)segment->pulses; } else { displacement = PlsrActiveConfig.segments[nextSegment - 1U].pulses; } if (displacement == 0) { return 0U; } positive = (displacement > 0) ? 1U : 0U; plan->magnitude = (displacement < 0) ? (uint64_t)(-displacement) : (uint64_t)displacement; plan->firstFrequencyHz = frequencyConfig->segments[nextSegment - 1U].frequencyHz; if (PlsrPrepareShortProfile(&plan->profile, nextSegment, plan->firstFrequencyHz, plan->firstFrequencyHz, plan->magnitude) != 0U) { plan->firstFrequencyHz = PlsrShortProfileTakeFrequency(&plan->profile); } plan->nextSegment = nextSegment; plan->positive = positive; plan->valid = 1U; return 1U; } static uint8_t PlsrBuildHandoffPlanBank( const PLSR_CONFIG *frequencyConfig) { uint8_t buildBank = (uint8_t)(PlsrPreparedHandoffBank ^ 1U); uint8_t sourceSegment; PLSR_HANDOFF_PLAN *destination; for (sourceSegment = 0U; sourceSegment < PLSR_SEGMENT_COUNT_MAX; sourceSegment++) { PlsrPreparedHandoffPlans[buildBank][sourceSegment].valid = 0U; } for (sourceSegment = 1U; sourceSegment <= PlsrActiveConfig.segmentCount; sourceSegment++) { destination = &PlsrPreparedHandoffPlans[buildBank][sourceSegment - 1U]; (void)PlsrBuildHandoffPlan(sourceSegment, frequencyConfig, destination); } return buildBank; } static uint8_t PlsrSelectPreparedHandoffPlan(PLSR_HANDOFF_PLAN *plan) { const PLSR_HANDOFF_PLAN *prepared; uint8_t preparedBank; uint8_t currentSegment; plan->valid = 0U; currentSegment = PlsrCurrentSegment; if ((PlsrRemainingPulses != 1UL) || (PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) || (PlsrStopRequested != 0U) || (PlsrCountOverflowPending != 0U) || (PlsrCutRequested != 0U) || (currentSegment == 0U) || (currentSegment > PlsrActiveConfig.segmentCount)) { return 0U; } preparedBank = PlsrPreparedHandoffBank; prepared = &PlsrPreparedHandoffPlans[preparedBank][currentSegment - 1U]; if ((prepared->valid == 0U) || (prepared->positive != PlsrCountPositive)) { return 0U; } plan->magnitude = prepared->magnitude; plan->firstFrequencyHz = prepared->firstFrequencyHz; PlsrCopyShortProfile(&plan->profile, &prepared->profile); plan->nextSegment = prepared->nextSegment; plan->positive = prepared->positive; plan->valid = 1U; return 1U; } static uint8_t PlsrPrimeHandoff(void) { PLSR_HANDOFF_PLAN candidatePlan; uint32_t actualFrequencyHz; PlsrHandoffPlan.valid = 0U; if (PlsrSelectPreparedHandoffPlan(&candidatePlan) == 0U) { return 0U; } PlsrDeferredFrequencyPending = 0U; if (PlsrPlatformQueueFrequency( (uint8_t)PlsrActiveConfig.pulseOutput, candidatePlan.firstFrequencyHz, &actualFrequencyHz) == 0U) { PlsrTimerErrorPending = 1U; return 0U; } PlsrQueuedFrequencyHz = actualFrequencyHz; PlsrHandoffPlan.magnitude = candidatePlan.magnitude; PlsrHandoffPlan.firstFrequencyHz = actualFrequencyHz; PlsrCopyShortProfile(&PlsrHandoffPlan.profile, &candidatePlan.profile); PlsrHandoffPlan.nextSegment = candidatePlan.nextSegment; PlsrHandoffPlan.positive = candidatePlan.positive; PlsrHandoffPlan.valid = 1U; return 1U; } static uint8_t PlsrTrySubsequentHandoff(void) { uint32_t requestedQueuedFrequencyHz; uint32_t actualQueuedFrequencyHz; uint32_t currentFrequencyHz; uint32_t queuedFrequencyHz; uint8_t nextSegment = PlsrHandoffPlan.nextSegment; uint64_t magnitude = PlsrHandoffPlan.magnitude; uint8_t positive = PlsrHandoffPlan.positive; if ((PlsrHandoffPlan.valid == 0U) || (PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) || (PlsrStopRequested != 0U) || (PlsrCountOverflowPending != 0U) || (PlsrCutRequested != 0U) || (PlsrCurrentFrequencyHz != PlsrHandoffPlan.firstFrequencyHz) || (nextSegment == 0U) || (nextSegment > PlsrActiveConfig.segmentCount)) { return 0U; } PlsrCopyShortProfile(&PlsrShortProfile, &PlsrHandoffPlan.profile); requestedQueuedFrequencyHz = PlsrCurrentFrequencyHz; if ((PlsrShortProfile.active != 0U) && (PlsrShortProfile.nextPeriod < PlsrShortProfile.pulseCount)) { requestedQueuedFrequencyHz = PlsrShortProfileTakeFrequency(&PlsrShortProfile); } PlsrDeferredFrequencyPending = 0U; if (PlsrPlatformQueueFrequency( (uint8_t)PlsrActiveConfig.pulseOutput, requestedQueuedFrequencyHz, &actualQueuedFrequencyHz) == 0U) { PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrTimerErrorPending = 1U; return 0U; } PlsrQueuedFrequencyHz = actualQueuedFrequencyHz; PlsrSegmentEpoch++; PlsrCurrentSegment = nextSegment; PlsrRemainingPulses = magnitude; PlsrCountPositive = positive; PlsrBoundaryFrequencyHz = PlsrCurrentFrequencyHz; PlsrSegmentClockStarted = 1U; PlsrSegmentElapsedMs = 0UL; PlsrWaitElapsedMs = 0UL; PlsrBoundaryRampStarted = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCutRequested = 0U; PlsrFrequencyUpdatePending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrExtEdgePending = 0U; PlsrExtPreviousLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); PlsrSeamlessHandoffPending = 1U; PlsrHandoffPlan.valid = 0U; if (PlsrRemainingPulses == 1UL) { (void)PlsrPrimeHandoff(); } queuedFrequencyHz = PlsrQueuedFrequencyHz; currentFrequencyHz = PlsrCurrentFrequencyHz; if (PlsrShortProfile.active != 0U) { if (queuedFrequencyHz > currentFrequencyHz) { PlsrRunStatus = PLSR_STATUS_ACCELERATING; } else if (queuedFrequencyHz < currentFrequencyHz) { PlsrRunStatus = PLSR_STATUS_DECELERATING; } else { PlsrRunStatus = PLSR_STATUS_RUNNING; } } else { PlsrRunStatus = PLSR_STATUS_RUNNING; } return 1U; } static void PlsrHandleBoundary(uint8_t extEdge) { const PLSR_SEGMENT_CONFIG *segment; uint8_t wasCut = PlsrBoundaryWasCut; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrPulseActive = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrDeferredFrequencyPending = 0U; PlsrCheckpointPosition(1U); if (PlsrTimerErrorPending != 0U) { PlsrTimerErrorPending = 0U; PlsrEnterError(PLSR_ERROR_TIMER); return; } if (PlsrCountOverflowPending != 0U) { PlsrCountOverflowPending = 0U; PlsrEnterError(PLSR_ERROR_COUNT); return; } if (PlsrStopRequested != 0U) { PlsrFinishStopped(); return; } if ((PlsrCurrentSegment == 0U) || (PlsrCurrentSegment > PlsrActiveConfig.segmentCount)) { PlsrEnterError(PLSR_ERROR_INTERNAL); return; } segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; if (wasCut != 0U) { PlsrTransitionToNext( (PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) ? 1U : 0U); return; } switch (segment->waitType) { case PLSR_WAIT_TIME: PlsrWaitElapsedMs = 0UL; PlsrRunStatus = PLSR_STATUS_WAITING; break; case PLSR_WAIT_SIGNAL: if (PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.waitInput) != 0U) { PlsrTransitionToNext(0U); } else { PlsrRunStatus = PLSR_STATUS_WAITING; } break; case PLSR_ACT_TIME: if (PlsrSegmentElapsedMs >= segment->actTimeMs) { PlsrTransitionToNext(0U); } else { PlsrRunStatus = PLSR_STATUS_WAITING; } break; case PLSR_EXT_SIGNAL: if (extEdge != 0U) { PlsrTransitionToNext(0U); } else { PlsrRunStatus = PLSR_STATUS_WAITING; } break; case PLSR_EXT_OR_COMPLETE: PlsrTransitionToNext( (PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) ? 1U : 0U); break; default: PlsrEnterError(PLSR_ERROR_INTERNAL); break; } } static void PlsrRequestCut(uint32_t expectedEpoch) { uint32_t criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch != expectedEpoch) { PlsrPlatformExitCritical(criticalState); return; } if (PlsrBoundaryPending != 0U) { PlsrBoundaryWasCut = 1U; } else if (PlsrPulseActive != 0U) { PlsrCutRequested = 1U; } else { PlsrBoundaryFrequencyHz = PlsrCurrentFrequencyHz; PlsrBoundaryWasCut = 1U; PlsrBoundaryPending = 1U; } PlsrPlatformExitCritical(criticalState); } static void PlsrPollWaiting(uint8_t extEdge) { const PLSR_SEGMENT_CONFIG *segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; switch (segment->waitType) { case PLSR_WAIT_TIME: PlsrWaitElapsedMs++; if (PlsrWaitElapsedMs >= segment->waitTimeMs) { PlsrTransitionToNext(0U); } break; case PLSR_WAIT_SIGNAL: if (PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.waitInput) != 0U) { PlsrTransitionToNext(0U); } break; case PLSR_ACT_TIME: if (PlsrSegmentElapsedMs >= segment->actTimeMs) { PlsrTransitionToNext(0U); } break; case PLSR_EXT_SIGNAL: if (extEdge != 0U) { PlsrTransitionToNext(0U); } break; default: PlsrEnterError(PLSR_ERROR_INTERNAL); break; } } static void PlsrPollPersistence(void) { PLSR_PERSIST_PAYLOAD payload; uint32_t criticalState; if ((PlsrPersistenceDirty == 0U) || (PlsrIsBusy() != 0U)) { return; } if (PlsrPersistenceDelayMs != 0U) { PlsrPersistenceDelayMs--; return; } payload.config = PlsrShadowConfig; criticalState = PlsrPlatformEnterCritical(); payload.position = PlsrPosition; payload.positionValid = PlsrPositionValid; PlsrPlatformExitCritical(criticalState); payload.wasBusy = 0U; payload.reserved = 0U; if (PlsrPlatformSave(&payload) != 0U) { PlsrPersistenceDirty = 0U; } else { PlsrRunStatus = PLSR_STATUS_ERROR; PlsrError = PLSR_ERROR_INTERNAL; } } uint8_t PlsrInit(void) { PLSR_PERSIST_PAYLOAD payload; PlsrInitialized = 0U; PlsrRunStatus = PLSR_STATUS_UNINITIALIZED; if (PlsrPlatformInit() == 0U) { return 0U; } if ((PlsrPlatformLoad(&payload) == 0U) || (PlsrConfigIsValid(&payload.config, 0U) == 0U) || (payload.positionValid > 1U) || (payload.wasBusy > 1U)) { PlsrSetDefaults(&PlsrShadowConfig); PlsrPosition = 0L; PlsrPositionValid = 1U; PlsrPlatformCheckpointConfig(&PlsrShadowConfig); PlsrPlatformCheckpointPosition(0L, 1U, 0U); PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); } else { PlsrShadowConfig = payload.config; PlsrPosition = payload.position; PlsrPositionValid = ((payload.positionValid != 0U) && (payload.wasBusy == 0U)) ? 1U : 0U; PlsrPersistenceDirty = 0U; PlsrPersistenceDelayMs = 0U; } (void)memset(&PlsrActiveConfig, 0, sizeof(PlsrActiveConfig)); (void)memset(&PlsrRamp, 0, sizeof(PlsrRamp)); (void)memset(&PlsrShortProfile, 0, sizeof(PlsrShortProfile)); (void)memset(&PlsrHandoffPlan, 0, sizeof(PlsrHandoffPlan)); (void)memset(PlsrPreparedHandoffPlans, 0, sizeof(PlsrPreparedHandoffPlans)); PlsrPreparedHandoffBank = 0U; PlsrRemainingPulses = 0UL; PlsrPulseActive = 0U; PlsrCutRequested = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrCountOverflowPending = 0U; PlsrPositionCheckpointDirty = 0U; PlsrFrequencyUpdatePending = 0U; PlsrDeferredFrequencyPending = 0U; PlsrFrequencyUpdateSegment = 0U; PlsrSeamlessHandoffPending = 0U; PlsrTimerErrorPending = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrBoundaryFrequencyHz = 0UL; PlsrFrequencyUpdateTargetHz = 0UL; PlsrDeferredFrequencyHz = 0UL; PlsrSegmentEpoch = 0UL; PlsrCurrentSegment = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrSegmentClockStarted = 0U; PlsrExtPreviousLevel = 0U; PlsrExtEdgePending = 0U; PlsrStopRequested = 0U; PlsrStopPulsesRemaining = 0U; PlsrLastDirectionValid = 0U; PlsrPositionCheckpointElapsedMs = 0U; PlsrCommandMailbox.command = 0U; PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EMPTY; PlsrError = PLSR_ERROR_NONE; PlsrRunStatus = PLSR_STATUS_IDLE; PlsrInitialized = 1U; return 1U; } static PLSR_MB_RESULT PlsrQueueCommand(uint16_t command) { uint32_t criticalState; PLSR_MB_RESULT result = PLSR_MB_OK; if (PlsrInitialized == 0U) { return PLSR_MB_SERVER_FAILURE; } criticalState = PlsrPlatformEnterCritical(); /* Capacity one: repeats acknowledge the first command; conflicts wait. */ if (PlsrCommandMailbox.state != PLSR_COMMAND_MAILBOX_EMPTY) { result = (PlsrCommandMailbox.command == command) ? PLSR_MB_OK : PLSR_MB_DEVICE_BUSY; PlsrPlatformExitCritical(criticalState); return result; } if (command == PLSR_COMMAND_START) { if ((PlsrIsBusy() != 0U) || (PlsrRunStatus == PLSR_STATUS_ERROR)) { result = PLSR_MB_DEVICE_BUSY; } else if ((PlsrRunStatus != PLSR_STATUS_IDLE) && (PlsrRunStatus != PLSR_STATUS_COMPLETED) && (PlsrRunStatus != PLSR_STATUS_STOPPED)) { result = PLSR_MB_ILLEGAL_VALUE; } else if ((PlsrConfigIsValid(&PlsrShadowConfig, 1U) == 0U) || ((PlsrShadowConfig.positionMode == PLSR_POSITION_ABSOLUTE) && (PlsrPositionValid == 0U))) { result = PLSR_MB_ILLEGAL_VALUE; } else { PlsrCommandMailbox.startConfig = PlsrShadowConfig; } } else if ((command == PLSR_COMMAND_CLEAR) && (PlsrIsBusy() != 0U)) { result = PLSR_MB_DEVICE_BUSY; } if (result == PLSR_MB_OK) { PlsrCommandMailbox.command = command; PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_PENDING; } PlsrPlatformExitCritical(criticalState); return result; } static void PlsrExecuteStart(void) { uint32_t criticalState; uint8_t handoffBank; PlsrActiveConfig = PlsrCommandMailbox.startConfig; handoffBank = PlsrBuildHandoffPlanBank(&PlsrActiveConfig); criticalState = PlsrPlatformEnterCritical(); PlsrPreparedHandoffBank = handoffBank; PlsrHandoffPlan.valid = 0U; PlsrPlatformExitCritical(criticalState); PlsrStopRequested = 0U; PlsrTimerErrorPending = 0U; PlsrShortProfile.active = 0U; PlsrError = PLSR_ERROR_NONE; PlsrLastDirectionValid = 0U; PlsrCheckpointPosition(1U); if (PlsrStartSegment((uint8_t)PlsrActiveConfig.startSegment, 0U, 0UL) == 0U) { PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); } } static uint8_t PlsrExecuteStop(void) { uint32_t criticalState; uint32_t stopTargetHz; if (PlsrIsBusy() == 0U) { return 0U; } criticalState = PlsrPlatformEnterCritical(); if (PlsrStopRequested != 0U) { PlsrPlatformExitCritical(criticalState); return 0U; } PlsrStopRequested = 1U; PlsrStopPulsesRemaining = 0U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrInvalidateHandoffPlans(); if (PlsrBoundaryPending != 0U) { PlsrPlatformExitCritical(criticalState); return 1U; } if (PlsrPulseActive == 0U) { PlsrPlatformExitCritical(criticalState); PlsrFinishStopped(); return 1U; } stopTargetHz = PlsrActiveConfig.stopSpeedHz; if (stopTargetHz > PlsrCurrentFrequencyHz) { stopTargetHz = PlsrCurrentFrequencyHz; } PlsrRampStart(PlsrCurrentFrequencyHz, stopTargetHz); PlsrRunStatus = PLSR_STATUS_DECELERATING; if (PlsrRamp.active == 0U) { if (PlsrApplyFrequency(stopTargetHz, PlsrSegmentEpoch) == 0U) { PlsrPlatformExitCritical(criticalState); PlsrEnterError(PLSR_ERROR_TIMER); return 1U; } PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); } PlsrPlatformExitCritical(criticalState); return 1U; } static void PlsrExecuteClear(void) { uint32_t criticalState; criticalState = PlsrPlatformEnterCritical(); PlsrPosition = 0L; PlsrPositionValid = 1U; PlsrRemainingPulses = 0UL; PlsrPlatformExitCritical(criticalState); PlsrPlatformCheckpointPosition(0L, 1U, 0U); PlsrCountOverflowPending = 0U; PlsrPositionCheckpointDirty = 0U; PlsrPositionCheckpointElapsedMs = 0U; PlsrCurrentSegment = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrCutRequested = 0U; PlsrBoundaryPending = 0U; PlsrBoundaryWasCut = 0U; PlsrDirectionDelayActive = 0U; PlsrDirectionDelayRemainingMs = 0U; PlsrExtEdgePending = 0U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrInvalidateHandoffPlans(); PlsrDeferredFrequencyPending = 0U; PlsrTimerErrorPending = 0U; PlsrRamp.active = 0U; PlsrError = PLSR_ERROR_NONE; PlsrRunStatus = PLSR_STATUS_IDLE; PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); } static uint8_t PlsrPollCommandMailbox(void) { uint16_t command; uint8_t endPoll = 1U; uint32_t criticalState; criticalState = PlsrPlatformEnterCritical(); if (PlsrCommandMailbox.state != PLSR_COMMAND_MAILBOX_PENDING) { PlsrPlatformExitCritical(criticalState); return 0U; } PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EXECUTING; command = PlsrCommandMailbox.command; PlsrPlatformExitCritical(criticalState); switch (command) { case PLSR_COMMAND_START: PlsrExecuteStart(); break; case PLSR_COMMAND_STOP: endPoll = PlsrExecuteStop(); break; case PLSR_COMMAND_CLEAR: PlsrExecuteClear(); break; default: PlsrEnterError(PLSR_ERROR_INTERNAL); break; } criticalState = PlsrPlatformEnterCritical(); PlsrCommandMailbox.command = 0U; PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EMPTY; PlsrPlatformExitCritical(criticalState); return endPoll; } static void PlsrEnterErrorIfEpoch(PLSR_ERROR error, uint32_t expectedEpoch) { uint32_t criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch == expectedEpoch) { PlsrEnterError(error); } PlsrPlatformExitCritical(criticalState); } void PlsrPoll1ms(void) { uint8_t extLevel; uint8_t extEdge; uint8_t activeSegmentNumber; uint8_t applyDynamicFrequency = 0U; PLSR_SEGMENT_CONFIG *activeSegment; uint32_t criticalState; uint32_t newTargetHz; uint32_t pollEpoch; if (PlsrInitialized == 0U) { return; } if (PlsrPollCommandMailbox() != 0U) { return; } PlsrPollPositionCheckpoint(); criticalState = PlsrPlatformEnterCritical(); pollEpoch = PlsrSegmentEpoch; extLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); extEdge = ((extLevel != 0U) && (PlsrExtPreviousLevel == 0U)) ? 1U : 0U; PlsrExtPreviousLevel = extLevel; if (PlsrExtEdgePending != 0U) { extEdge = 1U; } PlsrPlatformExitCritical(criticalState); if (PlsrBoundaryPending != 0U) { PlsrExtEdgePending = 0U; PlsrHandleBoundary(extEdge); PlsrPollPersistence(); return; } if (PlsrIsBusy() == 0U) { PlsrExtEdgePending = 0U; PlsrPollPersistence(); return; } if (PlsrSegmentEpoch != pollEpoch) { return; } if (PlsrDirectionDelayActive != 0U) { if (extEdge != 0U) { PlsrExtEdgePending = 1U; } if (PlsrDirectionDelayRemainingMs != 0U) { PlsrDirectionDelayRemainingMs--; } if (PlsrDirectionDelayRemainingMs == 0U) { PlsrDirectionDelayActive = 0U; if (PlsrBeginSegmentOutput(PlsrActiveConfig.startSpeedHz) == 0U) { PlsrEnterError(PLSR_ERROR_TIMER); } } return; } criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch != pollEpoch) { PlsrPlatformExitCritical(criticalState); return; } PlsrExtEdgePending = 0U; if (PlsrSegmentClockStarted != 0U) { PlsrSegmentElapsedMs++; } if (PlsrRunStatus == PLSR_STATUS_WAITING) { PlsrPlatformExitCritical(criticalState); PlsrPollWaiting(extEdge); return; } activeSegmentNumber = PlsrCurrentSegment; if ((activeSegmentNumber == 0U) || (activeSegmentNumber > PlsrActiveConfig.segmentCount)) { PlsrPlatformExitCritical(criticalState); PlsrEnterError(PLSR_ERROR_INTERNAL); return; } activeSegment = &PlsrActiveConfig.segments[activeSegmentNumber - 1U]; if (PlsrSeamlessHandoffPending != 0U) { PlsrSeamlessHandoffPending = 0U; if ((PlsrStopRequested == 0U) && (PlsrShortProfile.active == 0U) && (PlsrFrequencyUpdatePending == 0U)) { PlsrRamp.active = 0U; PlsrRunStatus = PLSR_STATUS_RUNNING; } } newTargetHz = PlsrFrequencyUpdateTargetHz; if ((PlsrStopRequested == 0U) && (PlsrFrequencyUpdatePending != 0U) && (PlsrFrequencyUpdateSegment == activeSegmentNumber)) { PlsrFrequencyUpdatePending = 0U; activeSegment->frequencyHz = newTargetHz; PlsrShortProfile.active = 0U; PlsrBoundaryRampStarted = 0U; PlsrRampStart(PlsrCurrentFrequencyHz, newTargetHz); if (PlsrRamp.active == 0U) { applyDynamicFrequency = 1U; } } PlsrPlatformExitCritical(criticalState); if (applyDynamicFrequency != 0U) { if (PlsrApplyFrequency(newTargetHz, pollEpoch) == 0U) { PlsrEnterErrorIfEpoch(PLSR_ERROR_TIMER, pollEpoch); return; } criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch == pollEpoch) { PlsrRunStatus = PLSR_STATUS_RUNNING; } PlsrPlatformExitCritical(criticalState); } if ((PlsrShortProfile.active == 0U) && (PlsrRamp.active != 0U) && (PlsrRampAdvance(pollEpoch) == 0U)) { PlsrEnterErrorIfEpoch(PLSR_ERROR_TIMER, pollEpoch); return; } criticalState = PlsrPlatformEnterCritical(); if (PlsrSegmentEpoch != pollEpoch) { PlsrPlatformExitCritical(criticalState); return; } if ((PlsrStopRequested != 0U) && (PlsrRamp.active == 0U)) { if (PlsrStopPulsesRemaining == 0U) { PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); } PlsrPlatformExitCritical(criticalState); return; } PlsrPlatformExitCritical(criticalState); if ((activeSegment->waitType == PLSR_ACT_TIME) && (PlsrSegmentElapsedMs >= activeSegment->actTimeMs)) { PlsrRequestCut(pollEpoch); return; } if (((activeSegment->waitType == PLSR_EXT_SIGNAL) || (activeSegment->waitType == PLSR_EXT_OR_COMPLETE)) && (extEdge != 0U)) { PlsrRequestCut(pollEpoch); return; } PlsrMaybePlanBoundaryRamp(pollEpoch); } void PlsrPulseTimerIrq(uint8_t pulseOutput) { uint32_t positionBits; uint32_t completedFrequencyHz; uint32_t activeFrequencyHz; if ((PlsrPulseActive == 0U) || (pulseOutput != (uint8_t)PlsrActiveConfig.pulseOutput)) { return; } completedFrequencyHz = PlsrCurrentFrequencyHz; activeFrequencyHz = PlsrPlatformActiveFrequency(pulseOutput); if (activeFrequencyHz == 0UL) { PlsrTimerErrorPending = 1U; return; } PlsrCurrentFrequencyHz = activeFrequencyHz; if ((PlsrStopRequested != 0U) && (PlsrRamp.active == 0U) && (PlsrStopPulsesRemaining != 0U)) { PlsrStopPulsesRemaining--; if (PlsrStopPulsesRemaining == 0U) { PlsrCutRequested = 1U; } } positionBits = (uint32_t)PlsrPosition; if (PlsrCountPositive != 0U) { if (PlsrPosition == INT32_MAX) { PlsrPositionValid = 0U; PlsrCountOverflowPending = 1U; } positionBits++; } else { if (PlsrPosition == INT32_MIN) { PlsrPositionValid = 0U; PlsrCountOverflowPending = 1U; } positionBits--; } PlsrPosition = (int32_t)positionBits; PlsrPositionCheckpointDirty = 1U; if (PlsrRemainingPulses != 0UL) { PlsrRemainingPulses--; } if ((PlsrRemainingPulses == 0UL) || (PlsrCutRequested != 0U) || (PlsrCountOverflowPending != 0U)) { if ((PlsrRemainingPulses == 0UL) && (PlsrTrySubsequentHandoff() != 0U)) { return; } PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrBoundaryFrequencyHz = completedFrequencyHz; PlsrBoundaryWasCut = (PlsrCutRequested != 0U) ? 1U : 0U; PlsrCutRequested = 0U; PlsrPlatformStopPulse(pulseOutput); PlsrPulseActive = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrBoundaryPending = 1U; return; } if ((PlsrShortProfile.active != 0U) && (PlsrAdvanceShortProfile(pulseOutput) == 0U)) { PlsrTimerErrorPending = 1U; PlsrShortProfile.active = 0U; PlsrHandoffPlan.valid = 0U; PlsrBoundaryFrequencyHz = completedFrequencyHz; PlsrBoundaryWasCut = 0U; PlsrCutRequested = 0U; PlsrPlatformStopPulse(pulseOutput); PlsrPulseActive = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrBoundaryPending = 1U; return; } if ((PlsrShortProfile.active == 0U) && (PlsrRemainingPulses == 1UL)) { (void)PlsrPrimeHandoff(); } if ((PlsrShortProfile.active == 0U) && (PlsrHandoffPlan.valid == 0U) && (PlsrCommitDeferredFrequency(pulseOutput) == 0U)) { PlsrTimerErrorPending = 1U; PlsrBoundaryFrequencyHz = completedFrequencyHz; PlsrBoundaryWasCut = 0U; PlsrCutRequested = 0U; PlsrPlatformStopPulse(pulseOutput); PlsrPulseActive = 0U; PlsrCurrentFrequencyHz = 0UL; PlsrQueuedFrequencyHz = 0UL; PlsrBoundaryPending = 1U; } } PLSR_MB_RESULT PlsrModbusReadHolding(uint16_t startAddress, uint16_t quantity, uint16_t *values) { PLSR_MB_RESULT classification; uint16_t index; uint32_t criticalState; int32_t position; uint32_t frequency; uint16_t statusWords[7]; if (values == NULL) { return PLSR_MB_ILLEGAL_VALUE; } classification = PlsrClassifyRange(startAddress, quantity); if (classification != PLSR_MB_OK) { return classification; } criticalState = PlsrPlatformEnterCritical(); position = PlsrPosition; frequency = PlsrCurrentFrequencyHz; statusWords[0] = PlsrLowWord((uint32_t)position); statusWords[1] = PlsrHighWord((uint32_t)position); statusWords[2] = PlsrLowWord(frequency); statusWords[3] = PlsrHighWord(frequency); statusWords[4] = (uint16_t)PlsrRunStatus; statusWords[5] = PlsrCurrentSegment; statusWords[6] = (uint16_t)PlsrError; PlsrPlatformExitCritical(criticalState); for (index = 0U; index < quantity; index++) { uint16_t address = (uint16_t)(startAddress + index); if ((address >= PLSR_CONFIG_FIRST_ADDRESS) && (address <= PLSR_CONFIG_LAST_ADDRESS)) { values[index] = PlsrReadConfigWord(&PlsrShadowConfig, address); } else if ((address >= PLSR_STATUS_FIRST_ADDRESS) && (address <= PLSR_STATUS_LAST_ADDRESS)) { values[index] = statusWords[address - PLSR_STATUS_FIRST_ADDRESS]; } else if (address == PLSR_CONTROL_ADDRESS) { values[index] = 0U; } else { return PLSR_MB_ILLEGAL_ADDRESS; } } return PLSR_MB_OK; } PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, uint16_t quantity, const uint16_t *values) { PLSR_MB_RESULT classification; PLSR_WORD_RESULT wordResult; uint16_t index; uint16_t pairedAddress; uint32_t requestEnd; uint32_t criticalState; uint8_t handoffBank = 0U; uint8_t updateActiveFrequencies = 0U; uint8_t drainedToDifferentSegment = 0U; uint8_t segmentBeforeDrain; uint32_t drainedSegmentTargetHz = 0UL; if (values == NULL) { return PLSR_MB_ILLEGAL_VALUE; } classification = PlsrClassifyRange(startAddress, quantity); if (classification != PLSR_MB_OK) { return classification; } if ((startAddress >= PLSR_STATUS_FIRST_ADDRESS) && (startAddress <= PLSR_STATUS_LAST_ADDRESS)) { return PLSR_MB_ILLEGAL_ADDRESS; } if (startAddress == PLSR_CONTROL_ADDRESS) { if (quantity != 1U) { return PLSR_MB_ILLEGAL_ADDRESS; } switch (values[0]) { case 0U: return PLSR_MB_OK; case PLSR_COMMAND_START: case PLSR_COMMAND_STOP: case PLSR_COMMAND_CLEAR: return PlsrQueueCommand(values[0]); default: return PLSR_MB_ILLEGAL_VALUE; } } if ((startAddress < PLSR_CONFIG_FIRST_ADDRESS) || ((uint32_t)startAddress + quantity - 1UL > PLSR_CONFIG_LAST_ADDRESS)) { return PLSR_MB_ILLEGAL_ADDRESS; } if ((PlsrIsBusy() != 0U) && (startAddress <= 0x1001U) && ((uint32_t)startAddress + quantity - 1UL >= 0x1000UL)) { return PLSR_MB_DEVICE_BUSY; } requestEnd = (uint32_t)startAddress + quantity; for (index = 0U; index < quantity; index++) { uint16_t address = (uint16_t)(startAddress + index); if (PlsrAddressIsDwordHalf(address, &pairedAddress) != 0U) { if (((uint32_t)pairedAddress < startAddress) || ((uint32_t)pairedAddress >= requestEnd)) { return PLSR_MB_ILLEGAL_ADDRESS; } } } PlsrCandidateConfig = PlsrShadowConfig; for (index = 0U; index < quantity; index++) { wordResult = PlsrWriteConfigWord(&PlsrCandidateConfig, (uint16_t)(startAddress + index), values[index]); if (wordResult == PLSR_WORD_ILLEGAL_ADDRESS) { return PLSR_MB_ILLEGAL_ADDRESS; } if (wordResult == PLSR_WORD_ILLEGAL_VALUE) { return PLSR_MB_ILLEGAL_VALUE; } } if (PlsrConfigIsValid(&PlsrCandidateConfig, 0U) == 0U) { return PLSR_MB_ILLEGAL_VALUE; } if (PlsrIsBusy() != 0U) { for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) { if (PlsrCandidateConfig.segments[index].frequencyHz != PlsrShadowConfig.segments[index].frequencyHz) { updateActiveFrequencies = 1U; } } } if (updateActiveFrequencies != 0U) { handoffBank = PlsrBuildHandoffPlanBank(&PlsrCandidateConfig); } criticalState = PlsrPlatformEnterCritical(); segmentBeforeDrain = PlsrCurrentSegment; if ((updateActiveFrequencies != 0U) && (PlsrPulseActive != 0U)) { PlsrPlatformDrainPendingPulse( (uint8_t)PlsrActiveConfig.pulseOutput); drainedToDifferentSegment = (PlsrCurrentSegment != segmentBeforeDrain) ? 1U : 0U; if ((drainedToDifferentSegment != 0U) && (PlsrCurrentSegment != 0U) && (PlsrCurrentSegment <= PlsrCandidateConfig.segmentCount)) { drainedSegmentTargetHz = PlsrCandidateConfig.segments[PlsrCurrentSegment - 1U].frequencyHz; } } PlsrShadowConfig = PlsrCandidateConfig; if (updateActiveFrequencies != 0U) { for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) { uint8_t frequencyChanged = (PlsrActiveConfig.segments[index].frequencyHz != PlsrCandidateConfig.segments[index].frequencyHz) ? 1U : 0U; PlsrActiveConfig.segments[index].frequencyHz = PlsrCandidateConfig.segments[index].frequencyHz; if ((frequencyChanged != 0U) && (index + 1U == PlsrCurrentSegment) && (PlsrStopRequested == 0U) && (PlsrRunStatus != PLSR_STATUS_WAITING)) { PlsrFrequencyUpdateTargetHz = PlsrCandidateConfig.segments[index].frequencyHz; PlsrFrequencyUpdateSegment = (uint8_t)(index + 1U); PlsrFrequencyUpdatePending = 1U; } } PlsrPreparedHandoffBank = handoffBank; PlsrHandoffPlan.valid = 0U; if ((drainedToDifferentSegment != 0U) && (PlsrPulseActive != 0U) && (PlsrCurrentSegment != 0U) && (PlsrCurrentSegment <= PlsrActiveConfig.segmentCount) && (PlsrStopRequested == 0U) && (PlsrRunStatus != PLSR_STATUS_WAITING) && (PlsrCurrentFrequencyHz != drainedSegmentTargetHz)) { PlsrFrequencyUpdateTargetHz = drainedSegmentTargetHz; PlsrFrequencyUpdateSegment = PlsrCurrentSegment; PlsrFrequencyUpdatePending = 1U; } if ((PlsrPulseActive != 0U) && (PlsrShortProfile.active == 0U) && (PlsrRemainingPulses == 1UL)) { (void)PlsrPrimeHandoff(); } } PlsrPlatformExitCritical(criticalState); if ((drainedToDifferentSegment != 0U) && (PlsrFrequencyUpdatePending != 0U)) { uint32_t drainEpoch = PlsrSegmentEpoch; uint32_t actualDrainFrequencyHz; if (PlsrPlatformQueueFrequency( (uint8_t)PlsrActiveConfig.pulseOutput, drainedSegmentTargetHz, &actualDrainFrequencyHz) == 0U) { PlsrTimerErrorPending = 1U; } else { uint32_t currentEpoch; uint8_t updateSegment; uint8_t currentSegment; criticalState = PlsrPlatformEnterCritical(); currentEpoch = PlsrSegmentEpoch; updateSegment = PlsrFrequencyUpdateSegment; currentSegment = PlsrCurrentSegment; if ((currentEpoch == drainEpoch) && (updateSegment == currentSegment)) { PlsrQueuedFrequencyHz = actualDrainFrequencyHz; PlsrFrequencyUpdatePending = 0U; PlsrRamp.active = 0U; PlsrRunStatus = PLSR_STATUS_RUNNING; } PlsrPlatformExitCritical(criticalState); } } PlsrPlatformCheckpointConfig(&PlsrShadowConfig); PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); return PLSR_MB_OK; } #ifdef PLSR_HOST_TEST uint64_t PlsrTestDivideU64ByU32(uint64_t dividend, uint32_t divisor, uint32_t *remainder) { return PlsrDivideU64ByU32(dividend, divisor, remainder); } void PlsrTestSetPosition(int32_t position, uint8_t positionValid) { uint32_t criticalState = PlsrPlatformEnterCritical(); PlsrPosition = position; PlsrPositionValid = (positionValid != 0U) ? 1U : 0U; PlsrRemainingPulses = 0UL; PlsrPositionCheckpointDirty = 0U; PlsrPlatformExitCritical(criticalState); PlsrPlatformCheckpointPosition(position, PlsrPositionValid, 0U); } #endif