Arduino实际操作中的PID调节详解
首先说明:是可以用arduino控制带编码器的电机实现速度闭环,可行的,可行的,可行的。在实际应用中我用一块2560控制了4个电机匀速转动,实现麦轮或者全向轮的走直线,最后拿了省一等奖。
前提知识:外部中断引脚可以对高低电平的变化进行监测
而一般的带编码器的电机的编码器引脚输出的也就是高低电平
先看2560上的外部中断引脚有哪些:
(图片摘自网络)
也就可以看到有6个引脚可以使用,分别是2、3、18、19、20、21
而编码器上有两个输出口输出矩形脉冲波,如果控制四个电机那么2560的端口就不够,于是简化一下,不使用正交解码,而是直接读取编码器一个接口的脉冲变化
初始化设置:
#define d_time 10
#define Kp 30
#define Ki 2.5
#define Kd 0 //pid
int pulse1,pulse2,pulse3,pulse4; // 全局变量,用于记录接收到的脉冲数
char direct1,direct2,direct3,direct4;
float error_sum1,error_sum2,error_sum3,error_sum4;
float error_last1,error_last2,error_last3,error_last4;
void setup()
{
pinMode(2,INPUT);pinMode(3,INPUT);
pinMode(18,INPUT);pinMode(19,INPUT);//将引脚设置为输入模式
attachInterrupt(digitalPinToInterrupt(2),READ1,CHANGE); //车轮1编码器电平跳变沿进入中断函数READ1
attachInterrupt(digitalPinToInterrupt(3),READ2,CHANGE); //车轮2进入中断函数READ2
attachInterrupt(digitalPinToInterrupt(18),READ3,CHANGE); //车轮3进入中断函数READ3
attachInterrupt(digitalPinToInterrupt(19),READ4,CHANGE); //车轮4进入中断函数READ4
FlexiTimer2::set(100, control);// 设置定时器2中断每100ms进入一次中断函数control
FlexiTimer2::start(); //使用中断使能
}
每个中断引脚有不同的中断号,digitalPinToInterrupt(pin) :取得引脚pin的中断号
关键语法借用太极创客的文章
关于中断
中断的具体就是让单片机放下正在运行的主函数比如loop中的函数,转去先执行一下中断函数中的代码
所以设置中断函数,名字自己随便取
我这里命名为READ1、2、3、4
中断函数中这样写(中断函数要放在初始化函数之后)
//pulse是全局变量,初始化的时候有设置
void READ1()
{
pulse1++;
}
void READ2()
{
pulse2++;
}
void READ3()
{
pulse3++;
}
void READ4()
{
pulse4++;
}
上面是外部中断函数
下面写定时器中断函数的内容
void control()
{
int PWM1,PWM2,PWM3,PWM4;
PWM1 = pid(pulse1,sudu1,1);PWM2 = pid(pulse2,sudu2,2);PWM3 = pid(pulse3,sudu3,3);PWM4 = pid(pulse4,sudu4,4);
decide1(PWM1);decide2(PWM2);decide3(PWM3);decide4(PWM4);
pulse1 = pulse2 = pulse3 = pulse4 = 0; //计数清零
}
decide函数就是判断一下电机正转还是反转或者是停转
void decide1(int abc)
{
if(direct1 == 'w')//正转
go1(abc);
if(direct1 == 's')
back1(abc); //反转
if(direct1 == 'p')//停转
down1();
}
关于PID
pid的具体含义就不在这里赘述了,这里直接贴代码(有些不规范,将就用)
int pid(int Pulse,float target_speed,int chelun)//位置式PID
{
int pwm;
float now_speed,error_speed;
now_speed=Pulse/(0.748*d_time); //将脉冲转换为速度
error_speed = target_speed-now_speed; //目标速度与当前速度的差距
switch(chelun) //这里其实可以对不同的电机采用不同的参数,这样鲁棒性会更好
{case 1:
error_sum1 += error_speed;
pwm = error_speed*Kp+error_sum1*Ki+(error_speed-error_last1)*Kd;//参数的定义在初始化那里,使用的宏定义
error_last1 = error_speed;
//Serial.println(now_speed);
break;
case 2:
error_sum2 += error_speed; //累计误差,由于是累计误差,所以每次起步之前要先清零,不然直接满转
pwm = error_speed*Kp+error_sum2*Ki+(error_speed-error_last2)*Kd;
error_last2 = error_speed;
//Serial.print("车轮2:");Serial.println(now_speed);
break;
case 3:
error_sum3 += error_speed;
pwm = error_speed*Kp+error_sum3*Ki+(error_speed-error_last3)*Kd;
error_last3 = error_speed;
//Serial.print("车轮3:");Serial.println(now_speed);
break;
case 4:
error_sum4 += error_speed;
pwm = error_speed*Kp+error_sum4*Ki+(error_speed-error_last4)*Kd;
error_last4 = error_speed;
//Serial.print("车轮4:");Serial.println(now_speed);
break;
}
if(pwm>170)pwm = 170; //这里可以丢进上面的switch判断,来仔细的限制车轮的最大输出来防止过冲
if(pwm<0) pwm = 0; //限幅作用
return pwm;
}
水平有限,欢迎指正。转载请注明出处!