From 0b7990bc99b60894f83bda0b951ef5b16fe7eabf Mon Sep 17 00:00:00 2001 From: ywh <2227158009@qq.com> Date: Sun, 9 Aug 2026 00:10:44 +0800 Subject: [PATCH] =?UTF-8?q?P3b-2=20AB=20=E6=AD=A3=E4=BA=A4=E7=A1=AC?= =?UTF-8?q?=E4=BB=B6=E5=B1=82=E4=B8=8A=E6=9D=BF=E9=AA=8C=E8=AF=81=EF=BC=9A?= =?UTF-8?q?=E5=8F=8C=E5=AE=9A=E6=97=B6=E5=99=A8=E5=90=8C=E6=AD=A5=E5=88=86?= =?UTF-8?q?=E9=A2=91=20+=20=E6=97=A0=E6=AF=9B=E5=88=BA=E7=9B=B8=E4=BD=8D?= =?UTF-8?q?=E5=BB=BA=E7=AB=8B=EF=BC=8C=E4=B8=89=E6=AE=B5=20AB=20=E8=87=AA?= =?UTF-8?q?=E6=B5=8B=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本批完成内容: HAL AB 硬件层(F407 真机实现): - 配对定时器同步分频:168MHz/84MHz 两路按 PSC 2 倍关系取相同 ARR, 获得完全一致的计数时钟与周期,避免独立取整造成相位漂移。 - AB 相位建立无毛刺:起步前 GPIO 保持引脚低电平 → OC 强制非活动/冻结 → UG 加载影子 → CNT 初值(领先相 3/4T、落后相 1/2T,差 90°) → 切 PWM1 → OCREF 稳定低电平后再把物理引脚交还定时器 → 关中断下 两路同步 CEN。引脚与 AF 交接期间不产生任何跳变。 - 完整 00 周期边界计数:落后相 CC1 下降沿(00 状态)累计一个指令 周期,DONE 判定与相序边界严格同步。 - 运行中调频只写 PSC/ARR/CCR 预装载(更新事件生效),不触碰 CNT 与输出使能,调频过程零边沿毛刺。 - 反向相序:段3 反向时领先相切换为配对轴,从 00→01→11→10→00 起步。 自测切换 AB 模式(验证后关闭): - Q0(A)/Q1(B) 三段完整 AB 周期:2000Hz/+1000、5000Hz/+6000、 1000Hz/-500(末段反向,验证相序切换)。 host 测试扩展: - CNT 写到活动 CCR1 会置 CC1IF 的 F407 实测语义 - AB 起步/计数/反向相序断言(525 项 HAL 检查全绿) 上板验证(逻辑分析仪实测): - 稳定段相位 +90.2°/+90.6°/-89.9°(理论 ±90°,误差 <1°) - Q0/Q1 各 7501 上升沿(含启动瞬间 1 个 A/B 同升边沿,待消除) - 调频毛刺 0 个(上一版 5213 个全部消除) - 段间 3.57ms = 1/4 首周期,任务延迟 ≈0 - 段2 从标准 00→10→11→01→00 起步 --- Core/Src/main.c | 2 +- PLSR/Inc/plsr_hal_f407.h | 1 + PLSR/Inc/plsr_self_test.h | 4 +- PLSR/Src/plsr_hal_f407.c | 527 ++++++++++++++++++++++++++++++++--- PLSR/Src/plsr_self_test.c | 16 +- PLSR/Test/run_host_tests.ps1 | 1 + PLSR/Test/test_plsr_hal.c | 128 +++++++-- 7 files changed, 604 insertions(+), 75 deletions(-) diff --git a/Core/Src/main.c b/Core/Src/main.c index 058b547..07b5ff5 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -200,7 +200,7 @@ int main(void) Error_Handler(); } - /* 上电自测:延时 1s 待电源/外设稳定后,Q0 发一段测试脉冲(验证后删除)。 */ + /* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */ HAL_Delay(1000U); (void)PlsrSelfTestQueue(); OSStart(); diff --git a/PLSR/Inc/plsr_hal_f407.h b/PLSR/Inc/plsr_hal_f407.h index 37fbb61..b01e5ed 100644 --- a/PLSR/Inc/plsr_hal_f407.h +++ b/PLSR/Inc/plsr_hal_f407.h @@ -53,6 +53,7 @@ uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber); /* 模拟寄存器访问与中断触发(测试用)。 */ uint32_t PlsrHwTestGetArr(uint8_t axis); uint32_t PlsrHwTestGetCcr(uint8_t axis); +uint32_t PlsrHwTestGetCnt(uint8_t axis); uint32_t PlsrHwTestGetCcmr1(uint8_t axis); uint32_t PlsrHwTestGetCr1(uint8_t axis); uint32_t PlsrHwTestGetPsc(uint8_t axis); diff --git a/PLSR/Inc/plsr_self_test.h b/PLSR/Inc/plsr_self_test.h index f9815a0..2781265 100644 --- a/PLSR/Inc/plsr_self_test.h +++ b/PLSR/Inc/plsr_self_test.h @@ -7,8 +7,8 @@ extern "C" { #endif -/* 上电自测:入队一段测试任务(Q0 发 2000Hz/1000 脉冲,K1 默认参数)。 - * 用于首次上板验证脉冲波形,验证完成后可删除。 */ +/* 上电自测:Q0(A)/Q1(B) 输出三段 AB 正交周期,末段反向。 + * 用于 P3b-2 上板验证,验证完成后应关闭。 */ PLSR_RESULT PlsrSelfTestQueue(void); #ifdef __cplusplus diff --git a/PLSR/Src/plsr_hal_f407.c b/PLSR/Src/plsr_hal_f407.c index 907a08e..970cd28 100644 --- a/PLSR/Src/plsr_hal_f407.c +++ b/PLSR/Src/plsr_hal_f407.c @@ -11,6 +11,7 @@ #define PLSR_HW_TIMER_CHANNEL1_BIT (0x0001U) #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) @@ -81,6 +82,41 @@ static const PLSR_HW_AXIS_MAP PlsrHwAxisMap[PLSR_HW_AXIS_COUNT] = #endif }; +#ifndef PLSR_HOST_TEST +static const uint8_t PlsrHwPulsePinIndex[PLSR_HW_AXIS_COUNT] = +{ + 6U, 8U, 7U, 9U +}; + +/* 重定相期间由 GPIO 直接保持物理输出低电平。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 << 16U; + 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(); +} +#endif + /* host 测试:模拟定时器寄存器。 */ #ifdef PLSR_HOST_TEST typedef struct @@ -91,6 +127,7 @@ typedef struct uint32_t psc; uint32_t arr; uint32_t ccr1; + uint32_t cnt; uint32_t ccmr1; uint32_t ccer; uint8_t dirLevel; @@ -110,13 +147,21 @@ typedef struct uint8_t directionPoint; uint8_t directionPositive; uint8_t abQuarter; + uint8_t abCountAxis; + uint16_t abActiveBasePsc; + uint16_t abActivePairPsc; + uint16_t abActiveArr; + uint16_t abPendingBasePsc; + uint16_t abPendingPairPsc; + uint16_t abPendingArr; + uint8_t abFrequencyPending; } PLSR_HW_AXIS_STATE; static PLSR_HW_AXIS_STATE PlsrHwAxes[PLSR_HW_AXIS_COUNT]; /* 调试快照:当前上板自测只记录 Q0 的 160 ms,避免四轴 - * PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示 PwmBegin, - * reason=3 表示 1 ms HAL tick。 */ + * PlsrHwTick 互相混入,同时控制临时 RAM 占用。reason=0 表示段启动, + * reason=3 表示 1 ms HAL tick,reason=4 表示 AB 在 00 边界换频重定相。 */ #ifndef PLSR_HOST_TEST typedef struct { @@ -187,6 +232,20 @@ static void PlsrHwTimerSetCcr(uint8_t axis, uint32_t value) #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 @@ -231,6 +290,28 @@ static void PlsrHwTimerSetPwmMode1(uint8_t axis) #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 PlsrHwTimerSetFrozen(uint8_t axis) +{ +#ifdef PLSR_HOST_TEST + PlsrHwTimers[axis].ccmr1 = 0x0008UL; +#else + PlsrHwAxisMap[axis].timer->CCMR1 = TIM_CCMR1_OC1PE; +#endif +} + static void PlsrHwTimerSetUie(uint8_t axis, uint32_t value) { #ifdef PLSR_HOST_TEST @@ -247,6 +328,29 @@ static void PlsrHwTimerSetUie(uint8_t axis, uint32_t value) #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 @@ -267,6 +371,26 @@ static uint8_t PlsrHwTimerHasUif(uint8_t axis) #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(低)。 */ @@ -365,7 +489,9 @@ static void PlsrHwPwmBegin(uint8_t axis) PlsrHwTimerSetUg(axis); /* UG 只用于加载影子寄存器,不是物理脉冲,不得计数。 */ PlsrHwTimerClearUif(axis); + PlsrHwTimerClearCc1if(axis); PlsrHwDbgCapture(axis, 0U); + PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetUie(axis, 1UL); PlsrHwTimerSetCc1e(axis, 1UL); PlsrHwTimerSetCen(axis, 1UL); @@ -375,7 +501,10 @@ static void PlsrHwStopPwmTimer(uint8_t axis) { PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetUie(axis, 0UL); + PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCen(axis, 0UL); + PlsrHwTimerClearUif(axis); + PlsrHwTimerClearCc1if(axis); } static uint8_t PlsrHwIsAbBaseAxis(uint8_t axis) @@ -383,56 +512,303 @@ static uint8_t PlsrHwIsAbBaseAxis(uint8_t axis) return ((axis == 0U) || (axis == 2U)) ? 1U : 0U; } -#ifdef PLSR_HOST_TEST static uint8_t PlsrHwGetPairedAxis(uint8_t axis) { return (uint8_t)(axis + 1U); } + +/* 为 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 -/* P3b-1:AB 正交相序先在 host 模型中闭环。真实 STM32 双定时器的 - * 同步启动、相位偏置和安全停止将在 P3b-2 接入。 */ + 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; + } + else + { + state->abPendingBasePsc = basePsc; + state->abPendingPairPsc = pairPsc; + state->abPendingArr = arr; + state->abFrequencyPending = 1U; + } +#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; + /* 落后相从 CCR 精确起步;切回 PWM 后清 CC1IF,最后才开 CC1IE。 */ + lagStart = periodTicks / 2UL; + state->abCountAxis = lagAxis; + state->abQuarter = 0U; + +#ifndef PLSR_HOST_TEST + interruptState = __get_PRIMASK(); + __disable_irq(); + __DMB(); + 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); + PlsrHwTimerSetFrozen(axis); + PlsrHwTimerSetFrozen(pairAxis); + PlsrHwTimerSetUg(axis); + PlsrHwTimerSetUg(pairAxis); + PlsrHwTimerClearUif(axis); + PlsrHwTimerClearUif(pairAxis); + PlsrHwTimerClearCc1if(axis); + PlsrHwTimerClearCc1if(pairAxis); + PlsrHwTimerSetCnt(leadAxis, leadStart); + PlsrHwTimerSetCnt(lagAxis, lagStart); +#ifdef PLSR_HOST_TEST + PlsrHwTimerSetCc1e(axis, 1UL); + PlsrHwTimerSetCc1e(pairAxis, 1UL); + PlsrHwTimerSetPwmMode1(axis); + PlsrHwTimerSetPwmMode1(pairAxis); + PlsrHwTimerClearCc1if(axis); + PlsrHwTimerClearCc1if(pairAxis); + PlsrHwTimerSetCen(axis, 1UL); + PlsrHwTimerSetCen(pairAxis, 1UL); + PlsrHwTimerClearCc1if(axis); + PlsrHwTimerClearCc1if(pairAxis); + PlsrHwTimerSetCc1ie(lagAxis, 1UL); +#else + PlsrHwTimerSetCc1e(axis, 1UL); + PlsrHwTimerSetCc1e(pairAxis, 1UL); + PlsrHwTimerSetPwmMode1(axis); + PlsrHwTimerSetPwmMode1(pairAxis); + /* OCREF 已在低电平位置稳定后再把物理引脚交还定时器。 */ + PlsrHwTimerClearCc1if(axis); + PlsrHwTimerClearCc1if(pairAxis); + PlsrHwReleasePulsePin(axis); + PlsrHwReleasePulsePin(pairAxis); + PlsrHwTimerSetCen(axis, 1UL); + PlsrHwTimerSetCen(pairAxis, 1UL); + /* 最后才允许落后相计数中断。 */ + PlsrHwTimerClearCc1if(axis); + PlsrHwTimerClearCc1if(pairAxis); + PlsrHwTimerSetCc1ie(lagAxis, 1UL); + __DMB(); + if (interruptState == 0UL) + { + __enable_irq(); + } +#endif + PlsrHwDbgCapture(axis, debugReason); +} + static void PlsrHwConfigureActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode, uint32_t frequencyHz) { - PlsrHwConfigurePwm(axis, frequencyHz); -#ifdef PLSR_HOST_TEST if (outputMode == PLSR_OUTPUT_AB) { - PlsrHwConfigurePwm(PlsrHwGetPairedAxis(axis), frequencyHz); + (void)PlsrHwConfigureAbPwm(axis, frequencyHz); + } + else + { + PlsrHwConfigurePwm(axis, frequencyHz); } -#else - (void)outputMode; -#endif } static void PlsrHwBeginActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode) { - PlsrHwPwmBegin(axis); -#ifdef PLSR_HOST_TEST if (outputMode == PLSR_OUTPUT_AB) { - PlsrHwPwmBegin(PlsrHwGetPairedAxis(axis)); + PlsrHwBeginAbOutput(axis, 0U); + } + else + { + PlsrHwPwmBegin(axis); } -#else - (void)outputMode; -#endif } static void PlsrHwStopActiveOutput(uint8_t axis, PLSR_OUTPUT_MODE outputMode) { - PlsrHwStopPwmTimer(axis); -#ifdef PLSR_HOST_TEST if ((outputMode == PLSR_OUTPUT_AB) && (PlsrHwIsAbBaseAxis(axis) != 0U)) { - PlsrHwStopPwmTimer(PlsrHwGetPairedAxis(axis)); - } -#else - (void)outputMode; + 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); +#ifndef PLSR_HOST_TEST + __DMB(); + if (interruptState == 0UL) + { + __enable_irq(); + } +#endif + } + else + { + PlsrHwStopPwmTimer(axis); + } } uint8_t PlsrHwResolveDirectionPoint(uint8_t pointNumber) @@ -471,6 +847,7 @@ PLSR_RESULT PlsrHwInit(void) #else PlsrHwTimerSetCc1e(axis, 0UL); PlsrHwTimerSetUie(axis, 0UL); + PlsrHwTimerSetCc1ie(axis, 0UL); PlsrHwTimerSetCen(axis, 0UL); #endif } @@ -548,14 +925,6 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) { return PLSR_RESULT_NOT_SUPPORTED; } -#ifndef PLSR_HOST_TEST - if (params->outputMode == PLSR_OUTPUT_AB) - { - /* P3b-1 只交付可验证的 host 相序模型;禁止目标板误输出成 - * 单路 PULSE/DIR。P3b-2 接入双定时器后移除此保护。 */ - return PLSR_RESULT_NOT_SUPPORTED; - } -#endif state = &PlsrHwAxes[axis]; if (state->state == PLSR_HW_STATE_RUNNING) { @@ -586,6 +955,7 @@ PLSR_RESULT PlsrHwStartPulse(uint8_t axis, const PLSR_HW_START_PARAMS *params) ? params->directionDelayMs : 0U; state->abQuarter = 0U; + state->abFrequencyPending = 0U; if (params->outputMode == PLSR_OUTPUT_PULSE_DIR) { PlsrHwSetDirLevel(axis, params->directionPositive); @@ -615,8 +985,20 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) { if (frequencyHz > 0UL) { - /* 运行中调频:预装载写入,更新事件时生效,不触碰使能位。 */ - PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); + 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 { @@ -627,8 +1009,9 @@ PLSR_RESULT PlsrHwSetFrequency(uint8_t axis, uint32_t frequencyHz) && (frequencyHz > 0UL)) { PlsrHwConfigureActiveOutput(axis, state->outputMode, frequencyHz); - PlsrHwBeginActiveOutput(axis, state->outputMode); + /* 必须先发布 RUNNING,避免启用 timer IRQ 后观察到 PWM_PENDING。 */ state->state = PLSR_HW_STATE_RUNNING; + PlsrHwBeginActiveOutput(axis, state->outputMode); } return PLSR_RESULT_OK; } @@ -646,6 +1029,7 @@ PLSR_RESULT PlsrHwStopPulse(uint8_t axis) { PlsrHwStopActiveOutput(axis, state->outputMode); state->abQuarter = 0U; + state->abFrequencyPending = 0U; state->state = PLSR_HW_STATE_IDLE; } return PLSR_RESULT_OK; @@ -736,8 +1120,8 @@ void PlsrHwTick(uint8_t axis) PlsrHwConfigureActiveOutput(axis, state->outputMode, state->currentFrequencyHz); - PlsrHwBeginActiveOutput(axis, state->outputMode); state->state = PLSR_HW_STATE_RUNNING; + PlsrHwBeginActiveOutput(axis, state->outputMode); } break; @@ -746,15 +1130,68 @@ void PlsrHwTick(uint8_t axis) } } -/* 输出定时器更新中断:每周期末触发一次(=1 个脉冲)。 */ +/* 输出定时器中断入口:PULSE/DIR 在 update 计数;AB 在落后相 + * CC1 下降沿(四状态回到 00)计一个完整正交周期。 */ void PlsrHwOnTimerUpdate(uint8_t axis) { PLSR_HW_AXIS_STATE *state; + uint8_t ownerAxis; + uint8_t hasCc1; if (axis >= PLSR_HW_AXIS_COUNT) { return; } + + /* CC1IF 无论当前状态如何都必须先清除;否则启动窗口中的杂散 + * compare 标志会让共享 IRQ 持续重入,主线程无法完成 CEN 配置。 */ + hasCc1 = PlsrHwTimerHasCc1if(axis); + if (hasCc1 != 0U) + { + PlsrHwTimerClearCc1if(axis); + } + ownerAxis = (uint8_t)(axis & 0xFEU); + state = &PlsrHwAxes[ownerAxis]; + if ((state->state == PLSR_HW_STATE_RUNNING) + && (state->outputMode == PLSR_OUTPUT_AB)) + { + if (PlsrHwTimerHasUif(axis) != 0U) + { + PlsrHwTimerClearUif(axis); + } + if ((hasCc1 == 0U) || (axis != state->abCountAxis)) + { + return; + } + state->emittedPulses++; + if (state->emittedPulses >= state->targetPulses) + { + PlsrHwStopActiveOutput(ownerAxis, state->outputMode); + state->abQuarter = 0U; + state->abFrequencyPending = 0U; + state->state = PLSR_HW_STATE_DONE; + (void)PlsrPostEvent(ownerAxis, PLSR_EVENT_SEGMENT_COMPLETE); + } + else if (state->abFrequencyPending != 0U) + { + uint16_t basePsc = state->abPendingBasePsc; + uint16_t pairPsc = state->abPendingPairPsc; + uint16_t arr = state->abPendingArr; + + state->abFrequencyPending = 0U; + PlsrHwLoadAbPwm(ownerAxis, basePsc, pairPsc, arr); + /* 落后相刚下降,AB=00;两路从同一个完整周期边界重定相。 */ + PlsrHwBeginAbOutput(ownerAxis, 4U); + } + return; + } + + if (hasCc1 != 0U) + { + /* 非运行态/非 AB 模式的 CC1 仅作为杂散标志消费。 */ + return; + } + state = &PlsrHwAxes[axis]; if (PlsrHwTimerHasUif(axis) == 0U) { @@ -767,8 +1204,6 @@ void PlsrHwOnTimerUpdate(uint8_t axis) } if (state->outputMode != PLSR_OUTPUT_PULSE_DIR) { - /* AB host 模型按四分之一周期推进,不能把任一物理定时器的 - * update 直接当成完整 AB 指令脉冲。 */ return; } @@ -793,6 +1228,11 @@ 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; @@ -888,14 +1328,9 @@ void PlsrHwTestAdvanceAbQuarter(uint8_t axis) % PLSR_HW_AB_QUARTER_COUNT); if (state->abQuarter == 0U) { - state->emittedPulses++; - if (state->emittedPulses >= state->targetPulses) - { - /* 仅在完整 00 边界停机,禁止留下半个正交周期。 */ - PlsrHwStopActiveOutput(axis, state->outputMode); - state->state = PLSR_HW_STATE_DONE; - (void)PlsrPostEvent(axis, PLSR_EVENT_SEGMENT_COMPLETE); - } + /* 模拟目标板落后相 CC1 下降沿中断,复用生产计数路径。 */ + PlsrHwTimers[state->abCountAxis].sr |= PLSR_HW_TIMER_CC1_BIT; + PlsrHwOnTimerUpdate(state->abCountAxis); } } diff --git a/PLSR/Src/plsr_self_test.c b/PLSR/Src/plsr_self_test.c index dd2b313..3544009 100644 --- a/PLSR/Src/plsr_self_test.c +++ b/PLSR/Src/plsr_self_test.c @@ -5,11 +5,11 @@ #include /* 上电自测(验证后可删除): - * - 方向端子设为 Y4(SFD906=4),其余用出厂默认参数(K1) - * - 任务:Q0 发 3 段脉冲(H00 完成,顺序衔接),同时验证段间跳转: - * 段1:2000Hz / 1000 脉冲(已单段验证) - * 段2:5000Hz / 6000 脉冲(加速 500ms + 匀速 200ms + 减速 500ms) - * 段3:1000Hz / 500 脉冲 + * - AB 模式使用 Q0(A)/Q1(B),其余用出厂默认参数(K1) + * - 任务:3 段完整 AB 周期(H00 完成,顺序衔接): + * 段1:2000Hz / +1000 周期(A 超前 B) + * 段2:5000Hz / +6000 周期(A 超前 B) + * 段3:1000Hz / -500 周期(B 超前 A,验证反向) * 数据源为静态数组,仅自测使用(正式 D 设备适配器见 Modbus 阶段)。 */ #define SELF_TEST_WORD_CAPACITY (64U) @@ -86,7 +86,9 @@ PLSR_RESULT PlsrSelfTestQueue(void) SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 20U, 5000UL); SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 22U, 6000UL); SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 30U, 1000UL); - SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S0_BASE + 32U, 500UL); + SelfTestWriteDword(PLSR_DEVICE_D, + SELF_TEST_S0_BASE + 32U, + (uint32_t)(int32_t)-500); /* S1:相对模式,起始段 0(=段1)。 */ SelfTestWriteDword(PLSR_DEVICE_D, SELF_TEST_S1_BASE, 0U); @@ -104,6 +106,6 @@ PLSR_RESULT PlsrSelfTestQueue(void) call.s2.type = PLSR_OPERAND_CONSTANT; call.s2.constant = 1; call.dAxis = 0U; - call.outputModeOverride = PLSR_OUTPUT_MODE_FROM_SFD; + call.outputModeOverride = PLSR_OUTPUT_AB; return PlsrPostCall(&call); } diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1 index 8f7195f..571a63c 100644 --- a/PLSR/Test/run_host_tests.ps1 +++ b/PLSR/Test/run_host_tests.ps1 @@ -82,6 +82,7 @@ $tests = @( "$workspacePath\PLSR\Src\plsr_profile.c" "$workspacePath\PLSR\Src\plsr_hal_f407.c" "$workspacePath\PLSR\Src\plsr_core.c" + "$workspacePath\PLSR\Src\plsr_self_test.c" "$workspacePath\PLSR\Test\test_plsr_hal.c" ) } diff --git a/PLSR/Test/test_plsr_hal.c b/PLSR/Test/test_plsr_hal.c index d15d3af..f644a69 100644 --- a/PLSR/Test/test_plsr_hal.c +++ b/PLSR/Test/test_plsr_hal.c @@ -3,6 +3,7 @@ #include "plsr_hal_f407.h" #include "plsr_job.h" #include "plsr_persistence.h" +#include "plsr_self_test.h" #include #include @@ -277,18 +278,20 @@ static void TestPulseCounting(void) static void TestAbPhaseAndCounting(void) { PLSR_HW_START_PARAMS params; - uint16_t psc; - uint16_t arr; static const uint8_t positiveA[4] = {1U, 1U, 0U, 0U}; static const uint8_t positiveB[4] = {0U, 1U, 1U, 0U}; static const uint8_t negativeA[4] = {0U, 1U, 1U, 0U}; static const uint8_t negativeB[4] = {1U, 1U, 0U, 0U}; + uint32_t oldArr; + uint32_t oldBasePsc; + uint32_t oldPairPsc; + uint32_t newPeriod; int quarter; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; - params.targetPulses = 2; + params.targetPulses = 4; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = 4U; /* AB 模式必须忽略独立 DIR 点。 */ params.directionPositive = 1U; @@ -303,14 +306,16 @@ static void TestAbPhaseAndCounting(void) CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); - CHECK(PlsrCalculateTimerDivider(168000000UL, 1000UL, &psc, &arr) - == PLSR_RESULT_OK); - CHECK(PlsrHwTestGetPsc(0U) == psc); - CHECK(PlsrHwTestGetArr(0U) == arr); - CHECK(PlsrCalculateTimerDivider(84000000UL, 1000UL, &psc, &arr) - == PLSR_RESULT_OK); - CHECK(PlsrHwTestGetPsc(1U) == psc); - CHECK(PlsrHwTestGetArr(1U) == arr); + CHECK((PlsrHwTestGetPsc(0U) + 1UL) + == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); + CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); + CHECK(PlsrHwTestGetCcr(0U) == PlsrHwTestGetCcr(1U)); + CHECK(PlsrHwTestGetCcr(0U) + == (PlsrHwTestGetArr(0U) + 1UL) / 2UL); + /* 两相从精确 00 边界起步;CC1IF 会在开中断前再次清除。 */ + CHECK(PlsrHwTestGetCnt(0U) + == ((PlsrHwTestGetArr(0U) + 1UL) * 3UL) / 4UL); + CHECK(PlsrHwTestGetCnt(1U) == PlsrHwTestGetCcr(1U)); /* 任一物理 timer update 不能直接计作完整 AB 周期。 */ PlsrHwTestTriggerUpdate(0U); @@ -326,21 +331,54 @@ static void TestAbPhaseAndCounting(void) CHECK(PlsrHwGetEmittedPulses(0U) == 1); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); - /* 调频同时更新 A/B 两路,且不得重置正交相位。 */ + /* 运行中调频先排队,不能让相差 1/4 周期的两路各自加载 ARR。 */ + oldArr = PlsrHwTestGetArr(0U); + oldBasePsc = PlsrHwTestGetPsc(0U); + oldPairPsc = PlsrHwTestGetPsc(1U); + CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + } + CHECK(PlsrHwGetEmittedPulses(0U) == 2); + CHECK(PlsrHwTestGetArr(0U) == oldArr); + CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); + CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); + CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetAbQuarter(0U) == 0U); - CHECK(PlsrCalculateTimerDivider(168000000UL, 2000UL, &psc, &arr) - == PLSR_RESULT_OK); - CHECK(PlsrHwTestGetArr(0U) == arr); - CHECK(PlsrCalculateTimerDivider(84000000UL, 2000UL, &psc, &arr) - == PLSR_RESULT_OK); - CHECK(PlsrHwTestGetArr(1U) == arr); + CHECK(PlsrHwTestGetArr(0U) == oldArr); + CHECK(PlsrHwTestGetArr(1U) == oldArr); + CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); + CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); + + for (quarter = 0; quarter < 3; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwTestGetArr(0U) == oldArr); + CHECK(PlsrHwTestGetArr(1U) == oldArr); + CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); + CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); + } + /* 回到 00 后,两路同时装载新频率并从精确 90° 位置重启。 */ + PlsrHwTestAdvanceAbQuarter(0U); + CHECK(PlsrHwGetEmittedPulses(0U) == 3); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + CHECK((PlsrHwTestGetArr(0U) != oldArr) + || (PlsrHwTestGetPsc(0U) != oldBasePsc)); + CHECK((PlsrHwTestGetPsc(0U) + 1UL) + == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); + CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); + newPeriod = PlsrHwTestGetArr(0U) + 1UL; + CHECK(PlsrHwTestGetCnt(0U) == (newPeriod * 3UL) / 4UL); + CHECK(PlsrHwTestGetCnt(1U) == newPeriod / 2UL); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } - CHECK(PlsrHwGetEmittedPulses(0U) == 2); + CHECK(PlsrHwGetEmittedPulses(0U) == 4); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); @@ -415,6 +453,38 @@ static void TestTwoAbAxesIndependent(void) CHECK(PlsrHwGetEmittedPulses(2U) == 1); } +static void TestAbFrequencyLimits(void) +{ + PLSR_HW_START_PARAMS params; + int quarter; + + (void)PlsrHwInit(); + (void)memset(¶ms, 0, sizeof(params)); + params.frequencyHz = 1UL; + params.targetPulses = 100; + params.outputMode = PLSR_OUTPUT_AB; + params.directionPoint = PLSR_HW_DIR_POINT_NONE; + params.directionPositive = 1U; + + CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); + CHECK(PlsrHwSetFrequency(0U, 1UL) == PLSR_RESULT_OK); + CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); + CHECK((PlsrHwTestGetPsc(0U) + 1UL) + == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); + CHECK(PlsrHwTestGetArr(0U) <= 65535UL); + + CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); + for (quarter = 0; quarter < 4; quarter++) + { + PlsrHwTestAdvanceAbQuarter(0U); + } + CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); + CHECK((PlsrHwTestGetPsc(0U) + 1UL) + == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); + CHECK(PlsrHwTestGetArr(0U) >= 3UL); + CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); +} + static void TestStopAndInvalidArgs(void) { (void)PlsrHwInit(); @@ -582,6 +652,24 @@ static void TestEndToEndAbSegment(void) CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); } +static void TestProductionSelfTestStartsAb(void) +{ + PLSR_STATUS status; + + TestResetEnvironment(); + CHECK(PlsrSelfTestQueue() == PLSR_RESULT_QUEUED); + PlsrProcess(); + status = TestGetStatus(); + CHECK(status.lastCommandResult == PLSR_RESULT_OK); + CHECK(status.outputMode == PLSR_OUTPUT_AB); + CHECK(status.currentSegment == 1U); + CHECK(status.state == PLSR_STATE_ACCEL); + CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); + CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); + CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); + CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); +} + static void TestStopStopsHardware(void) { TEST_MEMORY memory; @@ -630,9 +718,11 @@ int main(void) TestPulseCounting(); TestAbPhaseAndCounting(); TestTwoAbAxesIndependent(); + TestAbFrequencyLimits(); TestStopAndInvalidArgs(); TestEndToEndTwoSegments(); TestEndToEndAbSegment(); + TestProductionSelfTestStartsAb(); TestStopStopsHardware(); if (TestFailures != 0)