Skip to content

Commit d09ffc0

Browse files
authored
Merge pull request #41 from sparkfun/release_candidate
v1.2.2: DMP updates
2 parents cb9951f + eb040bf commit d09ffc0

File tree

12 files changed

+989
-184
lines changed

12 files changed

+989
-184
lines changed

DMP.md

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ The DMP data is returned via the FIFO (First In First Out). ```readDMPdataFromFI
7474
- ```ICM_20948_Stat_FIFONoDataAvail``` if no data or incomplete data is available
7575
- ```ICM_20948_Stat_Ok``` if a valid frame was read
7676
- ```ICM_20948_Stat_FIFOMoreDataAvail``` if a valid frame was read _and_ the FIFO contains more (unread) data
77+
- ```ICM_20948_Stat_FIFOIncompleteData``` if a frame was present in the FIFO but it was incomplete
7778

7879
You can examine the 16-bit ```icm_20948_DMP_data_t data.header``` to see what data the frame contained. ```data.header``` is a bit field; each bit indicates what data is present:
7980
- **DMP_header_bitmap_Compass_Calibr** (0x0020)

examples/Arduino/Example6_DMP_Quat9_Orientation/Example6_DMP_Quat9_Orientation.ino

+107-31
Large diffs are not rendered by default.

examples/Arduino/Example7_DMP_Quat6_EulerAngles/Example7_DMP_Quat6_EulerAngles.ino

+111-22
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* ** We are grateful to InvenSense for sharing this with us.
1212
*
1313
* ** Important note: by default the DMP functionality is disabled in the library
14-
* ** as the DMP firmware takes up 14290 Bytes of program memory.
14+
* ** as the DMP firmware takes up 14301 Bytes of program memory.
1515
* ** To use the DMP, you will need to:
1616
* ** Edit ICM_20948_C.h
1717
* ** Uncomment line 29: #define ICM_20948_USE_DMP
@@ -24,6 +24,8 @@
2424
* Distributed as-is; no warranty is given.
2525
***************************************************************/
2626

27+
//#define QUAT_ANIMATION // Uncomment this line to output data in the correct format for ZaneL's Node.js Quaternion animation tool: https://github.com/ZaneL/quaternion_sensor_3d_nodejs
28+
2729
#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU
2830

2931
//#define USE_SPI // Uncomment this to use SPI
@@ -48,17 +50,21 @@
4850
void setup() {
4951

5052
SERIAL_PORT.begin(115200); // Start the serial console
53+
#ifndef QUAT_ANIMATION
5154
SERIAL_PORT.println(F("ICM-20948 Example"));
55+
#endif
5256

5357
delay(100);
5458

59+
#ifndef QUAT_ANIMATION
5560
while (SERIAL_PORT.available()) // Make sure the serial RX buffer is empty
5661
SERIAL_PORT.read();
5762

5863
SERIAL_PORT.println(F("Press any key to continue..."));
5964

6065
while (!SERIAL_PORT.available()) // Wait for the user to press a key (send any serial character)
6166
;
67+
#endif
6268

6369
#ifdef USE_SPI
6470
SPI_PORT.begin();
@@ -67,7 +73,9 @@ void setup() {
6773
WIRE_PORT.setClock(400000);
6874
#endif
6975

76+
#ifndef QUAT_ANIMATION
7077
//myICM.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial
78+
#endif
7179

7280
bool initialized = false;
7381
while( !initialized ){
@@ -80,17 +88,23 @@ void setup() {
8088
myICM.begin( WIRE_PORT, AD0_VAL );
8189
#endif
8290

91+
#ifndef QUAT_ANIMATION
8392
SERIAL_PORT.print( F("Initialization of the sensor returned: ") );
8493
SERIAL_PORT.println( myICM.statusString() );
94+
#endif
8595
if( myICM.status != ICM_20948_Stat_Ok ){
96+
#ifndef QUAT_ANIMATION
8697
SERIAL_PORT.println( F("Trying again...") );
98+
#endif
8799
delay(500);
88100
}else{
89101
initialized = true;
90102
}
91103
}
92104

105+
#ifndef QUAT_ANIMATION
93106
SERIAL_PORT.println(F("Device connected!"));
107+
#endif
94108

95109
// The ICM-20948 is awake and ready but hasn't been configured. Let's step through the configuration
96110
// sequence from InvenSense's _confidential_ Application Note "Programming Sequence for DMP Hardware Functions".
@@ -103,8 +117,9 @@ void setup() {
103117

104118
// Enable accel and gyro sensors through PWR_MGMT_2
105119
// Enable Accelerometer (all axes) and Gyroscope (all axes) by writing zero to PWR_MGMT_2
106-
uint8_t zero = 0;
107-
success &= (myICM.write(AGB0_REG_PWR_MGMT_2, &zero, 1) == ICM_20948_Stat_Ok); // Write one byte to the PWR_MGMT_2 register
120+
success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0
121+
uint8_t pwrMgmt2 = 0x40; // Set the reserved bit 6
122+
success &= (myICM.write(AGB0_REG_PWR_MGMT_2, &pwrMgmt2, 1) == ICM_20948_Stat_Ok); // Write one byte to the PWR_MGMT_2 register
108123

109124
// Configure I2C_Master/Gyro/Accel in Low Power Mode (cycled) with LP_CONFIG
110125
success &= (myICM.setSampleMode( (ICM_20948_Internal_Mst | ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Cycled ) == ICM_20948_Stat_Ok);
@@ -136,6 +151,8 @@ void setup() {
136151

137152
// Turn off what goes into the FIFO through FIFO_EN_1, FIFO_EN_2
138153
// Stop the peripheral data from being written to the FIFO by writing zero to FIFO_EN_1
154+
success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0
155+
uint8_t zero = 0;
139156
success &= (myICM.write(AGB0_REG_FIFO_EN_1, &zero, 1) == ICM_20948_Stat_Ok);
140157
// Stop the accelerometer, gyro and temperature data from being written to the FIFO by writing zero to FIFO_EN_2
141158
success &= (myICM.write(AGB0_REG_FIFO_EN_2, &zero, 1) == ICM_20948_Stat_Ok);
@@ -149,8 +166,8 @@ void setup() {
149166
// Set gyro sample rate divider with GYRO_SMPLRT_DIV
150167
// Set accel sample rate divider with ACCEL_SMPLRT_DIV_2
151168
ICM_20948_smplrt_t mySmplrt;
152-
mySmplrt.g = 19; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 19 = 55Hz
153-
mySmplrt.a = 19; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 19 = 56.25Hz
169+
mySmplrt.g = 19; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 19 = 55Hz. InvenSense Nucleo example uses 19 (0x13).
170+
mySmplrt.a = 19; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 19 = 56.25Hz. InvenSense Nucleo example uses 19 (0x13).
154171
myICM.setSampleRate( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), mySmplrt ); // ** Note: comment this line to leave the sample rates at the maximum **
155172

156173
// Setup DMP start address through PRGM_STRT_ADDRH/PRGM_STRT_ADDRL
@@ -162,14 +179,24 @@ void setup() {
162179
// Write the 2 byte Firmware Start Value to ICM PRGM_STRT_ADDRH/PRGM_STRT_ADDRL
163180
success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS
164181

182+
// Set the Hardware Fix Disable register to 0x48
183+
success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0
184+
uint8_t fix = 0x48;
185+
success &= (myICM.write(AGB0_REG_HW_FIX_DISABLE, &fix, 1) == ICM_20948_Stat_Ok);
186+
187+
// Set the Single FIFO Priority Select register to 0xE4
188+
success &= (myICM.setBank(0) == ICM_20948_Stat_Ok); // Select Bank 0
189+
uint8_t fifoPrio = 0xE4;
190+
success &= (myICM.write(AGB0_REG_SINGLE_FIFO_PRIORITY_SEL, &fifoPrio, 1) == ICM_20948_Stat_Ok);
191+
165192
// Configure Accel scaling to DMP
166193
// The DMP scales accel raw data internally to align 1g as 2^25
167-
// In order to align internal accel raw data 2^25 = 1g write 0x4000000 when FSR is 4g
168-
const unsigned char accScale[4] = {0x40, 0x00, 0x00, 0x00};
169-
success &= (myICM.writeDMPmems(ACC_SCALE, 4, &accScale[0]) == ICM_20948_Stat_Ok); // Write 0x4000000 to ACC_SCALE DMP register
170-
// In order to output hardware unit data as configured FSR write 0x40000 when FSR is 4g
194+
// In order to align internal accel raw data 2^25 = 1g write 0x04000000 when FSR is 4g
195+
const unsigned char accScale[4] = {0x04, 0x00, 0x00, 0x00};
196+
success &= (myICM.writeDMPmems(ACC_SCALE, 4, &accScale[0]) == ICM_20948_Stat_Ok); // Write accScale to ACC_SCALE DMP register
197+
// In order to output hardware unit data as configured FSR write 0x00040000 when FSR is 4g
171198
const unsigned char accScale2[4] = {0x00, 0x04, 0x00, 0x00};
172-
success &= (myICM.writeDMPmems(ACC_SCALE2, 4, &accScale2[0]) == ICM_20948_Stat_Ok); // Write 0x40000 to ACC_SCALE2 DMP register
199+
success &= (myICM.writeDMPmems(ACC_SCALE2, 4, &accScale2[0]) == ICM_20948_Stat_Ok); // Write accScale2 to ACC_SCALE2 DMP register
173200

174201
// Configure Compass mount matrix and scale to DMP
175202
// The mount matrix write to DMP register is used to align the compass axes with accel/gyro.
@@ -178,6 +205,8 @@ void setup() {
178205
// X = raw_x * CPASS_MTX_00 + raw_y * CPASS_MTX_01 + raw_z * CPASS_MTX_02
179206
// Y = raw_x * CPASS_MTX_10 + raw_y * CPASS_MTX_11 + raw_z * CPASS_MTX_12
180207
// Z = raw_x * CPASS_MTX_20 + raw_y * CPASS_MTX_21 + raw_z * CPASS_MTX_22
208+
// The AK09916 produces a 16-bit signed output in the range +/-32752 corresponding to +/-4912uT. 1uT = 6.66 ADU.
209+
// 2^30 / 6.66666 = 161061273 = 0x9999999
181210
const unsigned char mountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00};
182211
const unsigned char mountMultiplierPlus[4] = {0x09, 0x99, 0x99, 0x99}; // Value taken from InvenSense Nucleo example
183212
const unsigned char mountMultiplierMinus[4] = {0xF6, 0x66, 0x66, 0x67}; // Value taken from InvenSense Nucleo example
@@ -204,26 +233,47 @@ void setup() {
204233
success &= (myICM.writeDMPmems(B2S_MTX_21, 4, &mountMultiplierZero[0]) == ICM_20948_Stat_Ok);
205234
success &= (myICM.writeDMPmems(B2S_MTX_22, 4, &mountMultiplierPlus[0]) == ICM_20948_Stat_Ok);
206235

207-
// Configure the Gyro scaling factor
208-
const unsigned char gyroScalingFactor[4] = {0x26, 0xFA, 0xB4, 0xB1}; // Value taken from InvenSense Nucleo example
209-
success &= (myICM.writeDMPmems(GYRO_SF, 4, &gyroScalingFactor[0]) == ICM_20948_Stat_Ok);
236+
// Configure the DMP Gyro Scaling Factor
237+
//const unsigned char gyroScalingFactor[4] = {0x26, 0xFA, 0xB4, 0xB1}; // Value taken from InvenSense Nucleo example
238+
//success &= (myICM.writeDMPmems(GYRO_SF, 4, &gyroScalingFactor[0]) == ICM_20948_Stat_Ok);
239+
// @param[in] gyro_div Value written to GYRO_SMPLRT_DIV register, where
240+
// 0=1125Hz sample rate, 1=562.5Hz sample rate, ... 4=225Hz sample rate, ...
241+
// 10=102.2727Hz sample rate, ... etc.
242+
// @param[in] gyro_level 0=250 dps, 1=500 dps, 2=1000 dps, 3=2000 dps
243+
success &= (myICM.setGyroSF(19, 3) == ICM_20948_Stat_Ok); // 19 = 55Hz (see above), 3 = 2000dps (see above)
210244

211245
// Configure the Gyro full scale
212-
const unsigned char gyroFullScale[4] = {0x10, 0x00, 0x00, 0x00}; // Value taken from InvenSense Nucleo example
246+
// 2000dps : 2^28
247+
// 1000dps : 2^27
248+
// 500dps : 2^26
249+
// 250dps : 2^25
250+
const unsigned char gyroFullScale[4] = {0x10, 0x00, 0x00, 0x00}; // 2000dps : 2^28
213251
success &= (myICM.writeDMPmems(GYRO_FULLSCALE, 4, &gyroFullScale[0]) == ICM_20948_Stat_Ok);
214-
252+
215253
// Configure the Accel Only Gain: 15252014 (225Hz) 30504029 (112Hz) 61117001 (56Hz)
216-
const unsigned char accelOnlyGain[4] = {0x00, 0xE8, 0xBA, 0x2E}; // Value taken from InvenSense Nucleo example
254+
const unsigned char accelOnlyGain[4] = {0x03, 0xA4, 0x92, 0x49}; // 56Hz
255+
//const unsigned char accelOnlyGain[4] = {0x00, 0xE8, 0xBA, 0x2E}; // InvenSense Nucleo example uses 225Hz
217256
success &= (myICM.writeDMPmems(ACCEL_ONLY_GAIN, 4, &accelOnlyGain[0]) == ICM_20948_Stat_Ok);
218257

219258
// Configure the Accel Alpha Var: 1026019965 (225Hz) 977872018 (112Hz) 882002213 (56Hz)
220-
const unsigned char accelAlphaVar[4] = {0x06, 0x66, 0x66, 0x66}; // Value taken from InvenSense Nucleo example
259+
const unsigned char accelAlphaVar[4] = {0x34, 0x92, 0x49, 0x25}; // 56Hz
260+
//const unsigned char accelAlphaVar[4] = {0x06, 0x66, 0x66, 0x66}; // Value taken from InvenSense Nucleo example
221261
success &= (myICM.writeDMPmems(ACCEL_ALPHA_VAR, 4, &accelAlphaVar[0]) == ICM_20948_Stat_Ok);
222262

223263
// Configure the Accel A Var: 47721859 (225Hz) 95869806 (112Hz) 191739611 (56Hz)
224-
const unsigned char accelAVar[4] = {0x39, 0x99, 0x99, 0x9A}; // Value taken from InvenSense Nucleo example
264+
const unsigned char accelAVar[4] = {0x0B, 0x6D, 0xB6, 0xDB}; // 56Hz
265+
//const unsigned char accelAVar[4] = {0x39, 0x99, 0x99, 0x9A}; // Value taken from InvenSense Nucleo example
225266
success &= (myICM.writeDMPmems(ACCEL_A_VAR, 4, &accelAlphaVar[0]) == ICM_20948_Stat_Ok);
226267

268+
// Configure the Accel Cal Rate
269+
const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example
270+
success &= (myICM.writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]) == ICM_20948_Stat_Ok);
271+
272+
// Configure the Compass Time Buffer. The compass (magnetometer) is set to 100Hz (AK09916_mode_cont_100hz)
273+
// in startupMagnetometer. We need to set CPASS_TIME_BUFFER to 100 too.
274+
const unsigned char compassRate[2] = {0x00, 0x64}; // 100Hz
275+
success &= (myICM.writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]) == ICM_20948_Stat_Ok);
276+
227277
// Enable DMP interrupt
228278
// This would be the most efficient way of getting the DMP data, instead of polling the FIFO
229279
//success &= (myICM.intEnableDMP(true) == ICM_20948_Stat_Ok);
@@ -248,10 +298,22 @@ void setup() {
248298
// Enable the DMP Game Rotation Vector sensor
249299
success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR) == ICM_20948_Stat_Ok);
250300

301+
// Enable any additional sensors / features
302+
//success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_GYROSCOPE) == ICM_20948_Stat_Ok);
303+
//success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_RAW_ACCELEROMETER) == ICM_20948_Stat_Ok);
304+
//success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED) == ICM_20948_Stat_Ok);
305+
251306
// Configuring DMP to output data at multiple ODRs:
252307
// DMP is capable of outputting multiple sensor data at different rates to FIFO.
253-
// Set the DMP Output Data Rate for Quat6 to 12Hz.
254-
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Quat6, 12) == ICM_20948_Stat_Ok);
308+
// Setting value can be calculated as follows:
309+
// Value = (DMP running rate / ODR ) - 1
310+
// E.g. For a 5Hz ODR rate when DMP is running at 55Hz, value = (55/5) - 1 = 10.
311+
success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Quat6, 0) == ICM_20948_Stat_Ok); // Set to the maximum
312+
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Accel, 0) == ICM_20948_Stat_Ok); // Set to the maximum
313+
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Gyro, 0) == ICM_20948_Stat_Ok); // Set to the maximum
314+
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Gyro_Calibr, 0) == ICM_20948_Stat_Ok); // Set to the maximum
315+
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Cpass, 0) == ICM_20948_Stat_Ok); // Set to the maximum
316+
//success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Cpass_Calibr, 0) == ICM_20948_Stat_Ok); // Set to the maximum
255317

256318
// Enable the FIFO
257319
success &= (myICM.enableFIFO() == ICM_20948_Stat_Ok);
@@ -267,7 +329,11 @@ void setup() {
267329

268330
// Check success
269331
if( success )
332+
{
333+
#ifndef QUAT_ANIMATION
270334
SERIAL_PORT.println(F("DMP enabled!"));
335+
#endif
336+
}
271337
else
272338
{
273339
SERIAL_PORT.println(F("Enable DMP failed!"));
@@ -279,8 +345,9 @@ void loop()
279345
{
280346
// Read any DMP data waiting in the FIFO
281347
// Note:
282-
// readDMPdataFromFIFO will return ICM_20948_Stat_FIFONoDataAvail if no data or incomplete data is available.
348+
// readDMPdataFromFIFO will return ICM_20948_Stat_FIFONoDataAvail if no data is available.
283349
// If data is available, readDMPdataFromFIFO will attempt to read _one_ frame of DMP data.
350+
// readDMPdataFromFIFO will return ICM_20948_Stat_FIFOIncompleteData if a frame was present but was incomplete
284351
// readDMPdataFromFIFO will return ICM_20948_Stat_Ok if a valid frame was read.
285352
// readDMPdataFromFIFO will return ICM_20948_Stat_FIFOMoreDataAvail if a valid frame was read _and_ the FIFO contains more (unread) data.
286353
icm_20948_DMP_data_t data;
@@ -307,9 +374,18 @@ void loop()
307374
double q2 = ((double)data.Quat6.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30
308375
double q3 = ((double)data.Quat6.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30
309376

310-
//SERIAL_PORT.printf("Q1:%.3f Q2:%.3f Q3:%.3f\r\n", q1, q2, q3);
377+
/*
378+
SERIAL_PORT.print(F("Q1:"));
379+
SERIAL_PORT.print(q1, 3);
380+
SERIAL_PORT.print(F(" Q2:"));
381+
SERIAL_PORT.print(q2, 3);
382+
SERIAL_PORT.print(F(" Q3:"));
383+
SERIAL_PORT.println(q3, 3);
384+
*/
311385

312386
// Convert the quaternions to Euler angles (roll, pitch, yaw)
387+
// https://en.wikipedia.org/w/index.php?title=Conversion_between_quaternions_and_Euler_angles&section=8#Source_code_2
388+
313389
double q0 = sqrt( 1.0 - ((q1 * q1) + (q2 * q2) + (q3 * q3)));
314390

315391
double q2sqr = q2 * q2;
@@ -330,12 +406,25 @@ void loop()
330406
double t4 = +1.0 - 2.0 * (q2sqr + q3 * q3);
331407
double yaw = atan2(t3, t4) * 180.0 / PI;
332408

409+
#ifndef QUAT_ANIMATION
333410
SERIAL_PORT.print(F("Roll:"));
334411
SERIAL_PORT.print(roll, 1);
335412
SERIAL_PORT.print(F(" Pitch:"));
336413
SERIAL_PORT.print(pitch, 1);
337414
SERIAL_PORT.print(F(" Yaw:"));
338415
SERIAL_PORT.println(yaw, 1);
416+
#else
417+
// Output the Quaternion data in the format expected by ZaneL's Node.js Quaternion animation tool
418+
SERIAL_PORT.print(F("{\"quat_w\":"));
419+
SERIAL_PORT.print(q0, 3);
420+
SERIAL_PORT.print(F(", \"quat_x\":"));
421+
SERIAL_PORT.print(q1, 3);
422+
SERIAL_PORT.print(F(", \"quat_y\":"));
423+
SERIAL_PORT.print(q2, 3);
424+
SERIAL_PORT.print(F(", \"quat_z\":"));
425+
SERIAL_PORT.print(q3, 3);
426+
SERIAL_PORT.println(F("}"));
427+
#endif
339428
}
340429
}
341430

0 commit comments

Comments
 (0)