# 巡线机器人3--巡线
找到并认出胶带后,根据传感器的情况调整马达,实现自主巡线行驶。
# 情景分析
- 上一节机器人找到了胶带,并停住,一个传感器在胶带上,另一个在纸张上。如果机器人继续移动,怎么确认是否仍在胶带上?第一节里面我们知道,感应到的数字是不稳定的,不能假设等于多少是在胶带上。碰到胶带时,可以知道胶带和纸张的感应值,计算一个刚好在它们中间的数字,保存到变量做参考。再次测量如果感应值大于这个数字,就认定是在胶带上(胶带的感应值比较大)。这样即使数字有小变动,也不会判断错。
- 中间数(平均数)就是两个数各取一半再加起来。例如,感应纸张得到的数值是400,胶带上的数值是800,那么计算后的中间数是600。以后只要该传感器检测到大于600,就认为该传感器在胶带上。
- 根据参考值分别判断两个传感器是否在胶带上,并适当调整机器人的动作。
# 流程解析
- 程序开始需要
int M = 0
定义一个变量M,来保存计算好的中间数,后面要用它来作参考。 - 再定义两个变量
boolean SL = false
,boolean SR = false
,保存左边/右边是否在胶带上的判断结果。因为设置方向时要多次用到这个结果。 - 上一节机器人右转,并碰到胶带,停住。现在右边的传感器是在胶带上(黑色胶带数值比较大),左边传感器在纸张上(白张数值比较小)。根据公式,
M = analogRead(A0) + analogRead(A1)
,M /= 2
计算两个传感器数值的中间数并保存到变量M。 - 分别读取两个传感器的值,各自判断它们是否大于中间数M(因为胶带的数值比较大,大于中间数代表比较像胶带),并将结果保存到变量
SL = analogRead(A1) > M
,SR = analogRead(A0) > M;
- 用if else-if根据两个传感器的组合情况,设置合适的马达速度。
# 参考程序
- 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);
}
}
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:可以表示,即(0-255)
- word:,即(0-65535)
- dword:,即(0-4294967295)
- qword:,即(0-18446744073709551615)
我们还会用到负数,在数字前面添加一个负号表示。但是电子系统不认识符号,只认识0和1 。所以负号在电子系统里面也是用0(表示正数),1(表示负数)来表示的,并规定开头的一位比特用来表示符号。对于byte本来有8个比特,如果用1个比特来表示符号,只剩下7个比特表示数字了。7个比特可以表示,范围为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,限于篇幅如果需要了解,请使用搜索引擎搜索。
如果你看了变量的基本知识,觉得半知半解,是很正常的。后面的课程里还有更多的使用例子,多看看怎么用就学会怎么用了。
# 课程解读
- 机器人不是原地转弯,而是向左前方或者右前方行驶的,以保持机器人总会前进,不会在原地摇头。
- 变量需要先定义后使用,定义格式是
变量类型 变量名 = 变量的初始值
,其中初始值可以不设置(等号也要去掉)。变量名只能是字母,数字和下划线组成,并且不能是数字开头。注意变量名是区分大小写的,例如int a
和int A
是两个不同的变量。 - 程序里面见到了两种变量类型,一个是
int M
。能表示的范围是-32768到32767。因为我们的传感器读取到的数据是0到1023,即使两个加起来,最大范围是从0到2046,没有超过范围,所以不需要选用更大的变量类型。 - 另一个变量类型是布尔变量 boolean ,它代表两种逻辑状态(实际上占用了1个字节),
真
或者假
,也可以理解为是
/非
,成立
/不成立
。通常用来保存判断表达式的结果。要么在胶带上,要么就在纸上,这样的关系合适用布尔变量来保存。每个循环都需要多次判断传感器是否在胶带上,所以使用变量SL,SR,把计算结果暂时存起来,后面就不用再次计算了。SL,SR两个变量代表的是左边,右边传感器是否在胶带上。如果大于中间值,就是在胶带上。否则就是在白纸上。 - 变量的设置格式是
变量名=表达式
,还可以用变量名/=表达式
这样的格式,表示变量名=变量名 / 表达式
。可用的符号有+
(加),-
(减),*
(乘以),/
(除以),%
(模除,求余数,例如) - 程序里还见到了一个逻辑运算
A&&B
(与运算),与运算要求两个表达式(或者布尔变量)同时成立,最终结果才成立。程序里是要求左边传感器和右边传感器同时在胶带上。除了与运算,还有A||B
(或运算),A和B只要有一个成立,最终结果就成立。!A
(非运算),如果A成立,最终结果是不成立,如果A不成立,最终结果成立。 - 运算符时有优先级的,例如
a + b * c
,会先计算b*c再加上a。