关于TC264单片机与智能车摄像头循迹的一些学习心得

前言:最近一段时间在准备智能车考核,其中要求使用TC264单片机实现PID控制的小车摄像头循迹。其中关于PID的部分在之前我已经上传过了,这篇文章主要讲怎么实现循迹与舵机的位置式PID调参和电机的增量式调参的一些心得。

一、摄像头循迹的实现

首先我们要明白英飞凌公司旗下的小钻风摄像头为灰度摄像头,它收集到的图像信号为黑白灰像素点构成的图像,那我们想要小车能识别到赛道边缘从而实现循迹,就需要对采集到的图像进行二值化处理。

那么什么叫二值化处理呢,下面我结合代码进行说明。

 int16 i,j;
 uint8 mt9v03x_image_2[120][188];
 uint8 th=100;//阈值大小


mt9v03x_init();//摄像头初始化
 tft180_set_dir(TFT180_CROSSWISE_180);                             // 需要横屏不然显示不下
 tft180_init();//屏幕初始化
 if(mt9v03x_finish_flag)//这个if函数内部进行对采集到的每一帧画面进行处理
      {
       erzhihua();//二值化函数
       tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);//将处理的图像显示出来
       mt9v03x_finish_flag = 0;
         }



void erzhihua(void)
{

//说明:摄像头采集的图像会以数组的形式存起来,其中数组的里面的数据为0~255的uint8型的数,其中0为黑色,255为白色
    int i,j;

    for(i=0;i

简单来说二值化就是将黑白灰图像转化为只有黑白的图像,通过阈值的合理设定达到对赛道边缘的黑化(便于检测)的效果。

但是二值化仅仅使得我们知道了赛道边缘在哪里,而我们想要小车保持在赛道中间,就需要控制转向的舵机的配合。具体怎么配合呢?

首先,我们要将得到二值化图像进行处理,将赛道的中线表示出来。此时这个所谓的赛道中线其实本质上就是一串数组对应的宽和高。通过将得到的赛道中线的水平位置与屏幕显示的中间位置(就是数组的宽的一半)进行比较,算出小车与赛道中心的误差值,将这个误差值输入到舵机的PID中进行计算,最后输出PWM给舵机进行转向。

具体代码如下

int16 i,j;
 uint8 mt9v03x_image_2[120][188];
 uint8 th=100;//阈值大小
 uint8 mid[120];
 int side_left[120],side_right[120];

void erzhihua(void)
{


    int i,j;

    for(i=0;i=0;j--)
      {
          if( mt9v03x_image_2[i][j]==0)
           { side_left[i]=j;break;}
      }
      mid[i]=(side_right[i]+side_left[i])/2;//赛道中线就是左右端点之和除以二

  }
    for(int i=120;i>70;i--)
        {
            feedbackpositionleijia+=mid[i];
        }
            feedbackposition= feedbackpositionleijia/50;//为提高数据可信度,减小误差,将50个中点求和算平均值
            feedbackpositionleijia=0;
}

void show_line(void)//将中线与边界线在显示的图像中描黑
{

    int i;
    for(i = MT9V03X_H-1; i >= 0; i--)
      {
          //画赛道中线
          if(mid[i] < MT9V03X_W && mid[i] >= 0)
          {
              mt9v03x_image_2[i][side_left[i]] = 0;
              mt9v03x_image_2[i][side_right[i]] = 0;
              mt9v03x_image_2[i][mid[i]] = 0;
          }
      }

}


if(mt9v03x_finish_flag)
         {
          erzhihua();
          xunji_Mid();
          show_line();
          tft180_displayimage03x((const uint8 *)mt9v03x_image_2, 160, 128);
          mt9v03x_finish_flag = 0;
         }//最后写在主函数循环里面的显示函数






       PID_Calc_wz(&mypid_Angle,targetposition,feedbackposition);//位置式pid控制舵机
       int angleout=dutyzhong+mypid_Angle.output;
       pwm_set_duty(SERVO_MOTOR_PWM,angleout);


int dutyzhong=720;//每个舵机的中点对应的PWM占空比不同,一般在750上下
float feedbackposition;
float feedbackpositionleijia;
float targetposition=80;//这里获取小车目标位置




void PID_Init(PID *pid,float p,float i,float d,int16 maxI,int16 maxOut)
{
    pid->kp=p;//调参只要改动这三个值就行p,i,d
    pid->ki=i;
    pid->kd=d;
    pid->maxIntegral=maxI;//最大积分
    pid->maxOutput=maxOut;//最大输出值
}

进行一次位置式pid计算
参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
void PID_Calc_wz(PID *pid,float reference,float feedback)
{
    //更新数据
    pid->lastError=pid->error;//将旧error存起来
    pid->error=reference-feedback;//计算新error
    //计算微分
    int dout=(pid->error-pid->lastError)*pid->kd;//误差的差值乘以Kd
    //计算比例
    int pout=pid->error*pid->kp;//误差乘以Kp
    //计算积分
    pid->integral+=pid->error*pid->ki;//误差的累加乘以Ki
    //积分限幅
    if(pid->integral > pid->maxIntegral) pid->integral=pid->maxIntegral;//积分过大则限幅赋值为max
    else if(pid->integral < -pid->maxIntegral) pid->integral=-pid->maxIntegral;//积分过小则赋值为min
    //计算输出
    pid->output=pout+dout+pid->integral;//输出值为P+I+D
    //输出限幅
    if(pid->output > pid->maxOutput) pid->output=pid->maxOutput;
    else if(pid->output < -pid->maxOutput) pid->output=-pid->maxOutput;

    if(pid->output < pid->minOutput) pid->output=pid->minOutput;


}

二、PID调参技巧

位置式PID:先调Kp,使得波形在目标值上下震荡,且幅度会逐渐减小。接下来就加入Kd来对震荡进行抑制,当Kd过大时就没有抑制作用,反而会使得波形不停震荡。最后加入Ki来使得最后趋于稳定的值更趋近于目标值。当你初步调节完成PID参数时,先记录下此时的参数,然后回过去继续微调Kp、Kd、Ki。直到最后波形达到一定的响应与稳定时才算调好了参数。而且每个电机的PID参数不尽相同,需要单独调节。

增量式PID:因为由于公式为增量,Ki对应的量才是error,所以先调Ki。接着再调Kp,最后调Kp。具体调到什么情况合适参考位置式PID。

可以用vofa这个软件,用电脑这个上位机来实时显示波形,便于我们的调参。

你可能感兴趣的:(单片机,嵌入式硬件)