原理图:
(1)位置式PID
是1:当前系统的实际位置,与你想要达到的预期位置的偏差, 2:进行PID控制,误差会一直累加,会使当前输出与过去的所有输入相关,输入uk出错,会导致系统大幅波动 3:位置式PID在积分项达到饱和时,误差仍然会在积分作用下继续累积,一旦误差开始反向变化,系统需要一定时间从饱和区退出,所以在u(k)达到最大和最小时,要停止积分作用,并且要有积分限幅和输出限幅, 4:用位置式PID时,一般我们直接使用PD控制,不使用积分项
实际应用中,用差分代替微分,连加代替积分,也就是离散型PID
令:
(1)实现:位置模式PID
#include <math.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include "matplotlibcpp.h"
#include <vector>
#include <math.h>
#include <string>
#include<stdlib.h>
namespace plt = matplotlibcpp;
class pid_p
{
private:
float ki;
float kp;
float kd;
float ek;
float ek_1;
float actual;
float de;
float target;
float yk;
public:
pid_p();
~pid_p();
pid_p(float p,float i,float d);
void get_error();
void get_value(float act,float tar);
float update();
};
pid_p::pid_p():kp(0),ki(0),kd(0),ek(0),ek_1(0),de(0),actual(0),yk(0)
{
}
pid_p::pid_p(float p,float i,float d):ek(0),ek_1(0),de(0),actual(0),yk(0)
{
kp=p;
ki=i;
kd=d;
}
pid_p::~pid_p()
{
}
void pid_p::get_value(float act,float tar)
{
actual=act;
target=tar;
get_error();
printf("actual:%f,target%f",actual,target);
}
void pid_p::get_error()
{
ek=target-actual;
}
float pid_p::update()
{
de+=ek;
yk=kp*ek+ki*de+kd*(ek-ek_1);
printf("p:%f,i:%f,d:%f,act:%f,yk:%f,ek:%f\r\n",kp,ki,kd,actual,yk,ek);
ek_1=ek;
return yk;
}
//输入三个参数kp,ki,kd
int main(int argc,char ** argv)
{ float target=1000;
std::string str_p=argv[1];
std::string str_i=argv[2];
std::string str_d=argv[3];
// std::string str_p="0.35";
// std::string str_i="0.0001";
// std::string str_d="0.0001";
float act=0;int N=100;
float kp=atof(str_p.c_str());
float ki=atof(str_i.c_str());
float kd=atof(str_d.c_str());
pid_p a(kp,ki,kd);
std::vector<float> x,y;
for (int i=0;i<N;i++)
{
x.push_back(i);
y.push_back(act);
a.get_value(act,target);
act+=a.update();
a.pid_printf();
//if(act>target)break;
}
plt::plot(x,y);
plt::show();
}
(2)增量式PID
原理:使控制器输出为增量,尽量使每次数据均与过去数据无关,没有积分项。
公式:Kp比例系数、Ki积分系数、Kd微分系数、e(k)偏差
Δu(k)=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
实现
/*
* 定义PID的结构体,结构体内存储PID参数、误差、限幅值以及输出值
*/
typedef struct
{
float Kp;
float Ki;
float Kd;
float last_error; //上一次偏差
float prev_error; //上上次偏差
int limit; //限制输出幅值
int pwm_add; //输出的PWM值
}PID;
/**
* @brief PID相关参数的初始化
* @param PID的结构体指针
*/
void PID_Init(PID *p)
{
p->Kp = Velocity_Kp;
p->Ki = Velocity_Ki;
p->Kd = Velocity_Kd;
p->last_error = 0;
p->prev_error = 0;
p->limit = limit_value;
p->pwm_add = 0;
}
/**
* @brief PID相关参数的初始化
* @param targetSpeed目标速度值,PID的结构体指针p
*/
int PID_Cal(int targetSpeed,int currentSpeed,PID *p)
{
int error = targetSpeed - currentSpeed; //得到目标速度与当前速度的误差
p->pwm_add += p->Kp*(error - p->last_error) + p->Ki*error + p->Kd*(error - 2*p->last_error+p->prev_error); //根据增量PID公式计算得到输出的增量
p->prev_error = p->last_error; //记录上次误差
p->last_error = error; //记录本次误差
if(p->pwm_add>p->limit) p->pwm_add=p->limit; //限制最大输出值
if(p->pwm_add<-p->limit) p->pwm_add=-p->limit;
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器6中断回调函数,每50ms调用一次
{
float c_leftSpeed,c_rightSpeed;
if(htim==(&htim6))
{
GetEncoderPulse();
c_leftSpeed = CalActualSpeed(encoderPulse[0]); //获得当前的速度值
c_rightSpeed = CalActualSpeed(encoderPulse[1]);
PID_Cal(leftTargetSpeed,c_leftSpeed,&LeftMotor_PID);//左边电机的PID计算
PID_Cal(rightTargetSpeed,c_rightSpeed,&RightMotor_PID);//右边电机的PID计算
MotorControl(leftMotor_PID.pwm_add,RightMotor_PID.pwm_add);
//通过上位机查看速度曲线以及PID调速效果
printf("{currentLeftSpeed is:%.2f}\r\n",c_leftSpeed);
printf("{currentrightSpeed is:%.2f}\r\n",c_rightSpeed);
}
}
关于PID的参数调节,一般遵循以下原则:
先调整比例系数Kp,然后再调整积分系数Ki,最后再调整微分系数Kd。
调整Kp时,从小到大进行调整,选取偏小的比例系数使得输出曲线基本贴合目标直线。
调整Ki,用Ki消除静态误差。
调整Kd,提高调速的响应速度。
(3) 积分分离式PID
原理:在系统误差较大时,取消积分环节;当误差较小时,引入积分环节。这样既不影响控制器的动态性能,又可以提高控制器的稳态精
实现:在位置式/增量式PID加入积分环节一个阈值,实现略
(4) 抗饱和积分式PID
原理:在计算U(k)的时候,先判断上一时刻的控制量U(k-1)是否已经超出了限制范围。若U(k-1)>Umax,则只累加负偏差;若U(k-1)<Umin,则只累加正偏差。从而避免控制量长时间停留在饱和区。
实现:
class pid_antisaturation
{
private:
float kp,ki,kd,uk,uk_1,yk,ek,ek_1,ek_2;
const float max_uk_1=500,min_uk_1=-500;
public:
pid_antisaturation():kp(0),ki(0),kd(0),uk(0),yk(0),ek(0),ek_1(0),ek_2(0),uk_1(0)
{};
pid_antisaturation(float p,float i,float d):kp(p),ki(i),kd(d),uk(0),yk(0),ek(0),ek_1(0),ek_2(0),uk_1(0)
{};
void get_value(float act,float target);
void update_error();
float update();
};
void pid_antisaturation::get_value(float act,float target)
{
uk_1=uk;
uk=act;
yk=target;
}
void pid_antisaturation::update_error()
{
ek_2=ek_1;
ek_1=ek;
ek=yk-uk;
}
float pid_antisaturation::update()
{
float increase;
update_error();
if((uk_1>max_uk_1)&(ek>0))
{
ek=0;
}
if((uk_1<min_uk_1)&(ek<0))
{
ek=0;
}
increase=kp*(ek-ek_1)+ki*ek+kd*(ek-2*ek_1+ek_2);
printf("p:%f,i:%f,d:%f,act:%f,yk:%f,ek:%f\r\n",kp,ki,kd,uk,yk,ek);
return increase;
}