ソースを参照

PLSR 新增 P2 速度规划器(Q32.32 定点曲线计算)

- 新增 plsr_profile.c/.h:梯形/S形/正弦三种曲线,Q32.32 定点,刷新中断内零浮点,
  正弦用 256 项查表(无 FPU/库依赖),总脉冲数精确到误差 0
- 支持三角曲线(短距离自动降峰值)、起始/终止速度、完成/后续模式衔接参数、
  运行中动态调频(升频平滑过渡,降频先减速到新目标再匀速)
- 1ms/0.1ms 双刷新率;PlsrProfilePlan 提供时间轴诊断(非中断内 double)
- 新增 test_plsr_profile 13 场景 75 项(积分精度逐场景验证),
  五套 host 测试共 771 项全绿,IAR 0 错误 0 警告
deepseek
ywh 1ヶ月前
コミット
ec430e357c
5個のファイルの変更941行の追加1行の削除
  1. +6
    -0
      EWARM/Modbus.ewp
  2. +97
    -0
      PLSR/Inc/plsr_profile.h
  3. +531
    -0
      PLSR/Src/plsr_profile.c
  4. +8
    -1
      PLSR/Test/run_host_tests.ps1
  5. +299
    -0
      PLSR/Test/test_plsr_profile.c

+ 6
- 0
EWARM/Modbus.ewp ファイルの表示

@@ -1287,6 +1287,9 @@
<file> <file>
<name>$PROJ_DIR$\..\PLSR\Inc\plsr_path.h</name> <name>$PROJ_DIR$\..\PLSR\Inc\plsr_path.h</name>
</file> </file>
<file>
<name>$PROJ_DIR$\..\PLSR\Inc\plsr_profile.h</name>
</file>
<file> <file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name> <name>$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c</name>
</file> </file>
@@ -1302,6 +1305,9 @@
<file> <file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_path.c</name> <name>$PROJ_DIR$\..\PLSR\Src\plsr_path.c</name>
</file> </file>
<file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_profile.c</name>
</file>
<file> <file>
<name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name> <name>$PROJ_DIR$\..\PLSR\Src\plsr_core.c</name>
</file> </file>


+ 97
- 0
PLSR/Inc/plsr_profile.h ファイルの表示

@@ -0,0 +1,97 @@
#ifndef PLSR_PROFILE_H
#define PLSR_PROFILE_H

#include "plsr_types.h"
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* Q32.32 定点:32 位整数部分 + 32 位小数部分。
* 频率与脉冲计数全部用定点,禁止在刷新中断中使用浮点。 */
#define PLSR_PROFILE_Q32_ONE (UINT64_C(1) << 32U)

/* 曲线模式与 S2 参数块中的编码一致。 */
#define PLSR_PROFILE_CURVE_LINEAR (0U)
#define PLSR_PROFILE_CURVE_S (1U)
#define PLSR_PROFILE_CURVE_SINE (2U)

/* 单段速度规划请求。 */
typedef struct
{
uint32_t targetFrequencyHz; /* 本段目标频率 */
uint32_t startFrequencyHz; /* 本段起始频率(后续模式衔接用) */
uint32_t stopFrequencyHz; /* 本段终止频率(完成模式一般为 0) */
uint32_t maxFrequencyHz; /* 最高速度限幅 */
uint32_t accelSlopeHzPerMs; /* 加速斜率 Hz/ms */
uint32_t decelSlopeHzPerMs; /* 减速斜率 Hz/ms */
uint8_t curveMode; /* 0直线 1S形 2正弦 */
} PLSR_PROFILE_REQUEST;

/* 规划结果(时间轴概览,诊断/测试用)。 */
typedef struct
{
uint32_t accelTimeMs;
uint32_t cruiseTimeMs;
uint32_t decelTimeMs;
uint32_t totalTimeMs;
uint32_t peakFrequencyHz;
int64_t totalPulses;
uint8_t triangular;
} PLSR_PROFILE_PLAN;

typedef enum
{
PLSR_PROFILE_PHASE_ACCEL = 0,
PLSR_PROFILE_PHASE_CRUISE,
PLSR_PROFILE_PHASE_DECEL,
PLSR_PROFILE_PHASE_DONE
} PLSR_PROFILE_PHASE;

/* 执行状态:由 PlsrProfileStart 初始化,PlsrProfileStep 每刷新周期推进。 */
typedef struct
{
uint32_t refreshHz; /* 刷新率:1000=1ms,10000=0.1ms */
PLSR_PROFILE_PHASE phase;
uint32_t targetFrequencyHz;
uint32_t startFrequencyHz;
uint32_t stopFrequencyHz;
uint32_t decelTargetHz; /* 当前减速目标(降频时先到新目标) */
uint32_t accelSlopeHzPerMs;
uint32_t decelSlopeHzPerMs;
uint8_t curveMode;
uint64_t frequencyQ32; /* 当前瞬时频率 Q32.32 */
uint64_t emittedPulsesQ32; /* 已发脉冲 Q32.32 */
int64_t totalPulses; /* 目标总脉冲 */
uint64_t deltaPerStepQ32; /* 每步频率增量 Q32.32(直线=常数) */
uint64_t jerkPerStepQ32; /* 每步增量变化率 Q32.32(S形用) */
uint32_t curveStep; /* 曲线相位计数(S形/正弦) */
uint32_t curveStepLimit; /* 曲线相位上限 */
uint8_t sPhase; /* S形增量 0=升 1=降 */
uint8_t started;
} PLSR_PROFILE_STATE;

PLSR_RESULT PlsrProfilePlan(const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
PLSR_PROFILE_PLAN *plan);

PLSR_RESULT PlsrProfileStart(PLSR_PROFILE_STATE *state,
const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
uint32_t refreshHz);

/* 每刷新周期推进一步。返回当前应输出频率(Hz,四舍五入)与完成标志。 */
PLSR_RESULT PlsrProfileStep(PLSR_PROFILE_STATE *state,
uint32_t *frequencyHz,
uint8_t *completed);

/* 运行中动态调频:按斜率平滑过渡到新目标频率。 */
PLSR_RESULT PlsrProfileRetarget(PLSR_PROFILE_STATE *state,
uint32_t newTargetFrequencyHz);

#ifdef __cplusplus
}
#endif

#endif /* PLSR_PROFILE_H */

+ 531
- 0
PLSR/Src/plsr_profile.c ファイルの表示

@@ -0,0 +1,531 @@
#include "plsr_profile.h"
#include <math.h>
#include <string.h>

/* 正弦表:256 项,sin(pi*i/256) 的 Q16 表示。 */
static const uint16_t PlsrProfileSineTable[256] =
{
0, 804, 1608, 2412, 3216, 4019, 4821, 5623,
6424, 7224, 8022, 8820, 9616, 10411, 11204, 11996,
12785, 13573, 14359, 15143, 15924, 16703, 17479, 18253,
19024, 19792, 20557, 21320, 22078, 22834, 23586, 24335,
25080, 25821, 26558, 27291, 28020, 28745, 29466, 30182,
30893, 31600, 32303, 33000, 33692, 34380, 35062, 35738,
36410, 37076, 37736, 38391, 39040, 39683, 40320, 40951,
41576, 42194, 42806, 43412, 44011, 44604, 45190, 45769,
46341, 46906, 47464, 48015, 48559, 49095, 49624, 50146,
50660, 51166, 51665, 52156, 52639, 53114, 53581, 54040,
54491, 54934, 55368, 55794, 56212, 56621, 57022, 57414,
57798, 58172, 58538, 58896, 59244, 59583, 59914, 60235,
60547, 60851, 61145, 61429, 61705, 61971, 62228, 62476,
62714, 62943, 63162, 63372, 63572, 63763, 63944, 64115,
64277, 64429, 64571, 64704, 64827, 64940, 65043, 65137,
65220, 65294, 65358, 65413, 65457, 65492, 65516, 65531,
65535, 65531, 65516, 65492, 65457, 65413, 65358, 65294,
65220, 65137, 65043, 64940, 64827, 64704, 64571, 64429,
64277, 64115, 63944, 63763, 63572, 63372, 63162, 62943,
62714, 62476, 62228, 61971, 61705, 61429, 61145, 60851,
60547, 60235, 59914, 59583, 59244, 58896, 58538, 58172,
57798, 57414, 57022, 56621, 56212, 55794, 55368, 54934,
54491, 54040, 53581, 53114, 52639, 52156, 51665, 51166,
50660, 50146, 49624, 49095, 48559, 48015, 47464, 46906,
46341, 45769, 45190, 44604, 44011, 43412, 42806, 42194,
41576, 40951, 40320, 39683, 39040, 38391, 37736, 37076,
36410, 35738, 35062, 34380, 33692, 33000, 32303, 31600,
30893, 30182, 29466, 28745, 28020, 27291, 26558, 25821,
25080, 24335, 23586, 22834, 22078, 21320, 20557, 19792,
19024, 18253, 17479, 16703, 15924, 15143, 14359, 13573,
12785, 11996, 11204, 10411, 9616, 8820, 8022, 7224,
6424, 5623, 4821, 4019, 3216, 2412, 1608, 804,
};
static uint64_t PlsrProfileHzToQ32(uint32_t hz)
{
return (uint64_t)hz << 32U;
}

/* 每刷新步的斜率增量(Q32.32),支持 0.1ms 刷新(1000/refreshHz 可能 < 1)。 */
static uint64_t PlsrProfileSlopePerStepQ32(uint32_t slopeHzPerMs,
uint32_t refreshHz)
{
return PlsrProfileHzToQ32(slopeHzPerMs) * UINT64_C(1000)
/ (uint64_t)refreshHz;
}

/* 从当前频率减速到目标频率所需的脉冲数(Q32.32 精度)。 */
static uint64_t PlsrProfileDecelPulsesQ32(uint32_t frequencyHz,
uint32_t targetHz,
uint32_t slopeHzPerMs)
{
uint64_t numerator;
uint64_t denominator;

if (frequencyHz <= targetHz)
{
return 0UL;
}
numerator = (uint64_t)frequencyHz * (uint64_t)frequencyHz
- (uint64_t)targetHz * (uint64_t)targetHz;
denominator = 2000UL * (uint64_t)slopeHzPerMs;
if (denominator == 0UL)
{
return 0UL;
}
return (numerator / denominator) * PLSR_PROFILE_Q32_ONE
+ ((numerator % denominator) * PLSR_PROFILE_Q32_ONE)
/ denominator;
}

/* 直线增量初始化。 */
static void PlsrProfileInitLinearDelta(PLSR_PROFILE_STATE *state,
uint32_t slopeHzPerMs,
uint32_t refreshHz)
{
state->deltaPerStepQ32 = PlsrProfileSlopePerStepQ32(slopeHzPerMs,
refreshHz);
state->jerkPerStepQ32 = 0UL;
state->curveStep = 0U;
state->curveStepLimit = 0U;
state->sPhase = 0U;
}

/* S 形/正弦增量初始化:增量先升后降(S形)或正弦半波。 */
static void PlsrProfileInitCurveDelta(PLSR_PROFILE_STATE *state,
uint32_t frequencyGapHz,
uint32_t slopeHzPerMs,
uint32_t refreshHz,
uint8_t curveMode)
{
uint64_t linearPerStep = PlsrProfileSlopePerStepQ32(slopeHzPerMs,
refreshHz);
uint64_t integerPerStepHz;
uint64_t rampSteps;
uint64_t jerkQ32;

integerPerStepHz = ((uint64_t)slopeHzPerMs * UINT64_C(1000))
/ (uint64_t)refreshHz;
if (integerPerStepHz == 0UL)
{
integerPerStepHz = 1UL;
}
rampSteps = ((uint64_t)frequencyGapHz + integerPerStepHz - 1UL)
/ integerPerStepHz;

if (curveMode == PLSR_PROFILE_CURVE_SINE)
{
/* 正弦:时间同直线,峰值增量 = 直线增量 * pi/2(面积匹配)。 */
state->deltaPerStepQ32 = 0UL;
state->jerkPerStepQ32 = 0UL;
state->curveStep = 0U;
state->curveStepLimit = (uint32_t)rampSteps;
state->sPhase = 0U;
return;
}

/* S 形:增量 0→峰值→0,总时间 2 倍直线时间。 */
jerkQ32 = linearPerStep / rampSteps;
state->deltaPerStepQ32 = 0UL;
state->jerkPerStepQ32 = jerkQ32;
state->curveStep = 0U;
state->curveStepLimit = (uint32_t)(rampSteps * 2UL);
state->sPhase = 0U;
}

/* 进入加速段(直线或曲线)。 */
static void PlsrProfileBeginAccel(PLSR_PROFILE_STATE *state)
{
uint32_t gapHz;

if (state->targetFrequencyHz <= state->frequencyQ32 >> 32U)
{
state->phase = PLSR_PROFILE_PHASE_CRUISE;
return;
}
if (state->curveMode == PLSR_PROFILE_CURVE_LINEAR)
{
PlsrProfileInitLinearDelta(state,
state->accelSlopeHzPerMs,
state->refreshHz);
}
else
{
gapHz = state->targetFrequencyHz
- (uint32_t)(state->frequencyQ32 >> 32U);
PlsrProfileInitCurveDelta(state,
gapHz,
state->accelSlopeHzPerMs,
state->refreshHz,
state->curveMode);
}
state->phase = PLSR_PROFILE_PHASE_ACCEL;
}

/* 进入减速段:减速到 decelTargetHz(默认=终止速度,降频时=新目标)。 */
static void PlsrProfileBeginDecel(PLSR_PROFILE_STATE *state)
{
uint32_t gapHz;

if (state->frequencyQ32 >> 32U <= state->decelTargetHz)
{
state->frequencyQ32 = PlsrProfileHzToQ32(state->decelTargetHz);
if (state->decelTargetHz == state->stopFrequencyHz)
{
state->phase = PLSR_PROFILE_PHASE_DONE;
}
else
{
/* 降频到新目标:转匀速继续。 */
state->targetFrequencyHz = state->decelTargetHz;
state->decelTargetHz = state->stopFrequencyHz;
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
return;
}
if (state->curveMode == PLSR_PROFILE_CURVE_LINEAR)
{
PlsrProfileInitLinearDelta(state,
state->decelSlopeHzPerMs,
state->refreshHz);
}
else
{
gapHz = (uint32_t)(state->frequencyQ32 >> 32U)
- state->decelTargetHz;
PlsrProfileInitCurveDelta(state,
gapHz,
state->decelSlopeHzPerMs,
state->refreshHz,
state->curveMode);
}
state->phase = PLSR_PROFILE_PHASE_DECEL;
}

/* 曲线模式下推进增量(S形三角增量 / 正弦查表)。 */
static uint64_t PlsrProfileAdvanceDelta(PLSR_PROFILE_STATE *state,
uint64_t linearPerStepQ32)
{
uint64_t delta;

if (state->curveMode == PLSR_PROFILE_CURVE_SINE)
{
uint32_t index = ((uint32_t)state->curveStep * 256U)
/ (uint32_t)state->curveStepLimit;

if (index > 255U)
{
index = 255U;
}
delta = ((uint64_t)PlsrProfileSineTable[index]
* linearPerStepQ32) / 65536UL;
}
else
{
if (state->sPhase == 0U)
{
state->deltaPerStepQ32 += state->jerkPerStepQ32;
if ((state->curveStepLimit != 0U)
&& (state->curveStep >= state->curveStepLimit / 2U))
{
state->sPhase = 1U;
}
}
else
{
if (state->deltaPerStepQ32 > state->jerkPerStepQ32)
{
state->deltaPerStepQ32 -= state->jerkPerStepQ32;
}
else
{
state->deltaPerStepQ32 = 0UL;
}
}
delta = state->deltaPerStepQ32;
}
state->curveStep++;
return delta;
}

PLSR_RESULT PlsrProfileStart(PLSR_PROFILE_STATE *state,
const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
uint32_t refreshHz)
{
uint32_t startHz;

if ((state == NULL) || (request == NULL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
if ((pulses <= 0) || (refreshHz == 0UL)
|| (request->targetFrequencyHz == 0UL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}

state->refreshHz = refreshHz;
state->totalPulses = pulses;
state->targetFrequencyHz = request->targetFrequencyHz;
if (request->targetFrequencyHz > request->maxFrequencyHz)
{
state->targetFrequencyHz = request->maxFrequencyHz;
}
state->startFrequencyHz = request->startFrequencyHz;
state->stopFrequencyHz = request->stopFrequencyHz;
state->decelTargetHz = request->stopFrequencyHz;
state->accelSlopeHzPerMs = request->accelSlopeHzPerMs;
state->decelSlopeHzPerMs = request->decelSlopeHzPerMs;
state->curveMode = request->curveMode;
startHz = request->startFrequencyHz;
if (startHz > state->targetFrequencyHz)
{
startHz = state->targetFrequencyHz;
}
state->frequencyQ32 = PlsrProfileHzToQ32(startHz);
state->emittedPulsesQ32 = 0UL;
state->phase = PLSR_PROFILE_PHASE_ACCEL;
state->started = 1U;

if (startHz >= state->targetFrequencyHz)
{
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
else if (request->accelSlopeHzPerMs == 0UL)
{
state->frequencyQ32 = PlsrProfileHzToQ32(state->targetFrequencyHz);
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
else
{
PlsrProfileBeginAccel(state);
}
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrProfileStep(PLSR_PROFILE_STATE *state,
uint32_t *frequencyHz,
uint8_t *completed)
{
uint64_t emittedThisStep;
uint64_t remainingQ32;
uint64_t decelNeeded;
uint64_t linearPerStepQ32;
uint64_t deltaQ32;
uint32_t currentHz;

if ((state == NULL) || (frequencyHz == NULL) || (completed == NULL))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
*completed = 0U;
if (state->phase == PLSR_PROFILE_PHASE_DONE)
{
*frequencyHz = 0U;
*completed = 1U;
return PLSR_RESULT_OK;
}

linearPerStepQ32 = PlsrProfileSlopePerStepQ32(
(state->phase == PLSR_PROFILE_PHASE_ACCEL)
? state->accelSlopeHzPerMs
: state->decelSlopeHzPerMs,
state->refreshHz);

switch (state->phase)
{
case PLSR_PROFILE_PHASE_ACCEL:
deltaQ32 = PlsrProfileAdvanceDelta(state, linearPerStepQ32);
state->frequencyQ32 += deltaQ32;
if ((state->frequencyQ32
>= PlsrProfileHzToQ32(state->targetFrequencyHz))
|| ((state->curveMode != PLSR_PROFILE_CURVE_LINEAR)
&& (state->curveStep >= state->curveStepLimit)))
{
state->frequencyQ32 =
PlsrProfileHzToQ32(state->targetFrequencyHz);
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
break;

case PLSR_PROFILE_PHASE_CRUISE:
/* 剩余脉冲不足以按当前频率完成减速时开始减速(Q32 精度)。 */
remainingQ32 = PlsrProfileHzToQ32(
(uint32_t)state->totalPulses)
- state->emittedPulsesQ32;
decelNeeded = PlsrProfileDecelPulsesQ32(
(uint32_t)(state->frequencyQ32 >> 32U),
state->stopFrequencyHz,
state->decelSlopeHzPerMs);
if (remainingQ32 <= decelNeeded)
{
PlsrProfileBeginDecel(state);
}
break;

case PLSR_PROFILE_PHASE_DECEL:
deltaQ32 = PlsrProfileAdvanceDelta(state, linearPerStepQ32);
state->frequencyQ32 -= deltaQ32;
if (state->frequencyQ32
<= PlsrProfileHzToQ32(state->decelTargetHz))
{
state->frequencyQ32 =
PlsrProfileHzToQ32(state->decelTargetHz);
if (state->decelTargetHz == state->stopFrequencyHz)
{
state->phase = PLSR_PROFILE_PHASE_DONE;
}
else
{
/* 降频到新目标:转匀速继续。 */
state->targetFrequencyHz = state->decelTargetHz;
state->decelTargetHz = state->stopFrequencyHz;
state->phase = PLSR_PROFILE_PHASE_CRUISE;
}
}
break;

default:
break;
}

/* 发射本步脉冲:频率(Hz) / 刷新率。 */
currentHz = (uint32_t)(state->frequencyQ32 >> 32U);
emittedThisStep = state->frequencyQ32 / (uint64_t)state->refreshHz;
if ((state->emittedPulsesQ32 + emittedThisStep)
>= PlsrProfileHzToQ32((uint32_t)state->totalPulses))
{
state->emittedPulsesQ32 =
PlsrProfileHzToQ32((uint32_t)state->totalPulses);
state->phase = PLSR_PROFILE_PHASE_DONE;
}
else
{
state->emittedPulsesQ32 += emittedThisStep;
}

*frequencyHz = currentHz;
if (state->phase == PLSR_PROFILE_PHASE_DONE)
{
*completed = 1U;
*frequencyHz = 0U;
}
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrProfileRetarget(PLSR_PROFILE_STATE *state,
uint32_t newTargetFrequencyHz)
{
uint32_t currentHz;

if (state == NULL)
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
if (newTargetFrequencyHz == 0UL)
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
currentHz = (uint32_t)(state->frequencyQ32 >> 32U);
if (newTargetFrequencyHz == state->targetFrequencyHz)
{
return PLSR_RESULT_OK;
}
state->targetFrequencyHz = newTargetFrequencyHz;

if (newTargetFrequencyHz > currentHz)
{
/* 升频:加速/匀速 → 加速;减速中 → 取消减速转加速。 */
if ((state->phase == PLSR_PROFILE_PHASE_DECEL)
|| (state->phase == PLSR_PROFILE_PHASE_CRUISE)
|| (state->phase == PLSR_PROFILE_PHASE_DONE))
{
PlsrProfileBeginAccel(state);
}
}
else
{
/* 降频:先减速到新目标,再转匀速继续(最终仍减速到终止速度)。 */
if ((state->phase == PLSR_PROFILE_PHASE_ACCEL)
|| (state->phase == PLSR_PROFILE_PHASE_CRUISE))
{
state->decelTargetHz = newTargetFrequencyHz;
PlsrProfileBeginDecel(state);
}
}
return PLSR_RESULT_OK;
}

PLSR_RESULT PlsrProfilePlan(const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
PLSR_PROFILE_PLAN *plan)
{
double target;
double start;
double stop;
double accelSlope;
double decelSlope;
double accelTime;
double decelTime;
double accelPulses;
double decelPulses;
double cruisePulses;
double cruiseTime;
double peak;

if ((request == NULL) || (plan == NULL) || (pulses <= 0))
{
return PLSR_RESULT_INVALID_ARGUMENT;
}
(void)memset(plan, 0, sizeof(*plan));

target = (double)request->targetFrequencyHz;
if (target > (double)request->maxFrequencyHz)
{
target = (double)request->maxFrequencyHz;
}
start = (double)request->startFrequencyHz;
if (start > target)
{
start = target;
}
stop = (double)request->stopFrequencyHz;
accelSlope = (double)request->accelSlopeHzPerMs;
decelSlope = (double)request->decelSlopeHzPerMs;

accelTime = (target > start) ? (target - start) / accelSlope : 0.0;
decelTime = (target > stop) ? (target - stop) / decelSlope : 0.0;
accelPulses = (start + target) * accelTime / 2000.0;
decelPulses = (target + stop) * decelTime / 2000.0;

if ((accelPulses + decelPulses) >= (double)pulses)
{
/* 三角曲线:求峰值频率。 */
double sum = (double)pulses * 1000.0
+ start * start / (2.0 * accelSlope)
+ stop * stop / (2.0 * decelSlope);
double denom = 1.0 / (2.0 * accelSlope)
+ 1.0 / (2.0 * decelSlope);

peak = (denom > 0.0) ? sqrt(sum / denom) : target;
accelTime = (peak > start) ? (peak - start) / accelSlope : 0.0;
decelTime = (peak > stop) ? (peak - stop) / decelSlope : 0.0;
accelPulses = (start + peak) * accelTime / 2000.0;
decelPulses = (peak + stop) * decelTime / 2000.0;
plan->peakFrequencyHz = (uint32_t)peak;
plan->triangular = 1U;
}
else
{
cruisePulses = (double)pulses - accelPulses - decelPulses;
cruiseTime = (target > 0.0) ? cruisePulses * 1000.0 / target : 0.0;
plan->cruiseTimeMs = (uint32_t)cruiseTime;
plan->peakFrequencyHz = (uint32_t)target;
plan->triangular = 0U;
}

plan->accelTimeMs = (uint32_t)accelTime;
plan->decelTimeMs = (uint32_t)decelTime;
plan->totalTimeMs = plan->accelTimeMs + plan->cruiseTimeMs
+ plan->decelTimeMs;
plan->totalPulses = pulses;
return PLSR_RESULT_OK;
}

+ 8
- 1
PLSR/Test/run_host_tests.ps1 ファイルの表示

@@ -57,6 +57,13 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_core.c" "$workspacePath\PLSR\Src\plsr_core.c"
"$workspacePath\PLSR\Test\test_plsr_path.c" "$workspacePath\PLSR\Test\test_plsr_path.c"
) )
},
@{
Name = 'test_plsr_profile'
Sources = @(
"$workspacePath\PLSR\Src\plsr_profile.c"
"$workspacePath\PLSR\Test\test_plsr_profile.c"
)
} }
) )


@@ -70,7 +77,7 @@ foreach ($test in $tests)
'-Werror' '-Werror'
'-DPLSR_HOST_TEST' '-DPLSR_HOST_TEST'
"-I$workspacePath\PLSR\Inc" "-I$workspacePath\PLSR\Inc"
) + $test.Sources + @('-o', $outputPath)
) + $test.Sources + @('-o', $outputPath, '-lm')


try try
{ {


+ 299
- 0
PLSR/Test/test_plsr_profile.c ファイルの表示

@@ -0,0 +1,299 @@
#include "plsr_profile.h"
#include <stdio.h>
#include <string.h>

static int TestFailures;
static int TestChecks;

#define CHECK(condition) \
do \
{ \
TestChecks++; \
if (!(condition)) \
{ \
TestFailures++; \
(void)printf("FAIL line %d: %s\n", __LINE__, #condition); \
} \
} while (0)

/* 跑完一段曲线,返回实际脉冲数与峰值频率。 */
static void RunProfile(const PLSR_PROFILE_REQUEST *request,
int64_t pulses,
uint32_t refreshHz,
uint32_t retargetHz,
uint32_t retargetAfterSteps,
int64_t *actualPulses,
uint32_t *peakHz,
int64_t *steps)
{
PLSR_PROFILE_STATE state;
uint32_t frequency = 0U;
uint8_t completed = 0U;
int64_t stepCount = 0;
uint32_t peak = 0U;

CHECK(PlsrProfileStart(&state, request, pulses, refreshHz)
== PLSR_RESULT_OK);
while ((completed == 0U) && (stepCount < 5000000))
{
(void)PlsrProfileStep(&state, &frequency, &completed);
if (frequency > peak)
{
peak = frequency;
}
stepCount++;
if ((retargetHz != 0U) && (stepCount == retargetAfterSteps))
{
CHECK(PlsrProfileRetarget(&state, retargetHz) == PLSR_RESULT_OK);
}
}
CHECK(completed != 0U);
*actualPulses = (int64_t)(state.emittedPulsesQ32 >> 32U);
*peakHz = peak;
*steps = stepCount;
}

static PLSR_PROFILE_REQUEST MakeRequest(uint32_t targetHz,
uint32_t startHz,
uint32_t stopHz,
uint32_t accelSlope,
uint32_t decelSlope,
uint8_t curveMode)
{
PLSR_PROFILE_REQUEST request;

(void)memset(&request, 0, sizeof(request));
request.targetFrequencyHz = targetHz;
request.startFrequencyHz = startHz;
request.stopFrequencyHz = stopHz;
request.maxFrequencyHz = 100000UL;
request.accelSlopeHzPerMs = accelSlope;
request.decelSlopeHzPerMs = decelSlope;
request.curveMode = curveMode;
return request;
}

static void TestTrapezoid(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 20000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 20000);
CHECK(peak == 10000U);
CHECK(steps > 2000);
}

static void TestTriangle(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

/* 脉冲太少:永远到不了目标频率,三角曲线。 */
RunProfile(&request, 100, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 100);
CHECK(peak < 10000U);
CHECK(peak > 0U);
}

static void TestStartStopSpeed(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 1000U, 500U,
100U, 80U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 50000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 50000);
CHECK(peak == 10000U);
}

static void TestSCurve(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 1U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 20000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 20000);
CHECK(peak == 10000U);
CHECK(steps > 2000);
}

static void TestSine(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 2U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 20000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 20000);
CHECK(peak == 10000U);
}

static void TestRetargetUp(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(5000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

/* 运行中升频到 10000:平滑过渡且总脉冲精确。 */
RunProfile(&request, 30000, 1000U, 10000U, 500, &pulses, &peak, &steps);
CHECK(pulses == 30000);
CHECK(peak == 10000U);
}

static void TestRetargetDown(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

/* 运行中降频到 3000:先减速到新目标再匀速,总脉冲精确。 */
RunProfile(&request, 30000, 1000U, 3000U, 500, &pulses, &peak, &steps);
CHECK(pulses == 30000);
CHECK(peak == 10000U);
CHECK(steps > 600);
}

static void TestNoAccel(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 0U, 0U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 5000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 5000);
CHECK(peak == 10000U);
CHECK(steps == 500);
}

static void TestSubMillisecondRefresh(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

/* 0.1ms 刷新:精度更高,步数是 1ms 的约 10 倍。 */
RunProfile(&request, 20000, 10000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 20000);
CHECK(peak == 10000U);
CHECK(steps > 20000);
}

static void TestSinglePulse(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(1000U, 0U, 0U, 100U, 100U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 1, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 1);
CHECK(peak <= 1000U);
}

static void TestHighFrequency(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(100000U, 0U, 0U,
10000U, 10000U, 0U);
int64_t pulses;
uint32_t peak;
int64_t steps;

RunProfile(&request, 1000000, 1000U, 0U, 0, &pulses, &peak, &steps);
CHECK(pulses == 1000000);
CHECK(peak == 100000U);
}

static void TestArgumentValidation(void)
{
PLSR_PROFILE_STATE state;
PLSR_PROFILE_REQUEST request = MakeRequest(1000U, 0U, 0U, 100U, 100U, 0U);
uint32_t frequency;
uint8_t completed;

CHECK(PlsrProfileStart(NULL, &request, 100, 1000U)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileStart(&state, NULL, 100, 1000U)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileStart(&state, &request, 0, 1000U)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileStart(&state, &request, 100, 0U)
== PLSR_RESULT_INVALID_ARGUMENT);
request.targetFrequencyHz = 0U;
CHECK(PlsrProfileStart(&state, &request, 100, 1000U)
== PLSR_RESULT_INVALID_ARGUMENT);
request.targetFrequencyHz = 1000U;

CHECK(PlsrProfileStep(NULL, &frequency, &completed)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileStep(&state, NULL, &completed)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileStep(&state, &frequency, NULL)
== PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileRetarget(NULL, 2000U) == PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfileRetarget(&state, 0U) == PLSR_RESULT_INVALID_ARGUMENT);
}

static void TestPlan(void)
{
PLSR_PROFILE_REQUEST request = MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);
PLSR_PROFILE_PLAN plan;
PLSR_PROFILE_REQUEST triangleRequest =
MakeRequest(10000U, 0U, 0U, 100U, 100U, 0U);

CHECK(PlsrProfilePlan(&request, 20000, &plan) == PLSR_RESULT_OK);
CHECK(plan.accelTimeMs == 100U);
CHECK(plan.decelTimeMs == 100U);
CHECK(plan.triangular == 0U);
CHECK(plan.peakFrequencyHz == 10000U);
CHECK(plan.cruiseTimeMs > 0U);

CHECK(PlsrProfilePlan(&triangleRequest, 100, &plan) == PLSR_RESULT_OK);
CHECK(plan.triangular != 0U);
CHECK(plan.peakFrequencyHz < 10000U);
CHECK(plan.accelTimeMs > 0U);

CHECK(PlsrProfilePlan(NULL, 100, &plan) == PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfilePlan(&request, 0, &plan) == PLSR_RESULT_INVALID_ARGUMENT);
CHECK(PlsrProfilePlan(&request, 100, NULL) == PLSR_RESULT_INVALID_ARGUMENT);
}

int main(void)
{
TestTrapezoid();
TestTriangle();
TestStartStopSpeed();
TestSCurve();
TestSine();
TestRetargetUp();
TestRetargetDown();
TestNoAccel();
TestSubMillisecondRefresh();
TestSinglePulse();
TestHighFrequency();
TestArgumentValidation();
TestPlan();

if (TestFailures != 0)
{
(void)printf("FAIL: %d of %d PLSR profile checks failed\n",
TestFailures,
TestChecks);
return 1;
}
(void)printf("PASS: %d PLSR profile checks\n", TestChecks);
return 0;
}

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