移动机器人控制软件的设计与实现
作者:李晓明   文章来源: Blog.RoboticFan.Com    更新时间:2006年08月09日   打印此文    浏览数:

2、移动机器人控制软件框架的设计

框架的设计一方面考虑了对体系结构的支持,另一方面又打算尽量与体系结构保持一定的独立性,所以对框架做了高度的抽象,整体框架被抽象成了三个部分,即Platform平台、Module模块和Wire通讯连线等。整个框架的结构(部分)如下图所示。

其中Platform类负责模块的加载,对模块的配置以及模块之间通讯的建立,Platform对象是整个应用程序的入口(有Main函数)和出口,并不考虑体系结构和机器人功能的实现,这些都是模块和配置实现的。Module定义了模块应该遵守的接口,和模块要实现的基本功能。模块和Applet类似,有初始化init,开始start,停止Stop和退出Destroy等行为,该接口没有定义模块应该实现哪些功能,这些是模块的具体实现应该考虑的,模块里有两个很重要的变量,分别是该模块的ModuleDoc和端口Ports。ModuleDoc描述了该模块的具体配置信息,例如和硬件通讯时使用的串口名称,或者是网络地址,IP地址,也可能是其他需要传递给具体模块的参数,其中最重要的是模块的名称,用来在系统中唯一的标识模块用的,不能重复;其次是该模块的实现类,供Platform在加载该模块的时候实例化所需。端口Port是模块必须具备的,一个模块至少有一个端口,端口分为输入端口和输出端口,是模块之间通讯的唯一途径。端口也是一个接口,符合该接口的任意实现类都可以作为端口来供模块使用,目前端口是硬编码在模块的代码中的,因此一旦模块编码完毕,使用何种端口也就确定不可更改了。在后续研究中可以考虑把端口与模块的实现分离开来,在系统运行时动态组合,实现更灵活的配置。下图是模块的一个典型配置。模块实现的基本操作是根据输入的数据进行处理后,从输出端口把数据输送出去,当然数据处理的过程可能很复杂,例如可能是图像识别,也可能是障碍物检测,有些还需要进行网络数据库等操作等;有些模块没有输入端口,例如一些人机接口模块(操作面板);也有些模块没有输出端口,例如图形化显示模块等。

整个软件的功能是由模块决定的,确切的说是由模块之间的协作实现的,模块之间的协作,则是利用端口之间的通讯实现的。在本系统中,端口之间通过虚拟的“线”连接起来的,在系统中用WireConfig来表示,线从输出端口引出,可以连接至多个不同的输入端口;不同线之间可能会存在着交点,根据不同的方式,交点有不同的类型,如下图所示。在输入端口的交点称之为抑制连接,即抑制线输入来的信号可以取代原线上的信号输送到输入端口里去,在输出端口的交点称之为禁止连接,即如果禁止信号线上有数据,则会禁止原信号线上的信号输出。这些概念均取自Brooks的包容体系结构。在Brooks的包容体系结构中,模块之间的通讯是异步的,分层的。在同一功能层内模块的数据是直接发送的;在不同层之间数据则是通过抑制(Inhibit)和禁止(Suppress)进行传递的。这种通讯机制最大的优点就是鲁棒性。即上层模块工作时,可以利用抑制取代原有底层模块的输出;当上层模块失效时,抑制功能丧失,下层模块的输出重新有效,从而避免因模块失效带来的整体功能失效,保证了机器人的基本安全。在我们设计的框架中,同样采用了抑制和禁止这两个通讯功能,不同的是没有分层,而是根据设计者的意图来确定哪些通讯直接相连,哪些通讯之间要互相抑制和禁止。通过这三种通讯方式实现模块之间的协作,实现各种自主智能功能。

其实在一般使用当中,模块之间基本上都是直接线连,出现交叉的情况很少,这种情况的设计主要是为了有软件鲁棒性考虑的应用设计的。例如模块A、B都连接到模块C上,而B优先级大于A,则B-A连接属于抑制连接,当B有信号的时候,会取代A的信号输入到C中,当B没有信号的时候(例如出现了故障,或者没有输出),A的数据仍然可以输入到C中。

2.3 框架的实现

前面分析的主要是思想,这部分讲述的是实现方法。由于本人一直在用Java语言做开发,所以这里也选用Java语言作为该框架的实现语言,但原则上讲用C语言来实现更好,但C语言面向对象特性不好,而且编程的难度大,更不能跨平台,而我的项目所剩时间无多,所以最终开始确定用Java开发。事实证明Java开发的效果也还不错(在实验过程出现了响应缓慢的症状,其原因在于导航的算法,而非语言的问题),而且更可以把某些实时性要求比较高的模块用JNI来实现,速度就更有保障了

(1)框架的结构

框架由一个平台对象Platform和一个系统配置文件构成,这两者之间的关系,打个比方,配置文件好比一个设计蓝图,描述了整个框架里应该加载多少模块,每个模块的加载信息,以及模块之间的通讯线路等;而Platform对象就像一个装配车间,按照图纸的要求,把模块加载进来,然后建立模块之间的通讯线路。其中,Platform的编程比较容易,因为Java虚拟机能够实现类的动态加载,而且读取XML文档的能力也很强。

实现模块的动态加载代码如下(有删改):

 /**
* loading module from xml configuration
* @param md - ModuleDoc, the robot configuration file
* @throws Exception - Something wrong, check the message
* @return the Module Object that has just been generated
*/
protected Module loadModule(ModuleDoc md) throws Exception
{
String cn = md.getClassName(); //读取XML文件中的类名称
if(cn == null || cn.length() == 0)
{
throw new Exception("Wrong Configuration for Module");
}
Object m = Class.forName(cn).newInstance(); //创建模块对象
if(m instanceof Module)
{
return (Module) m;
}else{
throw new Exception("Failed loading module: " + cn);
}
}

加载模块的启动如下(有删改)

 /**
* booting the platform,
* install all the modules and make the system working.
*
* @throws Exception
* @see #c onfig(String)
*/
public void booting() throws Exception
{
Iterator iterator = confdoc.getModuleDocs();
while(iterator.hasNext())
{
//Loading modules...
ModuleDoc md = (ModuleDoc) iterator.next();
Module m = loadModule(md);
//initialize the modules
m.init(md);
//put it in the module hash
module_hash.put(m.getName(), m);
}
  //start the modules
iterator = module_hash.values().iterator();
while(iterator.hasNext())
{
Module m = (Module) iterator.next();
m.start();
logit("Module " + m.getName() + " has been started.");
}
}

模块之间的通讯信息则存储在对象WireConfig的集合中,该类存储了从源模块某端口出发的通讯线路,描述了该线路如何与其他模块的什么端口进行连接,这样在模块发送数据时,可以动态的到该集合中搜索与自己关联的通讯线路,并根据该线路的描述,寻找到可以写入的模块和端口。这些工作都是由端口Port完成的,模块本身无需干预。

输出端口发送数据的实现代码:

 public void setValue(Module module, Object v, long duration)
{
//{{{1
/*judge if output is disabled*/
if(i_flag && ((System.currentTimeMillis() - timestamp) {
return; //output is disabled
}
/* Looking for the target module and port */
Platform platform = Platform.getPlatform();
if(wc == null)
{
wc = platform.getWireConfig(module.getName()+"."+name);
if(wc == null)
return;
}

List list = wc.getWireTargets();
for(int i=0; i {
WireConfig.WireTarget wt = (WireConfig.WireTarget) list.get(i);
Module m = platform.getModule(wt.getTModule());
if(WireConfig.WireTarget.WTT_DIRECT.equals(wt.getType()))
{
//direct connect
m.getPort(wt.getTPort()).setValue(module, v, 1000);
}
else if(WireConfig.WireTarget.WTT_SUPPRESS.equals(wt.getType()))
{
//to lower layer
m.getPort(wt.getTPort()).setValue(module, v, wt.getDuration());
}else
{
m.getPort(wt.getTPort()).setValue(module, v, wt.getDuration());
}
}
//}}}1
}

输入端口接收数据的代码如下:

 public void setValue(Module m, Object v, long duration)
{
if(m.getLayer() >= update_level || (System.currentTimeMillis() - update_timestamp) > valid_duration)
{
//do not care about the valide duration
value = v;
valid_duration = duration;
update_timestamp = System.currentTimeMillis();
bReady = true;
if(listener != null)
listener.valueUpdated();
}
}

至此,完成了模块的加载和通讯线路的建立。

(2)灵活的配置文件

本系统的一个有点就在于其灵活的配置文件,通过使用XML文档描述系统框架,可以在不修改任何代码的情况下通过修改XML配置文件就可以实现不同模块的加载以及模块之间通讯方式的改变,从而可以动态的修改应用的类型,这个在后面的例子中可以看到。首先介绍配置文件的格式如下:

该图描述了一个非常简单的配置文件,只包括了两个模块Sick和Monitor,一条线路连接了SIck模块的sick端口到Monitor模块的monitor端口。这个应用非常简单,就是把激光测距LMS测得的数据图形化的显示在屏幕上而已。可见,配置文件包括了两部分,一部分描述了模块的信息,另一部分描述了通讯线路的信息。目前该模块需要手工进行编辑,期望能够在下一个版本中做一个可视化的图形编辑界面,从而实现移动机器人图形化编程。

(3)模块间通讯

模块间是通过端口进行通讯的,端口只是定义了一些接口,凡是实现该接口的对象都可以做端口,实际上这也是设计的初衷,期望通过采用不同的端口实现类来实现不同的通讯能力。例如目前我们实现的简单端口SimpleInPort和SimpleOutPort就仅仅能够实现同一程序内的数据通讯,可以想象也可以设计可以通过以太网络的通讯端口!然而这部分工作我们还没有做,原因是涉及到网络间的远程调用,实现起来比较复杂,就暂时先放放了。目前网络通讯是通过相应模块实现的。

在目前的实现中,通讯是通过传递对象的引用实现的,这也就意味着无法实行跨系统跨应用,这是这个框架的缺点。优点是这样效率较高,而且可以通过一些折衷手段实现所谓的跨进程跨平台。通讯也分两种,同步和异步。本系统也实现了这两种通讯方式。


首页 上一页 1 2 3 下一页 尾页 跳转到

转载声明:凡文章出处为www.RoboticFan.com的,系本站的原创文章。其它媒体在注明出处为RoboticFan.com并给出原始链接后可以自由转载,否则将视为侵权!

上一篇:ABB最新机器人IRC5的介绍 下一篇:美国研发战场救援机器人

最近更新
  • 世嘉玩具与孩之宝将上市音乐机器人
  • Vstone携手KumoTek推出KT-X类人机器人系列
  • iRobot军用机器人最新杰作:“金属风暴”
  • 机器人工具包开始预定
  • 迪士尼与WowWee联手打造Wall-E系列产品
  • Arduino-目前为止最容易开发的控制板
  • 微软发布Robotics Developer Studio 2008 CTP
  • roBlocks——“块状”机器人
  • 后AIBO时代的机器狗——G-dog
  • 机器恐龙Pleo升级战

  • 相关文章

    推荐新闻

    赞助商广告