Loading

RoboMaster电控入门(4)PID控制器

算法原理

PID控制器即比例(proportion)积分(integral)微分(differential)控制器, 该算法以误差作为输入量,经过对比例,积分,微分三项加权求和之后得到输出量。PID控制器是非常经典的控制器,简单易懂,不需要精确的系统模型就可以使用,应用非常广泛。

关于PID的介绍资料网上有很多,也有很多讲的通俗易懂的,比如

https://zhuanlan.zhihu.com/p/39573490

我这里介绍算法原理时除了定性分析之外,还会涉及到一部分理论知识,需要一定的《自动控制原理》《信号与系统》的专业知识,不过不会过于深入,都是简单的入门级知识。

我们先说一下为什么需要闭环控制,以控制一个电机的转速为例。在实际控制之前,我们可能会想当然的觉得,只要我给电机一个100转/min的转速期望,它就会乖乖输出100转/min的结果,但是实际情况呢?电机空载的情况下它或许还能够达到期望的转速,但是假如给电机上加了一个10斤的负载呢?它的转速可能就掉到80转/min去了,再继续增加负载,转速可能会继续下降。

再举一个例子,做过智能小车之类的同学应该会深有体会,单片机输出同样脉宽PWM给电机,但是随着电源电压,轮子松紧等因素的干扰,几乎每次电机最后输出的实际转速都会变化。

如何解决这个问题呢?其实我们只需要根据电机的实际转速来改变我们的给电机的输入量就可以了。比如同样是希望电机挂了10斤负载的情况下能够输出100转/min,由于给电机的输入对应于空载100转/min时,实际输出只有80转/min,可以考虑将输入量提高到对应于空载时120转/min,这时电机的实际输出或许就能够达到100转/min。同样的,如果负载继续增加,我们就进一步增大输入量,以控制实际输出保持在100转/min。

Robomaster的大部分电机都是带霍尔传感器,可以反馈转速/位置信息的,因此都可以通过闭环的方式进行控制。

然后我们介绍一下什么是典型的反馈控制器(该模型可以在matlab的simulink中打开):

一般我们会将受控对象的反馈(measured output)与我们的期望(reference)输入相减,得到一个误差值(error),然后以该误差作为输入量给控制器(controller),控制器最后输出结果(control input)直接传给受控对象(plant)。

依然是结合我们之前的电机的例子,这里的期望即我们的期望电机转速100转/min,反馈即我们通过霍尔传感器获得的电机实际转速80转/min,我们将他们做差得到误差,然后将其送入控制器中,控制器根据内部的算法得到结果,算出120转/min的期望给电机,就能够使电机的实际转速恰好等于期望

由于误差控制器输出都是时变的,我们分别假设其为err(t)U(t),这样我们就可以引出连续型PID的公式

\[U(t) = K_p * err(t) + K_i * \int{{err(t)}dt} + K_d * \frac{{\rm d}err(t)}{{\rm d}t} \]

即对err(t)分别进行比例积分微分运算之后进行加权求和得到输出U(t)。为什么会同时需要这三项呢?我们来定性的分析一下每一项的含义——

首先我们假设KiKd两个系数均为0,则公式变化为

\[U(t) = K_p * err(t) \]

即输出量随误差大小等比例变化,那么这样得到的输出结果会是什么样的呢?(蓝线为输出,黄线为期望,紫线为误差)

我们会发现输出出现了两个问题——第一,出现了超调,第二,出现了静差

超调即实际输出量出现了一个大于期望值的尖峰,超调可能会引发很严重的问题——系统不稳定,这种情况往往出现在比例项过大的情况。一个超调量引发另一个超调,导致系统处于持续的震荡状态中,比如下图这种情况——

体现在实际的机器人中就会是“嘿,你的云台疯了”或者“嘿,你的底盘疯了”,你会看见自己的机器人的云台抽风一般的抽搐点头,底盘如同脱缰的野马肆意奔驰.......(所以为了安全,一定要给输出添加限幅!!被云台打到或者被底盘撞到真的很疼!!)

静差即输出稳定在一个小于期望的值,引发这一点的问题之一是比例系数过小。因为我们知道输出是和误差成正比例的,如果比例系数小了,则输出自然也会小。适当的增大比例系数可以改善这一问题,但是永远不可能彻底消除静差,因为如果真的达到了输出等于期望,也就是err(t)等于0的情况,则输出U(t)就也为0了......我们来看一下增大比例项的结果:静差大大减小,但是超调也增大了。

所以我们可以看出纯比例控制所遇到的一个矛盾的问题,系数太小则存在静差,系数太大则存在超调,所以为了解决这一问题,我们需要提高系统阶数,引入积分项。

引入积分项后,公式变成

\[U(t) = K_p * err(t) + K_i * \int{{err(t)}dt} \]

我们来分析一下积分项是如何解决静差这个问题的。由于积分的累积性,只要存在误差,积分项就会持续作用,即使在err(t)为0时,积分项也不会为0,而是继续保持作用,维持U(t)不为0。但是需要注意的是,积分项对于输出的影响是比较大的,不能贸然给很大的积分系数,否则......(“嘿,你云台/底盘疯了”),此外积分项一定程度上会助长超调的影响,使系统趋于稳定的时间变得更久,如下图所示。

通过增加积分项,我们解决了静差这一问题,但是超调问题依然存在,虽然通过增加减小比例项和积分项可以一定程度上的减小超调,但是代价就是输出消除静差的时间又要被延长了。所以这时我们引入第三项微分项。这时我们就得到了真正的完整PID控制器公式——

\[U(t) = K_p * err(t) + K_i * \int{{err(t)}dt} + K_d * \frac{{\rm d}err(t)}{{\rm d}t} \]

微分项的意义何在呢?我们可以将其与物理中的阻尼概念联系起来。每当err(t)产生急剧变化时,就会产生一个很大的微分项来抵消这一急剧的变化。这就恰好和我们的超调现象相对应,因为超调正是一个急剧上升后急剧下降的尖峰,而微分项是来和它做抵消的。

我们来看一下引入微分项后的输出结果——

可以看到超调量大大减小,且系统恢复稳定的时间也加快了。

通过以上的分析,我们知道了PID控制器各项的作用。然而注意,上面所说的一切都只是定性分析,下面让我们来结合《信号与系统》以及《自动控制原理》的专业知识,来说一下PID更深层次的原理,没有相关基础的同学可以跳过这一部分。

我们引入传递函数来进行系统稳定性的分析,一个闭环控制系统的系统框图如下——

假设我们已经获得了云台的传递函数(可以通过系统辨识获得)

写出PID控制器的s域表达式如下:

\[U(s) = K_p + K_i * \frac{1}{s} + K_d * s \]

通过调节积分项,使积分段作用于系统的低频成分,保证系统稳定性,将微分段作用于系统的高频成分,保证系统的动态性能。

在自动控制原理中,我们知道一个系统稳定的条件是该系统的极点全部位于复平面左侧。所以任何控制器所做的首要任务是配置系统极点,举一个例子。假设一个系统传递函数为

\[G(s) = \frac{1}{s-1} \]

显然其开环不稳定,其闭环传函为

\[\Phi(s) = \frac{G(s)}{1+G(s)H(s)}= \frac{1}{s} \]

显然闭环系统极点在原点上,不稳定

而在引入前馈纯比例控制器

\[C(s) = 2 \]

之后,我们可以看到闭环传函变为

\[\Phi(s) = \frac{C(s)G(s)}{1+C(s)G(s)H(s)}=\frac{2}{s+1} \]

系统极点为s=-1,系统稳定。

在保证系统稳定的前提下,我们根据系统闭环传递函数的波特图进行分析,使幅频响应和相频响应满足幅度裕度条件和相位裕度条件,可以借助matlab的工具箱进行分析。

关于系统辨识以及根据系统辨识的结果设计控制器,可以参考官方的开源教程

https://bbs.robomaster.com/thread-4941-1-1.html

https://bbs.robomaster.com/thread-5059-1-1.html

当然,没有掌握这些知识不代表一个人不能调PID,同样一个人掌握了这个知识也不等于他一定能够调好PID,很多情况下系统的传递函数并不容易获取,所以经验依然非常非常重要。

代码实现

需要注意的是,前文我们一直在讨论连续型PID控制器,但是单片机内部是一个数字系统,数字系统必然是离散的,所以我们需要把连续型PID转换出离散型PID,当然这一点其实也没有任何难度,只不过是把积分变成求和,微分变成差分而已。

离散型PID公式如下

\[U(n) = K_p * err(n) + K_i * \sum{err(n)} + K_d * [err(n) - err(n-1)] \]

眼尖的同学注意到我没有把周期T写在公式里面,其实考虑到周期是一个常数,我把它直接合在了系数中。

这里对应到代码中,也告诉了我们两件事

  1. PID控制器的运算必须在定时中断中执行
  2. 采样周期和控制周期会影响系数

关于第二点特别说明一下,一般来说增加控制周期和采样周期是可以让我们的控制变得更加平滑,控制周期即执行控制代码的周期,一般是放置运算PID代码的定时器的周期,采样周期即获取传感器数据的周期,比如电机的采样周期就是CAN接收中断的周期。

最后我们依然是结合官方开源代码来理解PID的代码实现。

这是pid.c下的pid_calculate函数,结合公式可以很容易理解含义。set即期望,get即反馈,两者相减得到误差err,pid->pout即比例项,直接将err线性放大,pid->iout即积分项,对err进行累加,pid->dout即微分,对err做差分。最后将三者叠加得到pid控制器输出。

/**
  * @brief     calculate delta PID and position PID
  * @param[in] pid: control pid struct
  * @param[in] get: measure feedback value
  * @param[in] set: target value
  * @retval    pid calculate output 
  */
float pid_calculate(struct pid *pid, float get, float set)
{
  pid->get = get;
  pid->set = set;
  pid->err = set - get;
  if ((pid->param.input_max_err != 0) && (fabs(pid->err) > pid->param.input_max_err))
    return 0;

  pid->pout = pid->param.p * pid->err;
  pid->iout += pid->param.i * pid->err;
  pid->dout = pid->param.d * (pid->err - pid->last_err);

  abs_limit(&(pid->iout), pid->param.inte_limit);
  pid->out = pid->pout + pid->iout + pid->dout;
  abs_limit(&(pid->out), pid->param.max_out);

  return pid->out;
}

调参技巧

最后说一点关于调参的问题,当然不会具体到各个参数的调节上,因为这个东西往往是各执一词,不同的人调起PID来即使大思路一致,具体细节上依然可能会有各种各样的不同。这里更多是推荐一些调参的工具,方法,注意事项等。

首先是工具,这里就要搬出我们无敌的Jscope了,在之前的博文中也提到过,这是一个非常便利的虚拟示波器,在mdk中由于只能看到数值,很难分析系统的动态性能,所以Jscope在这时可以派上大用处——

直接上官网就可以免费下载,建议使用比较新的版本,旧的版本存在不能查看结构体内部变量,不能正常画浮点数波形等一些问题。

https://www.segger.com/products/debug-probes/j-link/tools/j-scope/

然后是方法,这里说的方法不是说PID的参数调节顺序之类的(虽然正常人都是先调P然后I,D),而是如何设计输入与如何评价输出。设计输入时,一般以阶跃信号作为输入,逐步提升阶跃幅值以测定系统鲁棒性。进一步测试时可以将窄方波脉冲当作冲激信号来输入,因为赛场上的冲击一般是被可以看作冲激的,RM这个比赛一定程度上检验的就是稳定性,对自己系统的稳定性界限心知肚明对一个电控队员来说是很重要的。

评价输出则根据超调量,静差,恢复时间等参数作为指标。理想的输出曲线自然是能够又快又不超调的贴合上期望曲线,然后根据实际情况来调整参数,出现超调时适当衰减比例项,恢复时间过长时适当减小积分项,增大微分项等等。必须在调参的一开始就定好系统的静态和动态性能指标,有的放矢才能提高效率。

最后是注意事项:记得给控制器的输出限制幅度,切记,切记,切记,被铁疙瘩狠狠打一下或者撞一下,轻则瘀伤,重则伤筋动骨,希望各位电控同学注意自己的生命安全。一般调车时我个人不会把车放在地上调,而是放在车架上,这样可以在一定程度上避免跑飞带来的危险。

另外可以适当考虑采用无线下载器,磁吸附线等,保护好自己的下载器也是很重要的,在越来越多的队伍使用滑环做小陀螺的RM赛场上,云台抽风打转很有可能直接扯断下载线......

结语

关于PID就先说这么多了,还是那句话,实践出真知。掌握PID的理论可以帮助一个人更加科学地去调参,但是光有理论没有实践是没有意义的,还是得多调,积累经验才行。

posted @ 2019-10-14 18:06  sasasatori  阅读(13869)  评论(5编辑  收藏  举报