计算机控制小车推球

 

组长:方晓东

组号:C01

成员:(从左向右)

       薛晨欢5062119017 小车的焊接、调试,单片机程序编写;
       方晓东5062119013 图像的获取、处理、小车硬件的调试;
       晁悦5062119022 根据返回的坐标值编写小车前进的控制类,串口通信;
       丁文冠5062119007 识别小车和物块位置并返回坐标值,整合、编写上位机程序。

 

目录:

项目介绍及完成情况

项目整体结构说明
    小车部分
    计算机程序部分

系统测试情况

小车制作过程中遇到的问题

系统的不足及可改进的部分

自我评价

附录

 

项目介绍及完成情况:

本项目以计算机为上位机,小车车载51单片机为下位机,通过摄像头捕捉视频信息,计算机处理从摄像头获取的图像

并发出控制信号,单片机接收控制命令,并最终控制小车运动,使小车把物块推到目的地。如下图:

整体完成情况:小组最终完成的情况是:上位机C++程序编写完成,并通过编译,下位机keil C程序编写完成并通过编译。自行搭建实验所需要的小车,并成功下载了单片机的程序,但是因为在最近的一次调试过程中烧坏了几片单片机,至今未能找到原因,故导致最终没能来得及进行程序的调试。

 

项目整体结构说明:

小车部分:

小车部分分为两部分:硬件和软件。

小车硬件部分原本是已经提供好的,但是由于我们小组材料拿得晚,没有现成的,只能拿一堆散装元件自己组装。

小车硬件部分可分为三部分:单片机板,电机驱动板和小车。

小车由左右两个电机进行驱动,独立控制,可进行前进、后退、左转、右转等动作。

电机驱动板是接收单片机控制信号后,通过L298对电机进行驱动。

单片机板主要是控制信号的接收和转换。通过串口通信接收计算机发送的控制信号,区分出不同的小车行进方向,通过IO输出到L298驱动电机。

硬件部分资料老师已经提供了,原理图等都是现成的,我们只需要按照图焊接就行了。

软件部分主要是串口通信。在selftest程序的基础上进行修改。

串口通信包含串口初始化设置,包括定时器初始值设置和一些控制位的设置。此外,采用串口中断来串口信号。

由于我们小车在制作的过程中碰到很多问题,导致最后没时间调试串口通信。所以,程序虽然写了,但没调试通过。程序见源代码。

 

作品静态照(拍摄时USB数据线已经借给别的小组)

 

另外,由于没有P0口上拉排阻,我们自制了“排阻”,见图:

 

计算机程序部分:

DWG程序的最终目的是实现通过摄像头对图像的采集,进行数据处理,并向小车两个发动机发送控制程序。

DWG程序分为3个模块:CaptureCDataMgrCserial

 

1Capture——摄像头采集图像模块

程序头文件定义如下:

class Capture{

private:

       CvCapture* capture;                        //OpenCV图像采集指针

       IplImage* org_frame;                       //采集到的原始图像指针

       IplImage* frame;                              //经过尺寸变化后的图像指针

       BYTE* pic;                                     //转换成BYTE*类型后的图像指针

       CvSize size;

public:

       Capture();

       ~Capture();

       IplImage* Getorg_frame(){return org_frame;}

       IplImage* Getframe(){return frame;}

       BYTE* Getpic(){return pic;}

       CvSize Getsize(){return size;}

       void Grab();

       void Adjust();

       void Load(char*);

       int GetClr(int);

};

 

       其中需要说明的是

    1)图像采集利用OpenCV库进行,Capture类的私有成员数据作用见注释

 2)  BYTE* Getpic(){return pic;}函数

此函数的返回类型为BTYE类型的指针,即unsigned char 类型的指针,指向获取图片数据数组的首地址。是图像处理模块最重要的入口参数。

 3)  Void Grab()函数此函数返回值类型为void,其目的是控制摄像头开始抓取图片。由于算法的关系,我们抓取的是24位RGB色域的图像。

 

2CDataMgr——数据处理模块

       此模块有三个最主要函数

       DATA MgrCar(BYTE *p);           //获取小车前后坐标

       DATA MgrWood(BYTE*p);                 //获取木块中心坐标

       DATA MgrDst(BYTE *p);                   //获取目的地坐标

 

       其中利用一个struct  DATA保存获得的坐标值,如下

typedef struct DATA_1

{    

              int x1;

              int y1;

              int x2;

              int y2;

}DATA;

      

       即木块、小车、目的地均用上面的结构表示。x1y1为前段坐标,x2y2为后段坐标。

 

       本程序采用颜色识别进行小车、木块的检测,程序如下

 

       小车的检测(前后各安装红绿发光二极管一个,用以识别)

LONG lLineBytes=3;                                                                                         //GetDibWidthBytes();

       //检测红色标志点

       for(int m=0;m<480;m++)

       {

              for(int n=0;n<640;n++)

              {

                     if((m_pData[m*lLineBytes+3*n+2]>=200 )&&(m_pData[m*lLineBytes+3*n+1]<=50)&&(m_pData[m*lLineBytes+3*n]<=50))    //红色分量大于等于200

                            break;

              }

             

              if(n!=640-1)

              {

                     Car_data.x1=n;

                     Car_data.y1=m;

              }

             

 

       }

 

       //检测绿色标志点

       for(int j=0;j<480;j++)

       {

              for(int i=0;i<640;i++)

              {

                     if((m_pData[j*lLineBytes+3*i+1]>=200) &&(m_pData[j*lLineBytes+3*i]<=50)&&(m_pData[j*lLineBytes+3*i+2]<=50))   //绿色分量大于等于200

                            break;

              }

             

              if(i!=640-1)

              {

                     Car_data.x2=i;

                     Car_data.y2=j;

              }

             

 

       }

       return Car_data;

} 

 

    木块的识别

 

DATA CDataMgr::MgrWood(BYTE *p)

{

       DATA Wood_data;

       m_pData=p;

      

 

       LONG lLineBytes=3;                                 //GetDibWidthBytes();

//检测木块下端点

       for(int j=0;j<480;j++)

       {

              for(int i=0;i<640;i++)

              {

                     if((m_pData[j*lLineBytes+3*i]>=20)

                            &&(m_pData[j*lLineBytes+3*i+1]>=20)

                            &&(m_pData[j*lLineBytes+3*i+2]>=20)    //不为黑色

                            &&(i!=640-1))

                            {

                                   Wood_data.x1=i;

                                   Wood_data.y1=j;

                            }

 

              }    

                                                                                          //j=n,i=m

       }

//检测木块上端点

       for(int n=480;n>0;n--)

       {

              for(int m=0;m<640;m++)

              {

                     if((m_pData[n*lLineBytes+3*m]>=20)

                            &&(m_pData[n*lLineBytes+3*m+1]>=20)

                            &&(m_pData[n*lLineBytes+3*m+2]>=20)    //不为黑色

                            &&(m!=640-1))

                            {

                                   Wood_data.x2=m;

                                   Wood_data.y2=n;

                            }

 

              }    

             

       }

 

       Wood_data.x1=(Wood_data.x1+Wood_data.x2)/2;

       Wood_data.y1=(Wood_data.y1+Wood_data.y2)/2;

       Wood_data.x2=Wood_data.x1;

       Wood_data.y2=Wood_data.y1;

 

       return Wood_data;

}

 

原理示意图如下

 

 

 

 

 

 

 

 

 

 

 

 

   

 

3.CarCtr——小车路径算法及控制模块

小车路径算法的设计思路大致是:得到物块和小车的坐标后,物块与预设目的地之间连线为l,先使小车沿垂直l的方向开向l,然后转向90度,此时物块应位于小车和目的地之间,再沿“S”型路线将物块推向目的地。

int Turn(int x1,int y1,int x2,int y2)

{

       if((x2*y1-x1*y2)>0)

              return 1;//右转

       else

              return 0;//左转

}

int CarMove(int a,int b,int c,int d,int e,int f,int g,int h)

{

       int xn,yn;//垂足

       xn=((h-f)*(g-e)*(b-h)+(g-e)*(g-e)*a+(h-f)*(h-f)*g)/((g-e)*(g-e)+(h-f)*(h-f)); 

       yn=((h-f)*(h-f)*b+(g-e)*(g-e)*h+(h-f)*(g-e)*(a-g))/((g-e)*(g-e)+(h-f)*(h-f));

       int x1,y1,x2,y2,x3,y3;

       double dis;

       x1=a-c;

       y1=b-d;

       x2=g-e;

       y2=h-f;

       dis=(a-xn)*(a-xn)+(b-yn)*(b-yn);

       if(b>yn)

       {

              if(y2>0)

              {x3=1/x2;

              y3=-1/y2;

              }

           else

              {

              x3=-1/x2;

              y3=1/y2;

              }

       }

       else

       {

              if(y2>0)

              {

                     x3=-1/x2;

                     y3=1/y2;

              }

              else

              {

                     x3=1/x2;

                     y3=-1/y2;

              }

       }

       if(((xn-e)*(e-g))>0)

       {

              if(dis>10)

                     return Turn(x1,y1,x3,y3);

              else

                     return Turn(x1,y1,x2,y2);

       }

       else

              return Turn(x1,y1,-x3,-y3);

}

4Cserial——串行接口模块

CarCtr.h中定义了两个函数,一个是int Turn(int x1,int y1,int x2,int y2),另一个是int CarMove(int a,int b,int c,int d,int e,int f,int g,int h)

int Turn(int x1,int y1,int x2,int y2)

//是用来控制小车的转向的函数,被用于int CarMove()函数中。(x1,y1)、(x2,y2)分别代表解析函数中的两个向量,(x1,y1)代表从车尾中点指向车头中点的向量,(x2y2)代表想让小车指向的向量。比如,现在小车向量坐标为(11),而从要推的物块指向目的地的向量为(125),则在函数Turn11125)就会返回出数值,1=右转,0=左转。

int CarMove(int a ,int b ,int c ,int d ,int e ,int f ,int g ,int h)

//是控制小车前进的最主要函数。(ab)代表小车前端中点坐标,(bc)代表小车后端中点坐标,(ef)代表要推物块的坐标,(gh)代表目的地坐标。int CarMove(int a ,int b ,int c ,int d ,int e ,int f ,int g ,int h)返回01,分别代表左转或右转。

关于串口通信,我们采用的是第三方类的方法来实现得。我们在论坛同学的帮助下找到了一个关于第三方类的声明和函数实现,然后声明了一个void Sendout(int n)函数,代表传输字符函数。

void Sendout(int n)

{

       CSerial A;

       int x;

       if(!A.Open(2,9600))

              cout<<"Fail to open com2"<<endl;

      

           switch(n)

              {

            case 0:

                      x=A.SendData("0x00",1);

                         if(x==1)

                                cout<<"send successfully"<<endl;

                         else

                                cout<<"Fail to send"<<endl;

                            break;

                     case 1:

                            x=A.SendData("0x11",1);

                            if(x==1)

                                   cout<<"send successfully"<<endl;

                            else

                                   cout<<"Fail to send"<<endl;

                            break;

              }

}

 

首先打开串口,如果打开失败,则输出字符串Fail to open com2。根据n的值来判断传输的字符,n=0,则传输0x00字符,若传输成功输出send successfully,反之,Fail to sendn=1,传输0x11。下位机根据字符来判断左转还是右转。

程序界面截图:

 

系统测试情况:

系统能够从摄像头获取图像,对每一帧图像进行扫描并确定木块及车上红、绿标志点的位置,通过几何计算能确定出小车的运行方向,并发出控制指令。因为搭建小车耗时过长,导致串口通讯和小车控制程序并不完善。在测试中,单片机能够接收到计算机发出的指令,并响应。但是由于中间发生的意外(51单片机烧坏),使得在最后检测之前还是没能将上、下位机的程序连接起来,控制小车的运行。

 

小车制作过程中遇到的问题:

第一个问题,元器件没有。我们小组材料取得晚,所以几乎没拿到什么材料。最后是电路能简化就简化。MCU采用最小系统,只有一个起振电路和上电复位电路。小车的各部件都是从实验室里各个桌子上东拼西凑出来的。晶振没有,还是在实验室角落了发现一块废弃的板,拆了一个下来。

第二个问题,ISP无法下载程序。花了很长时间找原因,死活找不到。最后在老师的帮忙下,才找到了原因。原来是电路简化太厉害了,把下载线上的一个重要的电阻给忽略掉了。主要是当时没这一阻值的电阻。焊电路板时,我吧确的元件列了个清单,另外一个同学向老师去索取。拿回来时说不需要之类的,一时忽略掉了。

总结经验,调试过程中,碰到问题,要对照电路图一个一个排除原因,不能有先入为主的观念,以为不会缺焊某个元件之类的。

第三个问题,排阻焊反了。这个是个人不小心的过失,后来又去找老师拿了一个,结果8排阻没了,老师就拿了一个8引脚的“7排阻”,这一次特别小心,只焊了几个引脚进行调试,结果死活都不对。这个问题困挠了很长时间。后来实在受不住了,就拿万用表测了8个引脚两两引脚之间的电阻,结果发现根本不是7排阻,而是4个并排的电阻。最后实在没办法,只能用阻值相近的独立电阻焊了。最后总算小车能动了。可时第二天就是最后期限,检查了。我们只剩下一个晚上来写串口通信程序。直接导致最后没完成作品。

在调试过程中还碰到了一些奇怪现象,到现在还没明白是怎么回事。

我们先是在寝室调试的,由于没有8伏的电源,我们就拿电工做得61板的电源,把两个串起来形成9伏电源。没想到最后调试通过了,小车能动了,自检程序通过了。可是后来编写串口程序后发生了莫名奇妙的事情,至今也没明白是怎么回事。程序烧进单片机后再也擦不掉了,导致无法再烧程序进去。

此外,我们小车运行时间长一点之后,L298芯片就变得滚烫,手指碰上去还有轻微烫伤。问其他小组他们只是轻微发烫,我们却烫得离谱,也是到现在也没明白。

 

系统的不足及可改进的部分:

我们在这个科创中所建立的系统还不是一个完整的系统,很多的内容和功能我们还没有能够建立出来,系统还存在很多的不足之处。

l         不足一:我们的程序在运行时需先进行一个前景检测,所谓前景检测就是在程序刚开始时先执行一遍图像的扫描工作,来得到所要推到的目的地坐标值,然后程序再一次地运行检测小车的位置及球的位置。在程序中反映出来的就是,用MFC实现时,需要两个按钮,第一个就是前景检测,在开始后要先按一下;按下前景检测后,再按一下“开始”按钮,程序正式开始。设立前景检测目的是为了在一开始就确定目的地坐标值,在以后的程序执行中就不需要检测了(因为目的地没有特殊的如颜色等特征,不容易在程序运行中检测),但这样的设定所带来的麻烦就是,每次执行程序要按两次按钮,很麻烦;而且最重要的是如果在前景检测后,目的地坐标发生变化,那么程序无法自动检测到新的目的地坐标值。

l         不足二:根据我们的控制小车算法,小车一旦推到物块,即认为小车和物块是一体的,因此如果所推物体是正方体或长方体没什么问题,但如果所推物体是球体的话,问题就出现了,因为推的过程中球可能会因为推力过大而脱离小车,这样的话我们就无法把小车和物块当成一个整体,所以就可能出现小车仍然按照既定路径前进,但所推物块已经偏离轨迹。

l         不足三:我们在小车将物块推到目的地的方法是当小车到达目的地与小车连线处,并且方向与连线方向一致时,小车沿直线前进即推动小球。这里,沿直线前进的方法有两种,第一种是小车行进时根据坐标确定物块与小车之间的连线,看其与目的地与物块的连线偏离多少,当偏离到一定范围之内,沿直线前进;在范围之外,调整小车的方向,使其在范围之内,再继续沿直线前进。第二种是小车沿“S”形前进,即小车的马达在一个时刻只有一个在运转,当其偏离方向时,启动另一个马达,关掉当前马达;又偏离方向时,关掉当前马达,启动另一个。这种工作方法就是一会儿左马达转,一会儿右马达转,最后的效果就是小车沿“S”形前进。这两种方案都有一定的问题,第一个方案的问题是让小车一直前进(两个马达一起转),可能会出现瞬间小车偏离轨道过远,这样的话小车又会经过一段很长时间的调整才能重新回到轨迹;而对于第二种方案,虽然不会偏离很远,但问题是小车的前进速度很慢,而且推的时候因为小车实际上是在转弯(只有一个马达运行),所以推的方向会与应该方向有一定的偏差。

l         不足四:在获取图像时,虽然用到了很多方法,如滤波、二值化等,但仍然不可避免地会出现杂点,影响运行结果。

 

一些改进方案:

1、  对于如何去杂点。除了采用滤波等方法外,一个行之有效的方法是改变背景色,最好是与小车颜色是别所匹配的纯白色或纯黑色。

2、  关于前景检测的问题,考虑根据具体目的地的颜色进行颜色识别,这样即使目的地在动,程序运行时每一次的循环都可以检测到一次的目的地,返回目的地坐标。而且不再需前景检测,使得程序看起来更加简洁。

3、  关于小车运行时的前进方法,只有在完全改变算法的情况下才能有很大的提升。

 

 

自我评价:

我们四个人是来自微电子学院的,我们学院对于电院科创没有学分要求,但我们在做完科创一、二后还是选择了科创三,主要目的是为了锻炼自己的设计、创新能力,提高实验、动手能力。

说实话,我们的科创做得并不理想,在最后的检测中,我们甚至都没能让小车动起来,可以说是比较失败。科创三让我们的最大感触是,它比科创一、二难很多,它不再是老师手把手教你做,而是需要你自己探索,解决问题。我们在做科创三时,一度还没有适应这种工作模式,还希望有什么固定的方法来供我们选择,这也导致了我们前期工作的滞后。这种自主探索问题并且解决问题的工作模式是我们第一次接触的,也是科创三给我们的最大启示,我们明白了,以后做类似的项目时应该更加主动。

我们还知道了一些做这种团队科创时一些必备的技巧和方法。首先,要积极主动地和老师保持沟通。我们做科创三时因为小车拿得比较晚,所以没有拿到小车,要自己焊。在焊的时候会缺一些原件,在少的时候我们往往都是比较迟地找老师去要原件,致使进度拖得比较慢。以后再做这样的项目,遇到类似的问题时应该追着老师去解决问题。其次,我们了解了,应该和其他小组的同学保持良好的沟通。这样可以了解其它组成员的进度以及一些非常好的思想,有助于提高我们自己小组的工作水平。

在技术方面我们收获得更多。在科创的过程中我们学到了很多课堂上无法学到的知识。Open CV的使用,图像处理知识,利用C语言编写控制程序,串口通信的实现,单片机程序的编写。对于我们组来说,比较特别的是我们自己又焊了一遍小车,对于小车的构造、电路非常了解。不仅仅是学到了这些知识,更重要的是在学习的过程中,我们第一次体验到了如何去自学以前从未学过的东西,如何拿到一本很厚的书取其精华而得到自己想要的内容。这种学习中收获的方法和经验比所学到的知识更加有价值。

我们还体会到了调试的辛苦。以前总认为理论上完成了,大部分的工作就结束了,但这次的科创让我们深刻认识到了调试的重要性,无论是软件的调试还是硬件的调试。我们在调试的过程中遇到了各种各样的问题,碰到了各种各样的麻烦,我们花在调试上的时间甚至超过了花在其他方面的时间的总和。这让我们也看到了调试的重要性。

最后想总结一下我们这次科创三的历程:

第三周选课之后,从图书馆借书,学习有关open cvkeilc的有关内容,从网上查阅有关串口通信的内容,尝试写了一些程序,但实际上前9周我们进展不大。

12周期中考试结束后正式开始,但因为拿小车太晚,所以小车是由自己焊接完成的。正式开始写程序,程序分为三块:图像获取与处理;图像坐标获得;小车控制与串口通信。硬件方面有一个同学开始写单片机程序,两个同学共同调试小车。大概15周程序调试完毕,运行无错;小车运转正常。但15周之后我们的进展陷入困境,51连续烧坏,板子出现莫名的错误导致无法正常烧入程序,小车的调试也问题不断。最终我们在检测中没能取得很好的

结果。我们是受困于调试工作而无法取得成功的。

总结下来,我们的科创三虽然结果不甚理想,但过程我们还是很享受的,学到了很多东西,虽然最终我们没调出结果,但我们的每一行程序都是自己编出来的,看着运行结果0 error,我们还是非常欣慰的。如果我们能更多的和其他同学交流思想的话,也许结果就会非常完美了。虽然我们不是电院的学生,但我们以后希望以后还有机会参与科创,我们非常享受科创的过程。

最后感谢张老师这一学期以来给于我们的帮助和耐心指导!

 

 

附录:

   虽然系统没有调试完成,在此还是提供系统的使用说明:系统使用USB接口连接计算机与小车上的单片机,8V与5V双直流供电。打开程序后,需先将物块放在地图上,运行一遍前景检测,然后放上小车,点击开始后程序自动运行,控制小车寻找物块并推向程序预设的目的地。由于算法的限制,在摆放小车时须注意将小车摆在靠近物块的地方,而不是靠近目的地的地方。

 

电脑软件程序源代码及可执行文件

单片机程序源代码及hex文件(根据carselftest程序改变)

系统电路图