/*Github*/ Fork me on GitHub

PID库与PID基本优化(一)

本系列旨在以我自己写的PID lib为例,讲一下PID的几点基本优化,PID的基本原理网上有很多资料,因此本系列将不会涉及PID的基本实现原理,在这里特别推荐Matlab tech talk的PID教程:https://ww2.mathworks.cn/videos/series/understanding-pid-control.html。

由于笔者大一在读,还没有学习自动控制原理等课程,因此本系列将不会从自控原理角度展开,相反的,本系列将试图从“直觉”展开,通过直观的描述让大家从直觉上感受并理解PID的一些包括微分先行、积分分离等基础的优化。

由于笔者水平有限,文中难免存在一些不足和错误之处,诚请各位批评指正。

(一)中主要讲解代码结构与代码使用,算法有关内容于(二)开始讲解

1 代码结构

该PID lib全部代码详见:https://github.com/CharlesW1970/PID_Library

1.1 PID结构体与有关枚举

该lib通过pid结构体保存于pid运算有关的参数数据,通过枚举表示其他有关量:

//PID结构体
typedef struct _PID_TypeDef
{
    float Target;
    float LastNoneZeroTarget;
    float Kp;
    float Ki;
    float Kd;

    float Measure;
    float Last_Measure;
    float Err;
    float Last_Err;

    float Pout;
    float Iout; //Iout = ITerm_0 + ITerm_1 +....+ ITerm_n
    float Dout;
    float ITerm; //ITerm = Err * Ki

    float Output;
    float Last_Output;

    float MaxOut;
    float IntegralLimit;
    float DeadBand;
    float ScalarA; //变积分公式参数
    float ScalarB; //ITerm = Err*((A-abs(err)+B)/A)  when B<|err|<A+B

    uint8_t Improve; //用于使能优化

    PID_ErrorHandler_t ERRORHandler;

    void (*PID_param_init)(
        struct _PID_TypeDef *pid,
        uint16_t maxOut,
        uint16_t integralLimit,
        float deadband,
        float Kp,
        float ki,
        float kd,
        float A,
        float B,
        uint8_t improve);

    void (*PID_reset)(
        struct _PID_TypeDef *pid,
        float Kp,
        float ki,
        float kd);
} PID_TypeDef;

//PID优化功能枚举
typedef enum pid_Improvement_e
{
    NONE = 0X00,                        //无
    Integral_Limit = 0x01,              //积分限幅
    Derivative_On_Measurement = 0x02,   //微分先行
    Trapezoid_Intergral = 0x04,         //梯形积分
    Proportional_On_Measurement = 0x08, //该系列不涉及
    OutputFilter = 0x10,                //输出滤波
    ChangingIntegralRate = 0x20,        //变积分
    ErrorHandle = 0x80,                 //异常处理
} PID_Improvement_e;

//异常情况枚举,这里只写了电机堵转保护一种
typedef enum errorType_e
{
    PID_ERROR_NONE = 0x00U,
    Motor_Blocked = 0x01U
} ErrorType_e;

//异常情况结构体
typedef struct
{
    uint64_t ERRORCount;
    ErrorType_e ERRORType;
} PID_ErrorHandler_t;

1.2 PID初始化

在使用之前需要先调用PID_Init函数进行参数初始化和函数连接

void PID_Init(
    PID_TypeDef *pid,
    uint16_t max_out,
    uint16_t intergral_limit,
    float deadband,

    float kp,
    float Ki,
    float Kd,

    float A,
    float B,

    uint8_t improve)
{
    pid->PID_param_init = f_PID_param_init;
    pid->PID_reset = f_PID_reset; //连接Kp Ki Kd参数重设函数
    pid->PID_param_init(pid, max_out, intergral_limit, deadband,
                        kp, Ki, Kd, A, B, improve); //连接并调用参数初始化函数
}

static void f_PID_param_init(
    PID_TypeDef *pid,
    uint16_t max_out,
    uint16_t intergral_limit,
    float deadband,

    float kp,
    float Ki,
    float Kd,

    float Changing_Integral_A,
    float Changing_Integral_B,

    uint8_t improve)
{
    //参数初始化
    pid->DeadBand = deadband;
    pid->IntegralLimit = intergral_limit;
    pid->MaxOut = max_out;
    pid->Target = 0;

    pid->Kp = kp;
    pid->Ki = Ki;
    pid->Kd = Kd;
    pid->ITerm = 0;

    pid->ScalarA = Changing_Integral_A;
    pid->ScalarB = Changing_Integral_B;

    pid->Improve = improve;

    //异常处理初始化
    pid->ERRORHandler.ERRORCount = 0;
    pid->ERRORHandler.ERRORType = PID_ERROR_NONE;

    pid->Output = 0;
}

1.3 PID 计算

PID_Calculate函数与网上大多数代码大体结构相同,只是添加了不同的优化函数,具体优化在各函数(如:f_PID_ErrorHandle、f_Trapezoid_Intergral)中实现,PID_Calculate函数具体代码如下:

float PID_Calculate(PID_TypeDef *pid, float measure, float target)
{
    if (pid->Improve & ErrorHandle) 
    {
        //异常处理
        f_PID_ErrorHandle(pid);
        if (pid->ERRORHandler.ERRORType != PID_ERROR_NONE)
        {
            //电机堵转保护
            pid->Output = 0;
            return 0; 
        }
    }

    //误差更新
    pid->Measure = measure;
    pid->Target = target;
    pid->Err = pid->Target - pid->Measure;

    //死区内进行计算
    if (ABS(pid->Err) > pid->DeadBand)
    {
        //计算比例、微分输出与该周期积分项结果
        pid->Pout = pid->Kp * pid->Err;
        pid->ITerm = pid->Ki * pid->Err;
        pid->Dout = pid->Kd * (pid->Err - pid->Last_Err);

        //判断是否使能梯形积分
        if (pid->Improve & Trapezoid_Intergral)
            f_Trapezoid_Intergral(pid);
        //判断是否使能变积分
        if (pid->Improve & ChangingIntegralRate)
            f_Changing_Integral_Rate(pid);
        //判断是否使能积分限幅
        if (pid->Improve & Integral_Limit)
            f_Integral_Limit(pid);
        //判断是否使能微分先行
        if (pid->Improve & Derivative_On_Measurement)
            f_Derivative_On_Measurement(pid);

        //计算积分输出
        pid->Iout += pid->ITerm;

        //计算pid总输出
        pid->Output = pid->Pout + pid->Iout + pid->Dout;

        //判断是否使能输出滤波
        if (pid->Improve & OutputFilter)
            f_OutputFilter(pid);

        //输出限幅
        f_Output_limit(pid);
    }
    //数据保存供下一周期调用
    pid->Last_Measure = pid->Measure;
    pid->Last_Output = pid->Output;
    pid->Last_Err = pid->Err;

    return pid->Output;
}

2 如何使用

这里给出以发布在GitHub上的示例,具体不在详细讲解

//pid函数连接
PID_Init(&PID_Example, 9600, 5000, 3, 1, 5, 0.3, 0.3, 100, 100, 
   		 ErrorHandle | Integral_Limit | OutputFilter);

//修改kp ki kd
PID_Example.PID_reset(&PID_Example, 3, 1, 0);

//计算
PID_Calculate(&PID_Example, measure, target);

该篇对该lib结构和使用就讲到这里,下一篇将会开始算法讲解。

posted @ 2020-03-03 20:37  WangHongxi  阅读(6507)  评论(0编辑  收藏  举报