![]() |
|
Spaces home Flier's SkyPhotosProfileFriendsMore ![]() | ![]() |
|
Flier's Sky天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西
June 21 天堂里没有痛苦——yuannian 师兄,一路走好 晚上参加完公司的培训,回到宿舍刚坐定开始看文档,忽然收到QuickMouse从MSN上发来噩耗,原白云黄鹤BBS站长yuannian去世了。于是匆忙登录已经年余未上的白云黄鹤,在sysop看到了站务公告和QuickMouse的哀悼文章。为进一步获取详细情况,我又google到其父在文学论坛介绍的详情:
一页一页看下来,我已不知该如何形容此时的感觉:几分惋惜,几分悲哀,几分愤怒!
30 岁正值黄金年华,无法成功立业也罢,竟至白发人送黑发人。虽未有妻儿之累,但难以想像其老父母,如何度过痛失骄子的残生,如何面对冰冷的遗像。
5 年的青春都献给了华为,1 年至经理,3 年至技术负责人。在发现白血病时却已至中晚期,后四个月骤然而逝。作为师弟,作为站友,作为IT同业人,我感到悲哀的同时感到无比的愤怒!
首先,根据 google 的结果,白血病分急性和慢性两种:前者是急性发作,起病的时候有很多症状、体征,比如出血,很难说有潜伏期;后者症状不明显,可能让你没有觉察到,很多人是到了有严重的症状才去看病。如果每年有体检的机会,可能比较早期就能发现。通过血象检查,白血病是比较容易确诊的。也就是说,作为一个有几万名高科技人才的华为,一个有着各种完善业务流程的国际化公司,竟然没有一个最基本的,确保其核心资产:人力资源长期可用性的体检制度。而与之相似的更多所谓高科技企业,在这方面也令人深表怀疑。有幸的是我工作的两家企业,无论规模大小在这方面还是非常正规和严谨的,也说明这跟企业规模无关而取决于重视程度。
其次,白血病的早期症状中,除了中后期的各种出血外,“持续头晕、高烧不退,脸色苍白、容易疲倦”等现象,实际上跟目前大部分 IT 人士长期处于的“亚健康”状态类似。以我个人经历为例,在到目前公司之前的大概两三年时间中,也是长期处于高度紧张的工作状态。其时除了高烧不退的情况较少外,实际上跟白血病早期的症状难以分辨。而一旦常年处于这种状态,人对健康的敏感程度也会逐渐下降,等到出现出血等严重症状时可能已经完了。而自己年少轻狂之时,又往往视健康为无物,期望以燃烧生命来弥补知识和技能上的缺陷。
低头反思,自己五一前西藏之行也是折戟于轻视二字。仗着较为丰富的户外经验、几次高原徒步的成功经历,在休息和准备不足的情况下,贸然组队租车向世界屋脊——阿里进发。途中又低估平均四千多米海拔缺氧的严重性,最终因睡觉着凉发展为肺水肿。如果不是路遇几位好心的当地人,被两天三夜连续驱车赶路,送回拉萨住院就诊,估计在白云黄鹤上站务公告里面就是我的名字了。躺在拉萨总参医院的病床上,同样也是连坐起或翻身都困难的我,最终侥幸得还;但师兄却没有这么幸运,没有逃脱病魔的魔爪。
好在经过一段长考,我痛定思痛开始尝试转变生活方式,而这次的濒死体验也进一步完善了自己的认识。期望其他朋友也能多以长远的眼光,来思考和规划自己的未来之路。
btw: 刚刚跟身在华为的另一位同学聊,据称他一位华为的博后师兄,前不久也不幸去世。而他业已准备在今年合同到期后,离职暂时回家修养。
希望这样的事情不要在无谓的重复,yuannian 师兄,一路走好!
November 19 another day 又是平淡、充实的一天,睡到自然醒、美食、电影、羽毛球、麻辣烫,然后在许威悠远的歌声中享受着龙井的清香。但在快步穿过繁华的新街口商业街时,在高高跃起将一个后场球斜线扣死时,亦或隔着车窗看着夜色中快速略过的灯红酒绿时,我却似乎无法感觉到一丝的真实感,仿佛像是一个路过的灵魂在尘世中穿行。
当地铁列车从身边呼啸而过时,我仿佛又听到了小黑湖清晨时暴风雪的尖啸;当骑车的路人在车窗外缓缓穿行时,我仿佛又看到了烈日下慢慢爬升的马队;那路边欢喜的孩子的笑脸,也不知觉中与带着高原红的同样天真的孩子重合。
一霎那间,我仿佛能理解到兄弟连里,温斯特从伦敦地铁出来,在河边漫步,回旅舍在浴缸中泡热水时的那种奇妙感觉。那是一种由两个不同世界堆叠在一起的错乱感,是一种对世俗社会缺乏信任感和归属感的困惑,是一次对 Who am I 和 What am I doing 的质询。看来上一次的长考只管了半年 September 28 NXT 学习笔记 [3] 获取 NXT 信息 (C++) 这个系列文章因为开始涉及到太多技术细节,跟这儿的定位不符合,改到另外一个 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 后,在 document\fantom.chm 里是基本封装类型的使用帮助,以及一个简单的示例程序代码;includes 里是 fantom 的头文件,以及 labview 的 vi 支持库,这个后面有机会再详细介绍。targets\win32U\i386\msvc71\release 里是需要 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:\Study\Robot\LegoDemo\Debug>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=0x50001 size=0 io=1896 模块 Input.mod id=0x30001 size=0 io=80 模块 Button.mod id=0x40001 size=0 io=36 模块 Display.mod id=0xa0001 size=0 io=1720 模块 Loader.mod id=0x90001 size=0 io=8 模块 Low Speed.mod id=0xb0001 size=0 io=167 模块 Output.mod id=0x20001 size=0 io=100 模块 Sound.mod id=0x80001 size=0 io=30 模块 IOCtrl.mod id=0x60001 size=0 io=2 模块 Command.mod id=0x10001 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[] = { 0x03, 0x00, 0x18, 0x10, 0x00 }; nxt.sendDirectCommand( false /* 此命令不需要等待响应 */, reinterpret_cast< ViByte* >( cmdPlayTone ), sizeof( cmdPlayTone ), NULL, 0 /* 没有响应缓冲区 */, status ); } 上述代码会向 NXT 设备发送一个 Play Tone 的直接命令,发出一个短促的响声。回头有机会再详细分析吧 :) September 26 NXT 学习笔记 [2] 运行时数据 前面曾经提到 NXT 的可执行文件由运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分组成。其中运行时数据可进一步分为数据空间目录 (Dataspace table of contents - DSTOC) 和数据空间缺省值 (Dataspace Defaults)。VM 在加载一个可执行文件时,会在对应的 32K 内存中划出一块专门用于存储数据空间 (Dataspace),这块内存是由一系列不同类型的数据项组成,直接对应与可执行文件中的 DSTOC,如果有缺省值的话则自动进行初始化。因而后期字节码访问数据空间时,可以直接使用进程唯一的 DSTOC 索引号定位实际数据。与字节码指令类似,可执行文件中的 DSTOC 和缺省值不被加载到内存中,直接在 Flash 卡上进行地址映射。 而在数据空间中存储的数据项,其类型也是由 VM 预定义好的,直接在指令一级提供支持。 最常见的简单数据类型就是整数了,NXT 支持 8/16/32 位有符号或无符号整数,以及一系列针对整数的算术操作符。布尔类型在 NXT 中直接以 unsigned byte 表示,0 表示 FALSE,其它值均为 TRUE。而浮点数、分数甚至复数之类都不在直接支持的范畴内,不过可以通过整数运算进行模拟,只是语法一级的问题罢了。 较为复杂的数据类型是集合类型,NXT 提供内建数组 (array) 和簇 (cluster) 的支持。 数组 (array) 是一组具有相同数据类型子元素的集合,并支持在运行时调整大小。例如可以定义一个长度为 0 的数组表示一组点,在运行时根据传感器采样进行扩展和填充。ASCII 字符串是一种特殊的 unsigned byte 数组,为兼容 C 的习惯其末尾会有一个 NULL(0) 作为结束符。 簇 (cluster) 这是类似于其它语言中结构 (struct) 的概念,由一组不同类型子元素组合而成,一般被用于将相关数据封装到一起。 NXT 中聚合类型是可以相互嵌套的,可以在一个簇里面定义几个不同类型的数组,也可以在将数组的元素类型定义为数组来模拟多维数组。 此外 NXT 为多任务支持还提供了一种互斥量 (mutex) 类型。这种类型虽然实现上由一个 32bit 整数表示,但不能被用作聚集类型的子元素,因此只能独立存在并通过内建 OP_ACQUIRE/OP_RELEASE 指令进行操作。 既然 DSTOC 将程序可用到的数据及其类型都已定义清楚,从 VM 角度就可以将之进一步分为运行时内存布局不会发生变化的静态数据,以及运行时需要进行调整的动态数据。前者包括 DSTOC 中绝大多数类型的数据,它们在编译期就有编译器根据情况确定了布局;后者则包括所有的数组类型,因为 NXT 提供动态调整数组大小的能力,所以它们在 DSTOC 中只是一个入口,由 VM 在运行器进行管理。 落实到实现上,VM 将静态数据和动态数据放到两个不同的内存池中,前者在内存低地址区域固定,后者在内存高地址区域向下增长。如果动态内存区域超出内存池限额,系统会显示一个 File Error 的错误(不知道原因的话谁猜得出来)。 而对程序的字节码来说,这些都是透明的,指令只需要使用全局唯一的 DSTOC 索引号即可访问到相应的数据项 数据项地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset 对 VM 来说,静态数据基本上在编译期就确定了,它所需要关心的只是如何对动态数据进行管理。NXT 中引入了一个 dope vector (DV) 的概念,来针对每个动态数据项的变化进行跟踪。DV 结构的伪代码如下: typedef struct DopeVector { short Offset, // 数组数据在内存中的偏移 short ElementSize, // 数组子元素的大小 short ElementCount, // 数组子元素的个数 short BackPointer, // 未用,用于建立双向链表? short LinkIndex // 下一个 DV 的索引 } DV, *PDV; VM 维护了一个全局唯一的 dope vector array (DVA) 来跟踪在动态数据区域中分配的所有内存,所有 DV 又通过 DV.LinkIndex 以链表形式组织在一起,便于系统进行遍历和维护。当然这个 DVA 的存在对字节码来说是完全透明的,只是最大限度利用现有机制来实现自我管理。 当字节码需要通过 DSTOC 索引访问数组时,通过 DSTOC 获得的内存偏移地址实际上指向的是一个 DV 索引,然后再根据 DV 索引从 DVA 中获取到实际的数组偏移地址。计算的方式如下: DV 索引地址 = 数据空间开始地址 + DSTOC[数据空间索引].offset 数据项地址 = 数据空间开始地址 + DVA[DV 索引].offset 通过这样一个两级索引定位,VM 可以在不需要对 DSTOC 和字节码做任何调整的情况下,根据运行时指令的需求修改数组的大小,因此所有的 DV 索引会跟 DSTOC 中的数组数据项绑定。 此外对于嵌套实现的多维数组,除了第一维数组是在 DSTOC 中固定死的,其它每个子数组实际上在上一维中只是一个 DV 而已,字节码可以通过 OP_INDEX 等指令间接任意访问。 NXT 学习笔记 [1] 系统架构 熟悉了基本功能,接着可以看看一些技术层面的内容了,呵呵,毕竟买回来不是看手册拼模型的 :P
Lego 这方面非常厚道,从系统结构到指令集,设置关键部件的线路图都有提供,看来是拼足了劲要把这摊子事搞大啊,呵呵
这个手册虽然号称是可执行文件标准,但实际上对整个系统架构都做了比较详细的介绍。下面的大部分内容都是我通过阅读此手册总结或者翻译过来的,呵呵
NXT 的运算核心是一个 32bit 的 ARM7TDMI 嵌入式 RISC 处理器,主频一般在 115MHz(0.18 μm)-133MHz(0.13 μm) 之间,理论上回头可以自己把它换成 236MHz(90 nm) 的 ARM7,甚至接口兼容的其它型号,ARM 9/9E/10 都提供对 ARM7 程序的二进制兼容性。存储则包括 256K Flash 和 64K RAM,容量上跟现在动辄几 G 的系统没有可比性,不过对于嵌入式系统来说勉强也算够用。此外还集成了一个 8bit 的 AVR 嵌入式 RISC 处理器,以及对应的 4K Flash 和 512B RAM (-_-b),估计是用于 I/O 或步进电机控制。
NXT 的软件系统,主要是一个扮演 OS 角色的 ANSI C 编写的 Frimware,提供对各种传感器/步进电机的控制,并为二次开发提供虚拟机(VM)支持。用户编写的程序会被编译成 NXT 的字节码(Byte Code)指令,以后缀为 .RXE 的特定可执行文件格式存储在 Flash 上,在执行时由 VM 加载并解析,并提供 32K 的 RAM 供执行。 NXT 的可执行文件主要分为运行时数据 (Run-time Data)、调度 (Scheduling) 和字节码 (ByteCodes) 三部分。而在执行时 VM 会根据需求,为前两部分提供对应的 RAM 空间,存储可变的运行时状态信息。 NXT 的字节码允许用户执行常规的数学/逻辑运算、指令流控制以及 I/O 操作等等,基本上是一个精简版本的 RISC 指令集。这些指令将根据功能以名为 clump 的方式组织在一起,类似子过程。NXT 中较为特殊的指令调度信息,将提供各个 clump 分别包括哪些字节码、clump 的运行时状态以及当前 clump 执行后应加载的其它 clump。VM 会根据这些记录,在 clump 所依赖数据就绪后再调用之。clump 之间也可以相互调用,但这个调用过程必须是同步的,调用者需挂起直到调用返回。此外 clump 本身是不可重入的,如果两个不同 clump 需要调用一个共享 clump,则调用者必须通过互斥量 (Mutex) 确保不会重入,因此 clump 调用自身的递归操作也是不被支持的。 NXT 之所以采用这样一个机制,估计是放弃了基于堆栈的思路,通过 clump 为单位来简化 VM 的设计。与之相对应的是其可视化编程环境中,常用操作都是被一个个小方块(Lego 积木?)封装起来,然后通过拖拉和逻辑组合来实现算法;这样一来实现上就较为简单,常用操作映射到 clump,组合逻辑映射到调度 (Scheduling)。不过基于其字节码支持访问内存的考虑,理论上不排除能对其架构进行改进,通过内存操作模仿堆栈来实现 clump 重入和递归的可能性。 此外一个好消息是 VM 能够提供多个 clump 并行执行的支持,完成多个任务的 clump 在数据就绪的情况下能够逻辑上并行执行;同时一个坏消息则是虽然 VM 能在指令一级帮助解决资源冲突,但还是需要自己来判断并处理竞争条件。 NXT 的程序运行可以分为四个阶段: 1.验证 (Validation): 加载文件并验证版本和其它文件头信息 2.激活 (Activation): 在 RAM 中分配并初始化运行时数据 3.执行 (Execution): 解析文件中的字节码指令,根据调度信息运行相应 clump 4.停止 (Deactivation): 重新初始化所有 I/O 系统和 RAM 中的数据结构 值得注意的是在执行过程中,所有的字节码都不会被载入到内存,而是通过直接映射 Flash 卡到地址空间完成。这样的好处是内存只被用来保存可变得程序数据,坏处则是代码在执行的过程中无法被改变。 to be continue...
成功进入机器人时代 :D 经过在 eBay 上近半个月的坚苦搏杀,终于赶在十一前拿到了向往已久的 Lego Mindstorms NXT。与那些貌似有趣但本质上只会摇尾巴的所谓娱乐机器人不同,这次 Lego 发布的 NXT 绝对是一个划时代的产品。内建 32bit ARM7 处理器,自带光、声、雷达和接触传感器,最大三路步进电机输出,支持蓝牙/USB接口。最为关键的是,除了自带的图形化可视编程界面外,提供了较为完整的各种开发SDK,而 MS 更是推出了支持此平台的基于 C# 的开发环境 Microsoft Robotics studio。这意味着 NXT 已经脱离了一个玩具的范畴,第一次让机器人变得平民化,是迈向机器人时代的坚实的一步。回想当年 Intel 推出的 8086/8088 正是扮演了类似的角色,让 PC 真正成为平民化的工具,相信机器人时代的到来也不再是科幻小说中的情节。
说了半天废话,让我们先来看看 NXT 的第一印象,好大一个包装盒,呵呵
完整版本的 NXT 在 Lego 自己网站上标价 $250,但实际上通过 eBay 等方式 $200-$210 就能拿下,不过比较郁闷的是从米国邮寄回来大概需要 $50-$60,如果有朋友能从国外或香港带是最合适不过了。
拆开大盒子,其实里面东西不算太多。最多的是大把大把的 Lego 塑料积木,这是组成各种不同机器人造型的基础原料,种类我可以负责任的说是相当的多,光十字形连接杆就有9种不同长度,各种转接头的种类更是繁多,搞得我大部分时间用在寻找合适的元件上,回头得去买个厨房用的佐料盒分门别类 :P
除此之外在几个小盒子里面装的是 NXT 的核心部件,缺省自带一个主机兼控制器、四种传感器、三个步进电机以及一对乱七八糟的连接线啥的。具体的功效回头等我弄明白了再单独解说。:P
再来一张核心部件的分解图,呵呵
主机上提供是个传感器输入接口(1,2,3,4),三个步进电机输出接口(A, B, C),一个 USB 接口用于从 PC 上下载程序,一个小 LCD 黑白屏用于基本控制和状态显示,此外还内建蓝牙接口用于与外部通讯。能源部分使用六节 5 号电池,没有发现外接电源的接口。可以说整个系统设计的非常简介,在满足基本功能的前提下一点多余都没有,回头有时间将之拆开再仔细分析一把 :D 作为老牌的玩具厂商,LEGO 的可视化编程环境做的非常简单易用。从左边工具条中将各种常见操作(如A口步进电机正向转3圈,或者声音传感器收到一定大小的声音)拖动到中央工作区,然后用逻辑条件进行组合,最后点击一个 Download 按钮就会自动编译并下载到 NXT 的 Flash 上。此外右边还通过 Flash 方式提供了非常详细的 Step by Step 快速入门,可以根据上面的说明快速熟悉各种部件和环境的使用方法。 这是我根据 Quick Start 拼装出来的 TriBot 机器人,呵呵。它首先会用雷达探测面前是否有目标(一个蓝色小球);如果有的话就冲过去,直到碰撞传感器告诉它已经到跟前;接着它会等你的暗号,击掌一声后,用两个大钳子夹起球;掉转180度最大速度跑开(没人追你啊);碰到地上有条黑线后放下球。
呵呵,功能是弱了点,不过好歹是偶的 NXT 第一次露面,来两张特写先 :D June 05 泸沽湖 —— 亚丁 D -1 在我去过的诸多古城或号称古城的地方之中,丽江的大研古城是保护的相对完好的。但这种完好也仅仅体现在建筑风格上,真正的文化的核心————人,实际上也只是保持着商业上的完美。因此除了第一次去丽江时还饶有兴趣的逛逛街市,后面再去时基本上都刻意避开人群,免得看那些已经扮成当地人的外地人,和另外一些想扮成当地人的外地人,在这个纳西主题公园里面,贩卖着廉价的所谓文化。 不过在这熙熙攘攘的闹市之中,也是有不少闹中取静的好去处。例如下午四五点时,爬到狮子山万古楼下找个木椅、亦或躲到黄山公园的凉棚中,砌上一壶清茶、摊开一本好书、换上一副好心情,在从云中洒下的缕缕昔阳中,体会那种宁静而志远的心境。如果时节好的时候,恰好还能碰到低而厚重的雨云,从远处的山边慢慢笼罩过半壁古城,一面是明媚的阳光,一面是茫茫的细雨,不一会儿就云淡风清又一捧彩虹横挂枝头,就如许巍在歌中唱到的一样: ... 好像梦里醒来 见到清新的世界 此刻寂静的心 自在又安祥 我心深处的孤独渴望 我曾莫名的无尽等待 就这样消逝风里风里 就这样消逝风里 无踪影 ... 待天稍稍有些黑了,可以快步穿过四方街上目光呆滞的人群,去七一街抢上一碗川西的肥肠粉。如果食欲好,则会多走几步到木府边上的阿哩哩,尝尝阿呆自酿的青梅酒和姥姥的臭豆腐烧牛肉,这是丽江古城里面为数不多可以算作美食的 :P 说起阿哩哩的阿呆,这又是另一个故事了,有兴趣可以逛逛他的 blog (http://alili.tianyablog.com/)。 如果还是不想回去,可以再爬回狮子山,在半山的青年旅馆,要上一杯云南本地的小粒咖啡,坐在长长的木廊边,凝视着山下的万家灯火。四方街上的熊熊火焰与欢闹人群,给夜幕笼罩下的古城凭增一份奢靡。而在此时,摇曳的灯火和喧嚣的歌声,在黑暗中被放大并扭曲,让距离和时间都慢慢变得不真实起来。 不过这次我还是选择呆在客栈里面调整心态,以尽早从快节奏的都市生活切换到慢节奏的度假生活。 睡到自然醒,洗个热水澡,然后靸着拖鞋,窝在院里的沙发中,看着那只刚刚几个月的小狗,步履蹒跚在院中乱窜。 这家客栈有个小而精致的院落,可以看出以前的房东下过一番心思,虽然面积不大但各种植物错落有致,四季常青且花期相接。一面客房门前立着大片的大叶爬山虎,遮住烈日的同时不忘放入丝丝缕缕的光斑;对过是一棵挺拔的樱花树,不巧刚刚过了花期,只剩一些被雨打过的残花;四周则是一些叫不上名字的高矮植物,零零散散但并不杂乱的点缀在院中。青石和石子铺就的地面古朴而典雅,加上仿古的红漆大门和黄铜门环,真有点古意但并不显得做作。 在院里发了会儿呆翻了会儿书,趁天气凉爽到菜市场去溜达了一圈。如果说有什么地方能够让你直接感受到生活真实,那应该就是菜市场或农贸市场了。四处看看,给朋友带了条当地土烟,本还想整个西瓜回去,掂了掂分量只好放弃 :S 估计是因为老板怕吵,客栈里没有自动洗衣机,于是拿着盆子杀到白马龙潭寺门口洗衣服去 :S 跟一群大妈大婶一起,蹲在三眼井的末端,使尽十八般手艺慢慢折腾那几件衣物,全然不顾旁边参观的旅客诧异的目光。这三眼井历经百年的沧桑,蹲在井边的人换了一代又一代,真让我有点时光回朔的感觉 :) 晚上本想请老板出去喝酒,结果反而被老板拉回家蹭饭,完了又是一人一杯清茶,有一搭没一搭的聊天。虽然有李江山这样一个豪气的汉语名字,但他实际上内心并不适应丽江的商业气氛,对我这种扛着大包的背包组也有着独特的偏爱。在丽江生活几年之后,实际上他的心还留在虎跳峡的群山之中。而与我一样无奈的是,也只能通过不断的抱怨,和偶尔的调剂来缓解,并无法去打破或改变什么。而我这样的背包客,在能唤起他对往日回忆的同时,共同的对大自然的热爱和对生活的感恩,也使得交流起来格外容易。也许这就是现代人的悲哀,在现实和理想之间无法自己把握方向。 June 01 泸沽湖 —— 亚丁 D -2 本来是不打算写游记的,一来自己比较懒而且号称比较忙,二来一直以为旅程是无法记述而只能体会的。但架不住诸多朋友的反复关心,只好把一些零散的感想记录一下,希望能让朋友们分享我的快乐 :)
D -2
直到再一次踏出丽江机场的大门,在寂静星空照耀和凉爽的夜风吹拂下,呼吸着掺杂着沁人心脾的淡淡草香的气息,才真正意识到我又回到这个梦中的地方。虽然此时还不知我的大包被转运到哪架飞机上,也不知未来几天的旅程该如何安排,但现在想什么也没用,当务之急是找个小酒馆吃点新鲜的菜肴,加上一瓶冰镇的本地啤酒。:P
也许这就是我喜欢走长线的原因之一,从一个未知到另一个未知。 虽然从一个多月以前就开始准备这趟行程,但我并无意去把每一个细节都安排清楚。毕竟长线中有太多的不确定因素,明确方向然后坚定的前行是我唯一可控的。而在此过程中碰到的问题和解决问题的过程,往往才是旅程中最有趣的谈资。 就好比今天的旅程:早上还在公司翘着二郎腿开周年庆典大会,中午就匆匆忙忙打车去机场去赶堵车实况,好容易提前5分钟赶到换了登机牌后发现飞机晚点,百无聊赖在候机厅看完所有杂志后,到了丽江机场只好再一路狂奔,可还是晚了半分种才注册完毕,托运行礼却因为还没取到,只能跟着后面的航班转运,还免费获赠一张机场对我行礼不负责任的免责声明。于是我只好在饿着坐了一天飞机后,孤零零一个人躺在候机厅沙发上,等着还不知道在哪儿的行礼 :S 用当时某人给我回的短信的话来说,就是每一个精彩的旅程都有一个糟糕的开始,哈哈,我定义他在夸奖我 :D 也许是老天想多给我一个理由留在丽江,机场工作人员告诉我行礼要跟午夜最后一趟航班过来,于是我用最快的速度冲出机场,快步走向向往已久的最近的一个小酒馆,哈哈,一份火腿、一盆菜汤、一瓶啤酒、一碗米饭,完了再来上一壶龙井,开始有点找到度假的感觉了。 :P 因为准备转长途车去泸沽湖,决定住客运站附近的白马龙潭客栈,打电话跟老板套了套交情并订了床位,领到行礼后跟几个成都来的朋友拼车回丽江,顺道帮客栈老板拉了一票生意,忽悠了一车人直接开了过去。 说起这个客栈老板也是个很有趣的人,父母是在虎跳峡开著名的纳西雅阁客栈,女朋友是以前在虎跳峡客栈拣到的外地游客,然后家族业务拓展到丽江租了个小院落。沉默寡言但却很风趣,待人宽厚但疾恶如仇,没事就坐在院中沙发里跟游客聊天,时不时会掏出一些有趣的照片,或者告诉你一条新的线路,真有点象某某游戏里面客栈的提供游戏线索 NPC,哈哈。 既然到客栈已经一点多了,估计明天也是走不了的,索性跟这哥们一人一杯茶聊上了。春节从虎跳回来去梅里之前,就是住在他这儿,所以先给他灌输了一番梅里的大好形势,然后又听他侃了一通黎明和老君山,看来我的 todo list 又要加长了。 看看天色啥也没看出来,只好看看表已经不早了,于是约着明天找地方请他喝酒,然后洗洗睡一夜无梦。 February 13 被遗忘的寂静村落 | |||||||||||||||||||||||||||||||||||||||||||||||||