本帖最后由 wzzzq 于 2020-10-6 19:55 编辑
一 认识pid
首先要了解PID算法是什么。网上很多帖子要么就是只有理论,要么就没讲清楚,这次我们来通过一个实例来学习pid。
https://blog.csdn.net/qq_36554582/article/details/82721453
https://blog.csdn.net/qq_25352981/article/details/81007075
可以先看看这两篇文章了解了pid的基本原理,社区里也有一些好文,先理解一下概念。
二 调试,安装编码器
编码器有读取电机转速的作用,是组成pid控制的必要部分,编码器上有6个口,如图接线读取转速的主流方法有两种:pulsein读取脉冲的时间,或为外部中断读取单位时间触发霍尔传感器的次数.
两种方法各有优劣:
pulsein的原理是计时,把它的源码扒出来看后,我发现pulsein需要延时来达到测量目的,会阻塞程序运行.而且时间是一个标量,无法测量转向.
但pulsein也有优点,它代码简单,不需要中断,通常中断会带来各种神奇的问题.
而外部中断的优缺点都十分突出.外部中断不会阻塞程序运行,而且可以通过比较AB相触发的相位差来测量转向.
其缺点上文也提到了,会影响计时器,从而导致pulsein精度降低很多,进一步导致传统的PWM接收机信号不能被正确读取.
那么接下来就讲讲两种读取的方法
1.pulsein计时读取
可以看到有AB两相,输出的占空比可直接用arduino中的pulseIn读取。其实我们用一相就够了,因为正转还是反转我们不关心,而且我们也读取不到,我们只要知道转速。
这里注意转速越慢,值越大,因为相同路程,速度越大,时间越少,而pulseIn输出的是脉冲的长度,单位微秒。
先写一段测试代码,让电机转起来,然后测转速
这段代码只接了A相
[mw_shl_code=arduino,true]void setup() {
// put your setup code here, to run once:
pinMode(22,INPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
analogWrite(3,255);
digitalWrite(4,0);
int a=pulseIn(22,HIGH);
Serial.println(a);
delay(1000);
}[/mw_shl_code]
这是串口监视器读到A相的结果,268,264,254,263都是pwm输出满量程时的读数,看来这个电机不是很稳定,这也是引入pid的原因之一。
我发现这个量程不是很合适,于是映射到0~50。
那么就有朋友好奇B相输出是什么呢
其实跟A相差不多,他们差了一相
两相的作用是用来测正反的,我也不需要。
这是正反转pwm从50-255电机测速器输出的值
我们可以看到值是反的
2.外部中断
该方法需要四个外部中断的接口(对于N个需要调速的电机,就需要N个端口),这里就把很多控制板给刷掉了,一般常见的UNO,NANO均不符合此条件.而拥有6个外部中断的MEGA就可以使用.
首先讲讲原理.外部中断的本质是在loop运行时如果其条件被触发(如某个端口的电平变化),就会跳出主程序去运行外部中断指定的函数.这里如果检测到A相电平变化了,说明磁环上的一个磁极经过了霍尔传感器,让计数器++,然后我们有了总共的脉冲数,但我们要的是速度,也就是单位时间的脉冲数,于是我们就有了这样的思路:用一个t变量,t=系统时间,每过了30ms将in1赋值给M1S(速度变量),并将in1清零,用于下一个30ms的读取.
三 程序实现
这里以pulsein为例,外部中断的代码见
arduino pid麦克纳姆轮小车程序详解https://www.arduino.cn/forum.php ... &fromuid=176749
(出处: Arduino中文社区)
-----------------------正文----------------------------
[mw_shl_code=arduino,true]map(in1,620,210,0,50);[/mw_shl_code]
这句话实现了将转速并映射到0-50.
[mw_shl_code=arduino,true]in1=pulseIn(ma_in,HIGH,2000);[/mw_shl_code]
这句话读取了转速
然后看看pid核心代码(没d)
[mw_shl_code=arduino,true]float last1;
void M1PWM(int ref,int real)
{
float error=0,PWMINC=0;
error=ref-real;
PWMINC=Kp*(error-last1);
M1PWMOUT+=(PWMINC+Ki*error);
if(M1PWMOUT<50)
M1PWMOUT=50;
if(M1PWMOUT>255)
M1PWMOUT=255;
last1=error;
}[/mw_shl_code]
其实pid的核心代码很短,也不难理解,主要是调用很麻烦
调用代码如下
[mw_shl_code=arduino,true] int M1S,M2S,M3S,M4S,ref,in1,in2,in3,in4;
ref=max(map(a,sa,2007,0,50),15);
in1=pulseIn(ma_in,HIGH,2000);
in2=pulseIn(mb_in,HIGH,2000);
in3=pulseIn(mc_in,HIGH,2000);
in4=pulseIn(md_in,HIGH,2000);
if(in1==0)
M1S=0;
else
M1S=map(in1,620,210,0,50);
if(in2==0)
M2S=0;
else
M2S=map(in2,620,210,0,50);
if(in3==0)
M3S=0;
else
M3S=map(in3,620,210,0,50);
if(in4==0)
M4S=0;
else
M4S=map(in4,620,210,0,50);
M1PWM(ref,M1S);
M2PWM(ref,M2S);
M3PWM(ref,M3S);
M4PWM(ref,M4S);
forward();
printf("参考转速:%d ",ref);
printf("前进,M1转速:%d ",M1S);
printf("前进,M2转速:%d ",M2S);
printf("前进,M3转速:%d ",M3S);
printf("前进,M4转速:%d\n",M4S);[/mw_shl_code]
这是前进的代码,也就是4个轮都正转。
其他都类似,见总代码
四 调试
实测0.几秒就能稳定,完全足够了
图是我不断变速的曲线,不是一个速度用了这么久
如果你觉的稳定的慢了,就把Kp弄大,反之亦然。当然kp太大了就会很飘。
调好kp后调ki,直到稳差较小。
--------------------------------------------------------------------------------------------------
总代码
--------------------------------------------------------------------------------------------------
最后还有个问题就是一开始四个电机的转速不一样,导致一开始会有一些角度偏差,希望有大佬能帮忙解决这个问题,谢谢。
如果有问题或建议欢迎回复
当然arduino自带了PID库,也 十分好用,程序详解中使用的就是自带的库.
|