Skip to content

Commit 729afad

Browse files
committed
Updating the examples: Correcting ACC_SCALE;
Adding support for ZaneL's Node.js Quaternion animation tool: https://github.com/ZaneL/quaternion_sensor_3d_nodejs
1 parent e168cf1 commit 729afad

File tree

4 files changed

+700
-75
lines changed

4 files changed

+700
-75
lines changed

examples/Arduino/Example6_DMP_Quat9_Orientation/Example6_DMP_Quat9_Orientation.ino

Lines changed: 107 additions & 31 deletions
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

93-
SERIAL_PORT.println("Device connected!");
105+
#ifndef QUAT_ANIMATION
106+
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,9 +166,9 @@ 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 = 43; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 43 = 25Hz
153-
mySmplrt.a = 44; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 44 = 25Hz
154-
//myICM.setSampleRate( (ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), mySmplrt ); // ** Note: comment this line to leave the sample rates at the maximum **
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).
171+
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
157174
success &= (myICM.setDMPstartAddress() == ICM_20948_Stat_Ok); // Defaults to DMP_START_ADDRESS
@@ -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,15 +298,22 @@ void setup() {
248298
// Enable the DMP orientation sensor
249299
success &= (myICM.enableDMPSensor(INV_ICM20948_SENSOR_ORIENTATION) == 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 Quat9 to 12Hz.
254-
success &= (myICM.setDMPODRrate(DMP_ODR_Reg_Quat9, 12) == ICM_20948_Stat_Ok);
255-
256-
// Set the DMP Data Ready Status register
257-
const uint16_t dsrBits = DMP_Data_ready_Gyro | DMP_Data_ready_Accel | DMP_Data_ready_Secondary_Compass;
258-
const unsigned char drsReg[2] = {(const unsigned char)(dsrBits >> 8), (const unsigned char)(dsrBits & 0xFF)};
259-
success &= (myICM.writeDMPmems(DATA_RDY_STATUS, 2, &drsReg[0]) == 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_Quat9, 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
260317

261318
// Enable the FIFO
262319
success &= (myICM.enableFIFO() == ICM_20948_Stat_Ok);
@@ -272,7 +329,11 @@ void setup() {
272329

273330
// Check success
274331
if( success )
332+
{
333+
#ifndef QUAT_ANIMATION
275334
SERIAL_PORT.println(F("DMP enabled!"));
335+
#endif
336+
}
276337
else
277338
{
278339
SERIAL_PORT.println(F("Enable DMP failed!"));
@@ -284,8 +345,9 @@ void loop()
284345
{
285346
// Read any DMP data waiting in the FIFO
286347
// Note:
287-
// 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.
288349
// 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
289351
// readDMPdataFromFIFO will return ICM_20948_Stat_Ok if a valid frame was read.
290352
// readDMPdataFromFIFO will return ICM_20948_Stat_FIFOMoreDataAvail if a valid frame was read _and_ the FIFO contains more (unread) data.
291353
icm_20948_DMP_data_t data;
@@ -308,10 +370,12 @@ void loop()
308370
//SERIAL_PORT.printf("Quat9 data is: Q1:%ld Q2:%ld Q3:%ld Accuracy:%d\r\n", data.Quat9.Data.Q1, data.Quat9.Data.Q2, data.Quat9.Data.Q3, data.Quat9.Data.Accuracy);
309371

310372
// Scale to +/- 1
311-
float q1 = ((float)data.Quat9.Data.Q1) / 1073741824.0; // Convert to float. Divide by 2^30
312-
float q2 = ((float)data.Quat9.Data.Q2) / 1073741824.0; // Convert to float. Divide by 2^30
313-
float q3 = ((float)data.Quat9.Data.Q3) / 1073741824.0; // Convert to float. Divide by 2^30
373+
double q1 = ((double)data.Quat9.Data.Q1) / 1073741824.0; // Convert to double. Divide by 2^30
374+
double q2 = ((double)data.Quat9.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30
375+
double q3 = ((double)data.Quat9.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30
376+
double q0 = sqrt( 1.0 - ((q1 * q1) + (q2 * q2) + (q3 * q3)));
314377

378+
#ifndef QUAT_ANIMATION
315379
SERIAL_PORT.print(F("Q1:"));
316380
SERIAL_PORT.print(q1, 3);
317381
SERIAL_PORT.print(F(" Q2:"));
@@ -320,6 +384,18 @@ void loop()
320384
SERIAL_PORT.print(q3, 3);
321385
SERIAL_PORT.print(F(" Accuracy:"));
322386
SERIAL_PORT.println(data.Quat9.Data.Accuracy);
387+
#else
388+
// Output the Quaternion data in the format expected by ZaneL's Node.js Quaternion animation tool
389+
SERIAL_PORT.print(F("{\"quat_w\":"));
390+
SERIAL_PORT.print(q0, 3);
391+
SERIAL_PORT.print(F(", \"quat_x\":"));
392+
SERIAL_PORT.print(q1, 3);
393+
SERIAL_PORT.print(F(", \"quat_y\":"));
394+
SERIAL_PORT.print(q2, 3);
395+
SERIAL_PORT.print(F(", \"quat_z\":"));
396+
SERIAL_PORT.print(q3, 3);
397+
SERIAL_PORT.println(F("}"));
398+
#endif
323399
}
324400
}
325401

0 commit comments

Comments
 (0)