diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp
index 765f0fc..fe594b9 100644
--- a/EWARM/Modbus.ewp
+++ b/EWARM/Modbus.ewp
@@ -1287,6 +1287,9 @@
$PROJ_DIR$\..\PLSR\Inc\plsr_path.h
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_profile.h
+
$PROJ_DIR$\..\PLSR\Src\plsr_persistence.c
@@ -1302,6 +1305,9 @@
$PROJ_DIR$\..\PLSR\Src\plsr_path.c
+
+ $PROJ_DIR$\..\PLSR\Src\plsr_profile.c
+
$PROJ_DIR$\..\PLSR\Src\plsr_core.c
diff --git a/PLSR/Inc/plsr_profile.h b/PLSR/Inc/plsr_profile.h
new file mode 100644
index 0000000..83e87a1
--- /dev/null
+++ b/PLSR/Inc/plsr_profile.h
@@ -0,0 +1,97 @@
+#ifndef PLSR_PROFILE_H
+#define PLSR_PROFILE_H
+
+#include "plsr_types.h"
+#include
+
+#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 */
diff --git a/PLSR/Src/plsr_profile.c b/PLSR/Src/plsr_profile.c
new file mode 100644
index 0000000..34a5d14
--- /dev/null
+++ b/PLSR/Src/plsr_profile.c
@@ -0,0 +1,531 @@
+#include "plsr_profile.h"
+#include
+#include
+
+/* 正弦表: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;
+}
diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1
index b550a9d..ebf55ee 100644
--- a/PLSR/Test/run_host_tests.ps1
+++ b/PLSR/Test/run_host_tests.ps1
@@ -57,6 +57,13 @@ $tests = @(
"$workspacePath\PLSR\Src\plsr_core.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'
'-DPLSR_HOST_TEST'
"-I$workspacePath\PLSR\Inc"
- ) + $test.Sources + @('-o', $outputPath)
+ ) + $test.Sources + @('-o', $outputPath, '-lm')
try
{
diff --git a/PLSR/Test/test_plsr_profile.c b/PLSR/Test/test_plsr_profile.c
new file mode 100644
index 0000000..d2224d7
--- /dev/null
+++ b/PLSR/Test/test_plsr_profile.c
@@ -0,0 +1,299 @@
+#include "plsr_profile.h"
+#include
+#include
+
+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;
+}