发表日期:2017-02-17 09:39:20文章编辑:亮宁电子浏览次数: 标签:
一、预备知识
分治思想也称分治算法,指在面对一个信息量大、结构复杂的问题时,将这个问题拆分成若干个相对简单、易于处理的小问题,再根据各个小问题的具体特点和情况,采取针对性处理,分而治之,从而更为合理、高效、准确的处理复杂问题的程序编写方法。
二、理论分析
在巡线过程中遇到路口时,我们总是习惯于用2个以上的灰度传感器(排除最中间和左右两个传感器同时看到的情况)检测到黑线作为条件来判断路口。这种方法在一定的误差条件下的确是合理可行的,然而实际上,机器人在巡线的过程中遇到路口的形式是多种多样的(如图1),我们仅用一些简单的条件来判断所有的路口,必然对准确性和稳定性,和处理效率都可能带来较大的冲击。
例如在图2所示的路口1中,最靠左边的两个传感器正常情况下是不会看到黑线的,而我们的路口判断条件中还是包含了它们看到黑线的情况,这样不仅出现了无效的语句,还增加了平时巡线过程中误判路口的可能性。同理,在图2所示的路口2中,只有中间和最左边的传感器可以同时看到黑线,相较于我们常用的路口判断,其条件的准确性就值得商榷。
因此,如果想要进一步提高机器人判断路口的准确率和稳定性,则需要对每一个不同的路口,针对其特点单独设计判断条件。路口判断变得更加准确,也会进一步降低非路口巡线过程中误判的可能性,从而提高整个巡线过程的稳定性。
图1.多种多样的路口
图2.路口分析
因此,我们把整个赛道以路口为分界点分成若干个小的赛道,每个小的赛道包含两个部分的内容:非路口巡线(基本巡线)和路口巡线两个部分。其中基本巡线总是一样的,为了方便起见,我们会用一个子程序来编写这部分内容,具体见之后的程序分析。而路口巡线则是根据这部分路口的特点,单独写出判断条件,进行针对性处理。以上即是分治法在巡线过程中的具体体现。
三、程序详解
如图3所示,我们按路口把要走的路线分成5段。每段从基本巡线开始到处理完路口结束,然后紧接着下一段的基本巡线,从而实现段与段的衔接,完成整个巡线过程。
图3.路线分段示例
程序如下:
#include <LNDZ.h>
int ll,l,m,r,rr,n;
void findline()
//子程序定义,findline为程序名,花括号内的语句即是之后调用该子程序时将会运行的内容。
//子程序定义需要写在init()函数之前,程序的内容在定义时不会被运行,仅仅是为之后调用做准备。
//该子程序的内容就是基本巡线代码。
{
ll=AR(4)<619;//这里的值只是示例,具体值需实测。
l=AR(2)<632;
m=AR(1)<509;
r=AR(3)<644;
rr=AR(5)<684;
n=ll+l+m+r+rr;
if(ll==0) motor(-40,40);
else if(l==0) motor(0,40);
else if(m==0) motor(40,40);
else if(r==0) motor(40,0);
else if(rr==0) motor(40,-40);
}
void init()
{
B_start();
motor(40,40);
delay(150); //冲出起始的路口
while(1)
//while循环语句,小括号内是循环判断条件,判断条件成立返回1,循环继续,条件不成立返回0,循环跳出。
//当小括号内为非0的常数时,如1,2,3,100等,则表示条件总是成立的,循环一直进行,除非用break中断循环。
{
findline();
//调用基本巡线子程序,计算机自动运行定义时花括号内的所有内容,用于完成看到路口前的巡线过程。
if(n<2)
//第一个路口为十字路口,任意4个或5个传感器看到即可认为看到路口。
{
motor(40,40);
beep(150);
break; //break语句用于结束上一个while循环,不然程序无法转入下一段。
}
}//到此为止,第一段的巡线结束。
while(2)
//括号内的数字只要不是0都可以,这里用2仅仅是为了方便我们判断这是第几个路口的程序。
{
findline(); //每一个小段都需要一个基本巡线代码,别忘了。
if(m+r+rr==0||m+rr==0||r+rr==0)//根据路口特点写判断条件
{
motor(40,0);
beep(200);
break;
}
}
while(3)
{
findline();
if(l+r==0||ll+rr==0)
{
motor(40,0);
beep(200);
break;
}
}
while(4)
{
findline();
if(m+rr==0)
{
motor(40,0);
beep(200);
break;
}
}
while(5)
{
findline();
if(n<2)
{
motor(0,0);
break;
}
}
}
void repeat()
{
}
注意事项:
1、因为我们自己构造了单独的循环指令,所以只需要顺序执行,程序放在init()里即可。如果我们要放入repeat()函数中,还需要在最后加上while(1);,用于结束repeat()函数循环,不然程序又将重头开始。
2、在子程序中,我们把传感器实时检测路况和调节电机速度合在了一起,作为基本巡线部分。而一旦我们需要根据路况的不同,在各部分采用不同的电机速度时,就要将该子程序拆分为两个子程序:一个检测路况void check(),一个控制电机速度void find1()。如果我们需要不同的电机速度,只需要再定义一个void find2(),把其中的电机速度加以修改,然后根据情况确定调用哪一个。
例如,在图3所示路线中,第一和第二段都是直线,我们可以把速度加快,从而节约时间;而在后几段路线中,路口、弯道较多,我们需要适当降低速度,保证稳定性。
程序如下:
#include <LNDZ.h>
int ll,l,m,r,rr,n;
void check() //检测路况的子程序
{
ll=AR(4)<619;//这里的值只是示例,具体的值需要自己测。
l=AR(2)<632;
m=AR(1)<509;
r=AR(3)<644;
rr=AR(5)<684;
n=ll+l+m+r+rr;
}
void find1() //电机速度较快的巡线子程序。
{
if(ll==0) motor(-40,40);
else if(l==0) motor(0,40);
else if(m==0) motor(60,60);
else if(r==0) motor(40,0);
else if(rr==0) motor(40,-40);
}
void find2()//电机速度较慢的巡线子程序。
{
if(ll==0) motor(-40,40);
else if(l==0) motor(0,40);
else if(m==0) motor(40,40);
else if(r==0) motor(40,0);
else if(rr==0) motor(40,-40);
}
void init()
{
B_start();
motor(40,40);
delay(150);
while(1)
{
check();
find1();//同时调用两个子程序,加起来就组成了非路口的巡线程序。
if(n<2)
{
motor(40,40);
beep(150);
break;
}
}
while(2)
{
check();
find1();
if(m+r+rr==0||m+rr==0||r+rr==0)
{
motor(40,0);
beep(200);
break;
}
}
while(3)
{
check();
find2();//进入第三段巡线过程,因为路口多,所以调用电机速度较慢的find2()。
if(l+r==0||ll+rr==0)
{
motor(40,0);
beep(200);
break;
}
}
while(4)
{
check();
find2();
if(m+rr==0)
{
motor(40,0);
beep(200);
break;
}
}
while(5)
{
check();
find2();
if(n<2)
{
motor(0,0);
break;
}
}
}
void repeat()
{
}
3、我们把一些功能单独写成一个函数是有优势的。它使程序结构条理更为清晰,反复调用时更为方便,也符合程序结构化、模块化的思想。
4、利用分治思想,巡线程序被分成了一段一段的功能模块,虽然略显重复,但当出现错误、或进行细节调试时,就突显优势了。
四、路口判断示例。
如何根据不同的路口写出合理的路口判断条件是分治法巡线的关键。如果写的条件过于苛刻,则可能容易漏过路口,造成漏判。如果写的条件太宽松,则可能会在非路口巡线过程中把一些弯道误判为路口。因此,在我们写路口判断条件时,m+r==0和m+l==0两种条件因为在转弯的过程中特别容易满足,造成误判的几率特别大,所以尽可能不要使用。
这里提供了一些路口判断条件的参考,具体要怎么选择需要我们反复测试而定,直到找到一个最合理的方案。(n=ll+l+m+r+rr; n=0时,是所有传感器都遇到黑线)