前言

       随着人工智能技术和互联网科技的发展,无人驾驶并不再是遥远的技术。研究人员预测:到 2025 年,我们将看到大约 800 万辆无人或半无人汽车在路上行驶。无人驾驶正在如火如荼的展开着。百度和沃尔沃共同研发L4级别无人电动车,特斯拉的 Autopilot,奥迪的 Traffic Jam Pilo等等,我们可以知道,世界上最主流的无人驾驶技术都是基于激光雷达,摄像头视觉,GPS+IMU的方案,那么我们为什么不可以基于这些技术做一台自己的小车呢?正好借着极速越野组,我们来自己做一台无人驾驶小车,好了,让我们直入主题,下面就来给大家分享一下调车过程中的一些经验,希望对刚入门的车友们有所帮助。

一:微控制器与传感器

       微控制器可以选择使用MindMotion公司的芯片,我们常用的有MM32SPIN27、MM32F3277、MM32SPIN360C,其中选用MM32F3277作为主控芯片,电调芯片可以用MM32SPIN360C或者MM32SPIN27。传感器的选择更是多样,GPS、IMU、摄像头、超声波、编码器等,但是对于赛题来说,我们以GPS和IMU为主,对于IMU,可以使用带有微控制器的传感器,这一点降低了开发门槛。不过还是建议大家不要去买太贵的IMU,够用就可以了。

二:驱动

       对于有刷和无刷的选择,无刷虽好,但是无刷的控制难度是远大于有刷的,包括电调的制作也是有难度的,还是建议带先用有刷调到一定程度在上无刷。有刷电机控制在程序上就是简单给一路io一路pwm,利用占空比来调速,而无刷电机需要精准控制六步换向来使电机旋转,具体我就不在这里赘述了,可以参考逐飞开源的BLDC方案。

三:方案设计

  1. GPS+IMU导航

       我们想让小车按照规定的路径自主导航,那么第一点就是让小车做定位,将GPS与单片机进行串口通信传回来的数据如图1所示

图1.GPS数据

但实际我们需要的是经纬度信息,所以我们提取处理$GNRMC这样格式的数据即可。

例如($GNRMC,034404.00,A,3640.48056,N,11707.96443,E,0.04,0.000,271017,0.0,0,D*68)

这时就需要把不用的信息滤除,可以用上位机去设置,也可以发送十六进制指令,这个需要看不同型号GPS对应的操作手册。

/******************************************************************************************************************
** @brief    Original data
** @param    void
** @return   void
** @note     Extraction of raw data
*******************************************************************************************************************/
char Get_InitData(int* Lon_Z, int* Lon_X, int* Lat_Z, int* Lat_X)
{

  //判断纬度值中是否有‘.'有:说明有数据 例如:3755.81233 
  if((strstr(Save_Data.latitude, ".")) != NULL)
  {
    LAT_ = (Save_Data.latitude[5] - '0')*10000 + (Save_Data.latitude[6] - '0')*1000 + (Save_Data.latitude[7] - '0')*100 + \
        (Save_Data.latitude[8] - '0')*10 + (Save_Data.latitude[9] - '0');
    Decimals = 1.0*(Save_Data.latitude[5] - '0')/10 + 1.0*(Save_Data.latitude[6] - '0')/100 + 1.0*(Save_Data.latitude[7] - '0')/1000 + \
        1.0*(Save_Data.latitude[8] - '0')/10000 + 1.0*(Save_Data.latitude[9] - '0')/100000;
		
    *Lat_Z = (Save_Data.latitude[0] - '0')*1000 + (Save_Data.latitude[1] - '0')*100 + (Save_Data.latitude[2] - '0')*10 + \
		(Save_Data.latitude[3] - '0');
    *Lat_X = LAT_;       //取值为3755 和 81233
		
		Latitude = 1.0*(*Lat_Z + Decimals);
  }
  else
    return 0;
  //经度原理同上 例如:11223.89887
  if((strstr(Save_Data.longitude, ".")) != NULL)
  {
    LON_ = (Save_Data.longitude[6] - '0')*10000 + (Save_Data.longitude[7] - '0')*1000 + \
      (Save_Data.longitude[8] - '0')*100 + (Save_Data.longitude[9] - '0')*10 + (Save_Data.longitude[10] - '0');
		Decimals = 1.0*(Save_Data.longitude[6] - '0')/10 + 1.0*(Save_Data.longitude[7] - '0')/100 + \
      1.0*(Save_Data.longitude[8] - '0')/1000 + 1.0*(Save_Data.longitude[9] - '0')/10000 + 1.0*(Save_Data.longitude[10] - '0')/100000;
		
    *Lon_Z = (Save_Data.longitude[0] - '0')*10000 + (Save_Data.longitude[1] - '0')*1000 + \
      (Save_Data.longitude[2] - '0')*100 + (Save_Data.longitude[3] - '0')*10 + (Save_Data.longitude[4] - '0');		
    *Lon_X = LON_;       //取值为11223 和 89887
		
		Longitude = 1.0*(*Lon_Z + Decimals);
  }
  else
    return 0;
  return 1;
}

这里需要注意,把提取出来的经纬度放入百度地图里查看时,竟然发现定位相差甚远,不要着急,这并不是模块坏了。之所以会产生“偏差”,这就涉及到一个有关坐标系转换的问题:GCJ-02 火星坐标系统纠偏。模块输出数据的格式为NMEA-0183,使用的坐标系是WGS-84。而国内的地图软件大都是使用的GCJ-02坐标系。所以开发者如果将WGS-84的经纬度填入国内的地图软件,就会产生极大的偏差。这里我给出一个网站,可以很方便的测试纠偏结果 http://www.openluat.com/GPS-Offset.html

/******************************************************************************************************************
** @brief    Format conversion
** @param	 	 void
** @return	 void
** @note     $GNRMC Data DDMM.MMMMMM degree format changed to degree format
*******************************************************************************************************************/
void Realtime_data_conversion(void)
{	
  	Get_InitData(&Lon_IZ, &Lon_IX, &Lat_IZ, &Lat_IX);

    realresultlon = (int)(Longitude/100) + (Longitude/100.0 - (int)(Longitude/100)) *100.0 / 60.0;
    realresultlat = (int)(Latitude/100) + (Latitude/100.0 - (int)(Latitude/100)) *100.0 / 60.0;
}

        获取到经纬度信息后,我们在路径上采集点位信息。假设采集12个点位,如图2所示,那么按照顺序1—>2—>3—>4—>5—>6—>7—>8—>9—>10—>11—>12—>1逐点前进,就可以完成一圈的导航。这里注意一下,采集点位时要抱着电脑一个点一个点去输入,样子狼狈不说,还十分浪费时间,我推荐大家将点位存在Flash里,通过查芯片手册,F3277有128个扇区,每个扇区有4页,一页有1K,存储点位信息不在话下,通过按键来触发存点。

图2.点位示意图

        好的,点位采集完毕,那么该样对点循迹呢,因为提前采好了每个点的坐标,我们利用两点经纬度来计算出两个点之间的距离以及方向角。具体解算原理参考这个文章http://blog.sina.com.cn/s/blog658a93570101hynw.html 解算得到的距离用来判断是否到达目标点(距离小于一定值),方向角来告诉小车该往哪个方向行驶。这时小车虽然知道了该往哪个方向走,但是怎么知道自己有没有朝向这个方向呢,这个时候就用上了IMU,因为IMU不受限制,可以使用九轴陀螺仪,利用其中的磁力计解算并输出角度值(东北天坐标系下),将GPS解算出的角度值和IMU输出的角度值做闭环pid,控制舵机打角。

/******************************************************************************************************************
** @brief    AngleControl
** @param	 	 void
** @return	 void
** @note     empty
*******************************************************************************************************************/
void AngleControl(void)
{
  AngleError[0] = 1.0 * Angle_deviation;
	
  AnglePwm[0].P = Angle.P * (AngleError[0] - AngleError[1]) * 0.09;
	//AnglePwm[0].I = (Angle.I * AngleError[0]);
  AnglePwm[0].D = (Angle.D * (AngleError[2] - 2 * AngleError[1] + AngleError[0]) * 0.01);
  AnglePwm[0].S += (AnglePwm[0].P + AnglePwm[0].D);
	
	AngleError[2] = AngleError[1];
  AngleError[1] = AngleError[0];
	
	Steer = 750 + AnglePwm[0].S;
	Steer = Amplitude_Limit(Steer,645,890);
	pwm_duty_updata(TIM_2, TIM_2_CH1_A15, Steer);
}
  1. 摄像头导航

        对于操场而言,可以使用灰度摄像头来进行循迹,在室外阳光干扰大的情况下,需要把曝光时间调到很低。将图像进行二值化处理,再用左右两边黑白跳变点位置求出白线中心位置,和图像中心位置做闭环pid处理。考虑到循迹线不清晰的情况下,可以和GPS+IMU做组合,在摄像头看不清的情况下切换GPS导航等处理。

三:优化

        若想让车跑的稳又快,以上是远远不够的,算法优化才是关键。我们的系统采用了松组合导航组合方式,如图3所示,在该方式下,GPS与IMU各自独立工作,最终将两者数据融合,用于修正IMU系统的相关参数,最终给出较好的导航估计结果。

 图3.松组合结构

        对于GPS/IMU 的数据融合,给出以下四种方法供大家参考

1) 加权平均法

        利用过去若干个按照时间顺序排列起来的同一变量的观测值并以时间顺序变量出现的次数为权数,计算出观测值的加权算术平均数,以这一数字作为预测未来期间该变量预测值的一种趋势预测法。该方法从传感器提取数据,并将数据中的冗余信息作加权平均算法处理,以其结果作为融合值进行后续导航工作,简单好用。

(2) 卡尔曼滤波法

       卡尔曼滤波方法通过系统输入输出观测数据,进行数据的递推,对系统状态进行最优估计。卡尔曼滤波方法因其稳定性、容错性良好,在飞行器追踪、惯性导航初始对准等方面得到广泛运用,是目前最广泛的滤波算法之一。

(3) 多贝叶斯估计法

        贝叶斯估计法将传感器数据进行概率统计并组合,不确定性通过概率条件表达,当获取的传感器数据观测坐标一致时可直接对数据进行融合。多贝叶斯估计法将导航系统中的多个传感器输出数据分别制定一个贝叶斯估计,将各个关联概率分布合成后验概率分布,并使联合分布函数的似然数最小,以此得到融合数据。

(4)人工神经网络法

       它从信息处理角度对人脑神经元网络进行抽象, 建立某种简单模型,按不同的连接方式组成不同的网络。神经网络以强大的学习性、自适应性及容错性著称,能够进行大量非线性映射的计算。在数据融合中,各信息源所提供的数据都具有一定的不确定性,神经网络根据样本的相似性特征进行分类处理,同时根据人为设定的学习算法进行学习,得到数据分类的推理机制,实现信息融合。

        最后,如果有帮助的话请点赞收藏支持一下吧,祝大家在做无人车和机器人的路上越走越远,今年可以看到多少超过人类400米纪录的车呢?