精华内容
下载资源
问答
  • 增量式PID算法

    2011-11-01 11:28:40
    增量式PID算法,自己查阅资料,总结了增量式PID算法的模型内容等资料
  • Matlab增量式PID算法仿真 ,包含m文件和simulink仿真文件,希望对大家有帮助
  • Incremental PID Control 增量式PID算法C语言实现 C implementation of Incremental PID Control
  • 增量式PID算法的MATLAB实现

    千次阅读 多人点赞 2019-06-22 13:27:19
    增量式PID的MATLAB实现PID控制的分类连续PID控制离散PID控制位置式PID控制增量式PID控制位置式PID与增量式PID的比较离散PID控制的MATLAB实现实现增量式PID算法绘制GUI界面,实时更改给定值 PID控制的分类 连续PID控制...

    pdf和代码的下载地址: 增量式PID算法的MATLAB实现(访问密码:3834)

    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 ] (1.1) u(t) = K_p \left[ e(t) + \frac{1}{T_i} \int_0^t e(t) \, dt + T_d \frac{de(t)}{dt} \right] \tag{1.1} u(t)=Kp[e(t)+Ti10te(t)dt+Tddtde(t)](1.1)
    上式中, K p K_p Kp为比例系数, T i T_i Ti为积分时间常数, T d T_d Td为微分时间常数.对上式进行拉氏变换得到
    U ( s ) = K p [ E ( s ) + E ( s ) T i s + T d s E ( s ) ] (1.2) U(s) = K_p \left[ E(s) + \frac{E(s)}{T_i s} + T_d s E(s)\right] \tag{1.2} U(s)=Kp[E(s)+TisE(s)+TdsE(s)](1.2)
    PID控制器的传递函数为
    D ( s ) = U ( s ) E ( s ) = K p ( 1 + 1 T i s + T d s ) (1.3) D(s) = \frac{U(s)}{E(s)} = K_p \left( 1 + \frac{1}{T_i s} + T_d s \right) \tag{1.3} D(s)=E(s)U(s)=Kp(1+Tis1+Tds)(1.3)

    离散PID控制

    要对数字信号进行控制,我们要将其离散化,并进行离散PID控制,本质上就是对数字信号进行采样. 离散式PID根据采样方式不同,分为位置式PID控制增量式PID控制.

    位置式PID控制

    对连续PID控制以一定离散化方法离散后就可以得到数字PID控制,离散的本质是采样,假设采样为周期采样,采样周期为T,离散自变量为n,则离散PID控制可以表示为:
    u [ n ] = K p { e [ n ] + T T i ∑ i = 0 n e [ i ] + T d T { e [ n ] − e [ n − 1 ] } } (2.1) u[n] = K_p \left\{ e[n] + \frac{T}{T_i} \sum_{i=0}^n e[i] + \frac{T_d}{T} \left\{ e[n] - e[n-1]\right\} \right\} \tag{2.1} u[n]=Kp{e[n]+TiTi=0ne[i]+TTd{e[n]e[n1]}}(2.1)
    上式即为位置式PID控制的表达式

    增量式PID控制

    位置式PID控制表达式做差分,写出n-1时刻的控制量:
    u [ n − 1 ] = K p { e [ n − 1 ] + T T i ∑ i = 0 n − 1 e [ i ] + T d T { e [ n − 1 ] − e [ n − 2 ] } } (2.2) u[n-1] = K_p \left\{ e[n-1] + \frac{T}{T_i} \sum_{i=0}^{n-1} e[i] + \frac{T_d}{T} \left\{ e[n-1] - e[n-2]\right\} \right\} \tag{2.2} u[n1]=Kp{e[n1]+TiTi=0n1e[i]+TTd{e[n1]e[n2]}}(2.2)
    将式 ( 2.1 ) (2.1) (2.1)与式 ( 2.2 ) (2.2) (2.2)相减,得到

    Δ u [ n ] = u [ n ] − u [ n − 1 ] = K p { e [ n ] − e [ n − 1 ] } + K p T T i e [ n ] + K p T d T { e [ n ] − 2 e [ n − 1 ] + e [ n − 2 ] } (2.3) \begin{aligned} \Delta u[n] & = u[n] - u[n-1] \\ & = K_p \left\{ e[n] - e[n-1] \right\}+ \frac{K_p T}{T_i} e[n] + \frac{K_p T_d}{T} \left\{ e[n] -2 e[n-1] + e[n-2]\right\} \end{aligned} \tag{2.3} Δu[n]=u[n]u[n1]=Kp{e[n]e[n1]}+TiKpTe[n]+TKpTd{e[n]2e[n1]+e[n2]}(2.3)

    K i = K p T T i K_i= K_p \frac{T}{T_i} Ki=KpTiT为积分系数, K d = K p T d T K_d = K_p \frac{T_d}{T} Kd=KpTTd为微分系数,可将上式简化为
    Δ u [ n ] = K p { e [ n ] − e [ n − 1 ] } + K i e [ n ] + K d { e [ n ] − 2 e [ n − 1 ] + e [ n − 2 ] } (2.4) \Delta u[n] = K_p \left\{ e[n] - e[n-1] \right\}+ K_i e[n] + K_d \left\{ e[n] -2 e[n-1] + e[n-2]\right\} \tag{2.4} Δu[n]=Kp{e[n]e[n1]}+Kie[n]+Kd{e[n]2e[n1]+e[n2]}(2.4)
    增量式PID控制主要是通过求出增量,将原先的积分环节的累积作用进行了替换,避免积分环节占用大量计算性能和存储空间.

    位置式PID与增量式PID的比较

    增量式PID控制的优点:

    1. 计算机每次只输出控制增量,即对应执行机构位置的变化量,故机器发生故障时影响范围小、不会严重影响生产过程
    2. 手动-自动切换时冲击小.当控制从手动向自动切换时,可以作到无扰动切换
    3. 不进行累加运算,增量仅与近三次 e ( n ) e(n) e(n)有关

    增量式PID控制的缺点:

    1. 由于增量式需要对控制量进行记忆,所以对于不带记忆装置的系统,只能使用位置式PID控制方式进行控制.

    离散PID控制的MATLAB实现

    实现增量式PID算法

    我们本次实现增量式PID控制

    curTime = 0; ts = 0.1;			% curTime记录当前时间,ts记录采样周期
    r = 1000;						% 初始化给定值,设定给定值为1500
    u_n1 = 0; u_n = 0;				% 输出,u_n表示u[n],u_n1表示u[n-1],初始时输出均为0
    e_n = 0; e_n1 = 0; e_n2 = 0;	% 输入(偏差),e_n表示e[n],e_n1表示e[n-1],e_n2表示e[n-2]
    while stop ~= true
    	curTime = curTime + ts;		% 刷新当前时间
    	% 计算阀门开度增量
    	delta1 = np * (e_n - e_n1)
    	delta2 = ni * e_n
    	delta3 = nd *(e_n - 2*e_n1 + e_n2)
    	delta_u = delta1 + delta2 + delta3;	
    	% 绘图
    	plot([curTime-ts, curTime], [u_n1, u_n], 'r'); hold on;	% 绘制给定值变化曲线
    	plot([curTime-ts, curTime], [r, r]); hold on;			% 绘制阀门开度变化曲线
        % 更新各个量
    	r = r;		% 更新给定值
    	u_n1 = u_n;			
    	e_n2 = e_n1; e_n1 = e_n; e_n = r - u_n;
    	pause(0.1);
    end;
    

    运行上述程序,得到图像结果如下:
    在这里插入图片描述

    绘制GUI界面,实时更改给定值

    绘制GUI界面如下
    在这里插入图片描述
    将上一步中编写的核心算法作为各按钮的回调函数,完成软件的编写,软件运行效果如下
    在这里插入图片描述

    源代码下载地址: 下载地址: https://download.csdn.net/download/ncepu_chen/11253423

    pdf和代码的下载地址: 增量式PID算法的MATLAB实现(访问密码:3834)

    展开全文
  • 基于FPGA的增量式PID算法的设计与改进
  • 非常实用的 增量式PID算法PDF文档+MATLAB源程序,完全测试通过
  • 增量式PID算法 原理及实现方法

    热门讨论 2010-05-11 16:13:33
    增量式PID算法 原理及实现方法 物理模型 软件算法流程图流程图 增量式PID算法的优点
  • msp430 单片机 中增量式 pid 算法 的C语言 实现方法
  • 位置式、增量式PID算法C语言实现

    万次阅读 2018-03-24 12:29:06
    位置式、增量式PID算法C语言实现 芯片:STM32F107VC 编译器:KEIL4 作者:SY 日期:2017-9-21 15:29:19 概述 PID 算法是一种工控领域常见的控制算法,用于闭环反馈控制。有以下两种分类: 增量式...

    位置式、增量式PID算法C语言实现

    芯片:STM32F107VC

    编译器:KEIL4

    作者:SY

    日期:2017-9-21 15:29:19

    概述

    PID 算法是一种工控领域常见的控制算法,用于闭环反馈控制。有以下两种分类:

    • 增量式

      每次周期性计算出的 PID 为增量值,是在上一次控制量的基础上进行的调整。

    • 位置式

      每次周期性计算出的 PID 为绝对的数值,是执行机构实际的位置。

    我们使用高级语言的思想去实现两种 PID ,做到对于用户来说,调用相同的接口,内部实现不同的 PID 算法。

    代码

    pid.h

    enum PID_MODE {
        PID_INC = 0,    //增量式
        PID_POS,        //位置式
    };
    
    struct PID {
        enum PID_MODE mode;
    
        float kp;                   //比例系数
        float ki;                   //积分系数
        float kd;                   //微分系数
    
        double targetPoint;         //目标点
        double lastError;           //Error[-1]
        double prevError;           //Error[-2]
    
        void (*init)(struct PID *this, double targetPoint);         //PID初始化
        double (*outputLimit)(struct PID *this, double output);     //PID输出限制
        void (*setParameter)(struct PID *this, \
            float kp, float ki, float kd);                          //设置PID参数
        double (*calculate)(struct PID *this, double samplePoint);  //计算PID
    };
    
    /* 增量式PID */
    struct PID_INC {
        struct PID pid;
    };
    
    /* 位置式PID */
    struct PID_POS {
        struct PID pid;
        double iSum;                //积分和
    };

    其中 struct PID 就是我们提供给用户的接口,可以理解为 抽象类 ,增量式和位置式 PID 都去继承该抽象类,然后实现其中的抽象方法。

    对于位置式 PID 拥有自己的成员 iSum

    pid.c

    /*
    *********************************************************************************************************
    *                                           PID
    *********************************************************************************************************
    */
    /*
    *********************************************************************************************************
    * Function Name : PID_Init
    * Description   : PID初始化
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static void PID_Init(struct PID *this, double targetPoint)
    {
        this->targetPoint = targetPoint;
        this->lastError = 0;
        this->prevError = 0;    
    }
    
    /*
    *********************************************************************************************************
    * Function Name : PID_OutputLimit
    * Description   : PID输出限制
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static double PID_OutputLimit(struct PID *this, double output)
    {
        if (output < 0) {
            output = 0;
        } else if (output > DIGITAL_THROTTLE_VALVE_MAX_DEGREE) {
            output = DIGITAL_THROTTLE_VALVE_MAX_DEGREE;
        }
        return output;
    }
    
    /*
    *********************************************************************************************************
    * Function Name : PID_SetParameter
    * Description   : PID设置参数
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static void PID_SetParameter(struct PID *this, float kp, float ki, float kd)
    {
        this->kp = kp;
        this->ki = ki;
        this->kd = kd;
    }
    
    /*
    *********************************************************************************************************
    * Function Name : PID_SetTargetValue
    * Description   : PID设置目标值
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    void PID_SetTargetValue(struct PID *this, double targetPoint)
    {
        this->targetPoint = targetPoint;
    }
    
    /*
    *********************************************************************************************************
    * Function Name : PID_GetTargetValue
    * Description   : PID获取目标值
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    double PID_GetTargetValue(struct PID *this)
    {
        return this->targetPoint;
    }
    
    /*
    *********************************************************************************************************
    *                                           增量式PID
    *********************************************************************************************************
    */
    static double PID_IncCalculate(struct PID *this, double samplePoint);
    
    struct PID_INC g_PID_Inc = {
        .pid = {
            .mode           = PID_INC,
            .init           = PID_Init,
            .outputLimit    = PID_OutputLimit,
            .setParameter   = PID_SetParameter,
            .calculate      = PID_IncCalculate,
        },
    };
    
    /*
    *********************************************************************************************************
    * Function Name : PID_IncCalculate
    * Description   : 增量式PID计算
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static double PID_IncCalculate(struct PID *this, double samplePoint)
    {   
        double nowError = this->targetPoint - samplePoint;
        double out = this->kp * nowError +\
                     this->ki * this->lastError +\
                     this->kd * this->prevError;
        this->prevError = this->lastError;
        this->lastError = nowError;
    
        if (this->outputLimit) {
            out = this->outputLimit(this, out);
        }
    
        return out;
    }
    
    /*
    *********************************************************************************************************
    *                                           位置式PID
    *********************************************************************************************************
    */
    static double PID_PosCalculate(struct PID *this, double samplePoint);
    static void PID_PosInit(struct PID *this, double targetPoint);
    
    struct PID_POS g_PID_Pos = {
        .pid = {
            .mode           = PID_POS,
            .init           = PID_PosInit,
            .outputLimit    = PID_OutputLimit,
            .setParameter   = PID_SetParameter,
            .calculate      = PID_PosCalculate,
        },
    };
    
    /*
    *********************************************************************************************************
    * Function Name : PID_PosInit
    * Description   : 位置式PID初始化
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static void PID_PosInit(struct PID *this, double targetPoint)
    {
        PID_Init(this, targetPoint);
        struct PID_POS *pid_Handle = (struct PID_POS *)this;
        pid_Handle->iSum = 0;
    }
    
    /*
    *********************************************************************************************************
    * Function Name : PID_PosCalculate
    * Description   : 位置式PID计算
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    static double PID_PosCalculate(struct PID *this, double samplePoint)
    {
        struct PID_POS *pid_Handle = (struct PID_POS *)this;
    
        double nowError = this->targetPoint - samplePoint;
        this->lastError = nowError;
        //积分累计误差
        pid_Handle->iSum += nowError;
        double out = this->kp * nowError +\
                     this->ki * pid_Handle->iSum +\
                     this->kd * (nowError - this->prevError);   
        this->prevError = nowError;
    
        if (this->outputLimit) {
            out = this->outputLimit(this, out);
        }
    
        return out;
    }

    对于上述内容:最关键的是两个变量 struct PID_INC g_PID_Incstruct PID_POS g_PID_Pos ,他们针对抽象类实现了各自的算法。

    其中有一个很重要的小技巧,举例:

    static double PID_PosCalculate(struct PID *this, double samplePoint)
    {
        struct PID_POS *pid_Handle = (struct PID_POS *)this;
    }

    该函数的接口是 struct PID *this ,但是我们内部将他强制转换为 struct PID_POS *pid_Handle ,这是两种不同的数据类型,为什么可以进行转换呢?而且转换后的数据是正确的?

    因为对于 C 语言来说,结构体内部的第一个成员的地址和该结构体变量的地址是一样的,所以可以相互转换,实现向下转型,这就是 C 语言的强大之处。

    测试

    app.c

    struct KernelCtrl {
        struct DTV dtv;
        struct PID *dtvPid;
    };
    
    static struct KernelCtrl g_kernelCtrl;
    
    /*
    *********************************************************************************************************
    * Function Name : Kernel_Ctrl_Init
    * Description   : 内核控制初始化
    * Input         : None
    * Output        : None
    * Return        : None
    *********************************************************************************************************
    */
    void Kernel_Ctrl_Init(void)
    {
    #if 1
        /* 增量式 */
        g_kernelCtrl.dtvPid = &g_PID_Inc.pid;
    #else
        /* 位置式 */
        g_kernelCtrl.dtvPid = &g_PID_Pos.pid;
    #endif  
    }

    只要在初始化时,指定使用哪一种 PID 模式,在调用时两种方式可以使用同一个接口,这样对于用户来说就屏蔽了内部细节。

    参考

    Pid控制算法-变积分的pid算法的C++实现

    展开全文
  • 位置式、增量式PID算法C语言实现 芯片:STM32F107VC 编译器:KEIL4 作者:SY 日期:2017-9-21 15:29:19 概述 PID 算法是一种工控领域常见的控制算法,用于闭环反馈控制。有以下两种分类: 增量式 ...

    位置式、增量式PID算法C语言实现

    芯片:STM32F107VC

    编译器:KEIL4

    作者:SY

    日期:2017-9-21 15:29:19

    概述

    PID 算法是一种工控领域常见的控制算法,用于闭环反馈控制。有以下两种分类:

    • 增量式

      每次周期性计算出的 PID 为增量值,是在上一次控制量的基础上进行的调整。

    • 位置式

      每次周期性计算出的 PID 为绝对的数值,是执行机构实际的位置。

    我们使用高级语言的思想去实现两种 PID ,做到对于用户来说,调用相同的接口,内部实现不同的 PID 算法。

    代码

    pid.h

     1 enum PID_MODE {
     2     PID_INC = 0,    //增量式
     3     PID_POS,        //位置式
     4 };
     5 
     6 struct PID {
     7     enum PID_MODE mode;
     8 
     9     float kp;                   //比例系数
    10     float ki;                   //积分系数
    11     float kd;                   //微分系数
    12 
    13     double targetPoint;         //目标点
    14     double lastError;           //Error[-1]
    15     double prevError;           //Error[-2]
    16 
    17     void (*init)(struct PID *this, double targetPoint);         //PID初始化
    18     double (*outputLimit)(struct PID *this, double output);     //PID输出限制
    19     void (*setParameter)(struct PID *this, \
    20         float kp, float ki, float kd);                          //设置PID参数
    21     double (*calculate)(struct PID *this, double samplePoint);  //计算PID
    22 };
    23 
    24 /* 增量式PID */
    25 struct PID_INC {
    26     struct PID pid;
    27 };
    28 
    29 /* 位置式PID */
    30 struct PID_POS {
    31     struct PID pid;
    32     double iSum;                //积分和
    33 };

    其中 struct PID 就是我们提供给用户的接口,可以理解为 抽象类 ,增量式和位置式 PID 都去继承该抽象类,然后实现其中的抽象方法。

    对于位置式 PID 拥有自己的成员 iSum 。

    pid.c

      1 /*
      2 *********************************************************************************************************
      3 *                                           PID
      4 *********************************************************************************************************
      5 */
      6 /*
      7 *********************************************************************************************************
      8 * Function Name : PID_Init
      9 * Description   : PID初始化
     10 * Input         : None
     11 * Output        : None
     12 * Return        : None
     13 *********************************************************************************************************
     14 */
     15 static void PID_Init(struct PID *this, double targetPoint)
     16 {
     17     this->targetPoint = targetPoint;
     18     this->lastError = 0;
     19     this->prevError = 0;    
     20 }
     21 
     22 /*
     23 *********************************************************************************************************
     24 * Function Name : PID_OutputLimit
     25 * Description   : PID输出限制
     26 * Input         : None
     27 * Output        : None
     28 * Return        : None
     29 *********************************************************************************************************
     30 */
     31 static double PID_OutputLimit(struct PID *this, double output)
     32 {
     33     if (output < 0) {
     34         output = 0;
     35     } else if (output > DIGITAL_THROTTLE_VALVE_MAX_DEGREE) {
     36         output = DIGITAL_THROTTLE_VALVE_MAX_DEGREE;
     37     }
     38     return output;
     39 }
     40 
     41 /*
     42 *********************************************************************************************************
     43 * Function Name : PID_SetParameter
     44 * Description   : PID设置参数
     45 * Input         : None
     46 * Output        : None
     47 * Return        : None
     48 *********************************************************************************************************
     49 */
     50 static void PID_SetParameter(struct PID *this, float kp, float ki, float kd)
     51 {
     52     this->kp = kp;
     53     this->ki = ki;
     54     this->kd = kd;
     55 }
     56 
     57 /*
     58 *********************************************************************************************************
     59 * Function Name : PID_SetTargetValue
     60 * Description   : PID设置目标值
     61 * Input         : None
     62 * Output        : None
     63 * Return        : None
     64 *********************************************************************************************************
     65 */
     66 void PID_SetTargetValue(struct PID *this, double targetPoint)
     67 {
     68     this->targetPoint = targetPoint;
     69 }
     70 
     71 /*
     72 *********************************************************************************************************
     73 * Function Name : PID_GetTargetValue
     74 * Description   : PID获取目标值
     75 * Input         : None
     76 * Output        : None
     77 * Return        : None
     78 *********************************************************************************************************
     79 */
     80 double PID_GetTargetValue(struct PID *this)
     81 {
     82     return this->targetPoint;
     83 }
     84 
     85 /*
     86 *********************************************************************************************************
     87 *                                           增量式PID
     88 *********************************************************************************************************
     89 */
     90 static double PID_IncCalculate(struct PID *this, double samplePoint);
     91 
     92 struct PID_INC g_PID_Inc = {
     93     .pid = {
     94         .mode           = PID_INC,
     95         .init           = PID_Init,
     96         .outputLimit    = PID_OutputLimit,
     97         .setParameter   = PID_SetParameter,
     98         .calculate      = PID_IncCalculate,
     99     },
    100 };
    101 
    102 /*
    103 *********************************************************************************************************
    104 * Function Name : PID_IncCalculate
    105 * Description   : 增量式PID计算
    106 * Input         : None
    107 * Output        : None
    108 * Return        : None
    109 *********************************************************************************************************
    110 */
    111 static double PID_IncCalculate(struct PID *this, double samplePoint)
    112 {   
    113     double nowError = this->targetPoint - samplePoint;
    114     double out = this->kp * nowError +\
    115                  this->ki * this->lastError +\
    116                  this->kd * this->prevError;
    117     this->prevError = this->lastError;
    118     this->lastError = nowError;
    119 
    120     if (this->outputLimit) {
    121         out = this->outputLimit(this, out);
    122     }
    123 
    124     return out;
    125 }
    126 
    127 /*
    128 *********************************************************************************************************
    129 *                                           位置式PID
    130 *********************************************************************************************************
    131 */
    132 static double PID_PosCalculate(struct PID *this, double samplePoint);
    133 static void PID_PosInit(struct PID *this, double targetPoint);
    134 
    135 struct PID_POS g_PID_Pos = {
    136     .pid = {
    137         .mode           = PID_POS,
    138         .init           = PID_PosInit,
    139         .outputLimit    = PID_OutputLimit,
    140         .setParameter   = PID_SetParameter,
    141         .calculate      = PID_PosCalculate,
    142     },
    143 };
    144 
    145 /*
    146 *********************************************************************************************************
    147 * Function Name : PID_PosInit
    148 * Description   : 位置式PID初始化
    149 * Input         : None
    150 * Output        : None
    151 * Return        : None
    152 *********************************************************************************************************
    153 */
    154 static void PID_PosInit(struct PID *this, double targetPoint)
    155 {
    156     PID_Init(this, targetPoint);
    157     struct PID_POS *pid_Handle = (struct PID_POS *)this;
    158     pid_Handle->iSum = 0;
    159 }
    160 
    161 /*
    162 *********************************************************************************************************
    163 * Function Name : PID_PosCalculate
    164 * Description   : 位置式PID计算
    165 * Input         : None
    166 * Output        : None
    167 * Return        : None
    168 *********************************************************************************************************
    169 */
    170 static double PID_PosCalculate(struct PID *this, double samplePoint)
    171 {
    172     struct PID_POS *pid_Handle = (struct PID_POS *)this;
    173 
    174     double nowError = this->targetPoint - samplePoint;
    175     this->lastError = nowError;
    176     //积分累计误差
    177     pid_Handle->iSum += nowError;
    178     double out = this->kp * nowError +\
    179                  this->ki * pid_Handle->iSum +\
    180                  this->kd * (nowError - this->prevError);   
    181     this->prevError = nowError;
    182 
    183     if (this->outputLimit) {
    184         out = this->outputLimit(this, out);
    185     }
    186 
    187     return out;
    188 }

    对于上述内容:最关键的是两个变量 struct PID_INC g_PID_Inc 和 struct PID_POS g_PID_Pos ,他们针对抽象类实现了各自的算法。

    其中有一个很重要的小技巧,举例:

    1 static double PID_PosCalculate(struct PID *this, double samplePoint)
    2 {
    3     struct PID_POS *pid_Handle = (struct PID_POS *)this;
    4 }

    该函数的接口是 struct PID *this ,但是我们内部将他强制转换为 struct PID_POS *pid_Handle ,这是两种不同的数据类型,为什么可以进行转换呢?而且转换后的数据是正确的?

    因为对于 C 语言来说,结构体内部的第一个成员的地址和该结构体变量的地址是一样的,所以可以相互转换,实现向下转型,这就是 C 语言的强大之处。

    测试

    app.c

     1 struct KernelCtrl {
     2     struct DTV dtv;
     3     struct PID *dtvPid;
     4 };
     5 
     6 static struct KernelCtrl g_kernelCtrl;
     7 
     8 /*
     9 *********************************************************************************************************
    10 * Function Name : Kernel_Ctrl_Init
    11 * Description   : 内核控制初始化
    12 * Input         : None
    13 * Output        : None
    14 * Return        : None
    15 *********************************************************************************************************
    16 */
    17 void Kernel_Ctrl_Init(void)
    18 {
    19 #if 1
    20     /* 增量式 */
    21     g_kernelCtrl.dtvPid = &g_PID_Inc.pid;
    22 #else
    23     /* 位置式 */
    24     g_kernelCtrl.dtvPid = &g_PID_Pos.pid;
    25 #endif  
    26 }

    只要在初始化时,指定使用哪一种 PID 模式,在调用时两种方式可以使用同一个接口,这样对于用户来说就屏蔽了内部细节。

    参考

    Pid控制算法-变积分的pid算法的C++实现

     

     

    来源

     

    转载于:https://www.cnblogs.com/skullboyer/p/9700762.html

    展开全文
  • 增量式 PID 算法的 STM32 实现

    万次阅读 多人点赞 2018-07-20 22:17:29
    首先说说增量式PID的公式,这个关系到MCU算法公式的书写,实际上两个公式的写法是同一个公式变换来得,不同的是系数的差异。资料上比较多的是: 还有一种的算法是: 这里主要介绍第二种,具体会分析比例、积分、...

     

    虽然PID不是什么牛逼的东西,但是真心希望以后刚刚接触这块的人能尽快进入状态。特地分享一些自己如何实现的过程。
    首先说说增量式PID的公式,这个关系到MCU算法公式的书写,实际上两个公式的写法是同一个公式变换来得,不同的是系数的差异。
    资料上比较多的是:
     

    还有一种的算法是:
     

    这里主要介绍第二种,具体会分析比例、积分、微分三个环节的作用。

    硬件部分:
    控制系统的控制对象是4个空心杯直流电机,电机带光电编码器,可以反馈转速大小的波形。电机驱动模块是普通的L298N模块。

    芯片型号,STM32F103ZET6


    软件部分:
    PWM输出:TIM3,可以直接输出4路不通占空比的PWM波
    PWM捕获:STM32除了TIM6 TIM7其余的都有捕获功能,使用TIM1 TIM2 TIM4 TIM5四个定时器捕获四个反馈信号
    PID的采样和处理:使用了基本定时器TIM6,溢出时间就是我的采样周期,理论上T越小效果会越好,这里我取20ms,依据控制对象吧,如果控制水温什么的采样周期会是几秒几分钟什么的。


    上面的PWM输出和捕获关于定时器的设置都有例程,我这里是这样的:
    TIM3输出四路PWM,在引脚 C 的 GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9输出
    四路捕获分别是TIM4  TIM1  TIM2  TIM5   ,对应引脚是:  PB7 PE11 PB3 PA1
    高级定时器
    tim1的初始化略不同,它的中断”名称“和通用定时器不同。具体的内容,请大家看一下我分享的代码就明白了。
     程序.zip 



    主要讲解PID部分
    准备部分:先定义PID结构体:

    1. typedef struct 
    2. {
    3. int setpoint;//设定目标
    4. int sum_error;//误差累计
    5. float proportion ;//比例常数
    6. float integral ;//积分常数
    7. float derivative;//微分常数
    8. int last_error;//e[-1]
    9. int prev_error;//e[-2]
    10. }PIDtypedef;

    复制代码

    在文件中定义几个关键变量:

    1. float  Kp =     0.32  ; //比例常数
    2. float  Ti =                0.09 ; //积分时间常数
    3. float Td =                0.0028 ;  //微分时间常数
    4. #define T                  0.02 //采样周期
    5. #define Ki     Kp*(T/Ti)        // Kp Ki Kd 三个主要参数
    6. #define Kd                Kp*(Td/T)

    复制代码

    PID.H里面主要的几个函数:

    1. void PIDperiodinit(u16 arr,u16 psc);        //PID 采样定时器设定
    2. void incPIDinit(void);                //初始化,参数清零清零
    3. int incPIDcalc(PIDtypedef*PIDx,u16 nextpoint);           //PID计算
    4. void PID_setpoint(PIDtypedef*PIDx,u16 setvalue);  //设定 PID预期值
    5. void PID_set(float pp,float ii,float dd);//设定PID  kp ki kd三个参数
    6. void set_speed(float W1,float W2,float W3,float W4);//设定四个电机的目标转速

    复制代码


    PID处理过程:
    岔开一下:这里我控制的是电机的转速w,实际上电机的反馈波形的频率f、电机转速w、控制信号PWM的占空比a三者是大致线性的正比的关系,这里强调这个的目的是
    因为楼主在前期一直搞不懂我控制的转速怎么和TIM4输出的PWM的占空比联系起来,后来想清楚里面的联系之后通过公式把各个系数算出来了。

    正题:控制流程是这样的,首先我设定我需要的车速(对应四个轮子的转速),然后PID就是开始响应了,它先采样电机转速,得到偏差值E,带入PID计算公式,得到调整量也就是最终更改了PWM的占空比,不断调节,直到转速在稳态的一个小范围上下浮动。
    上面讲到的“得到调整量”就是增量PID的公式:

    1. int incPIDcalc(PIDtypedef *PIDx,u16 nextpoint)
    2. {
    3. int iError,iincpid;
    4. iError=PIDx->setpoint-nextpoint;  //当前误差
    5. /*iincpid=                                               //增量计算
    6. PIDx->proportion*iError                //e[k]项
    7. -PIDx->integral*PIDx->last_error          //e[k-1]
    8. +PIDx->derivative*PIDx->prev_error;//e[k-2]
    9. */
    10. iincpid=                                                          //增量计算
    11. PIDx->proportion*(iError-PIDx->last_error)
    12. +PIDx->integral*iError
    13. +PIDx->derivative*(iError-2*PIDx->last_error+PIDx->prev_error);
    14.  
    15. PIDx->prev_error=PIDx->last_error; //存储误差,便于下次计算
    16. PIDx->last_error=iError;
    17. return(iincpid) ;
    18. }

    复制代码

    注释掉的是第一种写法,没注释的是第二种以Kp KI kd为系数的写法,实际结果是一样的
    处理过程放在了TIM6,溢出周期时间就是是PID里面采样周期(区分于反馈信号的采样,反馈信号采样是1M的频率)
    相关代码:

    1. void TIM6_IRQHandler(void)        //        采样时间到,中断处理函数
    2. {          
    3.         
    4. if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET)//更新中断
    5.         {
    6.         frequency1=1000000/period_TIM4        ; //通过捕获的波形的周期算出频率
    7.         frequency2=1000000/period_TIM1        ;
    8.         frequency3=1000000/period_TIM2        ;
    9.         frequency4=1000000/period_TIM5        ;
    10. /********PID1处理**********/
    11.         PID1.sum_error+=(incPIDcalc(&PID1,frequency1));         //计算增量并累加 
    12.        pwm1=PID1.sum_error*4.6875  ;   //pwm1 代表将要输出PWM的占空比
    13.           frequency1=0; //清零
    14.      period_TIM4=0;
    15. /********PID2处理**********/
    16.          PID2.sum_error+=(incPIDcalc(&PID2,frequency2));         //计算增量并累加  Y=Y+Y'                
    17.          pwm2=PID2.sum_error*4.6875 ;   //将要输出PWM的占空比 
    18.         frequency2=0;
    19.         period_TIM1=0;
    20. /********PID3处理**********/
    21.          PID3.sum_error+=(incPIDcalc(&PID3,frequency3));          //常规PID控制
    22.         pwm3=PID3.sum_error*4.6875 ;   //将要输出PWM的占空比
    23.         frequency3=0;
    24.         period_TIM2=0;
    25. /********PID4处理**********/
    26.             PID4.sum_error+=(incPIDcalc(&PID4,frequency4));         //计算增量并累加
    27.          pwm4=PID4.sum_error*4.6875 ;   //将要输出PWM的占空比 
    28.         frequency4=0;
    29.         period_TIM5=0; 
    30.           }
    31. TIM_SetCompare(pwm1,pwm2,pwm3,pwm4);             //重新设定PWM值

    32. TIM_ClearITPendingBit(TIM6, TIM_IT_Update); //清除中断标志位                
    33. }

    复制代码

     

    TIM_SetCompare ()函数:

     

    上面几个代码是PID实现的关键部分

    还有整定过程:
    办法有不少,这里用的是先KP,再TI,再TD,在微调。其他的办法特别是有个尼古拉斯法我发现不适合我这个控制对象。
    先Kp,就是消除积分和微分部分的影响,这里我纠结过到底是让Ti 等于一个很大的值让Ki=Kp*(T/Ti)里面的KI接近零,还是直接定义KI=0,TI=0.
    然后发现前者没法找到KP使系统震荡的临界值,第二个办法可以得到预期的效果:即KP大了会产生震荡,小了会让系统稳定下来,当然这个时候是有稳态误差的。
    随后把积分部分加进去,KI=Kp*(T/Ti)这个公式用起来,并且不断调节TI 。TI太大系统稳定时间比较长。
    然后加上Kd        =Kp*(Td/T),对于系统响应比较滞后的情况效果好像好一些,我这里的电机反映挺快的,所以Td值很小。
    最后就是几个参数调节一下,让波形好看一点。这里的波形实际反映的是采集回来的转速值,用STM32的DAC功能输出和转速对应的电压,用示波器采集的。
    最后的波形是这样的:
     
    内容转自某莫
    当然最后还是要分享一下最终的PID文件
     pid.zip 

     

     

     

     

    PID控制算法的C语言实现一 PID算法原理

       最近两天在考虑一般控制算法的C语言实现问题,发现网络上尚没有一套完整的比较体系的讲解。于是总结了几天,整理一套思路分享给大家。

       在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万能算法,如果能够熟练掌握PID算法的设计与实现过程,对于一般的研发人员来讲,应该是足够应对一般研发问题了,而难能可贵的是,在我所接触的控制算法当中,PID控制算法又是最简单,最能体现反馈思想的控制算法,可谓经典中的经典。经典的未必是复杂的,经典的东西常常是简单的,而且是最简单的,想想牛顿的力学三大定律吧,想想爱因斯坦的质能方程吧,何等的简单!简单的不是原始的,简单的也不是落后的,简单到了美的程度。先看看PID算法的一般形式:

       PID的流程简单到了不能再简单的程度,通过误差信号控制被控量,而控制器本身就是比例、积分、微分三个环节的加和。这里我们规定(在t时刻):

       1.输入量为rin(t);

       2.输出量为rout(t);

       3.偏差量为err(t)=rin(t)-rout(t);

       pid的控制规律为

       理解一下这个公式,主要从下面几个问题着手,为了便于理解,把控制环境具体一下:

       1.规定这个流程是用来为直流电机调速的;

       2.输入量rin(t)为电机转速预定值;

       3.输出量rout(t)为电机转速实际值;

       4.执行器为直流电机;

       5.传感器为光电码盘,假设码盘为10线;

       6.直流电机采用PWM调速 转速用单位 转/min 表示;

      不难看出以下结论:

       1.输入量rin(t)为电机转速预定值(转/min);

       2. 输出量rout(t)为电机转速实际值(转/min);

       3.偏差量为预定值和实际值之差(转/min);

       那么以下几个问题需要弄清楚:

       1.通过PID环节之后的 U(k) 是什么值呢?

       2.通过调节 PWM 的电压占空比来调节电机的转速。

       3.那么U(k)与控制电机的 PWM 之间存在怎样的联系呢?

     

    看到有不少人问到底如何让UK值与PWM占空比值对应,进而实现占空比输出和输出控制电压对应。

    (注意,我这里讨论的前提是输出控制的是电压,不是PWM方波。PWM输出后要经过滤波整形再输出控制。)

    前提条件:

    输出电压控制电压范围是0-10V。

    给定、反馈、输出电压采样输入电压范围是0-5V(经过运放)。

    使用单片机AD为10位AD芯片。

    那么10位AD芯片电压采集得到的数据范围就是0-1024。

    PWM为 8位可调占空比方波,0对应输出占空比为0的方波,255对应输出占空比100%的方波,127对应输出50%的方波。

    比如当前给定是2.5V,反馈电压是1V。(KP,KI,KD等系数略,关于PID算法的整数实现我在前文中有论述如何实现)。

    那么经过AD采样

    1、给定2.5V对应为 512

    2、反馈1V对应为 205

    假定经过PID计算得到的UK为400

    也就意味着输出电压应当为(400*(UPWM峰值电压))/1024

    那么UK对应的PWM占空比是多少呢?

    我们知道,UK=1024对应占空比为100,也就是PWM的占空比系数为255。可知,PWM系数 = UK/4;

    那么400就应当对应系数 400/4=100。

    也就是输出电压=400*10/1024=3.9V

    同时,由于采样精度以及PWM输出占空比精度控制的问题,将导致输出电压和期望值不是那么线性,所以,我在项目内加入了输出电压采样的控制。

    采样AD输入为0-5V,所以,对于输出0-10V有一个缩小的比例。

    输出10V则采样值对应为255

    输出5V则采样之对应127

    可知,3.9V对应AD结果为97

    采样输出电压值,可以针对性的调整一下占空比输出,从而得到误差允许范围内的一个控制输出电压。

     

     

     

    展开全文
  • 1 增量式算法不需要做累加,控制量增量的确定仅与最近几次偏差采样值有关,计算误差对控制 量计算的影响较小。 而位置式算法要用到过去偏差的累加值,容易产生较大的累加误差。 2 增量式算法得出的是控制量的...
  • 增量式PID算法控制房间温度变化的简单例子 LabVIEW简单的PID控制程序 前面板 程序 控制器 控制器采用普通的PID控制。 加热器 k值就是就是控制器PID计算出来的u值,从而控制加热器加热的功率。 房间 用理论计算...
  • 从一开始接触PID,对于所谓的位置式,增量式算法,这两者只是在算法的实现上的存在差异,本质的控制上对于系统控制的影响还是相同.
  • 上篇介绍了连续系统的PID算法,但是计算机控制是一种采样控制,他只能根据采样时刻的偏差来计算控制量,因此计算机控制系统中,必须对公式进行离散化,具体就是用求和代替积分,用向后差分来代替微分,使模拟PID...
  • 上篇介绍了连续系统的PID算法,但是计算机控制是一种采样控制,他只能根据采样时刻的偏差来计算控制量,因此计算机控制系统中,必须对公式进行离散化,具体就是用求和代替积分,用向后差分来代替微分,使模拟PID离散...
  • 本文是本人自己做的简单笔记。 高手忽略哈。 typedef struct{ int SetPoint;...void PID_Arg_Init(PID* sptr) { sptr->SumError = 0; sptr->LastError = 0; sptr->PrevError = 0; sptr-&
  • 介绍了一种基于FPGA的用Veilog HDL语言设计的增量式PID控制器的设计方法, 并为了提高控制精度, 消除精差, 减少由于短时间内系统输出余量过大造成的偏差而引起系统较大的振荡, 因此增加了积分分离控制算法, 从而...
  • 位置式和增量式PID算法

    千次阅读 2018-11-01 21:33:05
    一、PID框图: 模拟的PID公式 u(t)=Kp[e(t)+1Ti∫0te(t)dt+Tdde(t)dt]u(t) = Kp [e(t) + \frac{1}{Ti} \int ^t_0 e(t) dt + Td \frac {de(t)}{dt} ]u(t)=Kp[e(t)+Ti1​∫0t​e(t)dt+Tddtde(t)​] 将PID...
  • 首先简单介绍一下增量式pid: 代码实现: 1.定义一个结构体变量 struct { float SetSpeed; //定义速度期望值 float ActualSpeed; //定义速度实际值 float err_0; //定义速度偏差值 float err_1; //定义上一...
  • 6 常用算法程序; > ) { ; "输入两个数用空格或者回车间隔\n) ; , ; 设t为的最小数 (m>n) ; ; (t>0) { (0 0) ; 每次检查m和n能否整除t 如果是则找到最大公约数 ; } "和的最大公约数为\n) ; 设t为的最大数 (m>n) ; ; ...
  • 增量式PID算法的C语言代码

    万次阅读 多人点赞 2015-07-30 20:43:04
    我力求把代码写得简洁、清晰,而具体的PID算法推导我就不当搬运工了。 typedef struct{ float limit; //输出限幅 float deadband; //死区 float target; //目标输出量 float feedback; //实际输出量 floa
  • 增量式PID E(t) = 设定值-t时刻采样值 增量d计算公式 d = KP*[E(t)-E(t-1)]+KI*E(t)+KD*[E(t)-2E(t-1)+E(t-2)] micropython程序实现 参考2011年电子设计大赛 风板控制系统(F题) 角度检测采用精密导电塑料电位器...
  • 1、PID算法离散化 在采样周期足够小时,可以作如下近似: u(t)≈u(k)u_{(t)}\approx u_{(k)}u(t)​≈u(k)​ ;e(t)≈e(k)e_{(t)}\approx e_{(k)}e(t)​≈e(k)​ ∫0te(t)dt=∑i=0ke(i)Δt=∑i=0kTe(i)\int_{0}^{t}e_{...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,635
精华内容 2,254
关键字:

增量式pid算法