Arduino 101 CurieIMU Orientation Visualiser
This tutorial demonstrates how to make use the 6-axis IMU to read the X, Y, and Z values of both the accelerometer and the gyroscope.
This tutorial demonstrates how to make use the Arduino 101's onboard 6-axis accelerometer/gyro to read the X, Y, and Z values of both the accelerometer and the gyroscope. While the gyroscope is able to determine the orientation of the board, the accelerometer measures the angular velocity of the board. Together, the accelerometer and the gyroscope form an Inertial Monitoring Unit (IMU) which can be used to precisely identify the orientation of the board. Madgwick's filter algorithm is used in this example to calculate quarternions from the 6 axes' values. The quarternions are then used to calculate Euler angles Pitch, Yaw, and Roll, which are received by Processing and used to control the rotation of an object around the X, Y and Z axes.
Hardware Required
The CurieIMU library uses the IMU (accelerometer + gyroscope) built into the Arduino 101.
Instructions
Set up the Arduino software (IDE) as described in Getting Started with Arduino 101.
Connect the 101 to your computer.
Launch the Arduino software (IDE) and select Arduino 101 from the Tools > Board menu.
Install the Madgwick library from library manager. To do this, open the Arduino Software (IDE), go to Sketch > Include Library > Manage Libraries. There you can search 'Madgwick' and install the library directly from there.
Download and Launch the Processing software and create a file with the Processing code shown below.
Change the Serial port to the one that your 101 is using (see "Processing Sketch" section).
Upload the example contained in the Madgwik library called Visualizer101 to your 101, making sure that the board is flat and stationery so it can perform the calibration accurately.
After a few seconds, run the Processing sketch, adjust the orientation of your board, and watch as the Processing sketch gives a visualization of your board. The Processing is contained in the "extras" folder of the Madgwick library.
The Circuit
image developed using Fritzing.
How it works
The Madgwick filter algorithm is open-source and is well documented in Madgwick's information and reports. The Madgwick filter algorithm was developed by Sebastian Madgwick during his Ph.D. in 2010 and is designed to be computationally inexpensive and efficient even at low sampling rates. The algorithm takes raw values from a gyroscope and accelerometer, and uses them to return four quaternions, which are 4-dimensional numbers which contain x, y, and z values to represent the axis around which rotation occurs, as well as a ω value which represents the value of rotation which occurs around the same axis. These quaternions can be used to calculate the Euler angles pitch, yaw, and roll; three angles used to describe the orientation of a rigid body in terms of x,y, and z as presented by Leonhard Euler in the 1700s. The equations (7) (8) (9) in Madgwick's Report are used to calculate the values for pitch, roll, and yaw, and their functions are included within the library.
We can create a 3D representation of the Arduino 101's onboard IMU in Processing, which will move as the board does. This is achieved with the values for Euler angles pitch, roll and yaw obtained by the Madgwick filter algorithm. These values can then be sent via Serial to Processing and used as angle arguments for Processing's to compute the position of the Arduino 3D model using the methods: applyMatrix(), pushMatrix(), and popMatrix() functions.
Arduino Sketch
The sketch uses functions inside the CurieIMU library to get the data from the accelerometer/gyro.
In order to see a 3D representation in Processing, the Arduino sketch must incorporate two main functionalities; using the IMU data and algorithm to calculate yaw, pitch, and roll values, and enabling serial communication in a handshake fashion in order to send those values to Processing.
First, we must create a Madgwick object to access the functions from the Madgwick class in the library. Here, we call it filter:
1Madgwick filter;
In the
setup()
function we perform a preliminary configuration of the CurieIMU, by setting the sample rate of the accelerometer and the gyro and the filter to 25Hz:1CurieIMU.begin();2
3CurieIMU.setGyroRate(25);4
5CurieIMU.setAccelerometerRate(25);6
7filter.begin(25);
Then we set the accelerometer range to 2g and the gyro range to 250 °/s:
1CurieIMU.setAccelerometerRange(2);2
3CurieIMU.setGyroRange(250);
In the
loop()
function we will need to send a sample according to the sample rate we set for the CurieIMU reading:1microsPerReading = 1000000 / 25;
We can then 'get' accelerometer and gyroscope data using the following functions from CurieIMU library and convert the raw data to acceleration (g) and angular velocity (°/s):
1CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);2
3// convert from raw data to gravity and degrees/second units4
5ax = convertRawAcceleration(aix);6
7ay = convertRawAcceleration(aiy);8
9az = convertRawAcceleration(aiz);10
11gx = convertRawGyro(gix);12
13gy = convertRawGyro(giy);14
15gz = convertRawGyro(giz);
We can then use the function updateIMU() from the Madgwick library.
1filter.updateIMU(gx, gy, gz, ax, ay, az);
After that, we are ready to obtain from the filter the roll, pith and yaw values:
1roll = filter.getRoll();2
3pitch = filter.getPitch();4
5heading = filter.getYaw();
These values are sent 25 times per second over the serial port to the Processing application.
As seen in the code, the gyroscope values have been scaled down by a variable factor so that they fit into a range which works well with the algorithm. Without this scaling, the values which are inputted to the function are too high and the visualization of the movement of the board becomes very sensitive to small changes of the 101's position, interpreting a slight change as a great change and causing the 'virtual' board to spin.
The full code can be found at the bottom of the page.
Note that the serial prints for gx,gy,gz,ax,az,ay are left in loop in comments for debugging and must be commented whilst communicating with Processing.
Processing Sketch
If you haven't already, the first thing to do is to download the latest version of Processing from processing.org. Processing is a language similar to Arduino which allows the user to draw dynamic imagery in the familiar
void setup()
and void loop()
structure. For more information on using Processing, please visit their Getting Started guide.The processing code receives incoming data from the serial port which is parsed and assigned to floats
yaw
, pitch
, and roll
, which are then used to compute the transformation matrix which moves the 3D model of the Arduino board.To enable Processing to read from the same port that Arduino is sending to, myPort needs to be changed to your serial port's name. In setup(), this is the Second parameter of Serial.
1myPort = new Serial(this, Serial.list()[0], 9600);
The correct port can be found by using the list() function from the Serial class. The number inside the square brackets refers to the number of the serial port and will be 0, 1, 2, etc. The sketch works if you have just one COM port active on your machine. As an alternative, you could specify directly the "COMx" port of the 101 board - the one used to program from Arduino Software (IDE) the 101 board - commenting this line
1myPort = new Serial(this, Serial.list()[0], 9600);
and uncomment of the lines corresponding to your OS:
1myPort = new Serial(this, "COM5:", 9600); // Windows2
3myPort = new Serial(this, "/dev/ttyACM0", 9600); // Linux4
5myPort = new Serial(this, "/dev/cu.usbmodem1217321", 9600); // Mac
You must replace the port string with the correct name for your COM port.
If in doubt, you can print a list of your available serial ports in a separate sketch to determine this name.
The function serialEvent() is then used to receive and parse data.
1void serialEvent()2{3
4 int newLine = 13; // new line character in ASCII5
6 String message;7
8 do {9
10 message = myPort.readStringUntil(newLine); // read from port until new line11
12 if (message != null) {13
14 String[] list = split(trim(message), " ");15
16 if (list.length >= 4 && list[0].equals("Orientation:")) {17
18 yaw = float(list[1]); // convert to float yaw19
20 pitch = float(list[2]); // convert to float pitch21
22 roll = float(list[3]); // convert to float roll23
24 }25
26 }27
28 } while (message != null);29}
This reads from the serial port until ASCII character 13 (new line) and then uses the split() function to separate the values using the comma character. Since we know that we sent from Arduino in the order yaw, pitch, roll, we can then convert each string to a float and assign them to the first three values in String array list[]. The strings are then converted into floats and stored in float variables. The full Arduino and Processing sketches can be seen below.
Code
Arduino Code
1#include <CurieIMU.h>2#include <MadgwickAHRS.h>3
4Madgwick filter;5unsigned long microsPerReading, microsPrevious;6float accelScale, gyroScale;7
8void setup() {9
10 Serial.begin(9600);11
12 // start the IMU and filter13
14 CurieIMU.begin();15
16 CurieIMU.setGyroRate(25);17
18 CurieIMU.setAccelerometerRate(25);19
20 filter.begin(25);21
22 // Set the accelerometer range to 2G23
24 CurieIMU.setAccelerometerRange(2);25
26 // Set the gyroscope range to 250 degrees/second27
28 CurieIMU.setGyroRange(250);29
30 // initialize variables to pace updates to correct rate31
32 microsPerReading = 1000000 / 25;33
34 microsPrevious = micros();35}36
37void loop() {38
39 int aix, aiy, aiz;40
41 int gix, giy, giz;42
43 float ax, ay, az;44
45 float gx, gy, gz;46
47 float roll, pitch, heading;48
49 unsigned long microsNow;50
51 // check if it's time to read data and update the filter52
53 microsNow = micros();54
55 if (microsNow - microsPrevious >= microsPerReading) {56
57 // read raw data from CurieIMU58
59 CurieIMU.readMotionSensor(aix, aiy, aiz, gix, giy, giz);60
61 // convert from raw data to gravity and degrees/second units62
63 ax = convertRawAcceleration(aix);64
65 ay = convertRawAcceleration(aiy);66
67 az = convertRawAcceleration(aiz);68
69 gx = convertRawGyro(gix);70
71 gy = convertRawGyro(giy);72
73 gz = convertRawGyro(giz);74
75 // update the filter, which computes orientation76
77 filter.updateIMU(gx, gy, gz, ax, ay, az);78
79 // print the heading, pitch and roll80
81 roll = filter.getRoll();82
83 pitch = filter.getPitch();84
85 heading = filter.getYaw();86
87 Serial.print("Orientation: ");88
89 Serial.print(heading);90
91 Serial.print(" ");92
93 Serial.print(pitch);94
95 Serial.print(" ");96
97 Serial.println(roll);98
99 // increment previous time, so we keep proper pace100
101 microsPrevious = microsPrevious + microsPerReading;102
103 }104}105
106float convertRawAcceleration(int aRaw) {107
108 // since we are using 2G range109
110 // -2g maps to a raw value of -32768111
112 // +2g maps to a raw value of 32767113
114
115
116 float a = (aRaw * 2.0) / 32768.0;117
118 return a;119}120
121float convertRawGyro(int gRaw) {122
123 // since we are using 250 degrees/seconds range124
125 // -250 maps to a raw value of -32768126
127 // +250 maps to a raw value of 32767128
129
130
131 float g = (gRaw * 250.0) / 32768.0;132
133 return g;134}
Processing Code
1import processing.serial.*;2Serial myPort;3
4float yaw = 0.0;5float pitch = 0.0;6float roll = 0.0;7
8void setup()9{10
11 size(600, 500, P3D);12
13 // if you have only ONE serial port active14
15 myPort = new Serial(this, Serial.list()[0], 9600); // if you have only ONE serial port active16
17 // if you know the serial port name18
19 //myPort = new Serial(this, "COM5:", 9600); // Windows20
21 //myPort = new Serial(this, "/dev/ttyACM0", 9600); // Linux22
23 //myPort = new Serial(this, "/dev/cu.usbmodem1217321", 9600); // Mac24
25 textSize(16); // set text size26
27 textMode(SHAPE); // set text mode to shape28}29
30void draw()31{32
33 serialEvent(); // read and parse incoming serial message34
35 background(255); // set background to white36
37 lights();38
39 translate(width/2, height/2); // set position to centre40
41 pushMatrix(); // begin object42
43 float c1 = cos(radians(roll));44
45 float s1 = sin(radians(roll));46
47 float c2 = cos(radians(pitch));48
49 float s2 = sin(radians(pitch));50
51 float c3 = cos(radians(yaw));52
53 float s3 = sin(radians(yaw));54
55 applyMatrix( c2*c3, s1*s3+c1*c3*s2, c3*s1*s2-c1*s3, 0,56
57 -s2, c1*c2, c2*s1, 0,58
59 c2*s3, c1*s2*s3-c3*s1, c1*c3+s1*s2*s3, 0,60
61 0, 0, 0, 1);62
63 drawArduino();64
65 popMatrix(); // end of object66
67 // Print values to console68
69 print(roll);70
71 print("\t");72
73 print(pitch);74
75 print("\t");76
77 print(yaw);78
79 println();80}81
82void serialEvent()83{84
85 int newLine = 13; // new line character in ASCII86
87 String message;88
89 do {90
91 message = myPort.readStringUntil(newLine); // read from port until new line92
93 if (message != null) {94
95 String[] list = split(trim(message), " ");96
97 if (list.length >= 4 && list[0].equals("Orientation:")) {98
99 yaw = float(list[1]); // convert to float yaw100
101 pitch = float(list[2]); // convert to float pitch102
103 roll = float(list[3]); // convert to float roll104
105 }106
107 }108
109 } while (message != null);110}111
112void drawArduino()113{114
115 /* function contains shape(s) that are rotated with the IMU */116
117 stroke(0, 90, 90); // set outline colour to darker teal118
119 fill(0, 130, 130); // set fill colour to lighter teal120
121 box(300, 10, 200); // draw Arduino board base shape122
123 stroke(0); // set outline colour to black124
125 fill(80); // set fill colour to dark grey126
127 translate(60, -10, 90); // set position to edge of Arduino box128
129 box(170, 20, 10); // draw pin header as box130
131 translate(-20, 0, -180); // set position to other edge of Arduino box132
133 box(210, 20, 10); // draw other pin header as box134}
Suggest changes
The content on docs.arduino.cc is facilitated through a public GitHub repository. If you see anything wrong, you can edit this page here.
License
The Arduino documentation is licensed under the Creative Commons Attribution-Share Alike 4.0 license.