学 校:南京邮电大学? 队伍名称:删库砸车跑路队 参赛队员:刘乐????? 孙锐????? 甘翠????? 带队教师:江兵????? 刘烨?????
?
第一章 方案设计
??本章主要介绍智能汽车系统总体设计方案,在后面的章节中将整个系统分为机械结构、电路设计、程序设计三部分对系统进行深入的介绍和分析。
1.1 系统方案的选择
??按照竞赛规则规定,我们选用C车车模,使用rt1064作为控制器,同时采用总钻风MT9V032摄像头进行主要道路识别,OpenART mini进行特殊元素识别。
1.2 系统方案的设计
??按照竞赛规则规定,本智能汽车系统采用恩智浦的rt1064单片机作为核心控制单元, 总钻风MT9V032摄像头采集赛道图像信息,OpenART mini采集特殊元素,返回到单片机用作控制智能车速度与转向。Ips200屏幕用于显示参数和摄像头采集到的图像。主控输出 PWM 波控制电机的转速和舵机打角以驱动智能车。由于是C车车模,所以需要使用程序差速控制,保证动力轮内侧轮胎的磨损均匀。为了控制的更加精细,我们使用编码器作为速度传感器,使用PID算法利用编码器返回的信号闭环电机的转速。
??根据以上系统方案设计,智能车共包括七大模块:rt1064主控模块、摄像头模块、特殊元素识别模块、电源模块、电机驱动模块、速度检测模块和辅助调试模块。Rt1064主控模块作为整个智能汽车的大脑,将摄像头,编码器等传感器的信号进行处理,将处理后的信息通过算法控制智能车完成相应的任务。
1.3 小结
??本章重点分析了智能汽车系统总体方案的选择,并介绍了系统的总体设计,简要地分析了系统中各模块的作用。
?
第二章 智能车机械结构
??本赛题使用的车模为C车车模,机械结构对赛车在赛道上的表现有着较大的影响,因此对机械结构的调节是智能车制作过程中不可忽视的一环。
2.1 智能车结构
??在正确组装车模的基础上,为获得最好的车模性能,我们小组在改装时力求重量轻、重心低、结构标准且坚固,大量采用了3D结构打印、标准件固定、一体化电路板并采用更轻的高倍率2S锂电池供电,各电路连接也采用更标准的一体化接口,基本摒弃了热熔胶带来的不稳定因素。最终我们使整车(带电池)重量控制在了1kg以内并且获得了超低的重心。
▲ 图2.1.1 参赛智能车
?
??3D打印结构包括:摄像头整体支撑结构、电池固定底座、斜撑碳纤维杆固定结构、车首二维度舵机云台、OpenARTmini与激光管固定结构。
▲ 图2.1-2 摄像头底座 图2.1-3 二维度舵机云台
2.2 小结
??机械结构性能的好坏是关乎整个智能汽车速度最高极限的决定性因素之一。电池尽量安装在靠近底盘处,由于模型车的自身特点,模型车底盘可利用空间很小,硬件组成员改进了电路板形状,使电路板和模型车的底盘形成了较为完美的契合。在实际操作中发现,往往程序没有改动,而对机械结构进行改良之后速度又上了一个台阶,这就证明了机械结构不可忽视的重要性。 ? ?
第三章 智能车电路设计
??硬件电路是程序的载体,硬件电路的好坏也是智能汽车性能好坏的关键因素。下面从主板电路,驱动板电路,运放电路,键盘电路四方面对整车电路进行分析。
3.1 一体化电路板设计
??一体化电路板设计之初便力求轻量化与极致的性能,便将电源管理、电磁检测电路、驱动、人机交互部分全部集成在一块双面的PCB板上,考虑到RT1064的封装便使用逐飞提供的核心板。各项元件均选择在合适范围内的最小封装以便集成。
- 电源管理:一路驱动分路、一路6V舵机电源降压、一路5V降压、两路3.3V降压;
- 驱动模块:两路全桥驱动、四通道缓冲隔离芯片;
- 电磁检测电路:四通道AD运放及可调电阻端子;
- 人机交互部分:2寸IPS屏幕、RGB可控指示灯、蜂鸣器电路、按键、串口;
??其余部分包括各通信串口、摄像头接口、编码器接口、电机接口等。 ??最终PCB板图如下图所示,主控板整体原理图见附录A。
▲ 图3.1-1 最终PCB板图
3.2 小结
??硬件电路是模型汽车系统的必备部分。只有稳定的硬件电路才能保证程序的正确控制。为此,我们在设计电路之时,考虑了很多问题,采用了模拟部分与数字部分隔离等措施。我们的硬件电路的设计思想是在保证正确检测信号的前提下,尽可能精简电路。 ? ?
第四章 智能车程序设计
4.1 图像二值化算法
??我们队的图像处理是在二值化化的图像上进行的,其好处就是运算速度快。但在智能车的赛道上经常遇到赛道光照不均匀的情况(光照实际上很难做到均匀),因此用固定阈值跑完全程是非常不稳定的做法,于是我们想寻找一个方法,能够自适应的设定阈值,在这个过程中,我们发现了运用比较广泛的OTUS算法。经过测试,OTUS算法能够解决视野中小范围反光的情况,给摄像头加上偏振片搭配OTUS算法效果最佳。下面介绍大津算法的原理。
??大津算法实际就是分类。简单用概率统计的方法来描述,对于一组数据T,数据个数为n,其均值为:
μ=E(X)(以此作为期望)
??那么这组数据离散程度可以用方差来表示:
??D(X)=∑_(i=1)n?〖P_I?〖(x_i-μ)〗2 〗
??如果单个的数据其越偏离于中心,那么,其方差值也就越大。
??图像的阈值化处理,就是将图像分为两个部分,高于阈值的部分,和小于阈值的部分。(暂不考虑多阈值的情况)。那么,如果将图像的每一个像素点的强度作为一个数据集合中的单元,那么,阈值化就相当于是一个二分类的问题。 ??假设整个图像的均值为m,只有两个数据,m1,m2,考虑着两个数据的离散程度,同样可以用上面的方差公式。方差越大,这两个类也就越离散,分的越开。就像考试一样,分差越大,说明学习努力程度差距越大。
??所以,我们求这个阈值时,就希望在阈值分割后,两个类中,每一个类都趋近于一个值,同时远离另一个值。 ? ??大津算法的详细步骤下如图所示:
▲ 图4.1-1 大津算法详细步骤
4.2 循迹算法
??如下图所示,从图像最下侧往上遍历,从中间往左右找边界,当找到连续两个像素点为黑时,记录边界,左右边界相加后左移一位计算中点,进入上一行,从计算的中点开始往左右找边界。如到达最左侧/最右侧仍未找到边界,若有一侧有边界,则通过单边界+(-)赛道宽度来计算右(左)边界,然后进入上一行的遍历。若两侧均无边界,则记为两侧丢边,进入上一行的遍历。
??图像的所有行遍历完成后,所有行左右边界相加后左移一位计算中点,然后判断整条线的斜率或使用某些点加权减去赛道中心点计算偏差,来计算应该赋给舵机的数值。
▲ 图4.2-1 示意图1
4.3 入库处理
??如图4.3-1所示,固定某一行,从左向右遍历,当黑白跳变点到达一定数量时,判断到达一次车库。
??到达两次车库后,如图4.3-2所示,将图像左(右)侧最下侧连接赛道和车库的拐点,作为左(右)边界,右(左)边界全部丢弃,然后补赛道宽作为右(左)边界,计算赛道中线,然后能直接导航进入车库。
▲ 图4.3-2 示意图3
4.4 十字处理
??如图4.4-1所示,当图像中心列及相邻几列为白且图像中心行及相邻几行为白,记为到达十字,这种判断十字的方法十分稳定,缺点是出环姿态不准时识别不到,我在代码中进十字后开始计时,定时一段时间后清除十字标志,即视为出已经出十字。
▲ 图4.4-1 示意图4
4.5 环岛处理
??如图4.5-1所示,当赛道中心列及相邻几列为白,且图像中心到右下角(注意斜率)为白,且左上角一定大小三角形为黑,视为即将进入右环岛,若一定时间未获取到进入右环岛标志,清除即将进入右环岛的标志。左环岛相同处理。 ??如图4.5-2所示,当赛道中心列及相邻几列为白,且图像中心到右上角(注意斜率)为白时,且左下角一定大小三角形为黑时,视为进入右环岛,将图像左(右)侧最下侧与赛道和环岛的拐点连线作为左(右)边界,左(右)边界加(减)赛道宽度记为右(左)边界,相加左移记为赛道中线,直接导航进环岛。
▲ 图4.5-1 示意图5
▲ 图4.5-2 示意图6
4.6 三叉处理
??如图4.5-2所示,若图像左侧靠上的小三角形、右侧靠上的小三角形、上侧中心的小三角形为黑色,且程序判断赛道整体呈现Y形,可记为出现了三叉元素,随后根据OpenART mini传回的串口信息选择走左侧/右侧。
▲ 图4.6-1 示意图7
4.7 Apriltag与靶标处理
4.7.1 Apriltag码识别处理
??利用OpenART自带的标准库,对路面上的Apriltag码进行识别,当识别结果为奇数时利用串口通信向RT1064发送数据‘ST1P’,识别结果为奇数时利用串口通信向RT1064发送数据“ST0P”。
4.7.2 靶标识别处理
● 动物水果处理
??先采集十类水果动物的图片制作数据集,数据集的质量直接影响识别的结果。经过实践,采用OpenART直接拍照制作数据集的形式会因为分辨率不足导致丢失大量特征,最终误识概率较高;扩充官方数据集得到的数据质量较好,误识率低。
??将数据集放入下图所示网络结构的模型中进行训练,完成训练后将训练好的模型文件放入nncu中进行量化,量化好的模型文件放入OpenART的SD卡中即可使用,最终识别率在78.6%左右。
▲ 图4.7.2-1 神经网络模型1
??当识别结果为水果时利用串口通信向RT1064发送数据‘SF1P’,并将pwm置50%让激光打靶;识别结果为动物时利用串口通信向RT1064发送数据‘SF0P’。
● 数字处理
??先采集0~9号数字的图片制作数据集,数字的特征不复杂,因此采用OpenART直接拍照制作数据集的形式就能够有很好的效果。如果采用大量数字图片进行训练会存在过拟合的现象,加入不同光照不同角度的图片可以提高泛化能力。 ??将数据集放入下图所示网络结构的模型中进行训练,完成训练后将训练好的模型文件放入nncu中进行量化,量化好的模型文件放入OpenART的SD卡中即可使用,最终识别率在99.2%左右。
??当识别结果为奇数时利用串口通信向RT1064发送数据‘SN1P’,识别结果为奇数时利用串口通信向RT1064发送数据‘SN0P’。
4.8 车速闭环
??使用增量式PID算法对车速进行闭环。之所以不用经典PID算法,是因为经典PID算法主要针对一些惰性系统,即变化速度较慢的系统,而智能车变化速度较快,因此采用相对来说比较适合的增量式PID,增量式PID具有以下优点:
(1) 由于计算机输出增量,所以误动作时影响小,必要时可用逻辑判断的方法关掉。
(2) 手动/自动切换时冲击小,便于实现无扰动切换。此外,当计算机发生故障时,由于输出通道或执行装置具有信号的锁存作用,故能保持原值。
(3) 算式中不需要累加。控制增量 的确定仅与最近k次的采样值有关,所以较容易通过加权处理而获得比较好的控制效果。
??但增量式PID也有其不足之处:积分截断效应大,有静态误差;溢出的影响大。使用时,常选择带死区、积分分离等改进PID控制算法。
??在调试电机闭环时,不能在空载的情况下进行调试,赛车毕竟要在赛道上跑,如果空载时调好PID参数,上赛道后很有可能会出现车速不稳定的情况。因此建议先铺设一段长直道,在直道上跑智能车,通过上位机观察车模运行过程中的车轮转速变化来调节PID参数。 ? ?
第五章 软件调试
??我们的软件程序编写任务主要在vs code平台下进行的,该平台是一个非常有效的集成开发环境(IDE),它使用户充分有效地开发并管理嵌入式应用工程。作为一个开发平台,他具备较为完善的特性。与此同时,vs code可以安装各种软件开发插件,对于程序开发的效率有比较大的提升。
??在智能车的调试过程中,光靠在线调试过程是远远不够的,我们需要获取车模在高速运行中的各项参数信息,根据这些信息或信息组成的波形图来调试参数。因此良好的上位机能让调参变得事半功倍。我们队在本次比赛中使用的上位机为VOFA+,一款插件驱动的高自由度上位机。和其它上位机相比,他有以下三个优点: ??直观:极致简约的协议设计,打印字符串或发送浮点数组,就可以轻松推送数据!数据、命令和参数,贯穿始终,随处绑定
- 灵活:插件式设计,无论协议还是控件,都可二次开发、动态插入,让你从最基础的文本调试,拓展到无限丰富。
- 强大:不仅仅是2维可视化,3D控件、图形化参数发送也已支持。自主研发的波形控件,更将实时傅里叶变换和直方统计图完美集成。
??我们队通过本款上位机对智能车进行串口控制、数据分析,取得了比较不错的效果。调试界面如下:
▲ 图5-1 VOFA+界面
?
第六章 智能车主要参数
- 车长(mm) 280
- 车宽(mm) 180
- 车高(mm) 315
- 摄像头高度(mm) 290
- 舵机型号及个数 S3010一个
- 电机个数 两个
- 传感器种类、数量 总钻风摄像头一个
- Openart mini一个
- 编码器两个
- 电池种类、规格、数量 锂电池 2S 2000mAh一个
?
参赛总结
??全国大学生智能汽车竞赛是以智能汽车为研究对象的创意性科技竞赛,是面向全国大学生的一种具有探索性工程实践活动,是教育部倡导的大学生科技竞赛之一。该竞赛涵盖了自动控制、模式识别、传感技术、电子、电气、计算机、机械与汽车等多个学科专业,意在培养学生的知识融合和实践动手能力。
??我们作为自动化学院、人工智能学院的学生,在制作智能车的过程中,来自不同专业的队员们通力合作,学习了如何设计、制作电路,设计、打印3D模型,对电路和机械结构的了解更加深入。在调车过程中,我们结合了硬件、软件和机械,尽力将赛车调试到最佳状态。我们遇到过问题,也因为粗心造成了很多失误,但经过我们的不断努力,我们最终制作出了能完成所有比赛任务的智能车。
??本届竞赛与去年相比,赛道元素更加复杂,赛道任务更加多样,这就要求我们需要对车子的控制更加精细,我们需要提升智能车的整体稳定性,保证在赛场上能够稳定发挥。虽然我们的智能车已经能完成比赛,但是我们本身能够表现的更加出色,这表明我们需要进一步的历练。
??我们相信通过这段时间的辛勤努力和指导教师的悉心教导,我们一定能在此次比赛中取得优异的成绩!
?
参考文献
- 卓晴、黄开胜、邵贝贝等.学做智能车——挑战“飞思卡尔”杯.北京:北京航空航天出版社,2007年3月第1版
- 第十六届全国大学生智能汽车竞赛秘书处,第十六届全国大学生智能汽车竞赛竞速比赛规则,2020年12月
- 隋金雪、杨莉、张岩等. “飞思卡尔”杯智能汽车设计与实例教程.北京:电子工业出版社,2015年3月第2版
- 伊恩·古德费洛、约书亚·本吉奥、亚伦·库维尔等. 深度学习.北京:人民邮电出版社,2017年8月第1版
- 王盼宝、樊越骁、曹楠、单超群、朱葛峻、渠占广、佟超、萧英喆等. 智能车制作——从元器件、机电系统、控制算法到完整的智能车设计.北京:清华大学出版社,2018年1月第1版
附录A 主控板整体原理图
附录B 中断服务函数代码
int main(void)
{
//关闭总中断
DisableGlobalIRQ();
//务必保留,本函数用于初始化MPU 时钟 调试串口
board_init();
//延时300ms,等待主板其他外设上电成功
systick_delay_ms(500);
//外设初始化
Init();
//总中断最后开启
EnableGlobalIRQ(0);
//开始计时任务
systick_start();
while (1)
{
csi_seekfree_sendimg_03x(USART_8, mt9v03x_csi_image[0], MT9V03X_CSI_W, MT9V03X_CSI_H);
ips200_displayimage032(image[0], MT9V03X_CSI_W, MT9V03X_CSI_H);
edgeline_show();
time_show();
}
}
?
附录C 中断服务程序代码
//串口接收数组
uint8 uart8_data[12];
uint8 uart4_data[12];
//程序运行测时
uint16 time;
uint16 time_start;
uint16 time_stop;
//智能车运行状态
uint16 key_flag = 0;
uint16 run_flag = 1;
uint16 motor_flag = 0;
uint16 start_flag = 0;
//特殊任务标志位
uint8 tag_flag = 0; //Apriltag码标志位,0为未识别,1为奇数,2为偶数
uint8 tag_class = 0; //此tag码对应的种类,0为未识别,1为动物,2为水果
uint8 num_class = 0; //数字码的奇偶,1为奇数,2为偶数
uint16 position = 0; //图像中心点坐标
void led_run(void);
void key_delay(void);
//特殊任务
void special_task(void)
{
//接收到Apriltag码对对应数据,执行对应操作
if (tag_flag == 1) //tag码识别为奇数,停车,x舵机向右转
rudderx(rudder_roll_rmax);
else if (tag_flag == 2) //tag码为偶数,停车,x舵机向左转
rudderx(rudder_roll_lmax);
//接收到Apriltag数据,开始接收对应的种类
if (tag_flag != 0)
{
if (tag_class == 1) //种类为水果,停车打靶
{
}
else if (tag_class == 2) //种类为动物,停车3s保护动物
{
}
}
}
void PIT_IRQHandler(void)
{
if (PIT_FLAG_GET(PIT_CH0))
{
if (key_flag) //按键开机
key_delay();
PIT_FLAG_CLEAR(PIT_CH0); //清中断标志
time_start = systick_getval_us(); //采集程序开始前时间
led_run(); //程序正常运行标志
if (run_flag) //程序正常运行状态
{
camera(); //图像处理
rudder_control(); //舵机控制
motor_picontrol(); //电机控制
special_task(); //特殊任务
}
else //无指令待机状态
{
motor(0, 0);
}
time_stop = systick_getval_us(); //采集程序结束时时间
time = time_stop - time_start; //程序用时计算
}
if (PIT_FLAG_GET(PIT_CH1))
{
PIT_FLAG_CLEAR(PIT_CH1);
}
if (PIT_FLAG_GET(PIT_CH2))
{
PIT_FLAG_CLEAR(PIT_CH2);
}
if (PIT_FLAG_GET(PIT_CH3))
{
PIT_FLAG_CLEAR(PIT_CH3);
}
__DSB();
}
//openart传数据串口
void uart4_interrupt(LPUART_Type *base, lpuart_handle_t *handle, status_t status, void *userData)
{
static uint8 UART4_i = 0;
if (kStatus_LPUART_RxIdle == status)
{
uart4_data[UART4_i++] = uart4_rx_buffer;
}
if (uart4_data[0] == 'T') //检测Apriltag码
{
if (uart4_data[UART4_i - 1] == '0') //检测为偶数
{
gpio_set(green, 0);
tag_flag = 2;
UART4_i = 0;
}
else if (uart4_data[UART4_i - 1] == '1') //检测为奇数
{
gpio_set(blue, 0);
tag_flag = 1;
UART4_i = 0;
}
}
else if (uart4_data[0] == 'A') //动物停3S
{
tag_class = 1;
}
else if (uart4_data[0] == 'F') //水果打靶
{
for (int j = 1; j < UART4_i - 2; j++)
{
position = position * 10 + (uart4_data[j] - 48);
}
}
else if (uart4_data[0] == 'N') //识别数字
{
if (uart4_data[0] == '0') //数字识别为偶数
{
num_class = 2;
}
else if (uart4_data[0] == '1') //数字识别为奇数
{
num_class = 1;
}
}
handle->rxDataSize = uart4_receive.dataSize; //还原缓冲区长度
handle->rxData = uart4_receive.data; //还原缓冲区地址
}
//电脑控制串口
void uart8_interrupt(LPUART_Type *base, lpuart_handle_t *handle, status_t status, void *userData)
{
static int UART8_i = 0;
if (kStatus_LPUART_RxIdle == status)
{
uart8_data[UART8_i++] = uart8_rx_buffer; //将数据取出
}
if (uart8_data[0] == 'M')
{
run_flag = 1;
if (uart8_data[UART8_i - 1] == 'A') //无电机启动
{
motor_flag = 0;
start_flag = 0;
UART8_i = 0;
}
else if (uart8_data[UART8_i - 1] == 'B') //无起跑线启动
{
motor_flag = 1;
start_flag = 0;
UART8_i = 0;
}
else if (uart8_data[UART8_i - 1] == 'C') //正常启动
{
motor_flag = 1;
start_flag = 1;
UART8_i = 0;
}
else if (uart8_data[UART8_i - 1] == 'Z') //停车
{
motor_flag = 0;
start_flag = 0;
UART8_i = 0;
}
}
handle->rxDataSize = uart8_receive.dataSize; //还原缓冲区长度
handle->rxData = uart8_receive.data; //还原缓冲区地址
}
//按键中断,发车
void GPIO3_Combined_0_15_IRQHandler(void)
{
if (GET_GPIO_FLAG(D4))
{
CLEAR_GPIO_FLAG(D4);
key_flag = 1;
}
}
//按键中断,改发车方向
void GPIO2_Combined_16_31_IRQHandler(void)
{
if (GET_GPIO_FLAG(C26))
{
CLEAR_GPIO_FLAG(C26);
if (motor_flag == 0)
{
if (start_position == 1)
{
start_position = 0;
}
else
{
start_position = 1;
}
}
}
}
void CSI_IRQHandler(void)
{
CSI_DriverIRQHandler();
__DSB();
}
//正常运行标志
void led_run(void)
{
static uint8 led_num = 0;
if (led_num++ == 50)
{
led_num = 0;
gpio_toggle(blue);
}
}
void key_delay(void)
{
static int key_num = 0;
key_num++;
if (key_num == 50)
{
run_flag = 1;
motor_flag = 1;
}
}
● 相关图表链接:
|