STM32增量式PID实战:从电机调速到参数整定全解析

发布时间:2026/7/15 22:44:11
STM32增量式PID实战:从电机调速到参数整定全解析 1. 增量式PID控制基础与公式推导我第一次接触增量式PID是在大四的机器人比赛上当时用STM32F103控制直流电机转速总是出现抖动导师一句试试增量式PID让我打开了新世界的大门。和传统的位置式PID相比增量式PID最大的特点是只输出控制量的变化值这对电机控制来说简直是天作之合。先看经典的位置式PID公式u(k) Kp*e(k) Ki*∑e(j) Kd*[e(k)-e(k-1)]而增量式PID通过对相邻时刻控制量的差分得到Δu(k) Kp*[e(k)-e(k-1)] Ki*e(k) Kd*[e(k)-2e(k-1)e(k-2)]这个推导过程很有意思用第k时刻的位置式输出减去第k-1时刻的输出消去了误差累加项。实际使用时需要记住u(k) u(k-1) Δu(k)我在调试四轴飞行器时发现增量式PID对电机堵转有天然的抗干扰能力。因为输出的是增量即使某次计算出错也不会造成灾难性后果。2. STM32硬件平台搭建2.1 电机驱动电路设计去年给学校智能车比赛做指导时看到有队伍用L298N驱动电机结果PWM频率只有1kHz导致电机发热严重。我的经验是有刷电机PWM频率建议8-16kHzMOSFET驱动芯片推荐DRV8871峰值电流3.6A一定要加续流二极管曾经烧过3个电机驱动板才长记性2.2 编码器接口配置STM32的定时器编码器模式真是神器以TIM4为例// 编码器接口配置 TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetCounter(TIM4, 0); TIM_Cmd(TIM4, ENABLE);实测发现1000线编码器在1500RPM时16MHz主频的STM32F103会丢脉冲解决方法是用TIMx_CR1的CKD分频或者换更高主频的型号2.3 PWM输出配置建议使用互补输出模式死区时间根据MOSFET参数设置TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; // 初始占空比0 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure);3. C语言实现细节3.1 数据结构设计这个结构体我优化过5个版本最终定型如下typedef struct { float Target; // 目标值 float Current; // 当前值 float Kp, Ki, Kd;// PID参数 float Error[3]; // 误差队列 e[k],e[k-1],e[k-2] float Output; // 当前输出 float OutputMax; // 输出限幅 uint16_t SampleTime; // 采样周期(ms) } PID_IncTypeDef;3.2 核心算法实现最关键的增量计算函数float PID_IncCalc(PID_IncTypeDef *pid) { float dError pid-Error[0] - pid-Error[1]; float ddError pid-Error[0] - 2*pid-Error[1] pid-Error[2]; float delta pid-Kp * dError pid-Ki * pid-Error[0] pid-Kd * ddError; // 更新误差队列 pid-Error[2] pid-Error[1]; pid-Error[1] pid-Error[0]; // 输出限幅 float new_output pid-Output delta; if(new_output pid-OutputMax) new_output pid-OutputMax; if(new_output 0) new_output 0; pid-Output new_output; return pid-Output; }3.3 定时中断集成在1ms定时中断中调用void TIM2_IRQHandler(void) { static uint16_t count 0; if(TIM_GetITStatus(TIM2, TIM_IT_Update)) { if(count motor_pid.SampleTime) { count 0; motor_pid.Current Encoder_GetSpeed(); motor_pid.Error[0] motor_pid.Target - motor_pid.Current; PWM_SetDuty(PID_IncCalc(motor_pid)); } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }4. PID参数整定实战技巧4.1 试凑法三步走去年调试物流机器人时总结的步骤纯比例调节Ki0,Kd0逐渐增大Kp直到系统出现等幅振荡记录临界增益Ku和振荡周期Tu取Kp 0.6*Ku加入积分环节初始值Ti 0.5*Tu观察消除静差的速度适当调整Ti加入微分环节初始值Td 0.125*Tu观察超调量微调Td4.2 典型现象分析振荡发散Kp过大或Kd过小响应迟缓Kp过小或Ti过大静差消除慢Ki过小超调过大Kd需要增大4.3 自动整定思路给系统加阶跃信号通过Ziegler-Nichols方法自动计算参数void PID_AutoTune(PID_IncTypeDef *pid) { float Ku 0, Tu 0; // ...自动获取Ku和Tu的代码 pid-Kp 0.6 * Ku; pid-Ki 2 * pid-Kp / Tu; pid-Kd pid-Kp * Tu / 8; }5. 常见问题排查指南5.1 电机转速波动大上周刚解决的案例检查编码器接线是否松动曾因接触不良导致速度跳变降低PWM频率从20kHz降到8kHz后纹波减小增加软件滤波采用滑动平均滤波5.2 响应速度慢检查采样周期是否过长建议10-50ms确认电机供电电压是否充足用示波器看PWM波形尝试增大Kp同时适当减小Ki5.3 超调严重我的调参笔记记录先确保Kp不过大适当增大Kd但过大会导致响应变慢加入设定值变化率限制软启动6. 进阶优化策略6.1 抗积分饱和在结构体中增加积分限幅if(fabs(pid-Error[0]) 50) { // 误差过大时不积分 delta - pid-Ki * pid-Error[0]; }6.2 动态参数调整根据误差大小自动调节参数float error_abs fabs(pid-Error[0]); if(error_abs 100) { pid-Kp 0.8; pid-Ki 0; } else { pid-Kp 0.5; pid-Ki 0.2; }6.3 串口调试接口添加参数实时修改功能void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { char cmd USART_ReceiveData(USART1); if(cmd P) motor_pid.Kp 0.1; else if(cmd p) motor_pid.Kp - 0.1; // 其他参数类似... } }最后分享一个真实案例在智能窗帘项目中电机负载会随窗帘位置变化。最终采用Kp分段动态积分限幅的方案使定位精度控制在±2mm内。调试过程中用Excel记录了几百组参数组合最终明白了一个道理PID调参既是科学也是艺术需要耐心更需要经验积累。