【PID】
【PID】
【教程】
https://blog.csdn.net/skythinker616/article/details/123019829?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522c8d8156fa931902260ce1e5203c8a34c%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=c8d8156fa931902260ce1e5203c8a34c&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-123019829-null-null.142^v100^pc_search_result_base8&utm_term=PID&spm=1018.2226.3001.4187
线性系统
(1)齐次性:
\(y=f(x)\)
\(ky=f(kx)\)
(2)叠加性:
\(y1=f(x1)\)
\(y2=f(x2)\)
\(y1+y2=f(x1)+f(x2)=f(x1+x2)\)
PID算法适用的系统
一阶系统
二阶系统
控制系统
一般开环控制系统
不检测,期望输出是多少就怎么操作
前馈控制系统
可以检测对输出的干扰,并且进行操作来抵消干扰
闭环控制系统
在输出后进行实时检测,并根据检测值来改变操作
双闭环系统
主对象:水池内的水容量
副对象:水管流水的速度
前馈-反馈复合控制系统
既可以在操作前看到干扰,又可以根据操作后的结果调整操作
参数
误差 E=X-Y
控制器输出 Uc
执行器输出 Ua
系统输出 Y
连续与离散信号
图形表示
连续
离散
信号算式表示
连续
离散
设计PID控制
【单级PID】
物理量设计:对物体进行位置控制为例
目标值/反馈值 :一般为同种物理量
误差=目标值-反馈值
输出值:一般为反馈值的低阶物理量
eg
【对小球进行速度控制】
PID目标值:需要小球达到的速度->s/t=v
PID反馈值:小球的实时速度
PID输出值:施加在小球上的力
->同样效果的串级PID
目标值:小球目标位置
外环反馈:小球实时位置
内环反馈:小球实时速度
输出值:施加在小球上的控制力
【控制电机转速】
PID目标值:需要电机达到的转速
PID反馈值:电机的实时转速
PID输出值:电机中流过的电流大小
【液位高度控制】
PID目标值:需要达到的液位高度
PID反馈值:液位实时高度
PID输出值:阀门液体流速
【串级PID】
内环一般负责低阶物理量的调节
而外环负责高阶物理量的调节并计算出低阶物理量的目标值
eg
外环目标值:需要电机达到的角度
外环反馈值:电机的实时角度
->计算内环目标值
内环反馈值:电机的实时速度
输出值:电机电流大小
PID控制原理
※注意:所有参数都不能设置过大->需要找到平衡点
->反效果:过充
公式
分为P算法,I算法,D算法
比例环节P:起主要控制作用,使反馈量向目标值靠拢,但可能导致振荡
积分环节I:消除稳态误差,但会增加超调量
微分环节D:产生阻尼效果,抑制振荡和超调,但会降低响应速度
P算法 Kp比例项:无法精确到达终点
P越大 控制系统响应速度越快 在接近目标时震荡越严重
P一般不变
例:小车
依照结果(到终点的距离)的偏差给P值 以此控制速度
无限接近但无法靠近
若改变P值
I算法 积分项
稳态误差Error
例:无人机悬停
P算法参数
(1)由于P算法无法到达指定位置,只能接近->I算法积分提供升力
(2)到达指定位置后,P算法0x100=0->无法控制->I算法积分提供升力
D算法 微分项
积分环节停不下来导致超调 ->加入D项进行抵消 ->使系统进行稳定
※一般不用D项
PID参数整定
试参数:Kp,Ki,Kd
(1)先将所有参数置零
(2)将输出限幅设为执行机构能接受的最大值
(3)增大p参数,使响应速度达到比较好的水平
(4)若存在稳态误差,逐渐增加i参数和积分限幅,使稳态误差消失
(5)若希望减少超调或振荡,逐渐增加d参数,在保证响应速度的前提下尽可能降低超调
伪代码
循环:
计算误差=实际值-期望值
计算积分:将误差累加
计算微分
输出:比例、积分、微分加权求和
算法等待t秒下一次循环
其他控制知识
积分限幅
误差大,积分作用越强,而且可以一直积累
积分积累越大->变化幅度越强->超调现象严重
若手一直按着:I项一直积累->若放开:会突然飞出去->限制I项积累程度
积分分离
设定e阈值
如果e超过一定范围->I项为0
e不超过一定范围->I项起作用
微分先行
只对输出量进行微分,而对给定指令不起微分作用
适合于给定指令频繁升降的场合,可以避免指令的改变导致超调过大
控制电机转动
代码实现
一般思路
//首先定义PID结构体用于存放一个PID的数据
typedef struct
{
float kp, ki, kd; //三个系数
float error, lastError; //误差、上次误差
float integral, maxIntegral; //积分、积分限幅
float output, maxOutput; //输出、输出限幅
}PID;
//用于初始化pid参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
pid->kp = p;
pid->ki = i;
pid->kd = d;
pid->maxIntegral = maxI;
pid->maxOutput = maxOut;
}
//进行一次pid计算
//参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
//更新数据
pid->lastError = pid->error; //将旧error存起来
pid->error = reference - feedback; //计算新error
//计算微分
float dout = (pid->error - pid->lastError) * pid->kd;
//计算比例
float pout = pid->error * pid->kp;
//计算积分
pid->integral += pid->error * pid->ki;
//积分限幅
if(pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral;
else if(pid->integral < -pid->maxIntegral)
pid->integral = -pid->maxIntegral;
//计算输出
pid->output = pout+dout + pid->integral;
//输出限幅
if(pid->output > pid->maxOutput) pid->output = pid->maxOutput;
else if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
}
PID mypid = {0}; //创建一个PID结构体变量
int main()
{
//...这里有些其他初始化代码
PID_Init(&mypid, 10, 1, 5, 800, 1000); //初始化PID参数
while(1)//进入循环运行
{
float feedbackValue = ...; //这里获取到被控对象的反馈值
float targetValue = ...; //这里获取到目标值
PID_Calc(&mypid, targetValue, feedbackValue);
//进行PID计算,结果在output成员变量中
//设定执行器输出大小(mypid.output);
delay(10); //等待一定时间再开始下一次循环
}
}
M3508速度单环
代码部分
SpeedPID.target=500;
//读取实际值并更新
chassis_motor_pid[i].SpeedPID.current = motor__chassis[i].speed_rpm;
pid_calc(&chassis_motor_pid[i].SpeedPID);//传结构体进来
【位置PID】
调参:Kp,Ki,Kd
void pid_calc(_pid* pid){//传结构体进来->传地址便于直接修改
pid->e = pid->target - pid->current;//算误差值
//p比例项
pid->p_out = (int32_t )(pid->Kp * pid->e);
//积分限幅
limit(&(pid->i_out),pid->IntergralLimit);
/*自己写的函数:【示例】
int32_t LimitMax(input, max)
{
if (input > max) input = max;
else if (input < -max) input = -max;
}
*/
//积分分离
if(fabs(pid->e) < I_Band)
{
//注意i积分项要加号
pid->i_out += (int32_t )(pid->Ki * pid->e);
}
else
{
pid->i_out = 0;
}
pid->d_out - (int32_t )(pid->Kd * (pid->e - pid->last_e));
pid->total_out = pid->p_out + pid->i_out + pid->d_out;
//pid输出限幅
limit(&(pid->total_out),pid->MaxOutput);
pid->last_e=pid->e;
}
6020角度双环
两个反馈:角度,速度
第一环:角度 --> 量纲转化 --> 第二环:速度
第一环算出的期望输出->AnglePID.out = SpeedPID.target->第二环的输入
代码部分
AnglePID.target=30°
//硬件传回更新角度值
update_angle(&manipulator_motor_pid[i]._angle,motor_manipulator[i].angle);
//实际输出传入
manipulator_motor_pid[i].AnglePID.current = manipulator_motor_pid[i]._angle.angle;
pid_calc(&manipulator_motor_pid[i].AnglePID);
//AnglePID.total_out 第一环算出的期望输出
manipulator_motor_pid[i].SpeedPID.target = manipulator_motor_pid[i].AnglePID.total_out;
manipulator_motor_pid[i].SpeedPID.current = motor_manipulator[i].speed_rpm;
pid_calc(&manipulator_motor_pid[i].SpeedPID);
【位置PID】 和单环一样
角度更新函数
void update_angle(motor_angle* _angle,uint16_t angle_fb)
{
_angle->encoder = angle_fb;
if(_angle->encoder_is_init)
{
//在规定时间内有没有转过一圈
if(_angle->encoder - _angle->last_encoder > 4096)
_angle->round_cnt--;
else if(_angle->encoder - _angle->last_encoder < -4096)
_angle->round_cnt++;
}
else
{
_angle->encoder_offset = _angle->encoder;
_angle->encoder_is_init=1;
}
_angle->angle_offset = _angle->encoder_offset / 8192.0f * 360.0f;
_angle->last_encode = _angle->encoder;
_angle->total_encoder = _angle->round_cnt * 8192 +
_angle->encoder - _angle->encoder_offset;
_angle->angle = _angle->total_encoder / 8192.f * 360.0f;
}