Skip to content

Commit 899d2c1

Browse files
committed
Adding I2C Master ODR configuration
1 parent e52df4c commit 899d2c1

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

examples/Arduino/Example10_DMP_FastMultipleSensors/Example10_DMP_FastMultipleSensors.ino

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Example10_DMP_FastMultipleSensors.ino
33
* ICM 20948 Arduino Library Demo
44
* Initialize the DMP based on the TDK InvenSense ICM20948_eMD_nucleo_1.0 example-icm20948
5-
* Paul Clark, February 15th 2021
5+
* Paul Clark, April 25th, 2021
66
* Based on original code by:
77
* Owen Lyke @ SparkFun Electronics
88
* Original Creation Date: April 17 2019
@@ -101,6 +101,54 @@ void setup()
101101

102102
bool success = true; // Use success to show if the configuration was successful
103103

104+
// Normally, when the DMP is not enabled, startupMagnetometer (called by startupDefault, which is called by begin) configures the AK09916 magnetometer
105+
// to run at 100Hz by setting the CNTL2 register (0x31) to 0x08. Then the ICM20948's I2C_SLV0 is configured to read
106+
// nine bytes from the mag every sample, starting from the STATUS1 register (0x10). ST1 includes the DRDY (Data Ready) bit.
107+
// Next are the six magnetometer readings (little endian). After a dummy byte, the STATUS2 register (0x18) contains the HOFL (Overflow) bit.
108+
//
109+
// But looking very closely at the InvenSense example code, we can see in inv_icm20948_resume_akm (in Icm20948AuxCompassAkm.c) that,
110+
// when the DMP is running, the magnetometer is set to Single Measurement (SM) mode and that ten bytes are read, starting from the reserved
111+
// RSV2 register (0x03). The datasheet does not define what registers 0x04 to 0x0C contain. There is definitely some secret sauce in here...
112+
// The magnetometer data appears to be big endian (not little endian like the HX/Y/Z registers) and starts at register 0x04.
113+
// We had to examine the I2C traffic between the master and the AK09916 on the AUX_DA and AUX_CL pins to discover this...
114+
//
115+
// So, we need to set up I2C_SLV0 to do the ten byte reading. The parameters passed to i2cControllerConfigurePeripheral are:
116+
// 0: use I2C_SLV0
117+
// MAG_AK09916_I2C_ADDR: the I2C address of the AK09916 magnetometer (0x0C unshifted)
118+
// AK09916_REG_RSV2: we start reading here (0x03). Secret sauce...
119+
// 10: we read 10 bytes each cycle
120+
// true: set the I2C_SLV0_RNW ReadNotWrite bit so we read the 10 bytes (not write them)
121+
// true: set the I2C_SLV0_CTRL I2C_SLV0_EN bit to enable reading from the peripheral at the sample rate
122+
// false: clear the I2C_SLV0_CTRL I2C_SLV0_REG_DIS (we want to write the register value)
123+
// true: set the I2C_SLV0_CTRL I2C_SLV0_GRP bit to show the register pairing starts at byte 1+2 (copied from inv_icm20948_resume_akm)
124+
// true: set the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW to byte-swap the data from the mag (copied from inv_icm20948_resume_akm)
125+
success &= (myICM.i2cControllerConfigurePeripheral(0, MAG_AK09916_I2C_ADDR, AK09916_REG_RSV2, 10, true, true, false, true, true) == ICM_20948_Stat_Ok);
126+
//
127+
// We also need to set up I2C_SLV1 to do the Single Measurement triggering:
128+
// 1: use I2C_SLV1
129+
// MAG_AK09916_I2C_ADDR: the I2C address of the AK09916 magnetometer (0x0C unshifted)
130+
// AK09916_REG_CNTL2: we start writing here (0x31)
131+
// 1: not sure why, but the write does not happen if this is set to zero
132+
// false: clear the I2C_SLV0_RNW ReadNotWrite bit so we write the dataOut byte
133+
// true: set the I2C_SLV0_CTRL I2C_SLV0_EN bit. Not sure why, but the write does not happen if this is clear
134+
// false: clear the I2C_SLV0_CTRL I2C_SLV0_REG_DIS (we want to write the register value)
135+
// false: clear the I2C_SLV0_CTRL I2C_SLV0_GRP bit
136+
// false: clear the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW bit
137+
// AK09916_mode_single: tell I2C_SLV1 to write the Single Measurement command each sample
138+
success &= (myICM.i2cControllerConfigurePeripheral(1, MAG_AK09916_I2C_ADDR, AK09916_REG_CNTL2, 1, false, true, false, false, false, AK09916_mode_single) == ICM_20948_Stat_Ok);
139+
140+
// Set the I2C Master ODR configuration
141+
// It is not clear why we need to do this... But it appears to be essential! From the datasheet:
142+
// "I2C_MST_ODR_CONFIG[3:0]: ODR configuration for external sensor when gyroscope and accelerometer are disabled.
143+
// ODR is computed as follows: 1.1 kHz/(2^((odr_config[3:0])) )
144+
// When gyroscope is enabled, all sensors (including I2C_MASTER) use the gyroscope ODR.
145+
// If gyroscope is disabled, then all sensors (including I2C_MASTER) use the accelerometer ODR."
146+
// Since both gyro and accel are running, setting this register should have no effect. But it does. Maybe because the Gyro and Accel are placed in Low Power Mode (cycled)?
147+
// You can see by monitoring the Aux I2C pins that the next three lines reduce the bus traffic (magnetometer reads) from 1125Hz to the chosen rate: 68.75Hz in this case.
148+
success &= (myICM.setBank(3) == ICM_20948_Stat_Ok); // Select Bank 3
149+
uint8_t mstODRconfig = 0x04; // Set the ODR configuration to 1100/2^4 = 68.75Hz
150+
success &= (myICM.write(AGB3_REG_I2C_MST_ODR_CONFIG, &mstODRconfig, 1) == ICM_20948_Stat_Ok); // Write one byte to the I2C_MST_ODR_CONFIG register
151+
104152
// Configure clock source through PWR_MGMT_1
105153
// ICM_20948_Clock_Auto selects the best available clock source – PLL if ready, else use the Internal oscillator
106154
success &= (myICM.setClockSource(ICM_20948_Clock_Auto) == ICM_20948_Stat_Ok); // This is shorthand: success will be set to false if setClockSource fails
@@ -257,9 +305,9 @@ void setup()
257305
const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example
258306
success &= (myICM.writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]) == ICM_20948_Stat_Ok);
259307

260-
// Configure the Compass Time Buffer. The compass (magnetometer) is set to 100Hz (AK09916_mode_cont_100hz)
261-
// in startupMagnetometer. We need to set CPASS_TIME_BUFFER to 100 too.
262-
const unsigned char compassRate[2] = {0x00, 0x64}; // 100Hz
308+
// Configure the Compass Time Buffer. The I2C Master ODR Configuration (see above) sets the magnetometer read rate to 68.75Hz.
309+
// Let's set the Compass Time Buffer to 69 (Hz).
310+
const unsigned char compassRate[2] = {0x00, 0x45}; // 69Hz
263311
success &= (myICM.writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]) == ICM_20948_Stat_Ok);
264312

265313
// Enable DMP interrupt

examples/Arduino/Example9_DMP_MultipleSensors/Example9_DMP_MultipleSensors.ino

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Example9_DMP_MultipleSensors.ino
33
* ICM 20948 Arduino Library Demo
44
* Initialize the DMP based on the TDK InvenSense ICM20948_eMD_nucleo_1.0 example-icm20948
5-
* Paul Clark, February 15th 2021
5+
* Paul Clark, April 25th, 2021
66
* Based on original code by:
77
* Owen Lyke @ SparkFun Electronics
88
* Original Creation Date: April 17 2019
@@ -101,14 +101,14 @@ void setup()
101101

102102
bool success = true; // Use success to show if the configuration was successful
103103

104-
// Normally, when the DMP is not enabled, startupMagnetometer (called by startupDefault, called by begin) configures the AK09916 magnetometer
104+
// Normally, when the DMP is not enabled, startupMagnetometer (called by startupDefault, which is called by begin) configures the AK09916 magnetometer
105105
// to run at 100Hz by setting the CNTL2 register (0x31) to 0x08. Then the ICM20948's I2C_SLV0 is configured to read
106106
// nine bytes from the mag every sample, starting from the STATUS1 register (0x10). ST1 includes the DRDY (Data Ready) bit.
107107
// Next are the six magnetometer readings (little endian). After a dummy byte, the STATUS2 register (0x18) contains the HOFL (Overflow) bit.
108108
//
109-
// But looking very closely at the InvenSense example code, we can see in inv_icm20948_resume_akm (in Icm20948AuxCompassAkm.c) that
109+
// But looking very closely at the InvenSense example code, we can see in inv_icm20948_resume_akm (in Icm20948AuxCompassAkm.c) that,
110110
// when the DMP is running, the magnetometer is set to Single Measurement (SM) mode and that ten bytes are read, starting from the reserved
111-
// RSV2 register (0x03). The datasheet does not define what registers 0x04 to 0x0C contain. There is definately some secret sauce in here...
111+
// RSV2 register (0x03). The datasheet does not define what registers 0x04 to 0x0C contain. There is definitely some secret sauce in here...
112112
// The magnetometer data appears to be big endian (not little endian like the HX/Y/Z registers) and starts at register 0x04.
113113
// We had to examine the I2C traffic between the master and the AK09916 on the AUX_DA and AUX_CL pins to discover this...
114114
//
@@ -124,7 +124,9 @@ void setup()
124124
// true: set the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW to byte-swap the data from the mag (copied from inv_icm20948_resume_akm)
125125
success &= (myICM.i2cControllerConfigurePeripheral(0, MAG_AK09916_I2C_ADDR, AK09916_REG_RSV2, 10, true, true, false, true, true) == ICM_20948_Stat_Ok);
126126
//
127-
// We should also set up I2C_SLV1 to do the Single Measurement triggering:
127+
// We also need to set up I2C_SLV1 to do the Single Measurement triggering:
128+
// 1: use I2C_SLV1
129+
// MAG_AK09916_I2C_ADDR: the I2C address of the AK09916 magnetometer (0x0C unshifted)
128130
// AK09916_REG_CNTL2: we start writing here (0x31)
129131
// 1: not sure why, but the write does not happen if this is set to zero
130132
// false: clear the I2C_SLV0_RNW ReadNotWrite bit so we write the dataOut byte
@@ -133,8 +135,19 @@ void setup()
133135
// false: clear the I2C_SLV0_CTRL I2C_SLV0_GRP bit
134136
// false: clear the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW bit
135137
// AK09916_mode_single: tell I2C_SLV1 to write the Single Measurement command each sample
136-
// except doing that causes the magnetometer to stall and give out stale data. So, for now, the next line needs to stay commented!
137-
//success &= (myICM.i2cControllerConfigurePeripheral(1, MAG_AK09916_I2C_ADDR, AK09916_REG_CNTL2, 1, false, true, false, false, false, AK09916_mode_single) == ICM_20948_Stat_Ok);
138+
success &= (myICM.i2cControllerConfigurePeripheral(1, MAG_AK09916_I2C_ADDR, AK09916_REG_CNTL2, 1, false, true, false, false, false, AK09916_mode_single) == ICM_20948_Stat_Ok);
139+
140+
// Set the I2C Master ODR configuration
141+
// It is not clear why we need to do this... But it appears to be essential! From the datasheet:
142+
// "I2C_MST_ODR_CONFIG[3:0]: ODR configuration for external sensor when gyroscope and accelerometer are disabled.
143+
// ODR is computed as follows: 1.1 kHz/(2^((odr_config[3:0])) )
144+
// When gyroscope is enabled, all sensors (including I2C_MASTER) use the gyroscope ODR.
145+
// If gyroscope is disabled, then all sensors (including I2C_MASTER) use the accelerometer ODR."
146+
// Since both gyro and accel are running, setting this register should have no effect. But it does. Maybe because the Gyro and Accel are placed in Low Power Mode (cycled)?
147+
// You can see by monitoring the Aux I2C pins that the next three lines reduce the bus traffic (magnetometer reads) from 1125Hz to the chosen rate: 68.75Hz in this case.
148+
success &= (myICM.setBank(3) == ICM_20948_Stat_Ok); // Select Bank 3
149+
uint8_t mstODRconfig = 0x04; // Set the ODR configuration to 1100/2^4 = 68.75Hz
150+
success &= (myICM.write(AGB3_REG_I2C_MST_ODR_CONFIG, &mstODRconfig, 1) == ICM_20948_Stat_Ok); // Write one byte to the I2C_MST_ODR_CONFIG register
138151

139152
// Configure clock source through PWR_MGMT_1
140153
// ICM_20948_Clock_Auto selects the best available clock source – PLL if ready, else use the Internal oscillator
@@ -143,7 +156,7 @@ void setup()
143156
// Enable accel and gyro sensors through PWR_MGMT_2
144157
// Enable Accelerometer (all axes) and Gyroscope (all axes) by writing zero to PWR_MGMT_2
145158
success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0
146-
uint8_t pwrMgmt2 = 0x40; // Set the reserved bit 6
159+
uint8_t pwrMgmt2 = 0x40; // Set the reserved bit 6 (pressure sensor disable?)
147160
success &= (myICM.write(AGB0_REG_PWR_MGMT_2, &pwrMgmt2, 1) == ICM_20948_Stat_Ok); // Write one byte to the PWR_MGMT_2 register
148161

149162
// Configure I2C_Master/Gyro/Accel in Low Power Mode (cycled) with LP_CONFIG
@@ -292,10 +305,9 @@ void setup()
292305
const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example
293306
success &= (myICM.writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]) == ICM_20948_Stat_Ok);
294307

295-
// Configure the Compass Time Buffer. The compass (magnetometer) is set to 100Hz (AK09916_mode_cont_100hz)
296-
// in startupMagnetometer. I think we need to set CPASS_TIME_BUFFER to 100 too.
297-
const unsigned char compassRate[2] = {0x00, 0x64}; // 100Hz
298-
//const unsigned char compassRate[2] = {0x04, 0x65}; // 1125Hz - same as the accelerometer sample rate
308+
// Configure the Compass Time Buffer. The I2C Master ODR Configuration (see above) sets the magnetometer read rate to 68.75Hz.
309+
// Let's set the Compass Time Buffer to 69 (Hz).
310+
const unsigned char compassRate[2] = {0x00, 0x45}; // 69Hz
299311
success &= (myICM.writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]) == ICM_20948_Stat_Ok);
300312

301313
// Enable DMP interrupt
@@ -449,6 +461,22 @@ void loop()
449461
SERIAL_PORT.print(F(" Z:"));
450462
SERIAL_PORT.println(z);
451463
}
464+
465+
if ((data.header & DMP_header_bitmap_Header2) > 0) // Header 2
466+
{
467+
if ((data.header2 & DMP_header2_bitmap_Secondary_On_Off) > 0) // Secondary On/Off
468+
{
469+
if (data.Secondary_On_Off.Sensors.Gyro_Off > 0) SERIAL_PORT.println(F("Gyro Off"));
470+
if (data.Secondary_On_Off.Sensors.Gyro_On > 0) SERIAL_PORT.println(F("Gyro On"));
471+
if (data.Secondary_On_Off.Sensors.Compass_Off > 0) SERIAL_PORT.println(F("Compass Off"));
472+
if (data.Secondary_On_Off.Sensors.Compass_On > 0) SERIAL_PORT.println(F("Compass On"));
473+
}
474+
if ((data.header2 & DMP_header2_bitmap_Fsync) > 0) // Fsync delay
475+
{
476+
SERIAL_PORT.print(F("Fsync delay: "));
477+
SERIAL_PORT.println(data.Fsync_Delay_Time);
478+
}
479+
}
452480
}
453481

454482
if (myICM.status != ICM_20948_Stat_FIFOMoreDataAvail) // If more data is available then we should read it right away - and not delay

src/ICM_20948.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,13 @@ ICM_20948_Status_e ICM_20948::startupMagnetometer(bool minimal)
12481248
debugPrintln(F(" tries"));
12491249
}
12501250

1251+
//Return now if minimal is true. The mag will be configured manually for the DMP
1252+
if (minimal) // Return now if minimal is true
1253+
{
1254+
debugPrintln(F("ICM_20948::startupMagnetometer: minimal startup complete!"));
1255+
return status;
1256+
}
1257+
12511258
//Set up magnetometer
12521259
AK09916_CNTL2_Reg_t reg;
12531260
reg.MODE = AK09916_mode_cont_100hz;
@@ -1262,13 +1269,6 @@ ICM_20948_Status_e ICM_20948::startupMagnetometer(bool minimal)
12621269
return status;
12631270
}
12641271

1265-
//Return now if minimal is true. The mag will be configured manually for the DMP
1266-
if (minimal) // Return now if minimal is true
1267-
{
1268-
debugPrintln(F("ICM_20948::startupMagnetometer: minimal startup complete!"));
1269-
return status;
1270-
}
1271-
12721272
retval = i2cControllerConfigurePeripheral(0, MAG_AK09916_I2C_ADDR, AK09916_REG_ST1, 9, true, true, false, false, false);
12731273
if (retval != ICM_20948_Stat_Ok)
12741274
{

src/util/ICM_20948_C.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern "C"
2525
extern int memcmp(const void *, const void *, size_t); // Avoid compiler warnings
2626

2727
// Define if the DMP will be supported
28-
// Note: you must have 14301 Bytes of program memory available to store the DMP firmware!
28+
// Note: you must have 14290/14301 Bytes of program memory available to store the DMP firmware!
2929
#define ICM_20948_USE_DMP // Uncomment this line to enable DMP support.
3030

3131
#define ICM_20948_I2C_ADDR_AD0 0x68 // Or 0x69 when AD0 is high

0 commit comments

Comments
 (0)