目录
前言
发现好多小伙伴们知道PID可以用来控制小车直行、精准控制电压输出等,但是不知如何进行运用,本文先介绍了PID库的相关内容,并给出倒立摆的例子进行说明。
一、PID
1. PID()
PID的初始化函数,并设置输入、输出、目标值等参数。具体如下:
PID(&Input, &Output, &Setpoint, Kp, Ki, Kd, Direction)
PID(&Input, &Output, &Setpoint, Kp, Ki, Kd, POn, Direction)
其中,
Input:(double) 输入参数,待控制的量
Output:(double) 输出参数,指经过PID控制系统的输出量
Setpoint:(double) 目标值,希望达到的数值
Kp, Ki, Kd:(double) PID控制的比例、积分、微分系数
Direction:DIRECT 或 REVERSE,指的是当输入与目标值出现偏差时,向哪个方向控制。笔者发现,当设置为REVERSE时,会在初始化时将Kp, Ki, Kd变化为原来的负值。
POn:P_ON_E (默认) 或 P_ON_M,传统PID控制一定会出现超调值,但是P_ON_M可以稍微缓解这一现象,但是会牺牲一些上升时间,对比图如下:
默认(P_ON_E): P_ON_M:
2. Compute()
含PID的控制算法,应该在loop()函数之中反复调用。但是具体的输出和采样时间有关,否则很可能会出现什么也不做,输出为0的情况。此函数无参数,但是有一个返回值。
返回值说明:
True: 输出是经过计算的输出
False: 什么也没做,不输出或输出为0
3. SetMode()
指定PID算法的运行计算过程是自动(AUTOMATIC)还是手动(MANUAL)。手动就是关闭PID算法的计算功能。调为AUTOMATIC模式时才会初始化PID算法,进行输出。
SetMode(AUTOMATIC);//自动模式
4. SetOutputLimits()
会使得Output输出范围限制在一定的范围之内。若不进行设置,则默认以Arduino的PWM输出模式(0-255)进行输出。最小值和最大值都是double类型。
//SetOutputLimits(min, max);
SetOutputLimits(200, 300); // 输出在200-300之间
5. SetTunings()
PID()函数中已经对其进行初始化,但是在某些情况下,可能会需要随时调整比例积分微分系数或工作模式(P_ON_E/P_ON_M),此时则可调用此函数进行设置,语法如下:
SetTunings(Kp, Ki, Kd);
SetTunings(Kp, Ki, Kd, POn);
6. SetSampleTime()
用以控制PID算法的采样时间,默认采样时间为200ms。输入的参数类型为int类型的毫秒数。在大多数场合下,这一时间足够快了。在Arduino中文社区的翻译帖中,贴出了常见被控参数的采样周期,这里笔者将其贴在这里以供参考:
被控参数 | 被控参数 | 备注 |
流量 | 1-5s | 优先选1-2s |
压力 | 3-10s | 优先选6-8s |
液位 | 6-8s | |
温度 | 15-20s | |
直流电机 | 100ms |
7. SetControllerDirection()
如果输入值高于设定值,输出是否增加或减少? 结合现实的情况,PID的控制可能有不同的选择。 用一辆车,输出应该减小,以减速。 对于冰箱来说,情况恰恰相反。 需要增加输出(冷却)以降低温度。 此函数指定PID连接到哪种类型的进程。 此信息也在PID构建时指定。 由于该过程不太可能从直接转换到反向,所以任何人都不会真正使用此功能。
SetControllerDirection(DIRECT);
SetControllerDirection(REVERSE);
8. 显示函数
GetKp();
GetKi();
GetKd();
GetMode();
GetDirection();
double PID::GetKp(){ return dispKp; }
double PID::GetKi(){ return dispKi;}
double PID::GetKd(){ return dispKd;}
int PID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;}
int PID::GetDirection(){ return controllerDirection;}
二、倒立摆例子概述
1. 倒立摆
由于倒立摆自动控制的数学模型是非线性、多阶、多输入的,是用于测试自动控制算法的理想模型。本代码只用到了简单的pid算法,而且直接调用了arduino的pid库。用Arduino nano的2个外部中断采集编码器的数据,通过中断数量计算摆杆角度。摆杆角度与垂直的差值作为pid算法的输入,经过主控计算后通过引脚pwm控制l289驱动直流减速电机转动。
2. 材料列表
主控arduino nano x1,nano扩展板 x1,pcb x1,pcb电机驱动l298 x1,欧姆龙高精度旋转编码器(2000线)x1, 编码器滤波放大电路板x1,直流减速电机 x1,12v直流电源 x1,碳纤维棒,木板,电源线
3. 代码
#include <PID_v1.h>//导入库函数,实现PID控制器的功能
#define pinA 2 //中断0
#define pinB 3 //中断1
#define INA 8 //电机正反
#define PWMA 9 //电机速度
int PDAJ = 12; //选择模式
long int angle; //角度
unsigned long time = 0; //时间
long count = 0; //计数值
long num = 0;
double Setpoint, Input, Output, setPoint;
double Kp = 0.040, Ki = 0.0005, Kd =0.0011;//Kp = 0.040, Ki = 0.0005, Kd =0.0011;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);//PID库的函数声明
void setup(){
Serial.begin(9600); //窗口初始化
pinMode(pinA, INPUT); //D2脚为输入
pinMode(pinB, INPUT); //D3脚为输入
pinMode(PDAJ, INPUT_PULLUP); 将引脚设置为输入模式,并启用上拉电阻。
attachInterrupt(0, blinkA, FALLING);//注册中断0调用函数blinkA
attachInterrupt(1, blinkB, FALLING);//注册中断1调用函数blinkB
time = micros(); //时间初值
myPID.SetTunings(Kp, Ki, Kd);//调用PID库,设置了PID控制器的参数、输出限制和采样时间
myPID.SetOutputLimits(-255,255);
myPID.SetSampleTime(5);
myPID.SetMode(AUTOMATIC);//调用PID库,将控制器设置为自动模式
}
//主体程序
void loop(){
int bun=digitalRead(PDAJ);//首先读取PDAJ引脚的状态,判断控制模式
if (bun==HIGH){//如果处于控制模式
num = count;
long int angle = num*18;//则根据旋转编码器的计数值计算得到角度,并进行PID控制
Serial.print(anlge);
Serial.print(" ");
while(anlge>36000){//根据角度的范围(0-36000),将角度换算成180度制
anlge=anlge-36000;
}
while(anlge<0){
anlge=anlge+36000;
Serial.println(anlge);
}
Input = angle;//并将其作为PID控制器的输入(Input)
myPID.Compute(); //调用myPID.Compute()函数计算PID控制器的输出(Output)
int spd;
Serial.print("output= ");
SetPoint = 18000;
Serial.print("jd= ");
Serial.print(JD_angle);
Serial.print("pt= ");
Serial.print(PT_angle);
Serial.print("anlge ");
Serial.print(anlge);
if (angle>15000&&angle<21000) {//然后根据输出的值调整电机的速度。具体的速度调整规则根据角度是否在一定范围内来确定
spd = Output;
}
Serial.print(" spd=");
Serial.println(spd);
motor(spd);//最后,通过motor()函数控制电机的转动
}else{//如果处于非控制模式,则按照一定的顺序以不同的速度逐渐调整电机的转动,然后停止一段时间。这部分代码可能是用于调试或其他辅助功能
motor(20); delay(40);
motor(30); delay(40);
motor(50); delay(40);
motor(100); delay(40);
motor(150); delay(40);
motor(0); delay(200);
motor(-20); delay(40);
motor(-30); delay(40);
motor(-50); delay(40);
motor(-100); delay(40);
motor(-150); delay(40);
motor(0); delay(200);
}
}
//中断0调用函数,用于读取旋转编码器的脉冲计数。这里使用了软件去抖动处理,避免干扰导致计数错误
void blinkA(){
if ((micros() - time) > 15) count ++;
time = micros();
}
//中断1调用函数,用于读取旋转编码器的脉冲计数。这里使用了软件去抖动处理,避免干扰导致计数错误
void blinkB(){
if ((micros() - time) > 15) count --;
time = micros();
}
void motor(int sp1){//用于控制电机的转动方向和速度。根据输入速度的正负值,设置电机的正反转,并通过PWM方式控制电机的速度大小
if(sp1>0) digitalWrite(INA, HIGH);
else digitalWrite(INA, LOW);
analogWrite(PWMA, abs(sp1));
}