PID位置式、增量式和串级PID

目录

前言

一、PID的原理

1、位置式

(1)位置式流程图 

(2)位置式公式 

(3)位置式使用注意事项

2、增量式

(1)增量式流程图 

(2)增量式公式  

3、串级PID

(1)串级PID流程图

(2)串级PID注意事项

二、PID代码

1、反馈值测量

 2、初始化

2、位置环

3、速度环

4、串级PID

三、PID调参

 1、上位机软件Vofa+

 2、调参

 (1)位置式

(2)增量式

四、PID使用注意事项


前言

 本人对应PID算法只算初窥门径,对于更加深入的学习不足,这里参考了很多大佬的博客,加上我自己对于PID算法的总结,希望这篇博客能对你的PID学习之路有所帮助。如有不足之处,欢迎进行讨论学习。

一、PID的原理

PID 是“比例-积分-微分”(Proportional-Integral-Derivative)的缩写,是一种常见的反馈控制算法,广泛应用于工业控制系统和各种自动化设备中。PID 控制器通过计算偏差或误差值(即期望输出与实际输出之间的差值),并应用比例(P)、积分(I)和微分(D)三种控制作用来调整控制量,以达到期望的控制效果。

  • 比例(P):这部分负责对当前的误差进行响应。比例增益越大,控制器对误差的反应越强烈,但过高的比例增益可能导致系统振荡。
  • 积分(I):积分作用累积误差随时间的变化,用于消除稳态误差,确保系统的长期准确性。积分作用太强可能会导致系统响应缓慢和过冲。
  • 微分(D):微分作用预测误差的趋势,对误差的变化率进行响应。它有助于减少系统的超调和振荡,提高系统的稳定性。微分作用较强会降低响应速度。

 

 个人理解P是主要的动力来源,但是由于其作为一个百分比很多时候无法到达目标值,即总是存在一个误差,称为稳态误差。这时候就需要I来上场,用以消除这个总是存在的误差,使系统能够达到目标值。但是当动力较大而阻力较弱,整个系统处于不平衡的状态最终导致系统的振荡,而D的作用相当于系统的阻力,阻力与动力平衡后,系统就能趋于稳定的状态。

1、位置式

(1)位置式流程图 

 从图上可以看出PID系统,输入为目标值、反馈值,输出是电机转动的PWM。并且与增量式不同,位置式的反馈值是累计的脉冲值。当到达目标值后电机会停止运动。但是如果反馈值是每次的脉冲值就会导致电机一直运动。

(2)位置式公式 

e(k)/e(i)     误差值   = 目标值 - 反馈值

e(k - 1)      上一次的误差值

(3)位置式使用注意事项

增量式算法不需要做累加,控制量增量的确定仅与最近几次偏差采样值有关,计算误差对控制 量计算的影响较小。而位置式算法要用到过去偏差的累加值,容易产生较大的累加误差。 

位置式PID需要有积分限幅和输出限幅,积分限幅用来防止积分饱和,输出限幅用来保护电机,而增量式只需要输出限幅

2、增量式

(1)增量式流程图 

(2)增量式公式  

e(k)    误差值   = 目标值 - 反馈值

e(k - 1)      上一次的误差值

e(k - 2)      上上一次的误差值

 增量式PID根据公式可以很好地看出,一旦确定了 KP、TI  、TD,只要使用前后三次测量值的偏差, 即可由公式求出控制增量,并且得出的控制量▲u(k)对应的是近几次位置误差的增量,而不是对应与实际位置的偏差 ,没有误差累加

也就是说,控制增量Δu(k)的确定仅与最近3次的采样值有关,容易通过加权处理获得比较好的控制效果,并且在系统发生问题时,增量式不会严重影响系统的工作。

3、串级PID

串级PID是将位置环和速度环串级使用,内环是速度环,外环是位置环。单一位置环是由于路线上的因素,电池的因素,每次转动的速度并不相同,这就会导致误差的发生。而串级PID就可以改善这个问题,减小误差。

(1)串级PID流程图

输入的值有三个:目标值、外环的反馈值、内环的反馈值。整个系统的目标值先作为外环(位置环)的目标值,外环的输出作为内环(速度环)的目标值,内环的输出就是整个系统的输出。反馈值都在系统运算前测出。

(2)串级PID注意事项

调试时两个环要先断开,先调试内环,在调试外环。两个环都调好后在串到一起,注意不要把输入的参数写错。

二、PID代码

1、反馈值测量

反馈值getSpeed = (float)(计数器值zj) * 1000 / 30.0f / 4 / 11;

参数解释:1000: 计数器值 / 10ms = 计数器值 * 1000

30.0f :电机减速比1:30

4:4倍频,计数器加4实际是加1

11:电机转一圈是11个脉冲

/*main函数中开启编码器*/
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);//开启TIM2编码器模式
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);//开启TIM3编码器模式
/*****************************************************************/
/*10ms定时器中断*/
int16_t zj;
float getSpeed = 0;
float Get_Speed(TIM_HandleTypeDef *Encoder_TIM_Handle)
{
    
    zj = __HAL_TIM_GetCounter(Encoder_TIM_Handle);
    __HAL_TIM_SetCounter(Encoder_TIM_Handle, 0);
    getSpeed = (float)zj * 1000 / 30.0f / 4 / 11;

    return getSpeed;
}

 2、初始化

main函数中初始化 

	MyUsart_Init();
	Motor_Init();
//	for(int i=0; i<2; i++)
//  {	
//    pid_init(&motor_pid[i]);
//		/*pid, maxout,intergral_limit,deadband,period,max_err,target,*/
//    motor_pid[i].f_param_init(&motor_pid[i],40,40,2,0,30,0,1.8f,0.05f,0);//1.8f,0.05f,0
//motor_pid[i].f_param_init(&motor_pid[i],5000,500,2,0,30,0,0.08f,0.00004f,0.015f);//0.08f,0.00004f,0.015f//位置环
//  }
	
  for(int i=0; i<2; i++)//串级PID参数
  {	
    pid_init(&CascadeMotor_pid[i].inner,SpeedPid_calculate);             
CascadeMotor_pid[i].inner.f_param_init(&CascadeMotor_pid[i].inner,40,40,250,0,30,0,1.8f,0.05f,0.0f);//1.8f,0.05f,0//速度环
		
		pid_init(&CascadeMotor_pid[i].outer,LocationPid_calculate);
		CascadeMotor_pid[i].inner.f_param_init(&CascadeMotor_pid[i].outer,5000,500,2,0,30,0,0.08f,0.0f,0.015f);//0.08f,0.00004f,0.015f//位置环0.08f,0.000001f,0.09f
  }
	
	
	HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);//开启TIM2编码器模式
	HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);//开启TIM3编码器模式
	MotorLocationTarget[0] = 2000;
	MotorLocationTarget[1] = 2000;
	
//	MotorTarget[0] = 30;
//	MotorTarget[1] = 30;
	
	HAL_TIM_Base_Start_IT(&htim4);
static void pid_param_init(
	PID_TypeDef * pid, 
	uint16_t maxout,
	uint16_t intergral_limit,
	float deadband,
	uint16_t period,
	int16_t  max_err,
	int16_t  target,
	
	float 	kp, 
	float 	ki, 
	float 	kd)
{
	
	pid->ControlPeriod = period;             //没用到
	pid->DeadBand = deadband;
	pid->IntegralLimit = intergral_limit;
	pid->MaxOutput = maxout;
	pid->Max_Err = max_err;
	pid->target = target;
	pid->last_err = 0;
	pid->llast_err = 0;
	
	pid->kp = kp;
	pid->ki = ki;
	pid->kd = kd;
	
	pid->output = 0;
}

 PID参数结构体 

typedef struct _PID_TypeDef
{
	float target;							//目标值
	float lastNoneZeroTarget;
	float kp;
	float ki;
	float kd;
	
	float   measure;			//测量值
	float   err;				//误差
	float   last_err;      		//上次误差
	float   llast_err;      	//上上次误差
	float 	sumErr;             //误差和
	
	float pout;
	float iout;
	float dout;
	
	float output;				//本次输出
	float last_output;			//上次输出
	
	float MaxOutput;		    //输出限幅
	float IntegralLimit;		//积分限幅
	float DeadBand;			   //死区(绝对值)
	float ControlPeriod;		//控制周期
	float  Max_Err;				//最大误差
	
	
	void (*f_param_init)(struct _PID_TypeDef *pid,  //PID参数初始化
				   uint16_t maxOutput,
				   uint16_t integralLimit,
				   float deadband,
				   uint16_t controlPeriod,
					int16_t max_err,     
					int16_t target,
				   float kp,
				   float ki,
				   float kd);
				   
	void (*f_pid_reset)(struct _PID_TypeDef *pid, float kp,float ki, float kd);		//pid三个参数修改
	int16_t (*f_cal_pid)(struct _PID_TypeDef *pid, float measure,int16_t target);   //pid计算

}PID_TypeDef;


//串级PID的结构体,包含两个单级PID
typedef struct
{
    PID_TypeDef inner; //内环
    PID_TypeDef outer; //外环
    int16_t output; //串级输出,等于inner.output
}CascadePID;

2、位置环

 

int16_t LocationPid_calculate(PID_TypeDef* pid, float measure,int16_t target)//位置式PID
{
	pid->measure = measure;
    pid->target = target;
	pid->err = pid->target - pid->measure;  //与pid P系数相乘。
    比例误差值 当前差值=目标值-实际值
	pid->sumErr	+= pid->err;//与pid I系数相乘。稳态误差值 误差相加作为误差总和,给积分项
	
	
	//是否进入死区
//	if((ABS(pid->err) > pid->DeadBand))
//	{
		
		pid->pout = (float)pid->kp * pid->err;
		pid->iout = (float)pid->ki * pid->sumErr;
		pid->dout =  (float)pid->kd * ((float)pid->err - (float)pid->last_err); 

		//积分是否超出限制
		if(pid->iout > pid->IntegralLimit)
			pid->iout = pid->IntegralLimit;
		if(pid->iout < - pid->IntegralLimit)
			pid->iout = - pid->IntegralLimit;
		
		pid->output = pid->pout + pid->iout + pid->dout; //计算PID
		

		if(pid->output>pid->MaxOutput)         
		{
			pid->output = pid->MaxOutput;
		}
		if(pid->output < -(pid->MaxOutput))
		{
			pid->output = -(pid->MaxOutput);
		}
		pid->llast_err = pid->last_err;
		pid->last_err  = pid->err;
		pid->last_output = pid->output;
//	}
	return pid->output;
}

 

3、速度环

int16_t SpeedPid_calculate(PID_TypeDef* pid, float measure,int16_t target)//增量式,速度环
{
	float proportion,differential;   
	pid->measure = measure;
  pid->target = target;
	
	
	pid->err = pid->target - pid->measure;
	
	proportion = (float)pid->err - (float)pid->last_err;                                                               //计算比例项
  differential = (float)pid->err - 2 * (float)pid->last_err + (float)pid->llast_err; 	//计算微分项
	
	pid->pout = (float)pid->kp * proportion;
	pid->iout = (float)pid->ki * pid->err;
	pid->dout =  (float)pid->kd * differential; 
	
	pid->output = (float)pid->last_output + pid->pout + pid->iout + pid->dout; //计算PID
		
	//是否进入死区
//	if((ABS(pid->err) > pid->DeadBand))
//	{

	if(pid->output>pid->MaxOutput)         
	{
		pid->output = pid->MaxOutput;
	}
	if(pid->output < -(pid->MaxOutput))
	{
		pid->output = -(pid->MaxOutput);
	}
	pid->llast_err = pid->last_err;
	pid->last_err  = pid->err;
	pid->last_output = pid->output;
//	}

	return pid->output;
}
float speed[2] = {0};
int16_t set_speed[2] = {0};
static void PID_Speed(void)
{
	speed[0] = Get_Speed(&Encoder1_TIM_Handle);
	set_speed[0] = motor_pid[0].f_cal_pid(&motor_pid[0],speed[0],MotorTarget[0]);
	
	speed[1] = Get_Speed(&Encoder2_TIM_Handle);
	set_speed[1] = motor_pid[1].f_cal_pid(&motor_pid[1],speed[1],MotorTarget[1]);
	
	LeftMotor(set_speed[0]);
	RightMotor(set_speed[1]);
	
	printf("%f,%f,%d\n",speed[0],speed[1],MotorTarget[0]);//发送数据给上位机
}
float location[2] = {0};
int16_t set_location[2] = {0};
static void PID_Location(void)
{
	location[0] += Get_Speed(&Encoder1_TIM_Handle);
	location[1] += Get_Speed(&Encoder2_TIM_Handle);
	
	set_location[0] = motor_pid[0].f_cal_pid(&motor_pid[0],location[0],MotorLocationTarget[0]);
	set_location[1] = motor_pid[1].f_cal_pid(&motor_pid[1],location[1],MotorLocationTarget[1]);
	
	
	LeftMotor(set_location[0]);
	RightMotor(set_location[1]);
	
	printf("%f,%f,%d\n",location[0],location[1],MotorLocationTarget[1]);
	
}

4、串级PID

/*放到10ms定时器中断*/
float locationOuter[2] = {0};
int16_t set_locationOuter[2] = {0};
float speedInner[2] = {0};
int16_t set_speedInner[2] = {0};
static void PID_Cascade(void)//串级PID
{
	
	speedInner[0] = Get_Speed(&Encoder1_TIM_Handle);
	speedInner[1] = Get_Speed(&Encoder2_TIM_Handle);
	locationOuter[0] += speedInner[0];
	locationOuter[1] += speedInner[1];
	
	set_locationOuter[0] = CascadeMotor_pid[0].outer.f_cal_pid(&CascadeMotor_pid[0].outer,locationOuter[0],MotorLocationTarget[0]);
	set_locationOuter[1] = CascadeMotor_pid[1].outer.f_cal_pid(&CascadeMotor_pid[1].outer,locationOuter[1],MotorLocationTarget[1]);
	
	set_speedInner[0] = CascadeMotor_pid[0].inner.f_cal_pid(&CascadeMotor_pid[0].inner,speedInner[0],set_locationOuter[0]);
	set_speedInner[1] = CascadeMotor_pid[1].inner.f_cal_pid(&CascadeMotor_pid[1].inner,speedInner[1],set_locationOuter[1]);
	
	LeftMotor(set_speedInner[0]);
	RightMotor(set_speedInner[1]);
	
	printf("%f,%f,%d,%f,%f,%d,%d\n",locationOuter[0],locationOuter[1],MotorLocationTarget[1],speedInner[0],speedInner[1],set_locationOuter[0],set_locationOuter[1]);

}

三、PID调参

 1、上位机软件Vofa+

下载链接:https://www.vofa.plus/downloads/

使用介绍:https://www.vofa.plus/docs/learning/start/quick_start

 2、调参

 (1)位置式

在调整PID控制器时,通常首先从比例(P)开始,以赋予系统基本的响应能力。当系统开始出现震荡时,适当减小P值以稳定系统。接下来,调整积分(I)项,其大小应根据系统的响应能力来设定。可以先假设一个阈值,通过计算与系统实际响应能力对比后,调整到一个合适的I值。有时,仅调整I值后系统就能达到预期性能。此时,如果需要,可以进一步调整微分(D)项。

对于微分(D)项,通常在系统震荡时减小D值以获得更好的稳定性。调整顺序并非固定,有时也可以先调整P使系统过冲,然后通过调整D来限制震荡,最后微调I以补足控制效果。

当I项调整到足够时,可以适当降低P值;而当D项调整得当时,可以适度增加P值。这里的增加或减少都应该是细微的调整,而不是大幅度变动。

(2)增量式

首先,调整积分(I)参数,设定一个初始值并观察系统的响应。如果系统能够收敛,尝试逐渐增加I参数以加快收敛速度。如果收敛速度过快或过慢,相应地调整I参数直到找到一个适中的收敛趋势。一旦系统表现出良好的收敛性,再引入比例(P)参数。

对于比例(P)参数,观察系统是否会出现震荡。设定一个初始P值,如果震荡幅度减小,可以适当增加P值;如果震荡幅度增大,则减小P值。需要不断微调P和I参数,以达到最佳效果。例如,如果调整P后效果不明显,可以适当减小I值,然后再次调整P。

至于增量式PID中的微分(D)参数,调试中对系统变化不太明显。

四、PID使用注意事项

1、PID是用来计算和控制运动,使用时应该只改变目标值。

2、避免在计算过程中使用延时,避免测量脉冲的卡顿导致疯转。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值