更新于 

软件设计

比赛开始后,通过软开关启动机器人进入上台程序,同时通过灰度传感器判断是否上台成功,若成功则开始漫游,若失败则重复进行执行上台。

上台程序:小车向前跑执行前上台,进行灰度判断是否上台成功,若成功则进行漫游,若失败则小车向后跑进行后,上台,剩余操作同上。

漫游程序:小车向前前进,当检测到边缘旋转90度,继续前进;若检测到敌人,则根据相应的红外传感器方位旋转相应角度旋转。

上述算法的实现如流程图下图所示。

算法流程图
算法流程图

擂台边缘检测算法

需要有传感器进行擂台边沿检测,当发现机器人已经靠近边沿立刻转弯或者掉头。擂台和地面存在比较大的高度差,我们通过红外光电传感器很容易感应这个高度落差,从而判断出擂台的边沿。如下图所示,在机器人上安装一个红外光电传感器,斜向下测量地面和机器人的距离,机器人到达擂台边沿时,由于红外光电传感器是一个IO量传感器(目标在设定距离内,则返回值0,否则返回1),传感器的返回值会突变(即由1变成0)。由于红外测距传感器使用方便,并且“创意之星”控制器最多可以接入12个红外光电传感器,我们可以将它作为首选方案。

擂台边缘检测
擂台边缘检测

光电开关实时传回数字信号,若检测距离大于阈值则返回为1,小于阈值则返回0。我们整个格斗机器人的光电开关斜向下对准台面,若在擂台边缘,测量距离大于阈值,光电开关将返回1,这时就可以使电机制动,让其调转方向,远离擂台边缘。

示例代码如下:

擂台边缘检测
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
int edge() {
//前左红外光电传感器
int forward_left_io = MFGetDigiInput(id_angle_f_l);
//前右红外光电传感器
int forward_right_io = MFGetDigiInput(id_angle_f_r);
//后右红外光电传感器
int back_right_io = MFGetDigiInput(id_angle_b_l);
//后左红外光电传感器
int back_left_io = MFGetDigiInput(id_angle_b_r);
if (forward_left_io == 0 && forward_right_io == 0 && back_right_io == 0 && back_left_io == 0) {
return 0; //没有检测到边缘
}
if (forward_left_io == 1 && forward_right_io == 0 && back_right_io == 0 && back_left_io == 0) {
return 1; //左前检测到边缘
}
if (forward_left_io == 0 && forward_right_io == 1 && back_right_io == 0 && back_left_io == 0) {
return 2; //右前检测到边缘
}
if (forward_left_io == 0 && forward_right_io == 0 && back_right_io == 1 && back_left_io == 0) {
return 3; //右后检测到边缘
}
if (forward_left_io == 0 && forward_right_io == 0 && back_right_io == 0 && back_left_io == 1) {
return 4; //左后检测到边缘
}
if (forward_left_io == 1 && forward_right_io == 1 && back_right_io == 0 && back_left_io == 0) {
return 5; //前方两个检测到边缘
}
if (forward_left_io == 0 && forward_right_io == 0 && back_right_io == 1 && back_left_io == 1) {
return 6; //后方两个检测到边缘
}
if (forward_left_io == 1 && forward_right_io == 0 && back_right_io == 0 && back_left_io == 1) {
return 7; //左侧两个检测到边缘
}
if (forward_left_io == 0 && forward_right_io == 1 && back_right_io == 1 && back_left_io == 0) {
return 8; //右侧两个检测到边缘
}
return -1; //异常
}

与此同时,还可以借助底盘下的灰度传感器,辅助判断擂台边缘。因为事实上,外侧四角到中心分别为纯黑到纯白渐变的灰度,那么擂台边缘必定与中心灰度不同,根据这一事实,当检测到灰度值靠近黑,则可能是擂台边缘(当然,也不排除是擂台下)。

敌方检测与攻击算法

8个红外光电传感器组成的360°的圆形探测网络实时搜寻周围的敌人,一旦发现敌人将触发攻击程序。进入攻击程序后,会对八个方向进行判别,当有且只有一个传感器检测到敌人之后,会以原地旋转的方式立刻调整车头方向,使车头正对敌人,然后加快速度攻击敌方。

敌方检测示例代码如下:

目标检测
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
int target() {
int front = MF_GetDigitInput(infrared_f_m);
int behind = MF_GetDigitInput(infrared_b_m);
int left = MF_GetDigitInput(infrared_m_l);
int right = MF_GetDigitInput(infrared_m_r);

int front_left = MF_GetDigitInput(infrared_f_l);
int front_right = MF_GetDigitInput(infrared_f_r);
int behind_left = MF_GetDigitInput(infrared_b_l);
int behind_right = MF_GetDigitInput(infrared_b_r);

if (front == 0 && behind == 0)
return 5; // 前后都有目标
if (left == 0 && right == 0)
return 6; // 左右都有目标
if (front == 0 && (left == 0 || right == 0))
return 7; // 前方+左方/右方 有目标
if (behind == 0 && (left == 0 || right == 0))
return 8; // 后方+左方/右方 有目标
if (front == 1 && right == 1 && behind == 1 && left == 1)
return 0; // 没有目标
if (front == 0 && right == 1 && behind == 1 && left == 1)
return 1; // 前方有目标
if (front == 1 && right == 0 && behind == 1 && left == 1)
return 2; // 右侧有目标
if (front == 1 && right == 1 && behind == 0 && left == 1)
return 3; // 后方有目标
if (front == 1 && right == 1 && behind == 1 && left == 0)
return 4; // 左方有目标
return 0;
}

台上台下识别算法

台上台下识别使用了平均滑动滤波算法。具体的是,使用灰度传感器,实时地对过去的N个灰度值采样。在程序中开辟一个N个数据的数据暂存区,新采集一个数据便存入暂存区中,同时去掉一个最老数据,保存这N个数据始终是最新更新的数据。采用环型队列结构可以方便地实现这种数据存放方式。

先手动测定台上和台下的灰度平均值,找到台上与台下之间的阈值,程序中使用平均滑动滤波算法得到的灰度值只需与这个阈值比较就可以知道是台上还是台下了。

示例代码如下:

台上台下识别算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Average sliding filtering algorithm
// to get the true AD value
// filter_buf is a global variable
void filter_ad(signed long int *avr, signed long int *sd)
{
int i;
int filter_sum = 0;
filter_buf[FILTER_N] = MF_GetAD(1);
for (i = 0; i < FILTER_N; i++)
{
filter_buf[i] = filter_buf[i + 1];
filter_sum += filter_buf[i];
}
signed long int average = filter_sum / FILTER_N;
signed long int sum = 0;
for ( i = 0; i < FILTER_N; i++)
{
sum += (filter_buf[i] - average) * (filter_buf[i] - average);
}
signed long int deviation = sqrt(sum / FILTER_N);

*avr = average;
*sd = deviation;
}

注意到,我们的示例代码还求出了相应的方差,可以用于判断当前的平均值序列数据是否稳定。