软件设计 思路 在进行代码编写之前,想找到一种简介的实现低级智能机器人的方式。目前的工业机器人采用的是精确控制的方法,在程序中固化每种处理逻辑。用程序员的思维来完成对机器人的操作。
对于程序员来说,机器人无非就是外接了物理传感输入和运动控制输出的一台电脑,不管是64位多核CPU或者8位单片机,只是性能的不同而已。理论上,都能通过程序设计来达到预定的目标。如果按照计算机上的普通程序来设计,那么大量的功能代码会相互耦合,每次为了增加一个新的运动功能或者任务,都需要编制新的代码,而新的代码给原来的环路反馈系统带来新的逻辑分支,所有的步骤都得经过程序员的验证和设计。这样的设计对于精确计算的PC程序或者擅长固定场景精确动作的工业机器人来说,都没有问题。但是对于这里要实现的“小强一号”机器人,是处在一个动态的变化环境当中,如果按照传统的软件设计逻辑,会存在大量耦合的逻辑分支,导致程序扩展性很差。举例来说,为了完成一个沿墙壁行走的功能,必须对各种传感器输入情况进行判断,然后做出预置的动作序列。
如果要执行的动作和需要判断的情况很多,那么程序逻辑分支就比较复杂,扩展性也比较差,有没有更好的适合机器人软件设计的方法呢?
换一种思维来思考的话,我们要做的不是电脑程序,只是一个实验机器人。这个实验机器人能做到自然界昆虫级的“智能”就很不错了。 试想,一只蚂蚁,一只蟑螂会去思考这么复杂的逻辑分支吗?去考虑各种情况下应该采取的动作? 不太可能。我们用了复杂的想法去解决简单的事情。
机器人和计算机的主要区别在于,计算机是按照预先规划的指令运行,而机器人需要根据变化的环境选择合适动作。我们首先可以简化动作模型,我把机器人的执行任务分成3个层次:任务-》行为-》动作
任务——机器人完成的一个期望目标,比如“在一个范围内收集地面上的垃圾”可以界定为一个机器人的任务。
行为——行为是对任务的分解,行为是机器人根据环境变化选择要做什么。比如碰到墙壁需要进行躲避。电源不足需要充电,就可以划分为一个行为。
动作——机器人最终控制电机等输出设备做出的动作,比如前进,后退,转弯。转动摄像头都可以划分成一个动作单元。
说明:任务由一系列的行为构成;行为有一个或者多个动作组成,动作是立即执行的最小单元。在任意一个时刻,只有一个动作可以得到执行。
软件实现的对象就是分解出简单的行为,针对每个行为单独实现,互相之间无干涉、无耦合。当多个行为要输出互相矛盾的动作时,通过一个行为优先级表来决定优先级高的行为来做出动作。在这个思路中,参考了基于行为编程的理论,关于详细的基于行为编程的描述,可以在网上搜索“Behavior-based robotics”。
通过这种设计思路,可以实现对动物反射特性的模拟,不需要把精力放在复杂的逻辑处理上,只需要根据分解出的基本行为实现一个一个小的行为单元。每个行为单元只关心自己的传感输入和需要输出的控制动作就行了。确定了设计思路,下一部分开始针对小强一号来设计软件了。
参考资料,有兴趣的可以阅读:
如果需要增加其他更加复杂的智能,那么只需要增加对应的行为单元就行了,不用修改已有的逻辑结构。每个行为单元只需要处理自己关心的传感器和需要控制的动作执行部分。这种方式的好处就是程序逻辑变得非常简单,功能单元之间不会存在互相影响的弊端。
下面的代码是“巡航”行为:
class BhCruis:public Behavior { public:
public: BhCruis(const char* name):Behavior(name){ }; BhCruis(); void Run(); void Setup();
};
void BhCruis::Setup(){
}
void BhCruis::Run(){ int bid = GetId(); GO_speed_left[bid] = 255; GO_speed_right[bid] = 255;
GT_beh_action[ACTION_TYPE_MOTOR][bid] = true; } |
上面的代码很简单,就是让两个电机以同样的速度晕装,其他都不用考虑。对于某些行为,需要引入稍微复杂一些的处理,比如发现障碍物,首先要停下来,然后判断是应该往左还是往右,做出决定之后,再进行具体的动作。针对这种不能在一个时刻完成,要经过几个不同的状态才能处理完成的行为,可以用状态机来解决。下图是“逃离”行为的状态机:

对于行为实现自己的状态机,在行为实现的代码内部实现,而不影响其他行为。这种结构可以让每个行为子关心自己的实现方式,从而最大的降低软件复杂度,方便功能添加和修改。逃离行为状态机实现原型代码:
void BhEscape::Run(){ int bid = GetId();
// 状态机实现 switch(state){ case 0: // 前方距离过小 if(GI_distance[DISTANCE_FORWORD] < 30){ GO_speed_left[bid] = 0; GO_speed_right[bid] = 0;
GT_beh_action[ACTION_TYPE_MOTOR][bid] = true;
timestart = millis(); state++; // 状态变化 }else{ GT_beh_action[ACTION_TYPE_MOTOR][bid] = false; }
break; case 1: // 观察左右距离 if(millis() - timestart > 1000){ GO_eyeAngle[bid] = 0; GT_beh_action[ACTION_TYPE_HEAD][bid] = true; timestart = millis(); state++; } case 2: // 观察左右距离 if(millis() - timestart > 1000){ GO_eyeAngle[bid] = 180; GT_beh_action[ACTION_TYPE_HEAD][bid] = true; timestart = millis(); state++; } case 3: // 决定往哪边转弯 if(millis() - timestart > 1000){ GO_eyeAngle[bid] = Angle_Center_90; GT_beh_action[ACTION_TYPE_HEAD][bid] = true;
if(GI_distance[DISTANCE_LEFT_1] > GI_distance[DISTANCE_RIGHT_1]){ // 左转 GO_speed_left[bid] = 0; GO_speed_right[bid] = 255; }else{ //右转 GO_speed_left[bid] = 255; GO_speed_right[bid] = 0; } GT_beh_action[ACTION_TYPE_MOTOR][bid] = true;
state = 0; Delay(2000); }
default: break; } } |

【