重要参考:
课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ
讲义链接:Introduction · Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程
8.3.5 电机调速01_PID控制理论
场景:
速度信息可以以m/s为单位,或者也可以转换成转速 r/s,而电机的转速是由PWM脉冲宽度来控制的,如何根据速度信息量化成合适的PWM值呢?
比如:现有一辆行驶中的无人车,要求将车速调整至100KM/h,那么应该如何向电机输出PWM值?或换言之,如何控制油门?
调速实现策略由多种,PID其中较为常用。
PID简介
PID算法是一种经典、简单、高效的动态速度调节方式,P代表比例,I代表积分,D代表微分。
PID公式如下:
-
e(t)作为 PID 控制的输入;
-
u(t)作为 PID 控制器的输出和被控对象的输入;
-
Kp 控制器的比例系数;
-
Ki控制器的积分时间,也称积分系数;
-
Kd控制器的微分时间,也称微分系数。
上述公式稍显晦涩,PID控制原理框架图更有助于理解:
1.P
如果实现上述场景中的车速控制,一种简单的实现方式是:确定目标速度,获取当前速度,使用(目标速度-当前速度)*某一系数
计算结果为输出的PWM,再获取当前速度,使用(目标速度-当前速度)*某一系数
计算结果为输出的PWM并输出...如此循环在上述模型中,调速实现是一个闭环,每一次循环都会根据当前时速与目标时速的差值,再乘以以固定系数,计算出需要输出的PWM值,这其中的系数,称之为比例。
2.I
上述模型算法中,最终速度与预期速度存在稳态误差,这意味着最终结果可能永远无法达成预期,解决的方法就是使用积分I。每次调速时,输出的PWM还要累加根据积分I计算的结果,以消除静态误差。
3.D
当I值设置的过大时,可能会出先"超速"的情况,超速之后可能需要多次调整,产生系统震荡,解决这种情况可以使用D微分,当速度越是接近目标速度时,D就会越施加反方向力,减弱P的控制,起到类似”阻尼“的作用。通过D的使用可以减小系统震荡。
综上,PID闭环控制实现是结合了比例、积分和微分的一种控制机制,通过P可以以比例的方式计算输出,通过I可以消除稳态误差,通过D可以减小系统震荡,三者相结合,最终是要快速、精准且稳定的达成预期结果,而要实现该结果,还需要对这三个数值反复测试、调整...下一节将介绍在 Arduino 中PID控制的具体实现,其中就包括PID库的调用,以及PID调试的具体方式。
8.3.6 电机调速02_PID控制实现
了解了PID原理以及计算公式之后,我们可以在程序中自实现PID相关算法实现,不过,在Arduino中该算法已经被封装了,直接整合调用即可,从而提高程序的安全性与开发效率。该库是:Arduino-PID-Library,接下来通过一个案例演示该库的使用。
需求:通过PID控制电机转速,预期转速为 80r/m。
实现流程:
- 添加Arduino-PID-Library;
- 编写Arduino程序直接调用相关实现;
- 使用串口绘图器调试PID值。
1.添加Arduino-PID-Library
首先在 GitHub 下载 PID 库: git clone GitHub - br3ttb/Arduino-PID-Library
然后将该文件夹移动到 arduino 的 libraries下: sudo cp -r Arduino-PID-Library /home/用户xxx/Arduino/libraries
还要重命名文件夹: sudo mv Arduino-PID-Library ArduinoPIDLibrary
最后重启 ArduinoIDE
2.编码
PID调速中,测速是实现闭环的关键实现,所以需要复制之前的电机控制代码以及测速代码。
完整代码实现:
/*
* PID 调速实现:
* 1.代码准备,复制并修改电机控制以及测速代码
* 2.包含PID头文件
* 3.创建PID对象
* 4.在setup中启用自动调试
* 5.调试并更新PWM
*
*/
#include <PID_v1.h>
int DIRA = 4;
int PWMA = 5;
int motor_A = 21;//中端口是2
int motor_B = 20;//中断口是3
volatile int count = 0;//如果是正转,那么每计数一次自增1,如果是反转,那么每计数一次自减1
void count_A(){
//单频计数实现
//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比
/*if(digitalRead(motor_A) == HIGH){
if(digitalRead(motor_B) == LOW){//A 高 B 低
count++;
} else {//A 高 B 高
count--;
}
}*/
//2倍频计数实现
//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 2
if(digitalRead(motor_A) == HIGH){
if(digitalRead(motor_B) == HIGH){//A 高 B 高
count++;
} else {//A 高 B 低
count--;
}
} else {
if(digitalRead(motor_B) == LOW){//A 低 B 低
count++;
} else {//A 低 B 高
count--;
}
}
}
//与A实现类似
//4倍频计数实现
//手动旋转电机一圈,输出结果为 一圈脉冲数 * 减速比 * 4
void count_B(){
if(digitalRead(motor_B) == HIGH){
if(digitalRead(motor_A) == LOW){//B 高 A 低
count++;
} else {//B 高 A 高
count--;
}
} else {
if(digitalRead(motor_A) == HIGH){//B 低 A 高
count++;
} else {//B 低 A 低
count--;
}
}
}
int reducation = 90;//减速比,根据电机参数设置,比如 15 | 30 | 60
int pulse = 11; //编码器旋转一圈产生的脉冲数该值需要参考商家电机参数
int per_round = pulse * reducation * 4;//车轮旋转一圈产生的脉冲数
long start_time = millis();//一个计算周期的开始时刻,初始值为 millis();
long interval_time = 50;//一个计算周期 50ms
double current_vel;
//获取当前转速的函数
void get_current_vel(){
long right_now = millis();
long past_time = right_now - start_time;//计算逝去的时间
if(past_time >= interval_time){//如果逝去时间大于等于一个计算周期
//1.禁止中断
noInterrupts();
//2.计算转速 转速单位可以是秒,也可以是分钟... 自定义即可
current_vel = (double)count / per_round / past_time * 1000 * 60;
//3.重置计数器
count = 0;
//4.重置开始时间
start_time = right_now;
//5.重启中断
interrupts();
Serial.println(current_vel);
}
}
//-------------------------------------PID-------------------------------------------
//创建 PID 对象
//1.当前转速 2.计算输出的pwm 3.目标转速 4.kp 5.ki 6.kd 7.当输入与目标值出现偏差时,向哪个方向控制
double pwm;//电机驱动的PWM值
double target = 80;
double kp=1.5, ki=3.0, kd=0.1;
PID pid(¤t_vel,&pwm,&target,kp,ki,kd,DIRECT);
//速度更新函数
void update_vel(){
//获取当前速度
get_current_vel();
pid.Compute();//计算需要输出的PWM
digitalWrite(DIRA,HIGH);
analogWrite(PWMA,pwm);
}
void setup() {
Serial.begin(57600);//设置波特率
pinMode(18,INPUT);
pinMode(19,INPUT);
//两个电机驱动引脚都设置为 OUTPUT
pinMode(DIRA,OUTPUT);
pinMode(PWMA,OUTPUT);
attachInterrupt(2,count_A,CHANGE);//当电平发生改变时触发中断函数
//四倍频统计需要为B相也添加中断
attachInterrupt(3,count_B,CHANGE);
pid.SetMode(AUTOMATIC);
}
void loop() {
delay(10);
update_vel();
}
核心代码解释:
1.包含PID头文件
#include <PID_v1.h>
2.创建PID对象
//创建 PID 对象
//1.当前转速 2.计算输出的pwm 3.目标转速 4.kp 5.ki 6.kd 7.当输入与目标值出现偏差时,向哪个方向控制
double pwm;//电机驱动的PWM值
double target = 120;
double kp=1.5, ki=3.0, kd=0.1;
PID pid(¤t_vel,&pwm,&target,kp,ki,kd,DIRECT);
3.setup中启用PID自动控制
pid.SetMode(AUTOMATIC);
4.计算输出值
pid.Compute();
4.调试
PID控制的最终预期结果,是要快速、精准、稳定的达成预期结果,P主要用于控制响应速度,I主要用于控制精度,D主要用于减小震荡增强系统稳定性,三者的取值是需要反复调试的,调试过程中需要查看系统的响应曲线,根据响应曲线以确定合适的PID值。
在 Arduino 中响应曲线的查看可以借助于 Serial.println() 将结果输出,然后再选择菜单栏的工具下串口绘图器以图形化的方式显示响应结果:
PID调试技巧:
参数整定找最佳,从小到大顺序查
先是比例后积分,最后再专把微分加
曲线振属荡很频繁,比例度盘要放大
曲线漂浮绕大湾,比例度盘往小扳
曲线偏离回复慢,积分时间往下降
曲线波动周期长,积分时间再加长
曲线振荡频率快,先把微分降下来
动差大来波动慢。微分时间应加长
理想曲线两个波,前高后低4比1
一看二调多分析,调节质量不会低