sparkfun / SparkFun_ICM-20948_ArduinoLibrary

Arduino support for ICM_20948 w/ portable C backbone
Other
160 stars 69 forks source link

Extracting accuracy for accelerometer, gyroscope and magnetometer #100

Open tomwcourtney opened 2 years ago

tomwcourtney commented 2 years ago

I am unable to obtain the accuracy of the raw sensors despite having them enabled. data->header never has the header2 bit set and hence the values of the I have tried rolling back to the library version around when these answers were posted that list how to access these values but the result was the same. Forum Post.

Has anyone been able to get non-zero values for

data->Accel_Accuracy data->Gyro_Accuracy data->Compass_Accuracy

using this library.

SamuelHawkins commented 1 year ago

I can only speak with any confidence about the gyro values but I suspect this carries across to the acc and compass values.

The Gyro_accuracy is not the number which to scale the raw gyro values by, but is an expression of accuracy, between 0~3. The lowest accuracy is 0 and 3 is the highest. This is probably done to save space in data registers on the chip.

This code implements the gyro scaling: https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/blob/fd948fbc5dbe080eddafe88e3c1f7eadf5baff69/src/ICM_20948.cpp#L220:L240

In this code, agmt.fss.g is accessing the same data as your data->Gyro_Accuracy. A Gyro_Accuracy value of 0 therefore corresponds to actual accuracy of 131, 1 to 65.5, 2 to 32.8, and 3 to 16.4. I assume 0 is the default scaling setting, hence your code returning 0 values.

Getting a value of 0 for the gyro accuracy is expected, though this isn't sufficient to say your implementation is working properly; that might require seeing the Gyro_Accuracy value change between 0 and 3 when loaded with other accuracy settings.

Hope this helps a little.

SamuelHawkins commented 1 year ago

Hello again!

I've found, oddly, that sometimes on startup (and very occasionally during operation), the data.Gyro_Accuracy term will begin to report a different value (in my case, erroneously reporting "0" when the true value is "3"), which does not correspond to an actual change in the resolution of the reported gyro values. This results in the gyro values suddenly changing in scale!

I do not know how much this behavior is caused by my implementation of the gyro scaling vs. caused by the library.

This has been a frustrating problem and my solve for now is to hardcode the gyro_accuracy number within my gyro scaling code, HOWEVER I think in light of this bug my previous answer is at best incomplete and at worst misinformation. Use at your own risk and please remember this problem I faced!

tomwcourtney commented 1 year ago

Thanks for your comments @SamuelHawkins. You wouldn't happen to know what the critical configuration is that has the DMP produce accuracy data for icm_20948_DMP_data_t data packets.

The IMU is working however whenever I look in data.Quat9.Data.Accuracy the accuracy is 0. I should note that I've ported the Cpp code to C for an STM32.

Example6_DMP_Quat9_Orientation.ino produces an accuracy reading in the 0 - 800 range however I get only 0.

SamuelHawkins commented 1 year ago

When you say "Example6_DMP_Quat9_Orientation.ino produces an accuracy reading in the 0 - 800 range" I assume you're referring to the output of this line, is that right?

If so, I haven't played with that code, and I'm not sure at the minute what values are expected from it. I am accessing data.Gyro_Accuracy, wherehas the example code (and your code) is accessing data.Quat9.Data.Accuracy. These are definitely different values, but are both part of the same data struct defined in ICM_20948_DMP.h. The comments on this structure in the .h file might be very useful for you.

My best guess right now is that you are accessing one of many accuracy values within the data struct. I think that your accuracy value might act more like an error bar for the calculated headings, while the value I am accessing is an indicator of the resolution. If you want to produce values for accuracy like the example code, I would start by copying all the function calls within void setup() of the example code, or gutting the contents of void loop() and writing your code in there, just to make sure you replicate the functionality of the example to start with.

Hope this helps, and apologies if I have misunderstood you or misjudged your current issue.

tomwcourtney commented 1 year ago

Hi Sam, yes I am referencing the line you've linked and yes as you say it's different to the data.Gyro_Accuracy you mention. I am at this stage hoping to get the accuracy/certainty of the DMP rather than the individual sensors so that I might filter out data from when the IMU becomes 'uncertain'.

In my ported STM version of Example 6 I manage to access and convert the quarternions to Euler's no problem but unfortunately accuracy is always 0. No doubt I've made a mistake in my porting from cpp to c but I am unsure where exactly haha. The configurations for the DMP firmware are fairly tedious. I am able to run an unsullied Example 6 on an Arduino with the IMU attached without issue.

I wonder if the 2 bytes of heading accuracy (thank you for highlighting that comment) relates only to compass heading?? Especially since only DMP modes that use the magnetometer feature an accuracy field.

My initialization function at the highest level looks like this:

ICM_20948_Status_e imu_init()
{

  if (imu_begin() != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (imu_initializeDMP() != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (imu_enable_sensors() != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (ICM_20948_enable_FIFO(&myICM, true) != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (ICM_20948_enable_DMP(&myICM, true) != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (ICM_20948_reset_DMP(&myICM) != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  if (ICM_20948_reset_FIFO(&myICM) != ICM_20948_Stat_Ok)
  {
    return ICM_20948_Stat_Err;
  }

  return ICM_20948_Stat_Ok;
}

And my test function looks like this:

static test_status_t test_imu(char *cmd_str)
{

  unsigned int test_dur = 0;

  test_status_t status = test_parse_cmd_args(cmd_str, &test_dur, 1);

  if (status != TEST_OK)
  {
    return status;
  }
  else
  {

    uint32_t start = osKernelGetTickCount();

    double q1 = 0;
    double q2 = 0;
    double q3 = 0;
    double q0 = 0;

    uint32_t count = 0;

    while (osKernelGetTickCount() - start < (test_dur * 1000))
    {
      icm_20948_DMP_data_t data;
      ICM_20948_Status_e readStatus;

      readStatus = imu_read_dmp(&data);

      if ((readStatus == ICM_20948_Stat_Ok))
      {

#ifdef IMU_QUAT9
        if ((data.header & DMP_header_bitmap_Quat9) > 0)
        {
          // Scale to +/- 1
          q1 = ((double) data.Quat9.Data.Q1) / 1073741824.0; // Convert to double. Divide by 2^30
          q2 = ((double) data.Quat9.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30
          q3 = ((double) data.Quat9.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30
          q0 = sqrt(1.0 - (((q1) * (q1)) + ((q2) * (q2)) + ((q3) * (q3))));
#elif defined IMU_QUAT6
        if ((data.header & DMP_header_bitmap_Quat6) > 0)
        {
          // Scale to +/- 1
          q1 = ((double) data.Quat6.Data.Q1) / 1073741824.0; // Convert to double. Divide by 2^30
          q2 = ((double) data.Quat6.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30
          q3 = ((double) data.Quat6.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30
          q0 = sqrt(1.0 - (((q1) * (q1)) + ((q2) * (q2)) + ((q3) * (q3))));
#endif

          double q2sqr = q2 * q2;

          // roll (x-axis rotation)
          double t0 = +2.0 * (q0 * q1 + q2 * q3);
          double t1 = +1.0 - 2.0 * (q1 * q1 + q2sqr);
          double roll = atan2(t0, t1) * 180.0 / PI;

          // pitch (y-axis rotation)
          double t2 = +2.0 * (q0 * q2 - q3 * q1);
          t2 = t2 > 1.0 ? 1.0 : t2;
          t2 = t2 < -1.0 ? -1.0 : t2;
          double pitch = asin(t2) * 180.0 / PI;

          // yaw (z-axis rotation)
          double t3 = +2.0 * (q0 * q3 + q1 * q2);
          double t4 = +1.0 - 2.0 * (q2sqr + q3 * q3);
          double yaw = atan2(t3, t4) * 180.0 / PI;
#ifndef IMU_QUAT9
          globals_myprintf("| Roll %-10.2lf | Pitch %-10.2lf | Yaw %-10.2lf | \n", roll, pitch, yaw);
#else
          globals_myprintf("| Roll %-10.2lf | Pitch %-10.2lf | Yaw %-10.2lf |  Accuracy  %-10u \n", roll, pitch, yaw,data.Quat9.Data.Accuracy);
#endif
        }
      }
    }
  }
  return TEST_OK;
}
texasfunambule commented 1 year ago

Same issue, I've been wondering about the meaning of the accuracy number (data.Quat9.Data.Accuracy). the type is int16_t, so 16 bits. Did not have time to check but my guess is that bits 0 and 1 are for one modality (say accel), then bits 4 and 5 for the next (say gyro) and 8 and 9 for the last (say compass)? Not sure about the position of the bits used (seems reasonnable to space every 4 bits). One way to test would be to list all the observed values.