目录
文章
前言
一、声明:本项目经验纯属个人的比赛经验,如有雷同纯属巧合。
在这里为什么要过了那么久才写这个博客分享呢!一方面是博主是一个大学生。平常要搞的东西有点多,忙东忙西的没时间对自己的经验进行沉淀积累。另一方面是UP要上学,专业学的东西有点多,且繁杂。没时间去写写小作文来装X。在这里写个比赛经验,其实是想给一些参加工科竞赛新手小白提供一些比赛的方案。你要是觉得还行,就点赞,收藏转发一下吧!
一、21年:越障排爆方案
这是本人第二个项目,这次参赛的的要求是跑完的全程的并且识别抽取的颜色的色卡对应颜色的气球,将其扎破即可。
将任务细分,这里面分为两个步骤,一方面是小车的全程循迹,另一方面是小车的视觉识别。还有就是操控舵机扎破气球
先说一下我负责的部分吧,我主要是负责小车的全程循迹,由于场地是黄背景黑色循迹线,一开始我想到的解决方案是用四路红外检测模块来做循迹识别。
识别的方式呢,就是简单遇到黄色的背景时亮灯,同时给arduino单片机发送高电平信号,黑色则是相反。arduino就会对相应的灯做出一定的校正,使小车回到黑色线的中心,效果呢,也还行,但是这只能用在速度很慢的情况下使用,速度快的话,哪怕是加入中断的情况下也很难校正回来,出现车子飞出去的情况.
然后我开始用另一种传感器—灰度模拟量传感器。
那时候跟大三的师兄交流得知,像这种循线的小车,用上PID算法会大大提高整体的轨迹流畅度,(那时候大二还没学自动控制原理,不知道有PID这种玩法,花了很长的时间才搞明白这个算法的原理。)
这个算法写法其实不难,就一道公式
PID_value=(kp*P+ki*I+kd*D);
P是小车在黑线情况下与现在设备所处环境的误差偏差。I是偏差值的累加值,D是上一个偏差值-目前偏差值所得的值。
kp,ki,kd;是PID各系数值,主要调的参数。具体的值要根据小车的系统的变化需要来设置。
当然你觉得这就完了?这么简单?
没有,当然没有!首先输入的值要稳定比较正常的画,要有一波滤波算法,比如卡尔曼滤波算法、平均敛波算法等,(我只用了平均敛波过滤一些环境突变引起的瞬时数值大幅度跳变,然后又正常的数值,数值就像一个峰一样的跳变。)
其次是速度限幅,由于当时比赛的时候为了保稳定的方案,所以设定了最高速度的速度值,防止速度过快,系统的PID的参数系数值(kp,ki,kd)无法校正回来系统回到稳态。所以速度限幅很有必要。
二、arduino 程序
1.引入库
#include"stdio.h"
#include "SoftwareSerial.h"//软串口头文件声明#include "MsTimer2.h" //2号定时器
2.数据采集及数据处理(包含滤波)
float get_sensor_information_and_PID_count()//该函数用于采集模拟量灰度读取到的数据
{
float sensor[2]={0,0};
int P=0,I=0,D=0;
float error=0,previous_error=0,PID_value=0;
sensor[0]=analogRead(A0);//左灰度
sensor[1]=analogRead(A4); //右灰度
for(i=0;i<4;i++)
{
sensor[0]+= sensor[0];
sensor[1]+=sensor[1];
}
sensor[0]= ((sensor[0])/4);
sensor[1]= ((sensor[1])/4);
error= sensor[0]-sensor[1]; //左减右
return error; //把偏差值传递到需要调用到该函数的函数里
}
3.
PID处理
float count()//pid运算处理函数,得到最终偏差值
{ float P=0,I=0,D=0; //P=error(偏差值),I(偏差累计值),D(最近两次偏差值相减的差值)
P=get_sensor_information_and_PID_count();
float previous_error=0,PID_value=0;//previous_error(上一次偏差值),PID_value(偏差最终值)
if(-30<P&&P<30)//直线判断条件,减少运算
{
P=0;
}
else //正常执行
{
P=P;
}
// Serial.print("P");
// Serial.println(P, DEC);
I=I+P; //累计偏差相加
//Serial.print("I");
//Serial.println(I, DEC);
D=P-previous_error; //最新的偏差减去上一次的偏差
PID_value=(kp*P+ki*I+kd*D); //pid运算公式
previous_error=P; //偏差迭代
// Serial.print("PID_value");
//Serial.println(PID_value, DEC);
return PID_value; //最终偏差值返回
}
4.速度限幅
void limit_speed_and_direction_control(float speed_A,float speed_B)//A左,B右(电机执行函数)
{ float PID_value=0;
PID_value=count();
int left_speed=0;
int right_speed=0;
left_speed=speed_A + PID_value; //左加右减
right_speed=speed_B -PID_value; //左加右减
/**********速度限幅***************/
if(left_speed>SPEED_MAX)
{
left_speed=SPEED_MAX ;
}
else if(left_speed < 0 )
{
left_speed = 0;
}
if(right_speed>SPEED_MAX)
{
right_speed=SPEED_MAX ;
}
else if (right_speed < 0)
{
right_speed = 0;
}
/**********速度限幅***************/
digitalWrite(7,HIGH);
digitalWrite(8,LOW);
digitalWrite(4,HIGH);
digitalWrite(5,LOW);
Serial.print("left_speed");
Serial.println(left_speed, DEC);
Serial.print("right_speed");
Serial.println(right_speed, DEC);
// //analogWrite(5 ,left_speed );
// // analogWrite(6 ,right_speed );
analogWrite(9, left_speed);
analogWrite(10,right_speed );
}
21年博主比赛失利的坑:
1、场地道具因素:由于博主当时的场地比较旧,跟比赛的场地有很大的差别.。越障的窄桥和阶梯有很大的差别(实验室的指导老师太抠了,不肯买。搞得当年所有队伍都寄了)
2、个人原因:21年的时候其实我还是比赛的经验比较浅,在一些单片机的理解上,还是比较浅。发挥的不是很好。然后用aduino跟open mv3的通信协议上做得不是很好,就是,有时能行,有时又不行。
3、车模结构原因:当时的车模是4轮的,除了轮子有点减震外,车子基本上从阶梯下来,就是直接飞下来一样。有时两个灰度基本都不沾线。根本就没机会校正,然后就会寄的那种。这里就不把车子的照片给你们看了。
三、22年的越障排爆方案
新的设计想法
1、模块采用创新
22年是痛定思痛的一年,在一开始的设计想法是准备用多路的模拟量灰度来解决去年只有两路灰度的缺陷,一开始是用6路模拟量灰度的。但是考虑到模拟量灰度的模块对环境影响的过于不稳定,后来就直接抛弃了灰度的方案,然后选了激光检测模块,我整整上了6路
2、STM32F103ZET6核心板。
22年我直接用STM32F103ZET6核心板,然后跟视觉的师弟调了两天。搞定了通信。稳定的一批
3、车模结构上的创新
我为了能够让车子稳定上阶梯和下阶梯,借鉴了六轮月球车的模型,上下坡的时候,完美的适配地形。保证车辆的稳定性,不会有飞出赛道的情况。
这是我校赛第二的成品
4、PCB画板
由于比赛的决赛过程要求要更换为官方的亚克力地板,为了能满足简便拆装,直接用了PCB画板集成了相应的模块。
5、程序创新、
为了避免上下阶梯或者窄桥出现掉落的情况,程序上用了算法去矫正车辆的姿态,避免了在上面掉下赛道的情况,同时用了数字量PID的程序设置将每一个激光对应的状态作为一个校正等级的处理方式。一方面简化了程序的写法,另一方面有有很流畅的运动轨迹。程序上有电机反转调整校正的情况,目的是在亚克力隧道里面能够顺利过大弯的情况.
代码如下(示例):
#include "pid.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
#include "stdio.h"
#include "stdlib.h"
#include "laser.h"
int Last_P=0;
struct _get{
int laserA;
int laserB;
int laserC;
int laserD;
int laserE;
int laserF;
float err;
int PID_val;
int P;
int I;
int D;
int Actul_sent;
//A B C D E 灯的位置
}get;
void laser_init(void)
{
get.laserA=L1;
get.laserB=L2;
get.laserC=ML;
get.laserD=MR;
get.laserE=R2;
get.laserF=R1;
get.err=0;
get.PID_val=0;
get.P=0;
get.I=0;
get.D=0;
get.Actul_sent=0;
}
int laser_PID()
{
int Kp=8;
int Ki=4;
int Kd=5;
int laserA = get.laserA;
int laserB = get.laserB;
int laserC = get.laserC;
int laserD = get.laserD;
int laserE = get.laserE;
int laserF = get.laserF;
laser_init();
if(laserA==1&&laserB==1&&laserC==1&&laserD==1&&laserE==1&&laserF==1)
{get.err=0;}
else if (laserA==0&&laserC==1&&laserF==1) {get.err=2;}
else if (laserA==1&&laserC==1&&laserF==0) {get.err=-2;}
else if(laserC==1&&laserD==0){get.err=-2;}//2
else if(laserD==1&&laserC==0){get.err=2;}
else if(laserA==1&&laserB==1&&laserD==1&&laserF==0){get.err=1;}//8
else if(laserA==1&&laserD==1&&laserF==0){get.err=1;}//8
else if(laserE==1&&laserB==0){get.err=6;}
else if(laserB==1&&laserE==0){get.err=-6;} //6
else if(laserA==1&&laserF==0){get.err=-11;}//8
else if(laserF==1&&laserA==0){get.err=11;}
else if (laserA==0&&laserB==0&&laserC==0&&laserD==0&&laserE==0&&laserF==0)
{get.err=0;}
get.P =get.err;
get.I+=get.err;
get.D =get.P - Last_P;
get.PID_val=Kp*get.P+ Ki*get.I+Kd* get.D;
Last_P=get.P;
get.Actul_sent=get.PID_val*59;
printf("get.err=%f\n",get.err);
printf(" get.Actul_sent=%d\n", get.Actul_sent);
return get.Actul_sent;
}
void run_movePID(void)
{
int err_control;
int pwm1=4500;
int pwm2=4500;
int pwm3;
int pwm4;
int low_case =3800;
int high_case = 4500;
// err_control=laser_PID();
// pwm1=vul_temp+err_control;
// pwm2=vul_temp-err_control;
//
if ((L1==1&&L2==1&&ML==1&&MR==1&&R2==1&&R1==0)
||(L1==0&&L2==1&&ML==1&&MR==1&&R2==1&&R1==1)
||(L1==1&&L2==1&&ML==1&&MR==1&&R2==1&&R1==1))
{
err_control = laser_PID();
pwm1=low_case + err_control;
pwm2=low_case - err_control;
if(pwm1>4100){pwm1 = 4100; pwm3 = 4100;}
if(pwm2>4100){pwm2 = 4100; pwm4 = 4100;}
if(pwm1<0) { pwm1 = 0; pwm3 = 0;}
if(pwm2<0) { pwm2 = 0; pwm4 = 0;}
}
else //快速
{err_control= laser_PID();
pwm1=high_case + err_control;
pwm2=high_case - err_control;
}
if ((pwm1<0)||(pwm2<0))
{if (pwm1<0)
{pwm1=abs(pwm1);
left();
pwm3 =1200;
}
else if(pwm2<0)
{pwm2=abs(pwm2);
right();
pwm4 = 1200;
}
}
else {forward();
pwm3 = pwm1*0.75;
pwm4 = pwm2*0.75;
}
// pwm3 = pwm1;
// pwm4 = pwm2;
//if(pwm1>vul_temp){pwm1=vul_temp;pwm3=vul_temp;}
//if(pwm2>vul_temp){pwm2=vul_temp;pwm4=vul_temp;}
//if(pwm1<0) {pwm1=0;pwm3=0;}
//if(pwm2<0) {pwm2=0;pwm4=0;}
if(pwm1>high_case){pwm1=high_case;pwm3=high_case;}
if(pwm2>high_case){pwm2=high_case;pwm4=high_case;}
if(pwm1<0) {pwm1=0;pwm3=0;}
if(pwm2<0) {pwm2=0;pwm4=0;}
printf("pwm1=%d,pwm2=%d,pwm3=%d,pwm4=%d\n",pwm1,pwm2,pwm3,pwm4);
TIM_SetCompare1(TIM3,pwm1); //左边
TIM_SetCompare2(TIM3,pwm2); //右边
TIM_SetCompare3(TIM3,pwm3); //左边
TIM_SetCompare4(TIM3,pwm4); //右边
}
6、附上22年的岭师赛场的照片
总结
以上就是本人的参赛经验分享,这两方案好处有吗? 有,肯定有. 弊端有吗? 肯定也有.这都看个人的抉择了.但总的来说好处肯定比坏处有多,我个人比较喜欢22年的方案,因为参数好调.感觉有帮助的不妨点赞 收藏. 之后我还会写一篇关于武术格斗擂台车的比赛经验.这都得看博主有没有时间而已.