这个系列文章因为开始涉及到太多技术细节,跟这儿的定位不符合,改到另外一个 blog 上连载了,呵呵
作为一个开放式扩展平台,LEGO 提供了 NXT 相关的非常详尽的资料。而作为二次开发不可或缺的 SDK,当然也包括在其中。在其网站的
NXT’reme 栏目中可以下载到针对 C++ 开发环境的 Driver SDK,支持 Windows 和 MAC 系统。
Driver SDK (fantomSDK1.0.2f0.zip 2.21MB)
因为 NXT 提供了 USB 和蓝牙两种连接方式,所以为了提供同一的编程接口,LEGO 通过一个 DLL 对其进行了封装。这个 DLL 有一个很 cool 的名字 —— 饭桶 (fantom.dll) -_-b
对 USB 方式的连接,LEGO 提供的驱动 fantom.sys 与 NXT 直接进行通讯; 对蓝牙方式,则是在进行配对 (pair) 后,把 NXT 设备映射到一个 COM 端口,然后通过 Win32 文件方式访问端口进行通讯。Microsoft 在
Robotics Studio 中对 NXT 的支持就是通过后者的方式实现的。前者的好处是速度快且一次安装后使用时无需配置;后者则更为灵活,能够无线通讯,且不需要依赖特定驱动的安装。
对于我们日常开发来说,直接使用 fantom.dll 可以很大程度上简化工作,下面我们看看如何通过 fantom 来获取 NXT 的各种信息。
安装 Drvier SDK 后,在 documentfantom.chm 里是基本封装类型的使用帮助,以及一个简单的示例程序代码;includes 里是 fantom 的头文件,以及 labview 的 vi 支持库,这个后面有机会再详细介绍。targetswin32Ui386msvc71release 里是需要 link 的库文件。
首先我们需要建立一个 iNXTIterator 对象,此对象能够对注册到系统上的 NXT 设备进行遍历。
int _tmain(int argc, _TCHAR* argv[])
{
tStatus status;
std::cout << "枚举 NXT 设备…";
// 建立一个迭代子遍历所有的 NXT 实例
iNXTIterator *nxtIteratorPtr = iNXT::createNXTIterator(
false /* 不搜索通过蓝牙连接的 NXT (仅应用 USB 连接 ) */,
0 /* 忽略蓝牙搜索的超时时间 */,
status );
if (status.isFatal())
{
std::cout << "失败,错误码:" << status.getCode() << std::endl;
return status.getCode();
}
else
{
std::cout << "成功!" << std::endl << std::endl;
}
for (; status.isNotFatal(); nxtIteratorPtr->advance(status))
{
iNXT* nxtPtr = nxtIteratorPtr->getNXT(status);
if (nxtPtr && status.isSuccess())
{
// 获取 NXT 设备的信息
iNXT::destroyNXT( nxtPtr );
}
}
iNXT::destroyNXTIterator(nxtIteratorPtr);
}
这里的 tStatus 对象是对各种错误状态的封装,因为 LEGO 提供的是调试版本的 DLL,所以还可以通过此对象取得错误发生所在的文件和行号 :S 不知道 LEGO 是不是准备后期把这块的代码也开放了。
值得注意的是,在操作 NXT 时创建的各种 iterator 或普通对象,使用完后都必须调用相应的 destroy 方法释放,最好是用 C++ 封装一个类来管理,这里为了简洁暂且不管。
在获得了 NXT 对象后,可以调用其 getDeviceInfo 和 getFirmwareVersion 获得设备的详细信息
const size_t MAX_DEVICENAME_LEN = 16;
void dump(iNXT& nxt)
{
ViChar name[MAX_DEVICENAME_LEN];
ViByte bluetoothAddress[6];
ViUInt8 signalStrength[4];
ViUInt32 availableFlash;
tStatus status;
nxt.getDeviceInfo(name, bluetoothAddress, signalStrength, availableFlash, status);
if (status.isFatal())
{
std::cerr << "获取 NXT 设备信息失败,错误码:" << status.getCode() << std::endl;
return;
}
ViUInt8 protocolVersionMajor, protocolVersionMinor;
ViUInt8 firmwareVersionMajor, firmwareVersionMinor;
nxt.getFirmwareVersion(protocolVersionMajor, protocolVersionMinor,
firmwareVersionMajor, firmwareVersionMinor, status);
if (status.isFatal())
{
std::cerr << "获取 NXT 设备固件版本失败,错误码:" << status.getCode() << std::endl;
return;
}
std::cout << "设备 " << (char *)name
<< ", protocol=" << (int)protocolVersionMajor << "." << (int) protocolVersionMinor
<< ", firmware=" << (int)firmwareVersionMajor << "." << (int)firmwareVersionMinor
<< ", Flash=" << std::setprecision(4) << (double)availableFlash / 1024 << "KB"
<< std::endl;
} 因为前面指定只枚举 USB 方式连接的设备,因此这儿的蓝牙地址等信息暂时无用。
输出信息大概如下:
D:StudyRobotLegoDemoDebug>LegoDemo.exe
枚举 NXT 设备…成功!
设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB
对每个 NXT 设备来说,都安装了很多缺省的模块用于对各种外设提供支持,我们可以通过 iModuleIterator 获取获得支持的模块列表。
const size_t MAX_MODULENAME_LEN = 20;
void dump(iModule& mod)
{
ViChar name[MAX_MODULENAME_LEN];
mod.getName(name);
std::cout << " 模块 " << std::setw(sizeof(name)) << (char *)name
<< "tid=0x" << std::hex << mod.getModuleID() << std::dec
<< "tsize=" << mod.getModuleSize()
<< "tio=" << mod.getModuleIOMapSize() << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
// 枚举 NXT 设备
std::cout << " 枚举设备支持模块…";
iModuleIterator *modIteratorPtr = nxtPtr->createModuleIterator("*.mod", status);
if (!modIteratorPtr || status.isFatal())
{
std::cout << "失败,错误码:" << status.getCode() << std::endl;
}
else
{
std::cout << "成功!" << std::endl;
for (; status.isNotFatal(); modIteratorPtr->advance(status))
{
iModule *modPtr = modIteratorPtr->getModule(status);
if (modPtr && status.isSuccess())
{
dump(*modPtr);
nxtPtr->destroyModule(modPtr);
}
}
nxtPtr->destroyModuleIterator(modIteratorPtr);
}
}
在我的 8527 上的模块列表大致如下,此列表在以后刷 firmware 的时候有可能会变化。
id 是模块的唯一编号;size 是模块的大小,不过似乎没用上;io 是此模块映射的 I/O 地址空间大小。
设备 NXT, protocol=1.124, firmware=1.3, Flash=50.32KB
枚举设备支持模块…成功!
模块 Comm.mod id=0×50001 size=0 io=1896
模块 Input.mod id=0×30001 size=0 io=80
模块 Button.mod id=0×40001 size=0 io=36
模块 Display.mod id=0xa0001 size=0 io=1720
模块 Loader.mod id=0×90001 size=0 io=8
模块 Low Speed.mod id=0xb0001 size=0 io=167
模块 Output.mod id=0×20001 size=0 io=100
模块 Sound.mod id=0×80001 size=0 io=30
模块 IOCtrl.mod id=0×60001 size=0 io=2
模块 Command.mod id=0×10001 size=0 io=32820
模块 Ui.mod id=0xc0001 size=0 io=40
此外另一类重要信息是 NXT 上的文件列表,遍历的方式非常类似
const size_t MAX_FILENAME_LEN = 20;
void dump(iFile& file)
{
tStatus status;
ViChar name[MAX_FILENAME_LEN];
file.getName(name);
std::cout << " 文件 " << std::setw(sizeof(name)) << std::right << (char *)name << std::left
<< "tsize=" << std::setw(10) << std::left << file.getSize(status)
<< "tavailable=" << file.getAvailableSize(status) << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
// 枚举 NXT 设备
status.clear();
std::cout << " 枚举设备包含文件…";
iFileIterator *fileIteratorPtr = nxtPtr->createFileIterator("*.*", status);
if (!fileIteratorPtr || status.isFatal())
{
std::cout << "失败,错误码:" << status.getCode() << std::endl;
}
else
{
std::cout << "成功!" << std::endl;
for (; status.isNotFatal(); fileIteratorPtr->advance(status))
{
iFile *filePtr = fileIteratorPtr->getFile(status);
if (filePtr && status.isSuccess())
{
dump(*filePtr);
nxtPtr->destroyFile(filePtr);
}
}
nxtPtr->destroyFileIterator(fileIteratorPtr);
}
}
注意在两次枚举之间,需要用 status.clear 显式重置状态,否则调用会失败。
一个典型的文件列表如下,此列表包括在 My Files 菜单下的所有类型文件
size 是文件的实际大小;available 是文件的可用大小,不过似乎也没有支持。
枚举设备包含文件…成功!
文件 config.txt size=77 available=0
文件 Timeglass.ric size=76 available=0
文件 hands2.ric size=834 available=0
文件 main.rxe size=16930 available=0
文件 Program.tmp size=13 available=0
文件 NVConfig.sys size=1 available=0
文件 RPGReader.sys size=14346 available=0
文件 Demo.rxe size=9436 available=0
文件 Try-Touch.rtm size=3788 available=0
文件 Try-Light.rtm size=4456 available=0
文件 Try-Sound.rtm size=6864 available=0
文件 Try-Ultrasonic.rtm size=3756 available=0
文件 Try-Motor.rtm size=2630 available=0
文件 Woops.rso size=4699 available=0
文件 faceopen.ric size=316 available=0
文件 faceclosed.ric size=316 available=0
文件 ! Startup.rso size=8161 available=0
文件 ! Click.rso size=451 available=0
文件 ! Attention.rso size=1755 available=0
实际上这种种的 API 调用,最终实现上都是通过相同的一套通讯协议完成的,也就是前面 NXT 信息中 protocol 版本所指定的协议。LEGO 在 Bluetooth Developer Kit (BDK) 中有一份 Appendix 1-LEGO MINDSTORMS NXT Communication protocol 文档详细介绍了这个协议。
因此我们也可以直接使用此协议,向 NXT 发送命令,这也是 Microsoft Robotics Studio 之所以能通过蓝牙方式,跳过必须安装驱动的限制的原因。
void dump(iNXT& nxt)
{
// 打印 NXT 基本信息
ViUInt8 cmdPlayTone[] = { 0×03, 0×00, 0×18, 0×10, 0×00 };
nxt.sendDirectCommand(
false /* 此命令不需要等待响应 */,
reinterpret_cast< ViByte* >( cmdPlayTone ), sizeof( cmdPlayTone ),
NULL, 0 /* 没有响应缓冲区 */,
status );
}
上述代码会向 NXT 设备发送一个 Play Tone 的直接命令,发出一个短促的响声。回头有机会再详细分析吧