概述
队伍名称:逐鹿—分母队
参赛队员:王泽南
张志宇
徐程升
带队教师:但永平
耿世勇
第一章 引言
智能车辆是一个集环境感知、规划决策、多等级辅助驾驶等功能于一体的综合系统,它集中运用了计算机、现代传感、信息融合、通讯、人工智能及自动控制等技术,是典型的高新技术综合体。目前对智能车辆的研究主要致力于提高汽车的安全性、舒适性,以及提供优良的人车交互界面。近年来,智能车辆己经成为世界车辆工程领域研究的热点和汽车工业增长的新动力,很多发达国家都将其纳入到各自重点发展的智能交通系统当中。
全国大学生智能汽车竞赛以“立足培养、重在参与、鼓励探索、追求卓越”为宗旨,是一项鼓励创新的科技竞赛活动。智能车设计内容涵盖了控制、模式识别、传感技术、汽车电子、电气、计算机、机械、能源等多个学科的知识,对学生的知识融合和实践动手能力的培养,具有良好的推动作用。竞赛要求在规定的汽车模型平台上,使用微控制器作为核心控制模块,通过增加道路传感器、电机驱动模块以及编写相应控制程序,制作完成能够自主识别道路并且完成其余指定任务的模型汽车。
车模采用NXP公司生产的i.MX RT1064单片机作为核心控制器,使用Openart-mini作为视觉识别核心,通过摄像头和电感采集赛道信息,自行涉及并制作主控板、驱动板等功能电路,使用多套速度方案,按照规定路线前进并完成赛道特殊元素的识别,识别Tag码并停车进行图像的识别与打靶。车模设计制作过程中,深入研究车模特点,设计整体结构及控制系统,并根据实际运行情况及时调整。
硬件机械设计时,针对小车结构特点,在传感器、电路模块安装时多次尝试,及时优化;软件系统设计时,使用了经典PID以及模糊PID等方法,外加差速控制,实现转向及速度控制,赛道识别上使用了动态阈值,保证了适应性。。
RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在RT-Thread系统中,任务是通过线程实现的,RT-Thread中的线程调度器也就是以上提到的任务调度器。且RT-Thread拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量达数千万台,成为国人自主开发、国内最成熟稳定和装机量最大的开源RTOS。
使用RT-thread操作系统的优势在于相比裸机开发的效率获得大幅提升,软件设计时引入嵌入式操作系统的任务调度,优化了程序执行的逻辑顺序,使用临界区资源保护,很好的增强了程序稳定性,避免了逻辑错误和冲突等。其次是RT-Thread的开源资料多,范围广。大多数问题都可以在RT-Thread社区内找到答案,对于国人相对友好。同时还有官方编写的文档《RT-Thread编程指南》,可以随时在其中查找内容,做到随用随查,搭配官方推出的视频教程以及官方和逐飞的demo例程,在极短的时间内就可以简单的入门。
本文第二章中简要介绍了车模的硬件结构设计,在第三章中列出了硬件系统的设计方案和具体原理,第四章详细说明了程序策略,第五章详细写出了图像识别使用的方法和优化策略,第六章则详细说明了使用RT-Thread相比裸机开发下的优势及其所发挥的作用,第七章主要内容是RT-Thread资料的使用以及解决问题的过程与步骤,第八章给出了车模制作过程中的情况,总结了RT-Thread所用到的功能,指出现存的一些问题与可能的解决方法。
第二章 车模机械设计
根据大赛组委会对智能视觉组进行车模和传感器的限定,本届智能视觉组我们使用C型车模和摄像头作为车模的构架。C型车模具有舵机与电机,是一种相对灵活的四轮车模,对于赛道的适应性高,而且稳定易于控制。由于智能视觉组对于尺寸方面没有任何限制,所以机械结构对于整车的性能至关重要,只有在车模拥有较好机械结构的前提下,控制算法才能发挥出其应有的效果。使智能车可以更好的适应高速运行的情况,保证其在高速时仍然具有较高的机械强度与灵活度,是本次比赛研究重点。在规则允许的范围内,我们对车模进行了优化改装,本章将介绍根据实际情况对车模机械做出的改进内容。
2.1. 前轮定位调整
四轮智能车出现直线走偏、转弯费力、轮胎磨损快等情况时大多与轮胎安装角度有关,涉及到一个非常重要的转向轮位置角度定位问题,叫做“前轮定位”。它的作用是保障智能车直线运行时的稳定性,使其转向轻便并减少轮胎的磨损。前轮是转向轮,它的安装位置由主销内倾、主销后倾、前轮外倾和前轮前束等四个项目决定,反映了转向轮、主销和前轴等三者在车架上的位置关系。
2.1.1. 主销后倾角
从侧面看车轮,转向主销(车轮转向时的旋转中心)向后倾倒,称为主销后倾角。设置主销后倾角后,主销中心线的接地点与车轮中心的地面投影点之间产生距离 (称作主销纵倾移距,与自行车的前轮叉梁向后倾斜的原理相同),使车轮的接地点位于转向主销延长线的后端,车轮就靠行驶中的滚动阻力被向后拉,使车轮的方向自然朝向行驶方向。设定很大的主销后倾角可提高直线行驶性能,同时主销纵倾移距也增大。但主销纵倾移距过大,会使转向沉重,而且由于路面干扰而加剧车轮的前后颠簸。
2.1.2. 主销内倾角
从车前后方向看轮胎时,主销轴向车身内侧倾斜,该角度称为主销内倾角。当车轮以主销为中心回转时,车轮的最低点将陷入路面以下,但实际上车轮下边缘不可能陷入路面以下,而是将转向车轮连同整个车模前部向上抬起一个相应的高度,这样车模本身的重力有使转向车轮回复到原来中间位置的效应,因而车模容易回正。此外,主销内倾角还使得主销轴线与路面交点到车轮中心平面与地面交线的距离减小,从而减小转向时舵机上的力,使转向更加轻便,同时也可减少从转向轮传到舵机上的冲击力。
2.1.3. 前轮外倾
从前后方向看车轮时,轮胎并非垂直安装,而是稍微倾倒呈现“八”字形张开,称为负外倾,而朝反方向张开时称正外倾。车模一般将外倾角设定得很小,接近垂直。若设定大外倾角会使轮胎磨偏,降低轮胎摩擦力。
2.1.4. 前轮前束
四轮定位前束值脚尖向内,所谓“内八字脚”的意思,指的是左右前轮分别向内。采用这种结构目的是修正上述前轮外倾角引起的车轮向外侧转动。如前所述,由于有外倾,转向变得容易。另一方面,由于车轮倾斜,左右前轮分别向外侧转动,为了修正这个问题,如果左右两轮带有向内的角度,则正负为零,左右两轮可保持直线行进,减少轮胎磨损。
在智能车程序基本完整,可以完成各类赛道元素后,根据实际情况对前轮定位进行调整。通过改变上横梁垫片的数目,增大后倾角。在智能车长期调试后,发现适当增大内倾角,可使转弯时车轮和地面有更大的接触面积,继而增大车与地面的摩擦程度,使车转向更灵活,减小因摩擦不够而引起的转向不足的情况。
2.2. 舵机安装调整
舵机摆杆的长度直接影响到舵机的转矩。由公式舵机转矩 = 舵机摆杆作用力 * 摆杆长度,得:舵机摆杆作用力越大,反应越灵敏,转向速度越快。转矩一定时,摆杆越长,输出的作用力越小,所以摆杆不能太长,不然会拉不动轮胎左右转向,从这个角度考虑拉杆越短越好。但是我们知道,拉杆越长的时候,舵机转一小圈,下面拉杆的会转很大的范围,也就是说,摆杆长度决定了舵机和拉杆变化的比例也就说明相应速度。所以我们又希望摆杆很长,这样轮子转向的响应速度就会很快。
综合考虑,我们选用的舵机摆杆的长度在 30mm 左右。同时考虑到阿克曼转向理论,理论转角如图 1.1,四个轮子路径的圆心大致上交会于后轴的延长线上瞬时转向中心,这样可以使车辆在过弯时转向轮处于纯滚动状态,减少过弯时的阻力,减小轮胎的磨损,提高车辆转弯性能。
根据杠杆原理,将与舵机连接的舵盘适当加长,再将转向传动杆连接在加长的输出盘的末端,就可以在舵机输出较小的转角下,取得更大的前轮转角,提高舵机的响应的速度。结合舵机重心,结构件安装等问题,最终使用了立式安装方式。
▲ 图1.1 阿克曼转角图
2.3. 底盘高度调整
降低车模底盘可以降低重心,车模重心低可以使车模运行更加稳定,获得 更好的转弯特性。所以,在保证车模可以通过灯盘的情况下,底盘尽可能的降低,可以使车更加快速稳定。对于C车模,修改前轮滚动的轴心和车模底盘的高度差,可以修改车模底盘的高度,所以通过在前轮固定处垫垫圈来降低车模底盘高度。
第三章 硬件系统设计及实现
3.1. 硬件方案设计
硬件电路在整个智能车系统中虽然是基础,但是也是整个体系中比较重要的一部分,是保障整个系统能够稳定运行的基础。硬件电路的设计不仅需要考虑功能性,而且需要对其稳定性及可靠性进行准确的设计,需要不断的测试与改进。
主要从系统的可靠性、稳定性、实用性、简洁、美观等方面来考虑硬件的整体设计。从初代板到最终板,我们经历了各种大大小小问题以及功能实现的讨论,才有了最终的硬件方案。
可靠性、稳定性以及实用性是一个系统能够完成预想功能的最大前提。在原理图与PCB的设计过程中,我们考虑到各个功能模块的电特性以及之间的耦合作用以及信号线之间的干扰。对易受干扰的模块做了电磁屏蔽作用,而其他部分则做了相应的接地、滤波、模拟与数字电路的隔离等工作。
简洁、美观是指在满足了可靠、稳定等的要求后,为了尽量减轻车模的负载,降低模型车的重心,应使电路设计尽量简洁,尽量减少元器件使用数量,缩小电路板面积,使电路部分重量轻,易于安装。在设计完原理图后,注重PCB板的布局,优化电路的走线,整齐排列元器件,最终做到电路板的简洁以及板子的观赏性。
3.2. 电源模块
电源模块是硬件系统稳定运行的关键保障,是能源供给的核心。整个硬件系 统的工作完全由电源供电的可靠性决定,电源供电不稳会导致电池损耗、单片机 复位、电感采集数据不稳定等问题。因此电源设计是硬件电路的重要一环。设计中,除了需要考虑电压范围和电流容量等基本参数之外,还要在电源转换效率、降低噪声、防止干扰和电路简单等方面进行优化。在 PCB 布局中电容的去耦作用尤为重要。本次竞赛的电源需求包括 3.3V、5V、6V、12V等。
3.2.1. 3.3V稳压模块
3.3V 供电我们使用 AMS1117芯片和RT9013-33作为稳压芯片,AMS1117是一款低压差的线性稳 压器,提供完善的过流保护和过热保护功能,它可以确保输出电压和参考源精度 在1%的精度范围内。其原理图如图3.1所示。
▲ 图 3.1 3.3V 稳压电路原理图
3.2.2. 5V稳压模块
5V稳压模块我们使用的是LM2940-5 稳压芯片,该芯片为输出电压固定的低压差三端稳压器;输出电压5V;输出电流1A;输出电流1A时,最小输入输出电压差小于0.8V;最大输入电压26V;工作温度-40~~+125℃;内含静态电流降低电路、电流限制、过热保护、电池反接和反插入保护电路。
5V电源电压的分配情况如下:
1) 将系统电源通过 LM2940-5 稳压管稳压到 5V,为单片机 5V 引脚单独供电;
2)将5V电压通过 RT9013-33 稳压芯片稳压到 3.3V,单独为 OLED、编码器、按键、蜂鸣器、运放芯片、陀螺仪、摄像头供电;
3)将系统电源通过 LM2940-5 稳压管稳压到 5V,为外部模块供电;
其原理图如图3.2.1、3.2.2、3.2.3所示。
▲ 图 3.2.1 5V 单片机电源原理图
▲ 图 3.2.2 5V 稳压电路原理图
▲ 图 3.2.3 5V 外设电源原理图
3.2.3. 6V舵机电源模块
6V电源模块我们采用的是AS1015芯片通过调节滑动变阻器使其输出的电压为6V左右来给舵机供电使用,其原理如图3.3所示。
▲ 图 3.3 舵机电源原理图
3.2.4. 12V升压模块
12V升压模块,我们采用的是MC34063升压模块,将系统电源通过MC34063升压升到12V,用于给驱动模块中的 IR2104 驱动电路供电。MC34063组成的升压电路原理如下图,当芯片内开关管(T1)导通时,电源经取样电阻Rsc、电感L1、MC34063的1脚和2脚接地,此时电感L1开始存储能量,而由C0对负载提供能量。当T1断开时,电源和电感同时给负载和电容Co提供能量。电感在释放能量期间,由于其两端的电动势极性与电源极性相同,相当于两个电源串联,因而负载上得到的电压高于电源电压。开关管导通与关断的频率称为芯片的工作频率。只要此频率相对负载的时间常数足够高,负载上便可获得连续的直流电压。
▲ MC34063升压电路原理图
其中比较器的反相输入端(脚5)通过外接分压电阻R1、R2监视输出电压。其中,输出电压U。=1.25(1+R2/R1)由公式可知输出电压。仅与R1、R2数值有关,因1.25V为基准电压,恒定不变。若R1、R2阻值稳定,U。亦稳定。其原理图如3.4所示。
▲ 图 3.4 12V升压原理图
3.3. 电机驱动模块
驱动电路是智能车对电机驱动及控制的保障。电机驱动的基本原理是H桥驱动原理,我们组采用的是H桥驱动电路。其中选择MOSFET时主要考虑的因素有:耐压、导通内阻和封装。智能汽车电源是额定电压为7.2V的电池组,由于电机工作时可能处于再生发电状态,所以驱动部分的元件耐压值最好取两倍电源电压值以上,即耐压在16V以上。而导通内阻则越小越好。封装越大功率越大,即同样导通电阻下通过电流更大,但封装越大栅极电荷越大,会影响导通速度。故MOSFET 应该基于以下一些原则:
(1)MOSFET 导通内阻要足够小;
(2)由于采用 PWM 进行控制,那么 MOSFET 需要频繁的开和关,所以选择的 MOSFET 需要有较高的开关频率;
(3)由于需要频繁的开关,其开关损耗也要很低才可以,开关响应速度要快;
(4)最大工作电流,耐压值等等都要足够大我们选用的MOSFET为IRLR7843TRPBF,耐压值为30V,最大电流为160A,导通内阻为3.1mΩ,并且在实际应用中发现该MOS容易发烫,应做好相应的散热。
在选择中专用栅极驱动芯片的时候,我们发现其中IR2104 型半桥驱动芯片可以驱动高端和低端两个N沟道 MOSFET,能提 供较大的栅极驱动电流,并具有硬件死区、硬件防同臂导通等功能。使用两片IR2104 型半桥驱动芯片可以组成完整的直流电机H桥式驱动电路。并且其功能完善,价格低廉容易采购,所以我们选择对它进行设计。其原理图如3.5所示。
▲ 图 3.5 H桥驱动电路原理图
3.3.1. 隔离芯片
我们采用的是74lvc245芯片芯片有20个引脚,DIR和OE非 是控制引脚,VCC和GND是电源引脚,A端和B端为8个输入输出口,在发送和接收方向上都具有非反向三态总线兼容输出,由OE控制输出,所以还具有隔离的作用。
其原理图如图3.6所示。
▲ 图 3.6 74lvc245电路原理图
3.4. 运放模块设计
为了能够准确的测量感应电压,还需对其进行进一步放大,一般将电压峰值 放大到 1-5V左右就可以进行幅度检测。我们选用 OPA4377运放芯片,OPA4377是一款低噪声 5.5MHz 带宽CMOS运放,具有低噪音,低功耗,失真小的特点。我们使用肖特基二极管1N5819为电路起到保护、钳制作用。其原理为肖特 基二极管1N5819由两个二极管反向串联组成的,一次只能有一个二极管导通, 而另一个处于截止状态,那么它的正反向压降就会被钳制在二极管正向导通压降 0.5-0.7以下,从而起到保护电路的目的.将周期性变化的波形的顶部或底部保持在某一确定的直流电平上。运放模块原理图如图3.7所示。
▲ 图 3.7 运放模块电路原理图
3.5. 激光打靶模块
因为在识别过程中,识别到水果的图片,要通过一个125Hz的激光去打靶来触发裁判系统,故我们组采用NE555电路来产生一个125Hz的PWM来实现此功能,当EN引脚给高电平激光就会发射125Hz的信号。其原理图如图3.8所示。
▲ 图 3.8 激光打靶模块电路原理图
3.6. 人机交互电路设计
人机交互模块由 TFT 屏幕、按键、蜂鸣器、无线串口、发光二极管等组成。主要用于调整车模运行方式、告知车模使用者车模状态及显示相关工作参数。其部分电路原理图如图所示。
3.7. 编码器模块
编码器模块的作用是计算出当前的车速,其原理图如图3.10所示。
▲ 图 3.10 编码器电路原理图
3.8. 云台电源
通过舵机电源给云台舵机供电,由Open art传过来的信号来实现转向以便于识别标靶。其原理图如图3.11所示。
▲ 图 3.11云台电路原理图
第四章 软件程序设计
比赛中采用RT1064单片机作为主控芯片,其特点在于快,主频高达600MHz,对于智能车上来说相当奢侈了,所以完全不需要担心计算的速度问题,主函数的执行速度经过测试大约在1ms到1.5ms左右,因此控制周期可以大大降低。
4.1. 摄像头控制流程
软件控制流程图如图 4.1 所示。对摄像头采集图像进行预处理,之后根据图像搜索赛道边界,并计算赛道中心,由此编写程序获得所需偏差,实现转向与速度控制。
▲ 图4.1 赛道图像处理流程图
4.1.1. 赛道图像采集
使用总钻风摄像头采集赛道信息,采集回188*90大小的赛道图像,图像采集帧数设置为100,实际测试对于我们的程序来说过高的帧数并没有显著的提升,为了保证稳定性所以没有继续加大帧数。图像的曝光度和增益等参数根据现场条件灵活选取。
4.1.2. 赛道图像预处理
对赛道图像进行预处理,首先对赛道信息的部分变量进行复位,再使用大津法进行阈值计算,根据该阈值判断每个像素点的黑白。详细计算过程不作为文章重点,在此不一一给出。
计算阈值后进行隔列处理,对于图像的188列间隔抽取导出到新数组,构成一个列数减半的图像。这样做的好处是在尽可能获取赛道特征的情况下,减少所需要的计算量。实际测试中发现对于赛道特征的提取完全没有影响,故采用该方案。
4.1.3. 扫线策略
采取继承式搜线策略,首先从理论中线开始,对离车身最近的一行进行从中间往两边扫线,使用这一行的实际中线开始,作为下一行的扫线起始点,依次类推,向远处扫到发现赛道边界或者遍历完整个图像为止。这样既获得了赛道边线的数据,也获得了赛道结束的位置,可以作为赛道类型的判断依据。
向左右扫线时可以在计算出来的阈值基础上进行一定程度的宽泛,从而增强程序的适应性,然后根据连续黑点可以判断出是赛道由黑变白,从而记录为赛道边界。
4.1.4. 中线计算与补线策略
赛道正常状态下可以直接根据左右边线进行平均计算出赛道中线值,特殊情况下需要使用到补线。
补线的方式分为以下几种:
①半宽补线:通过预先固定好摄像头角度,在理想情况下测出赛道在图像中的实际宽度,然后使用数组记录该图像对应的赛道宽度数据。半宽补线时要求至少赛道左或者右边界是完整的。补线时可以直接在左或右边界的基础上,加或者减半宽数据,获得所需要的中线。在车模运行中,入环岛前、出环岛时、出入岔口时和第一次经过车库时需要用到该补线方法。
②丢线补线:图像中出现左右同时丢线的情况,有可能是十字或者赛道反光,这时图像丢线区域仅为一小部分,通过在丢线区域前后进行中线计算,然后直接两点法做直线即可。
③环岛内补线:在环岛内,因为环岛的曲率有大有小,所以直接根据常规方法计算出的中线往往不能满足环岛内的行驶要求,所以要针对环岛内单独处理。通过在环岛内取最远处的边界点,与最近行的中心点,两点法连线做直线,获得一条斜拉的直线。实际测试发现使用根据曲率拟合的曲线和斜拉的直线表现出的效果没有区别,所以采用更为简单的斜拉直线。
4.1.5. 赛道偏差计算
赛道偏差使用加权平均的方法,对每行的中线与车身理论中线作差,获得计算后的赛道偏差。通过对每行设置不同的权重,可以使得车模获得不同的前瞻,以匹配不同的速度。
4.1.6. 赛道元素识别
在此对各元素特征进行简要概述:
①环岛:一边是直线另一边丢线数目满足条件即入环,陀螺仪计满数据即出环。
②岔路:赛道宽度变大且远处中间出现黑色部分,并且左右边线斜率在限定范围内,出岔路判断条件可宽松一些。
③Tag码和车库:均需要对黑白跳变点进行计数,Tag码跳变点数目相对车库更少,且车库处出现了丢线,故可以由此分辨处二者区别。
④其他元素:不需要特殊处理。
4.2. 转向舵机的位置式模糊PD控制
舵机部分我使用的是位置式模糊PD控制。因为对于舵机来说更需要实时性,所以采取了积分分离的位置式PD控制。
模糊控制则是为了减小车模运行时的晃动程度,尽可能的忽略小偏差带来的抖动,加强车模的稳定性和不同赛道的适应性。通过不同赛道上,偏差以及偏差变化率的大小不同,使用模糊控制可以计算出不同的Kp和Kd参数,使用这些参数再计算,可以在不同条件下得到不同的转弯性能。
比如在小S弯,使用模糊控制可以尽可能地减小车模在其中的抖动,实现小S直冲的效果。在180°弯则可以获得较强的转弯性能,配合基础Kp甚至可以实现内切过弯。其问题在于十字处,因为我们没有来得及针对十字特殊处理,所以如果出现过分的斜入十字,就会有可能导致十字处左拐或者右拐。
解决的方法是加上一个略大一点的基础Kp和Kd,略大一点也只是相对更改之前,和模糊计算出来的数值对比还是很小的。进而可以在对其他赛道影响不大的情况下解决这个问题。
4.3. 驱动电机的PID控制
电机部分分两次进行控制,一次是差速位置式PD计算,一次是控速增量式PI计算。差速PD计算是为了和舵机配合,所以也使用了模糊控制,使用相同的模糊规则表进行计算,使用单独的差速系数来控制差速的幅度,最终计算出一个目标速度。差速数值的计算方法程序会在附录中给出。
算出目标速度之后,就是通过增量式的PI控制电机,此处的PI参数由于时间关系,没有调整的足够精准,导致了大幅度加减速时会有影响,下文会指出。
4.4. 出入库与停车策略
4.4.1. 出入库
出库和入库采用直接调用预先编写好的路径程序,从而保证了出库和入库的路径不变,只需要确定出入库时的车身位置和姿态即可。
出库位置摆放好即可,入库则需要根据斑马线的位置,选择何时入库,通过多次测试就能得出最合适的位置。
4.4.2. Tag码停车
比赛任务要求车模在Tag码前停车,并进行相应的识别等任务。对于高速运动的车模来说,主要难度在于快速的停车,以尽可能地减少刹车距离,保证不冲出停车区域的同时,压缩所消耗的时间。
由于刚才所说的电机控制不够强力,所以不得不加入其他的方法来更快地减速。在此使用的是电机直接全速反转来减速,效果尚佳,缺点在于不够精准,容易导致车模后退一小段距离,需要再往前走回去。另外就是堵转时间较长,容易导致驱动电路发热或烧板。
第五章 图像识别部分
5.1. 任务简述
根据比赛要求,需要在比赛过程中需要部署神经网络识别动物、水果、数字、AprilTag码。根据识别的结果进行相应的动作。
识别任务属于典型的计算机视觉,借助于人工神经网络实现。传统的图像滤波很难实现所有的视觉任务,必须部署人工神经网络。同时在此也感谢逐飞科技在赛前提供的入门思路和教程给予了我们一定的启发
5.2. 平台
由于机器学习相关的开发环境搭建十分复杂,需要安装的软件包非常多。并且对于计算机的CPU可能由于在兼容性的问题而无法运行(一个不小心开发环境还会挂掉),并且陪伴我的笔记本属实寒酸,没有独立显卡,训练人工神经网络实在是有点力不从心。只好转战Google Colab。
5.2.1. 生成和训练平台
Colab全称为Colaboratory,是免费的Jupyter运行环境,并且完全在云端运行。并且提供了全套和最新的Keras软件支持包,完美契合人工神经网络的搭建的需求。简单来说,可以在这个网站上编程,调用最新Keras API搭建模型,而不需要安装任何支持包;并且模型的训练可以借助云端的算力,而不是计算机本身的算力。其实Colab提供的算力并不多。大概相当于一个装有中低端独立显卡的笔记本电脑。不过胜在使用方便。我生成和训练人工神经网络的模型几乎都在Colab上进行。
5.2.2. 部署和运行平台
经过训练的人工神经网络需要部署在小车上,也就是需要一个平台。在我们的方案中,将人工神经网络部署在OpenArt平台上。OpenArt可以加载经过量化的人工神经网络,将传感器采集的图片送入网络中进行识别。
5.3. 模型的生成和训练
5.3.1. 模型的种类和量化
最开始时按照逐飞给出的训练模型脚本和模型实例,使用输入层的尺寸为32323,再用逐飞给出的量化脚本进行量化。
不得不说,按照这样的方案来,识别率确实是惨不忍睹。分类结果基本上根据曝光度来决定。让我在很长一段时间内对能否顺利完成比赛任务产生了怀疑。
后发现在逐飞给出的资料中,发现tflite类型的文件也可以在OpenArt上部署,并且同样的模型在量化后识别率高出nncu类型的模型很多,之后就采用了tflite类型的模型进行部署。仅仅是采用tflite模型,识别率提高了很多。
5.3.2. 数字模型的生成和训练
相较于识别动物&水果模型,识别数字相而言较为轻松。对模型和训练集的要求都不高。在准备过程中数字一直是轻松省心的任务。
数字识别模型输入层尺寸48483也就是48*48像素的RGB色彩图片。其中加入比例系数为0.6的DropOut层。
通过阅读逐飞给出的生成模型脚本,可以发现,逐飞采用的方法是Keras-Tensorflow框架。只要对Keras-Tensorflow有一定的了解就可以根据自己的需求自己动手搭建人工神经网络。其中非常重要的一点就是输入层的大小,其实也就是传感器采集图像后送入网络的图片的尺寸;逐飞给出的实例中采用32323.属实是小了。用以识别数字已经比较勉强,识别动物和水果比较吃力。
经过实际测试,以OpenArt的算力,输入层为64643的普通结构的,参数大约在600K个左右的网络,识别速度绝对能达到要求。数字模型最终采用的结构如图所示:
▲ 图5.1 数字模型结构
由于识别对象只有十个即数字09,识别率很容易验证。在实际验证后,模型的准确率往往都是95%以上。除去光照造成的影响,完全可以识别出所有的数字。并且由于模型的尺寸很小(相较于动物水果模型),识别速度也很快。大约有34 fps。总结,数字识别部分只要不和动物水果模式一起使用,就没什么大的问题。识别率也很高。
5.3.3. 动物&水果模型
相较于数字识别,识别动物&水果难度大大提高,识别对象多了两个数量级。图片承载的信息也更加繁多。数字都是白底黑字,而动物水果背景丰富,内容更加多样。当卓老师公布全部907张图片之后,我心头一紧。图片数量多,内容复杂。想要做出能精确识别这十个种类并在计算机上运行,不难。但是经过量化后可以在移动端上运行,并不容易。
最终,动物&水果模型采用了量化后的MobilNet结构。并且准备了两个版本。
▲ 图5.2 版本一
▲ 图5.3 版本二
图5.2是MobeilNet模型在Alpha 参数为0.5并且加上系数为0.6的DropOut层时的模型结构;图5.3.2是MobeilNet模型在Alpha 参数为0.75并且加上系数为0.6的DropOut层时的模型结构。后者的参数达到了1.5M个。所占存储内存达到了1.8MB。“重量级模型”。
之所以会准备两个模型,是希望比赛时可以根据赛场状况调整。“重量级”准确率高,识别速度较慢,“轻量级”正相反。两者在识别中大概会有1s左右的差距。数据上的准确率其实两者相差无几。最后都是100%。Loss值都在0.001~0.0001这个区间。
▲ 图5.4 准确率数据
实际上,Alpha系数为0.75的重量级模型,实际测试中的准确率略胜一筹。但轻量级对于OpenArt的运行资源要求更少,更快,更稳定。
5.4. 模型的优化
5.4.1. 过拟合与DropOut
过拟合是机器学习中很难避免的问题。因为在机器学习的一些模型中,如果模型的参数太多,而训练样本又太少的话,这样训练出来的模型很容易产生过拟合现象。在训练bp网络时经常遇到的一个问题,过拟合指的是模型在训练数据上损失函数比较小,预测准确率较高(如果通过画图来表示的话,就是拟合曲线比较尖,不平滑,泛化能力不好),但是在测试数据上损失函数比较大,预测准确率较低。
实际上,在模型训练结束时数据上的准确率往往有90%,经过实际测往往是达不到这个准确率的。添加DropOut层用来对抗过拟合,在我们组的方案中是行之有效的方法。DropOut的作用很简单,训练时随机屏蔽一些网络,只根据剩余的网络进行预测,频闭的网络的数量占总数量的比值就是DropOut层唯一的参数。例如,系数为0.6的DropOut层即是说训练时每次屏蔽60%进行预测。因此,同样一个模型是否添加DropOut层,对模型最终的尺寸、占据的存储空间是不影响的。
起初没有添加过DropOut层,训练数据又明显的过拟合的趋势,后来逐步了解了一些对抗过拟合的方法,添加DropOut层就是最简单的一种。最开始DropOut层的参数是0.2。后来听取了一位同学的建议,直接调整到0.6,根据这个同学的说法就是,小型的网络很容易出现过拟合现象,DropOut系数可以给的大一些。经过实际的测试,0.6的比例系数是更合适的,对抗过拟合现象效果更加明显。
▲ 图5.5 无DropOut层准确率
▲ 图5.6 无DropOut层loss
图5.4.0和图5.4.1展示了在没有DropOut层作用的情况下,一个输入层尺寸为48483的模型在训练中准确率和Loss随着训练迭代的变化情况。基本上在训练进行到第10次迭代时就应开始过拟合,后续的训练都会增加过拟合程度。
▲ 图5.7 带DropOut层准确率
可以看出,增加了DropOut层有效的缓解了过拟合的问题,第10次迭代到第20次迭代都在有稳定的降低loss提升准确率。但是过拟合问题仍然存在。第30次迭代以后训练效果都不明显。
5.4.2. 数据增强
数据增强主要用来防止过拟合,用于dataset较小的时候。之前对神经网络有过了解的人都知道,虽然一个两层网络在理论上可以拟合所有的分布,但是并不容易学习得到。因此在实际中,我们通常会增加神经网络的深度和广度,从而让神经网络的学习能力增强,便于拟合训练数据的分布情况。在卷积神经网络中,有人实验得到,深度比广度更重要。
数据增强,简单来说就是对原有的数据经进行一些变换,得到新的数据集;这些变换包括:水平位移、垂直位移、伸缩、剪切、旋转等。除此之外还有例如ZCA白化、高斯噪声等适用于数据增强的算法。
高斯噪声是根据一张图片中某一个像素点周围其他像素点的色彩值,随机的反转中心像素点的色彩值,说白了就是人为的给图像添加噪声,破坏图像原有的“规律”使模型在训练时不会朝着这些无用的“规律”的方向过拟合。
▲ 图5.9 未增加高斯噪音 图5.10 已增加高斯噪音
由图5.9和图5.10分别表示了未增加高斯噪音和已经增加了高斯噪音的图片。为了适应模型训练,原图片已经经过了一些处理,图像边缘增加紫色边框,并且将分辨率将为96*96。
Keras支持数据增强,自带一部分常用的数据变化方法,例如伸缩、平移、旋转等,但不支持添加高斯噪音,需要在制作数据集的时候手动添加高斯噪音。Keras提供的imageDataGenerator类对象可以很方便的对数据进行变换。
▲ 图5.11 文档中的部分描述
图5.11是Keras中文文档中关于imageDataGenerator类对象的部分描述。
根据实际测试的结果,数据增强是对抗过拟合,提高模型准确率的一个重要且有效大的手段。但是过度夸张的数据增强,例如旋转角度过大,高斯噪声用力过猛,平移太狠之类都会反过来降低准确率。所以,数据增强并不是越夸张越好。
5.5. 定位靶标
除了图像识别以外的任务就是,寻找空间中的紫色边框。根据AprilTag码的内容,只能确定靶标在空间的大概位置,无法确定靶标和AprilTag码的相对位置,所以需要定位空间中的靶标。
其实就是找紫框。OpenArt有很多照搬OpenMV的机器视觉算法,其中就有找到图像中黑色矩形find_rect。逐飞给出的方法也是这个办法。但是这个方法是真的拉。对背景的要求太苛刻了。根据我的实际测试,这个方法很容易找例如门等类似矩形的东西。而对紫框视而不见。
后来改为find_blob.通过调整LAB通道阈值来滤除图像中除了紫色边框之外的成分。要特别注意的就是,光照条件对LAB的阈值影响很大。
▲ 图5.12原图像
▲ 图5.13过滤后图像
第六章 RT-Thread主要应用
首先我要感谢在本次比赛中,提供RT-Thread开源库的逐飞科技和RT-Thread官方。这为我们省去了大量的移植时间,并且能够较快的根据例程,学习并上手操作系统,学会其中的各个概念,故在此特别提出感谢。
智能车的难点主要在于赛道信息的采集处理和运动控制算法,大家往往都很重视程序各模块的效果,却经常忽视掉综合程序执行的逻辑顺序和总体效果。
举个例子来说,以智能视觉组的任务量来说,如果代码风格不够好,有较多冗余代码并且沿用了此前比较流行的NXP公司的K66单片机,那么就会导致程序的执行速度出现问题,并且是会发生在各模块程序组合作用下,一般不会在单模块调试阶段发现。那么这时就是因为程序的耦合导致了问题的加剧。哪怕是性能足够强悍的RT1064单片机,也需要在编程时多加注意程序的顺序或者逻辑。避免浪费算力。
下面就针对在智能车上,如何使用RT-Thread改善代码,优化程序。并和裸机状态做对比。
6.1. 多线程实现任务的同步进行
通过参考《RT-Thread编程指南》以及官方入门教程,完全可以做到几天入门。教程相当简单明了,只要基础的概念都有那么学习起来速度飞快,而且网络上的资料也十分充足。
智能车为了方便调试,经常需要在运行期间查看各个变量的值或者是摄像头采集回的图像,问题在于需要用到串口传输数据或者屏幕显示。这两个部分都比较占用程序运行时间,在裸机状态下,程序同时只能做一件事,如果加上屏幕显示函数,那么会导致程序运行时间变为原来的至少16倍也就是24ms。
那么就需要在保证车模运行的程序正常执行的前提下,再以较低的优先级加入调试用到的程序。在裸机开发下要实现只能通过查询的方法,效率相当低下。但是在操作系统中就不同了,只需要通过最简单的信号量(不仅限于信号量),即可实现。
主要原因是RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。所以只要把重要的程序设置成高优先级并使用信号量等方式,再把调试用的程序设置为低优先级,那么就可以让单片机仅仅在空闲状态下执行调试所用的程序。
举个例子:在智能车程序中常用做法是把摄像头采集完成时产生一个信号量,用来唤醒高优先级的图像处理线程,那么低优先级的调试函数就要被挂起。在实际应用中,由于摄像头图像处理程序所需要的时间相比图像显示程序时间极短,低一个数量级以上。所以使用多线程后的效果相比裸机状态下只使用显示函数的效果几乎一致,用作观察和调试完全没有影响。再加上串口调试的发送程序,影响更是微乎其微。除此之外,还可以通过对不同线程进行分优先级以及同优先级设定不同时间片等内容,进而可以进行更高度的自定义。
由此就可以实现多线程下,任务的同步进行。相比于裸机开发,使用RT-Thread操作系统可以说是“低耦合,高内聚”,极大程度上降低了程序之间的相互影响。
使用线程时要注意的主要有三点,上下文环境、线程的状态跃迁、线程运行时间长度。在创建线程时务必要注意设计要点,在智能车程序设计中,主要是要关注第二个,线程的状态跃迁。
这里说的状态跃迁指的是线程运行中状态的变化,从就绪态过渡到挂起态。实时系统一般被设计成一种优先级的系统,如果一个线程只有就绪态而无阻塞态,势必会影响到其他低优先级线程的执行。所以在进行线程设计时,就应该保证线程在不活跃的时候,必须让出处理器,即线程能够主动让出处理器资源,进入到阻塞状态。这需要设计者在设计线程的时候就明确的知道什么情况下需要让线程从就绪态跃迁到阻塞态。
6.2. 任务间的信息传递与通讯
在嵌入式系统中运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),它们访问的资源有时需要互斥(一个时刻只允许一个线程访问资源),它们之间有时也要彼此交换数据。这些需求,有的是因为应用需求,有的是多线程编程模型带来的需求。
操作系统必须提供相应的机制来完成这些功能,我们把这些机制统称为进(线)程间通信(Internal Process Communication IPC),RT-Thread中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列。
6.2.1. 信号量的应用及智能车中的生产消费问题
在智能车软件设计中,常常需要让中断和其他程序间传递信息。在裸机开发下的做法一般都是设立全局变量的方式,然后使用查询的方法来达成信息的传递。这样做虽然可行,但是带来的是大量的全局变量。这不仅导致了程序的可读性下降,而且大大增加了程序之间互相耦合的现象,很容易出现“牵一发而动全身”的结果。
那么在使用RT-Thread操作系统的情况下,就可以灵活使用IPC机制来避免这些情况的出现,并且可以更好更简洁的传递有效信息。在这些机制中智能车最常用的就要属信号量了。
▲ 图6.1信号量工作示意图
信号量工作示意图如上图6.1所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应信号量对象的实例数目(资源数目),假如信号量值N,则表示共有 N 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再请求该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。
上文中提到过,摄像头采集完成会产生一个信号量,从而确保图像处理程序可以得到执行。那么在图像处理完成之后,需要立刻让车模作出反应,利用刚才计算好的数据进行姿态控制,由于电机必须相同时间间隔控制,所以就要对舵机进行控制。可以采用的方法是:图像处理程序执行结束后,再产生一个信号量,去执行更高优先级的舵机控制程序,让车模的反应更加迅速,并且避免了无效图像的产生。
例如:在裸机开发下,如果把舵机控制程序放到中断里。当程序执行速度过快时,容易出现图像采集加处理完成之后,计算出的数据还没有来得及调用,就开始了下一帧图像的采集和处理。这会导致车模错过一帧图像并且计算出的数据和当前控制效果关联性差。这也是为什么不能一味的追求快速的图像处理而忽略掉作为根基的控制效果。
这个问题其实就是生产者消费者的典型案例,图像处理程序是生产者,舵机控制程序是消费者;从另一方面看,摄像头采集程序是生产者,图像处理程序和舵机控制程序又是消费者,所以严格把握好程序执行的逻辑关系相当重要。程序执行的逻辑出了问题哪怕有再强的硬件和算力,都得不到最好的控制效果。
使用信号量就能解决掉这个问题,因为从根本上来说,这个问题主要就是舵机控制程序要求必须和图像处理程序保持同步。且在数据没用掉的时候,不允许开始新一轮的采集与计算,也就是互斥的关系。只需创建几个信号量,通过采集和释放信号量就能解决问题,具体的解决方案如图6.2。
▲ 图6.2信号量解决生产消费问题流程图
信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程,中断与线程的同步中。在智能车上往往就是在线程同步时使用到的。
线程同步是信号量最简单的一类应用。例如,两个线程用来进行任务间的执行控制转 移,信号量的值初始化成具备0个信号量资源实例,而等待线程先直接在这个信号量上进行等待。
当信号线程完成它处理的工作时,释放这个信号量,以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:信号线程完成它自己的工作,然后通知等待线程继续下一部分工作。
6.2.2. 邮箱的应用及可行的优化方案
邮箱服务是实时操作系统中一种典型的任务间通信方法,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的4字节内容(针对32位处理系统,指针的大小即为4个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如图6.3所示,线程或中断服务例程把一封4字节长度的邮件发送到邮箱中。而一个或多个线程可以从邮箱中接收这些邮件进行处理。
▲ 图6.3邮箱工作示意图
在智能车软件设计中,经常用到地址的传递,或者是变量的传递,之前所说的信号量就不能满足要求了,而邮箱恰巧可以实现该要求,而且4字节内容也足够传送一些关键数据,数组等则可以采用传送地址的方式来实现。
在智能车程序中仅仅使用了最简单的变量传递功能,实际使用中还有很多部分可以使用邮箱来简化程序,只是限于时间问题,未对程序完成优化,下文会写出具体的改进方案。
例如在智能车上用到的是用邮箱传递变量,控制蜂鸣器工作的时间在裸机状态下,要在控制蜂鸣器定时工作的前提下不影响主程序的执行,就必须通过在定时器里查询的方式控制蜂鸣器的工作与停止。这样必然会增加程序的复杂度和耦合程度。
但是使用邮箱的方法传递数据,可以直接使用邮箱传递的数据,对单独的蜂鸣器线程进行控制、延时等操作,通过线程间的切换来提高工作效率。
当然,在实际应用中,灵活使用邮箱来在中断和其他程序之间传递变量,这样的方法远远好于使用查询全局变量的方法,能大大降低程序之间的相互作用,且轮询的方法并不适用于对时间控制要求较高的情境下,那么邮箱就能保证程序在时间上的稳定性。
因为邮箱是一种简单的线程间消息传递方式,在RT-Thread操作系统的实现中能够一次传递4字节邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数(邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是4字节,所以邮箱能够用于不超过4字节的消息传递,当传送的消息长度大于这个数目时就不能再采用邮箱的方式。最重要的是,在32位系统上4字节的内容恰好适合放置一个指针,所以邮箱同时也适合那种仅传递指针的情况。
所以通过指针的形式,传递结构体,数组等等都是没有问题的,也就实现了任何形式数据的传输。在智能车中可以取代大多数的消息传递,中断中关于电机控制部分的大量控制标志都可以转为使用邮箱来代替。
6.2.3. 中断服务程序及其操作系统中接口函数
在中断程序中,显然不能想当然的再按照正常状态下进行操作。当中断产生时,处理机将按如下的顺序执行:
• 保存当前处理机状态信息
• 载入异常或中断处理函数到PC寄存器
• 把控制权转交给处理函数并开始执行
• 当处理函数执行完成时,恢复处理器状态信息
• 从异常或中断中返回到前一个程序执行点
在RT-Thread操作系统中提供了关于中断的接口函数,包括开关中断,屏蔽中断,装载中断等函数,使用起来极为方便。
在智能车中主要用到的是其中与OS相关的中断接口。当整个系统被中断打断,进入中断处理函数时,OS需要知道当前已经进入到中断状态。针对这种情况,RT-Thread操作系统提供了两个函数:void rt_interrupt_enter(void);以及void rt_interrupt_leave(void);
rt_interrupt_enter函数用于通知OS,当前已经进入了中断状态;rt_interrupt_leave函数用于通知OS,已经离开中断状态。通常来说,OS需要知道这样的运行状态,这样在中断服务例程中,如果调用了OS相关的调用,OS好及时调整相应的行为,例如进行任务切换时应该采取中断中任务切换的策略,而不是立即进行切换。但是如果中断服务例程很显然、很必然地,它不会去调用OS相关的函数,这个时候,也可以不调用rt_interrupt_enter/leave函数。但是显然的,为了保证程序的稳定性,在没有其他影响因素的前提下应当每次都添加该组接口函数。
在智能车程序中多次使用到该接口函数,保证RT-Thread操作系统能够正常执行中断程序,避免出现意外情况。
6.3. 综合应用
第十六届智能车比赛任务相对十五届来说,任务更多更难。运行两圈考验车模的稳定性,并且元素数量多,也对各个元素任务的完成要求更高。那么由此来看,稳定是第一要求,但是智能车比赛同时还是竞速比赛,所以也要注意速度方面的问题。那么在高速运行状态下的车模,就需要足够快速的对赛道任务做出反应,程序执行的效率和代码的逻辑顺序也就不能出任何问题。
在裸机状态下,面对如此大的工程文件,很难做到完全不出逻辑问题,并且执行效率也不能得到保证。比如我在使用RT-Thread操作系统之前,就完全没有考虑过使用屏幕显示函数对于主程序带来的影响。进而导致主程序执行速度过慢,经常错过一场图像,使得摄像头的帧数失去实际意义,然后程序的执行受限于主程序的执行速度,显然是严重的逻辑问题。
那么在使用上操作系统后,通过使用操作系统测试后发现,屏幕显示程序对于整体的影响过大,进而使用上文提到的方法对其进行解决,使得程序执行速度大幅提升,车模运行速度在控制算法不变的前提下也大大提升,从2m/s直接提升至2.6m/s。
此外,由于智能视觉组相比其它组别要多一个识别的任务,所以对车模来说要求大幅度的加减速,那么加减速性能必然会随着车模运行速度的提升而变差。就需要特别的对于该任务进行车模控制。裸机状态下为了避免在大幅度减速的过程中舵机的抖动,不得不使用一些开环的方案;同时加减速只使用PID效果不佳,也使用了一些开环方法。虽然这些做法可以使用,但也降低了车模运行的稳定性,虽然一般情况下都可以正常完成,但是多次运行下还是会出现预期之外的错误,这就需要进行优化。
使用操作系统的环境下则可以对多任务分置在不同线程,使得车模在完成不同任务时,可以更高效实时的执行对任务的处理程序。
所以综合来说,使用RT-Thread操作系统相比此前裸机开发状态下,主要的优势就在于程序的管理能力大大增强,使得程序在不同情境下的应对能力也更强大。其原因主要是出于RT-Thread操作系统的几个特点:
①、任务/线程调度
②、任务同步机制
③、任务间通信机制
④、时间管理
⑤、内存管理
⑥、设备管理
以上除设备管理外所有的功能都在智能车工程中得到了体现,都得到了合理的应用,并且大大提高了程序的执行效率和稳定性,起到了去耦合的作用,保证了程序的逻辑顺序正确。而这些在裸机开发下都是不能得到保证的,但是智能车对稳定性和执行效率的要求却是相当严格的,所以使用RT-Thread操作系统对于比赛任务的完成和程序的设计来说起到了极大程度上的帮助。
第七章 基于RT-Thread的调试
7.1. 调试工具
调试RT-Thread操作系统主要用到的软件工具是IAR,VOFA+。硬件上除去本身车模的硬件之外,还使用到了来自逐飞的DAP-LINK和无线串口模块。
其中IAR用作程序的编写,用来编译工程文件,同时进行在线调试等,均通过DAP下载器进行配合,下载并在线观察参数,此部分不作为重点,在此不详细说明。VOFA+调试助手则配合无线串口使用,用来观察RT-Thread的命令行,极为方便,且还包含了许多组件,可以方便发送指令,界面如图7.1。
▲ 图7.1 VOFA+串口调试助手界面
7.2. 调试过程
调试过程主要分为两个模块,分别是对车模运动过程中各项参数指标的调试,以及对程序整体结构和操作系统的调试。
7.2.1. 调试车模参数
对车模运动参数的调试我选择通过VOFA+串口调试,使用串口数据绘制波形组件,构成虚拟示波器,以观察速度曲线等数据。对于车模运动过程的参数调节,如PID参数等需要经过实际仔细调节的参数,效率大大提升,很大程度上减少了工作量以及提高了效率。同时对车模运动的状态也可以使用串口打印的方式,实时观测。绘制波形图的界面如图7.2所示。
▲ 图7.2 VOFA+波形图界面
调试的重心其实主要在对于RT-Thread的调试和程序框架的修改。因为程序的好坏虽然必定受到参数的影响,但是参数只能起到锦上添花的作用,具体程序表现的还是由程序的结构以及操作系统的配置为根本的。
7.2.2. 调试操作系统
程序的逻辑操作在上文第四章中已经分模块的列举了,这里就不再一一赘述了,这部分的重点在于对于RT-Thread操作系统的调试以及一些系统参数的理解和修改。
首先是对于rtconfig.h文件的个人向修改,其中部分参数我经过搜索后添加了注释,便于之后对应问题进行修改。不得不指出,在学习RT-Thread内核的过程中,官方论坛和其他网络资源是相当的丰富,且开源风气极好,官方的技术支持也很棒,大大缩短了我学习内核部分所花的时间。由于比赛准备时间并不是很长,所以需要尽可能的快速学习,经过简单的理解后将其实践运用,所以非常感谢官方论坛等提供的支持。
回归正题,在操作系统配置中,操作系统时钟周期的宏定义RT_TICK_PER_SECOND一般可选择为1000或者100,经过考虑后认为,RT1064的主频为600MHz,性能足够,故选择1000,从而确保了操作系统的执行精度更高,更可控。另外关于优先级等尚且没有影响的配置,保留默认数值即可,无需改动,部分配置如图7.3所示:
▲ 图7.3 rtconfig.h文件中部分参数设置
其中图7.3中关于main线程的宏定义参数设置。通过观察IAR生成的map文件可以得知,程序所占用的内存空间如图5.4所示,而RT1064所提供的资源为:4MB Flash,1MB SRAM,32KB I-Cache,32KB D-Cache,且所使用的逐飞1064核心板板载了一颗32MB的SDRAM。
▲ 图7.4 .map文件关于内存的统计数据
由此可知占用的内存空间远小于剩余空间,为防止出现空间不足的问题,故把堆栈尺寸设置为8KB,虽然事实证明不需要修改此处,但也为问题出现时提供了解决思路。并且在图7.3中还可以修改main线程优先级,使用还是比较方便快捷的。
7.2.3. 使用操作系统联合调试及错误解决
使用操作系统在调试时,可以直接通过命令行,实时的发送指令到MCU使其更方便调试。发生错误时也可以使用命令行,实时观测打印出来的错误信息,进而发现问题原因,定位出错位置,也很大程度上提高了解决错误的效率。
通过调用MSH_CMD_EXPORT的宏,可以用来定义想通过命令行调用的函数导出到msh中。把该段代码加入到系统中任意可编译的文件中,并进行编译,即可完成导入msh。在之后的调试过程中,只需要在命令行中使用这里自行加入的命令,就能直接调用相关的函数。使用形式如图7.5所示。
▲ 图7.5向MSH导出命令
使用无线串口观察命令行中打印的错误信息,也是排查问题的重要方法。在车模的程序编写时,难免会有因为大意疏忽,导致出现如数组越界、堆栈溢出、内存溢出等问题。这类问题仅看实际车模运行效果很难发现问题出现的位置,但是在RT-Thread操作系统中,程序发生异常时进入Hard Fault中断服务程序,可以用串口观察错误信息。在车模调试过程中,由于出现了该问题,如图7.6所示:
▲ 图7.6串口打印错误信息
首先在官方论坛查询解决方案,别人的解决方法如图7.7:
▲ 图7.7官方论坛解决方法
经验证后确定该解决方案无效,进而通过咨询官方技术人员,获得新的解决方案。他建议通过CmBacktrace库对于错误位置进行定位,使用该方法,进而发现问题出现在了一个数组越界了,修改了程序内容后,解决问题。经过此问题我认识到了这类问题解决的方法,使用CmBacktrace库可以快速定位问题位置。在此基础上如果没有发现问题原因,检查代码的运行环境,检查指令、数据、地址和栈使用情况,其中栈使用情况最要关注,并且数组要做越界访问限制,这也需要引起重视。
第八章 结论
8.1. 车模制作总结
车模制作过程中,我们严格按照比赛要求选择传感器种类、数量,绝对符合比赛要求。
整体车模的搭建过程中,每一个部分都是经过慎重考虑和多次测试后,才确定的最终方案。舵机的倾角选择上,经过了大量的资料查找才选择了合适的角度。关于电机和编码器部分的齿轮啮合也是经常修整,否则因为长时间的运行会导致位置的改变,为避免齿轮之间咬合出现问题,就要时常调节。最重要的还要数摄像头的角度选择与固定,我们所选择的方案摄像头一共有三段可调,分别是绕x轴旋转、绕y轴旋转以及沿y轴的平移,这就导致我们要尽快的确定三个自由度的位置并且不能再轻易改动,于是有对其进行了大量的测试,并最终确定了一套最可靠的位置,然后将其固定。
与此同时,我们也关注车模搭建过程中,对于车体的重心位置带来的改变,所以也尽可能地将车模的重心降低,以增加其过弯性能。由于智能视觉组规则上不对尺寸作出限制,故我们选择的车模的摄像头高度为330mm,因此也对其做了辅助固定,增加了运动中摄像头的稳定性,避免了出现x轴或y轴方向的抖动。
车模技术指标:
- 车模基本参数 长 310mm
- 宽 290mm
- 高 360mm
- 重量(带电池) 2650g
- 功耗 空载 10W
- 带载 大于12W
- 电路电容总容量 2200uF
- 传感器 编码器 2个
- CMOS摄像头 1个
- 工字电感 4个
- 陀螺仪 1个
- 光电管 2个
除了车模原有的驱动电机、舵机之外伺服电机个数 2个
- 赛道信息检测 视野范围 55cm*110cm
- 检测精度 5mm
- 频率 100Hz
8.2. 操作系统功能及程序应用总结
在整个智能车工程中,主要应用了RT-Thread操作系统的的IPC(线程间通信)机制,实现了很多裸机开发下,不方便或不能实现的操作。通过信号量、邮箱等方法,保证了线程间的同步和优先级的正确。还解决了图像处理带来的生产消费问题,提高了程序执行的效率。在使用了RT-Thread操作系统之后,程序的耦合程度大幅度降低,降低了出现问题的可能,程序的秩序也更清晰,增强了可读性,解决问题也更简单。
并且在调试的过程中,RT-Thread操作系统所提供的内容也相当便捷。使用系统自带的msh命令行外加自行导入可以更方便的在无线串口搭配下,用命令行随时执行目标程序。同样是使用无线串口,可以观察需要观测的数据与出错时打印的错误信息,以及线程执行的实时情况,从而做到让程序的执行变得可观测且可介入。再加上使用多线程后即可随时使用屏幕观测车模采集的图像等,使用屏幕观测图像的效率又远高于使用无线串口,也就节约了调试时间。
当程序出现问题时,可以分析出错的地方,具体是逻辑关系上的错误,还是数据或者语句的操作等出现问题。如判断语句使用错或是浮点数据运算的字节对齐出错等,完全属于两类问题。前一种可以通过在线调试,推车在出错位置看相关变量的数值关系等即可,第二种问题则需要在第一种的基础上对问题进行排查和定位,使用我上文所说的CmBacktrace库定位后一般就可以快速解决。
8.3. 问题展望及感想
虽然我们目前的车模已经可以较快速的完成其他赛道任务,但是在比赛的时候还是因为没有正确识别出地上的Tag码,导致一直没有正常完赛,最终通过补赛完赛的。所以这也恰巧说明了我们的车模现在识别赛道信息的适应性还是不够,所以要优化识别Tag码的方案,当时也只差这一步,就能以很好的成绩完成比赛甚至名列前茅,还是要考虑到各个细节,把每一处都优化的比较可靠。
除去赛道元素的识别,在车模的运动控制上其实还有很大的优化空间。目前车模运行路径虽然已经比较可靠了,但是电机的控制是短板。通过串口调试助手的虚拟示波器观测到的实际电机速度并不能很好的跟随目标速度,所以还需要精调电机PID,并且修改电机控制部分的程序结构。最能看出问题的部分是在车库和Tag码的急刹车,在急刹车时我们的程序总是会出现车模倒退一小段距离的问题,但是在比赛中发现别的队伍是可以实现灵活刹车的,所以精调PID再优化程序框架是可以实现该效果的。
除了以上提到的,整个程序中还有很多可以优化地方,尤其是全局变量的数量,还可以少使用很多,使用IPC即可代替它们。程序的元素状态也可以使用有限状态机代替当前的方案,识别率和识别速度也还有提升的空间,车模的硬件稳定性以及结构的缺陷等这些都会在之后进行逐级优化。
在这几个月的备战过程中,场地和经费方面都得到了学校和学院的大力支持,在此特别感谢一直支持和关注智能车比赛的学校和学院领导以及各位指导老师、指导学长,同时也感谢比赛组委会能组织这样一项有意义的比赛。也感谢RT-Thread官方能够联合逐飞,推出移植好的开源库和例程,为我们的学习助力,大大的节约了学习的门槛,让我们都能够有机会知道、了解和深入的学习这样一个操作系统,也为我们开拓了新视野。同时RT-Thread的开源精神也深深的影响了我们,让我们理解了前辈们开源的良苦用心。
十五届的比赛因为疫情我们学院并没有参加,所以我们已经准备了相当长一段时间了,但是因为比赛前没有充分的考虑可能出现的赛道情况,所以现场并没有完赛,虽然可惜但是也是因为自身原因,还需要更多的努力。在补赛中去掉了Tag码元素和识别,我们以很快的速度完成了其余的赛道任务,也算是勉强给了自己一个交代。这辆小车作为我们小组努力了这么长时间的结晶,无论还有没有继续走下去的机会,这份经验和智能车精神都会伴随我们一生,时刻提醒着我们不忘初心,砥砺前行。
参考文献
[1] 卓晴,黄开胜,邵贝贝.学做智能车 [M].北京:北京航空航天大学出版社.2007.
[2] 王淑娟,蔡惟铮,齐明.模拟电子技术基础 [M].北京:高等教育出版社.2009
[3] 张军.AVR单片机应用系统开发典型实例 [M].北京:中国电力出版社,2005.
[4] 张文春.汽车理论 [M].北京.机械工业出版社.2005.
[5] 殷剑宏,吴开亚.图论及其算法 [M] .中国科学技术大学出版社,2003.
[6] 夏克俭.数据结构及算法 [M] .北京:国防工业出版社, 2001.
[7] 邵贝贝.单片机嵌入式应用的在线开发方法 [M].北京.清华大学出版社.2004.
[8] 蔡述庭.“飞思卡尔”杯智能汽车竞赛设计与实践 [M].北京:北京航空航天大学出版社. 2012.
■ 附录A核心部分程序源码
#include "headfile.h"
rt_sem_t camera_sem;
uint8 Ring_Number = 0;//环岛数量
uint8 Fork_Number = 0;//分岔路口
uint8 Starting_Line_Times = 0; // 起跑线经过次数
uint8 Tag_Number = 0;//Tag码数量
uint8 Run_times = 2;//运行圈数
char Speed_Set = 1; //速度档位
uint8 Crossroad_Number = 1;//十字路口数量
void Number_init(void)
{
Ring_Number = 2;//环岛数量
Fork_Number = 1;//分岔路口
Tag_Number = 2;//Tag码数量
}
int main(void)
{
int32 my_Journey = 0;
camera_sem = rt_sem_create("camera", 0, RT_IPC_FLAG_FIFO);
/********************** 配置 优先级 ************************/
NVIC_SetPriority(GPIO2_Combined_0_15_IRQn,0);
NVIC_SetPriority(CSI_IRQn,1);
NVIC_SetPriority(PIT_IRQn,5);
display_init();
mt9v03x_csi_init();
seekfree_wireless_init();
buzzer_init();
button_init();
MotorInit();
pwm_init(S_MOTOR_PIN, S3010_HZ, S3010_MID);
qtimer_quad_init(QTIMER_2,QTIMER2_TIMER0_C3,QTIMER2_TIMER3_C25);
qtimer_quad_init(QTIMER_3,QTIMER3_TIMER2_B18,QTIMER3_TIMER3_B19);
qtimer_quad_clear(QTIMER_2, QTIMER2_TIMER0_C3);
qtimer_quad_clear(QTIMER_3, QTIMER3_TIMER2_B18);
IncPID_Init(&Left_MOTOR_PID,Left_MOTOR); //电机左 PID参数初始化
IncPID_Init(&Right_MOTOR_PID,Right_MOTOR); //电机右 PID参数初始化
IO_Init();
AD_Init();
simiic_init();
//icm20602_init();
mpu6050_init();
openart_mini();
pit_init();
pit_interrupt_ms(PIT_CH0, 10);
pit_interrupt_ms(PIT_CH1, 10);
pit_interrupt_ms(PIT_CH2, 10);
pit_interrupt_us(PIT_CH3, 10);
EnableGlobalIRQ(0);
while (1)
{
//等待摄像头采集完毕
rt_sem_take(camera_sem, RT_WAITING_FOREVER);
Count_flag = 1;
tim_count = 0;
mt9v03x_csi_finish_flag = 0;
Image_Get();
Threshold_Get();
Image_Handle(image);
Track_Line();
Point = Dynamic_Point_Weight(Point_Num); //计算图像加权偏差并计算前瞻,如图中线
if(StartLine != InGarage && StartLine != End && StartLine != FinishFirst && Tag == NoTag && Fork != FindFork)
SteerControl();
if(Go_flag) // 出车库
{
else if(StartLine == InGarage)// 进车库
{
if(Tag != NoTag){
Get_AD_Value();
AD_Val_handle();
AD_guiyi();
AD_SteerControl();
}
Send_flag = 1;
}
}
int go(void)
{
int stop(void)
{
int artreset(void)
{
MSH_CMD_EXPORT(go, Set Go_flag);
MSH_CMD_EXPORT(stop, Set Stop_flag);
MSH_CMD_EXPORT(artreset, ReSet Openart);
void buzzer_init(void)
{
rt_thread_t tid;
gpio_init(BUZZER_PIN, GPO, 0, GPIO_PIN_CONFIG);// 初始化为GPIO浮空输入 默认上拉高电平
buzzer_mailbox = rt_mb_create("buzzer", 5, RT_IPC_FLAG_FIFO);
tid = rt_thread_create("buzzer", buzzer_entry, RT_NULL, 1024, 20, 2);
if(RT_NULL != tid)
rt_thread_startup(tid);
}
void button_init(void)
{
rt_timer_t timer1;
gpio_init(KEY_1, GPI, GPIO_HIGH, GPIO_PIN_CONFIG);//初始化为GPIO浮空输入
gpio_init(KEY_2, GPI, GPIO_HIGH, GPIO_PIN_CONFIG);
gpio_init(KEY_3, GPI, GPIO_HIGH, GPIO_PIN_CONFIG);
gpio_init(KEY_4, GPI, GPIO_HIGH, GPIO_PIN_CONFIG);
key1_sem = rt_sem_create("key1", 0, RT_IPC_FLAG_FIFO);//创建按键的信号量
key2_sem = rt_sem_create("key2", 0, RT_IPC_FLAG_FIFO);
key3_sem = rt_sem_create("key3", 0, RT_IPC_FLAG_FIFO);
key4_sem = rt_sem_create("key4", 0, RT_IPC_FLAG_FIFO);
timer1 = rt_timer_create( "button" , button_entry , RT_NULL , 20 , RT_TIMER_FLAG_PERIODIC);
if(RT_NULL != timer1)
rt_timer_start(timer1);
}
void PIT_IRQHandler(void)
{
float data[10];
rt_interrupt_enter();
if(PIT_FLAG_GET(PIT_CH0))
{
/******** 赛道检测 *********/
Track_Line();
/******** 速度控制 *********/
Adjust_Speed();
/******** 变速控制 *********/
Control_Speed();
/******** 差速控制 *********/
CS_Control();
/******** 编码器速度采集 *********/
speed_measure(); //测速及停车
/******** 电机增量pid控制 *********/
if(Stop_flag)
Left_High_Speed = 0,Right_High_Speed = 0;
if(Journey_Record == 1)
{
Left_MOTOR_Journey += Left_MOTOR_Speed;
Right_MOTOR_Journey += Right_MOTOR_Speed;
}
if(Go_flag)
{
Left_High_Speed = 150;
Right_High_Speed = 150;
}
MOTOR_Control();
PIT_FLAG_CLEAR(PIT_CH0);
}
if(PIT_FLAG_GET(PIT_CH1))
{
MPU6050_GetData(&GYRO, &ACC);//获取6050数据
Data_Filter();//滤波
Get_Attitude();//姿态解算
PIT_FLAG_CLEAR(PIT_CH1);
}
if(PIT_FLAG_GET(PIT_CH2))
{
data[0] = Left_MOTOR_Speed;
data[1] = Right_MOTOR_Speed;
data[2] = Left_High_Speed;
data[3] = Right_High_Speed;
data[4] = Left_MOTOR_Duty;
data[5] = Right_MOTOR_Duty;
data[6] = Track_Line_Num;
data[7] = Real_P;
data[8] = Real_D;
data[9] = mpu_acc_x;
seekfree_wireless_send_buff((char *)data, sizeof(float) * 10);
seekfree_wireless_send_buff(tail, 4);// 发送帧尾
PIT_FLAG_CLEAR(PIT_CH2);
}
if(PIT_FLAG_GET(PIT_CH3))
{
PIT_FLAG_CLEAR(PIT_CH3);
}
__DSB();
rt_interrupt_leave();
}
● 相关图表链接:
- 图1.1 阿克曼转角图
- 图 3.1 3.3V 稳压电路原理图
- 图 3.2.1 5V 单片机电源原理图
- 图 3.2.2 5V 稳压电路原理图
- 图 3.2.3 5V 外设电源原理图
- 图 3.3 舵机电源原理图
- MC34063升压电路原理图
- 图 3.4 12V升压原理图
- 图 3.5 H桥驱动电路原理图
- 图 3.6 74lvc245电路原理图
- 图 3.7 运放模块电路原理图
- 图 3.8 激光打靶模块电路原理图
- 图 3.10 编码器电路原理图
- 图 3.11云台电路原理图
- 图4.1 赛道图像处理流程图
- 图5.1 数字模型结构
- 图5.2 版本一
- 图5.3 版本二
- 图5.4 准确率数据
- 图5.5 无DropOut层准确率
- 图5.6 无DropOut层loss
- 图5.7 带DropOut层准确率
- 图5.9 未增加高斯噪音 图5.10 已增加高斯噪音
- 图5.11 文档中的部分描述
- 图5.12原图像
- 图5.13过滤后图像
- 图6.1信号量工作示意图
- 图6.2信号量解决生产消费问题流程图
- 图6.3邮箱工作示意图
- 图7.1 VOFA+串口调试助手界面
- 图7.2 VOFA+波形图界面
- 图7.3 rtconfig.h文件中部分参数设置
- 图7.4 .map文件关于内存的统计数据
- 图7.5向MSH导出命令
- 图7.6串口打印错误信息
- 图7.7官方论坛解决方法
最后
以上就是强健发夹为你收集整理的智能车竞赛技术报告 | 智能车视觉 - 中原工学院 - 逐鹿 - 分母队 第一章 引言 第二章 车模机械设计 第三章 硬件系统设计及实现 第四章 软件程序设计 第五章 图像识别部分 第六章 RT-Thread主要应用 第七章 基于RT-Thread的调试 第八章 结论 参考文献 的全部内容,希望文章能够帮你解决智能车竞赛技术报告 | 智能车视觉 - 中原工学院 - 逐鹿 - 分母队 第一章 引言 第二章 车模机械设计 第三章 硬件系统设计及实现 第四章 软件程序设计 第五章 图像识别部分 第六章 RT-Thread主要应用 第七章 基于RT-Thread的调试 第八章 结论 参考文献 所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复