PID算法,增量式和位置式控制,实现代码

0、前言

我这个文章慢慢更新,我现在也是学习阶段,后面会慢慢更新

1、 PID的概述

在我们学习PID之前在程序经常会写

if(温度<温度阈值)
{
    加热
}
else
{
    不加热
}

他就是直接判断当前的测量值是否跟我的阈值相同,如果相同做什么,不相同就做什么。这样虽然可以做出判断保证这个事件有被处理但是不精确,就比如这个加热模块,我要保证这个温度保持在这个阈值上,如果我当前温度大于阈值我要停止加热,但是由于我保险丝或者加热器还有温度,这个温度就会影响我现在这个温度,导致我们测量的温度会比我们想要的温度高那么一点点,反之亦然。

在我们PID算法中,我们就会尽量减少这种情况,通过控制器减少这种误差,让他保持在我们想要的数据附件。

PID:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。
在这里插入图片描述

我们下面说的都是对电机测速

1、P 比例控制

比例控制是一种最简单的控制方式。其控制器的输出与输入误差信号成比例关系。当仅有比例控制时系统输出存在稳态误差。比例项输出:

在这里插入图片描述

这个公式的意思是Pout是输出值、Kp是系数我们规定的, e(t)是误差值

我们规定一个我们的期望值(target),还有一个我们单片机测量的值(measure)

e(t) = target - measure

比例控制系统快速响应

如果只用P算法 输出的Pout直接作用给PWM,所以因为线性关系Kp直接决定这个调节的速度。

在这里插入图片描述

说明

  • Kp越大说明 他没有达到目标他就越容易接近目标、但是相反如果在目标附近他就会产生很大的增幅。
  • 如果这个系统存在外力,就是反作用于系统的元素,会导致系统存在静态误差(就是我的增量被我的反作用于系统的东西抵消了,如:我给了系统5N的力让他靠近目标值,但是这个系统还存在5N的摩擦力刚好抵消)

所以,单独的比例控制,在很多时候并不能满足要求。

2、I 积分控制

刚刚说P算法存在静态误差,I算法就是解决这个静态误差的,积分就是把每一次的误差值e(t)加起来,然后慢慢的累加起来,抵消静态误差

在这里插入图片描述

Ki 是自己设定的值

积分控制系统的准确性,消除累积的误差,输出到达目标值

积分项是误差对时间的积分,随着时间的增加,积分项会增大。

这样,即便误差很小,积分项也会随着时间的增加而加大,它推动控制器的输出增大使稳态误差进一步减小,直到等于零。

因此,比例积分(PI)控制器,可以使系统在进入稳态后无稳态误差。

不同积分增益 Ki 下, 受控变量的阶跃响应(Kp, Kd 维持稳定值)

在这里插入图片描述

相对于P算法来说 I 算法也是对输出起到促进作用,但是作用的强度低于P算法。

3、D 微分控制

D 算法就是相对于P和I算法的,作用是减少P和I的震荡效果

在这里插入图片描述

这次误差-上次误差

在微分控制中,控制器的输出与输入误差信号的微分成正比关系。微分调节就是偏差值的变化率。使用微分调节能够实现系统的超前控制。如果输入偏差值线性变化,则在调节器输出侧叠加一个恒定的调节量。大部分控制系统不需要调节微分时间。因为只有时间滞后的系统才需要附加这个参数。

也就是说,他是调节我们变化的快慢的,准确说是抑制,但P算法和I算法快速调节的时候他会减慢他们调节的程度。也就是减少震荡

在这里插入图片描述

4、pid算法

将P、I、D三个算法加起来就是我们的PID算法

在这里插入图片描述

在这里插入图片描述

2、位置式 PID 算法

位置式PID是当前系统的实际位置,与你想要达到的预期位置的偏差,进行PID控制

在这里插入图片描述

特点:

  • 所以每次输出均与过去状态有关,计算时要对ek进行累加,工作量大
  • 如果误差值一直是正的或者负的,位置式PID在积分项会一直累积,所以在使用时,必须对积分项进行限幅,同时也要对输出进行限幅。当偏差开始反向变化,位置式PID在积分项需要一段时间才能从最大值减下来,造成输出的滞后。(可以使用积分分离的思想,就是在保持稳定之前用pd算法,在保持稳定之后加入I算法

pid.c

#include "pid.h"
#include "main.h"

// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
	pid->p = p;
	pid->i = i;
	pid->d = d;
	pid->target = target;
	pid->err_curr = 0;
	pid->err_last = 0;
	pid->err_sum = 0;
}

// pid 位置式计算
float PID_Postion(_PID *pid,float current)
{
	volatile float Pout;   // 计算的输出值
	pid->err_curr = pid->target - current;   // 计算当前误差
	pid->err_sum += pid->err_curr;   //积分
	Pout = (pid->p * pid->err_curr) + (pid->i * pid->err_sum) + (pid->d * (pid->err_curr - pid->err_last));   // 计算pid的值
	printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * pid->err_curr) , (pid->i * pid->err_sum) , (pid->d * (pid->err_curr - pid->err_last)));
	pid->err_last = pid->err_curr;   // 记录上一次的误差为当前这一次
	return Pout;
}

pid.h

#ifndef _PID_H
#define _PID_H
typedef struct _PID
{
	volatile float p;
	volatile float i;
	volatile float d;
	float err_curr;   // 当前误差
	float err_last;  // 上次误差
	float err_sum;   // 误差和  积分
	float target;    // 目标值
}_PID;
void PIDStructInit(_PID *pid,float target,float p,float i,float d);  // 结构体初始化
float PID_Postion(_PID *pid,float current);   // 计算位置pid的值
#endif

3、增量式 PID 算法

增量式 PID 是对近几次的数据进行对比,并不像位置式 PID 对之前所有的误差值进行积分,这样就避免了位置式PID的缺陷,但是在实际开发中位置式和增量式要根据但是环境进行选择

在这里插入图片描述

特点

  • ▲u(k)对应的是近几次位置误差的增量,而不是对应与实际位置的偏差,没有误差累加
  • 积分是不需要累加前面所有的误差值的,增量Δu(k)的确定仅与最近3次的采样值有关,所以在系统发生问题时,增量式不会严重影响系统的工作
typedef struct _PID
{
	volatile float p;
	volatile float i;
	volatile float d;
	float err_curr;   // 当前误差
	float err_last;  // 上次误差
	float err_sum;   // 误差和  积分
	float err_last_last;  // 上上次的误差
	float target;    // 目标值
	float Out;  // 增量 输出的值
}_PID;

// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
	pid->p = p;
	pid->i = i;
	pid->d = d;
	pid->target = target;
	pid->err_curr = 0;
	pid->err_last = 0;
	pid->err_last_last = 0;
	pid->err_sum = 0;
	pid->Out  = 0;
}


// 增量式PID 计算
float PID_Incremental(_PID *pid,float current)
{
	volatile float Pout;   // 计算的输出值
	pid->err_curr = pid->target - current;   // 计算当前误差
	Pout = (pid->p * (pid->err_curr - pid->err_last)) + (pid->i * pid->err_last) + (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last)));   // 计算pid的值
	printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * (pid->err_curr - pid->err_last)) , (pid->i * pid->err_last) , (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last))));
	pid->err_last_last = pid->err_last;   // 上上次更新为上次
	pid->err_last = pid->err_curr;     // 将上次的值更新为这次
	pid->Out += Pout;
	return pid->Out;
}

4、什么时候用增量式、什么时候用位置式

我的理解就是

  • 如果你要的是速度这种线性变化的可以用增量式。
  • 如果你要的是那种向我要知道那种位置我当前的位置信息,随机变化很大的可以用位置式

5、 我现在搞好的 pid 驱动(后面还会完善)

// pid.c
#include "pid.h"
#include "main.h"




// pid结构体的初始化
void PIDStructInit(_PID *pid,float target,float p,float i,float d)
{
	pid->p = p;
	pid->i = i;
	pid->d = d;
	pid->target = target;
	pid->err_curr = 0;
	pid->err_last = 0;
	pid->err_last_last = 0;
	pid->err_sum = 0;
	pid->Out  = 0;
}

// pid 位置式计算
float PID_Postion(_PID *pid,float current)
{
	volatile float Pout;   // 计算的输出值
	pid->err_curr = pid->target - current;   // 计算当前误差
	pid->err_sum += pid->err_curr;   //积分
	Pout = (pid->p * pid->err_curr) + (pid->i * pid->err_sum) + (pid->d * (pid->err_curr - pid->err_last));   // 计算pid的值
	printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * pid->err_curr) , (pid->i * pid->err_sum) , (pid->d * (pid->err_curr - pid->err_last)));
	pid->err_last = pid->err_curr;   // 记录上一次的误差为当前这一次
	
	return Pout;
}


// 增量式PID 计算
float PID_Incremental(_PID *pid,float current)
{
	volatile float Pout;   // 计算的输出值
	pid->err_curr = pid->target - current;   // 计算当前误差
	Pout = (pid->p * (pid->err_curr - pid->err_last)) + (pid->i * pid->err_last) + (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last)));   // 计算pid的值
	printf("P = %.3f   I = %.3f   D = %.3f\r\n",(pid->p * (pid->err_curr - pid->err_last)) , (pid->i * pid->err_last) , (pid->d * ((pid->err_curr - 2* pid->err_last + pid->err_last_last))));
	pid->err_last_last = pid->err_last;   // 上上次更新为上次
	pid->err_last = pid->err_curr;     // 将上次的值更新为这次
	pid->Out += Pout;
	return pid->Out;
}



// pid.h

#ifndef _PID_H
#define _PID_H

typedef struct _PID
{
	volatile float p;
	volatile float i;
	volatile float d;
	float err_curr;   // 当前误差
	float err_last;  // 上次误差
	float err_sum;   // 误差和  积分
	float err_last_last;  // 上上次的误差
	float target;    // 目标值
	float Out;  // 增量 输出的值
}_PID;

void PIDStructInit(_PID *pid,float target,float p,float i,float d);  // 结构体初始化

float PID_Postion(_PID *pid,float current);   // 计算位置pid的值


float PID_Incremental(_PID *pid,float current);    // 增量式PID 计算
#endif

  • 24
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个增量式PID控制算法C语言代码示例: ```c/* * 增量式PID计算C语言代码 * 输入参数: * CurrentPoint:当前值 * SetPoint: 目标值 * sptr: 结构体指针,指向PID结构体变量,定义如下: * typedef struct * { * volatile double Proportion; // 比例常数 Proportional Const * volatile double Integral; // 积分常数 Integral Const * volatile double Derivative; // 微分常数 Derivative Const * volatile int Error1; // Error[n-1] * volatile int Error2; // Error[n-2] * volatile int iError; // Error[n] * }PID; * * 返回值: * PID计算增量值 */ float IncPIDCalc(int CurrentPoint, int SetPoint, PID* sptr) { float iIncpid; sptr->iError = SetPoint - CurrentPoint; // 计算当前误差 iIncpid = sptr->Proportion * (sptr->iError - sptr->Error1) // P + sptr->Integral * sptr->iError // I + sptr->Derivative * (sptr->iError -2*sptr->Error1 + sptr->Error2); // D sptr->Error2 = sptr->Error1; // 存储误差,用于下次计算 sptr->Error1 = sptr->iError; return iIncpid; // 返回增量值} ``` 该代码使用了一个名为`PID`的结构体来存储PID控制算法的参数和误差,其中`Proportion`表示比例常数,`Integral`表示积分常数,`Derivative`表示微分常数,`Error1`、`Error2`和`iError`分别表示上一次的误差、上上次的误差和当前的误差。函数`IncPIDCalc`接收当前值`CurrentPoint`和目标值`SetPoint`,然后根据PID算法计算出增量值,并更新误差的历史记录。最后返回增量值。 请注意,这只是一个示例代码,实际使用时可能需要根据具体需求进行修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哈哈啊哈h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值