Introduction and Motivation:
There are many excellent tutorials on how to utilize the onboard Inertial Measurement Unit(IMU) of the arduino/genuino 101 in your projects. For example: (https://www.arduino.cc/en/Tutorial/Genuino101CurieIMUOrientationVisualiser), but information on how to transmit this data using the Arduino/Genuino 101’s onboard Bluetooth Low Energy(BLE) is lacking.
This tutorial project will demonstrate one way to transmit raw IMU data to your mobile device over BLE using Evothings Workbench(www.evothings.com). This tutorial will also show how to display IMU data graphically in real time using the smoothie.js library(http://smoothiecharts.org/).
- Arduino/Genuino 101 board
- USB cable
- Arduino IDE 1.6.8(https://www.arduino.cc/en/Main/Software)
- Our custom arduino sketch, imuble01.ino
- Evothings Workbench & Evothings Viewer (https://evothings.com/download/)
- smoothie.js from SmoothieCharts(http://smoothiecharts.org/)
- Our modified app files, index.html and app.js
The code for this project is commented and I encourage you to read it. Instead of providing a line-by-line breakdown of the how all the code works, I will highlight the areas that I think are most important.
With a BLE connected Arduino/Genuino 101 project, it is important to understand the overall layout of the software required. In addition to the Arduino sketch residing on the board itself, you will have to write the software to run on the mobile device. To write the sketch, we use the familiar Arduino IDE and the libraries, CurieIMU and CurieBLE, needed for our project.
To develop our software for the mobile device, there are two broad categories of options:
- Writing a native app directly for the operating system of the mobile device we wish to interact with.
My first principle of writing software for arduino projects is to build upon the work of others, and this project is no different. I used the accelerometer and gyroscope raw data access example sketches as the basis for my project’s sketch. These are found at the bottom of the reference page for the CurieIMU library (http://www.arduino.cc/en/Reference/CurieIMU). I started with the accelerometer sketch and cut and pasted in the relevant gyroscope sketch elements into it afterwards.Bluetooth Low Energy
When I first starting working with BLE, I was overwhelmed by all the new terminology. I hoped it would be a simple as sending messages over the serial port. If you feel overwhelmed, don’t worry just start experimenting with it. The more you work with it, the more confident you will become in using BLE with your projects! There are many fine tutorials to help you get started. A couple of good places to start learning are the CurieBLE library reference and the Adafruit BLE tutorial :
The BLE standard specifies services, which are collections or sets of related data values. In the world of BLE, these values are called characteristics. Every service and characteristic is specified by a universally unique identifier(uuid). For officially accepted services these uuids are specified for you. Believe it or not, I was not able to find a standard service for inertial measurements! So for this application, I had to generate a custom uuid for my “imu service.” For custom services, you must create your own 128-bit custom uuid. This is very easy to do and there are many resources to generate these uuids. The one I use was an online uuid generator:
Once this is done, you can create your BLE service using the CurieBLE library:
BLEService imuService("917649A0-D98E-11E5-9EEC-0002A5D5C51B"); // Custom UUID
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 );
Please read the BLE code examples on the Arduino/Genuino 101 site ( https://www.arduino.cc/en/Tutorial/Genuino101CurieBLEHeartRateMonitor) and the comments in this code to further understand how to set up your BLE service and characteristics.Sending floats over BLE:All BLE data is transmitted as bytes. So no matter whether your data originates as an integer, character, or float, it must be converted to bytes to transmit it over BLE. It is not necessary to transmit IMU data as floating point numbers, the CurieIMU interface lets you access raw IMU data in the form of integers. However both example applications convert this raw data into physically meaningful numbers which are floating point numbers. These conversion is based on the equations recommended in the CurieIMU reference. For the accelerometer the following equation is recommend to convert raw accelerometer readings into mg:
float g = (gRaw/32768.0)*getAccelerometerRange()
What are our options for dealing with this?
- We can send the the raw data integers over BLE and implement the recommended equations on the mobile app side of our software. This shifts the issue to the mobile app.
- We can convert the floating point numbers into to strings. Strings are simply char arrays, or another way of looking at them is arrays of bytes. Then we can convert or decode those bytes however we want on the mobile app side of things. This is the solution you will find implied by the tutorial/reference for the CurieBLE library (http://www.arduino.cc/en/Reference/CurieBLE ).
- We can use some C programming wizardry to convert our floating point numbers into their char array or byte equivalents and then send those over BLE to be “decoded” by the mobile app.
Option 1 is fine, but doesn’t teach us anything new. Also, when we eventually come to a having data only available as floating point numbers, we will be right back where we started. The Intel Curie module is pretty powerful, so why not use it the perform the computation and let the mobile apps take care of other chores anyway? Option 2 is a very commonly used and acceptable, but involves all the overhead of working with strings and string conversions.
Option 3 is the perhaps the most complicated, but also most efficient. We skip all the overhead of dealing with strings. In this option we will also learn how to work with the actual bytes that represent our data. We will gain a greater understanding how our computers represent and store numbers. We will also gain the confidence that comes with taking Arduino C to its core. Most importantly, this option also will gain us some serious “geek cred” and therefore being good makers and hackers, we will of course go for option #3!
All data types have an underlying representation in memory. Knowing how floating point numbers are stored and represented is key to our solution. Arduino C uses 32 bits or 4 bytes to represent and store a floating point number. Both the char and unsigned char data types use 4 bits or 1 byte to represent and store char data.
The unsigned char is the “currency” of data exchange in the CurieBLE library:
* Set the current value of the Characteristic
* @param value New value to set, as a byte array. Data is stored in internal copy.
* @param length Length, in bytes, of valid data in the array to write.
* Must not exceed maxLength set for this characteristic.
* @return bool true set value success, false on error
bool setValue(const unsigned char value, unsigned short length);
As you can see, in order to change the value of our characteristic using the setValue() function, we need to pass our data as an array of char (an array of unsigned char is the same thing as byte array).
So the question now becomes, how can we pass off a floating number and as an array of unsigned char? We can do this by using a C language union. The union keyword allows us to have variables of different data types share the same space in memory. Since a float in C takes up 4 bytes, we would like to share 4 bytes of memory space with an array of 4 unsigned chars.
In a sense the compiler doesn’t really care how we interpret the bytes, it just reserves the space and allows us to work with this space as if it is of one of the data types in our union. We must, however, be very careful to ensure the we properly account for the size and type of variable when we work with it in our program.
Actually in our case, we will need to account for 3 floats each from the accelerometer and gyroscope. One float for each axis. We could consider a separate variable, union and BLE characteristic for each variable. However, knowing that each floating point number in 4 bytes long and that we can send a maximum of 20 bytes with each BLE transmission we could send the complete set of accelerometer and gyroscope data using 2 unions, 2 arrays of 3 floating point numbers and just 2 characteristics of 12 bytes each. This would not only make our code more readable and manageable but also more efficient.
For this we will use our union to share the space between an array of 3 floats, which requires 12 bytes and an array of 12 unsigned char. This is done as such:
unsigned char bytes;
When the data is read from the IMU and then converted to a float, we then assign it the float elements of our union:
CurieIMU.readAccelerometer(axRaw, ayRaw, azRaw);
accData.a = convertRawAcceleration(axRaw);
accData.a = convertRawAcceleration(ayRaw);
accData.a = convertRawAcceleration(azRaw);
Unfortunately we aren’t done just yet. If we try and pass the union to the setValue() function our compiler will complain and not let us proceed. One more step required to make this work. We have to make the compiler believe our union is really an array of unsigned characters by casting it as such. A cast is a bit of programming magic that allows us to transform one data type into another(https://www.arduino.cc/en/Reference/Cast). In some cases this can lead to a loss of data, but in our case, since we are just handling bytes, we won’t lose any information at all. In order to perform this cast however, we do need to use a bit of pointer magic:
unsigned char *acc = (unsigned char *)&accData;
Let’s translate this:
First, &accData, gets the address of the memory location of our 12 bytes. Then, (unsigned char *)&accData tells the complier with that we consider this memory location to be treated an unsigned an array characters. Actually this is a pointer to the first byte of this memory location. Finally, unsigned char *acc assigns the variable acc to this pointer. That’s a lot to process, but read it over several times and it will make sense. If not, just use the pattern as is to send your data. The exact same pattern of commands is repeated for the gyroscope data. Now we can move on to the mobile app.
Highlights of the Mobile App:
var ax = new DataView(data).getFloat32(0, true);
var ay = new DataView(data).getFloat32(4, true);
var az = new DataView(data).getFloat32(8, true);
The bytes are passed into our function with the data variable. This data is used to instantiate a DataView. Once we have this, we can call on one of the many methods of the DataView object to convert the data to our desired representation. In this case we want to convert it back into a float. Remember our floats created from the Arduino/Genuino 101 IMU were 4 bytes, or 32 bits long? The getFloat32() function is specifically written to deal with the 32 bit float representation. The parameters we send to the getFloat32() function are very important. The first is a number, this number which is 0, 4 or 8 in our case, represents the byte offset or index of the first byte of our 4 byte float. The second parameter, true, indicates that our float is in little endian format. That’s it, pretty simple. We can now assign all of the to a variable, ax, ay, or az to represent the accelerations along each axis.
The smoothie.js library is fully described at http://smoothiecharts.org/ website. In order to learn how to use the library, the authors have created a 10 minute tutorial( http://smoothiecharts.org/tutorial.html). After looking at the examples and reading their tutorial you should be able to easily use their code to create your own plot. The authors have also created a builder page(http://smoothiecharts.org/builder/), that allows you to graphically customize your plot. When you have the plot formatted to your wishes, you can cut and paste the code from text box at the bottom of the builder page directly into your app.
We will download the smoothie.js library from the smoothiecharts.org homepage into our mobile app directory to use it. Follow the instructions in the project recipe below in order to do this. The following line on our index.html allows us to use the the library in our app:
And that's about it for the teaching. Let's make the project!
Because the Arduino/Genuino 101 comes with on board BLE and IMU capabilities, there is no circuit to make! Simply connect your to your computer via USB to upload the sketch and to access the serial monitor window of the Arduino Software (IDE) for debugging purposes.
I will assume that you have installed the Arduino IDE, board support for the intel curie and all the necessary libraries for it. If not, instructions are available here: https://www.arduino.cc/en/Guide/Arduino101.
1) Download and install the EvothingsStudio and EvothingsViewer ( http://evothings.com/download/). Follow the instructions here specific to your operating system and mobile platform.
2) Start up the Evothings Workbench and click on the Examples tab.
3) Make a copy of the TI SensorTag CC2650 Demo app and give it a unique name.
4) With file browser, go to the EvothingsStudio/MyApps directory and find the home directory for the project you renamed in step 3.
5) Select and rename the index.html file to index_old.html.
6) Select and rename the app.js file to app_old.js.
7) Now copy the index.html and app.js files from this project tutorial into the home directory of your new project.
8) Make a directory called called smoothie.
9) Go to smoothiecharts.org and click on the button labeled Download smoothie.js. The text of the smoothie.js library should be displayed in your browser.
10) Click on the file tab of your browser and select save page as. Save this page as smoothie.js in the smoothie directory created in step 8.
11) Connect your Arduino/Genuino 101 to your computer with a usb cable.
12) Using your Arduino IDE 1.6.8, upload the imu_ble.ino sketch to the Arduino/Genuino 101.
13) Open up the serial port connection. Data will not be sent from the arduino/genuino101 imu until a BLE connection is established, so you should not expect to see any serial data at this time.
14) Go back to the EvothingsWorkbench and obtain your connection key from the Connect tab.
15) Start up the EvothingsViewer on your mobile device and enter the connection key.
16) Click the start button on the mobile app homepage.
17) Once the connection is made, you should see the following message on your mobile device, Status: Data stream active - accelerometer. Values should be streaming on the Arduino IDE serial window, and the lines should scrolling on the Accelerometer and Gyroscope plots.
18) Shake, twist and turn that Arduino/Genuino101!
There are many ways you could choose to collect, transmit and display IMU data but I believe the method presented here is one that teaches the most. There is really no substitute closely reading the code and experimenting with it. Your effort will be rewarded. As makers, the more we really understand how our projects work, the more we will be able to accomplish:
“The best dividends on the labor invested have invariably come from seeking more knowledge rather than more power.” Signed Wilbur and Orville Wright, March 12, 1906.”
― David McCullough, The Wright Brothers