# 巡线机器人6--超声波

智能仓库里的机器人成群结队,避免发生碰撞,是十分重要的事。

# 情景分析

  1. 结合之前接触过的超声波传感器,让机器人可以在有阻挡时停下来,多个机器人排队巡线,避免碰撞。

# 流程解析

  1. 程序开始需要定义变量P,T,VL,VR。
  2. 开机后,红外反射传感器是对着纸张的,先记保持纸张感应值P = analogRead(A0)
  3. 开机后机器人随意一个方向转弯,我们已经有了纸张的参考值,转弯以后,无论那一边先碰到胶带,都会出现跟纸张参考值相差很大的感应值,看看谁和P的差异大,就把谁保持到变量T。
  4. 程序进入loop,超声波模块检测障碍物距离
  5. 如果障碍物距离比较远,继续巡线过程,如果障碍物比较近,设置马达速度为0,机器人暂停移动。
uml diagram

# 参考程序

  • oseppBlock IDE程序

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

OseppRangeFinder U(2);
OseppTBMotor L(12, 11);
OseppTBMotor R(8, 3, LOW);
int P = 0;
int T = 0;
int VL = 0;
int VR = 0;

void setup() {
    L.backward(60);
    R.forward(60);
    P = analogRead(A0);  // 开机时,A0,A1都是纸张的感应值
    while (abs(analogRead(A1) - analogRead(A0)) < 300) {
    }
    VR = analogRead(A0);
    VL = analogRead(A1);
    // 谁和纸张的差异大,谁就是胶带
    if (abs(VL - P) > abs(VR - P)) {
        T = VL;
    } else {
        T = VR;
    }
}

void loop() {
    if (U.ping() > 300) {
        VR = analogRead(A0);
        VL = analogRead(A1);
        if (T > P) {
            T = max(T, max(VL, VR));
            P = min(P, min(VL, VR));
        } else {
            T = min(T, min(VL, VR));
            P = max(P, max(VL, VR));
        }
        L.forward(map(VR, P, T, 0, 120));
        R.forward(map(VL, P, T, 0, 120));
    } else {
        // 超声波感应300毫米内有障碍物,马达停止
        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
38
39
40
41
42
43
44
45

# 运行结果

同之前一样放置机器人。打开电源,机器人找到胶带后尝试跟着走。测量到有障碍物时暂停。

# 课程解读

  1. 我们去掉了机器人对于开机时转身方向的假设。现在巡线机器人只要求两个条件:胶带和纸张感应值要大于300,开机时站在胶带上传感器对着白纸。
  2. 速度较快时机器人有时会频繁摆动,我们的程序使用了PID控制的P比例控制。加上I积分,D微分控制能改善这个问题。

# oseppBlock操作视频

# ArduinoIDE操作视频


# 巡线机器人--总结

巡线机器人系列教程主要知识点为变量和数学,如果你是编程初学者,可能仍会觉得变量有些复杂,请坚持尝试使用,慢慢地就熟练了。看看最后的代码,对于机器人编程,代码是非常少的,但是如果不用变量,代码就会增加很多。使用变量能降低了编程的难度和结构复杂度,还可以避免重复计算,加快程序的运行速度。如果没有变量有些功能是实现不了的。
机器人一步步地学会了巡线,我们至今都不知道,最重要的依据:胶带、纸张的感应值大概是多少。甚至后两节程序都没有完整地思考功能实现的原理,只是根据逻辑改进之前的程序。如果你想让机器人做到一些你觉得复杂的事情,可以先放宽要求,再一步步去掉条件。尤其是逻辑和概念比较多的事情。通过逻辑来实现的功能,不要求具体的数字,还能有更好的适应性。

# 巡线机器人系列课程中提到的知识点

  • 红外反射传感器的使用,模拟量的读取。
  • 变量的定义,使用方法,变量名的要求。变量运用要注意变量的类型和范围。
  • 数学运算+(加),-(减),*(乘以),/(除以),%(模除)以及绝对值函数abs
  • 逻辑运算A&&B(与运算),A||B(或运算),!A(非运算)。
  • 最大值函数max(a,b),最小值函数min(a,b)

# 附:PID控制的巡线程序

默认启用PID控制,"L"指示灯灯亮。使用杜邦线将A5接到GND,则程序单独使用P控制,"L"指示灯灭。
PID控制并不能提高最高速度,只能提高巡线的流畅程度。通过平稳速度来减少调整姿态的时间。
程序中给定的参数并不是最优参数,如果有兴趣请搜索PID参数整定
待改进:程序中的超声波测量时间是变化的,这影响了PID的周期。需要将PID控制代码用定时器定时执行,loop函数内只负责超声波检测。如果只想看看能带来多少改善,请将if (U.ping() > 300)修改为if(1)来试试。

#include <oseppRobot.h>

OseppRangeFinder U(2);
OseppTBMotor L(12, 11);
OseppTBMotor R(8, 3, LOW);
int P = 0;
int T = 0;
int VL = 0;
int VR = 0;
float el = 0;
float er = 0;
float el_last = 0;
float er_last = 0;
float el_sum = 0;
float er_sum = 0;
float sl;
float sr;

//p调节,参数代表最高的正常速度。应最先确定p参数。
//单独用P参数控制时,即使存在不稳定,但仍然能保持不脱线的数字就是合适的。
//推荐范围0.3到0.6。每次调整0.1
float p = 0.5;

//d调节起预测调节作用
//在姿态调整初期时发现没有作用,加大调整力度。
//在姿态调整末期,预测可能会调整过头,减小调整力度。
//确定p参数后调整d参数,逐渐增大,机器人的震荡会从改善到恶化过度,选择合适的值。
//推荐范围在20到40。每次调整5
float d = 25.0;

//i调节让机器人不那么敏感地频繁调整,容许些许偏差。i参数最后确定。
//微微增加i参数,改善震荡情况。见好就收。
//推荐范围在0.001到0.005。每次调整0.001
float i = 0.002;

void setup() {
    pinMode(A5, INPUT_PULLUP);
    pinMode(13, OUTPUT);
    L.backward(60);
    R.forward(60);
    P = analogRead(A0);
    while (abs(analogRead(A1) - analogRead(A0)) < 300) {
    }
    VR = analogRead(A0);
    VL = analogRead(A1);
    if (abs(VL - P) > abs(VR - P)) {
        T = VL;
    } else {
        T = VR;
    }
}

void loop() {
    if (U.ping() > 300) {
        VR = analogRead(A0);
        VL = analogRead(A1);
        if (T > P) {
            T = max(T, max(VL, VR));
            P = min(P, min(VL, VR));
        } else {
            T = min(T, min(VL, VR));
            P = max(P, max(VL, VR));
        }
        //使用PID计算左边轮子的速度
        el = map(VR, P, T, 0, 255);
        el_sum += el;
        el_sum = constrain(el_sum, -10000, 10000);
        sl = el * p + el_sum * i + (el - el_last) * d;
        el_last = el;
        //使用PID计算右边轮子的速度
        er = map(VL, P, T, 0, 255);
        er_sum += er;
        er_sum = constrain(er_sum, -10000, 10000);
        sr = er * p + er_sum * i + (er - er_last) * d;
        er_last = er;
        //A5端口用作数字端口
        if (digitalRead(A5) == HIGH) { //如果是高电平,使用PID控制。
            digitalWrite(13, HIGH);    //并点亮控制器自带的LED
            L.forward(sl);             //设置马达速度
            R.forward(sr);
        } else {                       //如果是低电平,单独使用P控制。
            digitalWrite(13, LOW);     //并熄灭控制器自带的LED
            L.forward(el * p);         //设置马达速度
            R.forward(er * p);
        }
    } 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90