文章目录
1. PID
2. 串级PID
3. 串级PID的物理量
4. C语言实现单极PID
5. C语言实现串极PID
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设计
-
目标值:目标值是我们希望无人机达到的位置高度。
-
外环反馈:外环反馈量是无人机的实时高度位置。
-
内环反馈:内环反馈量是无人机的实时上升速度。
-
输出值:输出值是施加在无人机上的推力。
外环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,最大输出1000PID_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,最大输出1000PID_Init(&mypid.inner, 10, 0, 0, 0, 1000);// 初始化外环参数:比例系数5,积分系数0,微分系数5,最大积分0,最大输出100PID_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