(一)电磁处理

在此次智能车竞赛中,我选择了摄像头为主,电磁简单辅助路径规划,帮助车身在出弯后能快速回正,只不过后来因为赛场电磁线原因,关闭了电磁辅助,采用了纯摄像头循迹。

关于电磁循迹,我并没有使用归一化,只是简单的差比和处理

Diangan dg;
void Read_ADC()
{
  int16 i,j,temp;
  static int16 value_temp[5][5]={0};    //行:五个电感	列:每个电感自己五组均值
  int16 value_sum[5]={0};
  int16 value_ave[5]={0};  
  
  for(j=0;j<5;j++)
  {
    value_temp[0][j] = adc_mean_filter(ADC_1,ADC1_CH10_B21,5); 	//5次采样,取平均值     
    value_temp[1][j] = adc_mean_filter(ADC_1,ADC1_CH7_B18,5); 	
    value_temp[2][j] = adc_mean_filter(ADC_1,ADC1_CH8_B19,5); 
    value_temp[3][j] = adc_mean_filter(ADC_1,ADC1_CH5_B16,5); 
    value_temp[4][j] = adc_mean_filter(ADC_1,ADC1_CH6_B17,5);
  }
  
  /*=======================冒泡排序=================================*/
  for(i=0;i<5;i++)
  {
    for (j=0;j<4;j++)
    {
      if(value_temp[i][j]>value_temp[i][j+1])
      {
        temp 		           	= value_temp[i][j];
        value_temp[i][j]  	= value_temp[i][j+1];
        value_temp[i][j+1] 	= temp;
      }
    }
  }
  /*============中值滤波==========================================*/
    //求中间三项的和		舍去每个电感采集的五组值中最大最小值,剩余求平均
  for(i=0;i<5;i++)
  {
    value_sum[i] = value_temp[i][1] + value_temp[i][2] + value_temp[i][3];
    value_ave[i] = (int)(value_sum[i]/3);
  }
  
  dg.Middle_vlaue = value_ave[4];
  
  for(i=0;i<9;i++)
  {
    value_ave[i] = value_ave[i]/10*10;
  }
  
  dg.Left_heng    = value_ave[0];     //B21
  dg.Left_shu     = value_ave[1];     //B18
  dg.Right_heng   = value_ave[3];     //B16
  dg.Right_shu    = value_ave[2];     //B19
  dg.Middle_heng  = value_ave[4]-1000;     //B17
}
 
void ADC_deal()
{
  static float LevelTemp[5];	// 最近5次 差比和保存
  static float shuzhiTemp[5];
//  static float  ad_M_max=3400;
//  static float  ad_M_min =2800;
 
	
   dg.shuiping_error = (int)((float)( dg.Left_heng - dg.Right_heng )/( dg.Left_heng + 
                                       dg.Right_heng )* 100);//水平电感差比和   
   dg.shuzhi_error   = (int)((float)( dg.Left_shu  - dg.Right_shu  )/( dg.Left_shu  + 
                                       dg.Right_shu  )* 100);//内八电感差比和
	
   LevelTemp[4] = LevelTemp[3];
   LevelTemp[3] = LevelTemp[2];
   LevelTemp[2] = LevelTemp[1];
   LevelTemp[1] = LevelTemp[0];
   LevelTemp[0] = dg.shuiping_error;
   dg.shuiping_error_d =(int)(LevelTemp[0]-LevelTemp[3]);//水平电感偏差微分
   
   shuzhiTemp[4] = shuzhiTemp[3];
   shuzhiTemp[3] = shuzhiTemp[2];
   shuzhiTemp[2] = shuzhiTemp[1];
   shuzhiTemp[1] = shuzhiTemp[0];
   shuzhiTemp[0] = dg.shuzhi_error;
   dg.shuzhi_error_d = (int)(shuzhiTemp[0]-shuzhiTemp[3]);//竖直电感偏差微分
//   
//   if(dg.Middle_vlaue<ad_M_min)   ad_M_min = dg.Middle_vlaue;
//   if(dg.Middle_vlaue>ad_M_max)   ad_M_max = dg.Middle_vlaue;
//   dg.Middle_heng=(float)(dg.Middle_vlaue-ad_M_min)/(ad_M_max-ad_M_min);
//   dg.mid_normalized =(int)(100*dg.Middle_heng);
 
}

关于我的误差值,0.7*(摄像头误差)+0.4(电感误差):

car.Error=(int)((SteerSum)*0.7+dg.shuiping_error *0.4);


(二)陀螺仪使用

对于陀螺仪的使用,在我做比赛前,以为陀螺仪除双车组别,在其他组别上,也许只有在坡道上能用到,其实不然,陀螺仪在比赛中,可以辅助通过三个轴传回来的值,计算偏差的角度,在过弯后通过Z轴数值辅助车身快速回正,在出入圆环以及进出车库时,也可以通过对陀螺仪进行积分判断条件。

 
//简单的mpu6050转换  
gyro_x = ((float)mpu_gyro_x)*0.5f)/(16.384f);
gyro_y = ((float)mpu_gyro_y)*0.5f)/(16.384f);
gyro_z = ((float)mpu_gyro_z)*0.5f)/(16.384f);

(三)编码器闭环

曾看到一位大神说,电机闭环随便调两天,就会比开环跑的快。可见闭环控制的重要性,对于电机的闭环控制,主要通过设定的速度于编码器返回的速度进行做差,再进行pid计算,关于pid的讲解,网上无论是开源还是讲解都有很多优秀的文章,我就不过多讲解。这里我贴出我比赛的增量式pid代码,正对不同的赛道元素,我们可以设定不同的速度。

void Get_speed()
{
   //读取编码器计数值
  Speed.left_encoder  =  -qtimer_quad_get(QTIMER_1,QTIMER1_TIMER2_C2);
  Speed.right_encoder =  qtimer_quad_get(QTIMER_1,QTIMER1_TIMER0_C0);
  
  qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER0_C0);
  qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER2_C2);
  
  Speed.encoder_l=(int32)(Speed.left_encoder+0.00001);
  Speed.encoder_r=(int32)(Speed.right_encoder+0.00001);
  Speed.encoder = (int32)((Speed.left_encoder + Speed.right_encoder + 0.00001)/2);    
}
 
int16 error0,error1,error2;
void motor_pid()
{
  Get_speed();
  float P,I,D;
  //左电机
  error0 = error1 = error2 = 0;
  error0 = targrt_speed - Speed.encoder_l;//I
  error1 = error0 - pidl.lastError;//P
  error2 = error0 - 2*pidl.lastError + pidl.preError;//D
  pidl.preError = pidl.lastError;
  pidl.lastError = error0;
  
  P=speed_P; I=speed_I; D=speed_D;
  
  pidl.PWM += (int16)(P*error1 + I*error0 + D*error2);
 
  if(pidl.PWM > 30000)  pidl.PWM = 30000;
  if(pidl.PWM < -9999)  pidl.PWM = -9999;
  
  //右电机
  error0 = error1 = error2 = 0;
  error0 = targrt_speed - Speed.encoder_r;//I
  error1 = error0 - pidr.lastError;//P
  error2 = error0 - 2*pidr.lastError + pidr.preError;//D
  pidr.preError = pidr.lastError;
  pidr.lastError = error0;
  
  P=speed_P; I=speed_I; D=speed_D;
  
  pidr.PWM +=(int16)(P*error1 + I*error0 + D*error2);
  
  if(pidr.PWM > 30000)  pidr.PWM = 30000;        
  if(pidr.PWM < -9999)  pidr.PWM = -9999;
   
  pidl.PWM += (int16)(P*error1 + I*error0 + D*error2);
  pidr.PWM +=(int16)(P*error1 + I*error0 + D*error2);
 
   Motor_pwm_out(pidl.PWM,pidr.PWM ); 
}

对于电机pid如何调节,我们可以通过山外的上位机,通过串口发送波形,更改三个值的大小,直至波形完美为止

//串口发送至上位机
void display_sendware()
{
  int8 a[6]={0};
  a[0]=targrt_speed;
  a[1]=targrt_speed>>8;
  a[2]=(int16)Speed.left_encoder;
  a[3]=(int16)Speed.left_encoder>>8;
  a[4]=(int16)pidl.PWM;
  a[5]=(int16)pidl.PWM>>8;
  vcan_sendware((uint8*)a,6); 
}

大概的波形调至到这种形态就可以了

 因为手上没有调试的波形,所以只能大概画出来,红色为你设定的速度,蓝色为编码器返回的值,当打开电机时,编码器值能快速反应并迅速在设定好的速度周围很小的范围震荡。

(四)检测斑马线

对于摄像头采集图像,可以通过代码判断,但如果是纯电磁判断,可以使用干簧管以及红外对管判断,但是由于硬件原因,似乎只能倒车入库,且准确性不高,在本次比赛中,我看到有的学校使用激光模块识别,可以实现判断车库,不需要倒车入库,对于电磁的同学可以尝试。

(五)总结

如果有人已经看到这里,当你决定做智能车比赛时,我希望你能懂得,智能车竞赛与其他学科竞赛不同,它所需投入的时间要很多,且你最后取得的比赛成绩可能也与你投入的时间不成正比,除去时间,一个好的队友也及其重要,他可以不是那个硬件和软件最厉害的人,但一定是那个可以一直和你坚持下去的那个,我很荣幸我在这次比赛中有一个很好的队友。

最后,当有一个你投入了几个月时间的比赛却没有达到你想要的成绩时,心中会产生巨大的落差感。但如果有人问我你是否会后悔参加这个比赛时,我还是会说,不后悔。你所有的比赛经历都会转变成自己的成果沉淀,像荷花一样,也许有人已经于水面上盛放结果,或许属于你的还没有浮出水面,但深埋在淤泥之下的也是你积累的果实,相信它总有收获的那一天。加油,智能车人!