基于Android的智能车控制系统设计(源码+万字报告+部署讲解等)

目 录
摘 要 I
Abstract II
1 绪论 1
1.1 开发背景、目的及意义 1
1.2 研究内容和流程介绍 1
1.3 本章小结 2
2 开发平台及技术简介 2
2.1 Android平台 2
2.1.1 Android简介 2
2.1.2 AndroidStudio开发环境搭建 3
2.2 arduino平台 6
2.2.1 arduino简介 6
2.2.2 arduino 开发环境搭建 7
2.3 硬件环境 7
2.4 本章小结 9
3 路径识别方法研究 9
3.1 车辆视觉导航特点 9
3.2 直线检测 9
3.2.1 直线检测方法 9
3.2.2 直线检测具体步骤 11
3.3 直线检测的软件实现 13
3.3.1 OpenCV4Android算法设计过程 13
3.3.2 直线检测算法验证 16
3.4 本章小结 17
4 上位机与下位机软件设计 17
4.1 上位机与下位机的通信 17
4.1.1 蓝牙通信 17
4.1.2 usb转串口通信 19
4.2 上位机界面设计 21
4.3 下位机功能设计 22
4.4 本章小结 23
5 Android遥控器设计 23
5.1 遥控器功能设计 23
5.2 遥控器界面设计 24
5.3 本章小结 24
6 总结与展望 25
参 考 文 献 25
附录A 上位机程序 25
附录B 下位机程序 39
附录C 串口服务类 43
附录D 遥控器程序 52

1 绪论
1.1 开发背景、目的及意义
智能车辆是伴随着近些年微控制器技术和传感器技术的进步而新兴起的一个研发课题,涵盖多学科、多领域,主要内容包括环境感知、路径规划、车辆控制等。智能车辆的设计制造涵盖了包括机械、电子、材料、化工等几乎所有现代工业知识。
近些年来,许多智能车辆已经被应用于工业和生活中,比如工厂中用于室内运输的AGV小车、餐厅中巡线行驶提供零食的小机器人等。再加上智能交通系统的提出,人们对未来出行方式充满了期待,也就更加促进了对智能车辆的研究。[1]
鉴于智能车已经得到广泛应用的事实以及其目前受到的重视程度,研究与其相关的控制算法不仅对汽车工业具有积极意义,也对整个未来交通系统大有裨益。
1.2 研究内容和流程介绍
开发流程如图:

本文提供了一款小车模型,以摄像头为信息来源,设计了一种巡线算法。具体内容如下:
(1)在Android平台下利用CCD摄像头采集图像并执行路线检测算法。基于AndroidStudio开发环境,利用OpenCV提供的库函数实现路径检测。
(2)实现Android上位机控制器与arduino下位机控制器的通信,利用检测到的直线信息控制小车动作。
(3)设计手动控制程序,保证小车在任何情况下不会失控。
整个开发分为上位机控制软件开发和下位机软、硬件开发两部分。
上位机开发方面,首先安装Android软件开发所需的jdk和集成开发环境AndroidStudio,导入视觉处理包OpenCV,验证之后的灰度化、Canny变换等OpenCV的功能可用,建立新工程,首先确定上位机软件功能需求,进行界面设计,然后进行软件功能设计和实现,即实现路径识别并计算出下位机所需的控制参数。
下位机软件开发方面,首先安装arduino的集成开发环境,新建工程,定义引脚、变量,声明函数,在主程序中接收上位机传来的数据,控制电机、舵机的动作。
下位机硬件采用自制的由arduino控制板控制的小车。
1.3 本章小结
简要介绍了开发背景、目的与意义,用流程图说明了开发步骤和流程。
2 开发平台及技术简介
2.1 Android平台
2.1.1 Android简介
Android一词最早出现在一本法国的科幻小说《未来夏娃》中,本意指机器人。安卓的创始人安迪鲁宾于2005年将仅仅两岁的Android公司卖给谷歌,后者于两年后发布了安卓的第一个版本,同时创立了开放软件联盟。
作为移动操作系统,Android基于Linux内核,起初专门针对智能手机及平板设计,通过触摸、手势等直接操作进行人机交互。如今,Android已不仅仅局限于触屏产品,而是陆续开发出了针对电视的Android TV,针对汽车的Android AUTO,针对可穿戴设备的Android Wear,他们各自都有自己特定的交互界面。Android的衍生系统还用于电子书、游戏主机、数码相机等电子产品中。
Android源码由谷歌根据阿帕奇开源协议进行开源发布,鉴于其开源的方便性,Android在有高效率、低成本、可定制化需求的任务面前拥有极大优势,被许多公司和个人开发者所青睐。他的开源特性也激励着广大开发者和爱好者建立起庞大的社区,为旧设备提供升级支持,为高级用户提供新的特性以及在不同设备上移植Android系统。
如今,Android已经迈入7.0时代,其应用市场提供数百万种app,系统使用量已经超过微软的Windows,成为世界上使用最多的操作系统。相信,日后Android的使用范围会越来越广泛。[2]
2.1.2 AndroidStudio开发环境搭建
安装步骤:
在win7环境下安装,首先下载含Android SDK的Windows OS下推荐的Android Studio安装包和jdk安装包,确保安装目标磁盘空间大于40G。在目标磁盘(这里采用G盘)建立如下目录:
根目录为AndroidStudio,下面分为四个子文件夹Studio、java、sdk、wokspace和一个批处理文件StartStudio.bat。Studio用来安装AndroidStudio环境,java用来安装jdk,sdk用来安装AndroidStudio的sdk,workplace用来存放工程文件,批处理文件StartStudio.bat用以启动AndroidStudio开发环境。

首先安装jdk(java software development kit),双击下载的安装包jdk-8u102-windows-x64.exe,将安装目录改为D:\AndroidStudio\java,此步骤安装进度条走完后会再次提示选择目标文件夹,这里要选取的是java虚拟机的安装位置,本文将其指向D:\AndroidStudio\java\jre,确定等待安装结束,jdk的安装就完成了。
安装后要检查jdk是否安装成功,出于不明原因第一次安装时jre这个文件夹可能会安装不上,但是由于后面的AndroidStudio开发环境中内嵌了这个功能,所以并不影响一般使用,但是在后面串口通信部分可能会用到ndk开发,这时就会出现默认路径不对的情况,无论如何编译都不成功,报错:Exception in thread”main” java.lang.NullPointerException
at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)
at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)
at com.sun.tools.javah.Main.main(Main.java:46)
搜索这些报错信息,可以找到非常相似的结果,但与这里的错误完全是两回事,若按照搜索到的解决办法操作,无论如何都无法解决问题,出现这种干扰会极大的影响工作进度。问题原因只是第一次jdk没有安装成功,只需重装jdk即可。为避免这种干扰,检测jdk安装是否成功时主要关注java文件下的结构即可,若有jre文件夹则安装成功,可以继续接下来的操作,若没有jre文件夹,则需要重新安装jdk。正确安装的目录结构如下:

成功安装Java SDK后还需要告诉Windows 操作系统Java 虚拟机、Java SDK开发工具和Java 虚拟机用到的类库都在什么位置,要通过操作系统环境变量实现。具体而言要设置3个环境变量,分别是:JAVA_HOME、CLASSPATH和PATH。JAVA_HOME   指向D:\AndroidStudio\java,CLASSPATH指向D:\AndroidStudio\java \lib\dt.jar和tools.jar,PATH指向D:\AndroidStudio\java \bin和D:\AndroidStudio\java \jre\bin。注意,之前没有的环境变量可以新建,若之前已有环境变量,要在之前的内容后添加新内容,每条指向用分号分割,以免影响其他程序工作。设置完成后进入控制台,输入java –version,出现下页图中信息表示jdk安装和环境变量设置成功:

之后安装集成开发环境(IDE)AndroidStudio。双击下载的  android-studio-bundle-143.3101438-windows.exe,修改安装路径,AndroidStudio安装到Studio文件夹下,Android sdk安装到sdk文件夹下,开始安装,等待结束即可。安装结束后配置目标平台版本、sdk工具等,这类配置也可以在建立工程时添加或修改。

具体安装流程如图:

最后编写批处理文件如下:

@echo off
cd %CD%
set JAVA_HOME=%CD%\java
set PATH=%PATH%;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
set CLASSPATH=.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
cd %CD%\Studio
echo %CD%
start /b bin\studio64.exe
将该文件放在桌面,双击即可启动IDE。当一台主机上有多个用户时该方法非常有效,因为不同用户的环境变量不同,可能导致IDE在这个用户下可以启动,另一个用户却不能,利用批处理启动即可避免这种现象。
至此,AndroidStudio开发环境搭建完成。
2.2 arduino平台
2.2.1 arduino简介
Arduino计划开始于2003年,最初的目的是给位于意大利北部伊夫雷亚的设计院学生提供一种低成本高效率的方式来建立他们的设备与环境的连接。Arduino这个名字源自计划创立者们常去的一间小镇上的酒吧。
Arduino控制板采用一系列微处理器和微控制器。板子装备了一批数字量和模拟量的输入输出引脚,用以与外部设备和电路通信。板子实现了包括usb在内的串行通信接口,同时也用来从电脑上下载程序。软件开发通常使用C语言或C++语言,而且提供了集成开发环境。[3]
本文采用的控制器为Arduino Uno SMD R3,如图所示。主控芯片为采用表面镶嵌技术的Atmega328P,之所以舍弃了完全打通的镶嵌方式,是由于采用该方式镶嵌的产品供货不足。板子拥有14个数字输入输出引脚,其中6个可作为PWM输出,6个模拟输入引脚,一个16MHz晶振,一个usb口,一个电源插槽,一个ICSP口和一个复位按键。只需连接电源即可使用。Uno相对于其他产品的不同在于他采用Atmega8U2作为usb转串口芯片而不是FTDI。Uno在意大利语中意为“1”,用以纪念arduino1.0版本。

2.2.2 arduino 开发环境搭建
双击 arduino-1.5.6-r2-windows.exe 安装软件,选择安装位置,等待安装结束后打开IDE,导入 AF_Motor(L293 电机模块)驱动库文件,之后安装usb驱动,将开发板与电脑连接,按照pc端提示的驱动安装步骤安装。烧录程序是要注意在工具->端口菜单中选择正确的端口。

2.3硬件环境
本文的小车可以分为上位机和下位机两部分。上位机用以进行大运算量的路径识别任务,下位机用来控制小车的电机和舵机,上、下位机之间的通信可以采用有线的串口通信方式或无线的蓝牙通信方式。
电路连接示意图如下:[1]

上位机采用运行Android操作系统的平板电脑,接收摄像头采集的图像,对其进行分析计算,得到路径信息,通过USB串口或蓝牙发送到下位机。同时在Android平板的屏幕上显示人机交互接口(控制按键)、摄像头实时图像已经控制量信息。
下位机采用Arduino UNO SMD R3型号的单片机,作为一个开源计划,arduino的控制板大多易于上手,使用方便,适合自主开发所需功能。这里的Arduino控制板负责接收Android端发来的控制信息并控制小车的电机和舵机动作。
摄像头采集到的数据传送到Android平板,处理得到路径及控制信息后传到下位机arduino小车,舵机、电机及其驱动模块接到arduino控制器上,由单片机控制其动作。
具体的电路连接示意图如下:

Android控制板与arduino控制板通过usb线连接(若通过蓝牙通信则无需连线,但需加装蓝牙模块),12v电源给Android控制板及电机驱动板供电,arduino控制板由其自带电源供电。舵机信号接到arduino的2号引脚,电源接到5v和GND。电机信号端接9、10脚,电源接5v和GND。操作时Android平板要通过HDMI或VGA外接显示器并插上鼠标,图中为表现简洁而省去。
下位机外观如下:

线束已置于车体内,外观简洁。

2.4本章小结
本章分别概述了arduino和Android的简要情况和各自开发环境的搭建,尤其是AndroidStudio环境,搭建存在纰漏的话可能严重影响后续工作的进度。此外介绍了硬件环境的组成,为系统开发和实验打下基础。
3 路径识别方法研究
3.1 车辆视觉导航特点
视觉导航技术需要快速准确的确定导航路线,即车道线。影响系统准确性及实时性 的因素包括:
(1)光线因素:由于天气、阴影、太阳角度等不同,同一地点同一摄像头提取的图片会有很大差异。
(2)车辆因素:车速、路径、抖动均会对采集的图像产生较大影响。车速越快,摄像头对焦越难,图像质量越差;直线行驶要比曲线行驶得到的图像质量高;抖动越剧烈,图像质量越差。
(3)摄像头位置及角度:摄像头安装在不同位置或以不同角度安装,对同一地点采集的图片就会有不同效果,整个系统的预测及反应机制均需相应变化。
这就要求检测算法可以使车辆即使在拥有一定车速的情况下仍具有可靠的图像处理能力,对控制器计算能力和算法效率都提出了较高要求;由于车辆使用的环境复杂性,还需要算法可以适应不同的环境,即鲁棒性的要求。
3.2 直线检测
3.2.1 直线检测方法
要实现直线检测的功能,首先要对摄像头采集到的图像数据进行边缘检测,本文运用Canny边缘检测的方法。本方法对检测的直线有三点要求:[4]
(1)低错误率并检测到尽可能多的直线;
(2)检测出的边缘上的点应位于实际边缘线的中心;
(3)一条边缘只被标注一次,且噪声不会产生错误的边缘。
算法步骤包括:
(1)运用高斯滤波器减轻噪声干扰:
高斯滤波器对于阶跃输入没有超调,可以有效的去除图像中的噪声。高斯内核的选取会影响探测器的表现,内核越大,探测器对噪声的敏感度越低,对直线的位置偏差越大,一般情况下选用5*5的内核即可。
(2)找到图像的强度梯度;
Canny算法采用4个过滤器分别探测水平、竖直、2个对角线方向的直线。边缘检测算子可以返回水平、竖直方向的一阶导数和,梯度幅值为

方向为:,按的值选用4个过滤器中的一个。
(3)运用非极大值抑制去除虚假结果;
非极大值抑制用来细化直线。在应用了梯度运算后,找到的直线依然很模糊,而此方法可以把极大值之外的区域梯度降为0,这样强度值变化最大的区域即所求。对于梯度图的每个像素而言:将当前像素的边缘强度与其正导数方向和负导数方向的对比,如果当前像素的边缘强度最大则保留,否则抑制。
(4)运用两个阈值找到潜在边缘;
经过以上几步后,找到的边缘相对准确,但是仍会存在由于噪声或颜色干扰导致的错误边缘,这时就需要设置两个边缘强度阈值,高于高阈值的像素保留,低于低阈值的抑制,而两阈值之间的被标记为弱边缘像素。
(5)利用滞后效应追踪边缘:
通过以上几步,高于高阈值的强边缘像素已经确定为所找边缘上的像素,低于低阈值的像素也已确定舍去,只剩弱边缘像素存在争议。一般来讲,由真正边缘产生的弱边缘像素会与强边缘像素有所联系,所以对每个弱边缘像素进行判别,若其周围8个像素中存在强边缘像素,则将其保留,否则舍去。
检测到了图像边缘后,为了找到路径,需要选出边缘中的直线,这里使用霍夫变换来检测直线:[5]
通常情况下,在二维空间的任意一条直线可由两个变量表示:在直角坐标系,可由参数斜率和截距表示(k,b);在极坐标系,可由参数极径和极角(r,θ)表示。在极坐标下的一个点对应直角坐标下的一条直线,相应的直角坐标下过一定点的所有直线构成极坐标下的一条曲线,即两个坐标系构成一种升降维的关系。拿以下三点为例:[5]
过各个点的所有直线在极坐标平面上构成3条曲线:

可见,极坐标下三条曲线的交点即为直角坐标下三点共同所在的直线。
之前的边缘检测已经提供了所需点在直角坐标下的坐标,霍夫变换利用这种升降维关系,将寻找直角坐标系下的直线变为寻找极坐标下的交点,只需统计极坐标下过某点的曲线条数,大于阈值即可认定该点对应一条直线。
3.2.2 直线检测具体步骤
流程如图:
(1)图像采样
利用CCD摄像头采集图像。将摄像头安装在适当位置,使其视角可以清晰的拍摄到路面,对前方道路进行图像采集,采集到的数据被传至Android上位机,以待上位机处理。
(2)灰度图转换
摄像头采集到的原始图像为RGBA彩色图像,即用4个颜色数据来表示一个像素点(rgba原理),对于后续边缘检测和直线拟合没有益处,徒增计算量,因此这里将彩色图像进行二值化处理,转换为灰度图,即每个像素点由一个值表示。
(3)边缘检测
得到一帧摄像头数据的灰度图后,需要进行物体轮廓提取,便于后续的直线检测。这里用canny算子提取轮廓。

(4)霍夫变换
得到了一帧摄像头数据的边缘检测结果后,进行霍夫变换将边缘检测结果图中所有直线两端点坐标提取出来保存到一个数组中。
(5)直线拟合
由上步得到的直线端点坐标可得直线y=kx+b的解析式。坐标原点位于屏幕左上角,向右为x轴正方向,向下为y轴正方向。
3.3 直线检测的软件实现
3.3.1 OpenCV4Android算法设计过程

根据上述直线检测流程,可以在向工程中添加了OpenCV依赖库之后直接调用其中的库函数,具体的处理流程可以总结为上图。
首先确定OpenCV是否成功加载,实例化一个新的BaseLoaderCallback对象,重写onManagerConnected方法,若OpenCV成功加载则使能摄像头控件。接下来调用摄像头采集图像信息,在界面文件中添加摄吗头控件,在主程序中定义变量mOpenCvCameraView用于操作摄像头,将摄像头控件设为可见,打开其监听器以处理图像数据,设置摄像头分辨率。通过摄像头得到实时图像采样,对采集到的单帧图像进行处理。
像处理阶段首先利用Canny方法进行图片中的边缘检测, 调用Improc.Canny函数。该函数声明于Improc.java文件中:
public static void Canny(Mat image, Mat edges, double threshold1, double threshold2, int apertureSize, boolean L2gradient)[3]
{

    Canny_0(image.nativeObj, edges.nativeObj, threshold1, threshold2, apertureSize, L2gradient);

    return;
}
源码中其上方标有注释,明确了函数功能及各参数含义:函数用来查找边缘,image为传入的待处理图像,edges为输出的边缘,threshold1、2用于筛选边缘,apertureSize用来设定过滤器大小,L2gradient用于判别是否默认参数足以用来计算。
确定各参数值,调用函数:

Imgproc.Canny(mRgba, cannyEdges, 80, 100, 3, false);
然后利用霍夫变换找到所有边缘中的直线段,即要寻求的路径。调用Improc.HoughLinesP进行霍夫变换,同样声明于Improc.java文件中:
public static void HoughLinesP(Mat image, Mat lines, double rho, double theta, int threshold, double minLineLength, double maxLineGap)
{

HoughLinesP_0(image.nativeObj, lines.nativeObj, rho, theta, threshold, minLineLength, maxLineGap);

return;

}
注释中说明了:本函数用于在二值图中找到直线,image为传入的图片,lines为输出的线段,rho为以像素为单位的长度分辨率,theta为以弧度为单位的角度分辨率,threshold为阈值,达到该值的直线才被返回,minLineLength为最小返回线段长度,maxLineGap为同一直线上两点允许连接的最大距离。
确定各参数值,调用函数:
Imgproc.HoughLinesP(cannyEdges, lines, 1, PI / 180, 50, 20, 20);
这时所有的线段均保存在数组lines中,下面要找到需要的路径:
最简单的方法是选取lines中最长的线段,只需遍历数组,计算每条线段长度并找出最长的那条记录其位置信息即可。但是实验发现,由于光照、角度、摄像头安装位置等原因,按此方法找到的路径不稳定,会在目标轨迹的两边任意跳动,这就导致小车巡线的不稳定,经常出现大范围转向。
进而采样优化的方法:遍历lines数组,每次取出一条线段,由于lines中保存的是线段的端点坐标,就能得到本次取出的线段位置及长度信息,首先计算线段与x轴的夹角,若小于0则加π,以便后续的筛选。接着判断处理后的夹角大小,若属于π/4到3π/4则采用,否则弃用本条线段,回到lines数组取出下一条线段。对于采用的线段,首先计算其长度,与标记的最长长度(初值为0)对比,若大于标记的最长长度,则记录本线段为最长并保存本线段位置信息;若不大于最长长度,则将本线段与之前保存的最长线段位置进行对比,通过比较二者在x轴上的截距判断二者是否共线(由于视野环境较简单,不会出现斜率不同而截距相同的线段),若不共线,则记录本次线段的位置信息,之后返回继续遍历lines数组,若共线则直接返回,继续遍历lines数组,直到到达数组尾。
遍历lines数组之后,会保存下来最长线段的位置信息和一条与之不共线的线段的位置信息,根据视野环境,这两条线段即轨迹的两边,比较二者位置取右侧线段为轨迹即可。发送给下位机后进行下一帧的处理,循环往复。
这种方法根据实际的视野情况进行优化,稳定的找到轨迹的两边,一直采用轨迹右侧作为行进路线,解决了轨迹左右跳动的问题。
最后在交互界面上绘制图像。霍夫变换后线段的端点坐标已经保存到lines中,调用Core.line即可绘制出线段:
public static void line(Mat img, Point pt1, Point pt2, Scalar color, int thickness)
{

line_1(img.nativeObj, pt1.x, pt1.y, pt2.x, pt2.y, color.val[0], color.val[1], color.val[2], color.val[3], thickness);

return;

}
其中ima为要绘制线段的图像,pt1和pt2为线段的起点和终点坐标,color为线段颜色,顺序为bgr。
确认参数,调用函数:
Core.line(yuantu, p1t1, p2t1, new Scalar(255, 0, 0), 3);
Core.line(yuantu, p1t2, p2t2, new Scalar(255, 255, 0), 3);
Core.line(yuantu, ptest1, ptest2, new Scalar(0, 238, 118), 3);
绘制出找到的两条线段以及最后确定的路径,为便于观察,路径显示时向左偏移10个像素。
在画出了车道线之后,为了便于观察偏离车道情况,在图像中标注出了预瞄点和目标点的位置。预瞄点为预瞄驾驶员模型的观测点,目标点为与预瞄点同Y坐标的,在车道线上的点。
使用画圆的函数Core.circle并用Core.putText进行标注:
public static void circle(Mat img, Point center, int radius, Scalar color, int thickness)
{

circle_1(img.nativeObj, center.x, center.y, radius, color.val[0], color.val[1], color.val[2], color.val[3], thickness);

return;

}
public static void putText(Mat img, String text, Point org, int fontFace, double fontScale, Scalar color)
{

putText_2(img.nativeObj, text, org.x, org.y, fontFace, fontScale, color.val[0], color.val[1], color.val[2], color.val[3]);

return;

}
其中一个点和文字:
Core.circle(yuantu, P, r, new Scalar(100, 200, 0), 5);
String words = “predict point”;
Core.putText(yuantu, words, P, 3, 2, new Scalar(255, 0, 0));
3.3.2 直线检测算法验证

检测结果如上图,前进/停止按钮用来控制小车电机,开始/暂停按钮用来开始或停止路径识别。位置误差、角度误差和处理时间显示在开始/暂停按钮下方,处理时间单位为微秒。左上角为发送的控制参数,发送时保留2位小数。红色线(最左侧)为检测到的最长线,黄色(最右侧)为不与其共线的另一条线,绿色(中间)为最终路径,为便于观察,显示时左移了10个像素。绿色圆点(predict point)代表小车位置,蓝色圆点(aim point)为预瞄点。

3.4 本章小结
本章主要描述了路径识别的方法,解释了直线检测中最重要的canny算子和霍夫变换,在AndroidStudio环境下实现了直线检测并在交互界面上标注出检测信息。
4 上位机与下位机软件设计
4.1 上位机与下位机的通信
前一章中上位机已经完成了摄像头数据的采集和处理,得到了路径信息,本章主要讲述如何将路径信息传输给下位机。

4.1.1 蓝牙通信
Android系统支持蓝牙功能,IDE中内嵌了蓝牙的操作函数,进行蓝牙通信的步骤为:初始化蓝牙功能,建立连接,开始通信。初始化的工作是为了确保设备支持并已开启了蓝牙功能;建立连接的方式有两种,一种为搜索所以蓝牙设备以供选择,另一种为在已知要连接的设备地址后直接连接。前者连接花费的时间长,而且需要使用者每次连接时选择设备,而后者建立连接速度快,且只需在第一次连接时赋予设备权限即可,缺点是只能连接一个特定的设备。由于本文的下位机采用一块HC-06蓝牙芯片,地址固定,所以连接方式选用后者。
蓝牙通信流程如图:
首先初始化蓝牙,利用嵌入的getDefaultAdaper方法实例化一个蓝牙操作器变量,若实例化失败则说明设备不支持蓝牙,结束程序。得到蓝牙操作器后,运行isEnabled方法检测蓝牙功能是否开启,若未开启,提示“你的手机的蓝牙还没有启动”,重新进行初始化操作。在确定了蓝牙已经开启后,根据设备的UUID调用createRfcommSocketToServiceRecord创建socket,然后调用socket的connect函数进行连接,再调用getOutputStream创建输出流,完成后调用onPostExcute函数将后台计算结果返回给UI线程。UUID的查询可编写另一个软件,功能为查询所有搜索到的蓝牙设备ID,运用了startDiscovery、getBondedDevices等内嵌函数,在此不再赘述。

成功连接后新开一个线程以传输数据,新建数据发送类SendInfoTask继承自异步通信类AsyncTask,重写doInBackground函数,调用输出流的write函数将待发送的数据填至发送缓冲区,在发送线程中实例化一个发送类,执行其运行函数excute()即可实时发送,每次发送完成后进行0.2秒的线程等待,以保证数据发送完成。

4.1.2 usb转串口通信

由于AndroidStudio中没有内嵌串口通信的函数,这就需要自行实现该功能。找到实现该功能的开源包,其在工程中的地位和添加方式等同于OpenCV开发包,基于这个开源包编写服务UsbService,在其中实现usb设备的请求、过滤、连接、通信方法并设置波特率,以备在主程序中调用。Usb服务类设计流程如上图。
首先定义波特率变量并赋值,一般后续设置波特率时调用,运用变量而不是数值设定波特率方便日后更改。编写数据接收函数mCallback作为UsbReadCallback函数的实例,重写onReceivedData方法,将接收的数据比特流转化为字符串类型,发送到UI线程进行处理。之后编写广播接收函数usbReceiver用于处理系统发出的各种广播,包括usb权限的允许\否决,usb设备的插入\拔出等,在收到广播时进行提示。之后进行广播过滤器设置,指定设备接收的广播类型,以提高软件效率,较小功耗。再编写findSerialPortDevice函数用来连接设备,函数中先得到设备的PID和VID,与预设值对比,若不同,则重新查找串口设备,若相同,允许连接。最后编写独立的线程用以打开串口(连接),供主程序调用。虽然打开串口的操作很简单,但将其从UI线程独立出来仍然是较好的选择。
	连接线程工作如下:

首先得到一个usb设备对象,在串口打开的情况下,调用setBaudRate,setDataBits,setStopBits,setParity,setFlowControl函数依次设置波特率、数据位、停止位、校验、流控制,调用read函数读取数据,设置完成后向主程序发送usb就绪广播。
有了usb服务类后,就相当于为集成开发环境嵌入了usb串口通信的功能函数,可以参照蓝牙通信的开发方法进行开发。Usb通信流程如下:
首先在主程序中定义一个UsbService型的变量usbService用来操作,开启usb服务,开始进行广播接收,根据usb服务类中的设置,接收的广播包括五种:usb准备好、usb未授权、无usb连接、拔下usb设备、不支持usb。开启程序是会提示要不要进行授权,点击授权就会提示usb准备好,可以进行下一步操作;若点击不允许则提示usb未授权,程序结束;若开启程序时未插入usb设备,则提示无usb连接,程序结束;若将usb设备拔出,则提示拔下usb设备,程序结束;若设备不支持usb功能,则提示不支持usb,程序结束。在usb准备好后,过程与蓝牙通信相似,只需建立连接再开启发送线程。发送函数由蓝牙时采用的excute变为UsbService服务中实现的write,开启发送线程后计算发送数据,调用write函数发送,为保证发送完成进行0.2秒的线程等待,再开始下一个数据的计算和发送。
具体流程如图所示:

4.2 上位机界面设计
上位机界面采用FrameLayout布局,底层为摄像头控件,上层为显示和控制控件。摄像头控件宽、高均设为fill_parent,可见性设为gone,使其可被上层控件部分覆盖。显示和控制控件为达到外观整齐,采用LinearLayout布局,宽、高属性均设定为wrap_content,位置误差、角度误差、处理时间三个控件设为白色背景,便于发现。开始/暂停按钮用于切换图像识别的状态,按一下开始识别路径,再按一下暂停识别;前进/停止按钮用于切换小车运行状态,按一下小车开始前进,再按一下小车停下。位置误差、角度误差、处理时间用来显示程序运行时的各个数据,左上角为上位机传到下位机的数据。为了实现各数据的实时更新,开启UI更新线程,更新频率设为每秒一次,每次更新左上角控制数据和位置误差、角度误差、处理时间后面文本框Textview中的内容。

Android端界面如图: 

4.3 下位机功能设计

软件流程如下:

下位机为搭载arduino控制器的小车,软件系统用于接收执行上位机的指令。定义了各引脚、变量和小车动作函数。在主函数中接收上位机传来的数据,根据数据判别小车应采取的动作,发出指令使小车运动。
首先定义各引脚、变量,初始化端口,再定义小车动作函数,包括启动、停止、转向。在loop函数中采用while语句接收上位机传来的字符串类型数据并转化为int类型,根据数据判别小车应采取的动作,若指令在70到110之间,则舵机动作,控制小车转向;若指令为0,则电机动作,小车启动;若指令为1,小车停止。

4.4 本章小结
本章介绍了上位机与下位机的软件系统,着重阐述了上位机与下位机的通信方式,实现了二者之间的数据传输,从而实现了从摄像头采集数据识别出路径并使小车按该路径行驶的功能需求。之后介绍了上位机的界面,最后概述了下位机的软件功能。

5 Android遥控器设计
5.1 遥控器功能设计
前面4章实现了小车的巡线行驶,巡线功能本身依托于开源的OpenCV库函数,运行于Android平台,这就带来了几个问题:首先,Android系统作为目前使用率最高的操作系统,可以搭载于几乎所有的硬件设备上,甚至古老的诺基亚按键式的手机都可以刷入Android系统。但是巡线所用的OpenCV库函数中的Canny变换和霍夫变换都需要极大的运算量,这就导致硬件平台所能提供的性能跟不上软件要求;其次,软件运行需要合适的外部光照条件,太亮太暗都会影响检测结果;最后,摄像头的质量也会对结果产生影响,在路径识别阶段用到了几处直接对像素进行操作的语句,而摄像头不同初始的分辨率可能也不同,虽然可以在软件层设置,但是如果遗漏的话还是会造成影响,而且摄像头的焦距也会影响识别的路线,甚至当对焦不准时会完全检测不到路径。综合这些潜在风险,小车的巡线功能可能失效,这就需要设计一个遥控器,以防巡线失效后小车失控。
遥控器功能包括控制小车的前进、后退和转向。可以利用手机端集成的各种传感器和按键功能,按键控制小车速度,左右摇晃手机控制小车转向。具体来说,Android手机作为上位机提供速度和转向两个控制信息,通过按键改变速度控制信息,通过内嵌的传感器采集手机姿态,修改转向控制信息,将二者整合通过第四章提到的蓝牙通信方法转递给下位机小车,小车根据这些信息动作。
按键方面,要实现的功能包括:单击加速或减速按钮实现速度信息的逐一增加或减小;双击加速按钮实现传感器的校正;双击减速按钮实现速度信息归零,以适应急刹车情况;长按加速或减速按钮实现速度信息的持续改变。由于Android自带的Button控件只有敲击一种响应方式而没有长按的响应,要实现上述要求就需要自定义按钮。
创建一个自定义按钮类LongClickButton,在类中定义3种不同构造函数的重载,实现不同参数构造。接着初始化监听,当按钮被按下时开启线程。通过长按监听器中的handler传递数据判定repeataction是否执行,在主程序中确定repeataction的工作,以此实现长按的响应。双击的响应可以依靠自带的点击监听器实现,在点击监听器中开启一个线程等待第二次点击,利用点击监听器的handler传递数据判断执行单击操作还是双击操作。

你可能感兴趣的:(JavaEE,数据库,算法,网络,android)