随着省赛的落幕,近一年的智能车的生涯也就此画上了句号,虽然结果没有那么耀眼,但也算对得起三个大一萌新这么久以来不分昼夜所付出的心血。
在此我想对电磁车中的算法和赛场上一些的状况作进一步总结,也算是给自己一个交代。
我们知道,电感的基本排布分为水平,竖直和八字几种方案,而电感摆放位置的不同,可能会影响车体循迹的姿态。
通过实测我们发现:水平电感有助于直道循迹,八字电感,则有赛车助于过弯,竖直电感在普通赛道上值几乎很小,只有在某些特殊元素(如环岛)才会突增。
我们采用的是水平和八字电感共同循迹的方式,即在左右两端分别放上八字和水平电感,在电磁杆的中间和靠近赛车的中心位置各放置一个水平电感,用于特殊元素判断。
另外,为了更精确的采集赛道电磁值以至于不失真,需将电磁前瞻的高度稍微降低。
基本的舵机控制算法在上篇文章中已经提过,这里对其部分优化方案进行说明。
而在PD参数中,P参数控制响应速度,D参数消除稳差,那么可以想到,如果全局使用同一个PD参数,则会出现无法控制既在直道上平稳运行,又能很好的过各种弯道的局面。这时候就需要我们对PD参数进行分段控制。
而由于电磁车只能依靠电感传回的电磁信号判断赛道路况,所以可以将左右两边的电感值差作为分段控制的依据。
具体代码如下:
/* * * 分段PD * * */
if(backup<=15) angle_P=60;
else angle_P=95;
其中backup即为左右电感求得的偏差值,backup的值可以根据具体赛道及电感情况进行缩放处理。
上述的分段PD的办法是对其参数进行直接赋值,这种方法虽然可行,却不能达到很好的连续性效果,对此可以想到一种函数曲线的方法,将P的值关联backup,利用函数曲线对P值进行输出,从而更好的控制赛车在各个赛道元素上行驶的状态。
以下提供两种方案:
/* * * 舵机算法1 * * */
if(backup<=15) angle_P=63+backup*2;
if(15<backup<22) angle_P=33+backup*4;
if(22<backup<=30) angle_P=backup*5.5;
int k;
angle_P=22+(int)(n*n*k);
上篇文章的末尾提到了目标速度的确定,由于不同路况赛车的最高速度不同,需要对不同路况分别设置目标速度,具体代码如下:
set_speed_L=90+(int)((30-backup)*1.2); //弯道最低+直道加速
利用舵机控制赛车转向的同时,我们也可以利用差速对其进行辅助。同样根据backup的值对赛道路况进行判断,分别控制对赛车的两个轮子进行控制,达到差速的效果。程序设计上可以将上述set_speed_L计算公式中的90改为动态的bend_L/bend_R,再在具体bend_L/bend_R的计算中控制差速。
具体代码如下:
bend_L=(int)(90*cs_L);
bend_R=(int)(90*cs_R);
set_speed_L=bend_L+(int)((30-backup)*1.2);
set_speed_R=bend_R+(int)((30-backup)*1.2);
其中cs_L/cs_R为差速控制系数。
由于环岛的特殊性,可以选择在识别到环岛的同时令目标速度降低,增加其稳定性。识别到环岛后可以采用强制打角或切换曾八字电感循迹的方式进环,确定进环后再切换成普通元素循迹方式,出环前再判断一次标志位,采用编码器计步的方式强制出环,一段时间后清楚标志位。
在调试过程中经常会看到有赛车在坡道的位置突然转向,很多情况就是因为在坡道时误判成了环岛,这就需要我们对环条件加以限制,来避免此类问题的发生。
此时加在前瞻以及车头位置的中间电感便发挥了作用,一般来说,在坡道的位置,这两个电感的值不会与两侧水平电感同时增大,所以这也就是我们解决坡道误判问题的方法之一。
部分代码如下:
/* * * 环岛处理 * * */
if(huan_dw==1) //拨码开关2向下 执行环岛程序
{
if((AD_M_Left[0]>90&&AD_M_Left[1]>90&&AD_M_Right[1]>=65&&AD_M_Right[1]<=70&&AD_M_Right[0]>=65&&AD_M_Right[0]<=70) //环标志
||(AD_M_Right[0]>90&&AD_M_Right[1]>90&&AD_M_Left[1]>=65&&AD_M_Left[1]<=70&&AD_M_Left[0]>=65&&AD_M_Left[0]<70))
//||(AD_M_Right[0]>=99&&AD_M_Right[1]>=99&&AD_M_hou<=25&&AD_M_hou>=13))
{
if(huanw_a==1)
{
huan_ru=1;
set_speed_L=80;
set_speed_R=80;
huanw_a=0;
LED_ON(3);
}
}
if(huan_ru==1)
{
if((int)(AD_M_hou-AD_M_qian)==-2||(int)(AD_M_hou-AD_M_qian)==-1)
{
huan_ru=2;
set_speed_L=80;
set_speed_R=80;
force_angle=0;
}
}
}
到此,环岛识别结束,由于不同路况以及赛车本身的不同,环岛标志位都不同,以上标志位仅供参考。另外,出环结束后一定不要忘记清标志位,否则无法二次进环。
if(force>huanchu_over)
{
LED_OFF(2);
LED_OFF(3);
LED_OFF(4);
LED_OFF(5);
huan_ru=0;
huan_zhong=0;
huan_chu=0;
huanw_a=1;
force=0;
}
由于比赛现场调试时间短暂,如果用IAR里的live watch观察数据不太方便,所以我们选择将需要观察的数据显示在OLED上,尤其是由于信号发生器的不同而造成的电磁值误差,若将其获取的值打印到OLED上,则方便现场调试。
具体代码如下:
// 电感值输出在OLED //
void AD_oled()
{
char txt[30];
OLED_Print(15,0,"[满天星]智能车"); //显示队伍名称
sprintf((char*)txt,"L1 :%4d",AD_M_Left[0]);
OLED_P6x8Str(0,2,(u8*)txt);
sprintf((char*)txt,"L2 :%4d",AD_M_Left[1]);
OLED_P6x8Str(0,3,(u8*)txt);
sprintf((char*)txt,"R2 :%4d",AD_M_Right[1]);
OLED_P6x8Str(80,3,(u8*)txt);
sprintf((char*)txt,"R1 :%4d",AD_M_Right[0]);
OLED_P6x8Str(80,2,(u8*)txt);
sprintf((char*)txt,"QZ :%4d",(int)(AD_M_qian));
OLED_P6x8Str(0,4,(u8*)txt);
sprintf((char*)txt,"HZ :%4d",(int)(AD_M_hou));
OLED_P6x8Str(80,4,(u8*)txt);
sprintf((char*)txt,"P :%4d",angle_P);
OLED_P6x8Str(0,6,(u8*)txt);
sprintf((char*)txt,"b :%4d",(int)backup);
OLED_P6x8Str(80,6,(u8*)txt);
delayms(100);
}
两者原理基本相同,都是先读取接口状态(如电平的高低),再根据此状态对具体程序进行控制,比赛中我用了两个拨码开关,分别控制高低速和环岛屏蔽与否,并在发车时利用按键选择出入库方向,最大可能的为正式比赛提供便利。
具体代码如下:
// 车档位选择 //
void Select_car() //向上为0,向下为1
{
V_dw=GPIO_PinRead(PTC8); //速度档,向上为低速,向下为高速
huan_dw=GPIO_PinRead(PTC9); //环岛档,向上屏蔽环岛程序
char txt[30];
sprintf((char*)txt,"start_direction :%4d",start_direction_dw); //出库方向:
OLED_P6x8Str(0,2,(u8*)txt); //1为左出库 左按键触发 2为右出库 右按键触发
while(start_direction_dw==0)
{
switch(KEY_Read(0))
{
case 1:
break;
case 2:
start_direction_dw=1;
break;
case 3:
start_direction_dw=2;
break;
default:
break;
}
}
sprintf((char*)txt,"start_direction :%4d",start_direction_dw);
OLED_P6x8Str(0,2,(u8*)txt);
delayms(2000);
}
十五届到这里就结束了,怀念的同时,也感谢那些日子为梦想而星夜兼程的自己。我们行于黑暗,奉于光明。希望每位智能车人,都能摘到属于自己的那颗星星。
如有疑问或错误,欢迎和我私信交流指正。
W.By Xyq