# 巡线机器人3--巡线

找到并认出胶带后,根据传感器的情况调整马达,实现自主巡线行驶。

# 情景分析

  1. 上一节机器人找到了胶带,并停住,一个传感器在胶带上,另一个在纸张上。如果机器人继续移动,怎么确认是否仍在胶带上?第一节里面我们知道,感应到的数字是不稳定的,不能假设等于多少是在胶带上。碰到胶带时,可以知道胶带和纸张的感应值,计算一个刚好在它们中间的数字,保存到变量做参考。再次测量如果感应值大于这个数字,就认定是在胶带上(胶带的感应值比较大)。这样即使数字有小变动,也不会判断错。
  2. 中间数(平均数)就是两个数各取一半再加起来M=A2+B2=A+B2M=\frac{A}{2}+\frac{B}{2}=\frac{A+B}{2}。例如,感应纸张得到的数值是400,胶带上的数值是800,那么计算后的中间数是600。以后只要该传感器检测到大于600,就认为该传感器在胶带上。
  3. 根据参考值分别判断两个传感器是否在胶带上,并适当调整机器人的动作。

# 流程解析

  1. 程序开始需要int M = 0定义一个变量M,来保存计算好的中间数,后面要用它来作参考。
  2. 再定义两个变量boolean SL = false ,boolean SR = false,保存左边/右边是否在胶带上的判断结果。因为设置方向时要多次用到这个结果。
  3. 上一节机器人右转,并碰到胶带,停住。现在右边的传感器是在胶带上(黑色胶带数值比较大),左边传感器在纸张上(白张数值比较小)。根据公式,M = analogRead(A0) + analogRead(A1),M /= 2计算两个传感器数值的中间数并保存到变量M。
  4. 分别读取两个传感器的值,各自判断它们是否大于中间数M(因为胶带的数值比较大,大于中间数代表比较像胶带),并将结果保存到变量SL = analogRead(A1) > MSR = analogRead(A0) > M;
  5. 用if else-if根据两个传感器的组合情况,设置合适的马达速度。
uml diagram

# 参考程序

  • oseppBlock IDE程序

  • Arduino IDE程序
#include <oseppRobot.h>

OseppTBMotor L(12, 11);
OseppTBMotor R(8, 3, LOW);
int M = 0;           //保存纸张和胶带感应值的中间值,作为参考
boolean SL = false;  //左边传感器是否在胶带上
boolean SR = false;  //右边传感器是否在胶带上

void setup() {
    L.forward(60);
    R.backward(60);
    while (analogRead(A0) - analogRead(A1) < 300) {
    }
    //计算参考值
    M = analogRead(A0) + analogRead(A1);
    M /= 2;
}

void loop() {
    // 用参考值分别判断左边的传感器和右边的传感器有没有在胶带上
    SL = analogRead(A1) > M;  //胶带的值比较大,大于参考值就判定为在胶带上
    SR = analogRead(A0) > M;
    // 根据传感器状态,决定马达的方向
    if (SL && SR) {
        L.forward(100);
        R.forward(100);
    } else if (SL) {
        L.forward(0);
        R.forward(100);
    } else if (SR) {
        L.forward(100);
        R.forward(0);
    } else {
        L.forward(0);
        R.forward(0);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 运行结果


同图片中那样放置机器人,两个轮子都在胶带上,传感器对着纸张。打开电源,机器人开始缓慢右转,传感器碰到胶带时,刚好是前进方向。机器人尝试跟着胶带走,不小心走到胶带外面时停止。尝试调整机器人的速度来适应场景。
如果机器人表现为躲开胶带,请检查红外反射传感器和A1,A0接线是否颠倒了。
留意电池电量,如果电量不足,传感器会不稳定,探测到错误的数值。

# 变量

在电子世界里,最小的单位是bit(比特),它只能表示0或者1 。如果我们要表示2,就需要用两个比特来表示,比特00表示数字0,比特01表示数字1,比特10表示数字2,比特11表示数字3 。类推,如果要表示更大的数字,就需要更多的比特。我们常用的比特数是8、16、32、64比特。分别给它们命名为byte、word、dword、qword。按照推理

  • byte:可以表示2812^8-1,即(0-255)
  • word:21612^{16}-1,即(0-65535)
  • dword:23212^{32}-1,即(0-4294967295)
  • qword:26412^{64}-1,即(0-18446744073709551615)

我们还会用到负数,在数字前面添加一个负号表示。但是电子系统不认识符号,只认识0和1 。所以负号在电子系统里面也是用0(表示正数),1(表示负数)来表示的,并规定开头的一位比特用来表示符号。对于byte本来有8个比特,如果用1个比特来表示符号,只剩下7个比特表示数字了。7个比特可以表示2712^7-1,范围为0-127,负数时范围是1-128(负数不需要表示0),所以有符号数

  • byte:可以表示-128到+127。
  • word:可以表示-32768到+32767。
  • dword:可以表示-2147483648到+2147483647。
  • qword:可以表示-9223372036854775808到+9223372036854775807。

电子系统通过用补码表示有符号数 ,有符号数和无符号数的运算都可以用同样的电路来计算,所以对于电路而言它并不关心一个数有没有符号。例如比特11111111,对于电子系统,它就是8个1 。而对于我们,可以认为是有符号数字“-1”,也可以认为是无符号数“255” 。这是程序里特别特别需要注意的地方。例如:analogRead(A0)-analogRead(A1)<300 ,假如A0感应到数字6,A1感应到数字8 ,6-8结果应该是-2,小于300 。可是控制器可能不那么认为。因为电路计算6-8无论是有符号无符号最后的结果都是比特1111111111111110,也可以认为是无符号数65534,是大于300的。得到哪一个结果,是定义的时候就决定了的。analogRead的定义是int analogRead(pin),int在我们的控制器里是2个字节表示的有符号数。在我们的控制器里常用的类型是:

  • byte:8位有符号数,有符号的完整定义是signed byte,可以省略掉signed。无符号数要在前面加unsigned,例如unsigned byte v
  • char:相当于byte,通常用于保存ascii码。可能会被特殊处理把不在ascii码范围的字符转成空格。
  • int:16位有符号数,无符号数为unsigned int
  • long:32位有符号数,无符号数为unsigned long
  • long long:64位有符号数,无符号数为unsigned long long

如果没有定义类型,默认类型是int(int在不同的硬件中位数是可能不同的),例如if(6-8<300)是成立的。当两个不同类型的对象参与运算的时候,类型会被自动转换。转换规则是将运算符右边的对象转换成左边的对象类型。很多时候这不够严谨,强制类型转换可以消除这种歧义,例如:if ((unsigned int)(6 - 8) > 300) {Serial.println("6-8>300");}。还需要注意一点,强制转换高位数到低位数,会字节把多余的比特丢掉,例如:if((byte)(256)==0),256对应比特100000000,有9个比特,而byte只能容纳8比特,会把开头的1丢掉,变成00000000,结果为0。
我们的控制器是8位的处理器,可以同时处理8个比特。对于多于8比特的操作,需要分解成若干步骤执行。所以位数越少的,运算速度越快。同时用于变量的内存也非常有限。总共只有2048字节(Byte),有时会不够用。使用变量,除了考虑类型,还要考虑范围,例如:byte a=555,实际上a会是43,a=a+256,结果还是43。上面提到这些统称为整型变量,另外还有浮点数float,限于篇幅如果需要了解,请使用搜索引擎搜索。

如果你看了变量的基本知识,觉得半知半解,是很正常的。后面的课程里还有更多的使用例子,多看看怎么用就学会怎么用了。

# 课程解读

  1. 机器人不是原地转弯,而是向左前方或者右前方行驶的,以保持机器人总会前进,不会在原地摇头。
  2. 变量需要先定义后使用,定义格式是变量类型 变量名 = 变量的初始值,其中初始值可以不设置(等号也要去掉)。变量名只能是字母,数字和下划线组成,并且不能是数字开头。注意变量名是区分大小写的,例如int aint A是两个不同的变量。
  3. 程序里面见到了两种变量类型,一个是int M。能表示的范围是-32768到32767。因为我们的传感器读取到的数据是0到1023,即使两个加起来,最大范围是从0到2046,没有超过范围,所以不需要选用更大的变量类型。
  4. 另一个变量类型是布尔变量 boolean ,它代表两种逻辑状态(实际上占用了1个字节),或者,也可以理解为/成立/不成立。通常用来保存判断表达式的结果。要么在胶带上,要么就在纸上,这样的关系合适用布尔变量来保存。每个循环都需要多次判断传感器是否在胶带上,所以使用变量SL,SR,把计算结果暂时存起来,后面就不用再次计算了。SL,SR两个变量代表的是左边,右边传感器是否在胶带上。如果大于中间值,就是在胶带上。否则就是在白纸上。
  5. 变量的设置格式是变量名=表达式,还可以用变量名/=表达式这样的格式,表示变量名=变量名 / 表达式。可用的符号有+(加),-(减),*(乘以),/(除以),%(模除,求余数,例如10%3=110\%3=1)
  6. 程序里还见到了一个逻辑运算A&&B(与运算) ,与运算要求两个表达式(或者布尔变量)同时成立,最终结果才成立。程序里是要求左边传感器和右边传感器同时在胶带上。除了与运算,还有A||B(或运算) ,A和B只要有一个成立,最终结果就成立。!A(非运算) ,如果A成立,最终结果是不成立,如果A不成立,最终结果成立。
  7. 运算符时有优先级的,例如a + b * c,会先计算b*c再加上a。

# oseppBlock操作视频

# ArduinoIDE操作视频