解决方案

PID控制算法 – 1、Sample Time(采样时间)

seo靠我 2023-09-22 23:48:41

前面介绍的PID代码虽然能跑起来,但是还存在一些问题。

PID控制算法 – 0、PID原理_资深流水灯工程师的博客-CSDN博客

对应的代码也重新贴一下,方便比较

/*工作变量*/ unsiSEO靠我gned long lastTime; double Input, Output, Setpoint; double errSum, lastErr; SEO靠我double kp, ki, kd; void Compute() { /*计算上次PID调用到这次调用之间的时间间隔*/ unsignSEO靠我ed long now = millis(); //获得当前时间,这是Arduino的做法,其他平台自己可以去替换 double timeChange = (double)(now -SEO靠我 lastTime); //计算时间间隔 /*计算误差、误差的积分、误差的微分*/ double error = Setpoint - Input; SEO靠我errSum += (error * timeChange); double dErr = (error - lastErr) / timeChange; /*计算PSEO靠我ID的输出*/ Output = kp * error + ki * errSum + kd * dErr; /*保留一些变量,留着下次用,记录误差和时间*/ SEO靠我 lastErr = error; lastTime = now; } void SetTunings(double Kp, double KSEO靠我i, double Kd) { kp = Kp; ki = Ki; kd = Kd; }

主要有两个问题:

1、PID计算函SEO靠我数Compute()不是周期性的调用,相当于是轮询模式,调用的时间间隔不是一致的;

2、时间间隔的不一致,也就导致跟时间间隔相关的积分部分和微分部分每次都需要额外的计算;

解决方案

让PID计算函数CompSEO靠我ute()周期性的调用,这计时所谓的采样频率,也叫采样周期。这样就会省很多事。怎么个省法?还是来进行一下时间分析。

PID算法调用时间的分析

积分和微分与时间是直接相关的,现在可不比之前,现在有固定的采样SEO靠我周期了,记录每次调用PID算法的时间间隔就是固定的采样周期SampleTime

所以原先的累计误差errSum是这么表示:

那现在是固定的采样周期,累计误差errSum可以这么表示:

那整个积分部分

聪明的SEO靠我小朋友肯定知道把看成一个整体,因为Sampletime是一个固定的常量;

误差的微分dERR可以这么表示: 

那整个微分部分可以这么表示:

聪明的小朋友肯定又会把看成一个整体了。

那比例系数、积分系数、微分系数SEO靠我可以直接把采样时间Sampletime绑定一起

void SetTunings(double Kp, double Ki, double Kd) { double SamSEO靠我pleTimeInSec = ((double)SampleTime)/1000; kp = Kp; ki = Ki * SampleTimeInSec; SEO靠我 kd = Kd / SampleTimeInSec; }

既然比例系数、积分系数、微分系数已经算上了时间,那误差、误差积分、误差微分就改变了

//误差还是当年的误差 eSEO靠我rror = Setpoint - Input; //误差的积分已不是当年的积分 errSum += error; //误差的微分也不是当年的微分 SEO靠我 dErr = (error - lastErr);

PID的输出还是那个公式:

完整的周期性调用PID代码实现

/*工作变量*/ unsigned long lastTimeSEO靠我; double Input, Output, Setpoint; double errSum, lastErr; double kp, ki, kd;SEO靠我 int SampleTime = 1000; //1 sec void Compute() { unsigned long now =SEO靠我 millis();//记录当前是时间 int timeChange = (now - lastTime);//计算时间间隔,是为了判断采样时间是否到了 if(timeSEO靠我Change>=SampleTime)//时间间隔大于采样时间就可以进行PID计算 { /*计算误差、误差的积分、误差的微分*/ double erroSEO靠我r = Setpoint - Input; errSum += error; double dErr = (error - lastErr); /*计SEO靠我算PID输出*/ Output = kp * error + ki * errSum + kd * dErr; /*还是记录本次PID计算的误差和时间,留给下次使用*SEO靠我/ lastErr = error; lastTime = now; } } void SetTunings(doubSEO靠我le Kp, double Ki, double Kd) { double SampleTimeInSec = ((double)SampleTime)/1000; SEO靠我 kp = Kp; ki = Ki * SampleTimeInSec; kd = Kd / SampleTimeInSec; } SEO靠我 void SetSampleTime(int NewSampleTime) {//改变采样时间后,只需要将ki和kd等比例替换一下就行 if (NewSampSEO靠我leTime > 0) { double ratio = (double)NewSampleTime / (double)SampleTime; SEO靠我 ki *= ratio; kd /= ratio; SampleTime = (unsigned long)NewSampleTime; } SEO靠我 }

在第10和11行,该算法可以决定是否需要进行PID计算。

我们现在知道采样之间的时间是相同的,因此不需要不断地乘以时间变化。 只需适当地调整Ki和Kd(第31和32行),结果在数学上是SEO靠我等效的,但效率更高。

虽然这样做有点瑕疵。 如果用户决定在操作过程中更改采样时间,则需要重新调整Ki和Kd以反映此新更改。 这就是第39-42行的全部内容。

在第29行将采样时间转换为秒。其实要不要没什么SEO靠我区别,但是这样允许用户以1 / sec和s的单位输入Ki和Kd,而不是1 / mS和mS。

上面的代码主要做了以下更改:

PID算法会以固定的时间间隔进行计算【第11行】不需要再乘除时间变化。采样时间是一SEO靠我个常数,可以将其从计算代码中移出【第15、16行】,然后将其于调整常数一起输入【第31、32行】。这样做省去了每次PID计算中的一次乘法和一次除法运算。

细心的小朋友肯定会发现,这种方法总是要搞个时间戳SEO靠我,记录每次调用PID算法的时间,通过判断时间间隔的大小来确定是否进行PID计算,在轮询系统中也不一定能保证采样时间是固定的,总归会有一点点偏差。通过定时器中断的方式周期性的调用PID计算函数不香吗?香SEO靠我,当然香,个人认为通过定时器中断来进行PID计算更好,也不复杂,在单片机的应用中,只需要掌握定时器中断就行了,有机会后面再展开吧。

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2