赞
踩
网上的文章翻了一遍,都没有讲智能小车如何做到快速循迹,而且又稳的,是不是代码高保密,只传亲学弟。
有讲PID算法循迹的,要么运用到自己小车身上就不行,要么就是文章中没有效果展示,代码收费,导致不敢买。你的循迹小车是不是做不到速度快了小车还能稳稳循迹的效果,出线、左右摆的问题一直困扰你?网上人家智能车竞速的视频深深吸引着你,你在思考这小车怎么做到速度这么快,而且又稳的,此篇就帮你解决这个问题。话不多说,先看视频效果。
怎么样?稳吧,(如果还没你的好此时你应该退出这篇文章),下面正文开始。
如果你知道小车是如何循迹的可直接跳过。小车一般采用灰度、或者红外模块来判断小车在循迹线的位置,
我用到的是第二种,可以看到总共8个灯(8路),从左到右我们依次记为灯1 - 灯8,小车在线的中间位置时灯4和灯5就会亮起,所对应的信号输出口就会返回0,如果小车往右偏一点,那么灯3和灯4所对应的信号输出口就为0,小车就可以据此来做出判断。快速循迹一般采用12路循迹模块,最少8路,我使用的是12路,因为路数越多,小车的微动变化主控也能判断。
此篇主讲主控得到信号后怎样处理。你不会还在用if elseif 吧,太LOW了!以STM32标准库为例,有基础的想必都知道如果要读取C1口的高低电平的函数是
GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1);
一般做法是将它读取出来赋值给一个变量,像这样
int H1 = GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_1);
依次读取8个引脚,分别赋值给H1-H8,然后再对变量H1-H8进行if elseif 判断。每次写 if elseif 写一大堆,还不好看,今天我用另外一种处理方法来代替它,代码简洁明了。
我们知道假如第一个灯亮起,那么8个灯所返回的值依次是01111111,可以看做它一个二进制数,所对相应的就是127,此时我们用 switch语句来判断不就好了,关键在于怎样将8个数合并成一个数呢,直接上代码
- int read(void)
- {
- int val;
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT1) << 7);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT2) << 6);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT3) << 5);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT4) << 4);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT5) << 3);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT6) << 2);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT7) << 1);
- val |= (GPIO_ReadInputDataBit(GPIOC, OUT8) << 0);
- return val;
- }
这样不就全部保存在val这个变量里了嘛,下面就是用switch语句来对这个变量判断,上代码
- read();
- switch (read())
- {
- case 127: break; //0111 1111
- case 191: break; //1011 1111
- case 223: break; //1101 1111
- case 239: break; //1110 1111
- case 247: break; //1111 0111
- case 251: break; //1111 1011
- case 253: break; //1111 1101
- case 254: break; //1111 1110
-
- case 0: break; //0000 0000
- case 255: break; //1111 1111
- default: break;
- }
两个灯亮和三个灯亮的情况一样,这里就不给你了,自己动动小手算算一算吧。这不就完美解决了if elseif 写一堆的问题了嘛。
加12758 来了来了,它终于来了! 不过此篇还是单级PID,耐心看完,你会有收获的,还不知道什么叫做PID的小伙伴请去翻阅资料,我在此不做解释了,懂PID的小伙伴此时是不是不知道PID怎样运用在循迹小车上呢,我来给你解惑。PID最重要的是得有误差反馈,误差从何而来,就是你给它,什么意思呢,直接看代码
- float error;
- read();
- switch (read())
- {
- case 127: error = -3; break; //0111 1111
- case 191: error = -1; break; //1011 1111
- case 223: error = -0.5; break; //1101 1111
- case 239: error = 0; break; //1110 1111
- case 247: error = 0; break; //1111 0111
- case 251: error = 0.5; break; //1111 1011
- case 253: error = 1; break; //1111 1101
- case 254: error = 3; break; //1111 1110
-
- case 0: error = 0; break; //0000 0000
- case 255: error = 0; break; //1111 1111
- default: error = 0; break;
- }
这样误差不就有了嘛,小车往右偏,反馈误差为负,偏的越很,误差越大,小车在中间误差为0,
误差有了,那么下面就是对误差的PID算法处理,我们知道PID算法常见的有位置PID和增量式PID,很明显咱们要用位置PID,此处我只用P和D,上代码
- P = error; //P项
- D = error - previous_error; //D项(当前误差减上次误差)
- PID_value = (Kp * P)+ (Kd * D); //核心公式
- previous_error = error; //将当前误差保存
-
- X = Pwm +PID_value; //最终计算出左右轮要输出的PWM
- Y = Pwm -PID_value;
-
- if(X>=100) { X=100;}else if(X<-100){X=-100;} //限幅
- if(Y>=100) { Y=100;}else if(Y<-100){Y=-100;}
-
- motor(X,Y); //电机执行
previous_error,PID_value,X,Y,Pwm均已定义,motor函数为电机执行函数,如果motor函数传入的是两个负数,电机后转,一边正一边负就会左转或右转。限幅100是因为我给你PWM满量程为100;Pwm这个变量是你想让小车达到速度,想让小车以50的速度循迹就给50,想让小车以100的速度循迹就给100。在调参时先调KP,再调KD,具体调参过程不再细讲,请搜索相关文章,以我的经验来说KD要比KP大的多。
至此,单级PID循迹已经完成,但是!效果并不是太好,面对低速还可以,但是面对高速状态下仍然是左右震荡、出线!下面的串级PID完美解决了该问题。
52972所谓串级PID,用大白话来说就是两个PID串一块。具体原理请搜索相关文章,在此只说另外一个PID是什么,另外一个PID是加上了陀螺仪,我用的是最便宜的mpu6050陀螺仪,十几块钱,对它哪个值进行PID运算呢?Z轴角速度值!关于陀螺仪角速度、姿态角之类的不懂的请搜索相关文章,怎样串,先谁后谁,具体代码是什么,请有偿联系,创作不易,谢谢。(代码基于STM32F103ZET6,标准库)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。