位置式PID与增量式PID的代码实现与电机控制实战
📅 2026/7/15 23:22:59
👁️ 阅读次数
📝 编程学习
1. PID控制基础与电机控制场景
PID控制是工业控制领域最经典的算法之一,它通过比例(P)、积分(I)、微分(D)三个环节的组合,实现对系统的精确控制。在电机控制中,PID算法能够有效解决速度波动、负载变化等问题。比如当电机负载突然增大时,传统开环控制会导致转速下降,而闭环PID控制能自动调整PWM输出,维持转速稳定。
实际项目中,我常用STM32的定时器模块实现PID控制。以直流有刷电机为例,需要配置以下硬件资源:
- 编码器接口:通过TIMx的编码器模式读取脉冲数(如500线编码器每转产生2000个脉冲)
- PWM输出:使用TIMx的PWM模式生成驱动信号(通常10kHz-20kHz频率)
- 定时中断:用于固定周期的PID计算(典型周期1-10ms)
2. 位置式PID的代码实现
位置式PID的特点是输出值与执行机构位置一一对应,适合需要绝对控制的场景。其离散化公式为:
u(k) = Kp*e(k) + Ki*∑e(j) + Kd*[e(k)-e(k-1)]在STM32中的实现步骤如下:
2.1 定义PID结构体
typedef struct { float Target; // 目标值 float Current; // 当前值 float Err; // 当前误差 float Err_Last; // 上次误差 float Kp,Ki,Kd; // PID系数 float Integral; // 积分项 float Output; // 输出值 uint32_t Timestamp; // 时间戳(用于微分项计算) } PositionalPID;2.2 PID核心算法
float PositionalPID_Update(PositionalPID *pid, float current_val) { // 计算时间间隔(单位:秒) float dt = (HAL_GetTick() - pid->Timestamp) / 1000.0f; pid->Timestamp = HAL_GetTick(); // 误差计算 pid->Err = pid->Target - current_val; // 积分项(带抗饱和处理) pid->Integral += pid->Err * dt; if(pid->Integral > INTEGRAL_MAX) pid->Integral = INTEGRAL_MAX; else if(pid->Integral < -INTEGRAL_MAX) pid->Integral = -INTEGRAL_MAX; // 微分项 float derivative = (pid->Err - pid->Err_Last) / dt; // PID输出 pid->Output = pid->Kp * pid->Err + pid->Ki * pid->Integral + pid->Kd * derivative; // 保存本次误差 pid->Err_Last = pid->Err; return pid->Output; }2.3 定时器中断集成
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim6) { // 10ms定时器 // 读取编码器值(位置反馈) int32_t encoder_cnt = TIM4->CNT; TIM4->CNT = 0; // 计数器清零 // 位置式PID计算 float pwm = PositionalPID_Update(&pos_pid, encoder_cnt); // PWM输出限幅 pwm = (pwm > 1000) ? 1000 : (pwm < -1000) ? -1000 : pwm; // 更新PWM输出 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, abs(pwm)); HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, (pwm >= 0) ? GPIO_PIN_SET : GPIO_PIN_RESET); } }3. 增量式PID的代码实现
增量式PID输出的是控制量的增量,特别适合步进电机等执行机构。其公式为:
Δu(k) = Kp*[e(k)-e(k-1)] + Ki*e(k) + Kd*[e(k)-2e(k-1)+e(k-2)]3.1 增量式PID结构体
typedef struct { float Target; // 目标值 float Current; // 当前值 float Err; // 当前误差 float Err_Last; // 上次误差 float Err_BeforeLast; // 上上次误差 float Kp,Ki,Kd; // PID系数 float Output; // 输出增量 } IncrementalPID;3.2 算法实现
float IncrementalPID_Update(IncrementalPID *pid, float current_val) { // 误差计算 pid->Err = pid->Target - current_val; // 计算增量 pid->Output = pid->Kp * (pid->Err - pid->Err_Last) + pid->Ki * pid->Err + pid->Kd * (pid->Err - 2*pid->Err_Last + pid->Err_BeforeLast); // 更新误差记录 pid->Err_BeforeLast = pid->Err_Last; pid->Err_Last = pid->Err; return pid->Output; }3.3 速度环控制实例
void SpeedControlLoop(void) { static float total_pwm = 0; // 获取速度值(脉冲数/采样周期) int16_t speed = (int16_t)TIM4->CNT; TIM4->CNT = 0; // 增量PID计算 float delta_pwm = IncrementalPID_Update(&inc_pid, speed); // 累计输出 total_pwm += delta_pwm; // 输出限幅 total_pwm = (total_pwm > 1000) ? 1000 : (total_pwm < -1000) ? -1000 : total_pwm; // 更新PWM __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, abs(total_pwm)); HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, (total_pwm >= 0) ? GPIO_PIN_SET : GPIO_PIN_RESET); }4. 两种PID算法的对比与选型
4.1 性能对比表格
| 特性 | 位置式PID | 增量式PID |
|---|---|---|
| 输出性质 | 绝对量 | 增量 |
| 积分处理 | 需要累积误差 | 无累积误差 |
| 抗饱和 | 需额外处理 | 天然抗饱和 |
| 计算量 | 较大 | 较小 |
| 适用场景 | 位置控制、阀门控制 | 速度控制、步进电机 |
| 对突变的响应 | 可能产生大幅跳动 | 变化平缓 |
4.2 选型建议
位置式PID更适合:
- 需要精确位置控制的场景(如机械臂关节)
- 执行机构不带积分部件(如伺服阀)
- 系统允许较大的计算开销
增量式PID更适合:
- 速度控制场景(如直流电机调速)
- 执行机构本身具有积分特性(如步进电机)
- 需要避免积分饱和的场合
- MCU资源有限的场景
5. PID参数整定实战技巧
5.1 试凑法步骤
- 先调P:将Ki和Kd设为0,逐渐增大Kp直到系统出现等幅振荡
- 再调I:取振荡周期的一半作为积分时间,Ki=Kp*(采样周期/Ti)
- 最后调D:一般取Td=Ti/4,Kd=Kp*(Td/采样周期)
- 微调:根据响应曲线对三个参数进行10%左右的调整
5.2 常见问题处理
- 振荡过大:减小Kp或增大Kd
- 响应迟缓:增大Kp或减小Ki
- 稳态误差:适当增大Ki
- 超调严重:增大Kd或减小Kp
5.3 自动整定代码示例
void PID_AutoTune(PID *pid, float *input, float *output) { static uint8_t state = 0; static float peak[4] = {0}; static uint32_t last_cross = 0; // 注入阶跃信号 if(HAL_GetTick() < 2000) *output = 100; else *output = 0; // 检测振荡波形 if(*input > pid->Target * 1.05) { if(state == 0 || state == 2) { peak[state] = *input; state++; } } else if(*input < pid->Target * 0.95) { if(state == 1) { peak[state] = *input; state++; last_cross = HAL_GetTick(); } else if(state == 3) { peak[state] = *input; state = 0; // 计算参数 float Ku = 4*100/(3.1415*(peak[0]-peak[1])); float Tu = (HAL_GetTick() - last_cross) / 1000.0f; // Ziegler-Nichols法 pid->Kp = 0.6 * Ku; pid->Ki = 1.2 * Ku / Tu; pid->Kd = 0.075 * Ku * Tu; } } }6. 高级优化技巧
6.1 积分抗饱和
// 在位置式PID计算中加入 if(fabs(pid->Output) > OUTPUT_MAX) { pid->Integral -= pid->Err * dt; // 回退积分 }6.2 微分滤波
// 一阶低通滤波 float alpha = 0.2f; // 滤波系数 derivative = alpha * derivative + (1-alpha) * pid->LastDerivative; pid->LastDerivative = derivative;6.3 变参数PID
// 根据误差大小切换参数 if(fabs(pid->Err) > 50) { // 大误差区间 pid->Kp = 5.0; pid->Ki = 0.1; } else { // 小误差区间 pid->Kp = 2.0; pid->Ki = 0.5; }7. 电机控制中的特殊处理
7.1 死区补偿
当PWM值较小时,电机可能无法启动:
if(fabs(pwm) < DEAD_ZONE) { pwm = (pwm > 0) ? DEAD_ZONE : -DEAD_ZONE; }7.2 速度测量滤波
#define FILTER_LEN 5 static float speed_buf[FILTER_LEN] = {0}; // 滑动平均滤波 float Filter_Speed(float new_speed) { static uint8_t idx = 0; speed_buf[idx++] = new_speed; if(idx >= FILTER_LEN) idx = 0; float sum = 0; for(uint8_t i=0; i<FILTER_LEN; i++) { sum += speed_buf[i]; } return sum / FILTER_LEN; }7.3 串级PID实现
位置环+速度环的双闭环控制:
void CascadePID_Update(void) { // 外环(位置环) float speed_target = PositionalPID_Update(&pos_pid, encoder_cnt); // 内环(速度环) float current_speed = Filter_Speed((float)TIM4->CNT); TIM4->CNT = 0; float pwm = IncrementalPID_Update(&speed_pid, current_speed); // 更新输出 __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, abs(pwm)); }
编程学习
技术分享
实战经验