电机应用-控制系统、PID

目录

控制系统

闭环自动控制系统原理

开环自动控制系统原理

PID

增量式与位置式区别

位置式PID优缺点

增量式PID优缺点

Kp、Ki、Kd的作用

PID调试一般原则

确定P、I、D参数的一般步骤

确定比例增益P

确定积分时间常数Ti

确定积分时间常数Td

PID算法代码

位置式按键修改目标值

基本定时器配置TIM6

PID相关

协议相关

主体函数 

速度式按键修改目标值

基本定时器配置TIM6

PID相关

协议相关

主体函数 


控制系统

对生产中某些关键性参数进行自动控制,使它们在受到外界干扰(扰动)的影响而偏离正常状态时,能够被自动地调节而回到工艺所要求地数值范围内。

自动控制系统分为:开环、闭环。

闭环自动控制系统原理

闭环控制是负反馈控制。系统组成包括:传感器、控制装置、执行机构。

传感器检测被控对象的状态信息(输出量),并将其转变成电信号传给控制装置。

控制装置比较被控对象当前状态(输出量)和希望状态(给定量)的偏差,产生一个控制信号,通过执行机构驱动被控对象运行,使其运动状态接近希望状态。

开环自动控制系统原理

开环控制不能检测误差,不能够校正误差,只能按照事先确定好的程序和产生信号的条件,依次去控制对象并且无抑制的干扰能力。

PID

PID(Proportional比例、Integral积分、Differential微分)结合了比例、积分和微分,是闭环控制算法,是目前为止在连续控制系统中最为成熟的一种控制算法。

PID控制的实质是对目标值和实际值误差进行比例、积分、微分运算后的结果用来作用在输出上。

P、I、D是三种不同的调节作用,既可以单独使用(P、I、D),也可以两个两个用(PI、PD),也可以三个一起用(PID)。

PID最基本的三个参数:Kp、Ki、Kd。

PID控制是个对偏差的控制过程。

如果偏差为0,则比例环节不起作用,只有存在偏差时比例环节才起作用。

积分环节主要是用来消除系统稳定后输出值和设定值间的差值,积分环节实际上是偏差累计的过程,把累计的误差加到原有系统上以抵消系统造成的差值。

微分信号反应了偏差信号的变化规律(变化趋势),根据偏差信号的变化趋势来进行超前调节,从而增加了系统的预知性。

增量式与位置式区别

增量式算法不需要对积分项累加,控制量增量只与近几次的误差有关,计算误差对控制量计算的影响较小。而位置式算法对近几次的偏差进行积分累加,容易产生较大的累加误差。

增量式算法控制输出的是控制量增量(例如在阀门控制中,只输出阀门开度的变化部分,误动作影响小,必要时还可通过逻辑判断限制或禁止本次输出,不会严重影响系统的工作)。而位置式算法的输出直接对应对象的输出,因此对系统影响较大。

增量式算法控制输出的是控制量增量,并无积分作用,因此适用于执行机构带积分部件的对象,如步进电机等。而位置式算法适用于执行机构不带积分部件的对象,如电液伺服阀。

在进行PID控制时,位置式PID需要有积分限幅和输出限幅。而增量式PID只需输出限幅。

位置式PID优缺点

优点:非递推式算法,可直接控制执行机构(如平衡小车),u(k)的值和执行机构的实际位置(如小车当前角度)是一一对应的,因此在执行机构不带积分部件的对象中可以很好的应用。

缺点:每次输出都和过去的状态有关,计算时要对e(k)进行累加,运算工作量大。

增量式PID优缺点

优点:

        误动作时影响小,必要时可用逻辑判断的方法去除出错数据。

        手动/自动切换时冲击小,便于实现无扰动切换。

        算式中不需要累加。控制增量△u(k)的确定仅与最近3次的采样值有关。在速度闭环控制中有很好的实时性。

缺点:

        积分截断效应大,有稳态误差。

        溢出影响大。有的被控对象用增量式则不太好。

Kp、Ki、Kd的作用

举例水箱注水,要维持水箱水位在1m的高度。

假设刚开始水箱的水位是0.2m。则偏差为0.8m。现确定往里注水,取加水力度Kp为0.5。

则第一次加水时,加水量△u = 0.5 * 0.8 = 0.4m。接着第二次加水,当前水位是0.6m,则偏差为0.4m,△u = 0.5 * 0.4 = 0.2m,达到0.8m的水位量。

如此循环,可以预见最终水位会达到我们需要的1m。

一般情况下增大比例系数可以加快系统的响应,有助于减小静差。

再增加一些条件,水箱的水是要给别人使用的。假设每次加水前,别人都会使用掉0.1m的水位。假设Kp还是0.5,水位0.2m。

则第一次加水时,加水量△u = 0.5 * 0.8 = 0.4m。接着第二次加水,当前水位是0.5m,则偏差为0.5m,△u = 0.5 * 0.5 = 0.25m,达到0.75m的水位量。接着第三次加水,当前水位是0.65m,则偏差为0.35m,△u = 0.5 * 0.35 = 0.175m,达到0.825m的水位量。接着第四次加水,当前水位是0.725m,则偏差为0.275m,△u = 0.5 * 0.275 = 0.1375m,达到0.8625m的水位量。接着第五次加水,当前水位是0.7625m,则偏差为0.2375m,△u = 0.5 * 0.2375 = 0.11875m,达到0.88125m的水位量。如此循环,每次加水前都贴合0.8m,达到了平衡,水位将不再变化。

此时产生的误差就是稳态误差。此时稳态误差为0.2m。

一般情况下增大积分时间有利于减小超调,使系统稳定性增加,但是会增长消除静差的时间。

为了避免系统达到稳态却仍然不能达到目标值的情况,引入积分环节来消除稳态误差。

引入积分环节,相当于再加了一个小水龙头,给水箱注水。Ki项作用在整个环节累计的误差上,只要有误差,那么控制的力度就会不断的增大。

应用在实例中,与上述纯比例环节情况不同,引入积分环节后,即使比例环节达到了稳态,但是由于积分环节的存在,如果误差仍然存在时,那么小水龙头还会继续拧大,增加注水量,直到误差消除,此时积分项不再增大,输出维持不变。意思是在消除了误差的情况下,小水龙头的注水量和用水量也达到了平衡。

微分环节延续上面的系统,注水时突然来了个大爷给水箱加了一桶水,假设现在还没有超过预定的水位,那么在大爷加水前系统的偏差是不是比大爷加水后的偏差要大(如大爷加水前偏差水位是0.5m,加水后偏差是0.1米)。假设增加水位的速度是0.3m/次,那么下次加水就很容易漫出来。引入了微分环节的话,微分环节就会帮我们把水龙头给拧小,甚至会帮我们把水弄出去一些。

一般情况下微分环节具有超前调整的作用,抑制震荡。

PID调试一般原则

在输出不振荡时,增大比例增益P。

在输出不振荡时,减小积分时间常数Ti。

在输出不振荡时,增大微分时间常数Td。

确定P、I、D参数的一般步骤

确定比例增益P

首先去掉PID的积分项和微分项,一般是令Ti=0、Td=0,使PID为纯比例调节。

输入设定为系统允许的最大值的60%~70%,由0逐渐加大比例增益P,直到系统出现振荡。

然后在从此时的比例增益P逐渐减小,直到系统振荡消失。

记录此时的比例增益P,设定PID的比例增益P为当前值的60%~70%。比例增益P调试完成。

确定积分时间常数Ti

比例增益P确定后,设定一个较大的积分时间常数Ti的初值,然后逐渐减小Ti,直至系统出现振荡。

然后逐渐加大Ti,直到系统振荡消失。

记录此时的Ti,设定PID的积分时间常数Ti为当前值的150%~180%。积分时间常数Ti调试完成。

确定积分时间常数Td

积分时间常数Td一般不用设定,为0即可。若要设定,与前面方法一致,取不振荡时的30%。

系统空载、带载联调,再对PID参数进行微调,直至满足要求。

PID算法代码

位置式按键修改目标值

基本定时器配置TIM6

默认20ms为定时器周期,每20ms进一次中断处理函数。 

/* 以下两宏仅适用于定时器时钟源TIMxCLK=84MHz,预分频器为:1680-1 的情况 */
#define SET_BASIC_TIM_PERIOD(T)     __HAL_TIM_SET_AUTORELOAD(&TIM_TimeBaseStructure, (T)*50 - 1)    // 设置定时器的周期(1~1000ms)
#define GET_BASIC_TIM_PERIOD()      ((__HAL_TIM_GET_AUTORELOAD(&TIM_TimeBaseStructure)+1)/50.0)     // 获取定时器的周期,单位ms

/**
  * @brief  初始化基本定时器定时,默认20ms产生一次中断
  * @param  无
  * @retval 无
  */
void TIMx_Configuration(void)
{
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 3);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
	
    __TIM6_CLK_ENABLE();

    // 80MHz/1680 -- 50kHz
    // 定时器周期:20ms
    TIM_TimeBaseStructure.Instance 				= TIM6;
    TIM_TimeBaseStructure.Init.Period 			= 1000 - 1;
    TIM_TimeBaseStructure.Init.Prescaler 		= 1680 - 1;
    TIM_TimeBaseStructure.Init.CounterMode 		= TIM_COUNTERMODE_UP;
    TIM_TimeBaseStructure.Init.ClockDivision 	= TIM_CLOCKDIVISION_DIV1;

    HAL_TIM_Base_Init(&TIM_TimeBaseStructure);
    HAL_TIM_Base_Start_IT(&TIM_TimeBaseStructure);

#if defined(PID_ASSISTANT_EN)
    // 计算定时器周期,单位ms。20ms
    uint32_t temp = GET_BASIC_TIM_PERIOD();
    set_computer_value(SEND_PERIOD_CMD, CURVES_CH1, &temp, 1);     // 给通道 1 发送目标值
#endif
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim == (&TIM_TimeBaseStructure))
    {
		// 默认每20ms执行一次该函数
		time_period_fun();
    }
}

PID相关

初始化PID参数并向上位机发送PID参数,每20ms传入实际值执行一次pid算法,输出值(实际值)整数输出到上位机。 

typedef struct
{
    float target_val;   	//目标值
    float actual_val;   	//实际值
    float err;          	//定义偏差值
    float err_last;     	//定义上一个偏差值
    float Kp,Ki,Kd;     	//定义比例、积分、微分系数
    float integral;     	//定义积分值
}_pid;
_pid pid;

void PID_param_init()
{
    /* 初始化参数 */
    pid.target_val 	= 0.0;	//目标值
    pid.actual_val 	= 0.0;  //实际值
    pid.err 		= 0.0;  //定义偏差值
    pid.err_last 	= 0.0;  //定义上一个偏差值
    pid.integral 	= 0.0;	//定义积分值
    pid.Kp 			= 0.01;	//定义比例系数
    pid.Ki 			= 0.80;	//定义积分系数
    pid.Kd 			= 0.04;	//定义微分系数

#if defined(PID_ASSISTANT_EN)
    float pid_temp[3] = {pid.Kp, pid.Ki, pid.Kd};
    set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3);     // 给通道 1 发送 P I D 值
#endif
}

/**
  * @brief  设置比例、积分、微分系数
  * @param  p:比例系数 P
  * @param  i:积分系数 i
  * @param  d:微分系数 d
	*	@note 	无
  * @retval 无
  */
void set_p_i_d(float p, float i, float d)
{
    pid.Kp = p;    // 设置比例系数 P
    pid.Ki = i;    // 设置积分系数 I
    pid.Kd = d;    // 设置微分系数 D
}

/**
  * @brief  PID算法实现
  * @param  val		实际值
  * @note 	无
  * @retval 通过PID计算后的输出
  */
float PID_realize(float temp_val)
{
    /*计算目标值与实际值的误差*/
    pid.err = pid.target_val - temp_val;
	
    /*误差累积*/
    pid.integral += pid.err;
	
    /*PID算法实现*/
    pid.actual_val = pid.Kp * pid.err + pid.Ki * pid.integral + pid.Kd * (pid.err - pid.err_last);
	
    /*误差传递*/
    pid.err_last = pid.err;
	
    /*返回当前实际值*/
    return pid.actual_val;
}

/**
  * @brief  定时器周期调用函数
  * @param  无
	*@note 	无
  * @retval 无
  */
int 	pid_status 	= 0;
void time_period_fun(void)
{
    if (!pid_status)
    {
        float val = PID_realize(pid.actual_val);
		
		// 上位机需要整数参数,转换一下
        int temp = val;    
		
        set_computer_value(SEND_FACT_CMD, CURVES_CH1, &temp, 1);                // 给通道 1 发送实际值
    }
}

协议相关

后面会讲。

主体函数 
float set_point = 0.0;

int main(void)
{
	int run_i = 0;
	int temp = set_point;   									// 上位机需要整数参数,转换一下
	
	初始化工作
	
	set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &temp, 1);  // 给通道 1 发送目标值
	
	while (1)
    {
        /* 接收数据处理 */
        receiving_process();

		/* 按一次,预设目标值200 */
		/* 再按一次,预设目标值0 */
        if (Key_Scan(KEY2_GPIO_PORT, KEY2_PIN) == KEY_ON)
        {
            if (run_i % 2 == 0)
            {
                set_point = 200;
                pid.target_val = set_point;
            }
            else
            {
                set_point = 0;
                pid.target_val = set_point;
            }

            run_i++;
            temp = set_point;    											// 上位机需要整数参数,转换一下
            set_computer_value(SEND_TARGET_CMD, CURVES_CH1, &temp, 1);     	// 给通道 1 发送目标值
        }

		/* 按一次,停止pid调节,同步上位机的停止按钮状态 */
		/* 再按一次,开始pid调节,同步上位机的启动按钮状态 */
        if (Key_Scan(KEY3_GPIO_PORT, KEY3_PIN) == KEY_ON)
        {
            pid_status = !pid_status;

            if (!pid_status)
            {
                set_computer_value(SEND_START_CMD, CURVES_CH1, NULL, 0);    // 同步上位机的启动按钮状态
            }
            else
            {
                set_computer_value(SEND_STOP_CMD, CURVES_CH1, NULL, 0);     // 同步上位机的停止按钮状态
            }
        }
    }
}

速度式按键修改目标值

基本定时器配置TIM6

和上同。

PID相关

初始化PID参数并向上位机发送PID参数,每20ms传入实际值执行一次pid算法,输出值(实际值)整数输出到上位机。 

typedef struct
{
	float target_val;     	// 目标值
	float actual_val;     	// 实际值
	float err;            	// 定义当前偏差值
	float err_next;       	// 定义下一个偏差值
	float err_last;       	// 定义最后一个偏差值
	float Kp, Ki, Kd;     	// 定义比例、积分、微分系数
}_pid;
_pid pid;

void PID_param_init()
{
    /* 初始化参数 */
    pid.target_val 	= 0.0;	// 目标值
    pid.actual_val 	= 0.0;  // 实际值
    pid.err 		= 0.0;  // 定义偏差值
    pid.err_last 	= 0.0;  // 定义上一个偏差值
    pid.err_next 	= 0.0;	// 定义最后一个偏差值
    pid.Kp 			= 0.20;	//定义比例系数
    pid.Ki 			= 0.20;	//定义积分系数
    pid.Kd 			= 0.0;	//定义微分系数

    float pid_temp[3] = {pid.Kp, pid.Ki, pid.Kd};
    set_computer_value(SEND_P_I_D_CMD, CURVES_CH1, pid_temp, 3);     // 给通道 1 发送 P I D 值
}

/**
  * @brief  设置比例、积分、微分系数
  * @param  p:比例系数 P
  * @param  i:积分系数 i
  * @param  d:微分系数 d
	*	@note 	无
  * @retval 无
  */
void set_p_i_d(float p, float i, float d)
{
    pid.Kp = p;    // 设置比例系数 P
    pid.Ki = i;    // 设置积分系数 I
    pid.Kd = d;    // 设置微分系数 D
}

/**
  * @brief  PID算法实现
  * @param  val		目标值
	*	@note 	无
  * @retval 通过PID计算后的输出
  */
float PID_realize(float temp_val)
{
    /*计算目标值与实际值的误差*/
    pid.err = pid.target_val - temp_val;
	
    /*PID算法实现*/
    float increment_val = pid.Kp * (pid.err - pid.err_next) + pid.Ki * pid.err + pid.Kd * (pid.err - 2 * pid.err_next + pid.err_last);
	
    /*累加*/
    pid.actual_val += increment_val;
	
    /*传递误差*/
    pid.err_last = pid.err_next;
    pid.err_next = pid.err;
	
    /*返回当前实际值*/
    return pid.actual_val;
}

/**
  * @brief  定时器周期调用函数
  * @param  无
	*@note 	无
  * @retval 无
  */
int 	pid_status 	= 0;
void time_period_fun(void)
{
    if (!pid_status)
    {
        float val = PID_realize(pid.actual_val);
		
		// 上位机需要整数参数,转换一下
        int temp = val;    
		
        set_computer_value(SEND_FACT_CMD, CURVES_CH1, &temp, 1);                // 给通道 1 发送实际值
    }
}

协议相关

后面会讲。

主体函数 

和上同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值