Storing LIS3MDL calibration data in a Due's EEPROM

Breakout boards, sensors, other Adafruit kits, etc.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

Hello All!

I intend to perform vehicle navigation by using an Arduino microcontroller, a GPS sensor (Adafruit Ultimate GPS shield) and a pitch-roll-heading (PRH) sensor (Adafruit's LSM6DSOX + LIS3MDL board) combination. I have done some testing and found that the spherical coordinate system equations for terrestrial (Earth) navigation requires mathematical precision that the Uno and Mega processors cannot provide (specifically, very small numbers produce roundoff errors). Based upon this limitation I believe that a Due is my only Arduino microcontroller option. Yes, I have heard that Arduino is no longer manufacturing the Due.

In order to develop a robust AHRS (Attitude and Heading Reference System), I need vehicle heading data in a usable format (i.e., units in degrees, not Tesla) and for that data to be smoothed/filtered. To do that I want to use one of the filters mentioned in the Adafruit AHRS Overview (Mahony, Madgwick, or NXP Sensor Fusion). To do that I need to store sensor calibration data in the processor’s EEPROM memory. To do that I need to be able to read and write to the EPROM memory.

The Adafruit Calibration Pre-Check instructions, found here: https://learn.adafruit.com/how-to-fuse- ... -pre-check, discuss the process of reading/writing to EEPROM memory. My first clue that there would be a calibration data storage problem was found in the very first paragraph where the Arduino Due is not listed.

Additionally, the sensor_calibration_read example sketch has the following:

Code: Select all

#include "Adafruit_Sensor_Calibration.h"

// select either EEPROM or SPI FLASH storage:
#ifdef ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM
  Adafruit_Sensor_Calibration_EEPROM cal;
#else
  Adafruit_Sensor_Calibration_SDFat cal;
#endif
….
Looking at the code in the Adafruit_Sensor_Calibration.h file also indicates a problem because it, too, omits the Due from consideration:

Code: Select all

#ifndef __ADAFRUIT_SENSOR_CALIBRATION_H__
#define __ADAFRUIT_SENSOR_CALIBRATION_H__

#include <Adafruit_Sensor.h>

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega32U4__) ||              \
    defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(ESP32) ||       \
    defined(TEENSYDUINO)
#define ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM
#else
#define ADAFRUIT_SENSOR_CALIBRATION_USE_SDFAT
#endif
….
So now when I do run the sensor_calibration_read example sketch I get the following output indicating a fail:

Code: Select all

Calibration filesys test
Failed to initialize calibration helper
I suspect this failure is due to the Adafruit_Sensor_Calibration.h not listing the Due for consideration, thus not setting ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM to True, and thus terminating the sketch.

So my questions are these:
  • Does the Arduino Due have EEPROM memory?
  • Can the Arduino Due store LIS3MDL sensor calibration data in its EEPROM memory?
  • How can the Adafruit_Sensor_Calibration.h file be re-written to enable the Due to store LIS3MDL sensor calibration data in EEPROM memory?
  • Has someone already accomplished this some other way?

User avatar
adafruit_support_bill
 
Posts: 88087
Joined: Sat Feb 07, 2009 10:11 am

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by adafruit_support_bill »

The DUE does not have EEPROM. It does have the ability to write to the Flash, although it is a bit awkward since you need to write in whole pages. You can read some discussion on that here: https://forum.arduino.cc/t/eeprom/125852/2

Another approach is to use an external EEPROM board: https://www.adafruit.com/product/5146

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

I greatly appreciate Adafruit_support_bill's information below. Unfortunately, the recommendations in the links seem to require a lot more computer programming skill than I possess. I think, however, I have found a solution that may be useful to the layperson like me. My quest began with this comment posted on Adafruit's Learning page that Bill recommended:
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 its easy to lose the calibration, but it's always an option!
However there is nothing further in that regard. On a separate forum post regarding sensor drift, sj_remington recommended to me that I not only re-perform my sensor calibration but also that I consider hardcoding the sensor input in order to perform a quality test. That got me to thinking that calibration data could be retrieved from hardcode input vice being retrieved from the EEPROM and used in the calculations. For many folks that may be a suitable option. Sure... it's not optimal but it may be good enough if it works. For a Due, however, it is required.

The first thing to start with is to perform a good calibration. Initially, I used an Arduino Uno and the MotionCal software tool to capture the calibration data as specified in many posts by others. Unfortunately, I **thought** I had captured a suitable calibration but it appears that my effort was not good enough. Another calibration effort provided the following and using this new calibration data I was able to eliminate what I thought to be 'drifting' sensor output.

MotionCal_Calibrated_Sensor_Data-20221231.png
MotionCal_Calibrated_Sensor_Data-20221231.png (162.45 KiB) Viewed 384 times

My further recommendation, but not one that I have extensively tested, is that the calibration values displayed on the MotionCal screen should be used in your sketch while MotionCal's Send function (which is supposed to write the calibration data into the device's EEPROM) should not be relied upon. FYI, it took me about 30 minutes to get the values (Gaps, Variance, Wobble, and Fit Error) but your mileage may vary. Get a good screenshot of your calibration and save that for reference.

The next step is to edit a few files to enable hardcoding the calibration data and bypassing any reliance upon EEPROM memory reading/writing functions - which is required if you are going to use an Arduino Due. The first place to start is the Libraries->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration_EEPROM.cpp file where the offsets[] variables are replaced with your calibration data. Here is my edited file but you will need to edit this file with your specific sensor's calibration data.

Code: Select all

// This file was drawn from the Examples->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration_EEPROM.cpp
// file provided with the Arduino IDE.  It was then extensively modified to hardcode sensor calibration data
// obtained from the MotionCal software tool that reflects successful LSM6DSOX_LIS3MDL sensor calibration.
// Actions were disabled which attempt to read or write to the EEPROM.

#include "Adafruit_Sensor_Calibration.h"

#if defined(ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM)

/**************************************************************************/
/*!
    @brief Initializes Flash and filesystem
    @returns False if any failure to initialize flash or filesys
*/
/**************************************************************************/
bool Adafruit_Sensor_Calibration_EEPROM::begin(uint8_t eeprom_addr) {
  Serial.println(F("Begin"));
/*
  ee_addr = eeprom_addr;

#if defined(ESP8266) || defined(ESP32)
  EEPROM.begin(512);
#endif
*/
  return true;
}

bool Adafruit_Sensor_Calibration_EEPROM::saveCalibration(void) {
  Serial.println(F("Save Cal"));

/*
  uint8_t buf[EEPROM_CAL_SIZE];
  memset(buf, 0, EEPROM_CAL_SIZE);
  buf[0] = 0x75;
  buf[1] = 0x54;

  float offsets[16];
  memcpy(offsets, accel_zerog, 12);       // 3 x 4-byte floats
  memcpy(offsets + 3, gyro_zerorate, 12); // 3 x 4-byte floats
  memcpy(offsets + 6, mag_hardiron, 12);  // 3 x 4-byte floats

  offsets[9] = mag_field;

  offsets[10] = mag_softiron[0];
  offsets[11] = mag_softiron[4];
  offsets[12] = mag_softiron[8];
  offsets[13] = mag_softiron[1];
  offsets[14] = mag_softiron[2];
  offsets[15] = mag_softiron[5];

  memcpy(buf + 2, offsets, 16 * 4);

  uint16_t crc = 0xFFFF;
  for (uint16_t i = 0; i < EEPROM_CAL_SIZE - 2; i++) {
    crc = crc16_update(crc, buf[i]);
  }
  Serial.print("CRC: ");
  Serial.println(crc, HEX);
  buf[EEPROM_CAL_SIZE - 2] = crc & 0xFF;
  buf[EEPROM_CAL_SIZE - 1] = crc >> 8;

  for (uint16_t a = 0; a < EEPROM_CAL_SIZE; a++) {
    EEPROM.write(a + ee_addr, buf[a]);
  }

#if defined(ESP8266) || defined(ESP32)
  EEPROM.commit();
#endif
*/
  return true;
}

bool Adafruit_Sensor_Calibration_EEPROM::loadCalibration(void) {
  Serial.println(F("Load Cal"));
/*
  uint8_t buf[EEPROM_CAL_SIZE];

  uint16_t crc = 0xFFFF;
  for (uint16_t a = 0; a < EEPROM_CAL_SIZE; a++) {
    buf[a] = EEPROM.read(a + ee_addr);
    crc = crc16_update(crc, buf[a]);
  }

  if (crc != 0 || buf[0] != 0x75 || buf[1] != 0x54) {
    Serial.print("CRC: ");
    Serial.println(crc, HEX);
    return false;
  }
*/

  float offsets[16];
/*
  memcpy(offsets, buf + 2, 16 * 4);

  accel_zerog[0] = offsets[0];
  accel_zerog[1] = offsets[1];
  accel_zerog[2] = offsets[2];

  gyro_zerorate[0] = offsets[3];
  gyro_zerorate[1] = offsets[4];
  gyro_zerorate[2] = offsets[5];

  mag_hardiron[0] = offsets[6];
  mag_hardiron[1] = offsets[7];
  mag_hardiron[2] = offsets[8];

  mag_field = offsets[9];

  mag_softiron[0] = offsets[10];
  mag_softiron[1] = offsets[13];
  mag_softiron[2] = offsets[14];
  mag_softiron[3] = offsets[13];
  mag_softiron[4] = offsets[11];
  mag_softiron[5] = offsets[15];
  mag_softiron[6] = offsets[14];
  mag_softiron[7] = offsets[15];
  mag_softiron[8] = offsets[12];
*/
  // The below data was obtained using the MotionCal software tool.
  // Edit the below to suit your device's calibration.
  // Do not rely upon MotionCal to transfer data to your device.
  // Recommend capturing a screen shot of MotionCal's screen and using those values.
  accel_zerog[0] = 0.0;
  accel_zerog[1] = 0.0;
  accel_zerog[2] = 0.0;

  gyro_zerorate[0] = 0.0;
  gyro_zerorate[1] = 0.0;
  gyro_zerorate[2] = 0.0;

  mag_hardiron[0] = -41.84;
  mag_hardiron[1] =  23.76;
  mag_hardiron[2] = -29.44;

  mag_field = 45.71;

  mag_softiron[0] =  0.993;
  mag_softiron[1] =  0.043;
  mag_softiron[2] = -0.014;
  mag_softiron[3] =  0.043;
  mag_softiron[4] =  1.019;
  mag_softiron[5] =  0.000;
  mag_softiron[6] = -0.014;
  mag_softiron[7] =  0.000;
  mag_softiron[8] =  0.991;

  return true;
}

bool Adafruit_Sensor_Calibration_EEPROM::printSavedCalibration(void) {
  Serial.println(F("Print saved cal"));
  Serial.println(F("------------"));
/*
  for (uint16_t a = ee_addr; a < ee_addr + EEPROM_CAL_SIZE; a++) {
    uint8_t c = EEPROM.read(a);
    Serial.print("0x");
    if (c < 0x10)
      Serial.print('0');
    Serial.print(c, HEX);
    Serial.print(", ");
    if ((a - ee_addr) % 16 == 15) {
      Serial.println();
    }
  }
*/
  Serial.println(F("\n------------"));
  return true;
}

#endif
Once you do that you will need to lightly edit the Libraries->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration_EEPROM.h file. Editing this file is easy... just comment out the reference to including EEPROM.h (i.e., make it: // #include <EEPROM.h>). Commenting this line out worked for me... and I couldn't find it in any of my other library files... so I hope I am not committing a significant faux pas by commenting it out.

The next step is to edit the Libraries->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration.h file. Specifically, all you need to do is make this definition block define only one option. Since the Arduino Due is not specifically identified in the #if line you need to spoof the logic that determines where there is, or is not, EEPROM memory storage capability in your microprocessor. Here is the edited code that I used (which you should easily be able to find and edit yourself):

Code: Select all

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega32U4__) ||              \
    defined(__AVR_ATmega2560__) || defined(ESP8266) || defined(ESP32) ||       \
    defined(TEENSYDUINO)
#define ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM
#else
#define ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM
// #define ADAFRUIT_SENSOR_CALIBRATION_USE_SDFAT
#endif
If you edit these three files you should then be able to use an Arduino Due to perform the Examples->Adafruit_AHRS->calibrated_orientation->calibration_orientation.ino sketch. Using that sketch enables you to use any of the three filters in order to determine a good Heading that compensates for tilt (I have only used the Mahoney filter so far). I EXTENSIVELY modified that sketch and I offer it to you below for your consideration.

Code: Select all

// This sketch is loosely based upon the Arduino Examples-> Adafruit AHRS-> calibrated_orientation sketch.
// The purpose of this modification is to capture data to explore the effects of Mahoney filter calculations on different microprocessors.
// Two instances of simulated 9DOF sensor data are injected into the data stream in order to control the test's input.
// The output data was formatted as a .csv in order to utilize spreadsheet graphical tools and analysis.
//
// This sketch injects 'hardcoded' static LSM6DSOX and LIS3MDL sensor data but data from any other similar sensor can be used.
// Doing so will reveal whether observed Heading 'drift' is due to the sensor or calculations and
// whether or not the microprocessor / sensor / filter combination provides suitable, timeley results.
//
// This sketch also relies upon a customized version of the
// Examples->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration_EEPROM.cpp where calibration sensor
// data obtained from the MotionCal software tool is specified in the sketch vice relying upon EEPROM
// memory.  While the Arduino Uno and Mega have EEPROM memory the Due dues not.  Thus, this modification
// enables a standardized comparison between the three microprocessors regardless of EEPROM capability.
//
// Additionally, the Examples->Adafruit_Sensor_Calibration->Adafruit_Sensor_Calibration.h file was slightly
// modified in order to force definition of ADAFRUIT_SENSOR_CALIBRATION_USE_EEPROM vice providing an option for
// ADAFRUIT_SENSOR_CALIBRATION_USE_SDFAT.
//
// Sketch output was modified in order to produce .csv utility.
//
// Full orientation sensing using NXP/Madgwick/Mahony and a range of 9-DoF sensor sets.
// You *must* perform a magnetic calibration before this code will work.
//
// To view this data, use the Arduino Serial Monitor to watch the
// scrolling angles, or run the OrientationVisualiser example in Processing.
// Based on  https://github.com/PaulStoffregen/NXPMotionSense with adjustments to Adafruit Unified Sensor interface

#include <Adafruit_Sensor_Calibration.h>
#include <Adafruit_AHRS.h>

Adafruit_Sensor *accelerometer, *gyroscope, *magnetometer;

// Specify the combo 9-DoF (pick only one)
#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

// Specify the filter (pick only one) (slower = better quality output)
//Adafruit_NXPSensorFusion filter; // slowest
//Adafruit_Madgwick filter;        // faster than NXP
Adafruit_Mahony filter;            // fastest/smalleset

// Specify the calibration library with calibration data hardcoded into it.
Adafruit_Sensor_Calibration_EEPROM cal;

// Specify the filter update frequency
#define FILTER_UPDATE_RATE_HZ 100

// Declare the timer-related variables
unsigned long StartTime0;     // Used to determine test duration elapsed time, in milliseconds
unsigned long StartTime1;     // Used to determine filter update elapsed time, in milliseconds
unsigned long StartTime2;     // Used to determine data recording elapsed time, in milliseconds
unsigned long ElapsedTime0;   // Used to calculate elapsed time, in milliseconds
unsigned long ElapsedTime1;   // Used to calculate elapsed time, in milliseconds
unsigned long ElapsedTime2;   // Used to calculate elapsed time, in milliseconds
unsigned long TestDuration = 480 * 1000;   // Defines testing duration in seconds (480 seconds = 8 minutes), in milliseconds
unsigned long IntervalDuration = 500; // Defines data recording interval in seconds, in milliseconds

void setup()
{
  // Initialize serial port to enable Arduino IDE Serial Monitor
  Serial.begin(115200);
  while (!Serial)
  {
    delay(100);
  }
  Serial.println("\n\nCalibrated Orientation - Two orientation Mahoney filter response test.\n");

  if (!cal.begin())
  {
    Serial.println("Failed to initialize calibration helper");
    while(true) delay(100);
  }
  else
  {
    if (! cal.loadCalibration())
    {
      Serial.println("No calibration loaded/found");
      while(true) delay(100);
    }
    else
    {
      if (!init_sensors())
      {
        Serial.println("Failed to find sensors");
        while(true) delay(100);
      }
      else
      {
        // Obtain sensor details and print them out.
        accelerometer->printSensorDetails();
        gyroscope->printSensorDetails();
        magnetometer->printSensorDetails();

        // Initialise sensor
        setup_sensors();
        filter.begin(FILTER_UPDATE_RATE_HZ);

        Wire.setClock(400000); // 400KHz

        // Print header for .csv utility
        Serial.println("Time, Orientation, ,");
        Serial.println("(seconds), Heading, Pitch, Roll");

        // Start timers
        StartTime0 = millis();    // This is the total execution time timer start
        StartTime1 = StartTime0;  // This is the filter refresh cycle timer start
        StartTime2 = StartTime0;  // This is the sensor data print cycle timer start
      }
    }
  }
}

void loop()
{
  float heading, bearing, roll, pitch;
  float gx, gy, gz;

  //  INPUT DATA: The Examples->Adafruit AHRS->calibration sketch was used to capture the following data:
  /*
      REFERENCE ORIENTATION - 110 degrees - 20220102
      Adafruit AHRS - IMU Calibration!
      Calibration filesys test
      Begin
      Load Cal
      Loaded existing calibration
      ------------------------------------
      Raw:159,-170,8255,2,2,-16,-458,358,-674
      Uni:0.19,-0.20,9.88,0.0023,0.0023,-0.0183,-45.89,35.87,-67.47


      TEST ORIENTATION - 250 degrees - 20220102
      Adafruit AHRS - IMU Calibration!
      Calibration filesys test
      Begin
      Load Cal
      Loaded existing calibration
      ------------------------------------
      Raw:-13,21,8261,2,3,-16,-468,115,-666
      Uni:-0.02,0.03,9.88,0.0029,0.0037,-0.0180,-46.86,11.52,-66.66
  */

  ElapsedTime0 = millis() - StartTime0;
  ElapsedTime1 = millis() - StartTime1;
  ElapsedTime2 = millis() - StartTime2;

  // Ensure program execution is within testing timeframe
  if(ElapsedTime0 <= (2 * TestDuration) + IntervalDuration)
  {
    // Ensure enough time is allocated for filter calculations
    if (ElapsedTime1 >= 1000 / FILTER_UPDATE_RATE_HZ)
    {
      // Recieve data from the motion sensors
      sensors_event_t accel, gyro, mag;

      accelerometer->getEvent(&accel);
      gyroscope->getEvent(&gyro);
      magnetometer->getEvent(&mag);

      //  The following replaces the received sensor data with defined data
      //  For the first 480 seconds, use the data obtained where X faces North
      //  For the second 480 seconds, use the data obtained where X faces West
      //  Terminate at 960 seconds (16 minutes)

/*
      // Overwrite sensor data with specified test data.  See comments above for data source information.
      // This if/else section may be commented out in order to utilize 'live' sensor data captured above.
      if(ElapsedTime0 <= (1 * TestDuration) + IntervalDuration)
      {
        // Specify data captured in the reference orientation.
        // Uni:0.19,-0.20,9.88,0.0023,0.0023,-0.0183,-45.89,35.87,-67.47
        accel.acceleration.x =   0.19;
        accel.acceleration.y =  -0.20;
        accel.acceleration.z =   9.88;
        gx =                     0.0023;
        gy =                     0.0023;
        gz =                    -0.0183;
        mag.magnetic.x =       -45.89;
        mag.magnetic.y =        35.87;
        mag.magnetic.z =        -6.47;
      }
      else
      {
        // Specify data captured in the test orientation.
        // Uni:-0.02,0.03,9.88,0.0029,0.0037,-0.0180,-46.86,11.52,-66.66
        accel.acceleration.x =  -0.02;
        accel.acceleration.y =   0.03;
        accel.acceleration.z =   9.88;
        gx =                     0.0029;
        gy =                     0.0037;
        gz =                    -0.0180;
        mag.magnetic.x =       -46.86;
        mag.magnetic.y =        11.52;
        mag.magnetic.z =       -66.66;
      }
*/

      // Perform calibration on the sensor data
      cal.calibrate(mag);
      cal.calibrate(accel);

      cal.calibrate(gyro);
      // Gyroscope needs to be converted from Rad/s to Degree/s
      gx = gyro.gyro.x * SENSORS_RADS_TO_DPS;
      gy = gyro.gyro.y * SENSORS_RADS_TO_DPS;
      gz = gyro.gyro.z * SENSORS_RADS_TO_DPS;

      // Update the SensorFusion filter
      filter.update(gx, gy, gz,
                    accel.acceleration.x, accel.acceleration.y, accel.acceleration.z,
                    mag.magnetic.x, mag.magnetic.y, mag.magnetic.z);

       StartTime1 = millis(); // Reset the timer associated with the FILTER_UPDATE_RATE_HZ calculation

      // Ensure program output is on specified schedule
      if(ElapsedTime2 >= IntervalDuration)
      {
        // Calculate the heading, bearing, pitch and roll
        heading = filter.getYaw();

        bearing = heading + 180.0;
        while(bearing >= 360.0)
        {
          bearing = bearing - 360.0;
        }

        bearing = 360.0 - bearing;
        while(bearing < 0.0)
        {
          bearing = bearing + 360.0;
        }

        pitch = filter.getPitch();
        roll = filter.getRoll();

        Serial.print(float(ElapsedTime0) / 1000.0);
        Serial.print(", ");
        Serial.print(bearing);
        Serial.print(", ");
        Serial.print(pitch);
        Serial.print(", ");
        Serial.println(roll);

        StartTime2 = millis(); // Reset the timer associated with the data output
      }
      else
      {
        ;
      }
    }
    else
    {
      ;
    }
  }
  else
  {
    Serial.println("\n\n\nProgram execution complete.\n\n");
    while(true) delay(100);
  }
}
I then used the above code to conduct a comparison between how the Mahoney filter calculations perform when fed hardcoded data (which eliminates sensor input) versus 'live' data (but using a static/immobile sensor). I started the test with an 8-minute stabilization period and then either simulated or physically moved the sensor to a new position for an additional 8 minutes. The resulting curves were extremely different but both curves did allay my initial concerns that I had a 'drifting' sensor.

Sensor_Data_Response_Test-20230102.jpg
Sensor_Data_Response_Test-20230102.jpg (582.53 KiB) Viewed 384 times

There are several conclusions that can be drawn from the above graphic but that is **not** the point of my post on this forum. My intent was to enable the capability for me to use my Arduino Due to use the Mahoney filter to get high quality Heading data and this required bypassing the EEPROM data storage limitation found in the various Adafruit codes. Having found a way to do so I am sharing my method with everyone for their consideration.

User avatar
adafruit_support_bill
 
Posts: 88087
Joined: Sat Feb 07, 2009 10:11 am

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by adafruit_support_bill »

Thanks for posting your results. The down-side of hard-coding calibration data is that the sensors do tend to drift a bit with temperature changes. And magnetometers are sensitive to local variations in the surrounding magnetic field. But your solution is probably sufficient for use in a stable environment.

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

When all was said and done... I got quite frustrated with the calibration procedure, the inability to store sensor calibration data in the Due's EEPROM, and the expectation that the calibration would become 'lost' over time. Such is not acceptable for an autonomous vehicle that is expected to operate for extended periods of time.

What I have settled upon is a new AHRS sensor: the Adafruit 9-DOF Absolute Orientation IMU Fusion Breakout - BNO055 Product ID: 2472. It may be a discontinued item here at Adafruit... but it's self-calibration capability is the answer to my problems at this time. All I have to do is power it up, shake it around a bit, and it self-identifies magnetic north with consistent reliability.

I hope this helps others that are experiencing the same concerns.

User avatar
gammaburst
 
Posts: 1015
Joined: Thu Dec 31, 2015 12:06 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by gammaburst »

Don't get too excited by the BNO055.
It has been discontinued by Bosch. Distributors still have stock.
In fusion mode, its self-calibration algorithm is rather awful, and can't be adjusted or disabled.
The fusion algorithm is prone to significant pitch/roll drift during steady acceleration (like in a car).
Its Euler angles are broken (quaternion is fine).
Many many discussion threads in this forum going back 7+ years.

Try the BNO085 instead.
It does pretty much everything better, except its communication protocol is way over-complicated.
It immediately orients itself after power-up without having to twirl it around.
Plenty of discussion threads here.

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

For a sailboat application, there will never be a steady acceleration condition. Additionally, pitch and roll errors are not important to me at this time. The only thing that is needed is a reliable magnetic compass heading - even if the heading error is only accurate to +/-10 degrees. I am trying to avoid the "perfection is the enemy of good enough" condition and the heading 'drift' condition I encountered earlier (due to imperfect calibration).

I will check out the BNO085 and I GREATLY appreciate your recommendation. This near-term project using the BNO055 is for a prototype effort so switching to the improved BNO085 on a subsequent system is an option. MANY thanks for your post.

User avatar
gammaburst
 
Posts: 1015
Joined: Thu Dec 31, 2015 12:06 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by gammaburst »

Folks with cars, boats, and airplanes have all observed the BNO055's drift problem.
I haven't seen it because I generally use these sensors in non-moving applications.
I'm guessing the BNO055's auto-calibration chooses unfortunate moments to recalibrate itself, such as during acceleration, and that messes-up its internal gravity vector. I'm not sure but it may also occur during a gradual turn, that's acceleration too.
I think the BNO055's algorithm was tuned for use in a VR headset.

Here's the original LONG discussion about BNO055 calibration, drift, etc:
viewtopic.php?t=78459

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

Going to show off for just a moment. I was able to put together an autonomous motorboat using some Adafruit products, namely the Ultimate GPS shield, external GPS antenna and a BNO055 Absolute Orientation Sensor. The GPS shield worked as expected although I did have problems with the SD card logger. Note that problem was my SD card and NOT anything related to the shield. The external GPS antenna's 26db increase in signal made for very accurate positioning.

The BNO055 worked out well enough. As noted in field testing, the vehicle was able to get a very good azimuth reading in order to navigate quite accurately. It did, however, have problems retaining accelerometer calibration but that did NOT negatively impact getting a good enough compass reading for my purposes. Next time, however, I will take gammaburst's advice and use a BNO085.

While the Arduino Mega 2560 worked out well for this motorboat application I will try an Adafruit Metro M4 Express microcontroller for the next project - an autonomous sailboat. The M4 is much faster than I could possibly need and has all of the flash and RAM that I could hope for.

YouTube video for those that might be interested:

An Autonomous Motorboat
https://youtu.be/d9PTZ8AIAEw

THANKS for everyone's helpful suggestions.

User avatar
sj_remington
 
Posts: 997
Joined: Mon Jul 27, 2020 4:51 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by sj_remington »

You can be proud of that great run! All good fun.

Some years back I used the BNO055 as a compass, along with GPS on a very small autonomous, twin screw motorboat. The MCU created waypoints on the fly, programmed to drive in a large triangle. The C code fit comfortably into a 16Kb ATmega168 from Pololu.

Here's video of one run: http://www.uoxray.uoregon.edu/orangutan/boat2.mp4

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

sj_remington:

Very cool boat. I see that you used an LCD screen for I/O. Brilliant. I also noted the twisted wires. Again, brilliant. I must say that you are pretty gutsy to run your boat without any type of deck to protect it from swamping. The twin-screw arrangement sure made for some tight turns at each corner of the triangle. And you are right, that is a quite small craft.

Thanks for sharing. It is cool to see original designs and learn from how other people do things. Your LCD screen idea is one that I will adopt on future projects.

User avatar
sj_remington
 
Posts: 997
Joined: Mon Jul 27, 2020 4:51 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by sj_remington »

That garage-sale boat has a bilge pump built in, running off the starboard screw shaft.

The controller is a Pololu Orangutan, which very conveniently had the LCD screen, three push buttons and a dual motor driver on board. It seems Pololu has discontinued that product line, though.

User avatar
gammaburst
 
Posts: 1015
Joined: Thu Dec 31, 2015 12:06 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by gammaburst »

Oh, these are model boats!
I had been imagining a full-size craft sailing or jetting around a lake.
Fun projects. Nice to see successful results.

User avatar
Autonomous_Sailboat
 
Posts: 31
Joined: Thu Dec 22, 2022 10:26 pm

Re: Storing LIS3MDL calibration data in a Due's EEPROM

Post by Autonomous_Sailboat »

gammaburst, sj_remington, adafruit_support_bill:

OK, I am going to take your advice and 'upgrade' from a BNO055 to a BNO085 sensor (https://www.adafruit.com/product/4754) in order to obtain/use magnetic compass data for my next project... an autonomous sailboat. Yes, gammaburst, this will yet another model-sized project. :-)

Before I plunk down the money for a couple of sensors I want to ensure that the '85 has not been superseded by another (better) option. To your knowledge, is there yet another AHRS sensor besides the BNO055 and BNO085 that I should consider? The performance priorities are (in descending order):
[1] Self-calibration capability
[2] Stable performance
[3] Measurement accuracy

The requirement for a self-calibration capability rules out several of the AHRS sensors that I had initially considered. The vehicle's long-distance autonomous operation requires stable and reliable sensor performance. Measurement accuracy of +/-5 degrees would be sufficient for this vehicle's navigation.

Locked
Please be positive and constructive with your questions and comments.

Return to “Other Products from Adafruit”