PID 大师级教程:从公式到工业级闭环控制系统

适合人群:
已经了解 PID 基本公式,想进一步掌握 MCU、电机控制、FOC、机器人底盘、云台、机械臂、温控系统等真实工程中 PID 使用方法的同学。


目录

  • 一、为什么普通 PID 不够用?
  • 二、PID 的三个学习层次
  • 三、连续 PID 与离散 PID
  • 四、采样周期为什么很重要?
  • 五、位置式 PID 与增量式 PID
  • 六、PID 工程优化方法
  • 七、FOC 三环 PID 设计
  • 八、控制带宽与系统稳定性
  • 九、扰动观测器、状态观测器与自适应 PID
  • 十、完整 PID 工程代码
  • 十一、PID 调参与工程排查
  • 十二、总结

摘要

PID 是控制系统中最经典、最常用的控制算法之一。很多人学习 PID 时,第一反应是背公式:

输出 = Kp × 误差 + Ki × 误差积分 + Kd × 误差变化率

但是在真实工程中,尤其是:

  • MCU 控制
  • 电机控制
  • FOC 矢量控制
  • 机器人底盘
  • 云台控制
  • 机械臂控制
  • 温控系统
  • 平衡小车

PID 的难点并不只是公式,而是如何让系统:

稳定
快速
抗干扰
不过冲
不震荡
不发热
不乱抖

普通 PID 关注的是:

Kp、Ki、Kd 怎么调

进阶 PID 关注的是:

限幅、滤波、积分抗饱和、目标平滑、输出变化率限制

而大师级 PID 关注的是:

系统模型
采样周期
控制带宽
串级结构
前馈补偿
扰动观测
状态估计
增益调度
工程鲁棒性

一句话概括:
PID 从入门到大师级的关键,是从“调参数”升级为“设计闭环控制系统”。


一、为什么普通 PID 不够用?

很多初学者会写出下面这种 PID:

error = target - actual;
integral += error;
derivative = error - last_error;

output = kp * error + ki * integral + kd * derivative;

last_error = error;

这段代码看起来没问题,也确实可以在某些简单场景下运行。

但是一旦放到真实硬件上,常见问题就会出现:

常见现象 可能原因
电机来回震荡 Kp 过大、采样周期不稳定
速度忽快忽慢 反馈噪声大、滤波不合理
位置到达目标后还继续冲 积分饱和、目标突变
PWM 长时间顶满 输出未限幅、积分未限幅
电流异常发热 电流环参数过激
编码器反馈抖动导致输出抖动 D 项放大噪声
三环 PID 同时开后互相打架 内外环带宽不匹配

这些问题不是单纯改一个 KpKi 就能彻底解决的。

真实系统里还有很多工程因素:

采样周期
执行器饱和
机械惯量
摩擦死区
传感器噪声
电源电压限制
负载扰动
控制延迟
滤波延迟
内外环带宽匹配

二、PID 的三个学习层次

2.1 基础级 PID

基础级 PID 主要掌握:

P:比例
I:积分
D:微分
位置式 PID
增量式 PID
输出限幅
积分限幅

目标是:

能让系统基本闭环
能让电机速度大致稳定
能让温度接近目标
能让位置大致到达

2.2 进阶级 PID

进阶级 PID 主要掌握:

积分抗饱和
条件积分
积分分离
积分衰减
微分滤波
微分先行
目标斜坡
输出变化率限制
死区补偿
前馈控制
串级 PID

目标是:

系统不乱抖
不过分超调
目标变化更平滑
输出不会长时间顶满
硬件运行更稳定

2.3 大师级 PID

大师级 PID 主要掌握:

系统建模
采样周期设计
带宽设计
相位裕度
增益裕度
串级控制结构
前馈补偿
扰动观测器
状态观测器
增益调度
鲁棒性设计

目标是:

先分析系统,再设计控制结构
先确定带宽,再调 PID 参数
先解决硬件和反馈问题,再谈算法优化

核心思想:
初学者调参数,高手调结构,大师调系统。


三、连续 PID 与离散 PID

3.1 连续 PID 公式

理论上的 PID 是连续时间控制:

u(t) = Kp × e(t) + Ki × ∫e(t)dt + Kd × de(t)/dt

其中:

符号 含义
u(t) 控制输出
e(t) 当前误差
Kp 比例系数
Ki 积分系数
Kd 微分系数

3.2 MCU 中使用的是离散 PID

MCU 程序不是连续运行的,而是周期性运行的。

例如:

控制环 常见周期
FOC 电流环 25us ~ 100us
电机速度环 0.5ms ~ 5ms
位置环 2ms ~ 10ms
温度环 100ms ~ 1000ms
平衡小车角度环 1ms ~ 5ms

所以在 MCU 中需要使用离散 PID:

error = target - actual

integral += error × Ts

derivative = (error - last_error) / Ts

output = Kp × error + Ki × integral + Kd × derivative

其中:

Ts:PID 调用周期,单位是秒

例如 PID 每 10ms 调用一次:

Ts = 0.01

对应代码:

pid->error = pid->target - pid->actual;

pid->integral += pid->error * pid->dt;

pid->derivative = (pid->error - pid->last_error) / pid->dt;

pid->output = pid->kp * pid->error
            + pid->ki * pid->integral
            + pid->kd * pid->derivative;

四、采样周期为什么很重要?

很多人写 PID 时忽略了 dt,直接写:

integral += error;
derivative = error - last_error;

这样的问题是:PID 参数会严重依赖调用频率。

例如:

原来 PID 每 10ms 执行一次
后来改成每 5ms 执行一次

如果代码中没有 dt

积分累加速度会变化
微分项幅值会变化
原来的 PID 参数可能不再稳定

4.1 PID 应该放在哪里运行?

PID 最好放在固定周期任务中:

定时器中断
PWM 中断
ADC 采样完成中断
RTOS 周期任务

不建议直接放在不固定周期的 while(1) 中。

错误写法:

while (1)
{
    PID_Calculate(&pid, target, actual);
}

推荐写法:

void Timer_10ms_Callback(void)
{
    PID_Calculate(&pid, target, actual);
}

注意:
PID 的周期越稳定,参数越容易调,系统越容易稳定。


五、位置式 PID 与增量式 PID

5.1 位置式 PID

位置式 PID 每次直接计算当前控制输出。

公式:

output = Kp × error + Ki × integral + Kd × derivative

代码示例:

float PID_Position_Calc(PID_TypeDef *pid, float target, float actual)
{
    pid->target = target;
    pid->actual = actual;

    pid->error = pid->target - pid->actual;

    pid->integral += pid->error * pid->dt;

    pid->derivative = (pid->error - pid->last_error) / pid->dt;

    pid->output = pid->kp * pid->error
                + pid->ki * pid->integral
                + pid->kd * pid->derivative;

    pid->last_error = pid->error;

    return pid->output;
}

位置式 PID 的特点:

项目 说明
输出含义 当前应该给的控制量
优点 公式直观,容易理解
缺点 积分容易累积过大
适合场景 温度、位置、电流、电压控制

适合场景:

温度控制
位置控制
FOC 电流环
舵机角度控制
电压控制
慢速系统控制

5.2 增量式 PID

增量式 PID 不直接计算最终输出,而是计算本次输出相对上次输出应该增加或减少多少。

公式:

delta_output = Kp × (error - last_error)
             + Ki × error × Ts
             + Kd × (error - 2 × last_error + last_last_error) / Ts

output += delta_output

代码示例:

float PID_Increment_Calc(PID_TypeDef *pid, float target, float actual)
{
    float delta_output;

    pid->target = target;
    pid->actual = actual;

    pid->error = pid->target - pid->actual;

    delta_output = pid->kp * (pid->error - pid->last_error)
                 + pid->ki * pid->error * pid->dt
                 + pid->kd * (pid->error
                             - 2.0f * pid->last_error
                             + pid->last_last_error) / pid->dt;

    pid->output += delta_output;

    pid->last_last_error = pid->last_error;
    pid->last_error = pid->error;

    return pid->output;
}

增量式 PID 的特点:

项目 说明
输出含义 输出变化量
优点 输出变化更平滑
缺点 理解和调试略复杂
适合场景 电机速度、FOC 速度环、执行器平滑控制

5.3 位置式和增量式怎么选?

控制对象 推荐形式 原因
温度控制 位置式 PI/PID 输出直接对应加热功率
电机速度控制 增量式 PI 或位置式 PI 增量式输出更平滑
电机位置控制 位置式 P/PID 根据距离目标多远直接算控制量
FOC 电流环 位置式 PI 直接输出 Vd/Vq
FOC 速度环 增量式 PI 或位置式 PI 输出 Iq 目标
FOC 位置环 位置式 P 输出速度目标
平衡小车角度环 位置式 PD/PID 快速根据角度误差输出控制量

简单记忆:

需要直接算当前控制量:位置式
需要平滑改变输出量:增量式

六、PID 工程优化方法

6.1 输出限幅

真实控制系统中,执行器都有范围。

例如:

PWM:0 ~ 1000
电机正反转 PWM:-1000 ~ 1000
DAC:0 ~ 4095
FOC 电压输出:受母线电压限制
Iq 电流目标:受最大电流限制

所以 PID 输出必须限幅。

通用限幅函数:

static float Limit(float value, float min, float max)
{
    if (value > max)
    {
        return max;
    }

    if (value < min)
    {
        return min;
    }

    return value;
}

使用:

pid->output = Limit(pid->output,
                    pid->output_min,
                    pid->output_max);

工程经验:
没有输出限幅的 PID,在真实项目中是非常危险的。


6.2 积分饱和

积分项的作用是消除稳态误差,但是它也最容易导致问题。

假设:

目标速度:3000 RPM
电机最高只能跑到:2500 RPM
误差长期存在:500 RPM

积分项会不断累加:

integral += error × dt

时间一长,积分会变得非常大。

这就是:

积分饱和

常见表现:

输出长时间顶满
目标变化后恢复很慢
严重超调
系统来回摆动
电机发热
温度超过目标后还继续加热

6.3 积分限幅

最简单的抗积分饱和方法是限制积分项。

pid->integral += pid->error * pid->dt;

pid->integral = Limit(pid->integral,
                      pid->integral_min,
                      pid->integral_max);

这种方法简单可靠,适合大多数普通项目。


6.4 条件积分

条件积分的核心思想:

如果输出已经饱和,而且误差还在推动输出继续饱和,就停止积分。

示例:

if (!((pid->output >= pid->output_max && pid->error > 0.0f) ||
      (pid->output <= pid->output_min && pid->error < 0.0f)))
{
    pid->integral += pid->error * pid->dt;
}

这样可以减少积分项在输出饱和时继续累加。


6.5 积分分离

积分分离的思想是:

误差很大时不积分
误差较小时再积分

原因是:

误差大时,主要靠 P 项快速接近目标;
如果此时积分也大量累加,容易造成超调。

代码示例:

if (fabsf(pid->error) < pid->integral_start_error)
{
    pid->integral += pid->error * pid->dt;
}
else
{
    pid->integral = 0.0f;
}

适合场景:

位置控制
温度控制
目标变化较大的系统
容易超调的系统

6.6 微分滤波

微分项计算的是误差变化率:

derivative = (error - last_error) / dt

如果反馈有噪声,微分项会把噪声放大。

因此可以给 D 项加滤波:

derivative_raw = (error - last_error) / dt;

derivative_filtered = alpha * derivative_filtered
                    + (1.0f - alpha) * derivative_raw;

常用范围:

alpha = 0.7 ~ 0.95

代码:

derivative_raw = (pid->error - pid->last_error) / pid->dt;

pid->derivative = pid->d_filter_alpha * pid->derivative
                + (1.0f - pid->d_filter_alpha) * derivative_raw;

6.7 微分先行

普通微分是对误差微分:

D = Kd × d(error) / dt

但是:

error = target - actual

如果目标值突然变化,error 会突变,D 项也会突变,这叫做:

微分冲击

解决方法:

对反馈值微分,而不是对误差微分。

代码:

derivative = -(actual - last_actual) / dt;

示例:

pid->derivative = -(pid->actual - pid->last_actual) / pid->dt;

pid->output = pid->kp * pid->error
            + pid->ki * pid->integral
            + pid->kd * pid->derivative;

6.8 目标斜坡

很多系统不能接受目标突然变化。

例如电机速度:

0 RPM -> 3000 RPM

如果目标一步到位,误差瞬间很大,输出也会瞬间变大。

可以使用目标斜坡:

float Target_Ramp(float current_target,
                  float final_target,
                  float step)
{
    if (current_target < final_target)
    {
        current_target += step;

        if (current_target > final_target)
        {
            current_target = final_target;
        }
    }
    else if (current_target > final_target)
    {
        current_target -= step;

        if (current_target < final_target)
        {
            current_target = final_target;
        }
    }

    return current_target;
}

目标斜坡可以减少:

启动冲击
超调
电流尖峰
机械冲击

6.9 输出变化率限制

除了限制目标变化,也可以限制输出变化。

float Output_Rate_Limit(float output,
                        float last_output,
                        float max_delta)
{
    if (output - last_output > max_delta)
    {
        output = last_output + max_delta;
    }
    else if (last_output - output > max_delta)
    {
        output = last_output - max_delta;
    }

    return output;
}

适合场景:

电机控制
机械臂控制
云台控制
执行器不能突变的系统

6.10 死区补偿

很多执行器存在死区。

例如直流电机:

PWM 小于 80 时不转
PWM 大于 80 后才开始转

可以加入死区补偿:

int Motor_DeadZone_Compensate(int pwm)
{
    int dead_zone = 80;

    if (pwm > 0 && pwm < dead_zone)
    {
        pwm = dead_zone;
    }
    else if (pwm < 0 && pwm > -dead_zone)
    {
        pwm = -dead_zone;
    }

    return pwm;
}

注意:
死区补偿不是输出限幅,而是为了克服执行器启动门槛。


6.11 前馈控制

PID 是反馈控制,必须看到误差后才会修正。

但是很多系统中,有些控制量可以提前估计。

例如速度控制中:

目标速度越高,所需基础 PWM 越大

可以加入速度前馈:

output = PID_output + Kv × target_speed

代码:

float Speed_Control_With_Feedforward(float target_speed,
                                     float actual_speed)
{
    float feedforward;
    float pid_output;
    float output;

    feedforward = kv * target_speed;

    pid_output = PID_Calculate(&speed_pid,
                               target_speed,
                               actual_speed);

    output = feedforward + pid_output;

    output = Limit(output, -1000.0f, 1000.0f);

    return output;
}

前馈的作用:

减少 PID 压力
提高响应速度
减少稳态误差
减少积分依赖

常见前馈:

速度前馈
加速度前馈
摩擦补偿
重力补偿
电压前馈
转矩前馈

七、FOC 三环 PID 设计

FOC 常见三环:

电流环
速度环
位置环

更准确地说:

Id 电流环
Iq 电流环
Speed 速度环
Position 位置环

推荐组合:

输入误差 输出 推荐控制器 推荐形式
Id 电流环 Id_ref - Id_actual Vd PI 位置式
Iq 电流环 Iq_ref - Iq_actual Vq PI 位置式
速度环 Speed_ref - Speed_actual Iq_ref PI 增量式或位置式
位置环 Pos_ref - Pos_actual Speed_ref P 位置式

7.1 新手推荐组合

位置环:位置式 P
速度环:位置式 PI
电流环:位置式 PI

7.2 工程实用组合

位置环:位置式 P
速度环:增量式 PI
电流环:位置式 PI

7.3 为什么电流环一般用位置式 PI?

FOC 电流环输出的是:

Vd
Vq

它要直接计算当前应该给多少电压指令。

所以电流环通常用位置式 PI:

vd = PI_Position_Calc(&id_pi, id_ref, id_actual);
vq = PI_Position_Calc(&iq_pi, iq_ref, iq_actual);

原因:

1. 电流环频率最高
2. 输出是绝对电压指令
3. PI 已经足够控制电流
4. D 项容易放大电流采样噪声

7.4 为什么速度环适合 PI 或增量式 PI?

速度环输出的是:

Iq_ref

也就是转矩电流目标。

速度环可以用位置式 PI,也可以用增量式 PI。

增量式 PI 更适合需要输出平滑的场景:

本次 Iq_ref = 上次 Iq_ref + 增量

适合原因:

1. 电机是惯性系统
2. Iq 目标不希望突变
3. 增量式输出更平滑
4. 对执行器更友好

7.5 为什么位置环一般先用 P?

位置环输出的是速度目标:

Speed_ref = Kp_pos × Position_error

位置误差大,速度目标大;位置误差小,速度目标小。

如果位置环一开始加 I,很容易:

积分累积
冲过目标
来回摆动
静止时抖动

所以位置环建议:

先用 P
有必要再加很小的 I
超调明显时考虑 D 或轨迹规划

八、控制带宽与系统稳定性

8.1 什么是带宽?

简单理解:

带宽越高,系统响应越快
带宽越低,系统响应越慢

但是带宽不是越高越好。

带宽太高会导致:

噪声放大
输出抖动
相位裕度不足
系统震荡
对采样延迟敏感

8.2 串级控制的带宽原则

串级控制中,带宽要满足:

内环带宽 > 外环带宽

一般原则:

内环带宽至少是外环的 5 ~ 10 倍

例如 FOC:

电流环带宽:1000 Hz
速度环带宽:100 Hz
位置环带宽:10 Hz

或者从控制频率上看:

电流环频率:20 kHz
速度环频率:1 kHz
位置环频率:200 Hz

注意:
控制频率不等于控制带宽,但控制频率必须明显高于带宽。


8.3 延迟与相位裕度

系统中存在各种延迟:

ADC 采样延迟
PWM 更新延迟
计算延迟
滤波延迟
通信延迟
机械响应延迟

延迟会带来相位滞后。

如果相位滞后太大,系统就容易震荡。

这也是为什么:

滤波不能太重
外环不能太快
采样周期不能太长
D 项不能乱加

九、扰动观测器、状态观测器与自适应 PID

9.1 扰动观测器 DOB

PID 是看到误差后才修正。

如果外部负载突然变化,PID 需要等实际值变化后才会反应。

扰动观测器的思想是:

根据模型预测系统应该怎么动
再看实际系统怎么动
两者差异可以认为是扰动
然后反向补偿

常见应用:

负载转矩补偿
摩擦补偿
机器人关节抗扰
速度环抗扰增强
电机低速稳定性提升

简化理解:

PID 负责修正误差
DOB 负责估计扰动
前馈负责提前补偿

9.2 状态观测器

有些状态不能直接测量,或者测量噪声很大。

例如:

速度不直接测量,可以由位置估计
负载转矩不能直接测量,可以用模型估计
电流采样噪声大,可以通过观测器滤波
无感 FOC 中转子位置不能直接测量,需要估计

常见观测器:

Luenberger Observer
Kalman Filter
ESO 扩张状态观测器
滑模观测器
PLL 观测器
磁链观测器
反电动势观测器

9.3 自适应 PID 和增益调度

普通 PID 参数是固定的。

但是系统可能变化:

低速和高速特性不同
轻载和重载特性不同
电池电压会下降
电机温度会上升
摩擦会变化
机械负载会变化

这时可以使用增益调度。

示例:

void PID_Gain_Schedule(PID_TypeDef *pid, float speed)
{
    if (speed < 500.0f)
    {
        pid->kp = 0.8f;
        pid->ki = 0.2f;
        pid->kd = 0.0f;
    }
    else if (speed < 2000.0f)
    {
        pid->kp = 0.5f;
        pid->ki = 0.1f;
        pid->kd = 0.0f;
    }
    else
    {
        pid->kp = 0.3f;
        pid->ki = 0.05f;
        pid->kd = 0.0f;
    }
}

适合场景:

宽速度范围电机控制
负载变化大的系统
非线性明显的系统
低速和高速表现差异大的系统

十、完整 PID 工程代码

10.1 PID 结构体设计

typedef struct
{
    float kp;
    float ki;
    float kd;

    float dt;

    float target;
    float actual;
    float last_actual;

    float error;
    float last_error;
    float last_last_error;

    float integral;
    float derivative;

    float output;
    float last_output;

    float output_min;
    float output_max;

    float integral_min;
    float integral_max;

    float integral_start_error;

    float d_filter_alpha;

    float output_max_delta;

} PID_TypeDef;

10.2 PID 初始化函数

void PID_Init(PID_TypeDef *pid,
              float kp,
              float ki,
              float kd,
              float dt,
              float output_min,
              float output_max,
              float integral_min,
              float integral_max)
{
    pid->kp = kp;
    pid->ki = ki;
    pid->kd = kd;

    pid->dt = dt;

    pid->target = 0.0f;
    pid->actual = 0.0f;
    pid->last_actual = 0.0f;

    pid->error = 0.0f;
    pid->last_error = 0.0f;
    pid->last_last_error = 0.0f;

    pid->integral = 0.0f;
    pid->derivative = 0.0f;

    pid->output = 0.0f;
    pid->last_output = 0.0f;

    pid->output_min = output_min;
    pid->output_max = output_max;

    pid->integral_min = integral_min;
    pid->integral_max = integral_max;

    pid->integral_start_error = 999999.0f;

    pid->d_filter_alpha = 0.8f;

    pid->output_max_delta = 999999.0f;
}

10.3 完整位置式 PID

#include <math.h>

static float Limit(float value, float min, float max)
{
    if (value > max)
    {
        return max;
    }

    if (value < min)
    {
        return min;
    }

    return value;
}

static float Rate_Limit(float value,
                        float last_value,
                        float max_delta)
{
    if (value - last_value > max_delta)
    {
        value = last_value + max_delta;
    }
    else if (last_value - value > max_delta)
    {
        value = last_value - max_delta;
    }

    return value;
}

float PID_Position_Calc(PID_TypeDef *pid,
                        float target,
                        float actual)
{
    float derivative_raw;
    float output_unsat;

    pid->target = target;
    pid->actual = actual;

    pid->error = pid->target - pid->actual;

    if (fabsf(pid->error) <= pid->integral_start_error)
    {
        output_unsat = pid->kp * pid->error
                     + pid->ki * pid->integral
                     + pid->kd * pid->derivative;

        if (!((output_unsat >= pid->output_max && pid->error > 0.0f) ||
              (output_unsat <= pid->output_min && pid->error < 0.0f)))
        {
            pid->integral += pid->error * pid->dt;

            pid->integral = Limit(pid->integral,
                                  pid->integral_min,
                                  pid->integral_max);
        }
    }

    derivative_raw = (pid->error - pid->last_error) / pid->dt;

    pid->derivative = pid->d_filter_alpha * pid->derivative
                    + (1.0f - pid->d_filter_alpha) * derivative_raw;

    pid->output = pid->kp * pid->error
                + pid->ki * pid->integral
                + pid->kd * pid->derivative;

    pid->output = Limit(pid->output,
                        pid->output_min,
                        pid->output_max);

    pid->output = Rate_Limit(pid->output,
                             pid->last_output,
                             pid->output_max_delta);

    pid->last_error = pid->error;
    pid->last_actual = pid->actual;
    pid->last_output = pid->output;

    return pid->output;
}

10.4 微分先行 PID

float PID_Position_Calc_D_On_Measurement(PID_TypeDef *pid,
                                         float target,
                                         float actual)
{
    float derivative_raw;

    pid->target = target;
    pid->actual = actual;

    pid->error = pid->target - pid->actual;

    pid->integral += pid->error * pid->dt;

    pid->integral = Limit(pid->integral,
                          pid->integral_min,
                          pid->integral_max);

    derivative_raw = -(pid->actual - pid->last_actual) / pid->dt;

    pid->derivative = pid->d_filter_alpha * pid->derivative
                    + (1.0f - pid->d_filter_alpha) * derivative_raw;

    pid->output = pid->kp * pid->error
                + pid->ki * pid->integral
                + pid->kd * pid->derivative;

    pid->output = Limit(pid->output,
                        pid->output_min,
                        pid->output_max);

    pid->last_error = pid->error;
    pid->last_actual = pid->actual;
    pid->last_output = pid->output;

    return pid->output;
}

10.5 完整增量式 PID

float PID_Increment_Calc(PID_TypeDef *pid,
                         float target,
                         float actual)
{
    float delta_output;

    pid->target = target;
    pid->actual = actual;

    pid->error = pid->target - pid->actual;

    delta_output = pid->kp * (pid->error - pid->last_error)
                 + pid->ki * pid->error * pid->dt
                 + pid->kd * (pid->error
                             - 2.0f * pid->last_error
                             + pid->last_last_error) / pid->dt;

    pid->output += delta_output;

    pid->output = Limit(pid->output,
                        pid->output_min,
                        pid->output_max);

    pid->output = Rate_Limit(pid->output,
                             pid->last_output,
                             pid->output_max_delta);

    pid->last_last_error = pid->last_error;
    pid->last_error = pid->error;
    pid->last_actual = pid->actual;
    pid->last_output = pid->output;

    return pid->output;
}

十一、PID 调参与工程排查

11.1 PID 调参顺序

单环 PID:

先 P
再 I
最后 D

串级 PID:

先内环
再外环

FOC:

先电流环
再速度环
最后位置环

11.2 单环 PID 调参方法

第一步:只开 P

设置:

Ki = 0
Kd = 0

逐渐增大 Kp。

现象 说明
响应慢 Kp 太小
响应较快且不震荡 Kp 较合适
明显震荡甚至发散 Kp 太大

第二步:加入 I

逐渐增大 Ki。

现象 说明
有稳态误差 Ki 太小
最终能到目标 Ki 较合适
超调严重、恢复慢 Ki 太大

第三步:加入 D

如果系统超调明显,可以加少量 D。

现象 说明
抑制不明显 Kd 太小
超调减小 Kd 合适
输出抖动 Kd 太大或噪声大

11.3 PID 参数现象对照表

现象 可能原因 调整方向
响应太慢 Kp 太小 增大 Kp
一直不到目标 Ki 太小 增大 Ki
超调严重 Kp 或 Ki 太大 降低 Kp/Ki
来回震荡 Kp 太大、Ki 太大 降 Kp/Ki
输出抖动 Kd 太大或噪声大 降 D,加滤波
输出长时间顶满 积分饱和 加积分限幅
越调越远 方向反了 检查误差方向
到目标附近抖动 死区、噪声、Kp 太大 加死区/滤波/降 Kp
目标变化冲击大 目标突变 加目标斜坡

11.4 工程调试 Checklist

1. 反馈方向是否正确?

目标增大后,实际值是否朝目标方向变化?
如果越调越远,大概率方向反了。

2. 反馈值是否可靠?

编码器速度是否正常?
ADC 是否抖动?
角度是否跳变?
电流采样是否有偏置?

3. PID 周期是否固定?

不要在不固定周期的 while(1) 中直接跑 PID。
优先使用定时器中断或 RTOS 周期任务。

4. 输出是否限幅?

PWM 范围是多少?
电压输出范围是多少?
Iq 最大允许值是多少?
速度目标最大值是多少?

5. 积分是否抗饱和?

积分有没有限幅?
输出饱和后还会不会继续积分?
目标切换后积分是否需要清零?

6. 内外环频率是否合理?

内环必须比外环快。
外环输出不能超过内环能力。

十二、PID 工程文件组织建议

推荐结构:

Core
 ├── Inc
 │   ├── pid.h
 │   ├── motor.h
 │   ├── encoder.h
 │   ├── current_sample.h
 │   └── control.h
 │
 └── Src
     ├── pid.c
     ├── motor.c
     ├── encoder.c
     ├── current_sample.c
     └── control.c

模块职责:

文件 职责
pid.c 只负责 PID 算法
motor.c 只负责 PWM、方向、电压输出
encoder.c 只负责位置和速度反馈
current_sample.c 只负责电流采样
control.c 组织控制逻辑

建议:
不要让 PID 模块直接操作硬件,这样更方便移植和维护。


十三、从 PID 到更高级控制

当 PID 已经调得比较熟练后,可以继续学习:

1. 前馈控制
2. 扰动观测器 DOB
3. 状态观测器
4. 卡尔曼滤波
5. ADRC 自抗扰控制
6. LQR 控制
7. MPC 模型预测控制
8. 滑模控制
9. 无感 FOC 观测器

但是要注意:

高级控制不是为了炫技,而是为了解决 PID 难以解决的问题。

在很多工业场景中,最常见的仍然是:

PID + 前馈 + 限幅 + 滤波 + 保护 + 经验调参

十四、总结

PID 从基础到大师级,不是公式越来越复杂,而是控制思维越来越完整。

阶段 关注重点
基础级 PID Kp、Ki、Kd
进阶级 PID 限幅、滤波、积分抗饱和、目标平滑
高手级 PID 串级结构、前馈补偿、控制周期
大师级 PID 系统模型、控制带宽、扰动观测、工程鲁棒性

真正优秀的 PID 工程师,不是只会调参数,而是知道:

什么时候该调参数
什么时候该加限幅
什么时候该滤波
什么时候该做前馈
什么时候该换控制结构
什么时候问题根本不在 PID

最后用一句话总结:

初学者调 Kp、Ki、Kd,进阶者调限幅和滤波,高手调串级结构,大师先理解系统再设计控制器。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐