做智能车的过程中,如果有一天我把车做出来了,并且做的不错,我会把代码以及思路贡献出来,让以后做车的同学们少走些弯路,我们是省赛一等奖,所以看看还是有些意义的。
我是做光电的,不知道其他智能车是怎么写的,但根本都差不多。光电车控制简单分为3部分:寻迹,方向控制,电机控制。
首先我是基于lpld库写的程序,如果没看懂的话可以稍微了解下这个库
寻迹:首先进行ccd采集,有人搞半天都不知道曝光时间是什么东西。你可以大致的认为曝光改变ccd电压作用的,你可以先用静态曝光试试看,如果算法写的好的话,再用动态曝光。我们没用曝光程序,有人觉得不可思议,其实是可以的,我们用的是固定6ms采集。
void ImageCapture(uint8 * ImageData) //图像采集128个点
{
unsigned char i;
CCD_SI1; // SI = 1
delay200ns();
CCD_CLK1; // CLK = 1
delay200ns();
CCD_SI0; // SI = 0
delay200ns();
//delay 20us for the first pixel
delay10us();
ImageData[0]=LPLD_ADC_Get(ADC0,AD8);
CCD_CLK0; // CLK = 0
for(i=1;i<128;i++)
{
delay200ns();
delay200ns();
CCD_CLK1; //CLK=1
delay200ns();
delay200ns();
ImageData=LPLD_ADC_Get(ADC0,AD8);
CCD_CLK0; //CLK=0
}
delay200ns();
delay200ns();
CCD_CLK1; // CLK = 1
delay200ns();
delay200ns();
CCD_CLK0; // CLK = 0
}
采集好之后就可以进行区分黑白底了,区分黑白底方法主要分为两种:二值化法,边沿法。我相信大部分都用的二值化法,因为边沿法对采集回来的图像和ccd本身性能要求比较高,我也写过,不太好写。我们用的就是二值化方法,二值化主要就是确定一个阈值,确定阈值的方法也有很多种 ,我首先举一些我听过的算法,一种是车子在开机的时候采集赛道信息,分出黑点的灰度blackdc,以及白点的灰度whitedc,如果跑的过程中出现灰度在blackdc+20以下的默认为低,出现灰度在whitedc-20以上的默认为高,这是一种方法。
第二种方法,比较常用求平均值,怎么说呢,在不丢线的情况下,这个方法是可行的,丢线时要作特殊处理。第三种方法,就是静态阈值,就是自己在跑之前,通过上位机看一下图像,人为的输入一个阈值,这种方法有个弊端吧,就是每次换个新环境的时候,你都要担心你的阈值准不准确。我们用的是静态的阈值。
有了阈值就可以找出左右线了。
//==============随后即是提取黑线,代码如下:=====================
/*车位置越大说明靠左,ccd看到右边的还是右边的,左边还是左边的
直道时如果车位置较小,说明在车子靠右
弯道时,比如左转弯,他只看到右边的线,车位置也很小。
*/
void Get_Flag(void) //我感觉这里应该舍弃两边的几个点
{
shiziwan=0;
fandiuxianflag=0;//防丢线标志位
int16 left_num,right_num;
left_flag=0;
right_flag=0;
erzhihua();//二值化,里面包含了求动态阀值
Filter_Pixel_Two();//滤波
Filter_Pixel_Three();//滤波
if(left_point>100)
{
right_flag=0;
for(left_num=midlinexian;left_num>90;left_num--) //取得最左边的点,舍弃第一个坏点, 从上次车的中点开始向左边寻找舍弃了10个点
{
if(pixel[left_num]-pixel[left_num-1]==200)
{
left_point=left_num;
left_flag=1;
break;
}
}
fandiuxianflag=1;
}
if(right_point<28)
{
left_flag=0;
for(right_num=midlinexian+1;right_num<38;right_num++) //取得最右边的点舍弃了10个点 右边死区110
{
if(pixel[right_num]-pixel[right_num+1]==200)
{
right_point=right_num;
right_flag=1;
break;
}
}
fandiuxianflag=2;
}
if((fandiuxianflag!=1)&&(fandiuxianflag!=2))
{
for(left_num=midlinexian;left_num>11;left_num--) //取得最左边的点,舍弃第一个坏点, 从上次车的中点开始向左边寻找舍弃了10个点
{
if(pixel[left_num]-pixel[left_num-1]==200)
{
left_point=left_num;
left_flag=1;
break;
}
}
for(right_num=midlinexian+1;right_num<114;right_num++) //取得最右边的点舍弃了10个点 右边死区110
{
if(pixel[right_num]-pixel[right_num+1]==200)
{
right_point=right_num;
right_flag=1;
break;
}
}
}
}
有的时候在转弯的时候会出现串道的情况,就是跑到旁边的赛道了,这个时候要做特殊处理,上面有写。就是让他只检测一定范围的区域就行了。
寻迹到这就讲完了,重点就是上面这个程序。
接下来就是方向控制
对于方向控制基本上就两种方法:一个就是连续控制,一个就是分段控制。
这两种方法,我比较偏向于第一个,第一种跑起来更平滑。在直道的时候给个固定p值,弯道的时候给个二次函数的p值,算法如下:
if(a<14)
{
K_Direction_P=19; //14
K_Direction_D=100;
}
else if(a<26)
{
K_Direction_P=0.018*a*a+1.226*a; //a>16,P=0.048*a*a+0.037
K_Direction_D=100;
}
else
{
K_Direction_P=42; //a>16,P=0.048*a*a+0.037*a;
K_Direction_D=100;
}
这里的a是当前位置到中线的差值。
d值一般给大点就行了,有消抖的左右。
然后就是控制舵机打脚了,这个比较简单,
mid_err2=mid_err1; //上次偏差
mid_err1=midline-64; //车位置与中点的差
mid_err_err=mid_err1-mid_err2;
DirectionOutnew=(uint32)(angle_value+(K_Direction_P*mid_err1 +K_Direction_D*mid_err_err));//angle_value是指0度的时候对应的占空比angle_value=750
DirectionOutold=DirectionOutnew;
if(DirectionOutnew>rightlimit) //rightlimit=639向右打弯
{
LPLD_FTM_PWM_ChangeDuty(FTM0,FTM_Ch0,rightlimit);
}
else if(DirectionOutnew {
LPLD_FTM_PWM_ChangeDuty(FTM0,FTM_Ch0,leftlimit);
}
else
{
LPLD_FTM_PWM_ChangeDuty(FTM0,FTM_Ch0,DirectionOutnew);
}
就是中间值+pid值就行了,关于pid算法,什么增量式,什么是位置式,这没必要纠结,能用就行。用c编写程序的时候要经常用到变量的强制转换。还有就是定义变量的时候一定要注意变量大小。有时出错了,找一晚上都找不出来,最后才发现是变量定义的有问题。
最后就是最重要的速度控制了。速度控制是提速的保证,你的车想跑多快,不仅仅是方向控制写的好,到了后期大家拼的就是速度控制了。关于速度控制主要有三种方法:pid,bang-bang,pid+bang-bang;
那哪种方法好呢,只能说,哪种你的车跑的快,哪种就好。bang-bang算法具有很强的加速能力和减速能力,有个弊端就是容易飘逸。如果控制的好,我感觉是非常nb的。pid算法速度具有很强的连续性,加减速看的不明显,直道与弯道速度不能差太大,跑起来四平八稳,过弯道能力强。我们用的就是pid算法。pid+bangbang算法被认为是最理想的,本人也写过,一直到校赛我一直都用pid+bangbang ,效果其实也还行。但是最后不知道怎么还是钟情于pid了,呵呵。
接下来就是速度控制,
//===============速度控制函数==================
void speed_control(void)
{
float a,b;
a=abs(mid_err1);
if(stopflag==1) //stopflag是起跑线标志
{
speed_mid=0;
}
else
{
if(a<6)
{
speed_mid=zhixianspeed_mid;
}
else if(a<20)
{
speed_mid=ruwanspeed_mid1;
}
else
{
speed_mid=ruwanspeed_mid1-10;
}
}
/* if(stop==1)
{
speed_mid=0;
fanzhuanduty=1000;
if(ch0_pulseacc<3)
{
fanzhuanduty=0;
}
}*/
speed_pluse=ch0_pulseacc;//speed_pluse=pluse/(float)speedcount;
if(speed_pluse<5) //当编码器的值很小的时候,让车停下来
{
stop=1;
}
speed_err1=speed_mid-speed_pluse;
speedoutold=speedoutnew;
p_value=K_p*(speed_err1-speed_err2);
i_value=K_i*speed_err1;
d_value=K_d*(speed_err1-2*speed_err2+speed_err3);
speedoutnew=speedoutold+(p_value+i_value+d_value);//著名的pid算法,绝对正确
speed_err3=speed_err2;
speed_err2=speed_err1;
if(stop==1)
{
speedoutnew=0;
}
if(speedoutnew<-6000)
{
speedoutnew=-6000;
}
if(speedoutnew>6000)
{
speedoutnew=6000;
}
if((speed_pluse>speed_mid)&&(a<10)&&(stopflag==0)) //刚入弯的时
{ //候,如果当前速度大于目标速度,清
LPLD_FTM_PWM_ChangeDuty(FTM1,FTM_Ch0,0); //零
speed_zhen;
speedoutnew=0;
speed_err1=0;
speed_err2=0;
}
else
{
if(speedoutnew>0)
{
LPLD_FTM_PWM_ChangeDuty(FTM1,FTM_Ch0,(uint32)speedoutnew);
speed_zhen;
}
else
{
b=-speedoutnew;
LPLD_FTM_PWM_ChangeDuty(FTM1,FTM_Ch0,(uint32)b);//正转方向占空比为0 反转
speed_fang;
}
}
}
关于电机驱动我建议用mos管,btn能把你手烫熟了。
好了到了这里,基本就写完了,有人问那你人字弯和障碍以及十字怎么没写。我只能说自己探索的过程是别人无法知道的乐趣。再说明年就不一定是人字弯了,肯定又有新的挑战了。只有不断不断的学习,才能一直进步,不要满足现状。