#include "plsr_hal_f407.h" #include "plsr_address_map.h" #include "plsr_build_config.h" #include "plsr_core.h" #include "plsr_job.h" #include #ifndef PLSR_HOST_TEST #include "stm32f4xx.h" #include "stm32f4xx_hal.h" #endif #define PLSR_HW_TIMER_CHANNEL1_BIT (0x0001U) #define PLSR_HW_TIMER_CC1P_BIT (0x0002U) #define PLSR_HW_TIMER_UPDATE_BIT (0x0001U) #define PLSR_HW_TIMER_CC1_BIT (0x0002U) #define PLSR_HW_OUTPUT_POINT_COUNT (21U) #define PLSR_HW_DBG_SNAPSHOT_COUNT (160U) #define PLSR_HW_AB_QUARTER_COUNT (4U) #define PLSR_HW_COUNTER_COUNT (2U) #define PLSR_HW_COUNTER_NONE (0xFFU) #define PLSR_HW_COUNTER_BLOCK_PULSES (UINT64_C(65536)) typedef struct { uint32_t timerClockHz; uint8_t directionPoint; /* 0xFF = 无 */ #ifndef PLSR_HOST_TEST TIM_TypeDef *timer; GPIO_TypeDef *gpioPort; uint16_t gpioPin; uint8_t afMode; IRQn_Type irq; #endif } PLSR_HW_AXIS_MAP; #ifndef PLSR_HOST_TEST /* 输出点(Y 点号)→ GPIO 引脚:XDM-60T4-E 原理图。 * 点号 8/9/18/19 不存在(资源层掩码 0x0013FCFF 已约束)。 */ typedef struct { GPIO_TypeDef *port; uint16_t pin; } PLSR_HW_OUTPUT_PIN; static const PLSR_HW_OUTPUT_PIN PlsrHwOutputPins[PLSR_HW_OUTPUT_POINT_COUNT] = { {GPIOF, GPIO_PIN_6}, /* Y0 */ {GPIOF, GPIO_PIN_8}, /* Y1 */ {GPIOF, GPIO_PIN_7}, /* Y2 */ {GPIOF, GPIO_PIN_9}, /* Y3 */ {GPIOI, GPIO_PIN_8}, /* Y4 */ {GPIOE, GPIO_PIN_6}, /* Y5 */ {GPIOE, GPIO_PIN_5}, /* Y6 */ {GPIOE, GPIO_PIN_4}, /* Y7 */ {NULL, 0U}, /* Y8 */ {NULL, 0U}, /* Y9 */ {GPIOG, GPIO_PIN_7}, /* Y10 */ {GPIOG, GPIO_PIN_6}, /* Y11 */ {GPIOH, GPIO_PIN_9}, /* Y12 */ {GPIOH, GPIO_PIN_8}, /* Y13 */ {GPIOH, GPIO_PIN_7}, /* Y14 */ {GPIOH, GPIO_PIN_6}, /* Y15 */ {GPIOF, GPIO_PIN_11}, /* Y16 */ {GPIOB, GPIO_PIN_0}, /* Y17 */ {NULL, 0U}, /* Y18 */ {NULL, 0U}, /* Y19 */ {GPIOH, GPIO_PIN_5} /* Y20 */ }; #endif /* Q0~Q3 定时器:XDM-60T4-E。 * PF6=TIM10_CH1(AF3)、PF7=TIM11_CH1(AF3)、PF8=TIM13_CH1(AF9)、PF9=TIM14_CH1(AF9)。 * 定时器时钟由 RCC 实际配置计算(APB2 分频≠1 时定时器时钟×2)。 */ static const PLSR_HW_AXIS_MAP PlsrHwAxisMap[PLSR_HW_AXIS_COUNT] = { #ifndef PLSR_HOST_TEST {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM10, GPIOF, GPIO_PIN_6, 3U, TIM1_UP_TIM10_IRQn}, {84000000UL, PLSR_HW_DIR_POINT_NONE, TIM13, GPIOF, GPIO_PIN_8, 9U, TIM8_UP_TIM13_IRQn}, {168000000UL, PLSR_HW_DIR_POINT_NONE, TIM11, GPIOF, GPIO_PIN_7, 3U, TIM1_TRG_COM_TIM11_IRQn}, {84000000UL, PLSR_HW_DIR_POINT_NONE, TIM14, GPIOF, GPIO_PIN_9, 9U, TIM8_TRG_COM_TIM14_IRQn} #else {168000000UL, PLSR_HW_DIR_POINT_NONE}, {84000000UL, PLSR_HW_DIR_POINT_NONE}, {168000000UL, PLSR_HW_DIR_POINT_NONE}, {84000000UL, PLSR_HW_DIR_POINT_NONE} #endif }; #ifndef PLSR_HOST_TEST static const uint8_t PlsrHwPulsePinIndex[PLSR_HW_AXIS_COUNT] = { 6U, 8U, 7U, 9U }; /* 重定相期间由 GPIO 直接保持端子物理输出低电平。板上漏型输出级 * 会反相,所以 MCU 必须驱动高电平才能让 Q 端子为低。AFR 配置保持不变, * 只切换 MODER,因此恢复定时器复用功能只需一次寄存器写入。 */ static void PlsrHwHoldPulsePinLow(uint8_t axis) { GPIO_TypeDef *port = PlsrHwAxisMap[axis].gpioPort; uint32_t shift = (uint32_t)PlsrHwPulsePinIndex[axis] * 2UL; uint32_t moder; port->BSRR = (uint32_t)PlsrHwAxisMap[axis].gpioPin; moder = port->MODER; moder &= ~(3UL << shift); moder |= 1UL << shift; port->MODER = moder; __DMB(); } static void PlsrHwReleasePulsePin(uint8_t axis) { GPIO_TypeDef *port = PlsrHwAxisMap[axis].gpioPort; uint32_t shift = (uint32_t)PlsrHwPulsePinIndex[axis] * 2UL; uint32_t moder = port->MODER; moder &= ~(3UL << shift); moder |= 2UL << shift; port->MODER = moder; __DMB(); } /* Q0..Q3 share GPIOF. At an AB 00 boundary, transfer both pins with one * GPIO write before changing CCR/ARR. A larger downshift CCR can otherwise * assert one channel while it is still connected to the timer. */ static void PlsrHwHoldAbPairLowFast(uint8_t axis) { uint8_t pairAxis = (uint8_t)(axis + 1U); GPIO_TypeDef *port = PlsrHwAxisMap[axis].gpioPort; uint32_t firstShift = (uint32_t)PlsrHwPulsePinIndex[axis] * 2UL; uint32_t secondShift = (uint32_t)PlsrHwPulsePinIndex[pairAxis] * 2UL; uint32_t moder = port->MODER; port->BSRR = (uint32_t)PlsrHwAxisMap[axis].gpioPin | (uint32_t)PlsrHwAxisMap[pairAxis].gpioPin; moder &= ~((3UL << firstShift) | (3UL << secondShift)); moder |= (1UL << firstShift) | (1UL << secondShift); port->MODER = moder; __DMB(); } static void PlsrHwReleaseAbPairFast(uint8_t axis) { uint8_t pairAxis = (uint8_t)(axis + 1U); GPIO_TypeDef *port = PlsrHwAxisMap[axis].gpioPort; uint32_t firstShift = (uint32_t)PlsrHwPulsePinIndex[axis] * 2UL; uint32_t secondShift = (uint32_t)PlsrHwPulsePinIndex[pairAxis] * 2UL; uint32_t moder = port->MODER; moder &= ~((3UL << firstShift) | (3UL << secondShift)); moder |= (2UL << firstShift) | (2UL << secondShift); port->MODER = moder; __DMB(); } #endif /* host 测试:模拟定时器寄存器。 */ #ifdef PLSR_HOST_TEST typedef struct { uint32_t cr1; uint32_t dier; uint32_t sr; uint32_t psc; uint32_t arr; uint32_t ccr1; uint32_t cnt; uint32_t ccmr1; uint32_t ccer; uint8_t dirLevel; } PLSR_HW_TIMER_REGS; static PLSR_HW_TIMER_REGS PlsrHwTimers[PLSR_HW_AXIS_COUNT]; #endif typedef struct { PLSR_HW_STATE state; PLSR_OUTPUT_MODE outputMode; uint32_t currentFrequencyHz; int64_t targetPulses; int64_t emittedPulses; uint16_t directionDelayRemainingMs; uint8_t directionPoint; uint8_t configuredDirectionPoint; uint8_t directionPositive; uint8_t directionNegativeLogic; uint8_t directionTerminalOn; uint8_t directionOutputPending; uint8_t abQuarter; uint8_t abCountAxis; uint8_t abStartupPriming; uint16_t abActiveBasePsc; uint16_t abActivePairPsc; uint16_t abActiveArr; uint16_t abPendingBasePsc; uint16_t abPendingPairPsc; uint16_t abPendingArr; uint8_t abFrequencyPending; uint8_t abStopArmed; uint8_t abFastGated; uint8_t abPausePending; uint8_t abPauseGated; uint8_t abCompletionDeferred; uint8_t counterSourceAxis; uint32_t abCounterBoundaryCnt; uint8_t cwActiveAxis; uint8_t cwStopPending; uint8_t pulseBoundaryStopPending; uint8_t pulseTargetStopArmed; uint8_t counterIndex; uint8_t hardwareCounterActive; uint8_t hardwareCounterConfigured; uint64_t counterBlockPulses; uint64_t counterPublishedPulses; } PLSR_HW_AXIS_STATE; static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT]; static uint8_t PlsrHwDirectionBatchActive; static uint8_t PlsrHwCounterOwners[PLSR_HW_COUNTER_COUNT]; static volatile uint32_t PlsrHwMaxOutputIsrCycles; static volatile uint32_t PlsrHwMaxCounterIsrCycles; static volatile uint32_t PlsrHwMaxControlIsrCycles; static volatile uint32_t PlsrHwMaxAbGateCycles; static uint8_t PlsrHwValidationOutputMask; #ifndef PLSR_HOST_TEST static volatile uint8_t PlsrHwAbGateMeasurePending; #endif static volatile uint64_t PlsrHwTotalIsrCycles; static volatile uint32_t PlsrHwIsrBusyStarted; static volatile uint8_t PlsrHwIsrNesting; #ifndef PLSR_HOST_TEST static TIM_TypeDef * const PlsrHwCounters[PLSR_HW_COUNTER_COUNT] = { TIM9, TIM12 }; #endif static void PlsrHwCounterBegin(uint8_t axis); static void PlsrHwCounterSuspend(uint8_t axis); static void PlsrHwFinishDeferredAbWork(uint8_t axis); static uint8_t PlsrHwGateArmedAbOutputs(uint8_t preferredAxis); static void PlsrHwTimerSetCc1PolarityInverted(uint8_t axis, uint32_t value); #ifdef PLSR_HOST_TEST static uint8_t PlsrHwTestLateAbFlagAxis = PLSR_HW_COUNTER_NONE; static uint32_t PlsrHwTestAbFullGateCount; #endif #ifndef PLSR_HOST_TEST static uint32_t PlsrHwCycleBegin(void) { uint32_t started = DWT->CYCCNT; if (PlsrHwIsrNesting == 0U) { PlsrHwIsrBusyStarted = started; } PlsrHwIsrNesting++; return started; } static void PlsrHwRecordMaxCycles(volatile uint32_t *maximum, uint32_t started) { uint32_t finished = DWT->CYCCNT; uint32_t elapsed = finished - started; if (elapsed > *maximum) { *maximum = elapsed; } if (PlsrHwIsrNesting > 0U) { PlsrHwIsrNesting--; if (PlsrHwIsrNesting == 0U) { uint32_t busyStarted = PlsrHwIsrBusyStarted; uint64_t totalCycles = PlsrHwTotalIsrCycles; /* Count a nested TIM6/high-speed interrupt window once. */ totalCycles += finished - busyStarted; PlsrHwTotalIsrCycles = totalCycles; } } } #endif /* 调试快照:当前上板自测只记录 Q0 的 160 ms,避免四轴 * PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示段启动, * reason=3 表示 1 ms HAL tick,reason=4 表示 AB 在 00 边界换频重定相。 */ #if !defined(PLSR_HOST_TEST) && (PLSR_ENABLE_HW_TRACE != 0U) typedef struct { uint8_t reason; /* 0=PwmBegin(UG后) 3=PlsrHwTick(每1ms) */ uint32_t psc; uint32_t arr; uint32_t ccr; uint32_t cnt; uint32_t frequencyHz; int64_t emittedPulses; } PLSR_HW_DBG_SNAP; static PLSR_HW_DBG_SNAP PlsrHwDbgSnap[PLSR_HW_DBG_SNAPSHOT_COUNT]; static volatile uint16_t PlsrHwDbgCount; static void PlsrHwDbgCapture(uint8_t axis, uint8_t reason) { if (axis != 0U) { return; } if (reason == 0U) { PlsrHwDbgCount = 0U; } if (PlsrHwDbgCount < PLSR_HW_DBG_SNAPSHOT_COUNT) { PLSR_HW_DBG_SNAP *snap = &PlsrHwDbgSnap[PlsrHwDbgCount++]; snap->reason = reason; snap->psc = PlsrHwAxisMap[axis].timer->PSC; snap->arr = PlsrHwAxisMap[axis].timer->ARR; snap->ccr = PlsrHwAxisMap[axis].timer->CCR1; snap->cnt = PlsrHwAxisMap[axis].timer->CNT; snap->frequencyHz = PlsrHwAxes[axis].currentFrequencyHz; snap->emittedPulses = PlsrHwAxes[axis].emittedPulses; } } #else #define PlsrHwDbgCapture(axis, reason) ((void)0) #endif /* ---- 定时器寄存器访问抽象(host 模拟 / 生产真实) ---- */ static void PlsrHwTimerSetArr(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].arr = value; #else PlsrHwAxisMap[axis].timer->ARR = value; #endif } static void PlsrHwTimerSetPsc(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].psc = value; #else PlsrHwAxisMap[axis].timer->PSC = value; #endif } static void PlsrHwTimerSetCcr(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].ccr1 = value; #else PlsrHwAxisMap[axis].timer->CCR1 = value; #endif } static uint32_t PlsrHwTimerGetCcr(uint8_t axis) { #ifdef PLSR_HOST_TEST return PlsrHwTimers[axis].ccr1; #else return PlsrHwAxisMap[axis].timer->CCR1; #endif } static void PlsrHwTimerSetCnt(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].cnt = value; /* F407 实测语义:CNT 写到活动 CCR1 比较值会置 CC1IF。 */ if (value == PlsrHwTimers[axis].ccr1) { PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_CC1_BIT; } #else PlsrHwAxisMap[axis].timer->CNT = value; #endif } static void PlsrHwTimerSetCen(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].cr1 = (PlsrHwTimers[axis].cr1 & ~0x0001UL) | value; #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->CR1 |= TIM_CR1_CEN; } else { PlsrHwAxisMap[axis].timer->CR1 &= ~TIM_CR1_CEN; } #endif } static void PlsrHwStartAbTimersTightly(uint8_t axis, uint8_t pairAxis) { #ifdef PLSR_HOST_TEST PlsrHwTimerSetCen(axis, 1UL); PlsrHwTimerSetCen(pairAxis, 1UL); #else TIM_TypeDef *baseTimer = PlsrHwAxisMap[axis].timer; TIM_TypeDef *pairTimer = PlsrHwAxisMap[pairAxis].timer; uint32_t baseCr1 = baseTimer->CR1 | TIM_CR1_CEN; uint32_t pairCr1 = pairTimer->CR1 | TIM_CR1_CEN; /* Keep the two volatile stores adjacent. Calling the generic CEN helper * twice in the low-optimization validation build delayed the second * timer by about 0.33us. */ baseTimer->CR1 = baseCr1; pairTimer->CR1 = pairCr1; #endif } static void PlsrHwTimerSetCc1e(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].ccer = (PlsrHwTimers[axis].ccer & ~0x0001UL) | value; #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->CCER |= TIM_CCER_CC1E; } else { PlsrHwAxisMap[axis].timer->CCER &= ~TIM_CCER_CC1E; } #endif } /* 通道 1 输出模式 = PWM 模式 1(OC1M=110)+ CCR 预装载(OC1PE)。 * 上电复位后 CCMR1=0(冻结),通道输出恒定电平、无方波,必须显式配置。 */ static void PlsrHwTimerSetPwmMode1(uint8_t axis) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].ccmr1 = 0x0068UL; #else PlsrHwAxisMap[axis].timer->CCMR1 = (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2) | TIM_CCMR1_OC1PE; #endif } /* AB 启动和重定相时先把 OC1REF 钳到低电平,再切到 frozen 保持 00。 * 两路 CNT 就位后从 frozen 切到 PWM1,硬件会按当前 CNT/CCR 重新计算输出, * 避免 UG 后残留的 OC1REF 高电平经 CC1E 暴露为窄脉冲。 */ static void PlsrHwTimerSetForcedInactive(uint8_t axis) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].ccmr1 = 0x0048UL; #else PlsrHwAxisMap[axis].timer->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE; #endif } static void PlsrHwTimerSetUie(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].dier = (PlsrHwTimers[axis].dier & ~0x0001UL) | value; #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->DIER |= TIM_DIER_UIE; } else { PlsrHwAxisMap[axis].timer->DIER &= ~TIM_DIER_UIE; } #endif } static void PlsrHwTimerSetCc1ie(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].dier = (PlsrHwTimers[axis].dier & ~PLSR_HW_TIMER_CC1_BIT) | ((value != 0UL) ? PLSR_HW_TIMER_CC1_BIT : 0UL); if ((value != 0UL) && ((PlsrHwTimers[axis].sr & PLSR_HW_TIMER_CC1_BIT) != 0UL)) { PlsrHwOnTimerUpdate(axis); } #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->DIER |= TIM_DIER_CC1IE; } else { PlsrHwAxisMap[axis].timer->DIER &= ~TIM_DIER_CC1IE; } #endif } static void PlsrHwTimerClearUif(uint8_t axis) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].sr &= ~PLSR_HW_TIMER_UPDATE_BIT; #else PlsrHwAxisMap[axis].timer->SR &= ~TIM_SR_UIF; #endif } static uint8_t PlsrHwTimerHasUif(uint8_t axis) { #ifdef PLSR_HOST_TEST return ((PlsrHwTimers[axis].sr & PLSR_HW_TIMER_UPDATE_BIT) != 0UL) ? 1U : 0U; #else return ((PlsrHwAxisMap[axis].timer->SR & TIM_SR_UIF) != 0UL) ? 1U : 0U; #endif } static void PlsrHwTimerClearCc1if(uint8_t axis) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].sr &= ~PLSR_HW_TIMER_CC1_BIT; #else PlsrHwAxisMap[axis].timer->SR &= ~TIM_SR_CC1IF; #endif } static uint8_t PlsrHwTimerHasCc1if(uint8_t axis) { #ifdef PLSR_HOST_TEST return ((PlsrHwTimers[axis].sr & PLSR_HW_TIMER_CC1_BIT) != 0UL) ? 1U : 0U; #else return ((PlsrHwAxisMap[axis].timer->SR & TIM_SR_CC1IF) != 0UL) ? 1U : 0U; #endif } /* ---- DIR 输出 ---- * XDM 为晶体管(NPN 漏型)输出:ON(导通)= 引脚低电平。 * 正逻辑:正向=ON;负逻辑:正向=OFF。逻辑运动方向始终单独保存, * 不能因电气极性反转而改变位置符号、AB相序或SM方向标志。 */ static void PlsrHwApplyDirLevel(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if (state->directionPoint == PLSR_HW_DIR_POINT_NONE) { return; } #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].dirLevel = state->directionTerminalOn; state->configuredDirectionPoint = state->directionPoint; #else if (state->directionPoint < PLSR_HW_OUTPUT_POINT_COUNT) { const PLSR_HW_OUTPUT_PIN *pin = &PlsrHwOutputPins[state->directionPoint]; GPIO_InitTypeDef gpio; if (pin->port != NULL) { /* DIR 点按需配置为推挽输出(上电默认高阻=截止,安全)。 */ pin->port->BSRR = (state->directionTerminalOn != 0U) ? ((uint32_t)pin->pin << 16U) : (uint32_t)pin->pin; if (state->configuredDirectionPoint != state->directionPoint) { gpio.Pin = pin->pin; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(pin->port, &gpio); state->configuredDirectionPoint = state->directionPoint; } /* 漏型输出:ON(导通)= 低电平。 */ } } #endif } /* ---- PWM 启停 ---- * ARR/CCR 使用预装载(ARPE/OC1PE):运行中调频写入延迟到更新事件生效, * 避免 ARR 变小瞬间 CNT 超调提前回绕(每段加速会多出 ~ln(f1/f0) 个假脉冲)。 * 首次启动用 EGR.UG 把预装载值加载到影子寄存器,杜绝首个周期用复位值。 */ static void PlsrHwSetDirLevel(uint8_t axis, uint8_t positive, uint8_t negativeLogic) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; state->directionPositive = (positive != 0U) ? 1U : 0U; state->directionNegativeLogic = (negativeLogic != 0U) ? 1U : 0U; state->directionTerminalOn = (uint8_t)(state->directionPositive ^ state->directionNegativeLogic); if (state->directionPoint == PLSR_HW_DIR_POINT_NONE) { return; } if ((PlsrHwDirectionBatchActive != 0U) && (state->configuredDirectionPoint == state->directionPoint)) { state->directionOutputPending = 1U; return; } PlsrHwApplyDirLevel(axis); } void PlsrHwBeginDirectionBatch(void) { PlsrHwDirectionBatchActive = 1U; } void PlsrHwEndDirectionBatch(void) { uint8_t axis; #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif PlsrHwDirectionBatchActive = 0U; for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++) { if (PlsrHwAxes[axis].directionOutputPending != 0U) { PlsrHwAxes[axis].directionOutputPending = 0U; PlsrHwApplyDirLevel(axis); } } #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwTimerSetArpe(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].cr1 = (PlsrHwTimers[axis].cr1 & ~0x0080UL) | ((value != 0UL) ? 0x0080UL : 0UL); #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->CR1 |= TIM_CR1_ARPE; } else { PlsrHwAxisMap[axis].timer->CR1 &= ~TIM_CR1_ARPE; } #endif } /* 生成更新事件:立即加载 ARR/CCR/PSC 影子寄存器(启动时用)。 */ static void PlsrHwTimerSetUg(uint8_t axis) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_UPDATE_BIT; #else PlsrHwAxisMap[axis].timer->EGR = TIM_EGR_UG; #endif } /* 配置 PWM 定时器(预装载写入;启动/调频共用,不触碰使能位)。 */ static void PlsrHwConfigurePwm(uint8_t axis, uint32_t frequencyHz) { uint16_t psc; uint16_t arr; #ifndef PLSR_HOST_TEST uint32_t interruptState; #endif if (PlsrCalculateTimerDivider(PlsrHwAxisMap[axis].timerClockHz, frequencyHz, &psc, &arr) != PLSR_RESULT_OK) { return; } #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif PlsrHwTimerSetPsc(axis, psc); PlsrHwTimerSetArr(axis, arr); PlsrHwTimerSetCcr(axis, (uint32_t)arr / 2UL); /* 50% 占空比 */ PlsrHwTimerSetPwmMode1(axis); PlsrHwTimerSetArpe(axis, 1UL); #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } /* 首次启动输出:加载影子寄存器后使能更新中断、通道与计数。 */ static void PlsrHwPwmBegin(uint8_t axis) { /* Stop the slave before changing the source OCREF phase. This is also * required when a paused hardware-counted segment is resumed. */ PlsrHwCounterSuspend(axis); PlsrHwTimerSetUg(axis); if (PlsrHwAxes[axis].hardwareCounterActive != 0U) { /* PWM1 is inactive when CNT >= CCR1. Arm the ITR slave from that * known-low OCREF phase, then expose the first complete terminal high * half-cycle through CC1E. Starting at CNT=0 leaves OCREF high while * SMS is enabled; TIM9/TIM12 count that internal startup level as one * event even though no complete terminal pulse has occurred. */ PlsrHwTimerSetCnt(axis, PlsrHwTimerGetCcr(axis)); } /* UG 只用于加载影子寄存器,不是物理脉冲,不得计数。 */ PlsrHwTimerClearUif(axis); PlsrHwTimerClearCc1if(axis); /* The slave trigger was selected while OCREF was forced low. Enable its * external-clock mode only after the source PWM and startup UG are stable. */ PlsrHwCounterBegin(axis); PlsrHwDbgCapture(axis, 0U); PlsrHwTimerSetCc1ie(axis, 0UL); /* PULSE/DIR retains the previously validated non-inverted polarity. */ PlsrHwTimerSetCc1PolarityInverted(axis, 0UL); /* TIM9/TIM12 count OC events in hardware. Only the two fallback axes * retain a per-period output-timer interrupt. */ PlsrHwTimerSetUie(axis, (PlsrHwAxes[axis].hardwareCounterActive != 0U) ? 0UL : 1UL); PlsrHwTimerSetCc1e(axis, 1UL); #ifndef PLSR_HOST_TEST /* A previous stop may leave GPIO owning the pin at terminal-low. The * timer is fully configured and still stopped here; hand it back to AF * before CEN so the first transition is a deliberate full pulse. */ PlsrHwReleasePulsePin(axis); #endif PlsrHwTimerSetCen(axis, 1UL); } static void PlsrHwStopPwmTimer(uint8_t axis) { /* Freeze the ITR slave before changing OCREF/CC1E so a stop or pause * transition cannot be mistaken for a physical pulse boundary. */ PlsrHwCounterSuspend(axis); PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetUie(axis, 0UL); PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCen(axis, 0UL); PlsrHwTimerClearUif(axis); PlsrHwTimerClearCc1if(axis); } static uint8_t PlsrHwIsAbBaseAxis(uint8_t axis) { return ((axis == 0U) || (axis == 2U)) ? 1U : 0U; } static uint8_t PlsrHwGetPairedAxis(uint8_t axis) { return (uint8_t)(axis + 1U); } static void PlsrHwStopPulseDirOutput(uint8_t axis) { #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); /* At a natural update boundary the terminal is already low. GPIO-high * represents that same level through the board's inverting sink stage, * so taking ownership before CC1E is cleared cannot create a short pulse. */ PlsrHwHoldPulsePinLow(axis); #endif PlsrHwStopPwmTimer(axis); #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwArmAbBoundaryInterrupts(uint8_t axis) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); /* Either phase may be the first falling edge after an asynchronous * request. Observe both and accept only the event for which both PWM * phases are already low. */ PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwTimerSetCc1ie(axis, 1UL); PlsrHwTimerSetCc1ie(pairAxis, 1UL); } static void PlsrHwRestoreAbCycleInterrupt(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t pairAxis = PlsrHwGetPairedAxis(axis); PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCc1ie(pairAxis, 0UL); if ((state->hardwareCounterActive == 0U) || (state->abStopArmed != 0U)) { PlsrHwTimerClearCc1if(state->abCountAxis); PlsrHwTimerSetCc1ie(state->abCountAxis, 1UL); } } static uint8_t PlsrHwIsAbPhysicalZeroBoundary(uint8_t axis) { #ifdef PLSR_HOST_TEST return (PlsrHwAxes[axis].abQuarter == 0U) ? 1U : 0U; #else uint8_t pairAxis = PlsrHwGetPairedAxis(axis); TIM_TypeDef *baseTimer = PlsrHwAxisMap[axis].timer; TIM_TypeDef *pairTimer = PlsrHwAxisMap[pairAxis].timer; uint32_t baseCnt = baseTimer->CNT; uint32_t baseCcr = baseTimer->CCR1; uint32_t pairCnt = pairTimer->CNT; uint32_t pairCcr = pairTimer->CCR1; /* With the validated AB double inversion, physical Q is high exactly * while PWM1 CNT= baseCcr) && (pairCnt >= pairCcr)) ? 1U : 0U; #endif } /* 为 168MHz/84MHz 配对定时器选择相同 ARR,并让前者的 PSC 分频 * 始终是后者的 2 倍。两路获得完全相同的计数时钟与周期,避免 * 独立取整造成 AB 相位随运行时间漂移。 */ static uint8_t PlsrHwCalculateAbDividers(uint8_t axis, uint32_t frequencyHz, uint16_t *basePsc, uint16_t *pairPsc, uint16_t *arr) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint64_t baseClock = PlsrHwAxisMap[axis].timerClockHz; uint64_t pairClock = PlsrHwAxisMap[pairAxis].timerClockHz; uint64_t ratio; uint64_t pairDivider; uint64_t baseDivider; uint64_t periodTicks; if ((frequencyHz == 0UL) || (basePsc == NULL) || (pairPsc == NULL) || (arr == NULL) || (pairClock == 0UL) || ((baseClock % pairClock) != 0UL)) { return 0U; } ratio = baseClock / pairClock; if (ratio == 0UL) { return 0U; } pairDivider = (pairClock + (uint64_t)frequencyHz * UINT64_C(65536) - 1UL) / ((uint64_t)frequencyHz * UINT64_C(65536)); if (pairDivider == 0UL) { pairDivider = 1UL; } baseDivider = pairDivider * ratio; if ((pairDivider > UINT64_C(65536)) || (baseDivider > UINT64_C(65536))) { return 0U; } periodTicks = (pairClock + ((uint64_t)frequencyHz * pairDivider) / 2UL) / ((uint64_t)frequencyHz * pairDivider); if ((periodTicks < 4UL) || (periodTicks > UINT64_C(65536))) { return 0U; } *basePsc = (uint16_t)(baseDivider - 1UL); *pairPsc = (uint16_t)(pairDivider - 1UL); *arr = (uint16_t)(periodTicks - 1UL); return 1U; } static void PlsrHwLoadAbPwm(uint8_t axis, uint16_t basePsc, uint16_t pairPsc, uint16_t arr) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint32_t compare; compare = ((uint32_t)arr + 1UL) / 2UL; PlsrHwTimerSetPsc(axis, basePsc); PlsrHwTimerSetPsc(pairAxis, pairPsc); PlsrHwTimerSetArr(axis, arr); PlsrHwTimerSetArr(pairAxis, arr); PlsrHwTimerSetCcr(axis, compare); PlsrHwTimerSetCcr(pairAxis, compare); PlsrHwTimerSetPwmMode1(axis); PlsrHwTimerSetPwmMode1(pairAxis); PlsrHwTimerSetArpe(axis, 1UL); PlsrHwTimerSetArpe(pairAxis, 1UL); PlsrHwAxes[axis].abActiveBasePsc = basePsc; PlsrHwAxes[axis].abActivePairPsc = pairPsc; PlsrHwAxes[axis].abActiveArr = arr; } static uint8_t PlsrHwConfigureAbPwm(uint8_t axis, uint32_t frequencyHz) { uint16_t basePsc; uint16_t pairPsc; uint16_t arr; if (PlsrHwCalculateAbDividers(axis, frequencyHz, &basePsc, &pairPsc, &arr) == 0U) { return 0U; } PlsrHwLoadAbPwm(axis, basePsc, pairPsc, arr); return 1U; } /* 运行中的 AB 调频不能直接写两路 ARR 预装载:两路定时器相差 1/4 周期, * 各自的 update 时刻也相差 1/4 周期,会短暂使用不同周期并永久积累相位误差。 * 任务上下文只计算并发布最新参数,真正装载由 00 周期边界中断完成。 */ static uint8_t PlsrHwQueueAbFrequency(uint8_t axis, uint32_t frequencyHz) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint16_t basePsc; uint16_t pairPsc; uint16_t arr; #ifndef PLSR_HOST_TEST uint32_t interruptState; #endif if (PlsrHwCalculateAbDividers(axis, frequencyHz, &basePsc, &pairPsc, &arr) == 0U) { return 0U; } #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif if ((basePsc == state->abActiveBasePsc) && (pairPsc == state->abActivePairPsc) && (arr == state->abActiveArr)) { /* 量化后的分频参数未变化时取消旧请求,避免匀速段每 1ms 重定相。 */ state->abFrequencyPending = 0U; if ((state->abStopArmed == 0U) && (state->abPausePending == 0U)) { PlsrHwRestoreAbCycleInterrupt(axis); } } else { state->abPendingBasePsc = basePsc; state->abPendingPairPsc = pairPsc; state->abPendingArr = arr; state->abFrequencyPending = 1U; /* Observe both falling edges. The first can be the 11->single-low * boundary; only the second is a proven physical 00. */ PlsrHwArmAbBoundaryInterrupts(axis); } #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif return 1U; } static void PlsrHwBeginAbOutput(uint8_t axis, uint8_t debugReason) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint8_t leadAxis = (state->directionPositive != 0U) ? axis : pairAxis; uint8_t lagAxis = (state->directionPositive != 0U) ? pairAxis : axis; uint32_t periodTicks; uint32_t leadStart; uint32_t lagStart; #ifndef PLSR_HOST_TEST uint32_t interruptState; #else (void)debugReason; #endif #ifdef PLSR_HOST_TEST periodTicks = PlsrHwTimers[axis].arr + 1UL; #else periodTicks = PlsrHwAxisMap[axis].timer->ARR + 1UL; #endif leadStart = (periodTicks * 3UL) / 4UL + 1UL; if (leadStart >= periodTicks) { leadStart = periodTicks - 1UL; } /* Both counters must be strictly beyond CCR while GPIO is handed back to * AF. CNT==CCR can leave the compare/OCREF state implementation-defined * at the mux boundary and previously exposed one simultaneous A/B edge. */ lagStart = periodTicks / 2UL + 1UL; if (lagStart >= periodTicks) { lagStart = periodTicks - 1UL; } state->abCountAxis = lagAxis; state->counterSourceAxis = (axis == 0U) ? axis : pairAxis; state->abCounterBoundaryCnt = (state->counterSourceAxis == leadAxis) ? leadStart - 1UL : periodTicks / 2UL; state->abQuarter = 0U; state->abStartupPriming = 0U; state->abFastGated = 0U; #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); PlsrHwCounterSuspend(axis); PlsrHwHoldPulsePinLow(axis); PlsrHwHoldPulsePinLow(pairAxis); #endif PlsrHwTimerSetCen(axis, 0UL); PlsrHwTimerSetCen(pairAxis, 0UL); PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetCc1e(pairAxis, 0UL); PlsrHwTimerSetUie(axis, 0UL); PlsrHwTimerSetUie(pairAxis, 0UL); PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCc1ie(pairAxis, 0UL); PlsrHwTimerSetForcedInactive(axis); PlsrHwTimerSetForcedInactive(pairAxis); PlsrHwTimerSetUg(axis); PlsrHwTimerSetUg(pairAxis); PlsrHwTimerClearUif(axis); PlsrHwTimerClearUif(pairAxis); PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwTimerSetCnt(leadAxis, leadStart); PlsrHwTimerSetCnt(lagAxis, lagStart); /* The board's open-collector stage inverts the MCU waveform. Invert both * timer channels as well so forced-inactive/CNT>CCR and the lag compare * boundary are physical terminal 00 rather than 11. Complementing both * phases preserves the established quadrature direction. */ PlsrHwTimerSetCc1PolarityInverted(axis, 1UL); PlsrHwTimerSetCc1PolarityInverted(pairAxis, 1UL); /* Enable the forced-inactive channels while GPIO still owns the pins. * AF handoff and the later PWM1 selection therefore preserve the same 00 * electrical level at every mux point. */ PlsrHwTimerSetCc1e(axis, 1UL); PlsrHwTimerSetCc1e(pairAxis, 1UL); #ifndef PLSR_HOST_TEST __DMB(); /* CC1E is enabled, but forced-inactive drives the same idle level as * the GPIO hold. Hand the pins to AF now, before either timer can run. */ PlsrHwReleasePulsePin(axis); PlsrHwReleasePulsePin(pairAxis); #endif #ifdef PLSR_HOST_TEST PlsrHwTimerSetPwmMode1(axis); PlsrHwTimerSetPwmMode1(pairAxis); PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwCounterBegin(axis); PlsrHwStartAbTimersTightly(axis, pairAxis); PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwTimerSetCc1ie( lagAxis, ((state->hardwareCounterActive != 0U) && (state->abStopArmed == 0U)) ? 0UL : 1UL); #else PlsrHwTimerSetPwmMode1(axis); PlsrHwTimerSetPwmMode1(pairAxis); PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwCounterBegin(axis); PlsrHwStartAbTimersTightly(axis, pairAxis); PlsrHwTimerClearCc1if(axis); PlsrHwTimerClearCc1if(pairAxis); PlsrHwTimerSetCc1ie( lagAxis, ((state->hardwareCounterActive != 0U) && (state->abStopArmed == 0U)) ? 0UL : 1UL); __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif PlsrHwDbgCapture(axis, debugReason); } /* Apply an already calculated AB divider at a verified 00 boundary. Direct * target-register writes keep the complete rephase inside the 10us output ISR * budget while GPIO owns physical 00 during every CCR/ARR transition. */ static void PlsrHwApplyAbFrequencyAtBoundary(uint8_t axis, uint16_t basePsc, uint16_t pairPsc, uint16_t arr) { #ifdef PLSR_HOST_TEST PlsrHwLoadAbPwm(axis, basePsc, pairPsc, arr); PlsrHwBeginAbOutput(axis, 4U); #else PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint8_t leadAxis = (state->directionPositive != 0U) ? axis : pairAxis; uint8_t lagAxis = (state->directionPositive != 0U) ? pairAxis : axis; TIM_TypeDef *baseTimer = PlsrHwAxisMap[axis].timer; TIM_TypeDef *pairTimer = PlsrHwAxisMap[pairAxis].timer; TIM_TypeDef *leadTimer = PlsrHwAxisMap[leadAxis].timer; TIM_TypeDef *lagTimer = PlsrHwAxisMap[lagAxis].timer; uint32_t periodTicks = (uint32_t)arr + 1UL; uint32_t compare = periodTicks / 2UL; uint32_t leadStart = (periodTicks * 3UL) / 4UL + 1UL; uint32_t lagStart = periodTicks / 2UL + 1UL; uint32_t baseCr1; uint32_t pairCr1; if (leadStart >= periodTicks) { leadStart = periodTicks - 1UL; } if (lagStart >= periodTicks) { lagStart = periodTicks - 1UL; } baseTimer->CR1 &= ~TIM_CR1_CEN; pairTimer->CR1 &= ~TIM_CR1_CEN; PlsrHwCounterSuspend(axis); PlsrHwHoldAbPairLowFast(axis); baseTimer->CCER &= ~TIM_CCER_CC1E; pairTimer->CCER &= ~TIM_CCER_CC1E; baseTimer->DIER &= ~(TIM_DIER_UIE | TIM_DIER_CC1IE); pairTimer->DIER &= ~(TIM_DIER_UIE | TIM_DIER_CC1IE); baseTimer->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE; pairTimer->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1PE; baseTimer->PSC = basePsc; pairTimer->PSC = pairPsc; baseTimer->ARR = arr; pairTimer->ARR = arr; baseTimer->CCR1 = compare; pairTimer->CCR1 = compare; baseTimer->CR1 |= TIM_CR1_ARPE; pairTimer->CR1 |= TIM_CR1_ARPE; baseTimer->EGR = TIM_EGR_UG; pairTimer->EGR = TIM_EGR_UG; baseTimer->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF); pairTimer->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF); leadTimer->CNT = leadStart; lagTimer->CNT = lagStart; baseTimer->CCER = (baseTimer->CCER & ~(TIM_CCER_CC1P | TIM_CCER_CC1E)) | TIM_CCER_CC1P | TIM_CCER_CC1E; pairTimer->CCER = (pairTimer->CCER & ~(TIM_CCER_CC1P | TIM_CCER_CC1E)) | TIM_CCER_CC1P | TIM_CCER_CC1E; PlsrHwReleaseAbPairFast(axis); baseTimer->CCMR1 = (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2) | TIM_CCMR1_OC1PE; pairTimer->CCMR1 = (TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2) | TIM_CCMR1_OC1PE; state->abActiveBasePsc = basePsc; state->abActivePairPsc = pairPsc; state->abActiveArr = arr; state->abCountAxis = lagAxis; state->counterSourceAxis = (axis == 0U) ? axis : pairAxis; state->abCounterBoundaryCnt = (state->counterSourceAxis == leadAxis) ? leadStart - 1UL : periodTicks / 2UL; state->abQuarter = 0U; state->abStartupPriming = 0U; state->abFastGated = 0U; PlsrHwCounterBegin(axis); baseCr1 = baseTimer->CR1 | TIM_CR1_CEN; pairCr1 = pairTimer->CR1 | TIM_CR1_CEN; baseTimer->CR1 = baseCr1; pairTimer->CR1 = pairCr1; baseTimer->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF); pairTimer->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF); if ((state->hardwareCounterActive == 0U) || (state->abStopArmed != 0U)) { lagTimer->DIER |= TIM_DIER_CC1IE; } __DMB(); #endif } static void PlsrHwConfigureCwCcwPwm(uint8_t axis, uint32_t frequencyHz) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t activeAxis = state->cwActiveAxis; uint16_t psc; uint16_t arr; #ifndef PLSR_HOST_TEST uint32_t interruptState; #endif if (PlsrCalculateTimerDivider(PlsrHwAxisMap[activeAxis].timerClockHz, frequencyHz, &psc, &arr) != PLSR_RESULT_OK) { return; } #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif /* The compare ISR may arm final-pulse shutdown while TIM6 is calculating * a new divider. Recheck under the same short critical section as the * preload writes so the tail period can no longer be changed afterwards. */ if (state->cwStopPending == 0U) { PlsrHwTimerSetPsc(activeAxis, psc); PlsrHwTimerSetArr(activeAxis, arr); PlsrHwTimerSetCcr(activeAxis, (uint32_t)arr / 2UL); PlsrHwTimerSetPwmMode1(activeAxis); PlsrHwTimerSetArpe(activeAxis, 1UL); } #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwBeginCwCcwOutput(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint8_t activeAxis = state->cwActiveAxis; #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif /* Keep both pins in timer AF. On this output chain, switching a channel * to GPIO-low is observable as an asserted Q edge. CC1E=0 is the tested * inactive level and avoids the extra start/end edge. */ PlsrHwStopPwmTimer(axis); PlsrHwStopPwmTimer(pairAxis); /* CW/CCW retains the previously validated non-inverted polarity. */ PlsrHwTimerSetCc1PolarityInverted(axis, 0UL); PlsrHwTimerSetCc1PolarityInverted(pairAxis, 0UL); state->cwStopPending = 0U; PlsrHwTimerSetUg(activeAxis); PlsrHwTimerClearUif(activeAxis); PlsrHwTimerClearCc1if(activeAxis); PlsrHwTimerSetCnt(activeAxis, 0UL); PlsrHwTimerSetUie(activeAxis, 0UL); PlsrHwTimerSetCc1ie(activeAxis, 1UL); PlsrHwTimerSetCc1e(activeAxis, 1UL); PlsrHwTimerSetCen(activeAxis, 1UL); #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwStopCwCcwOutput(uint8_t axis) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif /* Disable both compare outputs while retaining AF mode; do not force * either pin through GPIO during the direction handover. */ PlsrHwStopPwmTimer(axis); PlsrHwStopPwmTimer(pairAxis); PlsrHwAxes[axis].cwStopPending = 0U; #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwConfigureActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode, uint32_t frequencyHz) { if (outputMode == PLSR_OUTPUT_AB) { (void)PlsrHwConfigureAbPwm(axis, frequencyHz); } else if (outputMode == PLSR_OUTPUT_CW_CCW) { PlsrHwConfigureCwCcwPwm(axis, frequencyHz); } else { PlsrHwConfigurePwm(axis, frequencyHz); } } static void PlsrHwBeginActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode) { if (outputMode == PLSR_OUTPUT_AB) { PlsrHwBeginAbOutput(axis, 0U); } else if (outputMode == PLSR_OUTPUT_CW_CCW) { PlsrHwBeginCwCcwOutput(axis); } else { PlsrHwPwmBegin(axis); } } static void PlsrHwStopActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode) { if ((outputMode == PLSR_OUTPUT_AB) && (PlsrHwIsAbBaseAxis(axis) != 0U)) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); /* DONE/STOP 后继续由 GPIO 保持 00,禁止已关闭 timer 泄漏残余边沿。 */ PlsrHwHoldPulsePinLow(axis); PlsrHwHoldPulsePinLow(pairAxis); #endif PlsrHwStopPwmTimer(axis); PlsrHwStopPwmTimer(pairAxis); PlsrHwAxes[axis].abStartupPriming = 0U; #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } else if ((outputMode == PLSR_OUTPUT_CW_CCW) && (PlsrHwIsAbBaseAxis(axis) != 0U)) { PlsrHwStopCwCcwOutput(axis); } else { PlsrHwStopPulseDirOutput(axis); } } static uint8_t PlsrHwCounterIndexForAxis(uint8_t axis) { return (uint8_t)(axis & 1U); } static void PlsrHwTimerSetCc1PolarityInverted(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST PlsrHwTimers[axis].ccer = (PlsrHwTimers[axis].ccer & ~PLSR_HW_TIMER_CC1P_BIT) | ((value != 0UL) ? PLSR_HW_TIMER_CC1P_BIT : 0UL); #else if (value != 0UL) { PlsrHwAxisMap[axis].timer->CCER |= TIM_CCER_CC1P; } else { PlsrHwAxisMap[axis].timer->CCER &= ~TIM_CCER_CC1P; } #endif } static uint8_t PlsrHwCounterTryAcquire(uint8_t axis, PLSR_OUTPUT_MODE outputMode) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint8_t counterIndex; uint8_t acquired = 0U; #ifndef PLSR_HOST_TEST uint32_t interruptState; #endif state->counterIndex = PLSR_HW_COUNTER_NONE; state->hardwareCounterActive = 0U; state->hardwareCounterConfigured = 0U; state->counterBlockPulses = 0UL; state->counterPublishedPulses = 0UL; if ((outputMode != PLSR_OUTPUT_PULSE_DIR) && (outputMode != PLSR_OUTPUT_AB)) { return 0U; } if (outputMode == PLSR_OUTPUT_AB) { /* One counter per fixed AB pair: Q0/Q1 -> TIM9, Q2/Q3 -> TIM12. * Very short jobs retain the existing per-cycle ISR because a * target-1 guard compare cannot be armed at raw count zero. */ if ((PlsrHwIsAbBaseAxis(axis) == 0U) || (state->targetPulses < 2)) { return 0U; } counterIndex = (uint8_t)(axis >> 1U); } else { counterIndex = PlsrHwCounterIndexForAxis(axis); } #ifndef PLSR_HOST_TEST interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif if (PlsrHwCounterOwners[counterIndex] == PLSR_HW_COUNTER_NONE) { PlsrHwCounterOwners[counterIndex] = axis; state->counterIndex = counterIndex; state->hardwareCounterActive = 1U; acquired = 1U; } #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif return acquired; } static void PlsrHwCounterRelease(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { #ifndef PLSR_HOST_TEST TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; counter->CR1 = 0UL; counter->DIER = 0UL; counter->SMCR = 0UL; counter->SR = 0UL; #endif if (PlsrHwCounterOwners[state->counterIndex] == axis) { PlsrHwCounterOwners[state->counterIndex] = PLSR_HW_COUNTER_NONE; } } state->counterIndex = PLSR_HW_COUNTER_NONE; state->hardwareCounterActive = 0U; state->hardwareCounterConfigured = 0U; state->counterBlockPulses = 0UL; state->counterPublishedPulses = 0UL; #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } static void PlsrHwCounterConfigure(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; state->counterBlockPulses = 0UL; state->counterPublishedPulses = 0UL; #ifndef PLSR_HOST_TEST if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; uint32_t triggerSelection = ((axis & 2U) == 0U) ? TIM_SMCR_TS_1 : (TIM_SMCR_TS_1 | TIM_SMCR_TS_0); uint64_t comparePulses = (uint64_t)state->targetPulses; /* PULSE/DIR owns one source timer and can force it inactive here. AB * owns a pair; its atomic startup routine establishes both OCREF lows * immediately before CounterBegin instead. */ if (state->outputMode == PLSR_OUTPUT_PULSE_DIR) { PlsrHwTimerSetCen(axis, 0UL); PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetForcedInactive(axis); PlsrHwTimerSetUg(axis); PlsrHwTimerClearUif(axis); PlsrHwTimerClearCc1if(axis); if (comparePulses > 1UL) { /* Wake one pulse before the target. The output timer can * then stop on the target pulse's compare/falling edge rather * than truncating the high level in this counter ISR. */ comparePulses--; } } else { /* Wake the lag-CC1 one complete cycle before the target boundary. * It verifies raw>=target at 00, avoiding ISR-latency overshoot. */ comparePulses--; } counter->CR1 = 0UL; counter->DIER = 0UL; counter->SMCR = 0UL; counter->PSC = 0UL; counter->ARR = 0xFFFFUL; counter->CCR1 = (uint32_t)(comparePulses & UINT64_C(0xFFFF)); counter->CNT = 0UL; counter->EGR = TIM_EGR_UG; counter->SR = 0UL; counter->SMCR = triggerSelection; counter->DIER = TIM_DIER_UIE | TIM_DIER_CC1IE; } #endif } static void PlsrHwCounterBegin(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if (state->hardwareCounterActive == 0U) { return; } if (state->hardwareCounterConfigured == 0U) { return; } #ifndef PLSR_HOST_TEST if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; counter->SMCR |= TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0; counter->CR1 |= TIM_CR1_CEN; } #endif } static void PlsrHwArmPulseTargetTail(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if (state->pulseTargetStopArmed != 0U) { return; } state->pulseTargetStopArmed = 1U; /* ARR/CCR are both preloaded and become active only at the target update. * The in-flight target pulse therefore retains its old validated width; * afterwards CNThardwareCounterActive == 0U) || (state->hardwareCounterConfigured == 0U)) { return; } #ifndef PLSR_HOST_TEST if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; counter->CR1 &= ~TIM_CR1_CEN; counter->SMCR &= ~(TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0); } #endif } static void PlsrHwCounterRebase(uint8_t axis, uint64_t pulses) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if ((state->hardwareCounterActive == 0U) || (state->hardwareCounterConfigured == 0U)) { return; } #ifdef PLSR_HOST_TEST state->counterBlockPulses = pulses; #else state->counterBlockPulses = pulses & ~(PLSR_HW_COUNTER_BLOCK_PULSES - UINT64_C(1)); if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; counter->CNT = (uint16_t)pulses; counter->SR = 0UL; } #endif state->counterPublishedPulses = pulses; } static uint64_t PlsrHwCounterRawSnapshot(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint64_t pulses = state->counterBlockPulses; #ifndef PLSR_HOST_TEST if (state->counterIndex < PLSR_HW_COUNTER_COUNT) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; pulses += (uint16_t)counter->CNT; /* Cover the short window after wrap and before the block ISR. */ if ((counter->SR & TIM_SR_UIF) != 0UL) { pulses += PLSR_HW_COUNTER_BLOCK_PULSES; } } #else pulses = (state->outputMode == PLSR_OUTPUT_AB) ? state->counterBlockPulses : (uint64_t)state->emittedPulses; #endif return pulses; } static uint64_t PlsrHwCounterSnapshot(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; uint64_t pulses = PlsrHwCounterRawSnapshot(axis); #ifndef PLSR_HOST_TEST if ((state->outputMode == PLSR_OUTPUT_AB) && (pulses > 0UL) && (state->counterSourceAxis < PLSR_HW_AXIS_COUNT)) { uint64_t verifiedPulses; uint32_t sourceCnt; uint8_t attempt; /* The ITR source rises inside an AB cycle, before the following 00 * boundary. A stable raw/CNT/raw snapshot identifies that interval * and publishes only complete four-state cycles. */ for (attempt = 0U; attempt < 2U; attempt++) { pulses = PlsrHwCounterRawSnapshot(axis); sourceCnt = PlsrHwAxisMap[state->counterSourceAxis].timer->CNT; verifiedPulses = PlsrHwCounterRawSnapshot(axis); if (pulses == verifiedPulses) { if ((sourceCnt < state->abCounterBoundaryCnt) && (pulses > 0UL)) { pulses--; } break; } pulses = verifiedPulses; } } #else if ((state->outputMode == PLSR_OUTPUT_AB) && (pulses > 0UL)) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); uint8_t leadAxis = (state->directionPositive != 0U) ? axis : pairAxis; uint8_t sourceQuarter = (state->counterSourceAxis == leadAxis) ? 1U : 2U; if ((state->abQuarter >= sourceQuarter) && (state->abQuarter != 0U)) { pulses--; } } #endif if (pulses > (uint64_t)state->targetPulses) { pulses = (uint64_t)state->targetPulses; } /* The source timer and its TIM9/TIM12 ITR slave are separate hardware * domains. Immediately after the source edge, source CNT can already be * inside the next AB cycle while the slave raw count still has its old * value. Phase correction would then transiently report N-1 after N was * already published, and the core correctly treats that regression as a * counter fault. Complete physical AB cycles never go backwards, so keep * the last verified value as a monotonic floor. */ if (pulses < state->counterPublishedPulses) { pulses = state->counterPublishedPulses; } else { state->counterPublishedPulses = pulses; } return pulses; } /* AB terminal and pause IRQs only freeze the two phase timers at a verified * 00 boundary. GPIO handoff, counter release/rebase and event publication * are deliberately deferred to PlsrHwTick so an equal-priority second AB * boundary can be serviced before its next quarter-period transition. */ static void PlsrHwFinishDeferredAbWork(uint8_t axis) { PLSR_HW_AXIS_STATE *state; if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return; } state = &PlsrHwAxes[axis]; if (state->abPauseGated != 0U) { uint64_t completedPulses = (state->hardwareCounterActive != 0U) ? PlsrHwCounterSnapshot(axis) : (uint64_t)state->emittedPulses; state->emittedPulses = (int64_t)completedPulses; PlsrHwStopActiveOutput(axis, state->outputMode); if (state->hardwareCounterActive != 0U) { PlsrHwCounterRebase(axis, completedPulses); } state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abPauseGated = 0U; state->abStopArmed = (completedPulses >= (uint64_t)(state->targetPulses - 1)) ? 1U : 0U; } if (state->abCompletionDeferred != 0U) { state->emittedPulses = state->targetPulses; PlsrHwStopActiveOutput(axis, state->outputMode); PlsrHwCounterRelease(axis); state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abStopArmed = 0U; state->abFastGated = 0U; state->abCompletionDeferred = 0U; (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } } uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber) { /* 与资源层一致的合法输出点掩码(Q0~Q7、Q10~Q17、Q20)。 */ const uint32_t validOutputMask = 0x0013FCFFUL; if (pointNumber >= PLSR_HW_OUTPUT_POINT_COUNT) { return 0U; } if ((validOutputMask & (1UL << pointNumber)) == 0UL) { return 0U; } #ifndef PLSR_HOST_TEST if (PlsrHwOutputPins[pointNumber].port == NULL) { return 0U; } #endif return 1U; } PLSR_RESULT PlsrHwSetValidationOutput(uint8_t pointNumber, uint8_t terminalOn) { uint8_t axis; if ((pointNumber < 1U) || (pointNumber > 4U)) { return PLSR_RESULT_INVALID_RESOURCE; } for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++) { if ((PlsrHwAxes[axis].directionPoint == pointNumber) && (PlsrHwAxes[axis].state != PLSR_HW_STATE_IDLE) && (PlsrHwAxes[axis].state != PLSR_HW_STATE_DONE)) { return PLSR_RESULT_RESOURCE_CONFLICT; } if ((pointNumber < PLSR_HW_AXIS_COUNT) && (PlsrHwAxes[axis].state != PLSR_HW_STATE_IDLE) && (PlsrHwAxes[axis].state != PLSR_HW_STATE_DONE) && ((axis == pointNumber) || (((PlsrHwAxes[axis].outputMode == PLSR_OUTPUT_AB) || (PlsrHwAxes[axis].outputMode == PLSR_OUTPUT_CW_CCW)) && ((axis & 0xFEU) == (pointNumber & 0xFEU))))) { return PLSR_RESULT_RESOURCE_CONFLICT; } } #ifdef PLSR_HOST_TEST if (terminalOn != 0U) { PlsrHwValidationOutputMask |= (uint8_t)(1U << (pointNumber - 1U)); } else { PlsrHwValidationOutputMask &= (uint8_t)(~(uint8_t)(1U << (pointNumber - 1U))); } #else { const PLSR_HW_OUTPUT_PIN *pin = &PlsrHwOutputPins[pointNumber]; GPIO_InitTypeDef gpio; uint8_t mask = (uint8_t)(1U << (pointNumber - 1U)); /* Board output ON is MCU-low. Establish OFF before changing MODER * so enabling the validation fixture cannot create a terminal pulse. */ if ((PlsrHwValidationOutputMask & mask) == 0U) { pin->port->BSRR = (uint32_t)pin->pin; gpio.Pin = pin->pin; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; gpio.Alternate = 0U; HAL_GPIO_Init(pin->port, &gpio); } pin->port->BSRR = (terminalOn != 0U) ? ((uint32_t)pin->pin << 16U) : (uint32_t)pin->pin; if (terminalOn != 0U) { PlsrHwValidationOutputMask |= mask; } else { PlsrHwValidationOutputMask &= (uint8_t)(~mask); } } #endif return PLSR_RESULT_OK; } void PlsrHwValidationOutputsOff(void) { uint8_t point; for (point = 1U; point <= 4U; point++) { (void)PlsrHwSetValidationOutput(point, 0U); } } PLSR_RESULT PlsrHwInit(void) { uint8_t axis; uint8_t counterIndex; (void)memset(PlsrHwAxes, 0, sizeof(PlsrHwAxes)); PlsrHwDirectionBatchActive = 0U; PlsrHwMaxOutputIsrCycles = 0UL; PlsrHwMaxCounterIsrCycles = 0UL; PlsrHwMaxControlIsrCycles = 0UL; PlsrHwMaxAbGateCycles = 0UL; PlsrHwValidationOutputMask = 0U; #ifndef PLSR_HOST_TEST PlsrHwAbGateMeasurePending = 0U; #endif PlsrHwTotalIsrCycles = 0UL; PlsrHwIsrBusyStarted = 0UL; PlsrHwIsrNesting = 0U; #ifdef PLSR_HOST_TEST PlsrHwTestLateAbFlagAxis = PLSR_HW_COUNTER_NONE; PlsrHwTestAbFullGateCount = 0UL; #endif for (counterIndex = 0U; counterIndex < PLSR_HW_COUNTER_COUNT; counterIndex++) { PlsrHwCounterOwners[counterIndex] = PLSR_HW_COUNTER_NONE; } for (axis = 0U; axis < PLSR_HW_AXIS_COUNT; axis++) { PlsrHwAxes[axis].state = PLSR_HW_STATE_IDLE; PlsrHwAxes[axis].directionPoint = PLSR_HW_DIR_POINT_NONE; PlsrHwAxes[axis].counterIndex = PLSR_HW_COUNTER_NONE; PlsrHwAxes[axis].configuredDirectionPoint = PLSR_HW_DIR_POINT_NONE; #ifdef PLSR_HOST_TEST (void)memset(&PlsrHwTimers[axis], 0, sizeof(PlsrHwTimers[axis])); #else PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetCc1PolarityInverted(axis, 0UL); PlsrHwTimerSetUie(axis, 0UL); PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCen(axis, 0UL); #endif } #ifndef PLSR_HOST_TEST { GPIO_InitTypeDef gpio; uint32_t tim6ClockHz; uint16_t tim6Psc; uint16_t tim6Arr; CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0UL; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; /* 1. 输出点 GPIO 时钟(DIR 点按需配置时使用)。 */ __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOI_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); /* 2. 上电安全:输出点保持复位默认高阻(漏型输出 = 截止 = OFF)。 * 不驱动任何 Y 点,DIR 点仅在 PlsrHwSetDirLevel 时按需配置。 */ /* 3. 定时器时钟。 */ __HAL_RCC_TIM10_CLK_ENABLE(); __HAL_RCC_TIM11_CLK_ENABLE(); __HAL_RCC_TIM13_CLK_ENABLE(); __HAL_RCC_TIM14_CLK_ENABLE(); __HAL_RCC_TIM6_CLK_ENABLE(); __HAL_RCC_TIM9_CLK_ENABLE(); __HAL_RCC_TIM12_CLK_ENABLE(); /* Independent 10kHz control clock for S2 refreshCode=2. */ tim6ClockHz = HAL_RCC_GetPCLK1Freq(); if ((RCC->CFGR & RCC_CFGR_PPRE1) != RCC_CFGR_PPRE1_DIV1) { tim6ClockHz *= 2UL; } if (PlsrCalculateTimerDivider(tim6ClockHz, 10000UL, &tim6Psc, &tim6Arr) != PLSR_RESULT_OK) { return PLSR_RESULT_DIVIDER_UNREPRESENTABLE; } TIM6->CR1 = 0UL; TIM6->DIER = 0UL; TIM6->PSC = tim6Psc; TIM6->ARR = tim6Arr; TIM6->EGR = TIM_EGR_UG; TIM6->SR = 0UL; TIM6->DIER = TIM_DIER_UIE; HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 2U, 0U); HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn); TIM6->CR1 = TIM_CR1_ARPE | TIM_CR1_CEN; /* 4. 脉冲点切定时器复用(PF6/7=AF3、PF8/9=AF9)。 * 定时器通道尚未使能(CC1E=0),输出级断开,无毛刺。 */ gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7; gpio.Alternate = 3U; HAL_GPIO_Init(GPIOF, &gpio); gpio.Pin = GPIO_PIN_8 | GPIO_PIN_9; gpio.Alternate = 9U; HAL_GPIO_Init(GPIOF, &gpio); /* 5. 更新中断 NVIC:高速计数/尾脉冲层(P3b 统一规划优先级表)。 */ HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn); HAL_NVIC_SetPriority(TIM1_TRG_COM_TIM11_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM1_TRG_COM_TIM11_IRQn); HAL_NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn); HAL_NVIC_SetPriority(TIM1_BRK_TIM9_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM1_BRK_TIM9_IRQn); HAL_NVIC_SetPriority(TIM8_BRK_TIM12_IRQn, 1U, 0U); HAL_NVIC_EnableIRQ(TIM8_BRK_TIM12_IRQn); } #endif return PLSR_RESULT_OK; } PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) { PLSR_HW_AXIS_STATE *state; uint8_t directionChanged = 0U; if ((axis >= PLSR_HW_AXIS_COUNT) || (params == NULL)) { return PLSR_RESULT_INVALID_ARGUMENT; } if (params->targetPulses <= 0) { return PLSR_RESULT_INVALID_ARGUMENT; } if ((uint32_t)params->outputMode > (uint32_t)PLSR_OUTPUT_CW_CCW) { return PLSR_RESULT_INVALID_ARGUMENT; } if (((params->outputMode == PLSR_OUTPUT_AB) || (params->outputMode == PLSR_OUTPUT_CW_CCW)) && (PlsrHwIsAbBaseAxis(axis) == 0U)) { return PLSR_RESULT_INVALID_AXIS; } state = &PlsrHwAxes[axis]; if ((state->state == PLSR_HW_STATE_RUNNING) || (state->abCompletionDeferred != 0U) || (state->abPauseGated != 0U)) { return PLSR_RESULT_BUSY; } /* A caller may replace a prepared-but-not-started segment. Return its * counter lease first, otherwise the paired axis would fall back forever. */ if (state->hardwareCounterActive != 0U) { PlsrHwCounterRelease(axis); } /* 方向延时只在方向发生变化时生效(首次启动/换向/换方向点): * 段间同向衔接不再等待 10ms,直接进入 PWM 待启动。 */ if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) { directionChanged = (state->directionPoint == PLSR_HW_DIR_POINT_NONE) || (state->directionPoint != params->directionPoint) || (state->directionPositive != params->directionPositive) || (state->directionNegativeLogic != params->directionNegativeLogic); } state->outputMode = params->outputMode; state->targetPulses = params->targetPulses; state->emittedPulses = 0; state->currentFrequencyHz = params->frequencyHz; state->directionPoint = (params->outputMode == PLSR_OUTPUT_PULSE_DIR) ? params->directionPoint : PLSR_HW_DIR_POINT_NONE; state->directionDelayRemainingMs = ((params->outputMode == PLSR_OUTPUT_PULSE_DIR) && (directionChanged != 0U)) ? params->directionDelayMs : 0U; state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abStopArmed = 0U; state->abFastGated = 0U; state->abPausePending = 0U; state->abPauseGated = 0U; state->abCompletionDeferred = 0U; state->pulseBoundaryStopPending = 0U; state->pulseTargetStopArmed = 0U; if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) { PlsrHwSetDirLevel(axis, params->directionPositive, params->directionNegativeLogic); } else { state->directionPositive = (params->directionPositive != 0U) ? 1U : 0U; state->directionNegativeLogic = 0U; } if (params->outputMode == PLSR_OUTPUT_CW_CCW) { state->cwActiveAxis = (state->directionPositive != 0U) ? axis : PlsrHwGetPairedAxis(axis); } (void)PlsrHwCounterTryAcquire(axis, params->outputMode); if (state->hardwareCounterActive != 0U) { PlsrHwCounterConfigure(axis); state->hardwareCounterConfigured = 1U; } state->state = (state->directionDelayRemainingMs > 0U) ? PLSR_HW_STATE_DIR_SETTLING : PLSR_HW_STATE_PWM_PENDING; return PLSR_RESULT_OK; } PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) { PLSR_HW_AXIS_STATE *state; if (axis >= PLSR_HW_AXIS_COUNT) { return PLSR_RESULT_INVALID_ARGUMENT; } state = &PlsrHwAxes[axis]; if ((state->pulseBoundaryStopPending != 0U) || (state->pulseTargetStopArmed != 0U)) { /* ACT/EXT already froze the requested waveform. Do not allow the * 100us profile ISR to move ARR/CCR before the natural tail edge. */ return PLSR_RESULT_OK; } if ((state->outputMode == PLSR_OUTPUT_CW_CCW) && (state->cwStopPending != 0U)) { /* Preserve the final physical high width until its natural update * boundary; no later profile write may move that boundary. */ return PLSR_RESULT_OK; } if ((state->state == PLSR_HW_STATE_RUNNING) && (frequencyHz == state->currentFrequencyHz)) { /* Cruise ticks commonly request the same frequency for every axis. * Recomputing PSC/ARR performs two 64-bit divisions and rewrites the * same preload registers without changing the waveform. */ return PLSR_RESULT_OK; } state->currentFrequencyHz = frequencyHz; if (state->state == PLSR_HW_STATE_RUNNING) { if (frequencyHz > 0UL) { if (state->outputMode == PLSR_OUTPUT_AB) { if (PlsrHwQueueAbFrequency(axis, frequencyHz) == 0U) { return PLSR_RESULT_DIVIDER_UNREPRESENTABLE; } } else { /* PULSE/DIR 仍由单定时器在自身 update 边界加载预装值。 */ PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); } } else { if (state->outputMode == PLSR_OUTPUT_AB) { #ifndef PLSR_HOST_TEST uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif /* PAUSE is a controlled AB stop. Keep both timers running * until the lag compare reaches the next real 00 boundary; * forcing GPIO low here would discard and later re-emit an * already-started cycle, adding one terminal edge. */ state->abFrequencyPending = 0U; state->abPausePending = 1U; PlsrHwArmAbBoundaryInterrupts(axis); #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } else { PlsrHwStopActiveOutput(axis, state->outputMode); } } } else if ((state->state == PLSR_HW_STATE_PWM_PENDING) && (frequencyHz > 0UL)) { PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); /* 必须先发布 RUNNING,避免启用 timer IRQ 后观察到 PWM_PENDING。 */ state->state = PLSR_HW_STATE_RUNNING; PlsrHwBeginActiveOutput(axis, state->outputMode); if ((state->outputMode == PLSR_OUTPUT_PULSE_DIR) && (state->targetPulses == 1)) { PlsrHwArmPulseTargetTail(axis); } } return PLSR_RESULT_OK; } PLSR_RESULT PlsrHwResumePulse(uint8_t axis) { PLSR_HW_AXIS_STATE *state; if (axis >= PLSR_HW_AXIS_COUNT) { return PLSR_RESULT_INVALID_ARGUMENT; } state = &PlsrHwAxes[axis]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->currentFrequencyHz != 0UL) || (state->abPausePending != 0U) || (state->abPauseGated != 0U) || (PlsrHwGetEmittedPulses(axis) >= state->targetPulses)) { return PLSR_RESULT_INVALID_STATE; } /* The pause boundary worker stopped the physical timers at 00 and kept * the completed-cycle count. PWM_PENDING makes the next non-zero control * tick use the clean-start path without resetting that count. */ state->state = PLSR_HW_STATE_PWM_PENDING; return PLSR_RESULT_OK; } PLSR_RESULT PlsrHwStopPulseAtBoundary(uint8_t axis) { PLSR_HW_AXIS_STATE *state; if (axis >= PLSR_HW_AXIS_COUNT) { return PLSR_RESULT_INVALID_ARGUMENT; } state = &PlsrHwAxes[axis]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_PULSE_DIR)) { return PLSR_RESULT_INVALID_STATE; } if (state->pulseBoundaryStopPending != 0U) { return PLSR_RESULT_OK; } #ifndef PLSR_HOST_TEST { uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); #endif state->pulseBoundaryStopPending = 1U; /* An update is the completed terminal-pulse boundary. Hardware-counted * axes normally keep UIE disabled, so explicitly arm one update here. */ PlsrHwTimerClearUif(axis); PlsrHwTimerSetUie(axis, 1UL); #ifndef PLSR_HOST_TEST __DMB(); if (interruptState == 0UL) { __enable_irq(); } } #endif return PLSR_RESULT_OK; } PLSR_RESULT PlsrHwStopPulse(uint8_t axis) { PLSR_HW_AXIS_STATE *state; if (axis >= PLSR_HW_AXIS_COUNT) { return PLSR_RESULT_INVALID_ARGUMENT; } state = &PlsrHwAxes[axis]; if (state->state != PLSR_HW_STATE_IDLE) { if (state->hardwareCounterActive != 0U) { state->emittedPulses = (int64_t)PlsrHwCounterSnapshot(axis); } PlsrHwStopActiveOutput(axis, state->outputMode); PlsrHwCounterRelease(axis); state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abStopArmed = 0U; state->abFastGated = 0U; state->abPausePending = 0U; state->abPauseGated = 0U; state->abCompletionDeferred = 0U; state->pulseBoundaryStopPending = 0U; state->pulseTargetStopArmed = 0U; state->state = PLSR_HW_STATE_IDLE; } return PLSR_RESULT_OK; } uint8_t PlsrHwIsPulseActive(uint8_t axis) { if (axis >= PLSR_HW_AXIS_COUNT) { return 0U; } return (PlsrHwAxes[axis].state == PLSR_HW_STATE_RUNNING) ? 1U : 0U; } PLSR_HW_STATE PlsrHwGetState(uint8_t axis) { if (axis >= PLSR_HW_AXIS_COUNT) { return PLSR_HW_STATE_IDLE; } return PlsrHwAxes[axis].state; } uint32_t PlsrHwGetTimerClockHz(uint8_t axis) { if (axis >= PLSR_HW_AXIS_COUNT) { return 0UL; } return PlsrHwAxisMap[axis].timerClockHz; } uint32_t PlsrHwGetCurrentFrequencyHz(uint8_t axis) { if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwAxes[axis].state != PLSR_HW_STATE_RUNNING)) { return 0UL; } return PlsrHwAxes[axis].currentFrequencyHz; } /* 硬件已发出的脉冲数(profile 虚拟计数校准用,中断内递增)。 */ int64_t PlsrHwGetEmittedPulses(uint8_t axis) { int64_t emittedPulses; if (axis >= PLSR_HW_AXIS_COUNT) { return 0; } #ifdef PLSR_HOST_TEST emittedPulses = (PlsrHwAxes[axis].hardwareCounterActive != 0U) ? (int64_t)PlsrHwCounterSnapshot(axis) : PlsrHwAxes[axis].emittedPulses; #else { uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); emittedPulses = (PlsrHwAxes[axis].hardwareCounterActive != 0U) ? (int64_t)PlsrHwCounterSnapshot(axis) : PlsrHwAxes[axis].emittedPulses; __DMB(); if (interruptState == 0UL) { __enable_irq(); } } #endif return emittedPulses; } uint8_t PlsrHwIsAbStartupPriming(uint8_t axis) { if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return 0U; } return PlsrHwAxes[axis].abStartupPriming; } uint8_t PlsrHwUsesHardwareCounter(uint8_t axis) { if (axis >= PLSR_HW_AXIS_COUNT) { return 0U; } return PlsrHwAxes[axis].hardwareCounterActive; } uint32_t PlsrHwGetMaxOutputIsrCycles(void) { return PlsrHwMaxOutputIsrCycles; } uint32_t PlsrHwGetMaxCounterIsrCycles(void) { return PlsrHwMaxCounterIsrCycles; } uint32_t PlsrHwGetMaxControlIsrCycles(void) { return PlsrHwMaxControlIsrCycles; } uint32_t PlsrHwGetMaxAbGateCycles(void) { return PlsrHwMaxAbGateCycles; } void PlsrHwGetCycleSnapshot(uint32_t *cycleCount, uint64_t *plsrIsrCycles) { #ifdef PLSR_HOST_TEST if (cycleCount != NULL) { *cycleCount = 0UL; } if (plsrIsrCycles != NULL) { *plsrIsrCycles = 0UL; } #else uint32_t interruptState = __get_PRIMASK(); __disable_irq(); __DMB(); if (cycleCount != NULL) { *cycleCount = DWT->CYCCNT; } if (plsrIsrCycles != NULL) { *plsrIsrCycles = PlsrHwTotalIsrCycles; } __DMB(); if (interruptState == 0UL) { __enable_irq(); } #endif } void PlsrHwTick(uint8_t axis) { PLSR_HW_AXIS_STATE *state; if (axis >= PLSR_HW_AXIS_COUNT) { return; } state = &PlsrHwAxes[axis]; if ((state->abPauseGated != 0U) || (state->abCompletionDeferred != 0U)) { PlsrHwFinishDeferredAbWork(axis); } /* 调试:每 tick 记录定时器实况(CNT 演化定位第一周期压缩)。 */ PlsrHwDbgCapture(axis, 3U); switch (state->state) { case PLSR_HW_STATE_DIR_SETTLING: if (state->directionDelayRemainingMs > 0U) { state->directionDelayRemainingMs--; } if (state->directionDelayRemainingMs == 0U) { state->state = PLSR_HW_STATE_PWM_PENDING; } break; case PLSR_HW_STATE_PWM_PENDING: if (state->currentFrequencyHz > 0UL) { PlsrHwConfigureActiveOutput(axis, state->outputMode, state->currentFrequencyHz); state->state = PLSR_HW_STATE_RUNNING; PlsrHwBeginActiveOutput(axis, state->outputMode); } break; default: break; } } static void PlsrHwFastGateAbPair(uint8_t axis) { uint8_t pairAxis = PlsrHwGetPairedAxis(axis); #ifdef PLSR_HOST_TEST PlsrHwTimerSetCen(axis, 0UL); PlsrHwTimerSetCen(pairAxis, 0UL); PlsrHwCounterSuspend(axis); #else PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; /* This is the sub-2.5us terminal boundary. Use the same register writes * as the generic helpers without their low-optimization call overhead. */ PlsrHwAxisMap[axis].timer->CR1 &= ~TIM_CR1_CEN; PlsrHwAxisMap[pairAxis].timer->CR1 &= ~TIM_CR1_CEN; if ((state->hardwareCounterActive != 0U) && (state->hardwareCounterConfigured != 0U) && (state->counterIndex < PLSR_HW_COUNTER_COUNT)) { TIM_TypeDef *counter = PlsrHwCounters[state->counterIndex]; counter->CR1 &= ~TIM_CR1_CEN; counter->SMCR &= ~(TIM_SMCR_SMS_2 | TIM_SMCR_SMS_1 | TIM_SMCR_SMS_0); } #endif } static void PlsrHwDeferAbCompletion(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; state->emittedPulses = state->targetPulses; state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abStopArmed = 0U; state->abPausePending = 0U; state->abPauseGated = 0U; state->abCompletionDeferred = 1U; state->state = PLSR_HW_STATE_DONE; } static void PlsrHwRecordFullAbGateTime(void) { #ifndef PLSR_HOST_TEST /* The wrapper records the endpoint after the common ISR accounting, just * before exception return. Measuring here would omit that equal-priority * blocking tail and could understate the 2.5us near-simultaneous window. */ PlsrHwAbGateMeasurePending = 1U; #else PlsrHwTestAbFullGateCount++; #endif } #ifndef PLSR_HOST_TEST static void PlsrHwFinishAbGateMeasurement(uint32_t started, uint8_t interruptAxis) { uint8_t finalScanRequired; uint8_t preferredAxis = (uint8_t)(interruptAxis & 0xFEU); (void)started; /* Re-scan as the final ISR operation. A second equal-priority AB pair may * reach its 00 boundary after PlsrHwOnTimerUpdate() performed its early * scans. Gating it here leaves only exception-return/tail-chain overhead * before its pending IRQ runs, rather than the bookkeeping tail of the * first pair. D1468 measures this safety-critical final scan. */ finalScanRequired = PlsrHwAbGateMeasurePending; if ((((PlsrHwAxes[0U].state == PLSR_HW_STATE_RUNNING) && (PlsrHwAxes[0U].outputMode == PLSR_OUTPUT_AB) && (PlsrHwAxes[0U].abStopArmed != 0U))) || (((PlsrHwAxes[2U].state == PLSR_HW_STATE_RUNNING) && (PlsrHwAxes[2U].outputMode == PLSR_OUTPUT_AB) && (PlsrHwAxes[2U].abStopArmed != 0U)))) { finalScanRequired = 1U; } if (finalScanRequired == 0U) { return; } if (PlsrHwGateArmedAbOutputs(preferredAxis) != 0U) { PlsrHwAbGateMeasurePending = 1U; } if (PlsrHwAbGateMeasurePending != 0U) { /* Actual gate latency is recorded at the register write that freezes * the pair. Do not extend it with checks performed after the output * is already physically safe. */ PlsrHwAbGateMeasurePending = 0U; } } #endif static uint8_t PlsrHwGateArmedAbOutputs(uint8_t preferredAxis) { uint8_t gated = 0U; uint8_t index; #ifndef PLSR_HOST_TEST uint32_t started = DWT->CYCCNT; #endif /* Both AB pairs use equal-priority IRQs. Scan and gate every pair before * doing any event publication so two simultaneous 100kHz completions * cannot make the second pair run an extra quarter while its IRQ waits. */ if ((preferredAxis != 0U) && (preferredAxis != 2U)) { preferredAxis = 0U; } for (index = 0U; index < 2U; index++) { uint8_t axis = (index == 0U) ? preferredAxis : (uint8_t)(preferredAxis ^ 2U); PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if ((state->state == PLSR_HW_STATE_RUNNING) && (state->outputMode == PLSR_OUTPUT_AB) && (state->abStopArmed != 0U) && (state->abFastGated == 0U) && (PlsrHwTimerHasCc1if(state->abCountAxis) != 0U) && (PlsrHwCounterRawSnapshot(axis) >= (uint64_t)state->targetPulses)) { /* This entry is the verified 00 boundary. Stop both counters * first, but keep CC1E driving the frozen 00 until the slower path * hands the pins to GPIO. */ PlsrHwFastGateAbPair(axis); state->abFastGated = 1U; gated = 1U; #ifndef PLSR_HOST_TEST { uint32_t elapsed = DWT->CYCCNT - started; if (elapsed > PlsrHwMaxAbGateCycles) { PlsrHwMaxAbGateCycles = elapsed; } } #endif } } return gated; } /* 输出定时器中断入口:PULSE/DIR 在 update 计数;AB 在落后相 * CC1 下降沿(四状态回到 00)计一个完整正交周期。 */ static uint8_t PlsrHwPulseDirFastPathAllowed(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_PULSE_DIR)) { return 0U; } /* An AB target boundary may arrive while another output IRQ is active. * Preserve the cross-pair scan in that short safety-critical window. */ if ( #ifndef PLSR_HOST_TEST (PlsrHwAbGateMeasurePending != 0U) || #endif ((PlsrHwAxes[0U].state == PLSR_HW_STATE_RUNNING) && (PlsrHwAxes[0U].outputMode == PLSR_OUTPUT_AB) && (PlsrHwAxes[0U].abStopArmed != 0U)) || ((PlsrHwAxes[2U].state == PLSR_HW_STATE_RUNNING) && (PlsrHwAxes[2U].outputMode == PLSR_OUTPUT_AB) && (PlsrHwAxes[2U].abStopArmed != 0U))) { return 0U; } return 1U; } static void PlsrHwAccountPulseDirUpdate(uint8_t axis) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[axis]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_PULSE_DIR)) { return; } if (state->pulseBoundaryStopPending != 0U) { #ifndef PLSR_HOST_TEST if (state->hardwareCounterActive != 0U) { state->emittedPulses = (int64_t)PlsrHwCounterSnapshot(axis); } else #endif { state->emittedPulses++; } state->pulseBoundaryStopPending = 0U; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); return; } #ifndef PLSR_HOST_TEST if (state->hardwareCounterActive != 0U) { uint64_t pulses = PlsrHwCounterSnapshot(axis); if ((state->pulseTargetStopArmed != 0U) && (pulses >= (uint64_t)state->targetPulses)) { state->emittedPulses = state->targetPulses; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } return; } #endif state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { state->emittedPulses = state->targetPulses; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } else if ((state->targetPulses > 1) && (state->emittedPulses >= (state->targetPulses - 1))) { PlsrHwArmPulseTargetTail(axis); } } static void PlsrHwHandlePulseDirUpdate(uint8_t axis) { if (PlsrHwTimerHasCc1if(axis) != 0U) { /* CC1IE is disabled for PULSE/DIR, but the compare flag itself still * latches. Consume it so it cannot survive a later mode change. */ PlsrHwTimerClearCc1if(axis); } if (PlsrHwTimerHasUif(axis) == 0U) { return; } PlsrHwTimerClearUif(axis); PlsrHwAccountPulseDirUpdate(axis); } static void PlsrHwHandlePulseDirFastGroup(uint8_t interruptAxis) { #ifdef PLSR_HOST_TEST uint8_t peerAxis; PlsrHwHandlePulseDirUpdate(interruptAxis); if (interruptAxis < 2U) { return; } peerAxis = (uint8_t)(interruptAxis ^ 1U); /* Host models the same single snapshot used by the target fast path. */ if ((PlsrHwPulseDirFastPathAllowed(peerAxis) != 0U) && (PlsrHwTimerHasUif(peerAxis) != 0U)) { PlsrHwHandlePulseDirUpdate(peerAxis); } #else if (interruptAxis < 2U) { PlsrHwHandlePulseDirUpdate(interruptAxis); return; } { uint32_t tim11Flags = TIM11->SR; uint32_t tim14Flags = TIM14->SR; /* Clear exactly the snapshotted update/compare flags in one APB write * per timer. Writing 1 to all other timer flags preserves an event * that arrives after the snapshot. */ if ((tim11Flags & (TIM_SR_UIF | TIM_SR_CC1IF)) != 0UL) { TIM11->SR = ~(tim11Flags & (TIM_SR_UIF | TIM_SR_CC1IF)); } if ((tim14Flags & (TIM_SR_UIF | TIM_SR_CC1IF)) != 0UL) { TIM14->SR = ~(tim14Flags & (TIM_SR_UIF | TIM_SR_CC1IF)); } if (((tim11Flags & TIM_SR_UIF) != 0UL) && (PlsrHwPulseDirFastPathAllowed(2U) != 0U)) { PlsrHwAccountPulseDirUpdate(2U); } if (((tim14Flags & TIM_SR_UIF) != 0UL) && (PlsrHwPulseDirFastPathAllowed(3U) != 0U)) { PlsrHwAccountPulseDirUpdate(3U); } } #endif } void PlsrHwOnTimerUpdate(uint8_t axis) { PLSR_HW_AXIS_STATE *state; uint8_t ownerAxis; uint8_t hasCc1; uint8_t abGated; if (axis >= PLSR_HW_AXIS_COUNT) { return; } if (PlsrHwPulseDirFastPathAllowed(axis) != 0U) { PlsrHwHandlePulseDirFastGroup(axis); return; } ownerAxis = (uint8_t)(axis & 0xFEU); abGated = PlsrHwGateArmedAbOutputs(ownerAxis); #ifdef PLSR_HOST_TEST /* Model the second equal-priority lag flag arriving after the first scan * but before any completion bookkeeping. */ if (PlsrHwTestLateAbFlagAxis < PLSR_HW_AXIS_COUNT) { PlsrHwTimers[PlsrHwTestLateAbFlagAxis].sr |= PLSR_HW_TIMER_CC1_BIT; PlsrHwTestLateAbFlagAxis = PLSR_HW_COUNTER_NONE; abGated = 1U; } #endif /* Completion cleanup is deferred. This second scan closes the injected * arrival window; a still-later flag gets CPU back before its next jump. */ if (abGated != 0U) { (void)PlsrHwGateArmedAbOutputs(ownerAxis); } /* CC1IF 无论当前状态如何都必须先清除;否则启动窗口中的杂散 * compare 标志会让共享 IRQ 持续重入,主线程无法完成 CEN 配置。 */ hasCc1 = PlsrHwTimerHasCc1if(axis); if (hasCc1 != 0U) { PlsrHwTimerClearCc1if(axis); } state = &PlsrHwAxes[ownerAxis]; if ((state->state == PLSR_HW_STATE_RUNNING) && (state->outputMode == PLSR_OUTPUT_AB)) { if (PlsrHwTimerHasUif(axis) != 0U) { PlsrHwTimerClearUif(axis); } if (hasCc1 == 0U) { return; } if ((state->abPausePending != 0U) || (state->abFrequencyPending != 0U)) { if (PlsrHwIsAbPhysicalZeroBoundary(ownerAxis) == 0U) { /* This was the first phase falling from 11. Leave both * one-shot interrupts armed for the later physical 00 edge. */ return; } } else if (axis != state->abCountAxis) { return; } if (state->abFastGated != 0U) { PlsrHwDeferAbCompletion(ownerAxis); PlsrHwRecordFullAbGateTime(); return; } if (state->abPausePending != 0U) { uint8_t targetReached; targetReached = (state->hardwareCounterActive != 0U) ? ((PlsrHwCounterRawSnapshot(ownerAxis) >= (uint64_t)state->targetPulses) ? 1U : 0U) : (((uint64_t)state->emittedPulses + 1UL >= (uint64_t)state->targetPulses) ? 1U : 0U); PlsrHwFastGateAbPair(ownerAxis); #ifndef PLSR_HOST_TEST /* Own the pins at physical 00 immediately. Waiting for the 1ms * deferred cleanup previously exposed a frozen active phase. */ PlsrHwHoldAbPairLowFast(ownerAxis); #endif if (targetReached != 0U) { PlsrHwDeferAbCompletion(ownerAxis); } else { if (state->hardwareCounterActive == 0U) { state->emittedPulses++; } state->abQuarter = 0U; state->abFrequencyPending = 0U; state->abPausePending = 0U; state->abPauseGated = 1U; } PlsrHwRecordFullAbGateTime(); return; } if (state->hardwareCounterActive != 0U) { /* One-shot 00 interrupt for a queued frequency change or for the * target guard. Counting itself remains entirely in TIM9/12. */ if (state->abFrequencyPending != 0U) { uint16_t basePsc = state->abPendingBasePsc; uint16_t pairPsc = state->abPendingPairPsc; uint16_t arr = state->abPendingArr; state->abFrequencyPending = 0U; PlsrHwApplyAbFrequencyAtBoundary(ownerAxis, basePsc, pairPsc, arr); } else if (state->abStopArmed == 0U) { PlsrHwTimerSetCc1ie(state->abCountAxis, 0UL); } return; } state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { PlsrHwFastGateAbPair(ownerAxis); PlsrHwDeferAbCompletion(ownerAxis); PlsrHwRecordFullAbGateTime(); } else if (state->abFrequencyPending != 0U) { uint16_t basePsc = state->abPendingBasePsc; uint16_t pairPsc = state->abPendingPairPsc; uint16_t arr = state->abPendingArr; state->abFrequencyPending = 0U; PlsrHwApplyAbFrequencyAtBoundary(ownerAxis, basePsc, pairPsc, arr); } return; } if ((state->state == PLSR_HW_STATE_RUNNING) && (state->outputMode == PLSR_OUTPUT_CW_CCW)) { uint8_t hasUif = PlsrHwTimerHasUif(axis); if (hasUif != 0U) { PlsrHwTimerClearUif(axis); } if (axis != state->cwActiveAxis) { return; } if (hasCc1 != 0U) { state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { /* The board output is inverted relative to OC1REF: CC1 is * the physical rising edge. Arm the tail here, then stop on * the following update (physical falling edge). */ state->cwStopPending = 1U; PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerClearUif(axis); PlsrHwTimerSetUie(axis, 1UL); } return; } if ((hasUif != 0U) && (state->cwStopPending != 0U)) { PlsrHwStopActiveOutput(ownerAxis, state->outputMode); state->state = PLSR_HW_STATE_DONE; (void)PlsrPostEvent(ownerAxis, PLSR_EVENT_SEGMENT_COMPLETE); } return; } state = &PlsrHwAxes[axis]; if ((hasCc1 != 0U) && !((state->state == PLSR_HW_STATE_RUNNING) && (state->outputMode == PLSR_OUTPUT_PULSE_DIR))) { /* 非运行态或其他模式的 CC1 仅作为杂散标志消费。PULSE/DIR * 的 CC1IE 关闭,但半周期比较仍会置 CC1IF;更新 IRQ 必须 * 在同一次入口继续消费 UIF,不能留下 UIF 再触发第二次 ISR。 */ return; } if (PlsrHwTimerHasUif(axis) == 0U) { return; } PlsrHwTimerClearUif(axis); if (state->state != PLSR_HW_STATE_RUNNING) { return; } if (state->outputMode != PLSR_OUTPUT_PULSE_DIR) { return; } if (state->pulseBoundaryStopPending != 0U) { #ifndef PLSR_HOST_TEST if (state->hardwareCounterActive != 0U) { state->emittedPulses = (int64_t)PlsrHwCounterSnapshot(axis); } else #endif { state->emittedPulses++; } state->pulseBoundaryStopPending = 0U; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); return; } #ifndef PLSR_HOST_TEST if (state->hardwareCounterActive != 0U) { uint64_t pulses = PlsrHwCounterSnapshot(axis); if ((state->pulseTargetStopArmed != 0U) && (pulses >= (uint64_t)state->targetPulses)) { state->emittedPulses = state->targetPulses; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } return; } #endif state->emittedPulses++; if (state->emittedPulses >= state->targetPulses) { state->emittedPulses = state->targetPulses; state->pulseTargetStopArmed = 0U; PlsrHwStopActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_DONE; PlsrHwCounterRelease(axis); (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); } else if ((state->targetPulses > 1) && (state->emittedPulses >= (state->targetPulses - 1))) { PlsrHwArmPulseTargetTail(axis); } } #ifdef PLSR_HOST_TEST uint32_t PlsrHwTestGetArr(uint8_t axis) { return PlsrHwTimers[axis].arr; } uint32_t PlsrHwTestGetCcr(uint8_t axis) { return PlsrHwTimers[axis].ccr1; } uint32_t PlsrHwTestGetCnt(uint8_t axis) { return PlsrHwTimers[axis].cnt; } uint32_t PlsrHwTestGetCcmr1(uint8_t axis) { return PlsrHwTimers[axis].ccmr1; } uint32_t PlsrHwTestGetCr1(uint8_t axis) { return PlsrHwTimers[axis].cr1; } uint32_t PlsrHwTestGetPsc(uint8_t axis) { return PlsrHwTimers[axis].psc; } uint8_t PlsrHwTestGetPwmEnabled(uint8_t axis) { return ((PlsrHwTimers[axis].ccer & PLSR_HW_TIMER_CHANNEL1_BIT) != 0UL) ? 1U : 0U; } uint8_t PlsrHwTestGetCc1PolarityInverted(uint8_t axis) { return ((PlsrHwTimers[axis].ccer & PLSR_HW_TIMER_CC1P_BIT) != 0UL) ? 1U : 0U; } uint8_t PlsrHwTestGetDirLevel(uint8_t axis) { return PlsrHwTimers[axis].dirLevel; } uint8_t PlsrHwTestGetAbPhaseA(uint8_t axis) { const PLSR_HW_AXIS_STATE *state; if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return 0U; } state = &PlsrHwAxes[axis]; if (state->directionPositive != 0U) { return ((state->abQuarter == 1U) || (state->abQuarter == 2U)) ? 1U : 0U; } return ((state->abQuarter == 2U) || (state->abQuarter == 3U)) ? 1U : 0U; } uint8_t PlsrHwTestGetAbPhaseB(uint8_t axis) { const PLSR_HW_AXIS_STATE *state; if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return 0U; } state = &PlsrHwAxes[axis]; if (state->directionPositive != 0U) { return ((state->abQuarter == 2U) || (state->abQuarter == 3U)) ? 1U : 0U; } return ((state->abQuarter == 1U) || (state->abQuarter == 2U)) ? 1U : 0U; } uint8_t PlsrHwTestGetAbQuarter(uint8_t axis) { if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return 0U; } return PlsrHwAxes[axis].abQuarter; } void PlsrHwTestSetAbQuarterWithoutCounter(uint8_t axis, uint8_t quarter) { if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return; } PlsrHwAxes[axis].abQuarter = (uint8_t)(quarter % PLSR_HW_AB_QUARTER_COUNT); } uint32_t PlsrHwTestGetAbFullGateCount(void) { return PlsrHwTestAbFullGateCount; } void PlsrHwTestAdvanceAbQuarter(uint8_t axis) { PLSR_HW_AXIS_STATE *state; uint8_t countAxis; uint8_t leadAxis; uint8_t sourceQuarter; if ((axis >= PLSR_HW_AXIS_COUNT) || (PlsrHwIsAbBaseAxis(axis) == 0U)) { return; } state = &PlsrHwAxes[axis]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_AB)) { return; } countAxis = state->abCountAxis; leadAxis = (state->directionPositive != 0U) ? axis : PlsrHwGetPairedAxis(axis); sourceQuarter = (state->counterSourceAxis == leadAxis) ? 1U : 2U; state->abQuarter = (uint8_t)((state->abQuarter + 1U) % PLSR_HW_AB_QUARTER_COUNT); if ((state->hardwareCounterActive != 0U) && (state->abQuarter == sourceQuarter)) { state->counterBlockPulses++; if ((state->abStopArmed == 0U) && (state->counterBlockPulses >= (uint64_t)(state->targetPulses - 1))) { /* Host model of the target-1 TIM9/TIM12 compare. */ state->abStopArmed = 1U; PlsrHwTimerClearCc1if(countAxis); PlsrHwTimerSetCc1ie(countAxis, 1UL); } } if ((state->abQuarter == 3U) && ((PlsrHwTimers[leadAxis].dier & PLSR_HW_TIMER_CC1_BIT) != 0UL)) { /* First falling edge after an asynchronous boundary request: AB is * not 00 yet, so production must observe it without applying work. */ PlsrHwTimers[leadAxis].sr |= PLSR_HW_TIMER_CC1_BIT; PlsrHwOnTimerUpdate(leadAxis); } if (state->abQuarter == 0U) { /* 模拟目标板落后相 CC1 下降沿中断,复用生产计数路径。 */ /* Model the lag CC IRQ only while it is enabled. This detects a * regression that accidentally restores one interrupt per AB cycle. */ if ((PlsrHwTimers[countAxis].dier & PLSR_HW_TIMER_CC1_BIT) != 0UL) { PlsrHwTimers[countAxis].sr |= PLSR_HW_TIMER_CC1_BIT; PlsrHwOnTimerUpdate(countAxis); } } } void PlsrHwTestSignalDualAbFinalBoundary(uint8_t firstAxis) { static const uint8_t baseAxes[2] = {0U, 2U}; uint8_t index; uint8_t firstCountAxis; if ((firstAxis != 0U) && (firstAxis != 2U)) { return; } for (index = 0U; index < 2U; index++) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[baseAxes[index]]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_AB) || (state->hardwareCounterActive == 0U)) { return; } } for (index = 0U; index < 2U; index++) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[baseAxes[index]]; state->counterBlockPulses = (uint64_t)state->targetPulses; state->abQuarter = 0U; state->abStopArmed = 1U; PlsrHwTimerSetCc1ie(state->abCountAxis, 1UL); PlsrHwTimers[state->abCountAxis].sr |= PLSR_HW_TIMER_CC1_BIT; } firstCountAxis = PlsrHwAxes[firstAxis].abCountAxis; PlsrHwOnTimerUpdate(firstCountAxis); } void PlsrHwTestSignalDualAbStaggeredFinalBoundary(uint8_t firstAxis) { static const uint8_t baseAxes[2] = {0U, 2U}; uint8_t firstCountAxis; uint8_t secondAxis; uint8_t secondCountAxis; uint8_t index; if ((firstAxis != 0U) && (firstAxis != 2U)) { return; } secondAxis = (firstAxis == 0U) ? 2U : 0U; for (index = 0U; index < 2U; index++) { PLSR_HW_AXIS_STATE *state = &PlsrHwAxes[baseAxes[index]]; if ((state->state != PLSR_HW_STATE_RUNNING) || (state->outputMode != PLSR_OUTPUT_AB) || (state->hardwareCounterActive == 0U)) { return; } state->counterBlockPulses = (uint64_t)state->targetPulses; state->abQuarter = 0U; state->abStopArmed = 1U; PlsrHwTimerSetCc1ie(state->abCountAxis, 1UL); } firstCountAxis = PlsrHwAxes[firstAxis].abCountAxis; secondCountAxis = PlsrHwAxes[secondAxis].abCountAxis; PlsrHwTimers[firstCountAxis].sr |= PLSR_HW_TIMER_CC1_BIT; PlsrHwTestLateAbFlagAxis = secondCountAxis; PlsrHwOnTimerUpdate(firstCountAxis); } void PlsrHwTestTriggerUpdate(uint8_t axis) { if (axis < PLSR_HW_AXIS_COUNT) { PlsrHwTimers[axis].cnt = 0UL; PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_UPDATE_BIT; } PlsrHwOnTimerUpdate(axis); } void PlsrHwTestSetUpdatePending(uint8_t axis) { if (axis < PLSR_HW_AXIS_COUNT) { PlsrHwTimers[axis].cnt = 0UL; PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_UPDATE_BIT; } } void PlsrHwTestTriggerUpdateAndCompare(uint8_t axis) { if (axis < PLSR_HW_AXIS_COUNT) { PlsrHwTimers[axis].cnt = PlsrHwTimers[axis].ccr1; PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_UPDATE_BIT | PLSR_HW_TIMER_CC1_BIT; } PlsrHwOnTimerUpdate(axis); } void PlsrHwTestTriggerCompare(uint8_t axis) { if (axis < PLSR_HW_AXIS_COUNT) { PlsrHwTimers[axis].cnt = PlsrHwTimers[axis].ccr1; PlsrHwTimers[axis].sr |= PLSR_HW_TIMER_CC1_BIT; } PlsrHwOnTimerUpdate(axis); } #endif #ifndef PLSR_HOST_TEST static void PlsrHwOnCounterInterrupt(uint8_t counterIndex) { TIM_TypeDef *counter; PLSR_HW_AXIS_STATE *state; uint32_t flags; uint64_t pulses; uint8_t axis; if (counterIndex >= PLSR_HW_COUNTER_COUNT) { return; } counter = PlsrHwCounters[counterIndex]; flags = counter->SR & (TIM_SR_UIF | TIM_SR_CC1IF); counter->SR = ~(TIM_SR_UIF | TIM_SR_CC1IF); axis = PlsrHwCounterOwners[counterIndex]; if ((flags == 0UL) || (axis >= PLSR_HW_AXIS_COUNT)) { return; } state = &PlsrHwAxes[axis]; if ((state->hardwareCounterActive == 0U) || (state->counterIndex != counterIndex) || (state->state != PLSR_HW_STATE_RUNNING)) { return; } if ((flags & TIM_SR_UIF) != 0UL) { state->counterBlockPulses += PLSR_HW_COUNTER_BLOCK_PULSES; } pulses = state->counterBlockPulses + (uint16_t)counter->CNT; if (state->outputMode == PLSR_OUTPUT_AB) { uint64_t guard = (uint64_t)state->targetPulses - 1UL; if (((flags & TIM_SR_CC1IF) != 0UL) && (pulses >= guard)) { /* Wake the lag-CC1 one cycle early. It remains enabled until a * 00 boundary observes raw>=target and fast-gates both phases. */ state->abStopArmed = 1U; PlsrHwTimerClearCc1if(state->abCountAxis); PlsrHwTimerSetCc1ie(state->abCountAxis, 1UL); } return; } if (((flags & TIM_SR_CC1IF) != 0UL) && (pulses >= ((uint64_t)state->targetPulses - 1UL))) { /* Do not gate on the counter source edge: it starts the terminal high * level. Pre-arm the output compare so the target high completes. */ PlsrHwArmPulseTargetTail(axis); } } void TIM1_UP_TIM10_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnTimerUpdate(0U); PlsrHwRecordMaxCycles(&PlsrHwMaxOutputIsrCycles, started); PlsrHwFinishAbGateMeasurement(started, 0U); } void TIM8_UP_TIM13_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnTimerUpdate(1U); PlsrHwRecordMaxCycles(&PlsrHwMaxOutputIsrCycles, started); PlsrHwFinishAbGateMeasurement(started, 1U); } void TIM1_TRG_COM_TIM11_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnTimerUpdate(2U); PlsrHwRecordMaxCycles(&PlsrHwMaxOutputIsrCycles, started); PlsrHwFinishAbGateMeasurement(started, 2U); } void TIM8_TRG_COM_TIM14_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnTimerUpdate(3U); PlsrHwRecordMaxCycles(&PlsrHwMaxOutputIsrCycles, started); PlsrHwFinishAbGateMeasurement(started, 3U); } void TIM1_BRK_TIM9_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnCounterInterrupt(0U); PlsrHwRecordMaxCycles(&PlsrHwMaxCounterIsrCycles, started); } void TIM8_BRK_TIM12_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); PlsrHwOnCounterInterrupt(1U); PlsrHwRecordMaxCycles(&PlsrHwMaxCounterIsrCycles, started); } void TIM6_DAC_IRQHandler(void) { uint32_t started = PlsrHwCycleBegin(); if ((TIM6->SR & TIM_SR_UIF) != 0UL) { TIM6->SR &= ~TIM_SR_UIF; PlsrControlTick100us(); } PlsrHwRecordMaxCycles(&PlsrHwMaxControlIsrCycles, started); } #endif