ICM-20948 Magnetometer Rate Issues


Well-known member
I've been working on updating my InvenSense IMU library to include support for the ICM-20948. It is located here in the "icm-dev" branch:

The sensor has a 6 DOF IMU and a 3 DOF mag all on the same die. You can access the magnetometer either using an external I2C interface in the IMU or by placing the IMU into I2C passthrough mode and then accessing the mag directly from the microcontroller. My preference is to use the external I2C interface in the IMU, since this enables accessing the magnetometer data even when the microcontroller to IMU interface is SPI.

The issue I'm running into is that the magnetometer sample rate is not behaving how I would expect it to. To ease debugging, in my ConfigSrd method, I always set the magnetometer to a desired output rate of 10 Hz (https://github.com/bolderflight/invensense-imu/blob/icm-dev/src/icm20948.cpp#L229) and I request 9 bytes from the magnetometer starting at the status byte.

In my Read method (https://github.com/bolderflight/invensense-imu/blob/icm-dev/src/icm20948.cpp#L417), I check the magnetometer byte to see if new magnetometer data is available and pass that along with the new_mag_data flag.

I made an example to check the timing of the mag data ready:

#include "icm20948.h"

/* Icm20649 object */
bfs::Icm20948 imu;

unsigned int t1, t2;

void setup() {
  /* Serial to display data */
  while(!Serial) {}
  /* Start the I2C bus */
  /* I2C bus,  0x68 address */
  imu.Config(&Wire, bfs::Icm20948::I2C_ADDR_SEC);
  /* Initialize and configure IMU */
  if (!imu.Begin()) {
    Serial.println("Error initializing communication with IMU");
    while(1) {}
  /* Set the sample rate divider */
  if (!imu.ConfigSrd(0)) {
    Serial.println("Error configuring SRD");
    while(1) {}

void loop() {
  /* Check if data read */
  if (imu.Read()) {
    if (imu.new_mag_data()) {
      t1 = millis();
      Serial.println(t1 - t2);
      t2 = t1;

With the SRD set to 0, I see the expected timing of 100 or occasionally 200 ms, as expected. However, if I change the SRD in this code to something like 4, which sets the IMU to run at a rate of 225 Hz, my magnetometer update rate becomes less consistent, with many updates taking over 1 second. I'm not sure what's going on. This code is similar to how I was working with the mag in the MPU-9250 and I don't experience any issues with that magnetometer, it reads at the desired rate. I tried setting the I2C_MST_P_NSR flag in the ICM-20948 thinking that it might help with the multi-byte reads, but it doesn't appear to have an effect. Similarly, I've tried setting the I2C_SLV4_DLY and I2C_SLV0_DELAY_EN flag thinking that the ICM-20948 might be polling the magnetometer too often. The behavior changed, but I didn't see an improvement.

I'll likely try setting the ICM-20948 into I2C bypass and then accessing with a Teensy directly to see if I can replicate this behavior and find a solution without having to deal with the ICM-20948 external I2C interface; however, I also thought I would post here in the meantime to see if anyone else has experienced these issues or has ideas on what might be causing them. Thanks!
Updated the icm-dev branch to include a driver for the AK09916. Two read methods are defined:
1. Read1 - starting from AK09916 register ST1, does a block read up to and including ST2. After the bulk read, check the ST1 register to see if the data ready flag was set, and if it was, update the mag sensor data from the data buffers.
2. Read2 - reads the single byte from register ST1 and checks the data ready flag. If the data ready flag is set, a block read starting from the HXL register to the ST2 register is performed to read and update the mag sensor data.

I set up the measurement rate to 10 Hz to make issues easier to spot. Using the Read1 method, which replicates the method done in the ICM-20948 driver, I see the same issues that I did in the ICM-20948 driver. The timing from the example does not match the configured timing. I'm guessing that by reading the full block of data, I'm inadvertently triggering the AK09916 to reset its data ready flag.

The Read2 method works great. The issue in the ICM-20948 driver will be the time required to do this handshake through the external I2C interface. There are several registers that are set in the ICM-20948 to send a single I2C byte over the external I2C interface. Then, you need to wait for the external device to fill registers in the ICM-20948 before you can pick off the data. In my mind, the approach to applying this in the ICM-20948 driver would be:
1. Set up the external interface to continuously poll the AK09916 ST1 register
2. Read the IMU data and up to the ST1 external data register
3. If the AK09916 data ready flag is set, then setup the external I2C interface to request the data registers
4. Do I wait here for the ICM-20948 registers to fill? Probably not, probably grab it on the next IMU read
5. Read the AK09916 data registers, then re-configure the external I2C interface to poll the ST1 register again

Alternatively, could just enable the I2C bypass and let folks connect to I2C if they need to read the mag, like the MPU-9150.

What a pain, wish it worked as well as the MPU-9250.
Seems like massive overkill to me, but if it's fun then why not?
Just saw this thread - not sure why I didn't notice it before. In the middle of something else - when done will switch over to this? Did you look at the sparkfun lib to see if they are doing anything differently?
That's a good idea, I'll have to read through their library. The commit mentioned with the two different read methods for the mag is eb4b930 on the icm-dev branch.