ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32位置环PID控制:从增量式算法到双环调试实战

STM32位置环PID控制:从增量式算法到双环调试实战 1. 从速度到位置为什么需要位置环在之前的章节里我们花了大量篇幅讨论如何用PID控制电机的转速也就是速度环。速度环的核心是让电机“跑多快”它通过编码器反馈的脉冲频率来计算实时速度并与目标速度比较通过PID运算输出PWM占空比来调节扭矩最终让实际速度跟上目标。这解决了“动起来”的问题。但很多实际应用场景比如机械臂的关节定位、3D打印机的喷头移动、数控机床的刀头进给光“动起来”还不够我们更关心的是“停在哪里”。电机需要精确地从一个位置移动到另一个位置并稳定地停在目标点。这就是位置环要解决的问题。你可以把它想象成开车速度环是控制你踩油门的力度让车保持60km/h而位置环则是导航它告诉你在前方500米处需要右转并精确地控制你在路口白线前停下。位置环的实现通常建立在速度环之上构成一个“串级PID”结构。内环是速度环负责快速响应对抗负载扰动让电机转速听话外环是位置环负责宏观规划计算为了到达目标位置电机“此刻应该以多快的速度运行”。外环的输出就是内环速度环的目标值。这种结构将复杂的定位问题分解内环处理动态响应外环处理静态精度是工程上非常经典且有效的设计。2. 位置环PID的核心增量式与位置式的抉择当我们谈论PID位置环时算法本身依然是比例、积分、微分三项的组合。但根据输出形式的不同主要分为位置式PID和增量式PID。这个选择至关重要直接影响到代码的实现、系统的稳定性和抗积分饱和的能力。2.1 位置式PID直观但需警惕积分饱和位置式PID是最直观的形式。它的输出u(k)直接代表了执行机构应该达到的“绝对位置”在电机控制中通常映射为速度环的目标值。公式为u(k) Kp * e(k) Ki * ∑e(j) Kd * [e(k) - e(k-1)]其中e(k) target_position - current_position即当前时刻的位置偏差。∑e(j)是从开始到当前时刻所有偏差的累加和积分项。u(k)是本次计算出的控制量输出。它的优点是逻辑清晰输出值与目标有直接的对应关系。但缺点也很明显积分饱和Integral Windup这是最大的坑。当系统存在较大偏差且持续一段时间例如电机卡住或者目标位置远超极限积分项会不断累加变成一个巨大的值。即使后来偏差减小了这个巨大的积分值也需要很长时间才能“消化”掉导致系统严重超调甚至振荡反应迟钝。想象一下水龙头你想关小一点但因为之前开得太大阀门被锈住了一时半会儿关不小。全量计算每次输出都依赖于过去所有误差的历史总和对历史数据有记忆性一旦出问题影响是全局的。在电机位置控制中如果直接将位置式PID的输出作为PWM占空比积分饱和问题会非常突出。因此更常见的做法是位置式PID作为外环其输出是“目标速度”然后交给内环的速度环通常是增量式PID去跟踪。这样位置环的积分饱和可以通过对输出值目标速度进行限幅来缓解。2.2 增量式PID更适用于执行机构增量式PID不计算控制量的绝对大小而是计算控制量的增量Δu(k)即本次输出相对于上次输出需要增加或减少多少。公式由位置式推导而来Δu(k) u(k) - u(k-1) Kp*[e(k)-e(k-1)] Ki*e(k) Kd*[e(k)-2e(k-1)e(k-2)]最终本次的实际输出为u(k) u(k-1) Δu(k)它的优点恰好弥补了位置式的缺点抗积分饱和能力强由于输出是增量即使计算过程中积分项很大但每次执行机构只改变Δu(k)的量。一旦偏差符号改变增量立刻反向系统响应迅速不易产生严重的饱和效应。误动作影响小算错了只影响一步增量不会对系统状态产生累积性影响。无需手动积分在代码实现上我们不需要维护一个全局的误差积分和只需要保存最近两次的误差e(k-1),e(k-2)即可程序结构更清晰。在电机控制中速度环几乎无一例外地使用增量式PID因为它直接输出PWM占空比的增量非常适合驱动器的特性。而对于位置环如果它直接输出PWM单环位置控制则也应使用增量式如果是作为外环输出目标速度则两种形式都可以但考虑到与内环的衔接和代码统一性许多工程师也倾向于让位置环采用增量式算法只不过其输出增量是“目标速度”的增量。实操心得在我的STM32项目中我让位置环和速度环都统一使用增量式PID算法。这样我的PID结构体可以复用同一套代码只需要不同的参数。位置环计算出的Δu我将其作为速度环目标值的增量进行累加。这种设计使得代码非常整洁调试时也更容易理解数据流。3. STM32 HAL库下的位置环PID实现与代码拆解理论说完了我们上干货。假设我们已经有一个运行良好的增量式PID速度环参考前文。现在我们要在它外面套上一个位置环。3.1 数据结构定义首先我们定义一个位置环PID的结构体。这里我选择让位置环也使用增量式算法以便和速度环保持结构一致。typedef struct { float TargetPosition; // 目标位置 (单位编码器脉冲数) float CurrentPosition; // 当前位置 (单位编码器脉冲数) float Err; // 当前误差 e(k) float Err_Last; // 上一次误差 e(k-1) float Err_BeforeLast; // 上上次误差 e(k-2)用于微分项 float Kp, Ki, Kd; // PID参数 float Output; // 本次输出值 (即目标速度) float Output_Last; // 上一次输出值 u(k-1) float OutputMax; // 输出限幅最大目标速度 float OutputMin; // 输出限幅最小目标速度通常为负 } PositionPID_TypeDef;关键点解析TargetPosition和CurrentPosition这是位置环的核心。CurrentPosition需要通过编码器接口定时读取并累积。例如在STM32的定时器编码器模式下我们可以读取TIMx-CNT的值并将其换算为累计脉冲数。Output这里的位置环输出我将其定义为目标速度。单位可以是 RPM转/分或者 脉冲数/秒需要与内环速度环的输入单位匹配。输出限幅OutputMax/Min这是防止积分饱和或目标速度突变的关键阀门。必须根据电机的机械特性最高转速和驱动器能力来设置。例如电机最高转速3000RPM这里就可以设为 ±3000。3.2 位置环PID计算函数/** * brief 位置环增量式PID计算 * param pid: 位置环PID结构体指针 * retval 本次计算出的目标速度 */ float PositionPID_Calculate(PositionPID_TypeDef *pid) { float increment 0; // 控制增量 Δu(k) // 1. 计算当前误差 pid-Err pid-TargetPosition - pid-CurrentPosition; // 2. 计算增量式PID increment pid-Kp * (pid-Err - pid-Err_Last) pid-Ki * pid-Err pid-Kd * (pid-Err - 2 * pid-Err_Last pid-Err_BeforeLast); // 3. 计算本次输出 上次输出 增量 pid-Output pid-Output_Last increment; // 4. 输出限幅 —— 至关重要的安全措施 if (pid-Output pid-OutputMax) { pid-Output pid-OutputMax; } else if (pid-Output pid-OutputMin) { pid-Output pid-OutputMin; } // 5. 更新历史误差为下一次计算做准备 pid-Err_BeforeLast pid-Err_Last; pid-Err_Last pid-Err; pid-Output_Last pid-Output; // 保存本次输出用于下次计算 return pid-Output; // 返回作为速度环的目标值 }3.3 系统集成双环控制流程整个系统需要在定时中断比如1ms中循环执行。流程如下// 定时中断服务函数 (例如1ms定时器) void TIMx_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(htimx, TIM_FLAG_UPDATE) ! RESET) { __HAL_TIM_CLEAR_FLAG(htimx, TIM_FLAG_UPDATE); // 1. 位置反馈读取并更新当前位置 // 假设使用32位变量累计编码器计数防止溢出 static int32_t total_encoder_count 0; int16_t current_encoder (int16_t)TIM_ENCODER-CNT; // 读取16位编码器值 TIM_ENCODER-CNT 0; // 清零或使用溢出中断做32位扩展 total_encoder_count current_encoder; g_position_pid.CurrentPosition (float)total_encoder_count; // 2. 运行位置环PID得到目标速度 float target_speed PositionPID_Calculate(g_position_pid); // 3. 更新速度环的目标值 g_speed_pid.Target target_speed; // 速度环PID结构体 // 4. 速度反馈计算当前速度 (例如通过编码器脉冲频率) // 这里简化处理可以用本次脉冲数除以时间间隔得到瞬时速度 float current_speed (float)current_encoder / 0.001f; // 1ms间隔单位 脉冲/秒 g_speed_pid.Current current_speed; // 5. 运行速度环PID得到PWM占空比增量 float pwm_duty_delta SpeedPID_Calculate(g_speed_pid); // 增量式速度环 // 6. 更新实际PWM输出 Update_Motor_PWM(pwm_duty_delta); } }踩坑记录在早期版本中我忘了对位置环的输出目标速度进行限幅。结果当设定一个很远的位置时位置环计算出一个高达数万的目标速度直接喂给速度环导致电机以最大功率猛转产生过冲和剧烈振荡甚至触发驱动器的过流保护。所以双环结构中外环的输出限幅是保证系统平稳启停的第一道保险。4. 位置环PID参数整定从内到外分段调试调试双环PID最忌讳的就是一把抓。必须遵循“先内环后外环”的原则因为内环是外环的基础。4.1 第一步确保速度环独自工作良好断开位置环在代码中暂时固定位置环的输出即速度环的目标值为一个常数比如 100 RPM。整定速度环PID使用前文介绍的方法如试凑法、临界比例度法只调节速度环的Kp_speed,Ki_speed,Kd_speed。目标是让电机能快速、平稳地跟随这个固定速度目标无静差、超调小、抗负载扰动能力强用手捏住转子松开后能快速恢复。验证带宽速度环的响应速度带宽决定了整个系统能达到的最大性能。一个响应迟钝的速度环外面套再好的位置环也快不起来。4.2 第二步接入位置环从纯比例P开始连接位置环将位置环的输出连接到速度环的目标值。将位置环的 Ki 和 Kd 设为 0只保留Kp_position。给定一个阶跃位置信号比如从0到1000脉冲。观察电机运动。如果电机根本不动或蠕动极慢增大Kp_position。位置环P的作用是产生与误差成正比的目标速度。误差大目标速度就大电机就转得快。如果电机开始运动但始终无法到达目标点在目标点附近停下这是纯比例控制的特性——静差。因为当接近目标时误差很小Kp*Err计算出的目标速度可能小于电机启动所需的最小速度或系统静摩擦力对应的速度电机就停在那里了。这说明需要引入积分I来消除静差。如果电机剧烈振荡甚至来回震荡说明Kp_position太大了。过大的P会让系统在目标点附近产生过大的“刹车”速度指令导致冲过头然后反向误差又产生反向速度如此反复。立即减小Kp_position。4.3 第三步引入积分I消除静差在Kp_position调到一个使电机能较快运动但略有静差或轻微振荡的值后逐渐加入一点Ki_position。积分项的作用是累积历史误差。只要还有静差积分值就会不断增长从而“推”着电机继续向目标点移动直到误差为零。积分带来的风险相位滞后导致系统响应变慢超调增大。调Ki时要非常小心一点点加。积分饱和。这就是为什么我们之前强调输出限幅。如果电机因机械限位卡住误差持续存在积分项会疯狂增长。虽然增量式算法能缓解但输出限幅是最后的硬保障。调试目标在保证系统稳定不振荡的前提下静差被消除。可以观察电机最终停止时编码器反馈的位置是否与设定位置一致。4.4 第四步谨慎加入微分D抑制超调位置环的微分项Kd_position作用是对误差的变化趋势进行预测并施加阻尼。当电机快速接近目标点时误差在快速减小微分项会产生一个负的增量相当于提前“刹车”有助于减少超调使定位更平稳。微分项的副作用对噪声极度敏感。编码器反馈如果有毛刺会被微分项放大引起控制量高频抖动。因此在实际使用中往往需要对位置反馈进行低通滤波或者使用“不完全微分”算法。微分太强Kd太大也会导致系统不稳定。我的经验是在许多对超调要求不极致的场合位置环只用PI控制就已经能达到很好的效果。D参数可以暂时设为0除非你确实观察到明显的过冲且通过调整P和I无法解决。4.5 参数整定经验表格下表总结了位置环PID参数调整的典型现象和对策观察现象可能原因调整方向备注响应太慢到达目标位置耗时极长Kp太小缓慢增大Kp优先保证系统稳定慢点没关系。存在静差最终停在离目标点有一段距离的地方纯比例控制固有特性Ki不足或为0缓慢加入Ki从非常小的值开始如0.001观察静差是否缓慢消除。到达目标点后严重超调并产生衰减振荡Kp过大和/或Ki过大先减小Kp 再减小Ki超调是位置控制的大忌宁可慢点也要稳。到达目标点后持续等幅振荡无法稳定参数严重不合理系统不稳定大幅减小Kp和Ki 回到第一步重新调检查硬件连接、编码器反馈是否正确速度环是否本身振荡。电机启动/停止时有“咯噔”冲击感Kd太大或微分项噪声被放大减小Kd 或对位置反馈进行滤波尝试在计算误差前对CurrentPosition进行一阶低通滤波。设定大位置指令时电机初期猛转末期蠕动位置环输出未限幅检查并合理设置OutputMax/Min必做步骤限幅值应略低于速度环能稳定跟踪的最大速度。电机在接近目标时出现低频爬行Ki过大积分作用太强减小Ki积分项在误差很小时仍起主导作用导致控制量变化缓慢电机蠕动。5. 进阶话题与实战避坑指南5.1 编码器计数的处理与溢出这是新手最容易栽跟头的地方之一。STM32的定时器编码器接口通常是16位的CNT寄存器为0~65535。电机正反转会产生加减计数。问题当连续正向旋转超过65535个脉冲后CNT会从0重新开始溢出。如果你直接用CNT值作为位置反馈位置会突然跳变到一个很小的值导致位置环计算出一个巨大的负误差电机疯狂反转解决方案在中断中实现32位或64位的扩展计数。volatile int32_t total_ticks 0; // 32位全局累计值 uint16_t cnt_now TIM2-CNT; // 读取当前16位值 static uint16_t cnt_last 0; int16_t diff (int16_t)(cnt_now - cnt_last); // 注意转为有符号数 total_ticks diff; cnt_last cnt_now; g_position_pid.CurrentPosition (float)total_ticks;通过计算两次中断间的差值diff并累加到total_ticks可以正确处理溢出得到连续的位置信息。diff的计算需要考虑有符号数才能正确处理反转。5.2 目标位置的变化率规划梯形曲线直接给位置环一个阶跃信号从0到10000意味着要求电机瞬间达到目标位置这会导致位置环瞬间输出一个巨大的目标速度受限于限幅电机以最大加速度启动又在终点以最大减速度刹车。这不仅对机械结构冲击大也可能引发振荡。更专业的做法是进行轨迹规划最常用的是梯形速度曲线Trapezoidal Profile。加速段目标位置以预设的加速度逐渐增加。匀速段目标位置以最大规划速度匀速增加。减速段目标位置以预设的减速度逐渐增加直到到达最终目标。这样输入给位置环的TargetPosition是一个平滑变化的曲线而不是阶跃。位置环只需要处理很小的跟踪误差系统运行会平稳得多。实现时可以在主循环或定时器中根据当前时间、最终目标、最大速度、加速度实时计算当前时刻的规划位置。5.3 抗积分饱和Anti-windup的增强措施虽然增量式PID和输出限幅已经大大缓解了积分饱和但在某些严苛条件下仍需加强。一个常见的工程技巧是积分分离或条件积分。思路当误差很大时比如电机刚开始启动积分项容易累积过快此时暂时关闭积分作用Ki0只用PD控制快速减小误差。当误差进入一个较小范围时再开启积分作用消除静差。// 在PID计算函数中加入积分分离逻辑 #define ERROR_THRESHOLD 100.0f // 误差阈值 float effective_Ki pid-Ki; if (fabs(pid-Err) ERROR_THRESHOLD) { effective_Ki 0.0f; // 大误差时取消积分 } increment pid-Kp * (pid-Err - pid-Err_Last) effective_Ki * pid-Err // 使用条件积分系数 pid-Kd * (pid-Err - 2*pid-Err_Last pid-Err_BeforeLast);5.4 调试工具利用串口与图形化上位机“盲调”PID参数是极其低效的。务必搭建调试通道。STM32端在定时中断中将关键变量如TargetPosition,CurrentPosition,Output目标速度, 速度环的Current等存入数组。利用DMA串口定时将数组数据通过串口DMA发送出去避免中断内进行耗时打印。上位机软件使用如SerialPlot、vofa、MATLAB或自己用Python (pySerialmatplotlib) 写的工具实时接收并绘制曲线。观察什么位置跟踪曲线实际位置是否平滑地跟随规划位置或阶跃目标误差曲线误差是否快速收敛到0超调量多大速度环目标值与实际值位置环输出的目标速度是否合理速度环跟踪得好不好通过图形化观察参数调整就从“猜谜”变成了“看图调参”效率提升十倍不止。位置环的引入让我们的电机从简单的“调速工具”升级为了“定位执行器”。它涉及的概念更多调试也更需要耐心和技巧。记住核心内环稳是外环准的基础限幅是安全的保证可视化是调试的捷径。从一个小幅度的阶跃响应开始逐步调整参数观察曲线你就能让电机稳稳地停在你想让它停的任何地方。
返回列表