ソースを参照

P3b-2 AB 正交硬件层上板验证:双定时器同步分频 + 无毛刺相位建立,三段 AB 自测通过

本批完成内容:

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 起步
deepseek
ywh 1ヶ月前
コミット
0b7990bc99
7個のファイルの変更604行の追加75行の削除
  1. +1
    -1
      Core/Src/main.c
  2. +1
    -0
      PLSR/Inc/plsr_hal_f407.h
  3. +2
    -2
      PLSR/Inc/plsr_self_test.h
  4. +481
    -46
      PLSR/Src/plsr_hal_f407.c
  5. +9
    -7
      PLSR/Src/plsr_self_test.c
  6. +1
    -0
      PLSR/Test/run_host_tests.ps1
  7. +109
    -19
      PLSR/Test/test_plsr_hal.c

+ 1
- 1
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();


+ 1
- 0
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);


+ 2
- 2
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


+ 481
- 46
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);
}
}



+ 9
- 7
PLSR/Src/plsr_self_test.c ファイルの表示

@@ -5,11 +5,11 @@
#include <string.h>

/* 上电自测(验证后可删除):
* - 方向端子设为 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);
}

+ 1
- 0
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"
)
}


+ 109
- 19
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 <stdio.h>
#include <string.h>

@@ -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(&params, 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(&params, 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, &params) == 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)


読み込み中…
キャンセル
保存