I’m using the ICM-90248 breakout module via I2C with the Sparkfun Thing esp32 board. When I set the low-pass filter cutoff frequency to anything other than “acc_d473bw_n499bw”, the output values from the accelerometer are erroneously decreased by a large factor (8 to 256 depending on which cutoff frequency setting I use). When I use “acc_d473bw_n499bw”, all is well as the accelerometer z axis output is approximately 16384 bits with the full-scale range set to +/- 2 g’s - as expected.
Am I doing something wrong in the setup (see below)? Is there any work-around for this? Any idea why it is happening?
So, for example, if I use “acc_d246bw_n265bw” as the LPF cutoff frequency setting, the output of the accelerometer z-axis is about 2048, a factor of 8 less than expected. If I flip the board upside down with this same setting, the acc.z output is about -2048. So, the problem isn’t an offset, it is a reduction in gain by a factor of 8. I also considered that maybe somehow the full-scale is getting set to +/- 16 g’s whereas I’m expecting it to be +/- 2 g’s but that is not the case. If I change the fsr to +/- 16 g’s using “acc_d246bw_n265bw”, the acc.z output decreases by another factor of 8 to give about 256 (whereas 2048 is expected).
The acc.z value gets cut by another factor of 2 each time the lpf cutoff frequency is cut in half. Here are the approximate acc.z values with the board lying flat on a table for each available lpf cutoff frequency setting with the fsr set to +/- 2 g’s. Each value should be about 16384.
acc_d246bw_n265bw : 2048
acc_d246bw_n265bw_1 : 2048
acc_d111bw4_n136bw : 1024
acc_d50bw4_n68bw8 : 512
acc_d23bw9_n34bw4 : 256
acc_d11bw5_n17bw : 128
acc_d5bw7_n8bw3 : 64
acc_d473bw_n499bw : 16384 (the expected value for each setting)
All of the values above are decreased by a factor of 2 when using an fsr of +/- 4 g’s, a factor of 4 when using +/- 8 g’s and a factor of 8 when using +/- 16 g’s. So, with “acc_d5bw7_n8bw3” and +/- 16 g’s, the accelerometer z-axis output with the sensor lying flat on a table is about 8 whereas it is expected to be 16384. I’ve tried all combinations of cutoff frequency and fsr and all combinations exhibit the erroneous behavior described except when the cutoff frequency is set to “acc_d473bw_n499bw”.
All of the examples cited above refer only to the z-axis but the same errors occur in the x and y axes too. I don’t have a way of determining whether the same applies to the gyro.
There is a comment in the Sparkfun ICM_90248 library file ICM_20948_C.c file at line 1091 that hints of the behavior described above but there is no indication of the cause or remedy. Here’s a snippet from ICM_20948_C.c:
pagmt->fss.a = acfg.ACCEL_FS_SEL; // Worth noting that without explicitly setting the FS range of the accelerometer it was showing the register value for +/- 2g but the reported values were actually scaled to the +/- 16g range
// ***Wait a minute... now it seems like this problem actually comes from the digital low-pass filter. When enabled the value is 1/8 what it should be...***
Here’s the setup code I’m using for the ICM-20948. The sample rate divisor is set to 22 to get a sample frequency of about 48.9 hz.
// Initialize IMU Communication
bool initialized = false;
while (!initialized)
{
// Start the IMU SPI or I2C unit
#ifdef USE_SPI
myICM.begin(CS_PIN, SPI_PORT, SPI_FREQ); // Here we are using the user-defined SPI_FREQ as the clock speed of the SPI bus
#else
myICM.begin(WIRE_PORT, AD0_VAL);
#endif
// Report Status
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;
}
}
SERIAL_PORT.println("Device connected!");
// Reset the IMU so that it starts from a known state
myICM.swReset();
if (myICM.status = ICM_20948_Stat_Ok)
{
SERIAL_PORT.print(F("Software Reset returned: "));
SERIAL_PORT.println(myICM.statusString());
}
delay(250);
// Wake the IMU
myICM.sleep(sensorSleep);
myICM.lowPower(false);
// The next few configuration functions accept a bit-mask of sensors for which the settings should be applied.
// Configure the Gyro, Accelerometer & Magnetometer in the IMU module & set the IMU sample mode and rate
// Set the sample MODE for the Gyro and Accelerometer
// options: ICM_20948_Sample_Mode_Continuous - Produces new data as fast as possible
// ICM_20948_Sample_Mode_Cycled - Produces new data periodically
myICM.setSampleMode((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Cycled); // Gyro, Accel & Mag are all sampled at the Gyro rate
SERIAL_PORT.print(F("setSampleMode returned: "));
SERIAL_PORT.println(myICM.statusString());
// Set the Gyro Sample RATE - All other data will be sampled at the same rate
ICM_20948_smplrt_t mySmplrt;
mySmplrt.g = SAMPLE_RATE_DIVISOR; // 1125/fs-1
myICM.setSampleRate(ICM_20948_Internal_Gyr, mySmplrt);
SERIAL_PORT.print(F("setSampleRate returned: "));
SERIAL_PORT.println(myICM.statusString());
// Set full-scale ranges for both acc and gyr
ICM_20948_fss_t myFSS; // This uses a "Full Scale Settings" structure that can contain values for all configurable sensors
myFSS.a = gpm2; // (ICM_20948_ACCEL_CONFIG_FS_SEL_e), Choices are: gpm2, gpm4, gpm8, gpm16. "gpm" = G's, +/-
myFSS.g = dps250; // (ICM_20948_GYRO_CONFIG_1_FS_SEL_e), Choices are: dps250, dps500, dps1000, dps2000. "dps" = Degrees/sec
delay(100);
myICM.setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
SERIAL_PORT.print(F("setFullScale returned: "));
SERIAL_PORT.println(myICM.statusString());
// }
delay(100);
// Choose whether or not to use DLPF
// Here we're also showing another way to access the status values, and that it is OK to supply individual sensor masks to these functions
ICM_20948_Status_e accDLPEnableStat = myICM.enableDLPF(ICM_20948_Internal_Acc, true);
ICM_20948_Status_e gyrDLPEnableStat = myICM.enableDLPF(ICM_20948_Internal_Gyr, true);
SERIAL_PORT.print(F("Enable DLPF for Accelerometer returned: "));
SERIAL_PORT.println(myICM.statusString(accDLPEnableStat));
SERIAL_PORT.print(F("Enable DLPF for Gyroscope returned: "));
SERIAL_PORT.println(myICM.statusString(gyrDLPEnableStat));
/* ################# NOTE #################
* Changing the filter cutoff frequency may change the required calibration constants.
* ##########################################
*/
// Setup IMU Digital Low-Pass Filter configuration
// The Nyquist Bandwidth is simply half of the sample rate used for the filter in the DLP for each selection.
// The filters used are simple moving averages of different numbers of samples for each setting.
ICM_20948_dlpcfg_t myDLPcfg; // Similar to FSS, this uses a configuration structure for the desired sensors
myDLPcfg.a = acc_d5bw7_n8bw3; // (ICM_20948_ACCEL_CONFIG_DLPCFG_e)
myDLPcfg.g = gyr_d361bw4_n376bw5; // (ICM_20948_GYRO_CONFIG_1_DLPCFG_e)
myICM.setDLPFcfg((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myDLPcfg);
if (myICM.status = ICM_20948_Stat_Ok)
{
SERIAL_PORT.print(F("setDLPcfg returned: "));
SERIAL_PORT.println(myICM.statusString());
}
// Start the Magnetometer
myICM.startupMagnetometer();
// if (myICM.status = ICM_20948_Stat_Ok)
// {
SERIAL_PORT.print(F("startupMagnetometer returned: "));
SERIAL_PORT.println(myICM.statusString());
// }
// Setup IMU interrupts. Many options, but for this just configuring the interrupt pin to signal that new data is ready
myICM.cfgIntActiveLow(true); // Active low to be compatible with the breakout board's pullup resistor
myICM.cfgIntOpenDrain(false); // Push-pull, though open-drain would also work thanks to the pull-up resistors on the breakout
myICM.cfgIntLatch(false); // false = Interrupt signal is cleared by IMU immediately after being set, true = interrupt latched until manually cleared (by isr)
SERIAL_PORT.print(F("cfgIntLatch returned: "));
SERIAL_PORT.println(myICM.statusString());
SERIAL_PORT.print("CPU Clock Frequency (MHz) = ");
SERIAL_PORT.println(getCpuFrequencyMhz(),1);
SERIAL_PORT.println("Configuration Complete, enabling IMU Interrupts now!");