前面的文章(电机控制基础)介绍了电机编码器原理、定时器输出PWM、定时器编码器模式测速等。
本文在之前文章的基础上,继续学习电机控制,利用PID算法控制电机的转速,并进行实验测试。
PID 是比例、积分和微分的缩写。
PID是一种经典的闭环控制算法。它具有原理简单、易于实现、应用广泛、控制参数独立、参数选择相对简单等优点。
每当某个物理量需要“稳定”(如保持平衡、稳定温度、速度等)时,PID就会派上用场。
PID算法可分为两类:位置式PID和增量式PID。
在实际编程应用中,需要采用离散化PID算法来适应计算机的使用环境。我们以电机速度控制为例,看一下两种PID算法的基本原理。
位置PID是当前系统的实际位置,与想要达到的预期位置的偏差用于PID控制。
比例P:本次e(k)误差
积分I: e(i) 误差累积
微分D:e(k) - e(k-1) 本次误差-上次误差
因为有一个误差积分e(i),它总是累加的,即当前的输出u(k)与过去的所有状态有关。
位置PID算法的伪代码如下:
//位置PID(伪代码) previous_err=0;integral=0;loop: //根据目标值和测量值(如电机的设定速度和旋转后编码器的读取速度),循环计算更新输出值(如PWM)误差=设定值- 测量值; /*误差项:目标值-测量值*/积分+=误差*dt; /*积分项:误差项的累加*/导数=(error - previous_error)/dt ; /*导数项:误差变化率*/输出=Kp*误差+Ki*积分+Kd*导数; /*将三项分别乘以PID系数,得到输出*/previous_err=err; //更新错误wait(dt); //等待固定计算周期goto循环;
比例P:e(k) - e(k-1) 本次误差-上次误差
积分I:e(k)本次误差d
微分D:e(k) - 2e(k-1)+e(k-2) 本次误差- 2上次误差+ 上次误差
注意,增量式PID首先计算u(k),然后将其与上次的输出相加,得到本次的输出结果。增量式PID没有误差累积,控制增量u(k)的确定只与最近的三个采样值有关。
增量PID算法的伪代码如下:
//增量PID(伪代码) previous02_error=0; //最后偏差previous01_error=0; //最后偏差积分=0; //积分且pid_out=0; //PID增量累加和loop: error=setpoint measured_value; /*误差项:目标值-测量值*/比例=误差- previous01_error; /*比例项:误差项-上次偏差*/积分=误差*dt; /*积分项:误差项累积*/导数=(error 2*previous01_error + previous02_error)/dt;/*导数项:最后一个误差与前一个误差之间的变化率*//*或者写为:导数=( (error previous01_error)/dt - (previous01_error - previous02_error)/dt )*/pid_delta=Kp 误差+ Ki 积分+ Kd 导数; //计算PID增量pid_out=pid_out + pid_delta; //计算最终PID输出previous02_error=previous01_error; //更新上次偏差previous01_error=error; //更新最后的偏差wait(dt); //等待固定计算周期goto循环;
以这个弹簧为例(假设没有重力,只有空气阻力),先到平衡位置(目标位置),拉动它,然后松开,它就会振荡。
P表示比例。类比弹簧的弹力(恢复力):F=k*x
当物体距离平衡位置越远时,弹力越大。反之,越接近平衡位置,力越小。
当块位于平衡位置之上时,弹性向下。当块体低于平衡位置时,弹性向上。也就是说,弹力总是迫使块体朝向平衡位置。
仅P控制,滑块不断上下摆动,整个系统不是特别稳定。
这是因为空气阻力太小。想象一下将其完全放入水中,该物体应该会很快停止。这是因为阻力。
D的功能相当于电阻:
它与变化的速度(单位时间内的变化量)有关。变化越大,施加的阻力就越大。
它的方向与目标值无关。例如,当木块从下到上经过平衡位置时,其方向始终是向下的。
即先阻止木块接近平衡位置,然后阻止木块远离平衡位置(对比P的效果,始终阻止木块远离平衡位置)
其作用是减少系统的超调(减少系统在平衡位置的振荡)
凭借P的力量和D的阻力,这个方块可以很快稳定下来。那么我的角色是什么?
想象一下,如果有其他外力的影响,在某一时刻,当木块即将到达平衡位置时,P的力量恰好抵消了外力(与P方向相反的恒定力),然后方块就会停在这里。接近(因为此时D的力也接近0并很快变为0),永远不会到达平衡位置。
这时就需要I的误差积分函数:
只要有错误,它计算的错误累积就会不断增加。一开始可能很小,但是只要没有达到平衡位置,这个值就会越来越大。
其作用是消除系统的静误差
实际应用中,调节PID参数时,一般采用试错法。 PID参数整定公式如下:
找到最佳的参数设置,按照从小到大的顺序查看,
首先是比例,然后是积分,最后是微分。
曲线振荡非常频繁,因此需要放大比例刻度盘。
曲线围绕海湾浮动,比例刻度盘变小。
当曲线偏离时,恢复缓慢,积分时间减少。
曲线波动周期较长,积分时间较长。
曲线振荡频率快,所以首先减小微分。
如果动态差值较大,则波动较慢,应加长微分时间。
理想的曲线有两个波浪,前高后低,4比1。
你再看第二次调整,多分析一下,调整的质量不会低。
上面介绍了PID的基础知识。接下来,位置PID用于控制直流电机的速度。
typedef struct{float target_val; //目标值float err; //偏差值float err_last; //最后偏差值float Kp,Ki,Kd; //比例、积分、微分系数float积分; //整数值float output_val; //输出值}PID;
float PID_realize(floatactual_val){/*计算目标值与实际值的误差*/pid.err=pid.target_val -actual_val;/*积分项*/pid.integral +=pid.err; /*PID算法实现*/pid.output_val=pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * (pid.err - pid.err_last );/*错误传递*/pid.err_last=pid.err;/*返回当前实际值*/return pid.output_val;}
//周期定时器回调函数void AutoReloadCallback(){int sum=0 ;/*编码器值(PID输入)*/int res_pwm=0;/*PWM值(PID输出)*//*读取编码器测量的速度值*/sum=read_encoder(); /*进行PID运算,得到PWM输出值*/res_pwm=PID_realize(sum);/*根据PWM值控制电机旋转*/set_motor_rotate(res_pwm); /*发送实际值到上位机通道1*/set_computer_value(SEND_FACT_CMD, CURVES_CH1, sum, 1 ); }
这里使用野火多功能调试助手的‘PID调试助手’进行实验,用于显示PID调节时的电机转速曲线。
将目标速度值设置为50(这里的目标值50使用编码器在10ms内捕获的脉冲数),并调整PID参数来测试电机是否可以更快地达到目标速度。
先使用P值10看看效果。从速度曲线可以看出,无法达到目标速度,且与目标速度相差较大。
PID1000
当P值增加到100时,从速度曲线可以看出,仍然没有达到目标速度。
PID10000
如果只用P,就会出现静态差异,永远达不到目标值。这时就必须使用积分项来消除静差。
将P 保持为100,并使用I 为0.2。从速度曲线可以看出,可以达到目标速度,但跟随速度较慢。
PID1000.20
将P 保持在100,增加I,并使用1.0。从速度曲线可以看出,可以达到目标速度,并且跟随速度加快。
保持PID1001.00P为100,继续增加I,采用3.0。从速度曲线可以看出,已经达到了目标速度,并且后续速度进一步加快。
PID1003.00P维持100,然后继续增加I,采用6.0。从速度曲线可以看出,能够达到目标速度,并且跟随速度也很快,但有一点超调。
PID1006.00 对于超调,可以尝试加微分。微分D相当于电阻的作用。
保持P 为100,I 为6.0,D 为3.0。从速度曲线来看,似乎没有明显的变化。
PID1006.03.0P 保持在100,I 保持在6.0,D 增加到6.0。从速度曲线来看,超调幅度有所减小。
PID1003.06.0
https://www.bilibili.com/video/BV18Q4y1R7e8?spm_id_from=333.999.0.0
本文简要介绍PID的基本原理和参数整定。想要调好PID参数,需要不断的练习和调试。