【嵌入式必读】一文彻底理解PID自整定及PID自整定代码设计

news2024/11/26 19:42:06

文章目录

  • 1. 前言
  • 2. PID简介
  • 3. 常用的PID自整定方法
    • 3.1 临界度比例法
    • 3.2 衰减曲线法
  • 4. 继电反馈整定法原理
    • 4.1 继电反馈自整定的基本思想
    • 4.2 继电反馈自整定原理
  • 5. 算法设计
  • 6 原代码
    • 6.1 头文件
    • 6.1 C文件代码
  • 7. 应用举例
    • 7.1 初始化
    • 7.2 新建一个自整定对象
    • 7.3 进行自整定

1. 前言

  PID算法是工业上一种常用的控制算法,因其容易理解、实现简单、鲁棒性强等特性而得到广泛应用。作为一名嵌入式工程师,我们经常和PID算法打交道,对PID算法的应用也早已烂熟于心。但是,要想让PID系统运行的稳定,有一个优秀的PID算法远远不够,还需要一组合适的PID值。在合适的PID值和算法的相互配合下,PID系统才能运行稳定。PID算法应用中最为重要的是参数的整定。通常,参数的整定高度依赖工程技术人员的经验,而且实际系统又是千差万别,存在着诸如非线性、时变、大滞后等因素。因此传统的经验整定方法既耗时又费力,整定得到的效果还不一定理想。使用过PID的工程师都知道,PID整定,永远是一件非常头疼的事情。

  那么,有没有一种方法,可以自动找出合适的PID参数,这种方法就是所谓的PID自整定。下面,我们就一起来看看PID自整定方法。

2. PID简介

  所谓PID即为Proportional(比例)、Integral(积分)、和Derivative(微分)三词的简称。在每个循环周期内,PID控制器利用SetPoint(输入值)和Measured Variable(测量值)之间的Error(偏差)来计算下个周期的PID输出值控制器Out Value(输出值)。

  Proportional(比例)为Error(偏差值)和一个常量系数Kp的乘积。

  Integral(积分)为Error(偏差值)的累计值和一个常量系数Ki的乘积。

  Derivative(微分)为Error(偏差值)的变化速率和一个常量系数Kd的乘积。

  最终,将上述Proportional(比例)、Integral(积分)和Derivative(微分)相加,即为最终的PID输出值。

上述用公式表示,即为:

u ( t ) = K p e ( t ) + K i ∫ 0 t e ( t ) d t + k d d e ( t ) d t − − − − − − − − − − − − − − − ( 1 ) \boxed{u(t) = K_pe(t) + K_i\int_0^te(t)dt + k_d{de(t)\over{dt}}} ---------------(1) u(t)=Kpe(t)+Ki0te(t)dt+kddtde(t)(1)

其中 K p K_p Kp, K i K_i Ki, K d K_d Kd为PID常数,其表示在t时刻的 u ( t ) u(t) u(t)(PID输出值)的计算方式。

  PID算法的性能很大程度上取决于是否选择了合适的PID常数,如果选择了合适的PID常数,则控制通常平滑收敛,如果选择的PID常数不合适,则系统可能会震荡、不稳定甚至失去控制。

  PID算法还有另一种形式的的公式表示,如下:

u ( t ) = K p [ e ( t ) + 1 T i ∫ 0 t e ( t ) d t + T d d e ( t ) d t ] − − − − − − − − − − − − − − − ( 2 ) \boxed{u(t) = K_p[e(t) + {1\over{T_i}}\int_0^te(t)dt + T_d{de(t)\over{dt}}]} ---------------(2) u(t)=Kp[e(t)+Ti10te(t)dt+Tddtde(t)](2)

  以上两个公式是等效的,(1)式可以看成是(2)式的简化版本,其中 K i = K p 1 T i K_i = K_p{1\over{T_i}} Ki=KpTi1 K d = K p T d K_d = K_pT_d Kd=KpTd

  在公式(2)中, T i T_i Ti T d T_d Td分别被称为积分时间和微分时间, K p K_p Kp是整个控制器总体的比例系数, K p K_p Kp的改变会影响积分项和微分项.

  我们后续采用公式2来进行PID整定。

3. 常用的PID自整定方法

  要实现PID参数的自整定,首先要对被控制的对象有一个了解,然后选择相应的参数计算方法完成控制器参数的设计。据此,可将PID参数自整定分成两大类:辨识法和规则法。基于辨识法的PID参数自整定,被控对象的特性通过对被控对象数学模型的分析来得到,在对象数学模型的基础上用基于模型的一类整定法计算PID参数。基于规则的PID参数自整定,则是运用系统临界点信息或系统响应曲线上的一些特征值来表征对象特性,控制器参数由基于规则的整定法得到。

  在本文,我们只对常用的规则法PID方法进行描述,对辨识法PID算法不做描述,有兴趣的朋友,可查阅相关资料。

  常用的规则法有临界比例度法,衰减曲线法和继电器整定法。

3.1 临界度比例法

  对于一个PID控制系统,仅在比例作用下,由小到大的改变比例常数,直到输出值出现即不发散也不衰减的等振幅振荡,此时的控制系统的比例常数为临界比例常数 C k C_k Ck,被调参数的工作周期为临界周期 T k T_k Tk

在这里插入图片描述

图 1 临界比力度法形成的等振幅振荡

根据临界比力度法的整定经验公式可得出PID参数。

控制器类型KpTiTd
P 0.5 C k 0.5C_k 0.5Ck无穷大0
PI 0.45 C k 0.45C_k 0.45Ck 0.833 T k 0.833T_k 0.833Tk0
PID 0.56 C k 0.56C_k 0.56Ck 0.50 T k 0.50T_k 0.50Tk 0.125 T k 0.125T_k 0.125Tk
表格 1临界度比例法计算PID经验公式

3.2 衰减曲线法

  衰减曲线法是临界比例法的一种变形。

  在纯比例作用下,比例系数逐渐增加的情况下,会出现如下图所示的振荡过程。

在这里插入图片描述

图 2 衰减曲线

这时,控制过程的比例系数称为n:1衰减比例系数 C k C_k Ck,两个峰之间的距离,称为n:1衰减周期 T k T_k Tk。常用的衰减比例有 4 : 1 4:1 41 10 : 1 10:1 101

得到衰减比例系数 C k C_k Ck和衰减周期 T k T_k Tk,根据以下经验公式,即可计算出相应的PID。

控制器类型KpTiTd
P C k C_k Ck无穷大0
PI 0.833 C k 0.833C_k 0.833Ck 0.5 T k 0.5T_k 0.5Tk0
PID 1.25 C k 1.25C_k 1.25Ck 0.30 T k 0.30T_k 0.30Tk 0.1 T k 0.1T_k 0.1Tk
表格 2衰减比4:1计算PID经验公式

控制器类型KpTiTd
P C k C_k Ck无穷大0
PI 0.833 C k 0.833C_k 0.833Ck 2 T k 2T_k 2Tk0
PID 1.25 C k 1.25C_k 1.25Ck 0.30 T k 0.30T_k 0.30Tk 0.1 T k 0.1T_k 0.1Tk
表格 3衰减比10:1计算PID经验公式

  以上两种方法原理简单,但是真正应用到工程中,却发现实际不好精确控制,比如衰减比的判断等等不好把握,另外寻找相应的振荡,需要花费不少时间。接下来我们介绍本文的主角,继电器反馈整定方法,此方法实现简单,可精确操作,目前已成为主流自整定方法。

4. 继电反馈整定法原理

4.1 继电反馈自整定的基本思想

  临界比例度法,衰减曲线法,都是通过在纯比例作用下,让控制器产生振荡波形。而继电反馈整定法是通过改变控制器的输出值,来造出一个振荡波型。

  首先,确定PID输出的最大值和最小值,例如,有个温控系统,PID输出为占空比,则确定最大的占空比和最小的占空比,假如分别为100%,0%;

  其次,给定一个常用的设定值。例如,该温控系统,控制温度范围为0~80℃。那么设置设定值为50℃。这个设定值一般设定为常用值,例如在这个温度控制系统中,我们选择一个比常温高的温度即可。

  再次,当测量值比设定值小的时候,我们输出最大值,当测量值比设定值大的时候,我们输出最小值。如此,循环至少3个周期后,我们就可以得到测量值的振荡波形。从这个振荡波形中,我们可以提取到系统的特征参数,从而得到我们想要的PID参数。例如,在该温控系统中,不加热情况下,温度测量值肯定比设定值50℃小,那么首先我们将输出占空比设定为100%,时刻检测温度值,当测量值大于50℃的时候,立马将输出占空比切换为0%。继续检测测量值,当测量值小于50℃的时候,我们将占空比立马切换为100%。如此,至少3个循环周期后,我们就可以得到一个温度测量值的振荡波形。

在这里插入图片描述

图 3温控系统自整定过程图
  我们用图来表示上述举例的温度整定过程,如图 3,黑色方波表示输出值占空比,红色线表示测量的实际温度,绿色线表示设定值。

  最后,提取系统的特征参数。如图 3所示,继电整定法的特征参数主要有测量值振荡波形的周期( T u T_u Tu)、测量值振荡波形的幅值(A)和输出值的幅值(d)。据此,我们求出了临界振荡周期Tu,可利用公式 K c = 4 d π A K_c={4d\over{πA}} Kc=πA4d计算出临界增益 K c K_c Kc。然后就可以根据 Ziegle-Nichols算法确定PID参数。

控制器类型KpTiTd
P K u / 2 Ku/2 Ku/2无穷大0
PI K u / 2.5 Ku/2.5 Ku/2.5 T u / 1.25 Tu/1.25 Tu/1.250
PID 0.6 K u 0.6Ku 0.6Ku T u / 2 Tu/2 Tu/2 T u / 8 Tu/8 Tu/8
佩森积分法则PID 0.7 K u 0.7Ku 0.7Ku 0.4 T u 0.4Tu 0.4Tu 0.15 T u 0.15Tu 0.15Tu
超调PIDKu/3 T u / 2 Tu/2 Tu/2 T u / 3 Tu/3 Tu/3
不超调PIDKu/5 T u / 2 Tu/2 Tu/2 T u / 1.25 Tu/1.25 Tu/1.25
表格 4 Ziegle-Nichol PID经验公式

  根据上述流程,即可用继电反馈的方法整定出PID调节器参数。继电自整定法是一种简单的自适应控制方法,它所需要的数据量小,实现简单,调节效果好,特别适用于内存量较小的调节器,因而得到广泛的应用。

4.2 继电反馈自整定原理

  为什么我们采用这一方式就能确定PID控制的参数呢?这是因为振荡波形的特性是由被控对象的特性决定的。我们可以将整定过程中的整个控制系统的框图等效如下:

在这里插入图片描述

图 4控制系统的框图

  当我们根据测量值与设定值的对比关系来给出最大或最小输出 时,基于被控对象的特性会产生一定频率和幅值的振荡波,从而我们就能确定系统的振荡频率 ω c ω_c ωc与临界增益 K c K_c Kc。比较常用的确定系统的振荡频率 ω c ω_c ωc与增益 K c K_c Kc的方法是描述函数法。所谓描述函数法,实际上是根据非线性环节输入信号与输出信号之间基波分量关系来进行近似的一种有效方法。

  关于非线性特征的描述函数 N ( A ) N(A) N(A)来说,就是当输入是正弦信号$ Asin(ωt) 时,输出的基波分量 时,输出的基波分量 时,输出的基波分量Ysin(ωt+φ)$对输入正弦量的复数比,即:

N ( A ) = Y A ∠ φ = A 1 2 + A B 1 2 A ∠ a r c t g ( A 1 / B 1 ) − − − − − − − − − − − − − − − ( 3 ) \boxed{N(A) = {Y\over{A∠φ}} = {\sqrt{A_1^2+AB_1^2}\over{A∠arctg}}(A_1/B_1)} ---------------(3) N(A)=AφY=AarctgA12+AB12 (A1/B1)(3)

  其中 A 1 A1 A1 B 1 B1 B1是输出 Y ( t ) Y(t) Y(t)的傅立叶级数的一次项系数。

  实际的带有回环的节点非线性环节特性的描述函数可以表示为:

N ( A ) = 4 d π A 2 ∗ A 2 − ε 2 − j ε − − − − − − − − − − − − − − − ( 4 ) \boxed{N(A) = {4d\over{πA^2}*{\sqrt{A^2-ε^2}-jε}}} ---------------(4) N(A)=πA2A2ε2 jε4d(4)

  公式中 A A A为正弦波幅值, d d d为回环幅值(即为图 3中的占空比), ε ε ε为回环宽度的一半。这里我们构建继电环节时,我们可以认为它是一个理想的继电环节,也就是说不带有回环,即 ε ε ε=0,于是就有:


N ( A ) = 4 d π A − − − − − − − − − − − − − − − ( 5 ) \boxed{N(A) = {4d\over{πA}}} ---------------(5) N(A)=πA4d(5)

  设被控对象的传递函数为如下形式:


G ( s ) = K e − τ s 1 + T s − − − − − − − − − − − − − − − ( 6 ) \boxed{G(s)= {K_e^{-τs}\over{1+T_s}}} ---------------(6) G(s)=1+TsKeτs(6)

  其中 K K K为对象的增益, T T T为对象的时间常数, τ τ τ为对象的滞后时间。

  根据前面继电回路结构框图,在这个简单的反馈系统中,闭环特征方程发生振荡的条件可以写为:


1 + N ( A ) G ( s ) = 0 ( s = j ω c ) ,即 G ( j ω c ) = − 1 N ( A ) − − − − − − − − − − − − − − − ( 7 ) \boxed{1+N(A)G(s)=0 (s=jω_c),即G(jω_c )= {-1\over{N(A)}} } ---------------(7) 1+N(A)G(s)=0(s=jωc),即G(jωc)=N(A)1(7)

  则可得出振荡频率 ω c ω_c ωc与增益 K c K_c Kc的关系为:


K c = 1 ∣ G ( j ω c ) ∣ = N ( A ) = 4 d π A − − − − − − − − − − − − − − − ( 8 ) \boxed{K_c= {1\over{|G(jω_c )|}}= N(A) = {4d\over{πA}} } ---------------(8) Kc=G(jωc)1=N(A)=πA4d(8)

  系统的振荡周期 T c T_c Tc可以通过测量输出曲线相邻峰值的时间得到。 至此我们就得到了临界频率 ω c ω_c ωc所对应的临界增益 K c K_c Kc和临界振荡周期 T c T_c Tc

  在图 3中,振荡波形周期为 T c T_c Tc的值,幅值A即为公式 K c = 4 d π A K_c={4d\over{πA}} Kc=πA4d中的A的值,d为最大占空比与最小占空比差的一半,即为50。 据此,我们可以求出临界增益 K c K_c Kc和临界振荡周期 T c T_c Tc

  在得到被控对象的临界增益和临界振荡周期后,就可以根据 Ziegle-Nichols算法确定PID参数。

5. 算法设计

  在上节,我们已经描述清楚了PID参数继电器反馈整定方法的操作流程以及原理。那么我们究竟如何实现这种算法呢?接下来我们就来设计基于PID参数继电器反馈整定方法的具体实现方法。

  使用PID参数继电器反馈整定方法主要涉及三个方面的内容。第一是通过人为主动输出一个方波,让系统产生振荡,这是测量出临界比例 K c K_c Kc和临界周期 T c T_c Tc的关键所在。第二是产生系统振荡后,如何从波形中提取出临界周期 T c T_c Tc和振荡波形幅值 A A A。第三是根据得到的振荡波型特征数据,计算出PID参数。所以我们从这三个方面的内容来考虑基于继电反馈的PID参数整定算法。

5.1 振荡的生成

  首先,我们来分析振荡波形是如何产生的。在继电反馈整定算法中,通过控制执行单元输出一个方波,从而使测量值随之变化,最终形成振荡波形。

  PID控制系统根据执行单元的输出类型,可分为双向控制和单向控制。双向控制系统,即为PID执行单元在两个相对的方向上均可进行控制;单向系统,PID只能在一个方向上进行控制。例如,对于一个温控系统,若执行单元既能加热,也能制冷,即为双向控制,若只能加热或制冷,即为单向控制系统。

  执行单元输出方波时,对于单向控制系统,方波下边沿应为执行单元输出的值对测量值无作用时的最大值。例如,一个温控系统,如果PID输出占空比小于5%时,对实时温度无影响,则应该设置方波下边沿为5%,方波上边沿可选择一个对输出有作用的PID值即可,但是为了整定方便,一般情况下,选择输出最大值。

  执行单元输出方波时,对于双向系统,方波下边沿应该选择反向作用的某个输出值,上边沿应选择正向作用的某个输出值。但是也为了整定方便,一般选择反向最大值和正向最大值。

  为了描述方便,后续不再区分单向控制系统和双向控制系统,方波的下边沿和上边沿的值分别统称为底输出和高输出。

  PID控制系统根据控制执行单元的输出值和反馈值的关系可分为正向控制系统和反向控制系统。正向控制系统,即为执行单元输出值增大,反馈值随之增大;反向控制系统与之相反,执行单元输出值增大,反馈值随之减小。

  后续分析,我们以正向系统进行分析,反向系统,将PID输出的方波翻转即可。

  一般在设定值大于测量值时,我们将执行单元的输出切换到相应的高输出,这时测量值将会随之而上升。当测量值上升到大于设定值时,我们将执行单元的输出切换到相应的低输出,这时测量值将会随之而下降。如此往复,我们就能得到测量值的振荡曲线。

  在每次切换执行单元的时候,我们记录一次转换次数。同时观察测量值与设定值的相对大小,每次测量值由大于设定值变为小于设定值,或者由小于设定值变为大于设定值都称之为一次过零。如果我们检测到3次过零,则我们就可以认为系统产生了振荡。

在这里插入图片描述

图 5 产生的系统振荡

  一般系统刚产生振荡时,产生的振荡波形不稳定,具体需要多少个波形后才能稳定,可能因控制系统而异。那我们怎么判断系统有没有稳定呢?我们可以通过计算至少相邻3个周期的周期和幅值的标准差。若标准差差满足要求,则认为系统稳定,具体标准差值为多少,则通过后期调试确定。

  另外,由于测量值存在噪声,所以信号是起伏的,其经过输入信号,可能会交叉几次,导致无法准确判断何时切换输出的方波值。

在这里插入图片描述

图 6 测量噪声
  为了解决上述问题,我们有2种方案可供选择。

方案一:

  通过让用户设置一个噪声带,从而可以创建两条触发线。高触发线的值为设定值与噪声带一半的和,输出值大于高触发线,则输出方波切换为高输出。底触发线的值为设定值与噪声带一半的差值,低于低触发线,则输出方波切换为低输出。

方案二:

  通过用户设置一个回滞量,此回滞量表示输出值和测量值相等后,再采集多少个测量值后,对方波进行反转。
上述两种方案,均可执行。在实际代码设计中,我选用了后一种方案。

5.2 提取出临界周期 T c T_c Tc和振荡波形幅值 A A A

  要提取出临界周期 T c T_c Tc和振荡波形幅值 A A A,我们需要识别峰值。最大峰值和最小峰值差的一半,即为幅值大小;两个相邻最大峰值或者最小峰值间的时间差即为临界周期 T c T_c Tc

  对于一个光滑的曲线,求最大值、最小值求导即可,但是对于带有噪声的输出值,这种方法不是最有效的方案。我采用以下方案:

  执行单元的输出值和设定值的交叉点分为2类,一类为交叉点后,输出值逐渐增大,将这类交叉点命名为增趋势交叉点,另一类交叉点后输出值逐渐减小,将这类交叉点命名为减趋势交叉点。在增趋势交叉点和减趋势交叉点之间,有波形最大值,减趋势和增趋势交叉点之间有波形最小值。因此,我们通过识别交叉点类型,然后定义一个变量来存储最值,找到交叉点的时候给其赋初值为设定值,然后不断的将其变量与输出值进行比较,并不断的更新最值,到下一个交叉点的时候,在变量中存储的值即为最值。

在这里插入图片描述

图 7增趋势交叉点和减趋势交叉点
  通过以上方法,我们可以找到若干个周期的最值,最后通过求平均值,得到平均最值,然后求出临界周期T_c和振荡波形幅值A。

5.3 计算出PID参数

  求出临界周期T_c和振荡波形幅值A后,计算PID参数就非常简单。但是在表格 4中提供的求PID的公式有6个之多,究竟选用哪个公式比较合理呢。常用的公式应该为第2、3、4个。为了调试方便,我们可以定义一个枚举变量,来表示不同的公式,通过改变此值,来得到不同的PID值进行调试。

6 原代码

6.1 头文件


    /*-----------------------------------------------------------------------------
                                共享宏定义
    -----------------------------------------------------------------------------*/
    #define AUTO_TUNE_OBJ_NUM       1   /*定义同时需要自整定对象资源的最大个数*/

    typedef  int32_t   TUNE_ID_t;       /*自整定类型定义,正常Id为>=0,若小于零,则返回的Id错误*/

    /*-----------------------------------------------------------------------------
                                数据类型定义
    -----------------------------------------------------------------------------*/
    typedef enum                 /*PID控制器类型*/
    {  
        CONTROLER_TYPE_PI,       /*PI控制器*/
        CONTROLER_TYPE_PID ,     /*PID控制器*/
    }TUNE_CONTROLER_TYPE_t;

    typedef enum                 /*PID状态*/
    {
        TUNE_INIT = 0,          /*PID自整定初始化中*/
        TUNE_START_POINT,       /*寻找起始点*/
        TUNE_RUNNING,           /*PID自整定中*/
        TUNE_FAIL,              /*整定失败*/
        TUNE_SUCESS,            /*整定成功*/
        
    }TUNE_STAT_t;

    typedef enum                /*驱动器作用*/
    {
        POSITIVE_ACTION,        /*设定值大于测量值时,执行单元执行高输出*/
        NEGATIVE_NATION,        /*设定值大于测量值时,执行单元执行低输出*/
    }DRIVER_ACTION_TYPE_t;

    typedef struct TUNE_CFG_PARAM_tag
    {
        TUNE_CONTROLER_TYPE_t cTrlType;         /*控制器类型,默认PD控制器*/
        DRIVER_ACTION_TYPE_t  acterType;        /*驱动器作用类型,默认正向作用*/
        float maxOutputStep;                    /*最大输出阶跃值,默认值为50*/
        float minOutputStep;                    /*最小输出阶跃值,默认值为0*/
        uint32_t hysteresisNum;                 /*反馈值在设定值处的迟滞相应个数,默认为5*/
        float setpoint;                         /*整定设定值,默认值为为50*/
        float ampStdDeviation;                 /*幅值标准差预期值,用来计算自整定波形是否稳定*/
        float cycleStdDeviation;                /*周期标准差预期值,用来计算自整定波形是否稳定*/
    }TUNE_CFG_PARAM_t, *pTUNE_CFG_PARAM_t;      /*pid自整定对象配置参数*/

    typedef struct TUNE_OGJ_tag *pTUNE_OBJ_t;   /*PID自整定参数*/
   /*-----------------------------------------------------------------------------------
    函数原型:  void TUNE_Init(void)
    功    能:  初始化自整定相关参数,使用默认的TUNE_CFG_PARAM_t参数初始化自整定参数
                default cTrlType = CONTROLER_TYPE_PI,
                default outputStep = 50,
                default hysteresisNum = 5
    输入参数:	NA
    输出参数:	NA
    返 回 值:	true:pram is protected can't be modifid; false: writable
    -----------------------------------------------------------------------------------*/
    extern void TUNE_Init(void);

    /*-----------------------------------------------------------------------------------
    函数原型:  TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam)
    功    能:  新建一个PID自整定对象
    输入参数:	pParam:自整定对象配置参数
    输出参数:	NA
    返 回 值:	<0,则新建自整定对象失败,可能对象资源已经用完,需要通过更改AUTO_TUNE_OBJ_NUM宏定义
                增加自整定对象资源,>=0,则为分配的自整定id,后续函数调用均通过此Id
    -----------------------------------------------------------------------------------*/
    extern TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam);

    /*-----------------------------------------------------------------------------------
    函数原型:  TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam)
    功    能:  释放ID所示自整定对象资源
    输入参数:	id:自整定ID
    输出参数:	NA
    返 回 值:	false:资源释放失败,true:资源释放成功
    注意事项:  只有在tuneStat为TUNE_FAIL或者TUNE_SUCESS状态下,才允许释放资源
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_Release(TUNE_ID_t id);
    /*-----------------------------------------------------------------------------------
    函数原型:  bool TUNE_Work(TUNE_ID_t id, float feedbackVal, float*outputVal)
    功    能:  自整定任务
    输入参数:	id:自整定ID
                delayMsec:调用的时间间隔
    输出参数:	outputVal:输出值
    返 回 值:	true:自整定完成,false:正在自整定中
    注意事项:  该函数需要以固定的时间间隔调用
    -----------------------------------------------------------------------------------*/
    extern TUNE_STAT_t TUNE_Work(TUNE_ID_t id, float feedbackVal, float*outputVal, uint32_t delayMsec);
    /*-----------------------------------------------------------------------------------
    函数原型:  bool TUNE_SetActerType(TUNE_ID_t id, float maxStep,DRIVER_ACTION_TYPE_t type)
    功    能:  设置驱动器类型
    输入参数:	id:自整定ID
                type:驱动器类型
    输出参数:	NA
    返 回 值:	true:设置成功,false:设置失败
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_SetActerType(TUNE_ID_t id, DRIVER_ACTION_TYPE_t type);
    /*-----------------------------------------------------------------------------------
    函数原型:  bool TUNE_Setsetpoint(TUNE_ID_t id, float setpoint)
    功    能:  设置自整定设定值
    输入参数:	id:自整定ID
                setpoint:自整定设置值
                
    输出参数:	NA
    返 回 值:	true:设置成功,false:设置失败
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_Setsetpoint(TUNE_ID_t id, float setpoint);

    /*-----------------------------------------------------------------------------------
    函数原型:   bool TUNE_SetOutStep(TUNE_ID_t id, float maxStep,float minStep)
    功    能:  设置输出阶跃值
    输入参数:	id:自整定ID
                maxStep:最大输出阶跃值
                minStep:最大输出阶跃值
    输出参数:	NA
    返 回 值:	true:设置成功,false:设置失败
    ------------------------------------------------------------------------------------*/
    bool TUNE_SetOutStep(TUNE_ID_t id, float maxStep,float minStep);

    /*-----------------------------------------------------------------------------------
    函数原型:   bool TUNE_SetCtrlType(TUNE_ID_t id, TUNE_CONTROLER_TYPE_t type)
    功    能:  设置控制器类型
    输入参数:	id:自整定ID
                type:
                    CONTROLER_TYPE_PI,PI控制器,积分项不使用
                    CONTROLER_TYPE_PID,PID控制器
    输出参数:	NA
    返 回 值:	true:设置成功,false:设置失败
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_SetCtrlType(TUNE_ID_t id, TUNE_CONTROLER_TYPE_t type);

    /*-----------------------------------------------------------------------------------
    函数原型:   float TUNE_GetKp(TUNE_ID_t id, float *pfactorP)
    功    能:   获取整定后的P参数
    输入参数:	id:自整定ID
    输出参数:	pfactorP:整定后的P参数
    返 回 值:	true:获取成功,否则失败
    -----------------------------------------------------------------------------------*/
    extern float TUNE_GetKp(TUNE_ID_t id, float *pfactorP);

    /*-----------------------------------------------------------------------------------
    函数原型:   float TUNE_GetKp(TUNE_ID_t id)
    功    能:   获取整定后的I参数
    输入参数:	id:自整定ID
    输出参数:	pfactorI:整定后的I参数
    返 回 值:	true:获取成功,否则失败
    -----------------------------------------------------------------------------------*/
    extern float TUNE_GetKi(TUNE_ID_t id,float *pfactorI);

    /*-----------------------------------------------------------------------------------
    函数原型:   float TUNE_GetKp(TUNE_ID_t id)
    功    能:   获取整定后的D参数
    输入参数:	id:自整定ID
    输出参数:	pfactorD:整定后的D参数
    返 回 值:	true:获取成功,否则失败
    ---------------------------------------------------------------------------------------*/
    extern float TUNE_GetKd(TUNE_ID_t id,float *pfactorD);

    /*-----------------------------------------------------------------------------------
    函数原型:   float TUNE_GetKp(TUNE_ID_t id)
    功    能:  获取整定后的PID参数
    输入参数:	id:自整定ID
    输出参数:	NA
    返 回 值:	true:获取成功,否则失败
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_GedPID(TUNE_ID_t id, float*paramP, float*paramI, float*paramD);
    /*-----------------------------------------------------------------------------------
    函数原型:   float TUNE_GetStat(TUNE_ID_t id, TUNE_STAT_t *pStat)
    功    能:  获取PID自整定状态
    输入参数:	id:自整定ID
    输出参数:	stat,自整定状态
    返 回 值:	true:获取成功,否则失败
    -----------------------------------------------------------------------------------*/
    extern bool TUNE_GetStat(TUNE_ID_t id, TUNE_STAT_t *pStat);
    #endif/* __TUNE__H*/

6.1 C文件代码


/*-----------------------------------------------------------------------------
                            自有宏定义
-----------------------------------------------------------------------------*/
#define LAST_PEAK_NUM    3       /*存储的最新峰个数*/
#define MAX_CYCLE        100     /*自整定最大震荡周期数,震荡周期数超过此值,则整定失败*/
#define MAX_TIME_MS      3600000 /*自整定最大震荡毫秒数,震荡时间数超过此值,则整定失败*/
/*-----------------------------------------------------------------------------
                            数据类型定义
-----------------------------------------------------------------------------*/
typedef enum                     
{
    FEEDBACK_BELOW_INPUT = 0,   /*反馈值小于于设定值*/
    FEEDBACK_ABOVE_INPUT,       /*反馈值大于设定值*/
}TUNE_OFFSET_STAT_t;            /*偏差状态*/


typedef struct 
{
    float feedabackVal;     /*反馈值*/
    uint32_t milSecond;     /*反馈值对应的相对时间*/
}PEAK_VAL_t;                /*峰值对应的反馈值和对应的时间*/


typedef struct                  /*一个周期的峰值*/
{
    PEAK_VAL_t maxPeak;        /*一个周期内的最大值*/
    PEAK_VAL_t minPeak;        /*一个周期内的最小值*/
}PEAK_VAL_IN_PERIOD_t;



typedef struct TUNE_OGJ_tag                 /*PID自整定参数*/
{
    TUNE_STAT_t tuneStat;                   /*整定状态位*/
    TUNE_CONTROLER_TYPE_t cTrlType;         /*控制器类型,PI或者PID*/
    DRIVER_ACTION_TYPE_t  acterType;        /*驱动器作用类型*/
    TUNE_ID_t  tuneId;                      /*自整定ID号,用于内部访问相应自整定对象使用*/
    TUNE_OFFSET_STAT_t offetStat;           /*反馈值相对于设定值的偏移状态*/
    float feedbackVal;                      /*pid反馈值*/
    float outputVal;                        /*pid输出值*/
    float setpoint;                         /*pid设定值*/
    float maxOutputStep;                    /*最大输出阶跃值,默认值为1*/
    float minOutputStep;                    /*最小输出阶跃值,默认值为0*/
    
    uint32_t tuneCounter;                   /*整定计时器*/
    uint32_t cycleCounter;                  /*周期计数器*/
    uint32_t hysteresisNum;                 /*反馈值在设定值处的迟滞相应个数*/
    uint32_t riseHysteresisCounter;         /*上升迟滞计数器*/
    uint32_t fallHysteresisCounter;         /*下降迟滞计数器*/
    uint32_t fullCycleFlag;                 /*运行一个完整周期的标志*/
    PEAK_VAL_IN_PERIOD_t lastPeakVal[LAST_PEAK_NUM];    /*存储找到的最新的峰值*/
    int32_t peakWriPos;                    /*下一个需要写峰的位置*/
    float Ku;                               /*整定结果幅值*/
    float Tu;                               /*整定结果周期*/
    float Kp;                               /*整定结果比例值*/
    float Ki;                               /*整定结果积分值*/
    float Kd;                               /*整定结果微分值*/

    float ampStdDeviation;                  /*幅值标准差预期值,用来计算自整定波形是否稳定*/
    float cycleStdDeviation;                /*周期标准差预期值,用来计算自整定波形是否稳定*/
    float CurAmpStdDeviation;
    float CurCycleStdDeviation; 
 }TUNE_OBJ_t,*pTUNE_OBJ_t;


/*-----------------------------------------------------------------------------
                            数据结构定义
-----------------------------------------------------------------------------*/
static TUNE_OBJ_t s_tuneObject[AUTO_TUNE_OBJ_NUM];      /*PID自整定对象*/


/*-----------------------------------------------------------------------------
                            内部函数声明
-----------------------------------------------------------------------------*/
static bool TUNE_StructInitToDefaultVal(TUNE_ID_t id);
static bool TUNE_FsmReset(TUNE_ID_t id);
static float TUNE_CalStdDeviation(float * fdata, uint32_t len);
static void TUNE_PeakValReset(TUNE_ID_t id,int32_t channel, float setpoint);
static  bool TUNE_CalPID(TUNE_ID_t id);

/*---------------------------------------------------------------------------------------
 函数原型:  void TUNE_Init(void)
 功    能:  初始化自整定相关参数,使用默认的TUNE_CFG_PARAM_t参数初始化自整定参数
            default cTrlType = CONTROLER_TYPE_PI,
            default outputStep = 50,
            default hysteresisNum = 5
            default acterType = POSITIVE_ACTION
 输入参数:	NA
 输出参数:	NA
 返 回 值:	NA
---------------------------------------------------------------------------------------*/
void TUNE_Init(void)
{
    for(int16_t i = 0; i< AUTO_TUNE_OBJ_NUM; i++)
    {
       TUNE_StructInitToDefaultVal(i);
    }
}

/*---------------------------------------------------------------------------------------
 函数原型:  static void TUNE_StructInitToDefaultVal(TUNE_ID_t id)
 功    能:  自整定对象初始化为默认值
 输入参数:	NA
 输出参数:	NA
 返 回 值:	true:成功,false:失败
---------------------------------------------------------------------------------------*/
static bool TUNE_StructInitToDefaultVal(TUNE_ID_t id)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;

    s_tuneObject[id].tuneId = -1;
    s_tuneObject[id].cTrlType = CONTROLER_TYPE_PI;
    s_tuneObject[id].maxOutputStep = 50;
    s_tuneObject[id].hysteresisNum = 5;
    s_tuneObject[id].acterType = POSITIVE_ACTION;
    if(!TUNE_FsmReset(id))
    {
        return false;
    }
    return true;
}

/*---------------------------------------------------------------------------------------
 函数原型:  static void TUNE_FsmReset(TUNE_ID_t id)
 功    能:  状态机复位
 输入参数:	NA
 输出参数:	NA
 返 回 值:	true:成功,false:失败
---------------------------------------------------------------------------------------*/
static bool TUNE_FsmReset(TUNE_ID_t id)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;

    s_tuneObject[id].tuneStat = TUNE_INIT;
    s_tuneObject[id].tuneCounter = 0;
    s_tuneObject[id].cycleCounter = 0;
    s_tuneObject[id].riseHysteresisCounter = 0;
    s_tuneObject[id].fallHysteresisCounter = 0;
    s_tuneObject[id].offetStat = FEEDBACK_ABOVE_INPUT;
    s_tuneObject[id].peakWriPos = 0;
    
    return true;
}
/*---------------------------------------------------------------------------------------
 函数原型:  TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam)
 功    能:  新建一个PID自整定对象
 输入参数:	pParam:自整定对象配置参数
 输出参数:	NA
 返 回 值:	<0,则新建自整定对象失败,可能对象资源已经用完,需要通过更改AUTO_TUNE_OBJ_NUM宏定义
            增加自整定对象资源,>=0,则为分配的自整定id,后续函数调用均通过此Id
 ---------------------------------------------------------------------------------------*/
TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam)
{
    for(int16_t i=0; i<AUTO_TUNE_OBJ_NUM; i++)
    {
        if(s_tuneObject[i].tuneId <0)
        {
            s_tuneObject[i].tuneId = i;
            if(pParam == NULL) return i;
            s_tuneObject[i]. cTrlType = pParam->cTrlType;
            s_tuneObject[i].maxOutputStep = pParam->maxOutputStep;
            s_tuneObject[i].minOutputStep = pParam->minOutputStep;
            s_tuneObject[i].hysteresisNum = pParam->hysteresisNum;
            s_tuneObject[i].setpoint = pParam->setpoint;
            s_tuneObject[i].acterType = pParam->acterType;
            s_tuneObject[i].ampStdDeviation = pParam->ampStdDeviation;
            s_tuneObject[i].cycleStdDeviation = pParam->cycleStdDeviation;
            return i;
        }
    }
    return -1;
}

/*---------------------------------------------------------------------------------------
 函数原型:  TUNE_ID_t TUNE_New(pTUNE_CFG_PARAM_t pParam)
 功    能:  释放ID所示自整定对象资源
 输入参数:	id:自整定ID
 输出参数:	NA
 返 回 值:	false:资源释放失败,true:资源释放成功
 注意事项:  只有在tuneStat为TUNE_FAIL或者TUNE_SUCESS状态下,才允许释放资源
 ---------------------------------------------------------------------------------------*/
bool TUNE_Release(TUNE_ID_t id)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;


    if(s_tuneObject[id].tuneStat == TUNE_FAIL
        ||s_tuneObject[id].tuneStat == TUNE_SUCESS
        ||s_tuneObject[id].tuneStat == TUNE_INIT)
    {
        TUNE_StructInitToDefaultVal(id);           
        return true;
    }
    return false;
}

/*---------------------------------------------------------------------------------------
 函数原型:  bool TUNE_Work(TUNE_ID_t id, float feedbackVal, float*outputVal)
 功    能:  自整定任务
 输入参数:	id:自整定ID
            delayMsec:调用的时间间隔
 输出参数:	outputVal:输出值
 返 回 值:	true:自整定完成,false:正在自整定中
 注意事项:  该函数需要以固定的时间间隔调用
---------------------------------------------------------------------------------------*/
TUNE_STAT_t TUNE_Work(TUNE_ID_t id, float feedbackVal, float*outputVal, uint32_t delayMsec)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;;
    if(s_tuneObject[id].tuneId<0) return false;
    
    float outputInSetVGreaterFBV;        //设定值大于反馈值时,使用的输出值
    float outputInSetVLessFBV;           //设定值小于反馈值时,使用的输出值
    
    TUNE_OBJ_t *this = &s_tuneObject[id];
    
    this->feedbackVal = feedbackVal;
    
    this->tuneCounter += delayMsec;
    
    
    /*获取驱动器是正向作用时,高输出为阶跃值,低输出为0*/
    if(this->acterType == POSITIVE_ACTION)    
    {
        outputInSetVGreaterFBV = this->maxOutputStep;
        outputInSetVLessFBV = this->minOutputStep; 
    }
    else/*获取驱动器是反向作用时,高输出为0,低输出为阶跃值*/
    {
        outputInSetVGreaterFBV = this->minOutputStep;
        outputInSetVLessFBV = this->maxOutputStep;
    }
    /*状态机*/
    switch(this->tuneStat)
    {
        case TUNE_INIT:/*初始化状态*/
            TUNE_FsmReset(id);
            if(feedbackVal <= this->setpoint)
            {
                *outputVal = outputInSetVGreaterFBV; //高输出
            }
            else
            {
                *outputVal = outputInSetVLessFBV;    //低输出
            }
            this->tuneStat = TUNE_START_POINT;
            return TUNE_INIT;
        case TUNE_START_POINT:/*寻找起始点,反馈值在设定值之上,则认为是起始点*/
            if(feedbackVal < this->setpoint)
            {
                this->riseHysteresisCounter = 0;
                if(++this->fallHysteresisCounter >= this->hysteresisNum)
                {
                    *outputVal = outputInSetVGreaterFBV; //高输出
                }
            }
            else 
            {
                this->fallHysteresisCounter = 0;
                if(++this->riseHysteresisCounter >= this->hysteresisNum)
                {    
                    TUNE_PeakValReset(id,0,this->setpoint);
                    TUNE_PeakValReset(id,1,this->setpoint);
                    TUNE_PeakValReset(id,2,this->setpoint);                    
                    this->tuneStat = TUNE_RUNNING;
                }
                
            }
            return TUNE_INIT;
        case TUNE_RUNNING:
           
            if(feedbackVal > this->setpoint)
            {
                this->fallHysteresisCounter = 0;
                /*反馈值大于设置值的次数大于回滞量,则认为反馈值进入高于设置值的上半轴象限了*/
                if(++this->riseHysteresisCounter >= this->hysteresisNum)
                {
                    *outputVal = outputInSetVLessFBV; 
                    if(this->offetStat == FEEDBACK_BELOW_INPUT)
                    {
                        this->offetStat = FEEDBACK_ABOVE_INPUT;

                        this->fullCycleFlag = 0;
                        
                        if(this->peakWriPos >= LAST_PEAK_NUM-1)
                        {
                            this->peakWriPos =  0;
                        }
                        else
                        {
                            this->peakWriPos++;
                        }
                        TUNE_PeakValReset(id,this->peakWriPos,this->setpoint);                        
                    }
                    /*反馈值进入高于设置值的上半轴象限时,有最大值,寻找最大值*/
                    if(feedbackVal >= this->lastPeakVal[this->peakWriPos].maxPeak.feedabackVal)
                    {
                        this->lastPeakVal[this->peakWriPos].maxPeak.feedabackVal = feedbackVal;
                        this->lastPeakVal[this->peakWriPos].maxPeak.milSecond = this->tuneCounter;
                    }
                }
                
            }
            else
            {
                this->riseHysteresisCounter = 0;
                /*反馈值小于设置值的次数大于回滞量,则认为反馈值进入低于设置值的下半轴象限了*/
                if(++this->fallHysteresisCounter >= this->hysteresisNum)
                {
                    *outputVal = outputInSetVGreaterFBV; 
                    if(this->offetStat == FEEDBACK_ABOVE_INPUT)
                    {
                        this->offetStat = FEEDBACK_BELOW_INPUT;
                        this->cycleCounter ++;
                        
                    }
                    /*反馈值进入低于设置值的下半轴象限时,有最小值,存储最小值*/
                    if(feedbackVal <= this->lastPeakVal[this->peakWriPos].minPeak.feedabackVal)
                    {
                        this->lastPeakVal[this->peakWriPos].minPeak.feedabackVal = feedbackVal;
                        this->lastPeakVal[this->peakWriPos].minPeak.milSecond = this->tuneCounter;
                        this->fullCycleFlag = 1;
                    }
                    else
                    {
                        if(this->fullCycleFlag > 0 )
                        {
                            this->fullCycleFlag++;
                        }
                    }
                    
                    /*反馈值穿越设置值的次数大于LAST_PEAK_NUM,则认为至少运行了LAST_PEAK_NUM个周期*/
                    if(this->cycleCounter >= LAST_PEAK_NUM 
                        && this->fullCycleFlag>=this->hysteresisNum)
                    {
                        float ftemp1,ftemp2;
                        float peak[LAST_PEAK_NUM];
                        float peakTime[LAST_PEAK_NUM];
                        //计算每个周期的峰高和周期时间
                        for(int i = 0; i<LAST_PEAK_NUM; i++)
                        {
                            peak[i] = this->lastPeakVal[i].maxPeak.feedabackVal - this->lastPeakVal[i].minPeak.feedabackVal;
                            peakTime[i] =  (float)(this->lastPeakVal[i].minPeak.milSecond - this->lastPeakVal[i].maxPeak.milSecond);
                        }
                        //计算峰高和周期时间的方差
                        ftemp1 = TUNE_CalStdDeviation(peak,LAST_PEAK_NUM);
                        ftemp2 = TUNE_CalStdDeviation(peakTime,LAST_PEAK_NUM);
                        this->CurAmpStdDeviation = ftemp1;
                        this->CurCycleStdDeviation = ftemp2;
                        #ifdef DEBUG 
                        printf("%f,%f\n",ftemp1,ftemp2);
                        #endif
                        
                        //方差满足预期要求,则认为PID自整定成功
                        if(ftemp1<this->ampStdDeviation && ftemp2 <this->cycleStdDeviation)
                        {
                            this->tuneStat = TUNE_SUCESS;

                        }
                     
                    }
                }
            }
            
            //如果100个周期或者1小时没成功,则自整定失败
            if(this->cycleCounter> MAX_CYCLE
                || this->tuneCounter >MAX_TIME_MS)
            {
                this->tuneStat = TUNE_FAIL;
            }
            #ifdef DEBUG 
            printf("%f,%f\n",feedbackVal,*outputVal); 
            #endif
            return TUNE_RUNNING;
        case TUNE_FAIL:
            TUNE_FsmReset(id);
            *outputVal = outputInSetVLessFBV;
            return TUNE_FAIL;
        case TUNE_SUCESS:
            TUNE_CalPID(id);
            TUNE_FsmReset(id);
            *outputVal = outputInSetVLessFBV;
            return TUNE_SUCESS;
        default:
            return TUNE_INIT;
    }
}
/*---------------------------------------------------------------------------------------
 函数原型:   static void TUNE_PeakValReset(int32_t channel,float setpoint)
 功    能:  峰值初始化
输入参数:	id:整定ID
            channel:存储峰值的通道号
            setpoint:设置值
 输出参数:	NA
 返 回 值:	方差
---------------------------------------------------------------------------------------*/
 static void TUNE_PeakValReset(TUNE_ID_t id,int32_t channel, float setpoint)
 {

    s_tuneObject[id].lastPeakVal[channel].maxPeak.feedabackVal = setpoint;
    s_tuneObject[id].lastPeakVal[channel].maxPeak.milSecond = 0;
    s_tuneObject[id].lastPeakVal[channel].minPeak.feedabackVal = setpoint;
    s_tuneObject[id].lastPeakVal[channel].minPeak.milSecond = 0;
 }
/*---------------------------------------------------------------------------------------
 函数原型:   bool TUNE_CalStdDeviation(float * data,uint32_t len)
 功    能:  计算标准差
 输入参数:	fdata:浮点数据
            lenL:数据个数
 输出参数:	NA
 返 回 值:	标准差
---------------------------------------------------------------------------------------*/
 static float TUNE_CalStdDeviation(float * fdata, uint32_t len)
 {
    if(fdata == NULL || len==0)
        return 0;

    float peakAver = 0,variance = 0;

    for(uint32_t i = 0; i<len; i++)
    {
        peakAver += fdata[i];
    }
    peakAver /= LAST_PEAK_NUM;

    for(uint32_t i = 0; i < len; i++)
    {
        variance += powf(fdata[i]-peakAver,2);
    }
    variance /= (float)len;
    
    return sqrtf(variance);
 }
/*---------------------------------------------------------------------------------------
 函数原型:  bool TUNE_Setsetpoint(TUNE_ID_t id, float setpoint)
 功    能:  设置自整定设定值
 输入参数:	id:自整定ID
            setpoint:自整定设置值
            
 输出参数:	NA
 返 回 值:	true:设置成功,false:设置失败
---------------------------------------------------------------------------------------*/
 bool TUNE_Setsetpoint(TUNE_ID_t id, float setpoint)
 {
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    s_tuneObject[id].setpoint = setpoint;
    return true;
 }
 /*---------------------------------------------------------------------------------------
 函数原型:   bool TUNE_SetOutStep(TUNE_ID_t id, float maxStep,float minStep)
 功    能:  设置输出阶跃值
 输入参数:	id:自整定ID
            maxStep:最大输出阶跃值
            minStep:最大输出阶跃值
 输出参数:	NA
 返 回 值:	true:设置成功,false:设置失败
---------------------------------------------------------------------------------------*/
 bool TUNE_SetOutStep(TUNE_ID_t id, float maxStep,float minStep)
 {
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    s_tuneObject[id].maxOutputStep = maxStep;
    s_tuneObject[id].minOutputStep = minStep;
    return true;
 }
 /*---------------------------------------------------------------------------------------
 函数原型:  bool TUNE_SetActerType(TUNE_ID_t id, float maxStep,DRIVER_ACTION_TYPE_t type)
 功    能:  设置驱动器类型
 输入参数:	id:自整定ID
            type:驱动器类型
 输出参数:	NA
 返 回 值:	true:设置成功,false:设置失败
---------------------------------------------------------------------------------------*/
 bool TUNE_SetActerType(TUNE_ID_t id, DRIVER_ACTION_TYPE_t type)
 {
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    s_tuneObject[id].acterType = type;
    return true;
 }
/*---------------------------------------------------------------------------------------
 函数原型:   bool TUNE_SetCtrlType(TUNE_ID_t id, TUNE_CONTROLER_TYPE_t type)
 功    能:  设置控制器类型
 输入参数:	id:自整定ID
            type:
                CONTROLER_TYPE_PI,PI控制器,积分项不使用
                CONTROLER_TYPE_PID,PID控制器
 输出参数:	NA
 返 回 值:	true:设置成功,false:设置失败
---------------------------------------------------------------------------------------*/
 bool TUNE_SetCtrlType(TUNE_ID_t id, TUNE_CONTROLER_TYPE_t type)
 {
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    s_tuneObject[id].cTrlType = type;
    return true;
 }

 /*---------------------------------------------------------------------------------------
 函数原型:   bool TUNE_CalPID(TUNE_ID_t id)
 功    能:  计算PID
 输入参数:	id:自整定ID
 输出参数:	NA
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
static  bool TUNE_CalPID(TUNE_ID_t id)
 {
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    TUNE_OBJ_t *this = &s_tuneObject[id];
    uint16_t pos;
    
    pos = this->peakWriPos == 0?LAST_PEAK_NUM-1:this->peakWriPos-1;

    this->Ku = 4.0f*(this->maxOutputStep-this->minOutputStep)
                /((this->lastPeakVal[this->peakWriPos].maxPeak.feedabackVal-this->lastPeakVal[this->peakWriPos].minPeak.feedabackVal)*3.14159f);

    this->Tu = (float)(this->lastPeakVal[this->peakWriPos].maxPeak.milSecond-this->lastPeakVal[pos].maxPeak.milSecond);
     
    
    this->Kp = this->cTrlType==CONTROLER_TYPE_PID ? 0.6f * this->Ku : 0.8f * this->Ku;
    this->Ki = 0.5f * this->Tu;
    this->Kd = 0.125f * this->Tu; 
    return true;
}
 
/*---------------------------------------------------------------------------------------
 函数原型:   float TUNE_GetStat(TUNE_ID_t id, TUNE_STAT_t *pStat)
 功    能:  获取PID自整定状态
 输入参数:	id:自整定ID
 输出参数:	stat,自整定状态
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
bool TUNE_GetStat(TUNE_ID_t id, TUNE_STAT_t *pStat)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    TUNE_OBJ_t *this = &s_tuneObject[id];
	*pStat = this->tuneStat;
    return true;
}

 /*---------------------------------------------------------------------------------------
 函数原型:   float TUNE_GetKp(TUNE_ID_t id, float *pfactorP)
 功    能:   获取整定后的P参数
 输入参数:	id:自整定ID
 输出参数:	pfactorP:整定后的P参数
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
float TUNE_GetKp(TUNE_ID_t id, float *pfactorP)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    TUNE_OBJ_t *this = &s_tuneObject[id];
	*pfactorP = this->Kp;
    return true;
}

 

 /*---------------------------------------------------------------------------------------
 函数原型:   float TUNE_GetKp(TUNE_ID_t id)
 功    能:   获取整定后的I参数
 输入参数:	id:自整定ID
 输出参数:	pfactorI:整定后的I参数
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
float TUNE_GetKi(TUNE_ID_t id,float *pfactorI)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    TUNE_OBJ_t *this = &s_tuneObject[id];

	*pfactorI = this->Ki;

    return true;
}
 /*---------------------------------------------------------------------------------------
 函数原型:   float TUNE_GetKp(TUNE_ID_t id)
 功    能:   获取整定后的D参数
 输入参数:	id:自整定ID
 输出参数:	pfactorD:整定后的D参数
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
float TUNE_GetKd(TUNE_ID_t id,float *pfactorD)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    TUNE_OBJ_t *this = &s_tuneObject[id];
	*pfactorD = this->Kd;
    return true;
}

/*---------------------------------------------------------------------------------------
 函数原型:   float TUNE_GetKp(TUNE_ID_t id)
 功    能:  获取整定后的PID参数
 输入参数:	id:自整定ID
 输出参数:	NA
 返 回 值:	true:获取成功,否则失败
---------------------------------------------------------------------------------------*/
bool TUNE_GedPID(TUNE_ID_t id, float*paramP, float*paramI, float*paramD)
{
    if(id>=AUTO_TUNE_OBJ_NUM) return false;
    if(s_tuneObject[id].tuneId<0) return false;
    if(paramP != NULL)  TUNE_GetKp(id,paramP );
    if(paramI != NULL)  TUNE_GetKi(id,paramI );
    if(paramD != NULL)  TUNE_GetKd(id,paramD );
    return true;
}

7. 应用举例

7.1 初始化

TUNE_Init();

7.2 新建一个自整定对象

TUNE_CFG_PARAM_t  tuneCfgParam;

tuneCfgParam.acterType = NEGATIVE_NATION;
tuneCfgParam.ampStdDeviation = 0.1f;
tuneCfgParam.cTrlType = CONTROLER_TYPE_PID;
tuneCfgParam.cycleStdDeviation = 0.1f;
tuneCfgParam.hysteresisNum = 0;
tuneCfgParam.maxOutputStep = 50.0f;
tuneCfgParam.minOutputStep = 10.0f;
tuneCfgParam.setpoint = 400;
TUNE_ID_t tune_id;

tune_id = TUNE_New(&tuneCfgParam);//新建一个PID,得到其句柄ID,在后续调用相关函数使用

7.3 进行自整定

在一个固定时长进行循环的函数WORK_Cycle中调用函数TUNE_Work

WORK_Cycle()
{
    float feedbackVal;
    float outputVal;
    uint32_t delayMsec = 100;
    TUNE_STAT_t tuneStat = TUNE_INIT;  /*整定状态*/
    float paramP, float paramI, float paramD;
    while(1)
    {
        osDelay(delayMsec);//循环间隔时间
        
        feedbackVal = GetFeedBackVal();//获取实时反馈值
        if(tuneStat != TUNE_SUCESS || tuneStat!= TUNE_FAIL)
        {
            tuneStat = TUNE_Work(tune_id, feedbackVal, &outputVal, delayMsec);
            PWM_SetDuty(PWM_CH[k],outputVal);//输出输出值,控制响应单元执行
        }
        else
        {
            PWM_SetDuty(PWM_CH[k],0.0f);
            //此处已计算出PID值,将其更新到PID参数中
            TUNE_GedPID(tune_id, &paramP, &paramI, &paramD);
            PID_Release(tune_id);//释放PID资源
            return}
    }
    
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1650573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SQL Server 存储过程中的字符串本身包含单引号的用法

文章目录 引言I 存储过程中的字符串本身包含单引号的用法1.1 问题1.2解决方法引言 使用场景: 字符串类型字段的值比较 I 存储过程中的字符串本身包含单引号的用法 在SQL Server中,单引号用于表示字符串常量。如果你的存储过程中的字符串本身包含单引号,你需要用两个连续的…

PMP的考试费用是多少啊?大概需要多少钱?

如何以最低的经济成本取得PMP证书呢&#xff1f;PMP的认证考试费用包括考试报名费、学习备考费用和续证费用三个部分。 考试报名费用 PMP考试费用&#xff1a;PMP普通申请者初次考试费用为固定3900元人民币&#xff0c;补考&#xff08;重考&#xff09;费用为2500元人民币。退…

springboot 获取maven打包时间

springboot 获取maven打包时间 pom <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.13.RELEASE</version><relativePath /> <!-- lookup parent…

【Linux系列】file命令

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C语言常见的动态内存错误及几个经典笔试题以及c/c++内存开辟空间等的介绍

文章目录 前言一、常见的动态内存错误1. 对NULL指针的解引用操作2. 对动态开辟空间的越界访问3. 对非动态开辟内存使用free()4. 使用free释放一块动态开辟内存的一部分5. 对同一块动态内存多次释放6. 动态开辟内存忘记释放&#xff08;内存泄漏&#xff09; 二、几个经典笔试题…

【busybox记录】【shell指令】shuf

目录 内容来源&#xff1a; 【GUN】【shuf】指令介绍 【busybox】【shuf】指令介绍 【linux】【shuf】指令介绍 使用示例&#xff1a; 打乱内容 - 默认输出 打乱内容 - 最多输出n行 打乱内容 - 将输出写入文件 打乱内容 - 重复输出 打乱内容 - 打乱本条指令的参数 打…

Concise CoT(CCoT)提示词工程

原文地址&#xff1a;concise-chain-of-thought-ccot-prompting 2024 年 1 月 24 日 传统的 CoT 是以增加输出令牌使用为代价的&#xff0c;CCoT 提示是一种提示工程技术&#xff0c;旨在减少 LLM 响应的冗长和推理时间。 基于LLMs的生成式人工智能应用程序必须使用多管齐下的方…

静态分配IP,解决本地连接不上Linux虚拟机的问题

在Window环境下&#xff0c;使用远程终端工具连接不了VMware搭建的Linux虚拟机&#xff08;CentOS 7&#xff09;&#xff0c;并且在命令行ping不通该Linux虚拟机的IP地址。下面通过配置网关解决本地与Linux虚拟机连接问题&#xff1a; 1 查看虚拟机网关地址 在VMware虚拟机上…

文本清洁器:如何一键批量删除空格,让内容更整洁的技巧

在日常工作和学习中&#xff0c;我们经常需要处理大量的文本内容。而文本中多余的空格往往会让内容显得杂乱无章&#xff0c;影响阅读体验。为了解决这个问题&#xff0c;我们可以使用办公提效工具来一键批量删除空格&#xff0c;让内容更加整洁易读。 一、为什么需要批量删除空…

js宏任务微任务输出解析

第一种情况 setTimeout(function () {console.log(setTimeout 1) //11 宏任务new Promise(function (resolve) {console.log(promise 1) //12 同步函数resolve()}).then(function () {console.log(promise then) //13 微任务})})async function async1() {console.log(async1 s…

AI模型:windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】

AI模型&#xff1a;windows本地运行下载安装ollama运行Google CodeGemma可离线运行数据模型【自留记录】 CodeGemma 没法直接运行&#xff0c;需要中间软件。下载安装ollama后&#xff0c;使用ollama运行CodeGemma。 类似 前端本地需要安装 node.js 才可能跑vue、react项目 1…

华为:三层交换机与路由器连通上网实验

三层交换机是一种网络交换机&#xff0c;可以实现基于IP地址的高效数据转发和路由功能&#xff0c;通常用于大型企业、数据中心和校园网络等场景。此外&#xff0c;三层交换机还支持多种路由协议&#xff08;如OSPF、BGP等&#xff09;&#xff0c;以实现更为复杂的网络拓扑结构…

Java数据结构---链表

目录 链表的基本概念 LinkedList ArrayList和LinkedList的区别 链表的基本概念 当在ArrayList任意位置插入或删除元素时&#xff0c;就需要将后续元素整体往前或者整体往后搬移&#xff0c;时间复杂度O&#xff08;n&#xff09;&#xff0c;效率比较低&#xff0c;因此Arra…

【计算机科学速成课】笔记三

文章目录 17.集成电路真空管时代晶体管时代集成电路时代印刷电路板时代光刻时代 17.集成电路 Over the past six episodes, we delved into software, 过去 6 集我们聊了软件 \N 从早期编程方式到现代软件工程 from early programming efforts to modern software engineerin…

每天五分钟计算机视觉:通过交并比判断对象检测算法的性能

本文重点 在对象检测领域,交并比(Intersection over Union,简称IoU)是衡量算法性能的重要指标之一。它不仅直观地反映了预测框与真实框之间的重叠程度,还是判断算法是否“运行良好”的关键依据。 那个定位是好的? 对象检测任务中,我们希望不仅检测到对象,同时我们还希…

分析错误ValueError: could not determine the shape of object type ‘Series‘

这个错误提示 ValueError: could not determine the shape of object type Series 通常发生在尝试将 pandas 的 Series 直接转换为 PyTorch 的 tensor 时&#xff0c;尤其是当 Series 的数据类型不明确或者包含非数值类型的数据时。为了修正这个问题&#xff0c;确保在转换之前…

每日两题 / 138. 随机链表的复制 148. 排序链表(LeetCode热题100)

138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 用哈希表记录原链表中的节点是否被复制过 遍历原链表并通过哈希表维护新链表 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;rand…

反汇编一个ARM64的机器码

文章目录 使用objdump直接阅读ARM64手册使用反汇编网站 有下面一个机器码&#xff1a;0x929ffee9&#xff0c;如何翻译成汇编呢&#xff1f; 下面介绍几种做法&#xff1a; 使用objdump 将这个机器码写到文件中&#xff0c;然后使用objdump去反汇编 创建一个二进制文件 dd…

特斯拉擎天柱已经进厂拣电池了,其他“机器打工人”赶得上吗? | 最新快讯

文陈斯达 编辑李然 5 月 6 日消息&#xff0c;特斯拉放出了他们的人形机器人——擎天柱&#xff08;Optimus&#xff09;的最新演示视频。 特斯拉的工厂中&#xff0c;擎天柱机器人经过数据训练&#xff0c;可以轻巧自由地拿取电池&#xff0c;放进电池槽中排列整齐。 来源&…

error: pathspec ‘XXX‘ did not match any file(s) known to git

使用vscode&#xff0c;在本地开发切换分支时&#xff0c;报以下错误&#xff1a; error: pathspec XXX did not match any file(s) known to git 该问题是由于没有对应分支的原因。 首先使用一下命令&#xff0c;查看本地及远程的所有分支。 git branch -a 若没有对应的分…