Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- Writing The Software
- Running And Testing The System
- Displaying Rotation Angles
- Summary
Introduction
This tutorial will teach you how to retrieve and display sensor readings from the on-board inertial measurement unit (IMU) of the Arduino Uno WiFi Rev2 development board. A basic understanding of electronics and programming is expected along with some familiarity with the Arduino platform. If you are new to the Arduino platform or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On An Arduino Uno tutorial before proceeding with this one.
The resources created for this tutorial are available on GitHub for your reference.
What Is Needed
- Linux, macOS, Or Windows Based Computer With A USB Port
- Arduino IDE
- Arduino Uno WiFi Rev2 (available on Arduino and SparkFun) With Compatible USB Cable
Background Information
The Arduino Uno WiFi Rev2 development board has an on-board IMU with a 3-axis accelerometer, a 3-axis gyroscope, and an embedded temperature sensor. This IMU is the LSM6DS3 device provided by ST Microelectronics (datasheet) accessed over the SPI serial bus interface.
I previously wrote the Accessing The IMU On The New Arduino Uno WiFi Rev2 article shortly after that board was released. The article utilized the SparkFun LSM6DS3 Breakout library since there was no official Arduino support available at the time. Arduino recently released its own Arduino_LSM6DS3 library for accessing the IMU that we will be using in this tutorial.
The accelerometer and gyroscope data is accessed a bit differently between the two libraries. In addition, the Arduino_LSM6DS3 library does not include access to the on-chip temperature sensor, but I have requested this enhancement. (See issue on GitHub)
My development system consists of the Arduino Uno WiFi Rev2 development board connected to a macOS based computer running the desktop Arduino IDE. If you are using a different computer setup, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.
If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.
Writing The Software
The first thing we need to do is open the Arduino IDE and install the Arduino_LSM6DS3 library from within the Library Manager (Main Menu > Tools > Manage Libraries…).
Next, create a new sketch named LSM6DS3_Arduino_Simple with the code shown below.
// LSM6DS3_Arduino_Simple - LSM6DS3_Arduino_Simple.ino // // Description: // Retrieves motion data from the on-board LSM6DS3 IMU of the Arduino Uno WiFi // Rev2 using the Arduino_LSM6DS3 library and displays readings in the Serial // Monitor. // // Created by John Woolsey on 01/28/2020. // Copyright (c) 2019 Woolsey Workshop. All rights reserved. // Includes #include <Arduino_LSM6DS3.h> void setup() { Serial.begin(9600); // initialize serial bus (Serial Monitor) while (!Serial); // wait for serial initialization Serial.print("LSM6DS3 IMU initialization "); if (IMU.begin()) { // initialize IMU Serial.println("completed successfully."); } else { Serial.println("FAILED."); IMU.end(); while (1); } Serial.println(); } void loop() { char buffer[8]; // string buffer for use with dtostrf() function float ax, ay, az; // accelerometer values float gx, gy, gz; // gyroscope values // Retrieve and print IMU values if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable() && IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) { Serial.print("ax = "); Serial.print(dtostrf(ax, 4, 1, buffer)); Serial.print(" g, "); Serial.print("ay = "); Serial.print(dtostrf(ay, 4, 1, buffer)); Serial.print(" g, "); Serial.print("az = "); Serial.print(dtostrf(az, 4, 1, buffer)); Serial.print(" g, "); Serial.print("gx = "); Serial.print(dtostrf(gx, 7, 1, buffer)); Serial.print(" °/s, "); Serial.print("gy = "); Serial.print(dtostrf(gy, 7, 1, buffer)); Serial.print(" °/s, "); Serial.print("gz = "); Serial.print(dtostrf(gz, 7, 1, buffer)); Serial.println(" °/s"); } delay(1000); // wait one second between readings }
The setup()
function initializes the serial port (for display to the Serial Monitor) and the IMU. The loop()
function retrieves and displays the IMU values in the Serial Monitor with a delay of one second between readings. I am using the dtostrf()
function here to convert floating point numbers to nicely formatted strings for display.
Verify and save your sketch when you are done.
Running And Testing The System
Open the Serial Monitor (Main Menu > Tools > Serial Monitor) window and upload your sketch to the Arduino board. You should see something like the following being displayed.
LSM6DS3 IMU initialization completed successfully. ax = 0.0 g, ay = -0.0 g, az = 1.0 g, gx = 0.9 °/s, gy = -4.3 °/s, gz = -3.1 °/s ax = 0.0 g, ay = -0.0 g, az = 1.0 g, gx = 0.9 °/s, gy = -4.5 °/s, gz = -3.1 °/s ax = 0.0 g, ay = 0.0 g, az = 1.0 g, gx = 1.0 °/s, gy = -4.4 °/s, gz = -3.1 °/s ax = 0.0 g, ay = 0.0 g, az = 1.0 g, gx = 1.0 °/s, gy = -4.5 °/s, gz = -3.1 °/s ax = 0.0 g, ay = 0.0 g, az = 1.0 g, gx = 1.2 °/s, gy = -4.3 °/s, gz = -3.0 °/s
Congratulations, you are now able to access the IMU and retrieve motion sensor readings from your Arduino Uno WiFi Rev2. These readings are showing you the acceleration, in g, that your device is experiencing along with how quickly, in degrees per second, the device is rotating around particular axes in 3-dimensional space. If it is sitting still on your desk, all readings should be close to 0, except for az
, which should be close to 1 due to the effect of earth’s gravity.
Pick up your Arduino and move, tilt, and shake it to see how the readings change.
Displaying Rotation Angles
We could have stopped here and called it a success, but it is often nice to see our data in a different form using rotation angles, e.g. roll, pitch, and yaw. Sometimes people refer to these as inclination or Euler angles. Let’s make some changes to our sketch to add that capability. For additional information on roll, pitch, and yaw, see Aircraft principal axes in Wikipedia.
Duplicate (Save As…) your original LSM6DS3_Arduino_Simple sketch and name the new one as LSM6DS3_Arduino_Rotation. This new sketch is the one we will be modifying.
There is already a library available that will calculate the rotation angles for us. Search for and install the Madgwick library provided by Arduino from the Arduino Library Manager. Once installed, include the library in your sketch at the end of the Includes
section.
#include <MadgwickAHRS.h>
Next we need to define a sample rate, in Hz, specifying how often the measurements will be read from the IMU and the rotation angles will be calculated. Add the following statement after the Includes
section.
#define SAMPLE_RATE 10
Now we need to instantiate the Madgwick filter class that will perform our rotation angle calculations. Add the following line just before the setup()
function.
Madgwick filter;
At the very bottom of the setup()
function, add a line to initialize the Madgwick filter with the specified sample rate.
filter.begin(SAMPLE_RATE);
Change the name of the loop()
function to printValues()
and remove the last delay()
line. This probably seems a bit strange, but it is the easiest way to retain our previous work.
Now add the loop()
function back, just before the printValues()
function, with the following contents.
void loop() { static unsigned long previousTime = millis(); unsigned long currentTime = millis(); if (currentTime - previousTime >= 1000/SAMPLE_RATE) { // printValues(); printRotationAngles(); previousTime = millis(); } }
The new loop()
function calculates the correct times, based on the defined sample rate, to call one of the functions. The commented out printValues()
function is our original method and the printRotationAngles()
function will be our new method. Note, the 10 Hz sample rate also means that the appropriate values will be printed to the Serial Monitor 10 times per second.
Let’s now add that new printRotationAngles()
function to the end of our sketch.
void printRotationAngles() { char buffer[5]; float ax, ay, az; float gx, gy, gz; if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable() && IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) { filter.updateIMU(gx, gy, gz, ax, ay, az); Serial.print("Roll = "); Serial.print(dtostrf(filter.getRoll(), 4, 0, buffer)); Serial.print(" °, "); Serial.print("Pitch = "); Serial.print(dtostrf(filter.getPitch(), 4, 0, buffer)); Serial.print(" °, "); Serial.print("Yaw = "); Serial.print(dtostrf(filter.getYaw(), 4, 0, buffer)); Serial.println(" °"); } }
There really isn’t much difference between the new printRotationAngles()
function and the original printValues()
function. The filter.updateIMU()
call was added to calculate the rotation angles and printing was adjusted to properly display the angles.
Please note that the yaw angle calculated by this method will be relative to the initial starting position of the IMU. In addition, the value will drift over time. To obtain an absolute, or true, yaw angle (heading), an additional data source, such as a magnometer, would be required.
Compile the new sketch to verify everything was entered correctly. Your updated sketch should now look similar to the following.
// LSM6DS3_Arduino_Rotation - LSM6DS3_Arduino_Rotation.ino // // Description: // Retrieves motion data from the on-board LSM6DS3 IMU of the Arduino Uno WiFi // Rev2 using the Arduino_LSM6DS3 library and displays rotation angles (roll, // pitch, and yaw) in the Serial Monitor. // // Created by John Woolsey on 01/28/2020. // Copyright (c) 2019 Woolsey Workshop. All rights reserved. // Includes #include <Arduino_LSM6DS3.h> #include <MadgwickAHRS.h> // Defines #define SAMPLE_RATE 10 // in Hz // Constructors Madgwick filter; // Madgwick algorithm for roll, pitch, and yaw calculations void setup() { Serial.begin(9600); // initialize serial bus (Serial Monitor) while (!Serial); // wait for serial initialization Serial.print("LSM6DS3 IMU initialization "); if (IMU.begin()) { // initialize IMU Serial.println("completed successfully."); } else { Serial.println("FAILED."); IMU.end(); while (1); } Serial.println(); filter.begin(SAMPLE_RATE); // initialize Madgwick filter } void loop() { static unsigned long previousTime = millis(); unsigned long currentTime = millis(); if (currentTime - previousTime >= 1000/SAMPLE_RATE) { // printValues(); printRotationAngles(); previousTime = millis(); } } // Prints IMU values. void printValues() { char buffer[8]; // string buffer for use with dtostrf() function float ax, ay, az; // accelerometer values float gx, gy, gz; // gyroscope values // Retrieve and print IMU values if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable() && IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) { Serial.print("ax = "); Serial.print(dtostrf(ax, 4, 1, buffer)); Serial.print(" g, "); Serial.print("ay = "); Serial.print(dtostrf(ay, 4, 1, buffer)); Serial.print(" g, "); Serial.print("az = "); Serial.print(dtostrf(az, 4, 1, buffer)); Serial.print(" g, "); Serial.print("gx = "); Serial.print(dtostrf(gx, 7, 1, buffer)); Serial.print(" °/s, "); Serial.print("gy = "); Serial.print(dtostrf(gy, 7, 1, buffer)); Serial.print(" °/s, "); Serial.print("gz = "); Serial.print(dtostrf(gz, 7, 1, buffer)); Serial.println(" °/s"); } } // Prints rotation angles (roll, pitch, and yaw) calculated using the // Madgwick algorithm. // Note: Yaw is relative, not absolute, based on initial starting position. // Calculating a true yaw (heading) angle requires an additional data source, // such as a magnometer. void printRotationAngles() { char buffer[5]; // string buffer for use with dtostrf() function float ax, ay, az; // accelerometer values float gx, gy, gz; // gyroscope values if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable() && IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) { filter.updateIMU(gx, gy, gz, ax, ay, az); // update roll, pitch, and yaw values // Print rotation angles Serial.print("Roll = "); Serial.print(dtostrf(filter.getRoll(), 4, 0, buffer)); Serial.print(" °, "); Serial.print("Pitch = "); Serial.print(dtostrf(filter.getPitch(), 4, 0, buffer)); Serial.print(" °, "); Serial.print("Yaw = "); Serial.print(dtostrf(filter.getYaw(), 4, 0, buffer)); Serial.println(" °"); } }
Once you are happy with your sketch, upload it to the Arduino Uno WiFi Rev2 board. Make sure the Serial Monitor is still open so that you can see the output which should look like the following.
LSM6DS3 IMU initialization completed successfully. Roll = 0 °, Pitch = -2 °, Yaw = 180 ° Roll = 0 °, Pitch = -3 °, Yaw = 179 ° Roll = 0 °, Pitch = -2 °, Yaw = 179 ° Roll = 0 °, Pitch = -2 °, Yaw = 179 ° Roll = -1 °, Pitch = -2 °, Yaw = 179 °
Pick up your Arduino and tilt and rotate it in multiple directions to see how the roll, pitch, and yaw rotation angles change. Pretend you are an airplane taking off or turning to a new vector.
You can get back to printing your standard (original) sensor readings once per second with this new sketch by:
- uncommenting the
printValues()
function call (within theloop()
function), - commenting out the
printRotationAngles()
function, - and changing the
SAMPLE_RATE
define from10
to1
.
Summary
In this tutorial, we learned how to retrieve motion data from the on-board IMU of the Arduino Uno WiFi Rev2 development board. We created a sketch utilizing the official Arduino_LSM6DS3 library and were able to see acceleration and rotation sensor readings being displayed within the Serial Monitor.
We then created a new sketch, adding the Madgwick library, to display rotation angles, e.g. roll, pitch, and yaw, calculated using the accelerometer and gyroscope values.
The final source code used for this tutorial is available on GitHub.
Thank you for joining me in this journey and I hope you enjoyed the experience. Please feel free to share your thoughts in the comments section below.
Hi,
You mentioned using a magnometer to get a true heading/yaw value, wondered if you’d had any luck in doing this? I’m seeing things similar but using other boards, is there a way of better integrating that with this board?
Thanks,
Oscar
Sorry, I haven’t researched it enough yet, but I hope you find what you are looking for.
Hi, I ran your program, but weirdly the Yaw number seems to simply crawl down from 360 to zero over and over. Pitch and Roll vary a little too, and this is with it sitting on the desk!
Also, supposedly the LSM6DS3 device has a temperature sensor but I am unable to find any way to access it. If you know of one I’d be curious.
Great site you created! I look at it often.
Yes, the yaw numbers will do that without an additional external source, such as a magnometer. I would expect the pitch and roll to move around a little, but not nearly as much as the yaw.
I submitted a request for the library to include access to the temperature sensor, but it has not yet been added. You could use the Sparkfun library, which does have temperature access, covered in the Accessing The IMU On The New Arduino Uno WiFi Rev2 tutorial to access the on-board temperature sensor. You can also review the temperature access code inside the Sparkfun library or the pull request associated with my issue request to learn how to do it yourself if you are comfortable with that.
Glad you are enjoying the site. Thanks for letting me know.
[…] Using The Arduino_LSM6DS3 Library To Access The Arduino Uno WiFi Rev2 IMU [3] […]
Hi, I’m trying to run this on an Arduino Nano 33 IoT and I’m getting this error
exit status 1
‘dtostrf’ was not declared in this scope
I tried this makeshift function https://github.com/arduino/Arduino/blob/a2e7413d229812ff123cb8864747558b270498f1/hardware/arduino/sam/cores/arduino/avr/dtostrf.c
and the program will compile however the values in the output are all blank.
Any advice?
It appears that the
dtostrf()
function is only available in the avr toolchain. I see people using other boards recommending adding the following to include it.#include <avr/dtostrf.h>
That did the trick! Thank you very much!! Good fortune to you sir 🙂
This is brilliant. Thank you very much for the amazing knowledge you provide. Now i tapped into the Temperature sensor by integrating the sparkfunLSM6DS3 library in the code you provided here, Oddly enough it seems to work without using the command ” myIMU.begin(); ” .. i don’t konw if this modification to the code is reasonable or not, since i don’t know much about writing code. please take a look…
#include
#include “SparkFunLSM6DS3.h”
LSM6DS3 myIMU(SPI_MODE, SPIIMU_SS); // SPI Chip Select
void setup() {
Serial.begin(9600); // initialize serial bus (Serial Monitor)
while (!Serial); // wait for serial initialization
Serial.print(“IMU initialization “);
if (IMU.begin() ) { // initialize IMU
Serial.println(“IS ROCK ‘N ROLL”);
} else {
Serial.println(“SUCKED”);
IMU.end();
while (1);
}
Serial.println();
Serial.print(“Accelerometer sample rate = “); Serial.print(IMU.accelerationSampleRate()); Serial.println(” Hz”);
Serial.println();
}
void loop() {
char buffer[8]; // string buffer for use with dtostrf(float_value, min_width, num_digits_after_decimal, where_to_store_string)
float ax, ay, az; // accelerometer values
float gx, gy, gz; // gyroscope values
// Retrieve and print IMU values
if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()
&& IMU.readAcceleration(ax, ay, az) && IMU.readGyroscope(gx, gy, gz)) {
Serial.print(“ax=”); Serial.print(dtostrf(ax, -4, 1, buffer));
Serial.print(” ay=”); Serial.print(dtostrf(ay, -4, 1, buffer));
Serial.print(” az=”); Serial.print(dtostrf(az, -4, 1, buffer)); Serial.print(” (g)”);
Serial.print(” gx=”); Serial.print(dtostrf(gx, -7, 1, buffer));
Serial.print(” gy=”); Serial.print(dtostrf(gy, -7, 1, buffer));
Serial.print(” gz=”); Serial.print(dtostrf(gz, -7, 1, buffer)); Serial.print(” (°/s)”);
Serial.print(” C = “); Serial.print(myIMU.readTempC(), 4); Serial.print(” F = “); Serial.println(myIMU.readTempF(), 4);
}
delay(1000);
}
Glad to hear you got it working.
Your modifications look reasonable, however, I believe you still need to include the Arduino library and remove the empty
#include
statement.#include <SparkFunLSM6DS3.h>
#include <Arduino_LSM6DS3.h>
It appears that
myIMU.begin()
is not necessary since you are not using the SparkFun library to load accelerometer and gyroscope values.Hi John,
I get this kind of rolling,drifting Yaw [1st column]
Orientation: 78.82 -1.90 0.49
Orientation: 78.41 -2.46 -0.53
Orientation: 77.94 -2.13 0.22
Orientation: 77.52 -2.16 -0.64
Orientation: 77.03 -2.00 0.29
Orientation: 76.62 -2.54 -0.73
Orientation: 76.14 -2.20 0.01
Orientation: 75.71 -1.82 -0.43
Orientation: 75.22 -1.84 0.68
Orientation: 74.80 -2.65 -0.33
Orientation: 74.34 -2.15 -0.03
Orientation: 73.94 -2.10 -0.87
Orientation: 73.44 -2.37 0.36
Orientation: 73.02 -2.31 -0.45
Orientation: 72.56 -1.81 -0.10
Orientation: 72.15 -3.15 -0.87
Orientation: 71.69 -2.71 -0.31
Orientation: 71.21 -2.31 0.34
Orientation: 70.80 -2.50 -0.60
Orientation: 70.32 -2.08 0.05
Orientation: 69.85 -1.55 0.16
Orientation: 69.41 -3.23 -0.17
Orientation: 68.96 -2.70 -0.06
Orientation: 68.52 -2.19 -0.07
Orientation: 68.10 -2.04 -0.79
Orientation: 67.60 -2.44 0.46
Orientation: 67.17 -2.25 -0.24
Orientation: 66.68 -3.04 1.01
Orientation: 66.26 -2.93 0.25
Orientation: 65.79 -2.40 0.55
Orientation: 65.36 -2.23 -0.18
Orientation: 64.94 -3.62 -0.89
Orientation: 64.49 -3.15 -0.49
Orientation: 64.02 -2.68 0.00
Orientation: 63.59 -2.24 -0.33
Orientation: 63.09 -2.45 0.88
Orientation: 62.66 -2.48 0.02
1. so this is expected [even with madgwick filter]?
2. this can ONLY be only corrected with magnetometer values?
3. Any other way of manual calibration without magnetometer?
Thanks
Unfortunately, I believe that to be the case.
Check out the Adafruit 9-DOF Absolute Orientation IMU Fusion Breakout – BNO055 board from Adafruit. It looks very interesting and may provide exactly what you are looking for, but it is currently out of stock. However, it does look like Digi-Key may have them in stock.