Gregory O. Voronin
Published © MIT

IMU to You!

Send Arduino101 IMU data over BLE to your mobile device with Evothings and smoothie charts!

IntermediateProtip1 hour22,120
IMU to You!

Things used in this project

Hardware components

Arduino 101
Arduino 101
×1

Software apps and online services

Evothings Studio
Evothings Studio
SmoothieCharts

Story

Read more

Schematics

Arduino/Genuino101

This project utilizes the onboard IMU and BLE, so there is no circuit other than the board itself.

Code

imuble01.ino

Arduino
This is the arduino sketch which will run on the arduino/genuino101.
/*
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

/*
   This sketch example demonstrates how to collect Inertial
   Measurement Unit(IMU) accelerometer and gyroscope data and then
   transmit that data using the on board Bluetooth Low Energ(BLE)
   capability of the Arduino/Genuino 101.

   This sketch is based on the IMU acceleromter and gyroscope examples
   sketches found at: https://www.arduino.cc/en/Reference/CurieIMU

   
*/

#include "CurieIMU.h"
#include <CurieBLE.h>


/**
* BLE Initialization Code
*
*/
BLEPeripheral blePeripheral;       // BLE Peripheral Device (the board you're programming)

/** 
 *  Inertial Measurement Unit(IMU) Service - somewhat surprisely I could not find a standard IMU
 *  service on the Bluetooth SIG website(https://www.bluetooth.com/develop-with-bluetooth). So I 
 *  used an online uuid generator(http://www.itu.int/en/ITU-T/asn1/Pages/UUID/uuids.aspx) to 
 *  create this uuid. You are welcome to use it or make your own. Keep in mind that custom service 
 *  uuid must always be specified in 128 bit format as seen below.
*/ 
BLEService imuService("917649A0-D98E-11E5-9EEC-0002A5D5C51B"); // Custom UUID

/** 
 *  Accelerometer & Gyroscope Characteristics - these are simple variations on the service uuid from 
 *  above. 
 *  
 *  A float in C takes up 4 bytes of space. We will send the x, y and z components of both the
 *  accelerometer and gyroscope values. The byte length of 12 is specified as the last value
 *  passed in the function here. Float data is stored in memory as little endian. There are many
 *  good tutorials on little and big endian, here is a link to one of them:
 *  (https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Data/endian.html).
 *  BLE limits us to 20 bytes of data to transmit. Therefore we cannot send all 6 floats using one 
 *  characteristics. So we will create one characteristic for each main element of the IMU.
 *  
 *  The appButtonCharacteristic will allow us to recieve button inputs from the mobile app. Since
 *  a single byte can represent 256 values, we could in principle differentiate among a minimum
 *  of 256 buttons!
*/
BLECharacteristic imuAccCharacteristic("917649A1-D98E-11E5-9EEC-0002A5D5C51B", BLERead | BLENotify, 12 );
BLECharacteristic imuGyroCharacteristic("917649A2-D98E-11E5-9EEC-0002A5D5C51B", BLERead | BLENotify, 12 );
BLEUnsignedCharCharacteristic appButtonCharacteristic("917649A7-D98E-11E5-9EEC-0002A5D5C51B", BLERead | BLEWrite );

BLEDescriptor imuAccDescriptor("2902", "block");
BLEDescriptor imuGyroDescriptor("2902", "block");

/** 
 *  Define pins, connect your LEDs to the appropriate pins as defined below. These pins are 
 *  not required for the demo to function and therefore the circuit to connect them
 *  are not necessary.
*/
#define BLE_CONNECT 3 // This pin will service as a hardware confirmation of the BLE connection
#define INDICATOR_LEDA 4 // This pin will be used to debug input buttons from mobile app


/**
* The union directive allows 3 variables to share the same memory location. Please see the 
* tutorial covering this project for further discussion of the use of the union
* directive in C.
*
*/
 union 
 {
  float a[3];
  unsigned char bytes[12];      
 } accData;

 union 
 {
  float g[3];
  unsigned char bytes[12];         
 } gyroData;
 

void setup() {

  // initialze serial port for debugging communications
  Serial.begin(9600); // initialize Serial communication
  while (!Serial);    // wait for the serial port to open
 
  Serial.println("Arduino101/IntelCurie/Accelerometer/Evothings Example Started");
  Serial.println("Serial rate set to 9600");
  
  // initialize IMU
  // Serial.println("Initializing IMU device...");
  CurieIMU.begin();

  // Set the accelerometer range to 2G
  CurieIMU.setAccelerometerRange(2);

  // Set the accelerometer range to 250 degrees/second
  CurieIMU.setGyroRange(250);

  // prepare & initiazlie BLE
  // enable LED pins for output.
  pinMode(BLE_CONNECT, OUTPUT);
  pinMode(INDICATOR_LEDA, OUTPUT);

  blePeripheral.setLocalName("imu");
  blePeripheral.setAdvertisedServiceUuid(imuService.uuid());  // add the service UUID
  blePeripheral.addAttribute(imuService);   
  blePeripheral.addAttribute(imuAccCharacteristic);
  blePeripheral.addAttribute(imuAccDescriptor);
  blePeripheral.addAttribute(imuGyroCharacteristic);
  blePeripheral.addAttribute(imuGyroDescriptor);
  blePeripheral.addAttribute(appButtonCharacteristic);


  // All characteristics should be initialized to a starting value prior
  // using them.
  const unsigned char initializerAcc[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
  const unsigned char initializerGyro[12] = { 0,0,0,0,0,0,0,0,0,0,0,0 };
 
  imuAccCharacteristic.setValue( initializerAcc, 12);
  imuGyroCharacteristic.setValue( initializerGyro, 12 );
  appButtonCharacteristic.setValue(0);
  
  blePeripheral.begin();
  
}

void loop() {
  
  int axRaw, ayRaw, azRaw;         // raw accelerometer values
  int gxRaw, gyRaw, gzRaw;         // raw gyro values
  

  // ?: Would there be an eficiency gained by declaring these outside the loop?

  // Here we connect oth central, your mobile device!
  BLECentral central = blePeripheral.central();
  if (central) {
    
    Serial.print("Connected to central: "); Serial.println(central.address());
    // It does not matter f you connect this LED or not, it is up to you.
    digitalWrite(BLE_CONNECT, HIGH);


    /**
     * All processing occurs in the context of an active BLE connection. 
    */
    while (central.connected()) {
      
    
      // read raw accelerometer measurements from device
      CurieIMU.readAccelerometer(axRaw, ayRaw, azRaw);

     /** 
      *  convert the raw accelerometer data to G's and assign them to the elements of
      *  the float array in the union representing the accelerometer data.
     */
     

      accData.a[0] = convertRawAcceleration(axRaw);
      accData.a[1] = convertRawAcceleration(ayRaw);
      accData.a[2] = convertRawAcceleration(azRaw);

      
      // read raw gyro measurements from device
     CurieIMU.readGyro(gxRaw, gyRaw, gzRaw);

     
    /** 
      *  convert the raw gyro data to degrees/second and assign them to the elements of
      *  the float array in the union representing the gyroscope data.
     */
     
     gyroData.g[0] = convertRawGyro(gxRaw);
     gyroData.g[1] = convertRawGyro(gyRaw);
     gyroData.g[2] = convertRawGyro(gzRaw);
     
     
     // These statements are for debugging puposes only and can be commented out to increae the efficiency of the sketch.
     Serial.print( "(ax,ay,az): " ); 
     Serial.print("("); Serial.print(accData.a[0]); Serial.print(","); Serial.print(accData.a[1]); Serial.print(","); Serial.print(accData.a[2]); Serial.print(")");Serial.println();
     Serial.print( "(gx,gy,gz): " ); 
     Serial.print("("); Serial.print(gyroData.g[0]); Serial.print(","); Serial.print(gyroData.g[1]); Serial.print(","); Serial.print(gyroData.g[2]); Serial.print(")");Serial.println();
     
     
      
     /**
      * The following two statements have the potential to cuase the most confusion. Please see the tutorial for
      * more on this.
      * What we are doing here is casting our union variables into a pointer of unsigned characters in
      * order to allow us to pass the array of bytes to the setValue() function.
     */
     unsigned char *acc = (unsigned char *)&accData;
     unsigned char *gyro = (unsigned char *)&gyroData;


      /**
       * Setting the values here will cause the notification mechanism on the moible app 
       * side to be enacted.
      */
     imuAccCharacteristic.setValue( acc, 12 );
     imuGyroCharacteristic.setValue( gyro, 12 );
      
  
     /**
      * When a button is pressed on the mobile app, the value of the characteristic is changed and
      * sent over BLE to the arduino/genuino101. A change in the characteristic is indicated
      * by a true value from the written() function and the value transmitted from the button 
      * on the mobile app is read here with the value() function.
      * The values change here essentially do nothing but print out a line to the serial port.
      * You can make them do anything here.
      * 
     */    
     if ( appButtonCharacteristic.written() ) {

        int appButtonValue = appButtonCharacteristic.value();

        switch(appButtonValue) {

          case 0:
            Serial.println( "App Input Value(0): " + appButtonValue );
            break;
          case 1:
            Serial.println( "App Input Value(1): " + appButtonValue );
            break;
          case 2:  
             Serial.println( "App Input Value(2): " + appButtonValue );
            break;  
        }
        
      }
      
      
    } // while central.connected  
  } // if central
} // end loop(){}


/**
 * The follwing functions are taken directly from the accelerometer
 * and gyroscope demo apps.
*/

float convertRawAcceleration(int aRaw) {
  // since we are using 2G range
  // -2g maps to a raw value of -32768
  // +2g maps to a raw value of 32767
  
  float a = (aRaw * 2.0) / 32768.0;

  return a;
}

float convertRawGyro(int gRaw) {
  // since we are using 250 degrees/seconds range
  // -250 maps to a raw value of -32768
  // +250 maps to a raw value of 32767
  
  float g = (gRaw * 250.0) / 32768.0;

  return g;
}

index.html

HTML
This is the html code to be used with the mobile app running on Evothings
<!DOCTYPE html>
<!--
	Description:

	BLE example application that demonstrates how to read values
	IMU values from the Arduino/Genuino101.

	Requirements:

	See text in the app user interface.
 
    See http://smoothiecharts.org/ for more information about real time data streaming with
    the smoothie.js library.
 
-->
<html>

<head>

	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, user-scalable=no,
		shrink-to-fit=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0" />

	<title>Arduino/Genuino101 IMU</title>

	<style>
		@import 'ui/css/evothings-app.css';
	</style>

	<script>
	// Redirect console.log to Evothings Workbench.
	if (window.hyper && window.hyper.log) { console.log = hyper.log }
	</script>

	<script src="cordova.js"></script>
	<script src="libs/jquery/jquery.js"></script>
    <script type="text/javascript" src="smoothie/smoothie.js"></script>
	<script src="libs/evothings/evothings.js"></script>
	<script src="libs/evothings/ui/ui.js"></script>
	<script src="libs/evothings/easyble/easyble.js"></script>
	<script src="app.js"></script>

</head>

<body ontouchstart=""><!-- ontouchstart="" enables low-delay CSS transitions. -->

	<header>
		<button class="back" onclick="history.back()">
			<img src="ui/images/arrow-left.svg" />
		</button>

		<img class="logotype" src="ui/images/logo.svg" alt="Evothings" />

		<button id="menu-button" tabindex="0" onclick="this.focus()">
			<img src="ui/images/menu.svg" />
		</button>

		<menu>
			<menuitem>
				<a href="#">Demo</a>
			</menuitem>

			<menuitem>
				<a href="#information">Info</a>
			</menuitem>
		</menu>
	</header>

	<h1>Arduino/Genuino101 IMU Demo</h1>

	<p id="info">Not Connected</p>
    
    <table>
        <tr>
        <td><button onclick="app.onStartButton()" class="green wide big">START</button></td>
        <td><button onclick="app.onStopButton()" class="red wide big">STOP</button></td>
        </tr>
    </table>

    <table>
        <tr><td>Accelerometer</td></tr>
        <tr><td><canvas id="canvasAcc" width="500" height="150"></canvas></td></tr>
        <tr><td>Gyroscope</tr></td>
        <tr><td><canvas id="canvasGyro" width="500" height="150"</canvas></td></tr>
    </table>

	<section id='information'>
        <!-- keep in case I would like to use this section later -->
	</section>

</body>

</html>

app.js

JavaScript
This is the JavaScript that will provide the functionality of the moible app running on the Evothings Platform
// JavaScript code for the TI SensorTag Demo app.

/**
 * Object that holds application data and functions.
 */
var app = {};

/**
 * for input buttons/write characteristics
 */
app.device = null;

/**
 * Data that is plotted on the canvas.
 */
app.dataPoints = [];

/**
 * smoothie.js data
 *
 */

var lineAX = new TimeSeries();
var lineAY = new TimeSeries();
var lineAZ = new TimeSeries();

var lineGX = new TimeSeries();
var lineGY = new TimeSeries();
var lineGZ = new TimeSeries();

app.AX = -1.00;
app.AY = -1.00;
app.AZ = -1.00;

app.GX = 0.00;
app.GY = 0.00;
app.GZ = 0.00;

var smoothieAcc;
var smoothieGyro;

app.coin = 'monk';


/**
 * Timeout (ms) after which a message is shown if the SensorTag wasn't found.
 */
app.CONNECT_TIMEOUT = 3000;

/**
 * Object that holds SensorTag UUIDs.
 */
app.curie = {};

/** 
 * UUIDs for movement services and characteristics. These must match the
 * UUIDs specified in the arduino sketch.
 */
app.curie.IMU_SERVICE = '917649a0-d98e-11e5-9eec-0002a5d5c51b';

app.curie.IMU_ACC = '917649a1-d98e-11e5-9eec-0002a5d5c51b';
app.curie.IMU_AXDESCRIPTOR = '00002902-0000-1000-8000-00805f9b34fb';

app.curie.IMU_GYRO = '917649a2-d98e-11e5-9eec-0002a5d5c51b';
app.curie.IMU_GXDESCRIPTOR = '00002902-0000-1000-8000-00805f9b34fb';


app.curie.APP_INPUTCHARACTERISTIC = '917649a7-d98e-11e5-9eec-0002a5d5c51b';



/**
 * Initialise the application.
 */
app.initialize = function()
{
	document.addEventListener(
		'deviceready',
		function() { evothings.scriptsLoaded(app.onDeviceReady) },
		false);

	// Called when HTML page has been loaded.
	$(document).ready( function()
	{
		// Adjust canvas size when browser resizes
		$(window).resize(app.respondCanvas);

		// Adjust the canvas size when the document has loaded.
		app.respondCanvas();
                      
        // set up smoothie canvas
        app.setUpSmoothie();
                      
	});
};

/**
 * Adjust the canvas dimensions based on its container's dimensions.
 */
app.respondCanvas = function()
{
	var canvas = $('#canvas')
	var container = $(canvas).parent()
	canvas.attr('width', $(container).width() ) // Max width
	// Not used: canvas.attr('height', $(container).height() ) // Max height
};

/**
 * This function allows us to customize our smoothie charts. If you use the
 * builder at http://smoothiecharts.org/builder/ to customize your chart,
 * you can paste the JavaScript from the bottom of that page into the code
 * below.
 */


app.setUpSmoothie = function()
{

    smoothieAcc = new SmoothieChart(
    {
        maxValue:2.05,minValue:-2.05,
        grid: { strokeStyle:'rgb(125, 0, 0)', fillStyle:'rgb(60, 0, 0)',
        lineWidth: 1, millisPerLine: 250, verticalSections: 6, },
        labels: { fillStyle:'rgb(60, 0, 0)' }
    });
    
    smoothieGyro = new SmoothieChart(
    
    {
        maxValue:300.00,minValue:-300.00,
        grid: { strokeStyle:'rgb(125, 0, 0)', fillStyle:'rgb(60, 0, 0)',
        lineWidth: 1, millisPerLine: 250, verticalSections: 6, },
        labels: { fillStyle:'rgb(60, 0, 0)' }
    });
    
    smoothieAcc.streamTo(document.getElementById("canvasAcc"),1000);
    smoothieGyro.streamTo(document.getElementById("canvasGyro"),1000);
    
    setInterval( function()
    {
                
      lineAX.append( new Date().getTime(), app.getAx() );
      lineAY.append( new Date().getTime(), app.getAy() );
      lineAZ.append( new Date().getTime(), app.getAz() );
       
      lineGX.append( new Date().getTime(), app.getGx() );
      lineGY.append( new Date().getTime(), app.getGy() );
      lineGZ.append( new Date().getTime(), app.getGz() );
                
                
    }, 1000);
    
    smoothieAcc.addTimeSeries(lineAX,  { strokeStyle:'rgb(0, 255, 0)', lineWidth:3 });
    smoothieAcc.addTimeSeries(lineAY,  { strokeStyle:'rgb(255, 0, 0)', lineWidth:3 });
    smoothieAcc.addTimeSeries(lineAZ,  { strokeStyle:'rgb(0, 0, 255)', lineWidth:3 });
    smoothieGyro.addTimeSeries(lineGX,  { strokeStyle:'rgb(0, 255, 0)', lineWidth:3 });
    smoothieGyro.addTimeSeries(lineGY,  { strokeStyle:'rgb(255, 0, 0)', lineWidth:3 });
    smoothieGyro.addTimeSeries(lineGZ,  { strokeStyle:'rgb(0, 0, 255)', lineWidth:3 });
    
    
    
};

app.onDeviceReady = function()
{
	app.showInfo('Activate the SensorTag and tap Start.');
};

app.showInfo = function(info)
{
	document.getElementById('info').innerHTML = info;
};

app.onStartButton = function()
{
	app.onStopButton();
	app.startScan();
	app.showInfo('Status: Scanning...');
	app.startConnectTimer();
};

app.onStopButton = function()
{
	// Stop any ongoing scan and close devices.
	app.stopConnectTimer();
	evothings.easyble.stopScan();
	evothings.easyble.closeConnectedDevices();
	app.showInfo('Status: Stopped.');
};

app.startConnectTimer = function()
{
	// If connection is not made within the timeout
	// period, an error message is shown.
	app.connectTimer = setTimeout(
		function()
		{
			app.showInfo('Status: Scanning... ' +
				'Please press the activate button on the tag.');
		},
		app.CONNECT_TIMEOUT)
}

app.stopConnectTimer = function()
{
	clearTimeout(app.connectTimer);
}

app.startScan = function()
{
	evothings.easyble.startScan(
		function(device)
		{
			// Connect if we have found a sensor tag.
			if (app.deviceIsArduino101(device))
			{
				app.showInfo('Status: Device found: ' + device.name + '.');
                // set up the app.device variable to access device for writing input button results to Arduino/Genuino101
                app.device = device;
				evothings.easyble.stopScan();
				app.connectToDevice(device);
				app.stopConnectTimer();
			}
		},
		function(errorCode)
		{
			app.showInfo('Error: startScan: ' + errorCode + '.');
		});
};

app.deviceIsArduino101 = function(device)
{
	console.log('device name: ' + device.name);
	return (device != null) &&
		(device.name != null) &&
		(device.name.indexOf('imu') > -1 ||
			device.name.indexOf('imu') > -1);
};

/**
 * Read services for a device.
 */
app.connectToDevice = function(device)
{
	app.showInfo('Connecting...');
	device.connect(
		function(device)
		{
			app.showInfo('Status: Connected - reading services...');
			app.readServices(device);
		},
		function(errorCode)
		{
			app.showInfo('Error: Connection failed: ' + errorCode + '.');
			evothings.ble.reset();
			// This can cause an infinite loop...
			//app.connectToDevice(device);
		});
};

// Some getters and setters to properly work with imu data

app.setAx = function( value )
{
  app.Ax = value;
};

app.getAx = function()
{
   return app.Ax;
};

app.setAy = function( value )
{
    app.Ay = value;
};

app.getAy = function()
{
    return app.Ay;
};

app.setAz = function( value )
{
    app.Az = value;
};

app.getAz = function()
{
    return app.Az;
};

app.setGx = function( value )
{
    app.Gx = value;
};

app.getGx = function()
{
    return app.Gx;
};

app.setGy = function( value )
{
    app.Gy = value;
};

app.getGy = function()
{
    return app.Gy;
};

app.setGz = function( value )
{
    app.Gz = value;
};

app.getGz = function()
{
    return app.Gz;
};




app.readServices = function(device)
{
	device.readServices(
		[
		app.curie.IMU_SERVICE // Movement service UUID.
		],
		// Function that monitors accelerometer data.
		app.startIMUNotification,
		function(errorCode)
		{
			console.log('Error: Failed to read services: ' + errorCode + '.');
		});
};



/**
 * Read accelerometer data.
 */
app.startIMUNotification = function(device)
{
	app.showInfo('Status: Starting IMU notification...');

	

	// Set accelerometer notifications to ON.
	device.writeDescriptor(
		app.curie.IMU_ACC,
		app.curie.IMU_AXDESCRIPTOR, // Notification descriptor.
		new Uint8Array([1,0]),
		function()
		{
			console.log('Status: writeDescriptor ok.');
		},
		function(errorCode)
		{
			// This error will happen on iOS, since this descriptor is not
			// listed when requesting descriptors. On iOS you are not allowed
			// to use the configuration descriptor explicitly. It should be
			// safe to ignore this error.
			console.log('Error: writeDescriptor: ' + errorCode + '.');
		});
    
    
    

    // Set gyroscope notifications to ON.
    device.writeDescriptor(
        app.curie.IMU_GYRO,
        app.curie.IMU_GXDESCRIPTOR, // Notification descriptor.
        new Uint8Array([1,0]),
        function()
        {
            console.log('Status: writeDescriptor ok.');
        },
        function(errorCode)
        {
            // This error will happen on iOS, since this descriptor is not
            // listed when requesting descriptors. On iOS you are not allowed
            // to use the configuration descriptor explicitly. It should be
            // safe to ignore this error.
            console.log('Error: writeDescriptor: ' + errorCode + '.');
        });
    
    
	// Start accelerometer notifications.
	device.enableNotification(
		app.curie.IMU_ACC,
		function(data)
		{
			app.showInfo('Status: Data stream active - IMU');
			
            /**
             * The stream of bytes sent over BLE comes in here as the variable data
             * We create a DataView object and use the getFloat32() method to get
             * the floating point representation of our data here.
             */
            var ax = new DataView(data).getFloat32(0, true);
            var ay = new DataView(data).getFloat32(4, true);
            var az = new DataView(data).getFloat32(8, true);
            
            // debugging, can comment out
            console.log( '(ax,ay,az):' + '(' + ax + ',' + ay + ',' + az + ')' );
                              
            app.setAx(ax);
            app.setAy(ay);
	        app.setAz(az);

        },
		function(errorCode)
		{
			console.log('Error: enableNotification: ' + errorCode + '.');
		});
    
    
    // Start gyroscope notifications.
    device.enableNotification(
        app.curie.IMU_GYRO,
        function(data)
        {
            // See comments above.
            var gx = new DataView(data).getFloat32(0, true);
	        var gy = new DataView(data).getFloat32(4, true);
            var gz = new DataView(data).getFloat32(8, true);
           
 	   
            console.log( '(gx,gy,gz):' + '(' + gx + ',' + gy + ',' + gz + ')' );
                              
            app.setGx(gx);
            app.setGy(gy);
            app.setGz(gz);
        },
        function(errorCode)
        {
                console.log('Error: enableNotification: ' + errorCode + '.');
        });
};


// Initialize the app.
app.initialize();

Credits

Gregory O. Voronin

Gregory O. Voronin

12 projects • 106 followers
biomedical researcher - hope & cures through research; making in my spare time - don't hesitate to reach out!

Comments