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

文章目录

  • 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}
    }
    
}


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】Docker 安装部署 Nacos

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 【Linux】Docker 安装部署 Nacos docker搜索na…

如何获得一个Oracle 23ai数据库(Virtual Appliance)

准确的说&#xff0c;是Oracle 23ai Free Developer版&#xff0c;因为企业版目前只在云上&#xff08;OCI和Azure&#xff09;和ECC上提供。 方法包括3种&#xff0c;本文介绍第1种&#xff1a; Virtual ApplianceRPM安装Docker 从此处下载虚拟机。 可以看到虚拟机需要4G内…

武汉星起航:精准布局,卓越服务——运营交付团队领跑亚马逊

在全球电商浪潮中&#xff0c;亚马逊平台以其独特的商业模式和全球化的市场布局&#xff0c;吸引了无数商家和创业者的目光。在这个充满机遇的市场中&#xff0c;武汉星起航电子商务有限公司凭借其专业的运营交付团队&#xff0c;以其独特的五对一服务体系和精准的战略布局&…

CSS学习笔记之基础教程(一)

1、CSS语法 CSS 规则集&#xff08;rule-set&#xff09;由选择器和声明块组成&#xff1a; 选择器指向您需要设置样式的 HTML 元素。 声明块包含一条或多条用分号分隔的声明。 每条声明都包含一个 CSS 属性名称和一个值&#xff0c;以冒号分隔。 多条 CSS 声明用分号分隔…

【Linux】文件内容相关的命令,补充:管道符

1、查看文件内容 &#xff08;1-1&#xff09;查看文件内容&#xff1a;cat&#xff0c;tac&#xff0c;head&#xff0c;tail 查看文件内容cat 文件名查看文件内容并显示行号cat -n 文件名倒着查看文件内容&#xff08;从最后一行开始&#xff09;tac 文件名查看文件前10行…

Pycharm远程同步的mapping与sync

用Pycharm进行项目远程部署的时候会遇到两个同步文件&#xff0c;一个是点击 tools—>deployment—>configration——>mapping 一个是链接虚拟环境的时候会有一个sync&#xff0c;那么这两种同步有什么区别呢&#xff1f; 区别就是&#xff0c;2包括1&#xff0c;要用…

GORM的常见命令

文章目录 一、什么是GORM&#xff1f;二、GORM连接mysql以及AutoMigrate创建表三、查询1、检索此对象是否存在于数据库&#xff08;First,Take,Last方法&#xff09;2、Find()方法检索3、根据指定字段查询 四、更新1、Save() 保存多个字段2、更新单个字段 五、删除 一、什么是G…

QT截图程序,可多屏幕截图

截图程序&#xff0c;支持多屏幕时跨屏幕截图。截图使用setMask达到镂空效果&#xff0c;截图后会有预览和保存功能。截图时按下Esc可退出。 mainwindow.ui mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> …

docker jenkins 部署springboot项目

1、创建jenkins容器 1&#xff0c;首先&#xff0c;我们需要创建一个 Jenkins 数据卷&#xff0c;用于存储 Jenkins 的配置信息。可以通过以下命令创建一个数据卷&#xff1a; docker volume create jenkins_data启动 Jenkins 容器并挂载数据卷&#xff1a; docker run -dit…

搞定 TS 装饰器,让你写 Node 接口更轻松

前言 亲爱的小伙伴&#xff0c;你好&#xff01;我是 嘟老板。你是否用过 TypeScript 呢&#xff1f;对 装饰器 了解多少呢&#xff1f;有没有实践应用过呢&#xff1f;今天我们就来聊聊 装饰器 的那点事儿&#xff0c;看看它有哪些神奇的地方。 什么是装饰器 咱们先来看一段…

密码学《图解密码技术》 记录学习 第十三章

目录 第十三章 13.1 本章学习的内容 13.2 PGP 简介 13.2.1 什么是 PGP 13.2.2 关于 OpenPGP 13.2.3关于GNU Privacy Guard 13.2.4 PGP 的功能 公钥密码 数字签名 单向散列函数 证书 压缩 文本数据 大文件的拆分和拼合 13.3 生成密钥对 13.4 加密与解密 13.4.1 加密 生成…

Qt | QComboBox(组合框)

01、上节回顾 Qt 基础教程合集02、QComBox 一、QComboBox 类(下拉列表、组合框) 1、QComboBox 类是 QWidget 类的直接子类,该类实现了一个组合框 2、QComboBox 类中的属性 ①、count:const int 访问函数:int count() const; 获取组合框中的项目数量,默认情况下,对于空…

js 图片渐变

1. 点击图片&#xff0c;使其渐变为另一张图片 通过定义keyframes来创建一个淡入淡出的动画效果。当图片被点击时&#xff0c;先添加淡出动画使图片透明度从0渐变到1&#xff0c;然后在1秒后切换图片源并添加淡入动画使新图片透明度从0渐变到1&#xff0c;实现图片渐变效果。 …

光伏SRM供应商管理解决方案

供应商管理是光伏企业中重要的一环&#xff0c;通过SRM管理供应商&#xff0c;可以提高产品质量&#xff0c;降低采购成本&#xff0c;并集成供应链&#xff0c;提高核心竞争力。 一、搭建管理系统 分为供应商和商户&#xff0c;供应商需要完善基本信息、类别、等级、产品概要…

2005-2021年全国各地级市生态环境注意力/环保注意力数据(根据政府报告文本词频统计)

2005-2021年全国各地级市生态环境注意力/环保注意力数据&#xff08;根据政府报告文本词频统计&#xff09; 2005-2021年全国各地级市生态环境注意力/环保注意力数据&#xff08;根据政府报告文本词频统计&#xff09; 1、时间&#xff1a;2005-2021年 2、范围&#xff1a;2…

记一些内存取证题

生活若循规蹈矩&#xff0c;我们便随心而动 1.Suspicion 给了俩文件 python2 vol.py -f mem.vmem imageinfo 查看可疑进程 python2 vol.py -f mem.vmem --profileWinXPSP2x86 pslist 发现可疑进程TrueCrypt.exe 把这个进程提取出来。memdump -p 进程号 -D 目录 python2 vol…

Ypay源支付6.9无授权聚合免签系统可运营源码

Ypay源支付6.9无授权聚合免签系统可运营源码 效果图说明安装说明后台 部分源码领取源码下期更新预报 效果图 YPay是一款专为个人站长设计的聚合免签系统&#xff0c;YPay基于高性能的ThinkPHP 6.1.2 Layui PearAdmin架构&#xff0c;提供了实时监控和管理的功能&#xff0c;让…

GhostNetV2 Enhance Cheap Operation with Long-Range Attention 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2211.12905 代码地址&#xff1a;https://github.com/huawei-noah/Efficient-AI-Backbones/tree/master/ghostnetv2_pytorch 解决了什么问题&#xff1f; 在计算机视觉领域&#xff0c;深度神经网络在诸多任务上扮演着重要角色。为…

Linux —— 信号(3)

Linux —— 信号&#xff08;3&#xff09; Core dump为什么core默认是被关闭的阻塞信号信号其他相关常见概念信号递达信号未决信号阻塞两者的区别信号的结构 信号集操作函数一个简单使用例子sigpending的使用例子 我们今天接着来了解信号&#xff1a; Core dump 大家不知道有…

大模型爱好者的福音,有了它个人电脑也可以运行大模型了

GPT4ALL是一款可以运行在个人电脑上的大模型系统&#xff0c;不需要GPU即可运行&#xff0c;目前支持mac&#xff0c;linux和windows系统。 什么是GPT4ALL&#xff1f; 不论学习任何东西&#xff0c;首先要明白它是个什么东西。 Open-source large language models that run …