Maker.io main logo

How to Fuse Motion Sensor Data into AHRS Orientation (Euler/Quaternions)

223

2024-03-11 | By Adafruit Industries

License: See Original Project Arduino

Courtesy of Adafruit

Guide by Lady Ada

Overview

 

AHRS is an acronym for Attitude and Heading Reference System, a ‎system generally used for aircraft of any sort to determine heading, ‎pitch, roll, altitude etc.

A basic IMU (Intertial Measurement Unit) generally provides raw sensor ‎data, whereas an AHRS takes this data one step further, converting it ‎into heading or direction in degrees. ‎

To help you get started designing your own AHRS system, or just to ‎help convert raw sensor data into useful numbers that you can relate ‎to the real world, we've created an Arduino library that lets you 'fuse' a ‎range of common accelerometer/gyroscope/magnetometer sensor sets ‎using a few different algorithms such ‎as Mahony, Madgwick, and NXP Sensor Fusion.‎

We recommend a Cortex M0 or faster/greater chipset - there's a lot of ‎math and memory required so 4KB+ or RAM and 32 MHz+ speed helps ‎for the fancier algorithms. That said, you can do some basic fusion ‎with an ATmega328p (Arduino UNO compatible). The data is output on ‎the serial port for easy integration.‎‎

  1. We'll start by verifying you can store calibrations on your chipset, ‎and then calibrating your sensors.‎‎
  2. Then we'll compile the AHRS library for your desired sensors and ‎algorithm/
  3. Finally, we'll visualize the motion using a Web Serial API 3D model.‎

sensors_Rabbit10DOF

Storing Calibrations

Every sensor will vary and need calibration. Some sensors have a 'range' ‎for their error output, which helps us know if the sensor is functioning ‎‎- but we still need to calibrate every sensor/board. One of the ‎challenges with per-device-calibration is how to store that calibration.‎

We could put the calibrations at the top of the Arduino sketch, but we ‎would have to re-compile it for each device, and it’s also easy to forget ‎or lose the calibration.‎

So, instead, we try to store the calibration in non-volatile memory - ‎that means either EEPROM or external FLASH. We try to use external ‎flash not internal flash, because internal flash can be erased and ‎overwritten when we upload new code.‎

There's lots of different ways you could save calibrations - but to make ‎things easiest we're going to stick to those two techniques. However, ‎that means our board has to have some sort of NVM available! Here's ‎what you can expect:‎

EEPROM Memory (either real or ‎simulated)‎

Some chips have EEPROM memory, this is a tiny amount of memory, ‎which is separate from FLASH, where you can tuck maybe 256 bytes of ‎data. There's two kinds of EEPROM, 'real' and 'simulated'. Real is, well, ‎real! There's literally a chunk of memory inside the chip itself, and ‎you'll see it mentioned in the datasheet as EEPROM.‎

The ATmega328 datasheet indicates that you can get up to 1KB of ‎EEPROM (depends on which model of the chip you buy.)

features_1

‎It’s not terribly common to see EEPROM in chips as you get to the ‎more expensive models. Partially that's because they tend to have SPI ‎Flash chips, but also cause they aren't that useful with larger chips. ‎Either way, simulated EEPROM is when some internal SPI flash or ‎external memory storage is dual used as EEPROM. For example, the ‎ESP8266 uses the very end of the SPI flash chip that holds the program memory ‎for EEPROM.

Some chips with real EEPROM:‎

  • ATmega328 (Arduino Uno/Metro/compatibles)
  • ATmega32u4 (Arduino Leonardo/compatibles)
  • ATmega2560 (Arduino Mega/compatibles)‎

Some chips with simulated EEPROM:‎

External SPI or QSPI memory

This is a separate chip used to store files, code, audio, graphics, etc. A ‎lot of Adafruit products have SPI/QSPI memory in order to support ‎CircuitPython. For example:‎

  • All Adafruit nRF52840 boards
  • All Adafruit SAMD51 ("M4") boards
  • Many SAMD21 ("M0") boards (they'll often have Express in the ‎name)‎

The reason we prefer external memory is that you can edit the file ‎using any text editor, when CircuitPython (or another mass-storage ‎enabled sketch) is loaded. Also, many of the above do not have ‎EEPROM, so there's often not a choice.‎

The Feather M0 Express has an 8-SOIC SPI Flash chip highlighted to ‎the left.‎

feather_2

The only thing to watch out for is that the SPI FLASH must ‎be formatted with a filesystem - much like an SD card (altho, these ‎days, SD cards come pre-formatted). A blank SPI Flash chip doesn't ‎have anything on it.‎

Manually Setting in Code

The least desirable, but sometimes necessary, option is to simply set ‎the calibration at the top of your sketch/code. It's not something we ‎like to do because it’s easy to lose the calibration, but it's always an ‎option!‎

Calibration Pre-Check

OK before we continue, we have to check that we are able to store ‎calibration either in the EEPROM (ATmega328, 'm32u4, 'm2560, ‎ESP8266, ESP32) or (Q)SPI Flash (most Adafruit M0, M4, nRF52840 ‎boards.)‎

Library Installation

You'll need a few libraries, install them through the library manager!‎

library_3

Search for and install Adafruit Sensor Calibration.

library_4

We strongly recommend using Arduino IDE 1.8.10+ because it will ‎automatically install any dependency libraries. If you have to install ‎manually, grab SdFat - Adafruit Fork, ArduinoJson, Adafruit ‎SPIFlash, Adafruit Unified Sensor as well (see all dependencies here.)‎

Compilation & Upload Check

Load sensor_calibration_read example - yes even though there is no ‎calibration yet, this will let us verify the basics!‎

load_5

EEPROM Storage

First, we'll try loading it into a chip with EEPROM (an ATmega328-‎based Metro mini!)‎

loading_6

Here's what you should look for:‎

  • At the top you'll see that Has EEPROM is 1 - indicating we're ‎using the internal EEPROM
  • It's OK to see No calibration loaded/found (it’s a fresh chip!)
  • The hex block after the **WARNING** is the raw calibration data ‎stored. It's normal to see all 0x00 or 0xFF's if this is the first time ‎running the program.
  • Finally, you'll see Calibrations found:‎
    • Magnetic Hard Offset should default to 0, 0, 0 (no offset)‎
    • Magnetic Soft Offset should default to 1, 0, 0, 0, 1, 0, 0, ‎‎0, 1 - note that isn't all zeros! It's a 3x3 identity matrix.
    • Gyro Zero Rate Offset should default to 0, 0, 0 (no offset)‎
    • Accel Zero Rate Offset should default to 0, 0, 0 (no offset)‎

If you got this far, you're good! Go to the next page where we try to ‎write calibrations.‎

Flash Storage

Here's what you can expect if you're using a chip with built in SPI/QSPI ‎storage!‎

storage_7

Here's what you should look for:‎

  • At the top you'll see that Has FLASH is 1 - indicating we're using ‎the external SPI Flash storage and that it successfully Mounted ‎Filesystem!
  • It's OK to see Failed to read file/No calibration ‎loaded/found (we haven't made one yet!)
  • Finally, you'll see Calibrations found:
    • Magnetic Hard Offset should default to 0, 0, 0 (no offset)
    • Magnetic Soft Offset should default to 1, 0, 0, 0, 1, 0, 0, ‎‎0, 1 - note that isn't all zeros! It's a 3x3 identity matrix.‎
    • Gyro Zero Rate Offset should default to 0, 0, 0 (no offset)‎
    • Accel Zero Rate Offset should default to 0, 0, 0 (no offset)‎

If you got this far, you're good! Go to the next page where we try to ‎write calibrations.‎

Flash Unformatted Error

There's a chance, if you have a totally fresh board, that the flash is ‎unformatted. In this case, when you upload the calibration reader, ‎you'll get that the JEDEC Chip ID was read (it may vary), and that the ‎flash size was detected - but failed to mount newly formatted filesystem!‎

flash_8

If that happens, you have two ways to format the filesystem.‎

  1. Easiest way (we think) is to install CircuitPython - that's because you ‎don't have to compile anything - simply enter the bootloader by ‎click/double-clicking and dragging over a UF2 file which will ‎format the disk for you.‎‎
  2. Or you can load the SdFat formatter example in the Adafruit ‎SPIFlash library:‎

filesystem_9

Compile, upload and check the serial monitor for instructions.‎

compile_10

Now re-upload/re-try the sensor calibration reading demo.‎

Calibration Write Check

OK now that we have our calibration storage worked out, let’s try ‎writing a calibration to disk!‎

Load up the other example, sensor_calibration_write

You'll see that we first load any existing calibration from non-volatile ‎storage with cal.loadCalibration(). Then we set the calibrations we want to ‎save with:‎

Download File

Copy Code
// in uTesla
cal.mag_hardiron[0] = -3.35;
cal.mag_hardiron[1] = -0.74;
cal.mag_hardiron[2] = -40.79;

// in uTesla
cal.mag_softiron[0] = 0.965;
cal.mag_softiron[1] = 0.018;
cal.mag_softiron[2] = 0.010;
cal.mag_softiron[3] = 0.018;
cal.mag_softiron[4] = 0.960;
cal.mag_softiron[5] = 0.003;
cal.mag_softiron[6] = 0.010;
cal.mag_softiron[7] = 0.003;
cal.mag_softiron[8] = 1.080;

// in Radians/s
cal.gyro_zerorate[0] = 0.05;
cal.gyro_zerorate[1] = -0.01;
cal.gyro_zerorate[2] = -0.01;

which only changes the calibrations in temporary memory. Finally, we ‎run cal.saveCalibration() to write the calibration to permanent storage.‎

Upload this to your board.‎

EEPROM Example

If you use a chip with EEPROM, you'll see similar output from the ‎previous page, this time you will get to see the HEX data stored in ‎EEPROM. We use the same format as PJRC's NXPMotionSense library so ‎you will see 0x75, 0x54 as the first two bytes of the data chunk.‎

eeprom_11

External FLASH example

If you use a chip with external flash, you should see similar output ‎from the previous pre-check, but now it will write the calibration and ‎also print out calibration file for you. You can see that it's stored in ‎JSON format for easy parsing in Python or Arduino!‎

external_12

Reading back calibration

OK no matter which way you calibrated, now you can load the ‎calibration read example to see the saved values loaded up and printed ‎out!‎

For EEPROM:‎

sensors_14

For external FLASH:‎

reading_13

Magnetic Calibration with MotionCal

Calibrating the magnetometer is required to get good orientation data!‎

Before running this example - make sure you have done the calibration ‎pre-check and write check!‎

Paul Stoffregen of PJRC wrote a really awesome cross-platform calibration ‎helper that is great for doing both soft and hard iron magnetometer ‎calibration. What's nice about it is you get a 3D visualization of the ‎magnetometer output, and it also tosses outliers and tells you how ‎much spherical coverage you got!‎

Step 1 - Download MotionCal ‎Software

MotionCal is available for Mac, Windows and Linux, you can download it ‎from clicking here.‎

Look for this section on the website:‎

section_15

And click the one that matches your computer the best.‎

Step 2 - Configure & Upload the ‎AHRS calibration Example

Next, we have to tell the microcontroller board to send the ‎magnetometer (and, if there is one, accelerometer and gyroscope) data ‎out over serial in the right format.‎

Open up the Adafruit_AHRS->calibration example.‎

open_16

At the top of the sketch, you'll see a section where you can #include ‎different sensor sets. Not every sensor-set is defined, but our most ‎popular ones are! (You'll need sensors that are Adafruit_Sensor ‎compatible.)‎

Uncomment whichever kit you are using, and comment out the rest.‎

kit_17

Select your desired board & port from the Tools menu then ‎click Upload.‎

tools_18

Open up the serial console and check that the EEPROM/Filesystem was ‎found. There may already be an existing calibration from prior ‎experiments.

experiments_19

You'll then see a stream of data that looks like:

Raw:-58,-815,8362,76,-121,-95,-375,-159,-24

Uni:-0.07,-0.98,10.00,0.0832,-0.1327,-0.1046,-37.50,-15.93,-2.50‎

The first three numbers are accelerometer data - if you don't have an ‎accelerometer, they will be 0.‎

The middle three numbers are gyroscope data - if you don't have a ‎gyroscope, they will be 0.‎

The last three numbers are magnetometer, they should definitely not ‎be zeros!‎

Step 3 - Run MotionCal

Close the serial port, and launch MotionCal.‎

close_20

Select the same COM/Serial port you used in Arduino.‎

com_21

Twist the board/sensor around. Make sure it’s not near any strong ‎magnets (unless that's part of the installation.)

twist_22

Keep twisting until you get a complete 'sphere' of red dots. At this ‎point you are calibrated!‎

twist_23

In the top right you'll see the hard magnetic offsets at the top, the soft ‎offsets in the middle and the field strength at the bottom.‎

In this case, the hard iron offsets are [-31.25, 35.67, -116.44]

Take a screenshot of this display, so you can refer to these ‎numbers later!‎

MotionCal does not calibrate the accelerometer or gyroscope (yet) - so ‎those offsets will be zero.‎

motion_24

Eventually you'll have enough datapoints that the Send Cal button will ‎activate (its grayed out by default.)

Once you can click the button, try clicking it (we had to try a few ‎times?)‎

click_25

You'll see a large green checkmark once the calibration is saved and ‎verified!

checkmark_26

Step 4 - Verify Calibration

Re-load the sensor_calibration_read sketch to verify the calibration ‎was saved!‎

26a

Sensor Fusion Algorithms

There's 3 algorithms available for sensor fusion. In general, the ‎better the output desired, the more time and memory the ‎fusion takes!‎

Note that no algorithm is perfect - you'll always get some drift and ‎wiggle because these sensors are not that great, but you should be ‎able to get basic orientation data.‎

In order of complexity, they are:‎

Mahony

This basic but effective algorithm will run on smaller chips like the ‎‎'328p which makes it a great one for any platform.‎

The original paper is available here

Madgwick

This algorithm is very popular when you have faster Cortex M0, M3, ‎M4 or faster chips. It isn't going to run on an atmega328p.‎

The original paper is available here

NXP Sensor Fusion

This really nice fusion algorithm was designed by NXP and requires a ‎bit of RAM (so it isn’t for a '328p Arduino) but it has great output ‎results.‎

As described by NXP:

Sensor fusion is a process by which data from several different sensors ‎are fused to compute something more than could be determined by ‎any one sensor alone. An example is computing the orientation of a ‎device in three-dimensional space. That orientation is then used to ‎alter the perspective presented by a 3D GUI or game. ‎

The NXP Sensor Fusion Library for Kinetis MCUs (also referred to as ‎Fusion Library or development kit) provides advanced functions for ‎computation of device orientation, linear acceleration, gyro offset, and ‎magnetic interference based on the outputs of NXP inertial and ‎magnetic sensors.

Version 7.00 of the development kit has the following features:‎

  • Full source code for the sensor fusion libraries
  • IDE-independent software based upon the NXP Kinetis Software ‎Development (KSDK).
  • The Fusion Library no longer requires Processor Expert for ‎component configuration.‎
  • Supports both bare-metal and RTOS-based project development. ‎Library code is now RTOS agnostic.
  • Optional standby mode powers down power-hungry sensors ‎when no motion is detected.‎
  • ‎9-axis Kalman filters require significantly less MIPS to execute
  • All options require significantly less memory than those in the ‎Version 5.xx library.‎
  • Full documentation including user manual and fusion data sheet

The fusion library is supplied under a liberal BSD open-source license, ‎which allows the user to employ this software with NXP MCUs and ‎sensors, or those of our competitors. Support for issues relating to the ‎default distribution running on NXP reference hardware is available via ‎standard NXP support channels. Support for nonstandard platforms ‎and applications is available at ‎https://community.nxp.com/community/sensors/sensorfusion.‎

Let's fuse!‎

OK now that the sensors are calibrated, and you know what the ‎options are for filters - it’s time to FUSE THOSE SENSORS! That's what ‎we're here for, right?‎

Install the Adafruit AHRS library from the library manager. We strongly ‎recommend using Arduino IDE 1.8.10+ because it will automatically ‎install any dependency libraries. See all dependencies here.

install_27

Open up the calibrated_orientation sketch.‎

open_28

At the top of the sketch, you'll see a section where you ‎can #include different sensor sets. Not every sensor-set is defined, but ‎our most popular ones are! (You'll need sensors that ‎are Adafruit_Sensor compatible.)

‎Uncomment whichever kit you are using and comment out the rest.‎

Download File

Copy Code
// uncomment one combo 9-DoF!
#include "LSM6DS_LIS3MDL.h" // can adjust to LSM6DS33, LSM6DS3U, LSM6DSOX...
//#include "LSM9DS.h" // LSM9DS1 or LSM9DS0
//#include "NXP_FXOS_FXAS.h" // NXP 9-DoF breakout

Next, you can select which fusion algorithm you want to try:‎

Download File

Copy Code
// pick your filter! slower == better quality output
//Adafruit_NXPSensorFusion filter; // slowest
Adafruit_Madgwick filter; // faster than NXP
//Adafruit_Mahony filter; // fastest/smalleset

By default, we'll be performing a calculation every 10 ms (100Hz) and ‎printing out 1 out of 10 calculations, however you can adjust those ‎numbers up or down in this section as well as adding debug output‎.

Download File

Copy Code
#define FILTER_UPDATE_RATE_HZ 100
#define PRINT_EVERY_N_UPDATES 10
//#define AHRS_DEBUG_OUTPUT

OK now compile & upload!‎

Open the serial console and you'll see the sensors detected and then ‎text like this.‎

Orientation: 180.82 -1.65 2.48‎

These are the Euler angle outputs from the fusion algorithm.‎

You'll also see text like

Quaternion: 0.7545, 0.2937, 0.5858, -0.0356‎

These are the quaternion outputs.‎

serial_29

Euler Angles

Euler angles describe orientation (in degrees) around a single reference ‎point in three-dimensional space. ‎

Various names are employed for the three angles, but the most ‎common terminology with aircraft is Roll (x), Pitch (y) and Yaw (z). ‎

The illustration below from the Wikipedia article on Euler angles should ‎illustrate the concept clearly. You normally have both positive and ‎negative angles (-180° to 180°) depending on the direction the airplane ‎is tilted, with 0° in every direction corresponding to the airplane being ‎perfectly aligned with each axis:‎

airplane_30

The printout of data is in Yaw (Z) Pitch (Y) Roll (X) order, so if you get

Orientation: 163.00 -4.90 33.56‎

The yaw is 163 degrees, pitch is -4.90 degrees and roll isare about ‎‎33.56 degrees. The sketch will keep updating itself with the latest ‎values at whatever speed we've set in the example sketch.‎

Try twisting the sensor along each axis as printed on the sensor ‎breakout/PCB to see the numbers change from -180~180 for each axis.‎

sensor_31

WebSerial Visualizer

sensors_99

Those three numbers are fine and good, but we want to see what they ‎mean in 3D space, right? Traditionally, a Processing sketch would be ‎used to read the serial data and convert it to a 3D rotation - but thanks ‎to Web Serial API we can use any Chrome browser - a lot easier than installing ‎Processing!‎

Step 1 - Install Chrome

Start by installing the Chrome browser if you haven't yet.‎

Step 2 - Enable Web Serial API if necessary

As of Chrome 89, Web Serial is enabled by default.‎

At the time of this tutorial, you'll need to enable the Serial API, which is ‎really easy.‎

Visit chrome://flags from within Chrome. Find and enable ‎the experimental web platform features.‎

chrome_32

Restart Chrome

Step 3 - Visit the Adafruit 3D Model viewer

In Chrome, ‎visit https://adafruit.github.io/Adafruit_WebSerial_3DModelViewer/‎

Verify you have 115200 Baud selected (it only really matters for non-‎native-serial devices but might as well make sure it’s right.)‎

Click Connect

click_33

When the security window pops up, pick the matching Serial/COM port ‎for your board running the AHRS sketches. Make sure the serial port ‎isn't open in Arduino or something.‎

window_34

You'll see the serial port monitor on the bottom and a 3D bunny on the ‎top. Try rotating and twisting the sensor to see it move!‎

monitor_35

Mfr Part # 5543
ADAFRUIT LSM6DS3TR-C + LIS3MDL -
Adafruit Industries LLC
127,68 kr.
View More Details
Mfr Part # 4517
LSM6DSOX + LIS3MDL 9 DOF IMU
Adafruit Industries LLC
127,68 kr.
View More Details
Mfr Part # 4516
FEATHER NRF52840 SENSE
Adafruit Industries LLC
252,80 kr.
View More Details
Mfr Part # 4500
CLUE NRF52840 EXPRESS
Adafruit Industries LLC
287,68 kr.
View More Details
Mfr Part # 3387
LSM9DS1 ACCEL/MAG/GYRO/TEMP BRD
Adafruit Industries LLC
144,00 kr.
View More Details
Mfr Part # 4438
LSM6DSOX 6 DOF STEMMA QWIIC
Adafruit Industries LLC
76,48 kr.
View More Details
Mfr Part # 4479
TRIPLE-AXIS MAGNETOMETER LIS3MDL
Adafruit Industries LLC
63,68 kr.
View More Details
Add all DigiKey Parts to Cart
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.