使用 Intel® XDK、Node.js 和 MRAA 库进行机器人编程
本文介绍了使用 Intel® XDK 通过 WiFi、Node.js 和 MRAA 库对机器人进行编程的另一种方法。
获取新的 Intel® IoT Developer Kit,这是一个完整的硬件和软件解决方案,可让开发人员使用 Intel® Galileo 和 Intel® Edison 板创建令人兴奋的新解决方案。访问 Intel® 物联网开发者中心。
引言
如今,可编程机器人套件形式多样,无论是在爱好商店还是在 dfrobot.com 等在线网站上都可以找到。您可以学习许多不同的平台、编程语言和工具。Dfrobot* 创建了一个名为 Devastator 的坦克机器人平台,该平台包含 Romeo* 控制板。此板已修改为与 Intel® Edison 计算模块配合使用,以通过增加的 I/O、集成 WiFi、USB Host、伺服控制和增加的处理能力来提高套件的功能。该套件可使用 Arduino* IDE 和开箱即用的 USB 连接进行编程。
本文介绍了使用 Intel® XDK 通过 WiFi、Node.js* 和 MRAA 库对机器人进行编程的另一种方法。特别是,本文将讨论使用的工具、Romeo 控制板、外围引脚映射、创建 Intel XDK 项目以及机器人传感器和执行器组件的实现。
本文将通过一个机器人自主运行和避障的原型来总结讨论的概念。
开始使用机器人平台
有关 Devastator 机器人平台的介绍和入门,请参阅以下链接
https://software.intel.com/en-us/articles/overview-of-intel-edison-based-robotics-platform
使用的工具
Node.js*
Node.js* 是一个轻量级的 JavaScript 运行时,具有非阻塞 I/O 模型,拥有庞大的开源库生态系统。您可以从以下链接了解更多关于 Node.js 的信息。
MRAA 库
MRAA 是一个基于 Linux 的开源底层 C 外设库,提供 C++、Python、Node.js 和 Java 语言的绑定。这使得开发人员在开发 IoT 应用程序时可以选择熟悉的语言,从而获得灵活性。该库支持各种 x86 和 ARM 平台,并具有通用的 API。Intel® Edison 在其 Linux 映像中预装了 MRAA 库,Intel XDK 可以管理将库更新到最新版本。要了解更多关于 MRAA 的信息,请参阅以下链接。
https://github.com/intel-iot-devkit/mraa
http://iotdk.intel.com/docs/master/mraa/edison.html
Intel XDK
Intel XDK 是一个完整的 IDE,用于通过 WiFi 连接开发、编程和调试 Intel® Edison IoT 应用程序。它自带 Node.js 环境设置,并已准备好使用 MRAA。该工具是免费的,可以从以下链接下载。
https://software.intel.com/en-us/intel-xdk
Romeo Intel® Edison 控制板
查看原理图有助于理解系统中的关键组件以及如何最好地将可用外设映射到机器人使用的传感器和执行器。该机器人使用各种外设来完成其任务。下面列出了机器人组件到外围引脚的映射。此外,还进行了从物理引脚到 MRAA 引脚的映射。这个引脚映射是我们将在代码中用于初始化外设的内容。还应注意,板上存在电平转换器。这些引脚也在下表中注明,以在查看原理图时帮助进行关联。
组件 (Component) |
外设 |
Intel® Edison 引脚 |
转换引脚 |
MRAA 引脚 |
被动红外传感器 |
数字输入 |
GPIO43 |
D11 |
38 |
倾斜伺服 |
PWM |
PWM1 |
D5 |
14 |
平移伺服 |
PWM |
PWM0 |
D3 |
20 |
超声波传感器 |
UART TX UART RX |
GP131 GP130 |
D1/TX D0/RX |
UART0 |
左 LED |
数字输出 |
GPIO48 |
D7 |
33 |
右 LED |
数字输出 |
GPIO41 |
D10 |
51 |
蜂鸣器 |
PWM |
PWM2 |
D6 |
0 |
有刷直流电机 |
I2C SCL I2C SDA |
SDA1 SCL1 |
I2C1_SDA I2C1_SDL |
I2C0 |
Intel® Edison 的 MRAA 引脚映射表可在以下链接中找到。
http://iotdk.intel.com/docs/master/mraa/edison.html
Romeo 原理图可从以下链接下载
创建 Intel XDK 项目
现在我们已经讨论了使用的工具并研究了原理图,我们可以创建一个新的 Intel XDK 项目并开始为机器人开发代码。
创建新项目
单击“新建项目”按钮
单击“空白 IoT Node.js 模板”
输入名称 -> 单击“创建”
连接到开发板
单击“升级 XDK 守护进程并更新开发板上的库”
传感器和执行器组件实现
现在我们已经创建了一个新项目并连接到开发板,我们可以开始编写代码来与 LED、红外传感器、伺服、蜂鸣器、超声波传感器和有刷直流电机组件进行接口。
MRAA 初始化
使用以下代码在项目中初始化 MRAA 库。
//MRAA Initialization
var m = require("mraa");
指示灯 LED
机器人正面装有两个指示灯 LED。例如,这些可用作机器人方向、物体检测和距离的指示。
通过为 MRAA 引脚 33 和 51 创建 2 个 MRAA GPIO 对象来初始化 LED。这两个引脚都使用 dir()
函数设置为 OUTPUT,默认值为 LOW。
var leftLED = new m.Gpio(33);
var rightLED = new m.Gpio(51);
leftLED.dir(m.DIR_OUT_LOW);
rightLED.dir(m.DIR_OUT_LOW);
典型的 LED 功能是打开、关闭和切换 LED。这可以使用下面的代码来完成,该代码针对右侧 LED 使用 GPIO write()
和 read()
函数。
//Turn On
rightLED.write(1);
//Turn Off
rightLED.write(0);
//Toggle
rightLED.write(rightLED.read()^1);
被动红外传感器 (PIR)
被动红外传感器安装在坦克机器人的后部,可以在坦克向后移动时指示运动。如果检测到运动,传感器会发出 HIGH 指示,如果没有运动则发出 LOW 指示。
此传感器的初始化与 LED 类似,它使用 MRAA 引脚 38 的 GPIO 对象,但使用 dir()
函数将传感器配置为 INPUT。
var pirMotionSensor = new m.Gpio(38);
pirMotionSensor.dir(m.DIR_IN);
通过轮询传感器,如下面的函数所示,也可以轻松检测到运动。使用 GPIO read()
函数轮询传感器。如果检测到运动,函数返回 TRUE,如果未检测到运动,函数返回 FALSE。
function isMotionDetected() {
if (pirMotionSensor.read())
return true;
else
return false;
}
伺服电机
该坦克包含两个伺服。第一个伺服用于平移超声波传感器和摄像头。平移使坦克能够左右观察并确定其附近的物体。第二个伺服用于向上或向下倾斜摄像机角度。标准伺服接口每隔至少 20ms 或 50Hz 期望一个脉冲。脉冲宽度决定了伺服位置,范围为 1ms-2ms,用于配置伺服位置。例如,1ms 脉冲宽度将伺服移动到 0 度位置,1.5ms 脉冲宽度将伺服移动到 90 度中立位置,2ms 脉冲将伺服移动到 180 度位置。使用脉冲宽度调制 (PWM) 外设可以轻松实现这一点。
通过使用 MRAA 引脚 14(用于倾斜)和 MRAA 引脚 20(用于平移)创建 2 个 MRAA PWM 对象来初始化伺服。伺服的周期通过使用 period_us()
函数来配置,该函数以微秒为单位设置周期。在外设初始化后,通过调用 panCenter()
和 tiltCenter()
函数将伺服居中到中立位置。
var tiltServo = new m.Pwm(14); //PWM1
var panServo = new m.Pwm(20); //PWM0
tiltServo.period_us(10000); //100Hz -> 10ms Period
panServo.period_us(10000); //100Hz -> 10ms Period
panCenter();
tiltCenter();
下面的函数通过调用 pulsewidth_us()
函数配置 PWM 外设的脉冲宽度,然后通过调用 enable(true)
函数启用外设,来实现平移伺服的移动,这些函数包括 panRight()
、panLeft()
和 panCenter()
。调用 sleep()
函数进行短暂延迟,以允许伺服移动,然后通过调用 enable(false)
函数禁用外设。
function panRight() {
panServo.enable(false);
panServo.pulsewidth_us(1000); //0 degree position
panServo.enable(true);
sleep(200);
panServo.enable(false);
}
function panLeft() {
panServo.enable(false);
panServo.pulsewidth_us(2000); //180 degree position
panServo.enable(true);
sleep(200);
panServo.enable(false);
}
function panCenter() {
panServo.enable(false);
panServo.pulsewidth_us(1500); //90 degree position
panServo.enable(true);
sleep(200);
panServo.enable(false);
}
向上或向下移动倾斜伺服与平移伺服的任务类似,只是角度不同,这些角度是经过实验确定的。函数 tiltUp()
、tiltDown()
和 tiltCenter()
列在下面。
function tiltUp() {
tiltServo.enable(false);
tiltServo.pulsewidth_us(1250); //45 degree position
tiltServo.enable(true);
sleep(200);
tiltServo.enable(false);
}
function tiltDown() {
tiltServo.enable(false);
tiltServo.pulsewidth_us(1750); //135 degree position
tiltServo.enable(true);
sleep(200);
tiltServo.enable(false);
}
function tiltCenter() {
tiltServo.enable(false);
tiltServo.pulsewidth_us(1500); //90 degree position
tiltServo.enable(true);
sleep(200);
tiltServo.enable(false);
}
蜂鸣器
该坦克包含一个蜂鸣器,可用于事件的音频指示。例如,当坦克接近物体时,它可以发出 C 大调音阶的不同音符来指示其距离。蜂鸣器也使用 PWM 外设,因此初始化和使用将与伺服类似。它与伺服的心态略有不同,因为我们将 PWM 外设用作简单的数模转换器 (DAC)。
通过在 MRAA PWM 对象上使用 MRAA 引脚 0 来初始化蜂鸣器。振幅通过 write()
函数配置,用于配置 PWM 占空比,并初始化为 0。通过调用 enable(false)
函数禁用外设。
var buzzer = new m.Pwm(0); //PWM2
buzzer.write(0.0); //Duty Cycle
buzzer.enable(false);
通过使用 period_us()
函数设置声音频率,然后通过调用 enable(true)
函数启用声音来产生蜂鸣器的声音。下面是一个播放 C 大调一个八度的示例函数,可用作机器人初始化时的指示音。一个简单的 125ms 定时器循环播放一个音符,并在八度音阶中递增到下一个音符。
var state2=0;
var handle2=setInterval(notes,125); //125ms timer loop
function notes() {
switch (state2){
//C4
case 0: buzzer.enable(false); buzzer.period_us(3831); buzzer.enable(true); state2++; break;
//D4
case 1: buzzer.enable(false); buzzer.period_us(3412); buzzer.enable(true); state2++; break;
//E4
case 2: buzzer.enable(false); buzzer.period_us(3039); buzzer.enable(true); state2++; break;
//F4
case 3: buzzer.enable(false); buzzer.period_us(2865); buzzer.enable(true); state2++; break;
//G4
case 4: buzzer.enable(false); buzzer.period_us(2551); buzzer.enable(true); state2++; break;
//A4
case 5: buzzer.enable(false); buzzer.period_us(2272); buzzer.enable(true); state2++; break;
//B4
case 6: buzzer.enable(false);buzzer.period_us(2028); buzzer.enable(true);state2++; break;
//C5
case 7: buzzer.enable(false);buzzer.period_us(1912); buzzer.enable(true);state2++; break;
//End
default: clearInterval(handle2); state2=0; buzzer.enable(false); break;
}
}
超声波传感器
坦克使用超声波传感器来确定其与物体的距离。它安装在机器人的前面,可以通过前面的平移伺服左右平移来观察周围环境。该传感器有 3 种不同的接口用于收集距离数据。有 PWM 输出、模拟输出或 UART 接口。您可以在下面的 wiki 中了解更多关于此传感器信息,其中包含有关传感器、其不同接口和命令协议的有用信息。
https://www.dfrobot.com/wiki/index.php/URM37_V4.0_Ultrasonic_Sensor_(SKU:SEN0001)#Introduction
对于我们的 Node.js 程序,使用 UART 接口与此传感器读取距离数据。传感器的 TXD 引脚 9 连接到 Intel® Edison RX GP130,传感器的 RXD 引脚 8 连接到 Intel® Edison TX GP131。
通过创建 MRAA UART 对象来初始化 UART。使用 setBaudRate()
函数将波特率设置为 9600 bps,使用 setMode()
设置数据模式,并使用 setFlowControl()
函数设置流控制。此外,还设置了命令缓冲区用于读取传感器数据。命令缓冲区字节 0 包含命令代码,字节 1 和 2 是虚拟字节,字节 3 是数据包的校验和。
var u = new m.Uart(0); //Default
u.setBaudRate(9600);
u.setMode(8,0,1);
u.setFlowcontrol(false, false);
sleep(200);
var command = new Buffer(4);
command[0] = 0x22;
command[1] = 0x00;
command[2] = 0x00;
command[3] = 0x22;
获取来自传感器的数据涉及通过 UART 发送命令数据包,然后接收响应数据包。下面的函数根据阈值确定物体是否靠近,并返回 TRUE 或 FALSE。命令数据包在上面的初始化中设置,并通过调用 write()
函数通过 UART 发送。响应数据包在延迟后接收,然后调用 read()
函数。响应数据包缓冲区字节 0 是虚拟字节,字节 1 和 2 分别是对应于厘米距离数据的低字节和高字节,字节 3 是校验和。为了确定数据是否有效,会分析校验和,然后将数据与传入的阈值进行比较。
function isObjectClose(threshold) {
var rxBuf;
var result;
u.write(command);
sleep(200);
rxBuf = u.read(4);
sleep(200);
if (rxBuf[3] == (rxBuf[0]+rxBuf[1]+rxBuf[2])) {
result = (rxBuf[1]<<8) | rxBuf[2];
if (result < threshold)
return true;
else
return false;
}
else
return true;
}
有刷直流电机
该坦克能够以前进、后退、向左或向右方向移动。该机器人包含两个有刷直流电机,它们安装在坦克底盘的左右两侧。查看原理图后,您会发现 Romeo 板包含一个全桥电机控制驱动器,该驱动器连接到 Atmega8* 微控制器。Intel® Edison 用于与微控制器通信的接口是 I2C。审查此微控制器中实现的 P 代码以了解已编程的 I2C 从机地址和用于控制电机的命令接口非常有用。Intel® Edison 是 I2C 主控,微控制器是地址为 0x4 的 I2C 从机。
您可以在以下链接中找到有关微控制器代码的更多信息
https://github.com/ouki-wang/remeo4edison/blob/master/NG/NG.ino
请参阅下表确定坦克方向和电机方向之间的对应关系
方向 |
左电机 |
右电机 |
前进 |
逆时针 |
逆时针 |
后退 |
顺时针 |
顺时针 |
左侧 |
逆时针 |
顺时针 |
右侧 |
顺时针 |
逆时针 |
通过创建一个新的 MRAA I2C 对象来初始化 I2C 外设。通过调用 address()
函数设置从机地址。命令缓冲区也已初始化,并在字节 0 和 1 处包含一个头。命令数据包的其余字节将在下面讨论。
var x = new m.I2c(0);
x.address(4);
var buf = new Buffer(5);
buf[0] = 0x55; //Header 1
buf[1] = 0xaa; //Header 2
下面的函数在给定传递给函数的 8 位速度值的情况下,以向前方向移动坦克。该函数设置 I2C 命令以设置左电机方向、右电机方向、左电机速度和右电机速度。命令数据包的其余命令字节是用于执行电机方向命令或电机速度命令的字节 2 中的命令代码,用于设置电机方向值或电机速度值的字节 3 中的命令参数,以及字节 4 中的校验和。通过调用 write()
函数发送 I2C 命令。
function tankForward(speed) {
if (speed > 0xFF)
speed = 0xFF;
//Left Motor CounterClockwise
buf[2] = 0xB1;
buf[3] = 0x1;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor CounterClockwise
buf[2] = 0xB2;
buf[3] = 0x1;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Left Motor Speed
buf[2] = 0xC1;
buf[3] = speed;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Speed
buf[2] = 0xC2;
buf[3] = speed;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
}
下面的附加函数以类似的方法用于向后、向左、向右移动坦克以及停止。
function tankBackward(speed) {
if (speed > 0xFF)
speed = 0xFF;
//Left Motor Clockwise
buf[2] = 0xB1;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Clockwise
buf[2] = 0xB2;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Left Motor Speed
buf[2] = 0xC1;
buf[3] = speed;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Speed
buf[2] = 0xC2;
buf[3] = speed;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
sleep(2000);
}
function tankRight() {
//Left Motor Clockwise
buf[2] = 0xB1;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Counter-Clockwise
buf[2] = 0xB2;
buf[3] = 0x1;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Left Motor Speed
buf[2] = 0xC1;
buf[3] = 0x90;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Speed
buf[2] = 0xC2;
buf[3] = 0x90;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
sleep(2000);
}
function tankLeft() {
//Left Motor Counter-Clockwise
buf[2] = 0xB1;
buf[3] = 0x1;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Clockwise
buf[2] = 0xB2;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Left Motor Speed
buf[2] = 0xC1;
buf[3] = 0xC0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Speed
buf[2] = 0xC2;
buf[3] = 0xC0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
sleep(2000);
}
function tankStop() {
//Left Motor Speed
buf[2] = 0xC1;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
//Right Motor Speed
buf[2] = 0xC2;
buf[3] = 0x0;
buf[4] = (buf[0]+buf[1]+buf[2]+buf[3]) & 0xFF;
x.write(buf);
}
自主机器人
现在我们已经使用 MRAA 库为传感器和执行器接口构建了函数基础,我们可以通过创建一个允许机器人自主移动和避免碰撞的状态机来将其整合起来。概念是坦克将尝试向前移动,直到它检测到有物体靠近。当有物体靠近时,它将尝试后退,如果后面没有运动。坦克将尝试向左或向右移动以避开物体,然后再次尝试向前移动。请参阅下面的流程图,该流程图描述了前进、后退、向左和向右状态的运动以及随后的实现。
流程图
实现
var state=0; //0-forward 1-backward 2-right 3-left
var turnDirection=0; //0-right 1-left
while(1) {
switch (state) {
//Forward State
case 0:
panCenter();
if (isObjectClose(10)) {
state=1; //Go to Backwards State
}
else {
tankForward(0x7F);
}
break;
//Backward State
case 1:
tankStop();
while (isMotionDetected()) {sleep(100); }
tankBackward(0x7F);
tankStop();
state=2^turnDirection; //Go to Right or Left State
break;
//Right State
case 2:
panRight();
if (isObjectClose(10)) {
state=1;
}
else {
tankRight();
tankStop();
turnDirection ^=1;
state=0; //Go to Forward State
}
break;
//Left State
case 3:
panLeft();
if (isObjectClose(10)) {
state=1;
}
else {
tankLeft();
tankStop();
turnDirection ^=1;
state=0; //Go to Forward State
}
break;
default:
tankStop(); //Should never get here
break;
}
}
摘要
使用 Intel XDK,我们展示了如何通过 WiFi 无线编程机器人。我们讨论了如何查看原理图并为 MRAA 库的用途映射外设和组件。我们讨论了传感器和执行器组件以及如何使用 Node.js 和 MRAA 库实现功能。最后,我们创建了一个具有流程图和实现的自主机器人概念。
作者简介
Mike Rylee 是 Intel Corporation 的一名软件工程师,在开发嵌入式系统以及 Android*、Windows*、iOS* 和 Mac* 的应用程序方面拥有丰富的经验。他目前从事物联网项目。
注意事项
本文档不授予任何知识产权的许可(明示或暗示,禁止反言或以其他方式)。
Intel disclaims all express and implied warranties, including without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement, as well as any warranty arising from course of performance, course of dealing, or usage in trade.
本文档包含有关正在开发中的产品、服务和/或流程的信息。此处提供的所有信息如有更改,恕不另行通知。请联系您的 Intel 代表以获取最新的预测、时间表、规格和路线图。
所描述的产品和服务可能包含称为勘误的缺陷或错误,这可能导致与公布的规格不符。当前的已特性化勘误可应要求提供。
可以通过致电 1-800-548-4725 或访问 www.intel.com/design/literature.htm 获取包含订单号且本文档中引用的文件的副本。
Intel、Intel 标识和 Intel RealSense 是英特尔公司在美国和/或其他国家的商标。
*其他名称和品牌可能被声明为他方财产
**本示例源代码根据 Intel 示例源代码许可协议发布 赞 订阅添加新评论标记为垃圾邮件。
© 2016 英特尔公司。