飞思卡尔光电结束散尽经验和程序

做智能车的过程中,如果有一天我把车做出来了,并且做的不错,我会把代码以及思路贡献出来,让以后做车的同学们少走些弯路,我们是省赛一等奖,所以看看还是有些意义的。
我是做光电的,不知道其他智能车是怎么写的,但根本都差不多。光电车控制简单分为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能把你手烫熟了。

好了到了这里,基本就写完了,有人问那你人字弯和障碍以及十字怎么没写。我只能说自己探索的过程是别人无法知道的乐趣。再说明年就不一定是人字弯了,肯定又有新的挑战了。只有不断不断的学习,才能一直进步,不要满足现状。

永不止步步 发表于11-13 13:54 浏览65535次
分享到:

已有0条评论

暂时还没有回复哟,快来抢沙发吧

添加一条新评论

只有登录用户才能评论,请先登录注册哦!

话题作者

永不止步步
金币:67410个|学分:307917个
立即注册
畅学电子网,带你进入电子开发学习世界
专业电子工程技术学习交流社区,加入畅学一起充电加油吧!

x

畅学电子网订阅号