PID 定速功能
本节介绍
📝本节您将了解PID的基本公式,通过实际值与目标值的误差,实现让实时速度以最快的时间接近目标速度。
🏆本章⽬标
1️⃣了解PID
2️⃣实现PID定速
定速功能说明
定速功能:使电机当前实时速度以最快的时间接近设定的目标速度。
实现这个功能最基础的想法是:
当 实时速度
小于 目标速度
,那就让实时速度变快;
当 实时速度
大于 目标速度
,那就让实时速度变慢;
按照这个想法写代码,最基础的想法如下:
当前速度=0, 目标速度=100 if ( 当前速度 < 目标速度 ) { 当前速度++; } if( 当前速度 > 目标速度 ) { 当前速度--; } | ![]() | |
---|---|---|
但是这个想法有问题。目前我的目标速度是100,这个实时速度的变化还能够接受,那如果是10000呢,百万,千万呢?如果还是以 1 为基础进行步进,那速度的提升就太慢了。
这个时候就希望有一个算法,能够让我们当前实时速度的步进单位不要一直是1
。 比如:
我的目标速度和实时速度差距非常大的时候,那我的步进单位是100,100的变化;
我的目标速度和实时速度差距非常小的时候,那我的步进单位是0.1,0.1的变化;
这样不管目标有多大,都能够实现快速的接近目标。
PID算法就可以实现。
PID的基本公式
PID算法,即比例P-积分I-微分D控制器,它通过控制系统的偏差(目标值与实际值之间的差)来调节控制变量,使得系统达到或维持在一个预定的状态。
PID的基本公式
公式拆分为静态参数和动态参数:
静态参数
- Kp 是比例增益
- Ki 是积分增益
- Kd 是微分增益
动态参数
- P = 设定值与实际值之间的误差e
- I = 误差e的累加
- D = 当前误差e - 之前的误差last_e
将公式转换为代码,如下:
typedef struct
{
float kp, ki, kd; // 三个静态系数
float change_p, change_i, change_d; // 三个动态参数
float error, last_error; // 误差、之前误差
float max_change_i; // 积分限幅
float output, max_output; // 输出、输出限幅
int target; // 目标
}PID;
/****************************************************
功能:PID计算
参数:pid = pid的参数输入
target = 目标值
current = 当前值
返回:PID计算后的结果
****************************************************/
float pid_calc(PID *pid, float target, float current)
{
//用上一次的误差值更新 之前误差last_error
pid->last_error = pid->error;
//获取新的误差 = 目标值 - 当前值
pid->error = target - current;
//计算比例P = 目标值与实际值之间的误差e
float pout = pid->error;
//计算积分I = 误差e的累加
pid->change_i += pid->error;
//计算微分D = 当前误差e - 之前的误差last_e
float dout = pid->error - pid->last_error;
//积分I 限制不能超过正负最大值
if(pid->change_i > pid->max_change_i)
{
pid->change_i = pid->max_change_i;
}
else if(pid->change_i < -pid->max_change_i)
{
pid->change_i = -pid->max_change_i;
}
//计算输出PID_OUT = (Kp x P)+ (Ki x I)+(Kd x D)
pid->output = (pid->kp * pout) + (pid->ki * pid->change_i) + (pid->kd * dout);
//输出 限制不能超过正负最大值
if(pid->output > pid->max_output) pid->output = pid->max_output;
else if(pid->output < -pid->max_output) pid->output = -pid->max_output;
//返回PID计算的结果
return pid->output;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
PID静态参数的调节
如果某个 P,I,D 参数偏离了原点的 PID 值,控制效果就会变化。控制效果与各个参数的规律如图所示。截取自:链接
当然,如果你能记录PID的曲线的话,就可以导入matlab进行自动调节pid参数,网上有挺多教程,大家可以搜一搜。这里我们就只介绍靠经验来调节了(简称瞎鼓捣)。
比较常用的有试错法,这个是一种经验性的方法,通过尝试不同的PID参数值,找到合适的参数设置。基本步骤如下:
TIP
- 首先,将I和D参数设置为0,只保留P参数。
- 增加P参数,直到系统的响应时间变得合适。过高的P参数会导致系统振荡,过低的P参数会导致系统响应慢。
- 接着,增加I参数,以减小稳态误差。适当调整I参数,直到系统达到满意的性能。
- 最后,增加D参数,以降低系统的超调量和提高响应速度。适当调整D参数,直到系统达到满意的性能。
假设我们需要控制一个直流减速电机的速度,为了达到某个设定值。我们将使用PID控制器来调节电机的驱动PWM,来达到所需的速度。
简要步骤如下:
- 初始化参数:首先将Kp,Ki,Kd全部设置为0,这样PID控制器不产生任何控制作用。
- 调节比例增益Kp:逐渐增加Kp的值,直到系统开始出现持续的振荡(速度在设定值附近上下波动)。此时记录下Kp的值。
- 调节积分增益Ki:将Kp设置为刚刚Kp值的一半,然后逐渐增加Ki的值,直到系统的静态误差(速度与设定值的差距)变得很小。过大的Ki值可能导致系统响应过慢或振荡,此时需要适当减小Ki值。
- 调节微分增益Kd:保持当前的Kp和Ki值不变,逐渐增加Kd的值,直到系统的动态性能满足要求(例如,要求速度在设定值附近的波动范围和响应时间)。过大的Kd值可能导致系统响应过于敏感或噪声敏感,此时需要适当减小Kd值。
- 微调参数:以上步骤得到的Kp,Ki,Kd值可能仍然不是最优解,因此还需要根据实际系统的性能要求进行微调。通常情况下,可以先调整Kp和Ki,然后再调整Kd。在调整过程中,需要关注系统的稳定性、阻尼、响应速度、过冲等性能指标。
![]() |
---|
三个静态参数影响什么?
这里就以恒温热水壶来举例说明了。
Kp(比例增益)
比例项是误差与控制器输出之间的线性关系。增大Kp会使系统响应速度更快,但可能会导致更大的过冲和振荡。减小Kp可以减少过冲和振荡,但会降低响应速度,甚至导致系统无法达到设定点(静态误差)。
以恒温热水壶为例,如果比例增益Kp设置得较大,当温度偏离设定点时,恒温热水壶会迅速地增加或减少加热功率。这可能导致温度在设定点附近发生过冲和振荡。而较小的Kp值可以减少过冲和振荡,但恒温器对温度偏差的响应速度会变慢。
Ki(积分增益)
积分项考虑了误差的累积效应,可以消除静态误差。增大Ki可以加快误差积分,使系统更快地达到设定点,但过大的Ki会导致积分过程过于敏感,引起系统的振荡和不稳定。
在上面恒温热水壶示例中,如果存在静态误差(即使加热持续了很久,温度仍无法达到设定点),可以通过增大积分增益Ki来消除这种静态误差,让水壶的加热功率再大一些。然而,过大的Ki值可能会导致恒温器对温度波动过于敏感,从而引起系统的振荡。
Kd(微分增益)
微分项关注误差的变化速度,可以预测系统的未来行为。增大Kd可以提高系统的阻尼性能,减小过冲和振荡,但过大的Kd会导致系统对噪声过于敏感,引入不稳定性。
在恒温热水壶示例中,如果系统在接近设定点时出现过冲和振荡,可以通过增大微分增益Kd来减小这些现象。微分项可以预测系统的未来行为,从而提前调整加热功率。然而,过大的Kd值可能会使恒温器对温度噪声过于敏感,从而影响系统的稳定性。
使用PID实现定速闭环控制
为了让电机的速度达到目标的速度,我们需要知道怎么获取电机的当前速度。而在之前的编码器驱动章节,我们就已经实现了电机的速度获取是 get_encoder_count()
。
我们将目标速度,和当前编码器采集到的实时速度传入PID控制器中,PID控制器计算完后输出结果,我们再将结果给电机,而在电机驱动章节,我们就已经实现了电机的控制是 set_motor
。
![]() |
---|
实现的代码:
/************************************************
功能:PID电机定速控制器
参数:target_speed = 目标值
返回:对应PID的地址
************************************************/
PID motor_speed_control(int target_speed)
{
int PWM;
//传入PID静态参数、目标值(目标速度)、当前值(当前编码器获取的实时速度)
//PID输出的值为控制信号,传入到PWM变量中
PWM = pid_calc( &speed_pid, target_speed, get_encoder_count() );
//控制信号转为电机实际控制值
if( PWM > 0 )//控制信号大于0
{
set_motor(0, PWM);//顺时针旋转,pwm为旋转速度
}
else if( PWM < 0 )
{
PWM = -PWM;//将负数转为正数(例如PWM=-10,则-(-10)=10)
set_motor(PWM, 0);//逆时针旋转,pwm为旋转速度
}
return speed_pid;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
需要注意一个问题,电机是PWM驱动的,PWM的范围是0~9999。我们将PID控制器输出的正负值或者小数点等结果都转为 PWM 的0~9999的范围,这样电机才能够转动。正负值或者小数点的处理上面的功能函数 PID motor_speed_control(int target_speed)
已经实现了,但是如果PID计算的值超过了PWM的最大值 9999 了怎么办?所以我们还需要限制。
/************************************************
功能:初始化PID各参数
参数:pid = 对应pid的结构体地址
p = pid的静态kp值
i = pid的静态ki值
d = pid的静态kd值
maxI = pid计算后的I最大值,即最大误差累加值
maxOut = PID最大输出值
target = 目标值
************************************************/
void pid_init(PID* pid, float p, float i, float d, float maxI, float maxOut, int target)
{
pid->kp = p;
pid->ki = i;
pid->kd = d;
pid->max_change_i = maxI;
pid->max_output = maxOut;
pid->target = target;
}
....
void main(void)
{
//传入speed_pid地址
//设定 kp 为35
//设定 ki 为6
//设定 kd 为10
//设定 I的最大值 为9999
//设定 PID最大输出值 为9999
//设定 目标值 为 98
pid_init(&speed_pid, 35, 6, 10, 9999, 9999, 98);
while(1)
{
//实现定速功能
motor_speed_control( speed_pid.target );
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
案例验证
使用上一章节的状态机进行编写。复制粘贴重新命名为 08_speed_pid
。
在 middle 文件夹下新增了 mid_pid.c 和 mid_pid.h。
在 app 文件夹下新增了 app_speed_pid.c 和 app_speed_pid.h。
与上一章节案例的代码更改说明
- 增加PID定速功能;
- 增加PID定速波形显示;
- 增加PID定速参数更新显示;
- 增加长按参数快速更新;
- 增加定时器中断的任务使能与任务失能函数;