串级PID控制算原理及法详解

文章目录

[1. PID](#1. PID)

[2. 串级PID](#2. 串级PID)

[3. 串级PID的物理量](#3. 串级PID的物理量)

[4. C语言实现单极PID](#4. C语言实现单极PID)

[5. C语言实现串极PID](#5. C语言实现串极PID)

[6. 模拟仿真](#6. 模拟仿真)


1. PID

PID是应用最广泛的闭环控制方法之一,是一种常用的反馈控制方法,对于每个PID控制器由三个部分组成:比例控制(Proportional)、积分控制(Integral)和微分控制(Derivative)。

PID三个环节的作用

由控制无人机案例我们可以总结出PID三个环节各自的主要作用和效应:

  • 比例环节:起主要控制作用,使反馈量向目标值靠拢,但可能导致振荡。

  • 积分环节:消除稳态误差,但会增加超调量。

  • 微分环节:产生阻尼效果,抑制振荡和超调,但会降低响应速度。

对于单环PID的控制算原理及法详解可以看下面这篇文章:

PID原理及控制算法详解-CSDN博客

2. 串级PID

串级PID控制是一种高级控制策略,通过使用两个(或更多)PID控制器来提高系统的稳定性和抗干扰能力。在串级控制中,外环控制器的输出作为内环控制器的设定值。图片中的示例详细解释了这种控制策略在四轴飞行器中的应用。

为什么要用串级PID?

表面上看,单一的PID控制器好像已经足够了,只要能够给出反馈,控制相应的物理量就可以了。但是,在一些复杂的系统中,单一的PID控制器可能无法满足控制需求。这时,就需要使用串级PID控制器来提高控制性能。

当进行无人机的调试时,可能会发现一个问题,如果无人机的高度与目标高度之间的距离较远的话,无人机在运动过程中的速度会很快,会导致较大的超调,而且不论怎么修改参数都很难让系统稳定。

这时如果运动过程中的速度没这么快就好了,这样就不会产生过冲了,这就要用到串级PID了。

在上一篇PID文章中所说的算法其实就是单级PID,目标值和反馈值经过一次PID计算就得到输出值并直接作为控制量,但如果目标物理量和输出物理量之间不止差了一阶的话,中间阶次的物理量我们是无法控制的。比如:目标物理量是位置,输出物理量是加速度,则无人机的速度是无法控制的。

而串级PID就可以改善这一点。串级PID其实就是两个单级PID"串"在一起组成的,它的框图如下:

图中的外环和内环就分别是一个单级PID,每个单级PID就如我们之前所说,需要获取一个目标值和一个反馈值,然后产生一个输出值。串级PID中两个环相"串"的方式就是将外环的输出作为内环的目标值。

小车上坡的类比

  • 单环PID的输出速度V不一定是真实的速度V,因此需要在内环再加上速度环PID,构成串级PID控制。
  • 外环控制位置,输出值是小车的理论速度,内环控制速度,输入是小车的理论速度,希望尽可能的使输出为理论速度。

四轴飞行器的控制

  • 外环为角度环,输出的是期望达到该角度所需的PWM(也可以理解为角速度和PWM的映射)。
  • 内环为角速度环,输入是期望的角速度和自身真实的角速度,输出为最终的PWM(角速度)。

串级PID控制的具体实现

  • 角度环PID控制器

    • 目标是控制四轴飞行器的角度。
    • 输入:期望角度和当前角度。
    • 输出:期望角速度(角度环PID输出值)。
  • 角速度环PID控制器

    • 目标是实现期望的角速度。
    • 输入:期望角速度和当前角速度。
    • 输出:控制电机的PWM信号。

3. 串级PID的物理量

在串级PID控制系统中,有三个输入和一个输出,且被控对象需要提供两个反馈量。我们以小球控制为例,来解释这些物理量的设置。

无人机控制案例中的串级PID设计

  1. 目标值:目标值是我们希望无人机达到的位置高度。

  2. 外环反馈:外环反馈量是无人机的实时高度位置。

  3. 内环反馈:内环反馈量是无人机的实时上升速度。

  4. 输出值:输出值是施加在无人机上的推力。

外环PID控制器

  • 输入:目标高度位置和当前实际高度位置的差值(位置误差)。
  • 输出:目标上升速度。

内环PID控制器

  • 输入:目标上升速度和当前实际上升速度的差值(速度误差)。
  • 输出:控制推力。

被控对象(无人机)

  • 输入:推力。
  • 输出:无人机的上升速度和高度位置。

具体流程

目标位置

  • 系统的目标是让无人机达到指定的高度位置。

外环PID控制器

  • 外环PID控制器接收目标高度位置和当前实际高度位置,计算出位置误差。
  • 根据位置误差,外环PID控制器计算出目标上升速度。

内环PID控制器

  • 内环PID控制器接收目标上升速度和当前实际上升速度,计算出速度误差。
  • 根据速度误差,内环PID控制器计算出需要施加的推力。

无人机

  • 施加推力后,无人机的上升速度和高度位置发生变化。
  • 实际上升速度和高度位置反馈回内环和外环控制器,形成闭环控制。

在无人机控制中,内环与无人机的速度控制形成一个闭环系统,PID内环负责无人机的速度控制;而外环与内环和无人机一起构成了一个位置控制系统,外环负责位置控制。总的来说,外环根据无人机位置误差计算出无人机需要达到的速度,而内环负责计算控制推力使无人机达到这个目标速度,两个环协同工作,就可以完成任务。

之前我们说到,使用串级PID控制后,我们可以对无人机的上升速度进行控制。那么,如何进行控制呢?其实就是对外环PID的输出进行限幅。因为外环PID输出的是目标速度,限制外环输出相当于限制了无人机目标速度的最大值,内环也就会维持无人机的上升速度不超过这个最大值。

在使用串级PID后,无人机的表现会有以下改变:

平稳的上升过程

  • 无人机不再像之前那样"着急"地向目标高度冲去,而是以近似匀速的方式上升,最终平稳地到达目标高度。

限幅作用

  • 由于高度误差较大,外环输出在大部分时间都处于限幅的最大值,这意味着无人机的上升速度被限制在一个安全的范围内,不会过快导致超调。
  • 内环PID则根据这个限幅的目标速度调整推力,使无人机平稳上升。

减少超调

  • 由于外环限制了目标速度,内环使无人机的速度变化缓慢,因此几乎没有超调。无人机的速度变化慢了,控制更加平稳。

控制位置和速度

  • 通过串级PID控制,我们不仅能精确控制无人机的高度位置,还能控制其上升速度,达到双重控制的效果。

4. C语言实现单极PID

这段代码实现了一个简单的单极PID控制器。PID控制器由三个部分组成:比例(P)、积分(I)和微分(D)。

#include <stdio.h>

// 定义PID结构体用于存放一个PID的数据
typedef struct
{
    float kp, ki, kd;        // 三个系数:比例、积分和微分
    float error, lastError;  // 当前误差、上次误差
    float integral, maxIntegral;  // 积分、积分限幅
    float output, maxOutput; // 输出、输出限幅
} PID;

// 用于初始化PID参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
    pid->kp = p;  // 设置比例系数
    pid->ki = i;  // 设置积分系数
    pid->kd = d;  // 设置微分系数
    pid->maxIntegral = maxI;  // 设置积分限幅
    pid->maxOutput = maxOut;  // 设置输出限幅
    pid->error = 0;
    pid->lastError = 0;
    pid->integral = 0;
    pid->output = 0;
}

// 进行一次PID计算
// 参数为(pid结构体, 目标值, 反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
    // 更新数据
    pid->lastError = pid->error;  // 将旧error存起来
    pid->error = reference - feedback;  // 计算新error

    // 计算微分项
    float dout = (pid->error - pid->lastError) * pid->kd;

    // 计算比例项
    float pout = pid->error * pid->kp;

    // 计算积分项
    pid->integral += pid->error * pid->ki;

    // 积分限幅
    if (pid->integral > pid->maxIntegral) 
        pid->integral = pid->maxIntegral;
    else if (pid->integral < -pid->maxIntegral) 
        pid->integral = -pid->maxIntegral;

    // 计算输出
    pid->output = pout + dout + pid->integral;

    // 输出限幅
    if (pid->output > pid->maxOutput) 
        pid->output = pid->maxOutput;
    else if (pid->output < -pid->maxOutput) 
        pid->output = -pid->maxOutput;
}

// 模拟设定执行器输出大小的函数
void setActuatorOutput(float output)
{
    // 这里是将PID的输出值应用到执行器上的代码
    // 在实际应用中,这可能是一个控制电机速度、阀门开度等的函数
    printf("Actuator Output: %f\n", output);
}

// 模拟获取反馈值的函数
float getFeedbackValue()
{
    // 这里是获取系统当前反馈值的代码
    // 在实际应用中,这可能是从传感器读取的值
    // 这里暂时返回一个模拟值
    static float feedback = 0;
    feedback += 1;  // 模拟反馈值增加
    return feedback;
}

// 模拟获取目标值的函数
float getTargetValue()
{
    // 这里是获取系统目标值的代码
    // 在实际应用中,这可能是从用户输入或者其他系统计算得到的
    // 这里暂时返回一个固定目标值
    return 100;
}

int main()
{
    PID mypid = {0};  // 创建一个PID结构体变量
    
    // 初始化PID参数:比例系数10,积分系数1,微分系数5,最大积分800,最大输出1000
    PID_Init(&mypid, 10, 1, 5, 800, 1000);

    while (1) // 进入循环运行
    {
        float feedbackValue = getFeedbackValue();  // 获取被控对象的反馈值
        float targetValue = getTargetValue();  // 获取目标值
        PID_Calc(&mypid, targetValue, feedbackValue);  // 进行PID计算,结果在output成员变量中
        setActuatorOutput(mypid.output);  // 将PID输出应用到执行器
        // 模拟延时,这里使用sleep函数,单位为秒
        // 在实际应用中,这个值根据系统需求调整
        sleep(1);  // 等待1秒再开始下一次循环
    }

    return 0;
}

定义PID结构体

  • 存放PID控制器的参数和状态,包括比例、积分、微分系数,当前和上次误差,积分值和积分限幅,输出值和输出限幅。

初始化PID参数的函数

  • 初始化PID结构体的参数。

进行一次PID计算的函数

  • 计算比例、积分和微分项,并对积分和输出进行限幅,更新PID输出值。

模拟设定执行器输出大小的函数

  • 打印PID控制器的输出值。在实际应用中,这将是控制执行器的代码。

模拟获取反馈值的函数

  • 返回一个模拟的反馈值。在实际应用中,这将是从传感器获取的反馈值。

模拟获取目标值的函数

  • 返回一个模拟的目标值。在实际应用中,这将是用户输入或其他系统计算得到的目标值。

主程序

  • 初始化PID参数,进入一个无限循环,获取反馈值和目标值,进行PID计算,将PID输出应用到执行器,并等待一段时间再进行下一次循环。

5. C语言实现串极PID

串级PID的调试

在编写代码时,PID的调参的顺序是先调整内环参数,内环控制效果达到理想效果后,再调整外环参数。

下面的代码实现了一个串级PID控制系统,通过两个单级PID控制器分别控制系统的不同部分(内环和外环)。

外环PID控制器计算出目标速度,内环PID控制器根据目标速度计算出实际控制量。

这种结构可以提高系统的稳定性和响应速度,适用于复杂控制系统,如无人机高度和速度控制。

#include <stdio.h>

// 定义PID结构体用于存放一个PID的数据
typedef struct
{
    float kp, ki, kd;        // 三个系数:比例、积分和微分
    float error, lastError;  // 当前误差、上次误差
    float integral, maxIntegral;  // 积分、积分限幅
    float output, maxOutput; // 输出、输出限幅
} PID;

// 用于初始化PID参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
    pid->kp = p;  // 设置比例系数
    pid->ki = i;  // 设置积分系数
    pid->kd = d;  // 设置微分系数
    pid->maxIntegral = maxI;  // 设置积分限幅
    pid->maxOutput = maxOut;  // 设置输出限幅
    pid->error = 0;
    pid->lastError = 0;
    pid->integral = 0;
    pid->output = 0;
}

// 进行一次PID计算
// 参数为(pid结构体, 目标值, 反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
    // 更新数据
    pid->lastError = pid->error;  // 将旧error存起来
    pid->error = reference - feedback;  // 计算新error

    // 计算微分项
    float dout = (pid->error - pid->lastError) * pid->kd;

    // 计算比例项
    float pout = pid->error * pid->kp;

    // 计算积分项
    pid->integral += pid->error * pid->ki;

    // 积分限幅
    if (pid->integral > pid->maxIntegral) 
        pid->integral = pid->maxIntegral;
    else if (pid->integral < -pid->maxIntegral) 
        pid->integral = -pid->maxIntegral;

    // 计算输出
    pid->output = pout + dout + pid->integral;

    // 输出限幅
    if (pid->output > pid->maxOutput) 
        pid->output = pid->maxOutput;
    else if (pid->output < -pid->maxOutput) 
        pid->output = -pid->maxOutput;
}




// 一直到这里,前面的都是单极PID的代码





// 串级PID的结构体,包含两个单级PID
typedef struct
{
    PID inner; // 内环
    PID outer; // 外环
    float output; // 串级输出,等于inner.output
} CascadePID;

// 串级PID的计算函数
// 参数(PID结构体, 外环目标值, 外环反馈值, 内环反馈值)
void PID_CascadeCalc(CascadePID *pid, float outerRef, float outerFdb, float innerFdb)
{
    PID_Calc(&pid->outer, outerRef, outerFdb); // 计算外环
    PID_Calc(&pid->inner, pid->outer.output, innerFdb); // 计算内环
    pid->output = pid->inner.output; // 内环输出就是串级PID的输出
}

// 模拟设定执行器输出大小的函数
void setActuatorOutput(float output)
{
    // 这里是将PID的输出值应用到执行器上的代码
    // 在实际应用中,这可能是一个控制电机速度、阀门开度等的函数
    printf("Actuator Output: %f\n", output);
}

// 模拟获取反馈值的函数
float getFeedbackValue()
{
    // 这里是获取系统当前反馈值的代码
    // 在实际应用中,这可能是从传感器读取的值
    // 这里暂时返回一个模拟值
    static float feedback = 0;
    feedback += 1;  // 模拟反馈值增加
    return feedback;
}

// 模拟获取目标值的函数
float getTargetValue()
{
    // 这里是获取系统目标值的代码
    // 在实际应用中,这可能是从用户输入或者其他系统计算得到的
    // 这里暂时返回一个固定目标值
    return 100;
}

CascadePID mypid = {0}; // 创建串级PID结构体变量

int main()
{
    // ...其他初始化代码
    // 初始化内环参数:比例系数10,积分系数0,微分系数0,最大积分0,最大输出1000
    PID_Init(&mypid.inner, 10, 0, 0, 0, 1000);
    // 初始化外环参数:比例系数5,积分系数0,微分系数5,最大积分0,最大输出100
    PID_Init(&mypid.outer, 5, 0, 5, 0, 100);

    while (1) // 进入循环运行
    {
        float outerTarget = getTargetValue(); // 获取外环目标值
        float outerFeedback = getFeedbackValue(); // 获取外环反馈值
        float innerFeedback = getFeedbackValue(); // 获取内环反馈值
        PID_CascadeCalc(&mypid, outerTarget, outerFeedback, innerFeedback); // 进行PID计算
        setActuatorOutput(mypid.output); // 设定执行器输出大小
        // 模拟延时,这里使用sleep函数,单位为秒
        // 在实际应用中,这个值根据系统需求调整
        sleep(1); // 等待1秒再开始下一次循环
    }

    return 0;
}

6. 模拟仿真

下面这个网站可以模拟调节PID参数来控制无人机

Webpack App

相关推荐
Swift社区2 小时前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Kent_J_Truman3 小时前
greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用
算法
IT 青年3 小时前
数据结构 (1)基本概念和术语
数据结构·算法
Dong雨3 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
SoraLuna4 小时前
「Mac玩转仓颉内测版24」基础篇4 - 浮点类型详解
开发语言·算法·macos·cangjie
liujjjiyun4 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥4 小时前
c++中mystring运算符重载
开发语言·c++·算法
trueEve5 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
天若有情6735 小时前
c++框架设计展示---提高开发效率!
java·c++·算法
ahadee5 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯