循迹避障智能小车的制作实例

题目

D智能电动小车.pdf

一、任务

设计并制作一个寻迹智能电动车,根据要求完成从出发区到终点区的任务:

二、要求

1、基本要求

  1. 电动车从出发区出发(车体不得超出出发区),沿引导黑线向终点区行驶,电动车行驶过程中不可脱离黑色引导线行驶。
  2. 电动车行驶过程中遇到十字路口时发出声光指示信息。
  3. 电动车避开障碍物通过不得与其接触且选择最短行驶距离到达终点区。
  4. 电动车到达终点后应立即停车,但全程行驶时间不能大于 60 秒,行驶时间达到60秒时必须立即自动停车。

2、发挥部分

  1. 电动车行驶过程中遇到正立放置的障碍物电动车必须选择向左转避开障碍物,遇到倒立放置的障碍物电动车必须选择向右转避开障碍物,电动车不得接触障碍物。
  2. 电动车在完成一次完整的行程(即完成一次基本要求)后,拿走场地所有障碍物,电动车重新拿至出发区,重新起动,能重复上次行驶路线到达终点区。
  3. 电动车进入终点区域后,能进一步准确驶入终点区,要求电动车的车身完全进入终点区到达终点区中心。停车后,能准确显示电动车全程行驶时间和路程。

四、说明

  1. 场地上面铺设白纸,可用一张A0或者两张A1纸制作。
  2. 场地的引导线宽度2cm,可以涂墨或粘黑色胶带。示意图中的和尺寸标注线不要绘制在白纸上,出发区和终点区的边框为 25cm*25cm 用签字笔细线标注。
  3. 电动车出发方向由测评专家指定,可选择(如图)正X 方向或正Y 方向。
  4. 障碍物为饮用水瓶使用 380ml的农夫山泉空瓶,场地上可允许有最多两个障碍物(也可只有一个,也可以放置两个,由测评专家指定),放置位置可在任意十字路口中间位置(T 字路口不放置) 。
  5. 电动车允许用玩具车改装,但不能由人工遥控,其外围尺寸(含车体上附加装置)的投影,长<30cm,宽*20cm。
  6. 要求在电动车顶部明显标出电动车的中心点位置,即横向与纵向两条中心线的交点。

成品展示


↑调试过程中拍的 车身上全是模块

现在已经缩短了车身 重新做了板子,两个瓶子的各种情况都能在11-25秒到达终点。
图片展示:

方案确定

1.循迹

循迹模块,使用红外收发管作为传感器即可。
需要在方格中直行、转弯,且能判断十字、T字路口,至少需要使用三路传感器。
这里使用了五路,多了两路辅助判断车体的体势。

2.避障/判断正倒立

同样使用红外一体管,通过区分当前高度能否接收到一定量的反射光来判断正瓶子的正倒立。

3.主控

选了自己熟悉的STM32

4.小车车身和电机

选用容易控制角度的2只舵机+1只导向轮。
车身用现成的套件组装。

程序部分

最重要的部分为“where_to_go.c”路径决策程序,以及“ctrl_system.c”控制主程序,使用一个平面状态机来完成功能。

总体描述

控制主程序

状态转换图

这个转换图应该挺难看懂,圆圈里的状态,与下面这个状态枚举是一一对应的:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef enum SystemState{
NONE, //初状态
INIT, //就绪,等待用户操作
GO_STRAIGHT, //直行中
TURN_LEFT, //普通左转中
TURN_RIGHT, //普通右转中
TURN_LEFT_EX, //紧急左转中
TURN_RIGHT_EX, //紧急右转中
GO_NEXT_GRID, //直行进入下一个格子
STOPPING, //完成任务 正在停止
COMPLETED, //完成任务后显示结果
STATE_UNKNOW, //未知状态
}SystemState;

这里列举一下”steering.h”(舵机驱动)供外部调用的函数(其实这个函数集,还是少了一个“?轮前进?角度”的功能的函数,现在用延迟代替,偷懒了呵呵)

1
2
3
4
5
6
7
8
9
void STEERING_Init(void);
void STEERING_GoLeft(void); //向左走
void STEERING_GoRight(void); //向右走
void STEERING_GoStraight(void); //直走
void STEERING_GoStraightSlow(void); //缓慢直走
void STEERING_GoBack(void); //倒退走
void STEERING_Stop(void); //停止
void STEERING_TurnLeft(void); //向左转
void STEERING_TurnRight(void); //向右转

总之,这个图想表达的信息,就是——探测障碍物和做决策的时间点:

  1. 直行中,遇到十字路口时–>做决策–>普通转弯
  2. 普通转弯后–>做决策–>紧急转弯
  3. 紧急转弯后–>做决策–>紧急转弯

(可以看出“普通转弯”和“紧急转弯”的应用时机不同,因为车的体态不一样,这里的普通转弯有一个轮子前进半圈的过程(为了转弯后,车身能与轨迹平行),而紧急转弯是直接转动车身。)
每当“做决策”的时候,控制程序便调用“where_to_go.c”的函数,告诉决策模块:前方是否有障碍物(以及障碍物的正倒立情况)、是否达到了下一个十字路口(供决策程序计算更新当前坐标),而决策程序返回“该向左还是向右走”的决策。

决策程序

这里实现的决策程序最终得到的,并不是最优路径,这里用了一种偷懒的方式。

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
91
92
93
94
95
96
97
98
99
100
typedef enum{
DIR_HEAD_EAST,
DIR_HEAD_SOUTH,
DIR_HEAD_WEST,
DIR_HEAD_NORTH,
DIR_HEAD_UNKNOW,
}DIR_HEAD; //小车朝向

typedef enum {
DIR_GO_STRAIGHT,
DIR_GO_LEFT,
DIR_GO_RIGHT,
DIR_GO_STOP,
DIR_GO_UNKNOW,
}DIR_GO; //行驶方向

typedef struct{
uint8_t x;
uint8_t y;
DIR_HEAD dir;
}Poi; //当前朝向

/*当前状态记录变量*/
Poi nowPoi;
uint8_t replayMode; //是否为replay模式

/*历史状态记录变量*/
DIR_GO lastGoDir;
#define HISTORY_LENGTH 32
DIR_GO history[HISTORY_LENGTH]; //历史决策
uint8_t history_ptr; //历史决策ptr
Poi barrierPoi[5]; //障碍物位置记录
uint8_t barrierPoiPtr; //障碍物位置记录ptr

/*路程统计*/
uint8_t distance;

/*决策表 [x][y][dir] */
#define X_LENGTH 8
#define Y_LENGTH 5
DIR_GO WTG_TABLE[X_LENGTH][Y_LENGTH][4];

/**
参数goDir表示是否由WTG决策
goDir= GO_DIR_UNKNOW 询问决策程序应该往哪走
OTHER 告诉决策程序 自己已经做好的决策

参数reachPoint表示是否已经到达下一个点了
reachPoint = 0 未到达下一个点
1 已经到达下一个点
**/

DIR_GO WTG_Go(DIR_GO goDir,uint8_t reachPoint){
/*----------replay模式----------*/
if(replayMode){
if(history[history_ptr] == DIR_GO_STRAIGHT) distance++; //统计路程
return history[history_ptr++];
}
/*----------普通模式----------*/
/*若已移至下一个路口 先计算当前点*/
if(reachPoint){
nowPoi=move(DIR_GO_STRAIGHT,nowPoi);
}
if(goDir != DIR_GO_UNKNOW){
/*----------前方有障碍 已确定这次的转向----------*/
/*已决策 说明正前方存在障碍 记录障碍*/
Poi barrierPoi = move(DIR_GO_STRAIGHT,nowPoi);
saveBarrierPoi(&barrierPoi);
}else{
/*----------前方无障碍 需要决策----------*/
/*读决策表*/
goDir = WTG_TABLE[nowPoi.x][nowPoi.y][nowPoi.dir];
/*若新方向上有已知障碍 则直行*/
Poi nextPoi = move(goDir,nowPoi);
if(checkBarrierPoi(&nextPoi)){
/*有障碍*/
goDir = DIR_GO_STRAIGHT;
}
}
/*记录路径 给replay模式用*/
history[history_ptr++] = goDir;
/*统计路程*/
if(goDir == DIR_GO_STRAIGHT) distance++;
/*记录小车新方向 但坐标不移动(等下次遇到十字路口再计算)*/
Poi newPoi = move(goDir,nowPoi);
nowPoi.dir = newPoi.dir;
return goDir;
}

/**
以下为函数原型,不贴出具体实现
**/
void saveBarrierPoi(Poi *bPoi);//障碍物记录
uint8_t checkBarrierPoi(Poi *checkPoi);//检查某点是否存在障碍物
Poi move(DIR_GO dirGo,Poi poi);//计算移动一格后的Poi
void createdWTGTable(void);//构建决策表
/* 初始化,startDir=初始朝向,isReplayMode=是否为回放模式 */
void WTG_Init(DIR_HEAD startDir,uint8_t isReplayMode);
uint8_t WTG_Go_GetDistance();//取当前行程

决策程序的主体思想是:
事先人工构建一个决策表(这里的DIR_GO WTG_TABLE[X_LENGTH][Y_LENGTH][4]),由当前的坐标和车身方向,确定一个新的行走方向(直走,左转,右转或者停止)。

  1. 普通模式下:当行车路线不由障碍物正倒立情况决定时,根据决策表决定方向,并记录决策
  2. 回放模式下:无视障碍物情况,完全依据历史记录行走。

其中还有一个小细节,就是确定前方有障碍物之后,应记录下来,确保之后不会走到那个点(checkBarrierPoi函数)。

一些细节

到这里,小车主体功能已经实现了,但是实际调试的时候,会发现一些问题。

  1. 避障模块,区分正倒立的特殊情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //bit0 1:上面两个探测器,bit2 3:下面两个探测器
    const BARRIER_TYPE DATA_TYPE_TABLE[16] =
    {
    BARRIER_TYPE_NONE, //0000
    BARRIER_TYPE_DOWN, //0001
    BARRIER_TYPE_DOWN, //0010
    BARRIER_TYPE_DOWN, //0011
    BARRIER_TYPE_UP, //0100
    BARRIER_TYPE_UP, //0101
    BARRIER_TYPE_UP, //0110
    BARRIER_TYPE_DOWN, //0111
    BARRIER_TYPE_UP, //1000
    BARRIER_TYPE_UP, //1001
    BARRIER_TYPE_UP, //1010
    BARRIER_TYPE_DOWN, //1011
    BARRIER_TYPE_UP, //1100
    BARRIER_TYPE_UP, //1101
    BARRIER_TYPE_UP, //1110
    BARRIER_TYPE_UP, //1111
    };
  2. 循迹传感器模块 传感器间距的问题
    最好让间距正好合适:在未脱轨时,靠前的三个传感器,有且只有一条感应到轨迹。
    这样能让车子的前进路线更直。

  3. 车身惯性的问题
    注意放慢速度以及适当停止运作一段时间即可。

总结

程序编写,总共耗时三天半,一天半完成各部分驱动以及大致流程,一天研究路径决策,一天边调试边修改程序..
最麻烦的地方还是路径决策..其他功能都是可以慢慢调试出的.
这个路径决策,我还是不满意,理想的结果应该是:每获得新的障碍物情报,根据当前位置和情报重新计算具体路线,若路线上再发现情况,再计算新路径。算法还是得多积累。

写在作品验收之后

验收时看到不少同学的作品,发现了一些别人做这题出现的问题:

  • 调速不到位,不知道是使用了差的减速齿轮还是程序没写好,小车速度降不下来
  • 传感器配置不合理,有人做了个——前2个红外对管,后2个红外对管,到最后都没调试出来——没事别给自己增加额外的难度
  • 程序编写问题,尽量用状态机思路吧,别想着全靠延迟来完成这东西,现实中可变因素太多了
  • 调试场地和实际场地不符,这肯定要跪

2015-10-19

比赛结束了,我把资料包放出来吧.

完整程序下载