跳转至

Chapter0 PID

PID介绍

PID是Proportional(比例)、Integral(积分)、Differential(微分)的首字母缩写; 是一种结合比例、积分和微分三种环节于一体的闭环控制算法,它是目前为止在连续控制系统中计数最为成熟的一种控制算法;

在工业过程控制中,按被控对象的 实时数据采集值y(t) 与 给定值r(t) 比较产生的 误差e(t) 的比例、积分和微分进行控制。PID控制具有原理简单,鲁棒性强和实用面广等优点,是一种技术成熟、应用最为广泛的控制系统。

数字PID(离散PID),结合计算机的计算与逻辑功能,不但继承了PID控制器的这些特点,而且由于软件系统的灵活性,使PID算法可以得到修正更加完善,变得更加灵活多样,更能满足生产过程中提出的多种控制要求。在实际应用中,可以根据被控制对象的特性和控制要求灵活地改变其结构,取其中一部分环节构成控制系统。如P比例控制、PI比例积分控制、PD比例微分控制等。

下述中,我们将 error(t) 即误差随时间的变化关系,简写为 e(t)

PID算法

\(u(t) = K_p \left[ e(t) + \frac{1}{T_i} \int_0^t e(t) dt + T_d \frac{de(t)}{dt} \right]\)

\(u(t) = K_p e(t) + K_i \int_0^t e(t) dt + K_d \frac{de(t)}{dt}\)

\(u(t) = K_p 误差 + K_i 面积 + K_d 斜率\)

  • Ti——积分时间常数
  • Td——微分时间常数
  • u(t)——PID控制器的输出信号,关于时间t的函数
  • e(t)——error(t),给定值r(t)与测量值y(t)之差,关于时间t的函数

比例P(Kp)

比例控制是最简单的一种控制方式,成比例的反应控制系统中输入与输出的偏差信号,只要偏差一旦产生,就立即产生控制的作用来减小产生的误差。

比例控制器的输出与输入成正比关系,能够迅速的反应偏差,偏差减小的速度取决于比例系数Kp,Kp越大偏差减小的就越快,但是极易引起震荡(即微量误差被放大)。Kp减小发生震荡的可能性减小(即微量误差作用小),但是调节的速度变慢。

单纯的比例控制存在不能消除的静态误差,这里就需要积分来控制。

积分I(Ki)

在比例控制环节产生了静态误差,在积分环节中,主要用于就是消除静态误差提高系统的无差度。积分作用的强弱,取决于积分时间常数Ti, Ti越大积分作用越弱,反之则越强。

积分控制作用的存在与偏差e(t)的存在时间有关,只要系统存在着偏差,积分环节就会不断起作用,对输入偏差进行积分,使控制器的输出及执行器的开度不断变化,产生控制作用以减小偏差。

在积分时间足够的情况下,可以完全消除静差,这时积分控制作用将维持不变。 Ti越小,积分速度越快,积分作用越强。积分作用太强会使系统超调加大,甚至使系统出现振荡。

微分D(Kd)

虽然积分环节可以消除静态误差但是降低了系统的响应速度,所以引入微分控制器就显得很有必要,尤其是具有较大惯性的被控对象使用PI控制器很难得到很好的动态调节品质,系统会产生较大的超调和振荡,这时可以引入微分作用。

微分环节的作用是反应系统偏差的一个变化趋势,也可以说是变化率,可以在误差来临之前提前引入一个有效的修正信号,有利于提高输出响应的快速性,减小被控量的超调和增加系统的稳定性。

在偏差刚出现或变化的瞬间,不仅根据偏差量作出及时反应(即比例控制作用), 还可以根据偏差量的变化趋势(速度)提前给出较大的控制作用(即微分控制作用),将偏差消灭在萌芽状态, 这样可以大大减小系统的动态偏差和调节时问,使系统的动态调节品质得以改善。

微分环节有助于系统减小超调,克服振荡,加快系统的响应速度,减小调节时间,从而改善了系统的动态性能,但微分时间常数过大,会使系统出现不稳定。

微分控制作用一个很大的缺陷是容易引入高频噪声,所有在干扰信号比较严重的流量控制系统中不宜引入微分控制作用。

离散化PID算法

由于计算机的运算是离散的,要想实现数字PID控制首先需要将连续函数进行离散化。

如图,PID控制即对偏差的控制,如果偏差为0,则比例环节不起作用,只有存在偏差时,比例环节才起作用。

积分环节主要是用来消除静差,所谓静差,就是系统稳定后输出值和设定值之间的差值,积分环节实际上就是偏差累计的过程,把累计的误差加到原有系统上以抵消系统造成的静差;

而微分信号则反应了偏差信号的变化规律,也可以说是变化趋势,根据偏差信号的变化趋势来进行超前调节,从而增加了系统的预知性;

\(u(t) = K_p \left[ e(t) + \frac{1}{T_i} \int_0^t e(t) dt + T_d \frac{de(t)}{dt} \right]\)

  • 假设采集数据的间隔时间为T,则在第 k 时刻有:
  • 误差等于第k个周期时刻的误差等于输入(目标)值减输出(实际)值,则有: \(r_{in}(k)-r_{out}(k) = e(k)\)
  • 积分环节为所有时刻的误差和,则有: \(e(k)+e(k-1)+e(k-2)+…+e(1)= \sum_{i=1}^{k} e(i)\)
  • 微分环节为第k时刻误差的变化率,则有:\(\frac{e(k)-e(k-1)}{T}\)

\(u(k) = K_{p} \left[e(k) + \frac{T}{T_{i}} \sum_{i=1}^{k} e(i) + T_d \frac{e(k) - e(k-1)}{T} \right]\)

位置式PID

通过上面的推导,我们就获得了第一个离散PID算法,即位置式PID

\(u(k)=K_{p}e(k)+K_{i}\sum_{i=1}^{k} e(i)+K_{d}[e(k)-e(k-1)]\)

  • k为采样的序号
  • e(k)为第k次的误差
  • u(k)为输出量

增量式PID

接下来只需要两步,就可以得到第二个离散PID算法,即增量式PID

第一步,将k-1代入k得:\(u(k-1)=K_{p}e(k-1)+K_{i}\sum_{i=1}^{k-1} e(i)+K_{d}[e(k-1)-e(k-2)]\)

第二步,由 Δu(k) = u(k) - u(k-1)得:

\(\Delta u(k) = K_{p} (e(k) - e(k-1)) + K_{i}e(k) + K_{d} [e(k) - 2e(k-1) + e(k-2)]\)

  • \(\sum_{i=1}^{k} e(i)- \sum_{i=1}^{k-1} e(i)= e(k)\)
  • \(u(k) = u(k-1) + \Delta u(k)\)

位置式代码示例

typedef struct
{
    // 1 PID核心参数(需根据实际系统调试)
    float kp;       // 比例系数
    float ki;       // 积分系数
    float kd;       // 微分系数

    // 2 偏差相关变量
    float target;   // 目标值(期望值)
    float feedback; // 反馈值(实际测量值)
    float err;      // 当前偏差 e(k) = target - feedback
    float err_prev; // 上一次偏差 e(k-1)
    float err_sum;  // 偏差积分累积 sum(e(0)~e(k))

    // 3 输出限制(防止积分饱和、硬件过载)
    float out_max;  // 输出最大值(如PWM占空比上限1000)
    float out_min;  // 输出最小值(如下限0)
    float sum_max;  // 积分限幅最大值
    float sum_min;  // 积分限幅最小值

    // 4 可选功能:死区阈值(小于该偏差时不响应,避免抖动)
    float dead_zone;

    // 5 PID输出结果
    float output;   // 最终输出 u(k)
}pid_st;

float positional_pid(pid_st *pid, float target_val, float feedback_val)
{
    // 空指针保护
    if (pid == NULL) return 0.0f;

    // 计算当前偏差
    pid->target = target_val;
    pid->feedback = feedback_val;
    pid->err = pid->target - pid->feedback;

    // 死区处理(偏差小于阈值时,输出0,避免微小抖动)
    if (fabs(pid->err) < pid->dead_zone) 
    {
        pid->err = 0.0f;
    }

    // 积分分离(误差较大时去掉积分项作用)
    if(fabs(pid->err) < pid->out_max/2)
    {
        // 积分,累加
        pid->sum += pid->err;
        // 积分限幅,防止积分饱和
        if(pid->sum > sum_max)
        {
            pid->sum = sum_max;
        }
        else if(pid->sum < sum_min)
        {
            pid->sum = sum_min;
        }
    }

    // 计算原始输出
    pid->output = (pid->kp * pid->err)                     /* 比例环节 */
                + (pid->ki * pid->err_sum)                  /* 积分环节 */
                + (pid->kd * (pid->err - pid->err_prev));   /* 微分环节 */

    // 传递误差
    pid->err_prev = pid->err;

    // 输出限幅
    if (pid->output > pid->out_max)
    {
        pid->output = pid->out_max;
    } 
    else if (pid->output < pid->out_min)
    {
        pid->output = pid->out_min;
    }

    return pid->output;
}

增量式代码示例

typedef struct
{
    // 1 PID核心参数(需根据实际系统调试)
    float kp;       // 比例系数
    float ki;       // 积分系数
    float kd;       // 微分系数

    // 2 偏差相关变量
    float target;   // 目标值(期望值)
    float feedback; // 反馈值(实际测量值)
    float err;      // 当前偏差 e(k) = target - feedback
    float err_prev1; // 上一次偏差 e(k-1)
    float err_prev2; // 上上次偏差 e(k-2)
    float err_sum;  // 偏差积分累积 sum(e(0)~e(k))

    // 3 输出限制(防止积分饱和、硬件过载)
    float out_max;  // 输出最大值(如PWM占空比上限1000)
    float out_min;  // 输出最小值(如下限0)

    // 4 可选功能:死区阈值(小于该偏差时不响应,避免抖动)
    float dead_zone;

    // 5 PID输出结果
    float output;   // 最终输出 u(k)
}pid_st;

float incremental_pid(pid_st *pid, float target_val, float feedback_val)
{
    // 空指针保护
    if (pid == NULL) return 0.0f;

    // 计算当前偏差
    pid->target = target_val;
    pid->feedback = feedback_val;
    pid->err = pid->target - pid->feedback;

    // 死区处理(偏差小于阈值时,输出0,避免微小抖动)
    if (fabs(pid->err) < pid->dead_zone) 
    {
        pid->err = 0.0f;
    }

    //累加
    pid->output += (pid->kp * (pid->err - pid->err_prev1))                      /* 比例环节 */
                + (pid->ki * pid->err)                                          /* 积分环节 */
                + (pid->kd * (pid->err - 2 * pid->err_prev1 + pid->err_prev2)); /* 微分环节 */

    // 传递误差
    PID->err_prev2 = PID->err_prev1;
    PID->err_prev1 = PID->err;

    // 输出限幅
    if (pid->output > pid->out_max)
    {
        pid->output = pid->out_max;
    } 
    else if (pid->output < pid->out_min)
    {
        pid->output = pid->out_min;
    }

    return pid->output;
}

参考资料

野火电机应用开发指南——PID算法的通俗解说