| @@ -21,3 +21,5 @@ build-plsr_host-Desktop_Qt_5_6_3_MinGW_32bit-Debug/ | |||||
| plsr/test/ | plsr/test/ | ||||
| tools/ | tools/ | ||||
| .cursor/ | .cursor/ | ||||
| 3. 定位控制手册.pdf | |||||
| @@ -234,6 +234,57 @@ uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0, | |||||
| return PlsrAccelCurveLerp(f0, f1, r); | return PlsrAccelCurveLerp(f0, f1, r); | ||||
| } | } | ||||
| /** | |||||
| * 斜坡第一拍频率(规划/开跑共用)。 | |||||
| * f0>=1:第一拍即 f0。 | |||||
| * f0==0:t=0 曲线为 0;若强行按 1Hz 计整周期(~1s),加速时间常被第一拍吃光, | |||||
| * 表现为到不了目标频、曲线台阶/长拖尾。此处用 look-ahead 自洽点,且 ≥1Hz。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0, | |||||
| uint32_t f1, | |||||
| uint32_t T_us, | |||||
| PlsrAccelMode_e mode) | |||||
| { | |||||
| uint32_t f; | |||||
| uint32_t guess; | |||||
| uint32_t per; | |||||
| uint32_t fnew; | |||||
| uint32_t i; | |||||
| if (T_us == 0U) | |||||
| { | |||||
| f = (f1 >= 1U) ? f1 : 1U; | |||||
| return f; | |||||
| } | |||||
| f = PlsrAccelCurveFreqOnRampUs(f0, f1, 0U, T_us, mode); | |||||
| if (f >= 1U) | |||||
| { | |||||
| return f; | |||||
| } | |||||
| guess = (f1 >= 2U) ? (f1 / 2U) : 1U; | |||||
| if (guess < 1U) | |||||
| { | |||||
| guess = 1U; | |||||
| } | |||||
| for (i = 0U; i < 8U; i++) | |||||
| { | |||||
| per = (1000000UL + (guess / 2UL)) / guess; | |||||
| fnew = PlsrAccelCurveFreqOnRampUs(f0, f1, per, T_us, mode); | |||||
| if (fnew < 1U) | |||||
| { | |||||
| fnew = 1U; | |||||
| } | |||||
| if (fnew == guess) | |||||
| { | |||||
| break; | |||||
| } | |||||
| guess = fnew; | |||||
| } | |||||
| return guess; | |||||
| } | |||||
| /** | /** | ||||
| * 脉冲起点锁频递推脉冲数(与 ISR 一致);f(t) 按 mode 形状。 | * 脉冲起点锁频递推脉冲数(与 ISR 一致);f(t) 按 mode 形状。 | ||||
| * 仅任务/规划上下文;禁止进脉冲 ISR。 | * 仅任务/规划上下文;禁止进脉冲 ISR。 | ||||
| @@ -264,7 +315,15 @@ static uint32_t PlsrAccelCurveEstimatePulses(uint32_t f_from, | |||||
| f = PlsrAccelCurveFreqOnRampUs(f_from, f_to, elapsed_us, T_us, mode); | f = PlsrAccelCurveFreqOnRampUs(f_from, f_to, elapsed_us, T_us, mode); | ||||
| if (f < 1U) | if (f < 1U) | ||||
| { | { | ||||
| f = 1U; | |||||
| if ((elapsed_us == 0U) && (f_from == 0U)) | |||||
| { | |||||
| f = PlsrAccelCurveFirstPulseFreq(f_from, f_to, T_us, mode); | |||||
| } | |||||
| else | |||||
| { | |||||
| /* 止速到 0:末段按 1Hz 计周期,与运行输出下限一致 */ | |||||
| f = 1U; | |||||
| } | |||||
| } | } | ||||
| elapsed_us += (1000000UL + (f / 2UL)) / f; | elapsed_us += (1000000UL + (f / 2UL)) / f; | ||||
| n++; | n++; | ||||
| @@ -43,6 +43,15 @@ uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0, | |||||
| uint32_t T_us, | uint32_t T_us, | ||||
| PlsrAccelMode_e mode); | PlsrAccelMode_e mode); | ||||
| /** | |||||
| * 斜坡第一拍频率(起速为 0 时用自洽点,避免按 1Hz 整秒吃光加速相)。 | |||||
| * ISR 可用:O(1)。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0, | |||||
| uint32_t f1, | |||||
| uint32_t T_us, | |||||
| PlsrAccelMode_e mode); | |||||
| /** 拟合斜坡时间(ms),使离散模型脉冲数尽量等于 max_pulses(任务上下文) */ | /** 拟合斜坡时间(ms),使离散模型脉冲数尽量等于 max_pulses(任务上下文) */ | ||||
| uint32_t PlsrAccelCurveFitRampTimeMs(uint32_t f_from, | uint32_t PlsrAccelCurveFitRampTimeMs(uint32_t f_from, | ||||
| uint32_t f_to, | uint32_t f_to, | ||||
| @@ -47,7 +47,7 @@ | |||||
| #define PLSR_SEG_OFF_WAIT 4U /* 等待类型 */ | #define PLSR_SEG_OFF_WAIT 4U /* 等待类型 */ | ||||
| #define PLSR_SEG_OFF_WAIT_MS 5U | #define PLSR_SEG_OFF_WAIT_MS 5U | ||||
| #define PLSR_SEG_OFF_ACT_MS 6U | #define PLSR_SEG_OFF_ACT_MS 6U | ||||
| #define PLSR_SEG_OFF_JUMP 7U /* 0=顺序下一段;1~N=跳转段号 */ | |||||
| #define PLSR_SEG_OFF_JUMP 7U /* 0=结束;1~N=跳转目标段号 */ | |||||
| /* 分帧传输状态(上位机可轮询) */ | /* 分帧传输状态(上位机可轮询) */ | ||||
| #define PLSR_PARAM_XFER_RECV_REG 0x0FFCU | #define PLSR_PARAM_XFER_RECV_REG 0x0FFCU | ||||
| @@ -102,8 +102,8 @@ typedef struct { | |||||
| uint16_t seg_count; | uint16_t seg_count; | ||||
| uint16_t start_seg; /* 1-based */ | uint16_t start_seg; /* 1-based */ | ||||
| uint32_t default_speed; /* 脉冲默认速度 Hz:定加减速斜率 K;段频为 0 时也作段目标 */ | uint32_t default_speed; /* 脉冲默认速度 Hz:定加减速斜率 K;段频为 0 时也作段目标 */ | ||||
| uint32_t start_speed; /* 脉冲起始速度:首段(或断链重启)从该频起跳 */ | |||||
| uint32_t end_speed; /* 脉冲终止速度:末段减速落到该频后再停 */ | |||||
| uint32_t start_speed; /* 起速Hz;0=从静止按斜率爬升,输出≥1Hz */ | |||||
| uint32_t end_speed; /* 止速Hz;0=规划减到0,输出收到≥1Hz再停 */ | |||||
| uint16_t accel_ms; /* 默认速度加速时间 ms(定加速斜率) */ | uint16_t accel_ms; /* 默认速度加速时间 ms(定加速斜率) */ | ||||
| uint16_t decel_ms; /* 默认速度减速时间 ms(定减速斜率) */ | uint16_t decel_ms; /* 默认速度减速时间 ms(定减速斜率) */ | ||||
| } PlsrCfg_t; | } PlsrCfg_t; | ||||
| @@ -115,7 +115,7 @@ typedef struct { | |||||
| PlsrWaitType_e wait_type; | PlsrWaitType_e wait_type; | ||||
| uint16_t wait_ms; | uint16_t wait_ms; | ||||
| uint16_t act_ms; | uint16_t act_ms; | ||||
| uint16_t jump_seg; /* 0=顺序;1~N=跳转 */ | |||||
| uint16_t jump_seg; /* 0=结束;1~N=跳转目标段号 */ | |||||
| } PlsrSeg_t; | } PlsrSeg_t; | ||||
| /* ---------- 运行映像访问 ---------- */ | /* ---------- 运行映像访问 ---------- */ | ||||
| @@ -11,16 +11,17 @@ int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based) | |||||
| uint16_t n = PlsrParamGetSegCount(); | uint16_t n = PlsrParamGetSegCount(); | ||||
| uint16_t jump = seg->jump_seg; | uint16_t jump = seg->jump_seg; | ||||
| /* jump=0:顺序下一;越界则结束 */ | |||||
| /* | |||||
| * 跳转目标(1-based): | |||||
| * 0 → 结束(含末段写 0) | |||||
| * 1..N → 跳到对应段(可顺序、回跳循环、任意段) | |||||
| * 其它 → 结束 | |||||
| */ | |||||
| if (jump == 0U) | if (jump == 0U) | ||||
| { | { | ||||
| if ((cur_seg_0based + 1U) >= n) | |||||
| { | |||||
| return -1; | |||||
| } | |||||
| return (int16_t)(cur_seg_0based + 1U); | |||||
| (void)cur_seg_0based; | |||||
| return -1; | |||||
| } | } | ||||
| /* jump=1..N:跳到指定段;非法则结束 */ | |||||
| if ((jump >= 1U) && (jump <= n)) | if ((jump >= 1U) && (jump <= n)) | ||||
| { | { | ||||
| return (int16_t)(jump - 1U); | return (int16_t)(jump - 1U); | ||||
| @@ -14,7 +14,7 @@ | |||||
| * @brief 解析下一段索引 | * @brief 解析下一段索引 | ||||
| * @param cur_seg_0based 当前段 0-based | * @param cur_seg_0based 当前段 0-based | ||||
| * @return 下一段 0-based;无下一段或非法跳转返回 -1 | * @return 下一段 0-based;无下一段或非法跳转返回 -1 | ||||
| * @note jump=0 顺序下一段;jump=1~N 跳到对应段 | |||||
| * @note jump=0 结束;jump=1~N 跳到对应段(可循环) | |||||
| */ | */ | ||||
| int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based); | int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based); | ||||
| @@ -152,6 +152,16 @@ static uint32_t PlsrRunControlClampSpeed(uint32_t spd) | |||||
| return spd; | return spd; | ||||
| } | } | ||||
| /** 运行输出下限:参数起/止速为 0 时曲线可算到 0,TIM 最低按 1Hz 输出 */ | |||||
| static uint32_t PlsrRunControlOutFreq(uint32_t f) | |||||
| { | |||||
| if (f < 1U) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| return f; | |||||
| } | |||||
| static void PlsrRunControlApplyOutFreq(uint32_t profile_freq, uint8_t do_start) | static void PlsrRunControlApplyOutFreq(uint32_t profile_freq, uint8_t do_start) | ||||
| { | { | ||||
| /* | /* | ||||
| @@ -374,6 +384,7 @@ static void PlsrRunControlRefreshProfile(uint8_t do_start) | |||||
| /* | /* | ||||
| * ARPE 超前一拍:这里算出的 next 是“再下一拍”的预装载。 | * ARPE 超前一拍:这里算出的 next 是“再下一拍”的预装载。 | ||||
| * remain==2 时预装载最后一拍 → 必须为 f_end;remain==1 最后一拍已在跑。 | * remain==2 时预装载最后一拍 → 必须为 f_end;remain==1 最后一拍已在跑。 | ||||
| * (止速参数为 0 时规划端点已是 1Hz,最后一拍落到 1Hz 再段结束停表。) | |||||
| */ | */ | ||||
| if ((remain <= 1U) || (T_us == 0U)) | if ((remain <= 1U) || (T_us == 0U)) | ||||
| { | { | ||||
| @@ -409,16 +420,14 @@ static void PlsrRunControlRefreshProfile(uint8_t do_start) | |||||
| } | } | ||||
| } | } | ||||
| if ((do_start != 0U) || (next != s_pending_freq) || (s_pwm_on == 0U)) | |||||
| if (next < 1U) | |||||
| { | { | ||||
| PlsrRunControlApplyOutFreq(next, do_start); | |||||
| next = 1U; | |||||
| } | } | ||||
| if ((next == 0U) && (s_phase == PH_DECEL) && (s_state == RC_RUN) && | |||||
| (s_done < s_target)) | |||||
| if ((do_start != 0U) || (next != s_pending_freq) || (s_pwm_on == 0U)) | |||||
| { | { | ||||
| s_done = s_target; | |||||
| PlsrRunControlAfterSegDone(); | |||||
| PlsrRunControlApplyOutFreq(next, do_start); | |||||
| } | } | ||||
| } | } | ||||
| @@ -683,6 +692,7 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0) | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| /* 起速可为 0:规划/斜率按 0;开跑第一拍用 FirstPulseFreq(≥1Hz) */ | |||||
| f_from = PlsrRunControlClampSpeed(cfg->start_speed); | f_from = PlsrRunControlClampSpeed(cfg->start_speed); | ||||
| } | } | ||||
| @@ -709,6 +719,7 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0) | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| /* 止速可为 0:规划减到 0;运行输出下限 1Hz */ | |||||
| f_end = PlsrRunControlClampSpeed(cfg->end_speed); | f_end = PlsrRunControlClampSpeed(cfg->end_speed); | ||||
| } | } | ||||
| } | } | ||||
| @@ -866,29 +877,46 @@ void PlsrRunControlTickMs(void) | |||||
| { | { | ||||
| PlsrRunControlEnterPhase(PH_CONST, s_approach_from); | PlsrRunControlEnterPhase(PH_CONST, s_approach_from); | ||||
| } | } | ||||
| /* 第一个脉冲 = 脉冲起始速度(不是目标频、不是 1Hz) */ | |||||
| PlsrRunControlApplyOutFreq(s_approach_from, 1U); | |||||
| /* | /* | ||||
| * ARPE:立刻预装载第 2 拍频率,否则第 2 拍仍是起速(等于多锁一拍低频)。 | |||||
| * 第一拍:起速>0 用起速;起速=0 用 FirstPulseFreq(≥1Hz 自洽点)。 | |||||
| * 禁止整拍按 1Hz(周期约 1s)开跑,否则会吃光加速相。 | |||||
| */ | */ | ||||
| if ((s_phase == PH_APPROACH) && (s_approach_from >= 1U) && | |||||
| (s_approach_from != s_accel_plan.f_tgt)) | |||||
| { | { | ||||
| uint32_t T_us = s_accel_plan.t_acc_ms * 1000UL; | uint32_t T_us = s_accel_plan.t_acc_ms * 1000UL; | ||||
| uint32_t per = (1000000UL + (s_approach_from / 2UL)) / | |||||
| s_approach_from; | |||||
| uint32_t f2 = PlsrAccelCurveFreqOnRampUs( | |||||
| uint32_t f_start; | |||||
| uint32_t per; | |||||
| uint32_t f2; | |||||
| if (s_phase == PH_CONST) | |||||
| { | |||||
| f_start = PlsrRunControlOutFreq(s_approach_from); | |||||
| if ((s_approach_from == 0U) && | |||||
| (s_accel_plan.f_tgt >= 1U)) | |||||
| { | |||||
| f_start = s_accel_plan.f_tgt; | |||||
| } | |||||
| PlsrRunControlApplyOutFreq(f_start, 1U); | |||||
| } | |||||
| else | |||||
| { | |||||
| f_start = PlsrAccelCurveFirstPulseFreq( | |||||
| s_approach_from, | s_approach_from, | ||||
| s_accel_plan.f_tgt, | s_accel_plan.f_tgt, | ||||
| per, | |||||
| T_us, | T_us, | ||||
| s_accel_plan.mode); | s_accel_plan.mode); | ||||
| if (f2 < 1U) | |||||
| { | |||||
| f2 = 1U; | |||||
| f_start = PlsrRunControlOutFreq(f_start); | |||||
| PlsrRunControlApplyOutFreq(f_start, 1U); | |||||
| per = (1000000UL + (f_start / 2UL)) / f_start; | |||||
| f2 = PlsrAccelCurveFreqOnRampUs( | |||||
| s_approach_from, | |||||
| s_accel_plan.f_tgt, | |||||
| per, | |||||
| T_us, | |||||
| s_accel_plan.mode); | |||||
| f2 = PlsrRunControlOutFreq(f2); | |||||
| PlsrRunControlApplyOutFreq(f2, 0U); | |||||
| PlsrPulseDriverApplyPending(); | |||||
| } | } | ||||
| PlsrRunControlApplyOutFreq(f2, 0U); | |||||
| PlsrPulseDriverApplyPending(); | |||||
| } | } | ||||
| } | } | ||||
| return; | return; | ||||