PID入门到精通(一篇足够)!!!!!
本篇文章是我看完江科大的PID入门教程所写的,能够帮助大家快速入门PID
同时如果调整软件PID很长时间都没有见到好的效果,其实可以想一想是不是硬件的问题啊
目录
我使用的编码电机是wheeltec的mg513编码电机



1理论知识
1.1PID基本原理
PID是比例(Proportional)、积分(Integral)、微分(Differential)的缩写 PID是一种闭环控制算法,它动态改变施加到被控对象的输出值(Out),使得被控对象某一物理量的实际值(Actual),能够快速、准确、稳定地跟踪到指定的目标值(Target)
PID是一种基于误差(Error)调控的算法,其中规定:误差=目标值-实际值,PID的任务是使误差始终为0
PID对被控对象模型要求低,无需建模,即使被控对象内部运作规律不明确,PID也能进行调控
PID公式与系统框图

比例项(P)

下面四个图就是不同kp的时候目标值与实际值的误差

稳态误差
因为如果误差项为0的时候,那么电机驱动力度就为0了,由于摩檫力等因素,这个误差项会加大,然后再去调控,当摩檫力与电机驱动力度相同的时候,那么这个就会保持在一个实际转动速度,但是小于目标值
所以稳态误差就是由于一些摩擦之类的
积分项(I)PI控制
在程序中积分就是给一个变量,所产生的误差累加起来,累加起来的值乘以Ki就是输出值

积分项最严重的问题就是滞后性(可能会使过充过大)!!!因为积分累加这是一个过程,比例项的反应是最快的
累加之后的值就是摩檫力,正好抵消


Ki的值过大就会超调,抖动也会更厉害
微分项(D)


连续形式的PID与离散形式的PID公式
单片机的PID是离散型的,每隔一个周期就会去调控一次,因为最开始通用的是模拟电子电路
模拟电子电路里面就会有许多运算方式,但是模拟电路实现PID已经被淘汰了
连续形式PID离散化的示意图
位置式PID与增量式PID公式
位置式PID其实就是简化过后的离散式PID

位置式PID与增量式PID的比较
举个栗子吧
用打开阀门来举例,位置式PID相当于告诉阀门打开位置50%,30%,增量式PID告诉阀门每次增加8%,减少7%等等
1.2PID程序实现
1 ,在while循环中执行调控,用delay()来控制调控时间
2,用一个定时器进行调控,中断中去调用,但是要注意,中断中,相当于是多线程,不能同时访问同一块内存空间(这个也是常用的)
3, 这个用标志位来进行调控(主程序不能阻塞)

位置式PID程序的实现

增量式PID程序的实现

基础驱动编写
这个就是串口,电机驱动,定时器等等的底层编写
编码器解释:
我用的式mg513GMR编码器,磁铁级数为500,减速比1:30
这里编码器是检测AB相的上升下降沿,那以下面为例,转一圈AB输出11个脉冲,那么编码器的计数值就为4*11=44,然后减速比为9.3,那么你吧电机旋转一圈编码器就会有44*9.3=408个数,这样就可以通过这个数除408再*360,就可以计算出转动的角度,这个速度就是用定时器每隔一段时40ms间,就Encoder_get(),这样就是/40ms----就可以换算速度了
编码器测速周期要与PID调控周期一样
还要注意,这个电机正向旋转给的PWM的极性要和编码器测速的极性相同,这样调控才是负反馈,要不然这两个极性相反的话就是正反馈了,电机直接满速转动
PID参数的整定
P:一般就是输出/输入
I : 这个要看是否有稳态误差,一般比较小
D:不一定要加,看到实际的波形再做调整
2代码编写
位置式PID
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include <math.h>
int16_t Speed;
uint8_t KeyNum;
int8_t PWM;
float Target,Actual,Out;
float Kp=0.6,Ki=0.2,Kd=0.2;
float Error0,Error1,ErrorInt;
int main(void)
{
OLED_Init();
LED_Init();
Key_Init();
Motor_Init();
Timer_Init();
Encoder_Init();
Serial_Init();
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Target+=10;
}
else if(KeyNum == 2)
{
Target-=10;
}
else if(KeyNum == 3)
{
Target=0;
}
OLED_Printf(0,0,OLED_8X16,"Tar:%+04.0f",Target);
OLED_Printf(0,16,OLED_8X16,"Act:%+04.0f",Actual);
OLED_Printf(0,32,OLED_8X16,"Out:%+04.0f",Out);
Serial_Printf("%f,%f,%f\r\n",Target,Actual,Out);
OLED_Update();
}
}
void TIM1_UP_IRQHandler()
{
static uint16_t Count = 0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update==SET))
{
Key_Tick();
Count++;
if(Count >= 20)
{
Count = 0;
//Speed = Encoder_Get()/(50);
Actual = Encoder_Get()/(25);
Error1 = Error0;
Error0 = Target -Actual;
if(fabs(Ki)>0.001)
{
ErrorInt +=Error0;
}
Out = Kp*Error0+Ki*ErrorInt+Kd*(Error0-Error1);
if(Out>100){Out = 100;}
if(Out<-100){Out = -100;}
Motor_SetSpeed(Out);
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
增量式PID
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include <math.h>
int16_t Speed;
uint8_t KeyNum;
int8_t PWM;
float Target,Actual,Out;
float Kp=0.9,Ki=0.3,Kd=0;
float Error0,Error1,Error2;
int main(void)
{
OLED_Init();
LED_Init();
Key_Init();
Motor_Init();
Timer_Init();
Encoder_Init();
Serial_Init();
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Target+=10;
}
else if(KeyNum == 2)
{
Target-=10;
}
else if(KeyNum == 3)
{
Target=0;
}
OLED_Printf(0,0,OLED_8X16,"Tar:%+04.0f",Target);
OLED_Printf(0,16,OLED_8X16,"Act:%+04.0f",Actual);
OLED_Printf(0,32,OLED_8X16,"Out:%+04.0f",Out);
Serial_Printf("%f,%f,%f\r\n",Target,Actual,Out);
OLED_Update();
}
}
void TIM1_UP_IRQHandler()
{
static uint16_t Count = 0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update==SET))
{
Key_Tick();
Count++;
if(Count >= 20)
{
Count = 0;
//Speed = Encoder_Get()/(50);
Actual = Encoder_Get()/(25);
Error2 = Error1;
Error1 = Error0;
Error0 = Target -Actual;
Out += Kp*(Error0-Error1)+Ki*Error0+Kd*(Error0-2*Error1+Error2);
if(Out>100){Out = 100;}
if(Out<-100){Out = -100;}
Motor_SetSpeed(Out);
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
位置式PID定位控制
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include <math.h>
int16_t Speed;
uint8_t KeyNum;
int8_t PWM;
float Target,Actual,Out;
float Kp=0.7,Ki=0.005,Kd=0.1;
float Error0,Error1,ErrorInt;
int main(void)
{
OLED_Init();
LED_Init();
Key_Init();
Motor_Init();
Timer_Init();
Encoder_Init();
Serial_Init();
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Target+=50;
}
else if(KeyNum == 2)
{
Target-=50;
}
else if(KeyNum == 3)
{
Target=0;
}
OLED_Printf(0,0,OLED_8X16,"Tar:%+04.0f",Target);
OLED_Printf(0,16,OLED_8X16,"Act:%+04.0f",Actual);
OLED_Printf(0,32,OLED_8X16,"Out:%+04.0f",Out);
Serial_Printf("%f,%f,%f\r\n",Target,Actual,Out);
OLED_Update();
}
}
void TIM1_UP_IRQHandler()
{
static uint16_t Count = 0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update==SET))
{
Key_Tick();
Count++;
if(Count >= 20)
{
Count = 0;
//Speed = Encoder_Get()/(50);
Actual += Encoder_Get()/(50);
Error1 = Error0;
Error0 = Target -Actual;
if(fabs(Ki)>0.001)
{
ErrorInt +=Error0;
}
Out = Kp*Error0+Ki*ErrorInt+Kd*(Error0-Error1);
if(Out>100){Out = 100;}
if(Out<-100){Out = -100;}
Motor_SetSpeed(Out);
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
增量式PID定位控制
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Timer.h"
#include "Key.h"
#include "Motor.h"
#include "Encoder.h"
#include "Serial.h"
#include <math.h>
int16_t Speed;
uint8_t KeyNum;
int8_t PWM;
float Target,Actual,Out;
float Kp=0.99,Ki=0.01,Kd=0.2;
float Error0,Error1,Error2;
int main(void)
{
OLED_Init();
LED_Init();
Key_Init();
Motor_Init();
Timer_Init();
Encoder_Init();
Serial_Init();
while (1)
{
KeyNum = Key_GetNum();
if(KeyNum == 1)
{
Target+=50;
}
else if(KeyNum == 2)
{
Target-=50;
}
else if(KeyNum == 3)
{
Target=0;
}
OLED_Printf(0,0,OLED_8X16,"Tar:%+04.0f",Target);
OLED_Printf(0,16,OLED_8X16,"Act:%+04.0f",Actual);
OLED_Printf(0,32,OLED_8X16,"Out:%+04.0f",Out);
Serial_Printf("%f,%f,%f\r\n",Target,Actual,Out);
OLED_Update();
}
}
void TIM1_UP_IRQHandler()
{
static uint16_t Count = 0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update==SET))
{
Key_Tick();
Count++;
if(Count >= 20)
{
Count = 0;
//Speed = Encoder_Get()/(50);
Actual += Encoder_Get()/(25);
Error2 = Error1;
Error1 = Error0;
Error0 = Target -Actual;
Out += Kp*(Error0-Error1)+Ki*Error0+Kd*(Error0-2*Error1+Error2);
if(Out>100){Out = 100;}
if(Out<-100){Out = -100;}
Motor_SetSpeed(Out);
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
2.1总结一下
在定位控制的程序中,这个积分项一定不能太大,因为如果太大,积分项的累加的结果会使Out直接输出到边界,一个强力的扭力让电机猛的一下转动,结果调整过头了,这样就会反反复复的转动,无法达到平衡,其实也可以直接用PD控制不要积分项
增量式PID比较依赖积分项,因为如果某一次Out是错误的,那么这个错误就会产生稳态误差,需要通过积分项来消除这个误差(所以最好不要将Ki设置为0)
增量式PID Kp,Ki,Kd全为0的时候,Out的值并不会降低到0,而是下降到一个稳定的值,这时候可以手动调整target的值,相当于进行手动控制,而且切换的时候抖动没有位置式PID那么大,更多用于手动切换到自动的模式
3PID算法改进
积分限幅:限制积分的幅度,防止积分深度饱和
积分分离:误差小于一个限度才开始积分,反之则去掉积分部分
变速积分:根据误差的大小调整积分的速度
--------------------------------------------------------------------------------------------------------------------
微分先行:将对误差的微分替换为对实际值的微分
不完全微分:给微分项加入一阶惯性单元(低通滤波器)
--------------------------------------------------------------------------------------------------------------------
输出偏移:在非0输出时,给输出值加一个固定偏移
输入死区:误差小于一个限度时不进行调控
积分限幅
积分限幅一般出现在位置式PID里面,积分项会一直累加,当电机卡住时,积分会饱和,卡住的时间越久,积分到后面达到深度饱和,当外力去掉的时候,电机会全速转动一段时间
积分上下限确定
1可以通过测量正常工作的时候积分项的最大值和最小值来确定,2可以通过Out/Ki来确定积分项的上下限,因为如果Ki*积分项等于100(Out的边界),那么积分项再变大就没有意义了,输出只能还是100
两种程序的实现
积分分离
定位置控制加入积分项会有超调问题,定速没有(很小)
超调的原因:因为积分项累加的力一定会导致超调
阈值的确定:通过测定无积分项的时候移动位置看看无法消除的稳态误差有多少,基本上让阈值大于这个值就行了
两种实现方法
变速积分
变速积分的问题其实就是积分分离的阈值给的比较小,当误差超过这个阈值的时候,就完全没有积分项的作用了,其实变速积分就是积分分离的升级版 (但是变速积分最后用积分限幅控制一下)
变速积分与积分分离的对比
这里就是设置一个函数用来改变积分项
两种实现方法
微分先行
就是微分项在目标突变时会变得很大,微分项其实就是为了阻碍我们朝着目标值变化,当是根据两次误差的斜率来确定的,但是如果目标值突然变大,这个斜率是正无穷大,之后会变为负数逐渐减小(其实这个不用微分先行的话可以加快响应速)
对误差求微分其实他的值就是对实际值求微分的负值(但是这个可以改进也可以不改变,这种就是让效果变得更平滑,但是突然改变的时候输出响应比较慢)
程序实现
不完全微分
噪声对微分项的影响是 最大的,所以不完全微分其实就是加入低通滤波器(用均值滤波)
但是滤波后这个就会产生时延,对于相应比较快的项目是不行的(所以只对D项加滤波)
加权平均后就会有滤波的作用
在程序中用rand给实际值一个噪声,用来模拟
但是我调整的还不是很明显(但是out输出平缓一点了)
程序实现
输出偏移
这是对于移动需要一定力度的执行器,如果输出值比较小,电机就不会动,这就会产生调控误差
而且这时候的Out比较小,既不产生驱动,而且还消耗电流,我们就可以设定一个区域,直接跳过这个不能驱动电机的Out区域
但是加上输出偏移后,会出现抖动,只要加一点力度,电机就会转动
程序实现
输入死区
这个就是防止抖动
输出偏移和输出死区两个同时用的时候效果是比较好的
其实要我说,最后一个输出偏移+输入死区就是很好的调控(我个人感觉效果最好)
扩展:多环串级PID
单环PID只能对被控对象的一个物理量进行闭环控制,而当用户需要对被控对象的多个维度物理量(例如:速度、位置、角度等)进行控制时,则需要多个PID控制环路,即多环PID,多个PID串级连接,因此也称作串级PID
多环PID相较于单环PID,功能上,可以实现对更多物理量的控制,性能上,可以使系统拥有更高的准确性、稳定性和响应速度
定位置控制单环与双环的比较
内环速度环可以用PI控制器,外环位置环可以用PD控制器
程序实现
其实关键就是外层的输出值是内层的目标值
举个栗子
1如果要实现平衡小车,那么就要用到双环PID,一个环是速度环,一个环是平衡环,平衡环需要MPU6050的水平值为0,速度环需要将速度保持一个速度,但是水平保持如果不稳定,就需要速度的调控
2对于四轴飞行器,其实也是内环是角速度PID ,外环是位置环PID
双环PID是先设计内环,再设计外环,调参也是先调内环再调外环,两个环访问被控对象的时间不同,同时外环的输出值作用于内环的目标值,首先调节内环的调节,调节好了再让外环工作
双环PID的控制效果是很好的,几乎没有误差,多小的误差几乎都可以消除,响应速度也非常快
通过改变外环的输出限幅,就可以进行定速控制
代码封装:
PID.h
#ifndef __PID_H
#define __PID_H
typedef struct
{
float Target;
float Actual;
float Out;
float Kp;
float Ki;
float Kd;
float Error0;
float Error1;
float ErrorInt;
float OutMax;
float OutMin;
}PID_t;
void PID_Update(PID_t * p);
#endif
PID.c
#include "stm32f10x.h" // Device header
#include "PID.h"
//这只是最基本的PID调控,在实际运用中可以用许多改进算法来提高准确度
void PID_Update(PID_t * p)
{
p->Error1 = p->Error0;
p->Error0 = p->Target -p->Actual;
if(p->Ki!=0)
{
p->ErrorInt += p->Error0;
}
else
{
p->ErrorInt = 0;
}
p->Out = p->Kp * p->Error0 + p->Ki * p->ErrorInt + p->Kd * (p->Error0-p->Error1);
if(p->Out > p->OutMax){p->Out = p->OutMax;}
if(p->Out < p->OutMin){p->Out = p->OutMin;}
}
定时器中的PID控制代码
void TIM1_UP_IRQHandler()
{
static uint16_t Count1 = 0,Count2=0;
if(TIM_GetITStatus(TIM1,TIM_IT_Update==SET))
{
Key_Tick();
//内层PID
Count1++;
if(Count1 >= 40)
{
Count1 = 0;
Speed = Encoder_Get()/(50);
Location +=Speed;
Inner.Actual = Speed;
PID_Update(&Inner);
Motor_SetSpeed(Inner.Out);
}
//外层PID
Count2++;
if(Count2 >= 40)
{
Count2 = 0;
Outer.Actual = Location;
PID_Update(&Outer);
Inner.Target = Outer.Out;//这里可以去限制转动的速率
}
TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}
}
更多推荐







定位置控制加入积分项会有超调问题,定速没有(很小)





就是微分项在目标突变时会变得很大,微分项其实就是为了阻碍我们朝着目标值变化,当是根据两次误差的斜率来确定的,但是如果目标值突然变大,这个斜率是正无穷大,之后会变为负数逐渐减小(其实这个不用微分先行的话可以加快响应速)


噪声对微分项的影响是 最大的,所以不完全微分其实就是加入低通滤波器(用均值滤波)


这是对于移动需要一定力度的执行器,如果输出值比较小,电机就不会动,这就会产生调控误差
这个就是防止抖动







所有评论(0)