SparkFun 9DoF IMU Breakout - ICM-20948 Measurement Frequency

Hi !

I’m using the ICM-20948:

https://www.sparkfun.com/products/15335

But am only able to produce 10Hz measurement (I am aiming for 100Hz minimum).

Does it make sense its so slow?

Attached is the code.

#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

//#define USE_SPI       // Uncomment this to use SPI

#define SERIAL_PORT Serial

#define SPI_PORT SPI // Your desired SPI port.       Used only when "USE_SPI" is defined
#define CS_PIN 2     // Which pin you connect CS to. Used only when "USE_SPI" is defined

#define WIRE_PORT Wire // Your desired Wire port.      Used when "USE_SPI" is not defined
#define AD0_VAL 1      // The value of the last bit of the I2C address.                \
                       // On the SparkFun 9DoF IMU breakout the default is 1, and when \
                       // the ADR jumper is closed the value becomes 0

#ifdef USE_SPI
ICM_20948_SPI myICM; // If using SPI create an ICM_20948_SPI object
#else
ICM_20948_I2C myICM; // Otherwise create an ICM_20948_I2C object
#endif

// SD CARD///
#include <Wire.h>
#include "SparkFun_Qwiic_OpenLog_Arduino_Library.h"
OpenLog myLog; //Create instance
/////////////
void setup()
{
  //SD CARD////
  
  Wire.begin(); //Initialize I2C
  myLog.begin();
  ///////////
  
  SERIAL_PORT.begin(115200);
  while (!SERIAL_PORT)
  {
  };
  
#ifdef USE_SPI
  SPI_PORT.begin();
#else
  WIRE_PORT.begin();
  WIRE_PORT.setClock(400000);
#endif

  //myICM.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial

  bool initialized = false;
  while (!initialized)
  {

#ifdef USE_SPI
    myICM.begin(CS_PIN, SPI_PORT);
#else
    myICM.begin(WIRE_PORT, AD0_VAL);
#endif

    SERIAL_PORT.print(F("Initialization of the sensor returned: "));
    SERIAL_PORT.println(myICM.statusString());
    if (myICM.status != ICM_20948_Stat_Ok)
    {
      SERIAL_PORT.println("Trying again...");
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }
}

void loop()
{

  if (myICM.dataReady())
  {
    myICM.getAGMT();         // The values are only updated when you call 'getAGMT'
                             //    printRawAGMT( myICM.agmt );     // Uncomment this to see the raw values, taken directly from the agmt structure
    printScaledAGMT(&myICM); // This function takes into account the scale settings from when the measurement was made to calculate the values with units
    
   //SD CARD///////
    myLog.syncFile();
    /////////////
  }
  else
  {
    SERIAL_PORT.println("Waiting for data");
    delay(500);
  }
}

// Below here are some helper functions to print the data nicely!

void printPaddedInt16b(int16_t val)
{
  if (val > 0)
  {
    SERIAL_PORT.print(" ");
    if (val < 10000)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 1000)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 100)
    {
      SERIAL_PORT.print("0");
    }
    if (val < 10)
    {
      SERIAL_PORT.print("0");
    }
  }
  else
  {
    SERIAL_PORT.print("-");
    if (abs(val) < 10000)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 1000)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 100)
    {
      SERIAL_PORT.print("0");
    }
    if (abs(val) < 10)
    {
      SERIAL_PORT.print("0");
    }
  }
  SERIAL_PORT.print(abs(val));
}

void printRawAGMT(ICM_20948_AGMT_t agmt)
{
  SERIAL_PORT.print("RAW. Acc [ ");
  printPaddedInt16b(agmt.acc.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.acc.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.acc.axes.z);
  SERIAL_PORT.print(" ], Gyr [ ");
  printPaddedInt16b(agmt.gyr.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.gyr.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.gyr.axes.z);
  SERIAL_PORT.print(" ], Mag [ ");
  printPaddedInt16b(agmt.mag.axes.x);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.mag.axes.y);
  SERIAL_PORT.print(", ");
  printPaddedInt16b(agmt.mag.axes.z);
  SERIAL_PORT.print(" ], Tmp [ ");
  printPaddedInt16b(agmt.tmp.val);
  SERIAL_PORT.print(" ]");
  SERIAL_PORT.println();
}

void printFormattedFloat(float val, uint8_t leading, uint8_t decimals)
{
  float aval = abs(val);
  if (val < 0)
  {
    SERIAL_PORT.print("-");
    myLog.print("-");
  }
  else
  {
    SERIAL_PORT.print(" ");
    myLog.print(" ");
  }
  for (uint8_t indi = 0; indi < leading; indi++)
  {
    uint32_t tenpow = 0;
    if (indi < (leading - 1))
    {
      tenpow = 1;
    }
    for (uint8_t c = 0; c < (leading - 1 - indi); c++)
    {
      tenpow *= 10;
    }
    if (aval < tenpow)
    {
      SERIAL_PORT.print("0");
      myLog.print("0");
    }
    else
    {
      break;
    }
  }
  if (val < 0)
  {
    SERIAL_PORT.print(-val, decimals);
    myLog.print(-val, decimals);
  }
  else
  {
    SERIAL_PORT.print(val, decimals);
    myLog.print(val, decimals);
  }
}

#ifdef USE_SPI
void printScaledAGMT(ICM_20948_SPI *sensor)
{
#else
void printScaledAGMT(ICM_20948_I2C *sensor)
{
#endif
  SERIAL_PORT.print("Scaled. Acc (mg) [ ");
  myLog.print("Scaled. Acc (mg) [ ");
  printFormattedFloat(sensor->accX(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->accY(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->accZ(), 5, 2);
  SERIAL_PORT.print(" ], Gyr (DPS) [ ");
  myLog.print(" ], Gyr (DPS) [ ");
  printFormattedFloat(sensor->gyrX(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->gyrY(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->gyrZ(), 5, 2);
  SERIAL_PORT.print(" ], Mag (uT) [ ");
  myLog.print(" ], Mag (uT) [ ");
  printFormattedFloat(sensor->magX(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->magY(), 5, 2);
  SERIAL_PORT.print(", ");
  myLog.print(", ");
  printFormattedFloat(sensor->magZ(), 5, 2);
  SERIAL_PORT.print(" ], Tmp (C) [ ");
  myLog.print(" ], Tmp (C) [ ");
  printFormattedFloat(sensor->temp(), 5, 2);
  SERIAL_PORT.print(" ]");
  myLog.print(" ]");
  SERIAL_PORT.println();
  myLog.println();
}

Thanks!

What is your main processor? Are you using it with Arduino UNO or anything else?

The ICM-20948 magnetometer is capable of 100 Hz output data rate, maximum. The gyro and accelerometer are much faster. Check the library default settings.

Printing so much extraneous information and storing data on the SD card are likely to be the limiting factor.

tepalia02:
What is your main processor? Are you using it with Arduino UNO or anything else?

Hi!

Im using sparkfun redboard

jremington:
The ICM-20948 magnetometer is capable of 100 Hz output data rate, maximum. The gyro and accelerometer are much faster. Check the library default settings.

Printing so much extraneous information and storing data on the SD card are likely to be the limiting factor.

Hi! Thanks for the reply

Viewing the readings on screen i see around 10hz for all measurements. By library default settings you mean the .cpp file? I couldn’t make sense of it. I went through the data sheet from page 11 but cant understand how to use it to set up the sample rates :frowning:

Is it possible for you to try with an Arduino mega? Sparkfun Redboard is equivalent to Arduino UNO. And I have seen many SPI devices used to run slower in Arduino UNO. Sometimes they hang. In such cases, I usually switched to Mega.

Will try. Im actually using the i2c qwiic connection.

But i understand there os a way to set the sample rate. Any ideas how?

sharonf:
Will try. Im actually using the i2c qwiic connection.

But i understand there os a way to set the sample rate. Any ideas how?

Update: i get the same sample rate with a mega :cry:

The sample rates are determined by register settings, which are described in the data sheet. You can modify those settings, but in an unfortunate break from tradition, the Sparkfun library for the ICM-20948 does not provide an easy way to do that. The library is also very poorly documented (read: not at all). I spent some time looking through the library source code and could not find the default settings.

To determine the best rate you can get, remove the SD card calls.

Unless you can get help from the Sparkfun people who put that library together, you are probably on your own. Keep in mind that the ICM-20948 is an EXTREMELY complex device.

My suggestion would be to use a different sensor, with a full-featured library that allows you to easily set those rates. Comparable results can be had from the LSM9DS1 sensor (my current favorite, does have a full featured library). The magnetometer sets the overall maximum data rate to 80 Hz for 9DOF measurements, and that is the default.

I ran this minimal example, and get a loop data update rate of 553 Hz on an Arduino Uno. No printing, no SD card.

I imagine that “old” magnetometer readings are used, as it is not capable of such a high ODR. So, this is not a valid measure of the true 9DOF update rate.

// get raw data, and time acquisition, using Sparkfun library
// Derived by SJR from
/****************************************************************
   Example1_Basics.ino
   ICM 20948 Arduino Library Demo
   Use the default configuration to stream 9-axis IMU data
   Owen Lyke @ SparkFun Electronics
   Original Creation Date: April 17 2019

   Please see License.md for the license information.

   Distributed as-is; no warranty is given.
 ***************************************************************/
#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

//#define USE_SPI       // Uncomment this to use SPI

#define SERIAL_PORT Serial

#define SPI_PORT SPI // Your desired SPI port.       Used only when "USE_SPI" is defined
#define CS_PIN 2     // Which pin you connect CS to. Used only when "USE_SPI" is defined

#define WIRE_PORT Wire // Your desired Wire port.      Used when "USE_SPI" is not defined
#define AD0_VAL 1      // The value of the last bit of the I2C address.                \
  // On the SparkFun 9DoF IMU breakout the default is 1, and when \
  // the ADR jumper is closed the value becomes 0

#ifdef USE_SPI
ICM_20948_SPI myICM; // If using SPI create an ICM_20948_SPI object
#else
ICM_20948_I2C myICM; // Otherwise create an ICM_20948_I2C object
#endif

void setup()
{

  SERIAL_PORT.begin(115200);
  while (!SERIAL_PORT)
  {
  };

#ifdef USE_SPI
  SPI_PORT.begin();
#else
  WIRE_PORT.begin();
  WIRE_PORT.setClock(400000);
#endif
  //myICM.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
  bool initialized = false;
  while (!initialized)
  {
#ifdef USE_SPI
    myICM.begin(CS_PIN, SPI_PORT);
#else
    myICM.begin(WIRE_PORT, AD0_VAL);
#endif

    SERIAL_PORT.print(F("Initialization of the sensor returned: "));
    SERIAL_PORT.println(myICM.statusString());
    if (myICM.status != ICM_20948_Stat_Ok)
    {
      SERIAL_PORT.println("Trying again...");
      delay(500);
    }
    else
    {
      initialized = true;
    }
  }
}

void loop()
{
  static int count = 0;
  static unsigned long now = millis();

  if (myICM.dataReady())
  {
    myICM.getAGMT();         // The values are only updated when you call 'getAGMT'
    count++;
    if (millis() - now > 1000UL) {
      SERIAL_PORT.println(count);
      now = millis();
      count = 0;
    }
    //    printRawAGMT( myICM.agmt );     // Uncomment this to see the raw values, taken directly from the agmt structure
  }
}

void printRawAGMT(ICM_20948_AGMT_t agmt)
{
  SERIAL_PORT.print(agmt.acc.axes.x);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.acc.axes.y);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.acc.axes.z);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.gyr.axes.x);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.gyr.axes.y);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.gyr.axes.z);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.mag.axes.x);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.mag.axes.y);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.mag.axes.z);
  SERIAL_PORT.print(", ");
  SERIAL_PORT.print(agmt.tmp.val);
  SERIAL_PORT.println();
}