Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT

Programming Robotics using the Intel® XDK, Node.js, and MRAA library

5.00/5 (2 votes)
21 Dec 2016CPOL13 min read 9.9K  
This article describes another method of programming the robot using the Intel® XDK to program the robot over WiFi, Node.js, and the MRAA library.

Get access to the new Intel® IoT Developer Kit, a complete hardware and software solution that allows developers to create exciting new solutions with the Intel® Galileo and Intel® Edison boards. Visit the Intel® Developer Zone for IoT.

Introduction

Programmable robotic kits are attainable in many forms these days whether it is from a hobby store or online site like dfrobot.com. There are many different platforms, programming languages, and tools that you can learn. Dfrobot* created a tank robot platform called Devastator that contains the Romeo* controller board. This board was modified for use with the Intel® Edison compute module to bring more capability to the kit with an increased number of I/O’s, integrated WiFi, USB Host, servo control, and increased processing power. The kit can be programmed using the Arduino* IDE and a USB connection out of the box.

This article describes another method of programming the robot using the Intel® XDK to program the robot over WiFi, Node.js*, and the MRAA library. In particular, the article will discuss about the tools used, the Romeo controller board, mapping peripheral pins, creating an Intel XDK project, and the implementation of the sensor & actuator components for the robot.

The article will conclude by tying together the concepts discussed with a prototype for the robot to operate autonomously and avoid collisions.

Getting started with the robotics platform

For an introduction and getting started with the Devastator robotics platform please see the link below:

https://software.intel.com/en-us/articles/overview-of-intel-edison-based-robotics-platform

Tools Used

Node.js*

Node.js* is a lightweight JavaScript runtime with a non-blocking I/O model that has a large ecosystem of open source libraries. You can learn more about Node.js from the link below.

https://nodejs.org/en/about/

MRAA Library

MRAA is a Linux based open source low level C peripheral library that has bindings for C++, Python, Node.js, and Java languages. This gives a developer flexibility in choosing a familiar language when developing an IoT application. The library supports a variety of x86 and ARM platforms and has a common API. Intel® Edison comes with the MRAA libraries pre-installed on its Linux image and the Intel XDK can manage updating the libraries to the latest. To learn more about MRAA, please see the links below.

https://github.com/intel-iot-devkit/mraa

http://iotdk.intel.com/docs/master/mraa/edison.html

Intel XDK

The Intel XDK is a full IDE for developing, programming, and debugging an Intel® Edison IoT application over a WiFi connection. It comes with the Node.js environment setup and is MRAA ready. The tool is free and can be downloaded from the link below.

https://software.intel.com/en-us/intel-xdk

Romeo Intel® Edison Controller Board

Reviewing the schematic helps in understanding the key components in the system and how you can best map the available peripherals to the sensors and actuators used by the robot. The robot uses a variety of peripherals to accomplish its tasks. Below is a listing of component to peripheral pin mapping for the robot. An additional mapping is made from the physical pin to the MRAA pin. This pin mapping is what we will use in the code when initializing the peripherals. It should also be noted that there are level translators on the board. These pins are also indicated in the table below to aid in correlation when viewing the schematic.

Component:

Peripheral:

Intel® Edison Pin:

Translated Pin:

MRAA Pin:

Passive Infrared Sensor

Digital Input

GPIO43

D11

38

Tilt Servo

PWM

PWM1

D5

14

Pan Servo

PWM

PWM0

D3

20

Ultrasonic Sensor

UART TX

UART RX

GP131

GP130

D1/TX

D0/RX

UART0

Left LED

Digital Output

GPIO48

D7

33

Right LED

Digital Output

GPIO41

D10

51

Buzzer

PWM

PWM2

D6

0

Brushed DC Motors

I2C SCL

I2C SDA

SDA1

SCL1

I2C1_SDA

I2C1_SDL

I2C0

The MRAA pin mapping table for Intel® Edison can be found at the link below.

http://iotdk.intel.com/docs/master/mraa/edison.html

The Romeo schematic can be downloaded from the link below

https://github.com/Arduinolibrary/DFRobot_Intel_Edison/blob/master/Romeo%20for%20Edison%20Controller%20Schematic.pdf?raw=true

Creating an Intel XDK Project

Now that we have discussed the tools used and studied the schematic, we are ready to create a new project in the Intel XDK and start developing code for the robot.

To create a new project:

Click New Project Button

Image 1

Click Blank Iot Node.js Template

Image 2

Give Name->Click Create

Image 3

Connect to the board

Image 4

Click Upgrade XDK daemon and Update libraries on board

Image 5

Sensor & Actuator Components Implementation

Now that we have a new project created and have connected to the board, we are ready to write some code for interfacing to the LEDs, infrared sensor, servo, buzzer, ultrasonic sensor, and brushed dc motor components.

MRAA Initialization

Initializing the MRAA library in the project is done with the code below.

JavaScript
//MRAA Initialization
var m = require("mraa");

Indication LEDs

Two indication LEDs are mounted on the front of the robot. For example, these can be used for indication of robot direction, object detection & distance.

Initializing the LEDs is done by creating 2 MRAA GPIO objects for MRAA pins 33 and 51. The pins are both set to an OUTPUT with the dir() function with a LOW value by default.

JavaScript
var leftLED = new m.Gpio(33);
var rightLED = new m.Gpio(51);
leftLED.dir(m.DIR_OUT_LOW);
rightLED.dir(m.DIR_OUT_LOW);

Typical LED functions are to turn on, off, and toggle the LEDs. This can be accomplished with the code below shown for the right LED using the GPIO write() and read() functions.

JavaScript
//Turn On
rightLED.write(1);

//Turn Off
rightLED.write(0);

//Toggle
rightLED.write(rightLED.read()^1);

Passive Infrared Sensor (PIR)

The passive infrared sensor is mounted on the rear of the tank robot and can give indication of movement while the tank is moving backwards. The sensor gives a HIGH indication if there is motion detected or a LOW indication is there is no motion.

Initialization for this sensor is similar to the LED that it uses a GPIO object for MRAA pin 38, but the sensor is configured as an INPUT using the dir() function.

JavaScript
var pirMotionSensor = new m.Gpio(38);
pirMotionSensor.dir(m.DIR_IN);

Detecting motion is also simple by polling the sensor with the function below. Polling the sensor is done using the GPIO read() function. If motion is detected the function returns TRUE, if no motion is detected the function returns FALSE.

JavaScript
function isMotionDetected() {
    if (pirMotionSensor.read())
        return true;
    else
        return false;
}

Servos

The tank contains two servos. The first servo is used for panning an ultrasonic sensor and camera. Panning will allow the tank to look left and right and determine what is in its proximity. The second servo is used for tilting the camera angle up or down. Standard servo interfaces expect a pulse periodically at least every 20ms or 50Hz. The pulse width determines the servo position and is between 1ms-2ms to configure the servo position. For example a 1ms pulse width moves the servo to the 0 degree position, a 1.5ms pulse width moves the servo to the 90 degree neutral position, and a 2ms pulse moves the servo to the 180 degree position. Implementing this is easily accomplished using the pulse width modulation (PWM) peripheral.

Initializing the servos is done by creating 2 MRAA PWM objects using MRAA pin 14 for tilt and MRAA pin 20 for pan. The servos period is configured by using the PWM period_us() function that sets up the period in microseconds. After the peripheral is initialized the servos are centered to the neutral position by calling the panCenter() and tiltCenter() functions.

JavaScript
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();

Moving the pan servo is accomplished below with functions to panRight(), panLeft(), and panCenter(). The functions configure the pulse width of the PWM peripheral by calling the pulsewidth_us() function and then turning on the peripheral by calling the enable(true) function. A short delay is called with the sleep() function to allow the servo to move and then the peripheral is disabled by calling the enable(false) function.

JavaScript
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);
}

Moving the tilt servo up or down is a similar task to what we did with the pan servo except that the angles used are different after experimentation to determine what is practical. The functions for tiltUp(), tiltDown(), and tiltCenter() are listed below.

JavaScript
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);
}

Buzzer

The tank contains a buzzer that can be used for audible indication of events. For example, as the tank gets close to an object it could sound different notes from the c major scale to indicate its proximity. The buzzer also uses the PWM peripheral so initialization and usage will be similar to what we did with the servos. It is a little different mindset than the servos because we are using the PWM peripheral as a simple digital to analog converter (DAC).

Initializing the buzzer is done with a MRAA PWM object on MRAA pin 0. The amplitude is configured with the write() function for configuring the PWM duty cycle and is initialized to 0. The peripheral is disabled by calling the enable(false) function.

JavaScript
var buzzer = new m.Pwm(0);  //PWM2
buzzer.write(0.0);  //Duty Cycle
buzzer.enable(false);

Creating sounds with the buzzer is accomplished by setting the sound frequency using the period_us() function and then enabling the sound by calling the enable(true) function. Below is an example function for playing one octave of the C major scale that could be used as an indication tone when the robot is initialized. A simple 125ms timer loop that plays a note and increments to the next note over the octave.

JavaScript
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;
    }
}

Ultrasonic Sensor

The ultrasonic sensor is used on the robot to determine its distance to an object. It is mounted on the front of the robot and can be panned left and right to look around using the pan servo mentioned earlier. The sensor has 3 different interfaces for gathering the distance data. There is a PWM output, analog output, or UART interface. You can learn more about this sensor at the wiki below which contains useful information about the sensor, its different interfaces, and command protocol.

https://www.dfrobot.com/wiki/index.php/URM37_V4.0_Ultrasonic_Sensor_(SKU:SEN0001)#Introduction

For our Node.js program the UART interface is used with this sensor to read the distance data. The sensors TXD pin 9 is connected to Intel® Edison RX GP130 and the sensors RXD pin 8 is connected to Intel® Edison TX GP131.

Initializing the UART is done by creating a MRAA UART object. The baud rate is set to 9600 bps using the setBaudRate() function, the data mode is setup using the setMode(), and flow control is setup using the setFlowControl() function. In addition, the command buffer is setup for reading the sensor data. The command buffer byte 0 contains the command code, bytes 1&2 are dummy bytes, and byte 3 is a checksum of the packet.

JavaScript
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;

Getting the distance data from the sensor involves sending a command packet out the UART and then receiving the response packet. The function below determines if an object is close given a threshold and returns TRUE or FALSE. The command packet was setup in the initialization above and is sent out the UART by calling the write() function. The response packet is received after a delay and then calling the read() function. The response packet buffer byte 0 is a dummy byte, bytes 1 & 2 are the high and low bytes respectively corresponding to the distance data in cm, and byte 3 is the checksum. To determine if the data is valid the checksum is analyzed and then the data is compared to a threshold passed in.

JavaScript
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;
}

Brushed DC Motors

The tank is able to move in a forward, backward, left, or right direction. The robot contains two brushed dc motors that are mounted across from each other on the right and left side of the tank chassis. After reviewing the schematic, you will see that the Romeo board contains a full bridge motor control driver that is connected to an Atmega8* microcontroller. The interface used by Intel® Edison to communicate to the microcontroller is I2C. It is useful to review the code implemented in this microcontroller to learn about the I2C slave address that is programmed and the command interface for controlling the motors. The Intel® Edison is the I2C master and the microcontroller is the I2C slave at address 0x4.

You can find more information about the microcontroller code at the link below:

https://github.com/ouki-wang/remeo4edison/blob/master/NG/NG.ino

See the table below to determine the correlation between tank direction and motor direction

Direction:

Left Motor

Right Motor

Forward

Counter-Clockwise

Counter-Clockwise

Backward

Clockwise

Clockwise

Left

Counter-Clockwise

Clockwise

Right

Clockwise

Counter-Clockwise

Initializing the I2C peripheral is done by creating a new MRAA I2C object. The slave address is set by calling the address() function. The command buffer is also initialized and contains a header at bytes 0 & 1. The remaining bytes in the command packet are discussed below.

JavaScript
var x = new m.I2c(0);
x.address(4);
var buf = new Buffer(5);
buf[0] = 0x55;  //Header 1
buf[1] = 0xaa;  //Header 2

The function below moves the tank in a forward direction given the 8-bit speed value passed to the function. The function sets up I2C commands to setup the left motor direction, right motor direction, left motor speed, and right motor speed. The remaining command bytes in the command buffer are the command code at byte 2 for doing either a motor direction command or motor speed command, command argument at byte 3 for setting the motor direction value or motor speed value, and a checksum at byte 4. The I2C command is sent by calling the write() function.

JavaScript
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);
}

Additional functions are listed below and follow a similar method for moving the tank backwards, left, right, and stop.

JavaScript
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);
}

Autonomous Robot

Now that we have built a foundation of functions using the MRAA library for interfacing to the sensors and actuators, we can start to tie it together by creating a state machine that allows the robot to move autonomously and avoid collisions. The concept is that the tank will try and move forward until it detects that an object is close. When an object is close it will attempt to move backwards if there is not motion behind it. The tank will attempt to go left or right to avoid the object and then attempt to go forward again. Please see the flow chart diagrams below that describe movement in the forward, backward, left, and right states and the implementation that follows.

Flow Chart Diagrams

Image 6

Image 7

Implementation

JavaScript
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;
    }
}

Summary

Using the Intel XDK, we showed how to program a robot wirelessly over WiFi. We discussed how to take a schematic and map out the peripherals & components for use with the MRAA library. We discussed the sensor & actuator components and how to implement the functionality using Node.js and the MRAA library. We concluded by creating an autonomous robot concept with flow charts and implementation.

Image 8

About the author

Mike Rylee is a Software Engineer at Intel Corporation with a background in developing embedded systems and apps for Android*, Windows*, iOS*, and Mac*. He currently works on Internet of Things projects.

Notices:

No license (express or implied, by estoppel or otherwise) to any intellectual property rights is granted by this document.

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.

This document contains information on products, services and/or processes in development. All information provided here is subject to change without notice. Contact your Intel representative to obtain the latest forecast, schedule, specifications and roadmaps.

The products and services described may contain defects or errors known as errata which may cause deviations from published specifications. Current characterized errata are available on request.

Copies of documents which have an order number and are referenced in this document may be obtained by calling 1-800-548-4725 or by visiting www.intel.com/design/literature.htm.

Intel, the Intel logo, and Intel RealSense are trademarks of Intel Corporation in the U.S. and/or other countries.

*Other names and brands may be claimed as the property of others

**This sample source code is released under the Intel Sample Source Code License Agreement Like SubscribeAdd new commentFlag as spam.

© 2016 Intel Corporation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)