65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 驱动 Lego Mindstorms 机器人

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (42投票s)

2008年11月20日

GPL3

14分钟阅读

viewsIcon

146517

downloadIcon

3692

本文介绍了如何使用 C# 操作乐高 Mindstorms 机器人。

引言

机器人技术一直让我很感兴趣,我一直想在这方面做些什么。如今,机器人离我们越来越近,它们不再局限于驱动生产线的常规工业机器人,而是发展到与人互动、协助人们日常生活的机器人。当然,当今的机器人发展速度很快,变得非常复杂,需要大量的研究工作才能教会它们做一些有用的事情,但如果你想在这个领域开始,最好从一些简单的东西入手。

幸运的是,业余爱好者和研究人员有许多不同的机器人套件可供选择,可以用来快速入门机器人领域。有些很简单,有些则更复杂,需要更多的知识。我想,我们将从目前最简单的套件开始——乐高 Mindstorms 机器人套件。乐高有两个机器人套件,分别称为 RCX 和 NXT。RCX 套件是乐高推出的第一个机器人套件,现在似乎已不再乐高商店中出售(但仍可以在 eBay 或其他地方找到并订购),而 NXT 是最新的套件,可在乐高商店中购买。我们将尝试使用它们两者...

在开始之前,我想说明一点,本文与使用乐高套件搭建机器人无关——乐高和互联网上已经提供了足够的说明和指南。本文将专注于使用 AForge.NET 框架从 C# 应用程序中操作乐高机器人。

乐高 Mindstorms RCX

Lego Mindstorms RCX brick

乐高似乎已经停止了对 RCX 砖块的支持,因此越来越难以找到有关它的任何文档。几个月前,官方网站上还能找到乐高 RCX SDK(非常有帮助),但现在已经找不到了。因此,现在所有的知识只能从仍然支持该设备的各种网站和项目中收集。RCX 最有用的在线资源之一是 RCX Internals,由 Kekoa Proudfoot 整理。在那里,你可以找到关于 RCX 硬件、通信协议等方面的信息。

为了与 PC 通信,乐高 RCX 砖块使用红外通信接口,这需要将乐高的红外收发器连接到 PC。红外塔有两种变体——早期版本通过串行接口连接到 PC,而最新版本通过 USB 接口连接到 PC。如果你拥有串行红外塔,可以在 RCX Internals 网站上找到串行协议的详细信息,你还可以找到操作该设备的所有命令的描述。

由于串行乐高红外收发器非常老旧,现在主要使用 USB 收发器,串行通信协议的描述对我们来说用处不大。幸运的是,有一个替代方案。RCX SDK 包含 GhostAPI,这是一组库,应用程序开发人员可以使用它来控制乐高 RCX 砖块。这些库是动态加载库(DLL),并且可以从大多数编程语言中使用。由于我们将使用 C#,我们将使用互操作服务来利用 GhostAPI。

好吧,我们将跳过关于如何进行 GhostAPI 互操作的描述,因为这个 API 是由普通的 DLL 表示的,并且互联网上有很多关于如何从 C# 等 .NET 语言访问 DLL API 的信息。取而代之的是,我们将简要描述一下如何使用 GhostAPI,采用已经互操作的 API,它由 C# 类表示(该类的​​方法与原始 GhostAPI 函数的名称相同,因此在互操作版本方面应该没有困惑)。

我们需要做的第一件事是连接到我们的 RCX 砖块。连接到 RCX 砖块需要三个步骤:

  1. 创建通信堆栈,我们需要指定通信端口(USB 或串行)和协议。
  2. 选择要通信的设备。
  3. 连接到选定的设备。
IntPtr stack;
uint status;

// create stack
status = GhostAPI.GhCreateStack(
    "LEGO.Pbk.CommStack.Port.USB",
    "LEGO.Pbk.CommStack.Protocol.IR",
    "LEGO.Pbk.CommStack.Session",
    out stack );

if ( !GhostAPI.PBK_SUCCEEDED( status ) )
    return false;
    
// select first available device
StringBuilder sb = new StringBuilder( 200 );
status = GhostAPI.GhSelectFirstDevice( stack, sb, sb.Length );

if ( !GhostAPI.PBK_SUCCEEDED( status ) )
    return false;

// open communication stack
if ( !GhostAPI.PBK_SUCCEEDED( GhostAPI.GhOpen( stack ) ) )
    return false;
    
// we are connected to RCX ...

现在,我们已经连接到 RCX 砖块,我们可能希望向其发送一些命令,指示砖块执行操作。命令发送分几个步骤:

  1. 创建命令队列
  2. 将命令添加到队列
  3. 执行命令队列
  4. 销毁队列
IntPtr queue;
uint status;

// create command queue
status = GhostAPI.GhCreateCommandQueue( out queue );

if ( !GhostAPI.PBK_SUCCEEDED( status ) )
    return false;

// append command to the queue
status = GhostAPI.GhAppendCommand( queue, command, 
                                   command.Length, expectedReplyLen );

if ( GhostAPI.PBK_SUCCEEDED( status ) )
{
    // execute command
    status = GhostAPI.GhExecute( stack, queue );

    ...
}
    
// destroy command queue
GhostAPI.GhDestroyCommandQueue( queue );

从上面的代码可以看出,我们将某种命令变量传递给 GhostAPI.GhAppendCommand()。正如我们可以推测的那样,这告诉 RCX 该做什么。我们的猜测是正确的——command 变量只是一个字节数组,其中包含命令代码及其参数。在这里,RCX internals 网站变得非常有用了,因为它提供了乐高 Mindstorm RCX 砖块支持的所有命令的描述。

例如,如果我们想让我们的 RCX 砖块响两次,我们可以使用 播放声音 请求。根据其描述,我们只需要发送两个字节:命令代码的字节(实际上所有 RCX 命令代码都用一个字节编码),以及一个参数字节,即要播放的声音类型。

// 0x51 - play sound command comde
// 0x01 - double beep sound type
byte[] command = new byte[] { 0x51, 0x01 };

从 RCX 命令文档可以看出,一些命令可能会返回响应,例如 RCX 传感器状态。所以,发送命令后的最后一步是检索响应。

// execute command
status = GhostAPI.GhExecute( stack, queue );

if ( GhostAPI.PBK_SUCCEEDED( status ) )
{
    IntPtr commandHandle;
    uint replyLen;

    // get first command and its reply data length
    if (
        ( GhostAPI.PBK_SUCCEEDED( GhostAPI.GhGetFirstCommand
                ( queue, out commandHandle ) ) ) &&
        ( GhostAPI.PBK_SUCCEEDED( GhostAPI.GhGetCommandReplyLen
                ( commandHandle, out replyLen ) ) )
        )
    {
        // get reply
        status = GhostAPI.GhGetCommandReply( commandHandle, reply, replyLen );
    }
}

以上看起来是否复杂或令人困惑?也许是,也许不是。幸运的是,如果您想控制 RCX 砖块,不必研究 GhostAPI 的所有细节——AForge.NET 已经提供了一个类,可以以更友好的方式操作您的 RCX 设备。RCXBrick 类允许执行最常需要的操作,如控制电机、获取传感器值、播放声音等。

// create an instance of RCX brick
RCXBrick rcx = new RCXBrick( );
// connect to the device
if ( rcx.Connect( ) )
{
    // set forward direction of motor A
    rcx.SetMotorDirection( RCXBrick.Motor.A, true );
    // set power of motor
    rcx.SetMotorPower( RCXBrick.Motor.A, 1 );
    // turm motor on
    rcx.SetMotorOn( RCXBrick.Motor.A, true );
    // ...
    // turn off motors A, B and C
    rcx.SetMotorOn( RCXBrick.Motor.ABC, false );

    // get first sensor's value
    short value;

    if ( rcx.GetSensorValue( RCXBrick.Sensor.First, out value ) )
    {
        // ...
    }
    // ...
}

AForge.NET 框架还提供了一个简单的 RCX 砖块测试应用程序,允许测试大多数 RCX 功能,并作为从 .NET 应用程序控制 RCX 砖块的简单示例。

RCX test sample application

乐高 Mindstorms NXT

Lego Mindstorms NXT brick

乐高 Mindstorms NXT 是乐高推出的一款新的、得到积极支持的机器人套件,这使得查找有关它的各种信息、机器人搭建教程、支持它的项目以及许多其他内容变得容易。该设备更复杂,支持更广泛的传感器,这使得机器人爱好者更有趣,并支持更灵活的命令集,为机器人程序员提供了更多机会。

每个乐高 NXT 砖块都支持通过两种接口与 PC 通信:蓝牙和 USB。由于无线机器人移动更灵活,并且有机会实现自主,我们将专注于与这些设备的蓝牙通信。好消息是乐高提供了关于蓝牙通信协议的信息以及设备支持的所有命令的描述。虽然这些信息不易找到,但可以在其 乐高 NXT SDK 页面上找到。

注意:如果您不想研究通信协议的细节,但又想使用能够隐藏所有复杂性的东西,可以尝试 Fantom 库,它是乐高 NXT SDK 的一部分。该库的优点是支持所有通信接口。缺点是它会为您的软件增加一个额外的依赖项,这并不总是受青睐。

因此,我们将手动实现蓝牙通信。幸运的是,这不像最初听起来那么复杂——PC 的蓝牙适配器可以配置为提供一个虚拟串行端口来与蓝牙设备通信。所以,我们只需要编写一些代码,通过串行端口进行通信,以特定的格式发送一些命令给 NXT 砖块并接收响应。

我们需要做的第一件事与 RCX 的情况相同——连接到我们的设备。由于我们使用虚拟串行端口与设备通信,第一步极其简单——只需创建串行端口类的实例并打开特定的端口。

// create serial port, specifying port name (COM8, for example)
SerialPort port = new SerialPort( portName );
// connect to NXT device
port.Open( );

是的,就是这么简单!现在,我们需要发送一条消息给 NXT,让它做某事。这也可以像连接一样简单——我们只需要将一个字节数组发送到打开的串行端口。

// message to send to NXT
byte[] message = new byte { ... };

// send 2 bytes of message length
// (suppose we have short message not exceeding 255 bytes length)
byte[] messageLength = new byte[2] { (byte) length, 0 };
port.Write( messageLength, 0, 2 );

// send actual message
port.Write( message, offset, length );

从上面的代码可以看出,NXT 的蓝牙通信协议假设,首先,我们需要发送两个字节的消息长度,然后是实际消息。要发送的消息格式是什么?消息格式非常简单,并在乐高在 NXT SDK 页面上提供的文档中有描述:

  • 1 字节 - 命令类型
    • 0x00 - 直接命令,需要响应
    • 0x01 - 系统命令,需要响应
    • 0x80 - 直接命令,不需要响应
    • 0x81 - 系统命令,不需要响应
  • 1 字节 - 命令代码(参见乐高文档)
  • 可变长度 - 命令数据(取决于命令代码)

例如,让我们做与 RCX 之前相同的操作——准备一个播放音调的命令。

short frequency = 300; // tone frequency in Hz
short duration = 1000; // tone duration in milliseconds

byte[] command = new byte[6];

// prepare command
command[0] = (byte) 0x00; // direct command requiring reply
command[1] = (byte) 0x03; // play tone command
command[2] = (byte) ( frequency & 0xFF );
command[3] = (byte) ( frequency >> 8 );
command[4] = (byte) ( duration & 0xFF );
command[5] = (byte) ( duration >> 8 );

上面的命令实际上可以使用另一种类型——0x80,它不需要响应。要求此类命令响应的唯一原因是为了确保 NXT 成功接收并处理了该命令。

由于不同的命令可能导致 NXT 返回响应,我们可能需要读取它。

// read 2 bytes of message length
// - assume we expect a small message
int toRead = port.ReadByte( );
// - skip the second bytes
port.ReadByte( );

// read the actual message
byte[] buffer = new byte[toRead];
length = port.Read( buffer, 0, toRead );

响应数据的格式看起来与命令数据非常相似:

  • 1 字节 - 响应类型
    • 0x02 - 从 NXT 砖块接收到的响应命令
  • 1 字节 - 命令代码,等于之前发送给 NXT 的命令代码
  • 1 字节 - 错误代码(如果有)或 0 表示成功
  • 可变长度 - 响应数据(取决于命令代码)

以上就是通过蓝牙与乐高 Mindstorm NXT 通信的所有内容——看起来相当简单,并且不需要任何额外的库。

正如 RCX 的情况一样,上面关于 NXT 的所有信息都仅适用于那些想了解如何与乐高 Mindstorms NXT 设备通信的人。但是,如果我们只是想在不遇到太多复杂情况的情况下开始使用乐高,我们可以使用 AForge.NET 框架,它提供了 NXTBrick 类,使我们能够控制电机、传感器并执行其他操作。

// create an instance of NXT brick
NXTBrick nxt = new NXTBrick( );
// connect to the device
if ( nxt.Connect( "COM8" ) )
{
    // run motor A
    NXTBrick.MotorState motorState = new NXTBrick.MotorState( );

    motorState.Power      = 70;
    motorState.TurnRatio  = 50;
    motorState.Mode       = NXTBrick.MotorMode.On;
    motorState.Regulation = NXTBrick.MotorRegulationMode.Idle;
    motorState.RunState   = NXTBrick.MotorRunState.Running;
    motorState.TachoLimit = 1000;

    nxt.SetMotorState( NXTBrick.Motor.A, motorState );

    // get input value from the first sensor
    NXTBrick.SensorValues sensorValues;

    if ( nxt.GetSensorValue( NXTBrick.Sensor.First, out sensorValues ) )
    {
        // ...
    }
    // ...
}

此外,该框架还提供了一个 NXT 砖块测试应用程序,可用于测试与 NXT 砖块的通信,并作为从 C# 控制设备的示例。

NXT test sample application

驱动乐高汽车机器人

现在我们知道如何与不同的乐高 Mindstorms 砖块通信了,是时候构建一些东西并用 C# 来操作它了。我们将从经典的开始,构建一些简单的汽车机器人。

RCX car bot

NXT car bot

我们如何驱动我们的机器人并设置所需的​​速度和方向?我们需要记住,这些机器人没有左右转向轮。相反,它们只有两个独立的电机,连接到左右轮。因此,如果你想直线行驶,只需为两个轮子设置相同的功率。如果你想转弯,你需要减小你想行驶方向的轮子的功率。这样的机器人可以自然地用操纵杆或游戏手柄来操控。但是,如果我们没有它们怎么办?或者想用普通的鼠标来控制这些机器人...

嗯,编写我们自己的“软件”操纵杆并不复杂。让我们创建一个圆形控件,并将一个操纵杆放在控件的中心。用鼠标左键单击操纵杆并将其从控件中心拖开,机器人就会移动。现在,我们将控件分为两部分:上半部分用于前进,下半部分用于后退。所以,如果我们从中心直线向上拖动操纵杆,机器人将直线前进。但将操纵杆从中心直线向下拖动会将机器人直线向后移动。如果我们想向左/向右转弯,只需将操纵杆拖到相应方向远离中心即可。所以,将操纵杆拖到不同的方向应该会导致机器人向不同的方向移动。至于移动速度,这很简单——操纵杆离中心越远,机器人移动的速度就越快。下图展示了控件的外观(上面的控件)。

Controls to manipulate car bots

拖动我们“软件”操纵杆的操纵杆会触发控件的事件,这些事件会通知操纵杆的位置变化,并提供其 笛卡尔坐标(X,Y - 相对于控件中心的​​位置)。我们所要做的就是将这些坐标重新计算为两个电机的功率(我们将在此处省略几何代码,因为它可以在附带的演示应用程序中找到)。

我们得到的控件非常不错,可以让我们以不同的速度向前或向后移动我们的汽车机器人。而且,这一切都只需使用普通的鼠标。然而,这个控件仍然缺少一个非常有用的功能——原地转向(原地转弯)。从机器人的角度来看,这很简单——我们只需要为两个电机设置相同的绝对功率,但对于一个电机,功率应该是正的,对于另一个电机,功率应该是负的。例如,如果我们谈论 NXT 设备,其中电机功率在 -100 到 100 之间变化(0 - 不移动),我们可以为左电机设置 75 的功率,为右电机设置 -75 的功率——这将导致我们的机器人顺时针转动。所以,这对我们的机器人来说很简单,但对我们的控件来说并不简单,因为它们不允许电机朝不同方向转动。

为了解决原地转弯问题,让我们创建一个额外的控件(上图中的第二个控件)。该控件看起来像一个滑块控件,操纵杆位于控件的中间。如果我们向右拖动操纵杆,机器人将顺时针转动。如果我们向左拖动操纵杆,机器人将逆时针转动。操纵杆离中心越远,机器人的移动速度就越快。

就这样——两个控件,我们就可以按照想要的方式控制我们的汽车机器人的移动了,只需拖动鼠标!

看还是不看?

汽车机器人作为开始不错,但是……如果机器人什么也看不见怎么办?我们肯定需要解决这个问题,所以让我们给我们的 NXT 机器人装备一个“眼睛”——一个摄像头。由于我们不想失去蓝牙通信带来的移动性,我们绝对不想使用有线摄像头。这意味着我们需要一个无线摄像头。

最简单的解决方案是使用无线 IP 摄像头,但在大多数情况下,它们并不那么小,并且需要更多的电源。所以,我们将使用一个普通的无线针孔摄像头。当然,这个解决方案需要一个接收器和一个视频采集设备,但它为我们的机器人提供了一个很小的附加设备。让我们将摄像头和 NXT 部件放在一起,并将其安装到我们的汽车机器人上……

Wireless camera

Eyed Lego NXT

最后一步是将我们摄像头的视图引入我们的应用程序。我使用的采集设备支持 DirechShow,这使得访问视频非常容易。使用 AForge.NET 框架,只需几行代码即可在我们的应用程序中启用视频。首先,我们将 VideoSourcePlayer 控件放在我们的窗体上,然后我们将使用 VideoCaptureDevice 从我们的摄像头获取帧。

VideoCaptureDevice videoSouce = new VideoCaptureDevice( deviceMonikerString );

videoSourcePlayer.VideoSource = videoSouce;
videoSourcePlayer.Start( );

这就是视觉的到来……

Lego Driver application

现在,这一切都在行动中……(抱歉,我的无线摄像头和蓝牙通信之间存在干扰——尝试使用其他频率的摄像头)。

结论

好吧,有些人可能会说乐高 Mindstorms 是非常简单的设备,不允许构建复杂的机器人。是的,它们不复杂,并且不允许插入很多东西——最多三个电机和四个传感器。但这就是关键——它们不复杂这一事实使得可以快速开始机器人项目,并且不需要额外的电子知识。只需一个构造器,就可以让你专注于构建机器人的想法。而且,看看 乐高 Mindstorm 网站或在互联网上搜索,你会发现即使有了这个简单的套件,也能做很多事情。而且,很快,我们将继续使用乐高和其他一些设备来做不同的事情……

© . All rights reserved.