Mahony AHRS Arduino code for LSM9DS1 breakout

Working AHRS Arduino code has been posted for the LSM9DS1 breakout, based on the Madgwick/Mahony filter. I also posted code for a tilt-compensated compass and detailed instructions and code for the most accurate 3D accel/magnetometer calibration. The AHRS fusion code requires the Sparkfun LSM9DS1 library. With default settings, the update rate is 80Hz on a 16 MHz Arduino Pro Mini.

This sensor is by far the best performing one I have yet encountered, among the consumer grade IMUs.

I used the Adafruit LSM9DS1 breakout for the development as it has on board level shifters for 3V and 5V processors, but it should work perfectly with the Sparkfun breakout and a 3.3V processor.

Download at https://github.com/jremington/LSM9DS1-AHRS

Note: The X, Y and Z accel/gyro axes DO NOT form a right handed system, as stated in the Sparkfun breakout board product description. This is corrected in the code.

Happy to entertain questions!

Hi Jim,

Thank you for your code. I am especially interested in the tilt-compensated example.

I had hoped that the BNO080 would provide a turnkey solution to achieve this same functionality, but it has proven to be very difficult to work with.

Once SparkFun has the LSM9DS1 back in stock, I’ll pick up a few sensors specifically to test with your code.

Cheers,

Adam

I gave up on the BNO055 because the (critical) sensor calibration is undocumented, doesn’t work well and can’t be turned off, so I did not bother to look at the BNO080.

What sort of difficulties did you have with the BNO080?

Hi Jim,

I do recall many of your comments about the BNO055 over the past few years.

The main issue with the BNO080 is I2C-related. I believe clock stretching is ultimately to blame. The sensor effectively does not work with SAMD or Artemis-based boards. This seems to be a common problem throughout the various communities (Adafruit, SparkFun, Teensy, ESP, Arduino, etc.).

Other issues I’ve encountered include not being able to place the BNO080 to sleep (minimum quiescent current is ~7 mA), and not being able to achieve absolute orientations when power cycling the sensor (i.e. inconsistent relative values upon wake).

For your tilt-compensated example, I assume power cycling or placing the sensor to sleep won’t be an issue once calibrated?

Cheers,

Adam

I have not tried to use sleep modes with the LSM9DS1, but neither sleeping nor power cycling should affect calibration (sleeping does not affect LSM303D sensors).

I’ve recalibrated one LSM9DS1 several times between power on/off periods, and the results are consistent with expected statistical variations.

Hi Jim,

So apparently, there’s a global supply shortage of the LSM9DS1! I’m curious, can you recommend any other IMUs that you’ve had good experiences with in the past?

I design and deploy [ open-source iceberg tracking beacons in the Canadian Arctic and have previously used the LSM303 and Pololu library to measure pitch, roll and tilt-compensated (magnetic) heading.

I’m currently looking for a suitable replacement and have been exploring the BNO080 and ICM-20948. However, due to various issues, neither are suitable for my application. Keen to know if you have any thoughts.

Cheers,

Adam](ice-tracking-beacon/Archive/v1.0 at main · cryologger/ice-tracking-beacon · GitHub)

In my hands the MinIMU-9 V3 works about as well as the LSM9DS1 and the V5 even better. There are plenty of both in stock at Pololu, e.g. https://www.pololu.com/product/2738

Tilt compensated compass code for the V3 is here: https://github.com/jremington/AltIMU-AHRS

For the V5, which has the LSM6 accel/gyro and the LIS3MDL magnetometer, tilt compensated compass code is at the end of this thread: https://forum.pololu.com/t/correcting-t … eter/14315

Thanks Jim,

I had a Pololu shopping cart ready to go, so I’ll toss in a couple of MinIMU-9 V5 to test out. I’m a little concerned about the power consumption, but I suspect I’ll just need to cut a trace or two. For the older Pololu LSM303D I had to remove the regulator entirely.

Cheers,

Adam

FYI, I have tested this adafruit breakout: https://www.adafruit.com/product/4517 with the AHRS code https://github.com/adafruit/Adafruit_AHRS (Kalman filter version) on the Artemis processor: https://github.com/jerabaul29/Artemis_M … L_AHRS.ino . It seems to work like a charm:

/*

testing on SFE Artemis Redboard with burst mode for the Kalman filtering:

I2C took 2915 us

Update took 676 us

Raw acc: -2.4736, -3.9248, 8.8959, | gyr: 0.0000, -0.1487, -0.3062, | mag: 23.4142, -7.2201, -25.2412

Orientation: 44.10, 14.35, -23.77

Quaternion: 0.8902, -0.2353, 0.0365, 0.3883

Printing took 4694 us


testing without burst mode:

I2C took 2917 us

Update took 1234 us

Raw acc: -2.3982, -3.7908, 8.9773, | gyr: 0.2450, -0.2800, -0.3325, | mag: 23.0780, -7.0155, -25.5481

Orientation: 43.27, 13.82, -22.90

Quaternion: 0.8956, -0.2266, 0.0369, 0.3809

Printing took 4729 us

*/

I think I will use this instead of the BNO08x that is proving to be a pain to talk to. I am also waiting for a couple of these https://www.adafruit.com/product/4569 but bought from digikey as they had them in stock, since they may have even higher performance (?).

Wondering if this may be a better way to go actually: simple, low power 9-dof sensor, with the Kalman filtering being done on the “over powerful” and low power Artemis.

Hi Jim,

I’ve been reviewing your Balboa tilt-compensated heading code for the LIS3MDL. Would the calibration and heading code also be suitable for use with the [LSM303AGR, which has the improved LIS2MDL magnetometer?

For my application, I really only need 6DOF, so the LSM303AGR could be a simple solution. For the past few years, I’ve relied on the Pololu LSM303 library for tilt-compensated heading but it looks like I’ll need to calculate this manually if I switch to the LIS2MDL.

Cheers,

Adam](Adafruit LSM303AGR Accelerometer Magnetometer - STEMMA QT Qwiic : ID 4413 : Adafruit Industries, Unique & fun DIY electronics and kits)

The tilt compensated compass code needs only offset and scale corrected accelerometer and magnetometer data, in a right handed coordinate system, so if you can cobble together something together to get the raw data out of that unit, fine! It will take some study of the data sheet.

I glanced through the data sheet and it appears that the X, Y, Z coordinate system of the LSM303AGR is left handed, so one of the axes (e.g. Z) has to be inverted for each sensor.

The late model accelerometers I’ve tested recently are very well calibrated at the factory, so it is likely that only the magnetometer needs to be calibrated.

Thanks, Jim!

STMicroelectronics has a [library available through the Arduino IDE for the LSM303AGR that allows access to the raw data.

I also have an LSM6DS33+LIS3MDL sensor on hand, so I can try to reproduce your results with the Balboa code.

Ultimately, I’m after reliability and simplicity, so happy to use whichever sensor checks both those boxes.

Cheers,

Adam](GitHub - stm32duino/LSM303AGR: Arduino library to support the LSM303AGR 3D accelerometer and 3D magnetometer)

Hi Jim,

I performed the magnetometer calibration outlined in the Balboa example with the LSM6DS33 + LIS3MDL and had good success (see screenshots below). The only issue I encountered was needing to remove <malloc.h> when compiling magneto.c, as it is deprecated on macOS. It can be replaced with <stdlib.h>.

For pitch and roll, I’ve been using the formulas provided by Kevin at Pololu. I assume they should be equally suitable with your tilt-compensated compass code?

float pitch = atan2(-imu.a.x, sqrt((int32_t)imu.a.y * imu.a.y + (int32_t)imu.a.z * imu.a.z));
float roll = atan2(imu.a.y, imu.a.z);

I do notice the LSM6DS33 + LIS3MDL is a bit noisy, so I’ll look perhaps at filtering the data, and/or exploring how the other sensors I have on hand perform using the same calibration procedure.

Cheers,

Adam

Raw:


Corrected:

The following equations assume that Z is roughly vertical and must be revised for other standard orientations, whereas the tilt compensated compass codes makes no such assumption. I would replace the (int32_t) cast with (float).

float pitch = atan2(-imu.a.x, sqrt((int32_t)imu.a.y * imu.a.y + (int32_t)imu.a.z * imu.a.z));
float roll = atan2(imu.a.y, imu.a.z);