/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "common.h" #include "gyro_cal.h" #include "gyro_still_det.h" #include "gyro_cal_init_for_test.h" #include "motion_sense.h" #include "test_util.h" #include #include #include #include float kToleranceGyroRps = 1e-6f; float kDefaultGravityMps2 = 9.81f; int kDefaultTemperatureKelvin = 298; #define NANOS_TO_SEC (1.0e-9f) #define NANO_PI (3.14159265359f) /** Unit conversion: milli-degrees to radians. */ #define MDEG_TO_RAD (NANO_PI / 180.0e3f) #define MSEC_TO_NANOS(x) ((uint64_t)((x) * (uint64_t)(1000000))) #define SEC_TO_NANOS(x) MSEC_TO_NANOS((x) * (uint64_t)(1000)) #define HZ_TO_PERIOD_NANOS(hz) (SEC_TO_NANOS(1024) / ((uint64_t)((hz)*1024))) struct motion_sensor_t motion_sensors[] = { [BASE] = {}, [LID] = {}, }; const unsigned int motion_sensor_count = ARRAY_SIZE(motion_sensors); /** * This function will return a uniformly distributed random value in the range * of (0,1). This is important that 0 and 1 are excluded because of how the * value is used in normal_random. For references: * - rand() / RAND_MAX yields the range [0,1] * - rand() / (RAND_MAX + 1) yields the range [0,1) * - (rand() + 1) / (RAND_MAX + 1) yields the range (0, 1) * * @return A uniformly distributed random value. */ static double rand_gen(void) { return ((double)(rand()) + 1.0) / ((double)(RAND_MAX) + 1.0); } /** * @return A normally distributed random value */ static float normal_random(void) { double v1 = rand_gen(); double v2 = rand_gen(); return (float)(cos(2 * 3.14 * v2) * sqrt(-2.0 * log(v1))); } /** * @param mean The mean to use for the normal distribution. * @param stddev The standard deviation to use for the normal distribution. * @return A normally distributed random value based on mean and stddev. */ static float normal_random2(float mean, float stddev) { return normal_random() * stddev + mean; } /** * Tests that a calibration is updated after a period where the IMU device is * stationary. Accelerometer and gyroscope measurements are simulated with data * sheet specs for the BMI160 at their respective noise floors. A magnetometer * sensor is also included in this test. * * @return EC_SUCCESS on success. */ static int test_gyro_cal_calibration(void) { int i; struct gyro_cal gyro_cal; /* * Statistics for simulated gyroscope data. * RMS noise = 70mDPS, offset = 150mDPS. */ /* [Hz] */ const float sample_rate = 400.0f; /* [rad/sec] */ const float gyro_bias = MDEG_TO_RAD * 150.0f; /* [rad/sec] */ const float gyro_rms_noise = MDEG_TO_RAD * 70.0f; const uint64_t sample_interval_nanos = HZ_TO_PERIOD_NANOS(sample_rate); /* * Statistics for simulated accelerometer data. * noise density = 200ug/rtHz, offset = 50mg. */ /* [m/sec^2] */ const float accel_bias = 0.05f * kDefaultGravityMps2; /* [m/sec^2] */ const float accel_rms_noise = 0.0002f * kDefaultGravityMps2 * fp_sqrtf(0.5f * sample_rate); /* * Statistics for simulated magnetometer data. * RMS noise = 0.4 micro Tesla (uT), offset = 0.2uT. */ const float mag_bias = 0.2f; /* [uT] */ const float mag_rms_noise = 0.4f; /* [uT] */ float bias[3]; float bias_residual[3]; int temperature_kelvin; uint32_t calibration_time_us = 0; bool calibration_received = false; gyro_cal_initialization_for_test(&gyro_cal); /* No calibration should be available yet. */ TEST_EQ(gyro_cal_new_bias_available(&gyro_cal), false, "%d"); /* * Simulate up to 20 seconds of sensor data (zero mean, additive white * Gaussian noise). */ for (i = 0; i < (int)(20.0f * sample_rate); ++i) { const uint32_t timestamp_us = (i * sample_interval_nanos) / 1000; /* Generate and add an accelerometer sample. */ gyro_cal_update_accel( &gyro_cal, timestamp_us, normal_random2(accel_bias, accel_rms_noise), normal_random2(accel_bias, accel_rms_noise), normal_random2(accel_bias, accel_rms_noise)); /* Generate and add a gyroscrope sample. */ gyro_cal_update_gyro(&gyro_cal, timestamp_us, normal_random2(gyro_bias, gyro_rms_noise), normal_random2(gyro_bias, gyro_rms_noise), normal_random2(gyro_bias, gyro_rms_noise), kDefaultTemperatureKelvin); /* * The simulated magnetometer here has a sampling rate that is * 4x slower than the accel/gyro */ if (i % 4 == 0) { gyro_cal_update_mag( &gyro_cal, timestamp_us, normal_random2(mag_bias, mag_rms_noise), normal_random2(mag_bias, mag_rms_noise), normal_random2(mag_bias, mag_rms_noise)); } calibration_received = gyro_cal_new_bias_available(&gyro_cal); if (calibration_received) break; } TEST_EQ(calibration_received, true, "%d"); gyro_cal_get_bias(&gyro_cal, bias, &temperature_kelvin, &calibration_time_us); bias_residual[0] = gyro_bias - bias[0]; bias_residual[1] = gyro_bias - bias[1]; bias_residual[2] = gyro_bias - bias[2]; /* * Make sure that the bias estimate is within 20 milli-degrees per * second. */ TEST_LT(bias_residual[0], 20.f * MDEG_TO_RAD, "%f"); TEST_LT(bias_residual[1], 20.f * MDEG_TO_RAD, "%f"); TEST_LT(bias_residual[2], 20.f * MDEG_TO_RAD, "%f"); TEST_NEAR(gyro_cal.stillness_confidence, 1.0f, 0.0001f, "%f"); TEST_EQ(temperature_kelvin, kDefaultTemperatureKelvin, "%d"); return EC_SUCCESS; } /** * Tests that calibration does not falsely occur for low-level motion. * * @return EC_SUCCESS on success. */ static int test_gyro_cal_no_calibration(void) { int i; struct gyro_cal gyro_cal; /* Statistics for simulated gyroscope data. */ /* RMS noise = 70mDPS, offset = 150mDPS. */ const float sample_rate = 400.0f; /* [Hz] */ const float gyro_bias = MDEG_TO_RAD * 150.0f; /* [rad/sec] */ const float gyro_rms_noise = MDEG_TO_RAD * 70.0f; /* [rad/sec] */ const uint64_t sample_interval_nanos = HZ_TO_PERIOD_NANOS(sample_rate); /* Statistics for simulated accelerometer data. */ /* noise density = 200ug/rtHz, offset = 50mg. */ /* [m/sec^2] */ const float accel_bias = 0.05f * kDefaultGravityMps2; /* [m/sec^2] */ const float accel_rms_noise = 200.0e-6f * kDefaultGravityMps2 * fp_sqrtf(0.5f * sample_rate); /* Define sinusoidal gyroscope motion parameters. */ const float omega_dt = 2.0f * NANO_PI * sample_interval_nanos * NANOS_TO_SEC; const float amplitude = MDEG_TO_RAD * 550.0f; /* [rad/sec] */ bool calibration_received = false; gyro_cal_initialization_for_test(&gyro_cal); for (i = 0; i < (int)(20.0f * sample_rate); ++i) { const uint32_t timestamp_us = (i * sample_interval_nanos) / 1000; /* Generate and add an accelerometer sample. */ gyro_cal_update_accel( &gyro_cal, timestamp_us, normal_random2(accel_bias, accel_rms_noise), normal_random2(accel_bias, accel_rms_noise), normal_random2(accel_bias, accel_rms_noise)); /* Generate and add a gyroscope sample. */ gyro_cal_update_gyro( &gyro_cal, timestamp_us, normal_random2(gyro_bias, gyro_rms_noise) + amplitude * sin(2.0f * omega_dt * i), normal_random2(gyro_bias, gyro_rms_noise) - amplitude * sin(2.1f * omega_dt * i), normal_random2(gyro_bias, gyro_rms_noise) + amplitude * cos(4.3f * omega_dt * i), kDefaultTemperatureKelvin); /* Check for calibration update. Break after first one. */ calibration_received = gyro_cal_new_bias_available(&gyro_cal); if (calibration_received) break; } /* Determine that NO calibration had occurred. */ TEST_EQ(calibration_received, false, "%d"); /* Make sure that the device was NOT classified as "still". */ TEST_GT(1.0f, gyro_cal.stillness_confidence, "%f"); return EC_SUCCESS; } /** * Tests that a shift in a stillness window mean does not trigger a calibration. * * @return EC_SUCCESS on success. */ static int test_gyro_cal_win_mean_shift(void) { struct gyro_cal gyro_cal; int i; /* Statistics for simulated gyroscope data. */ const float sample_rate = 400.0f; /* [Hz] */ const float gyro_bias = MDEG_TO_RAD * 150.0f; /* [rad/sec] */ const float gyro_bias_shift = MDEG_TO_RAD * 60.0f; /* [rad/sec] */ const uint64_t sample_interval_nanos = HZ_TO_PERIOD_NANOS(sample_rate); /* Initialize the gyro calibration. */ gyro_cal_initialization_for_test(&gyro_cal); /* * Simulates 8 seconds of sensor data (no noise, just a gyro mean shift * after 4 seconds). * Assumptions: The max stillness period is 6 seconds, and the mean * delta limit is 50mDPS. The mean shift should be detected and exceed * the 50mDPS limit, and no calibration should be triggered. NOTE: This * step is not large enough to trip the variance checking within the * stillness detectors. */ for (i = 0; i < (int)(8.0f * sample_rate); i++) { const uint32_t timestamp_us = (i * sample_interval_nanos) / 1000; /* Generate and add a accelerometer sample. */ gyro_cal_update_accel(&gyro_cal, timestamp_us, 0.0f, 0.0f, 9.81f); /* Generate and add a gyroscope sample. */ if (timestamp_us > 4 * SECOND) { gyro_cal_update_gyro(&gyro_cal, timestamp_us, gyro_bias + gyro_bias_shift, gyro_bias + gyro_bias_shift, gyro_bias + gyro_bias_shift, kDefaultTemperatureKelvin); } else { gyro_cal_update_gyro(&gyro_cal, timestamp_us, gyro_bias, gyro_bias, gyro_bias, kDefaultTemperatureKelvin); } } /* Determine that NO calibration had occurred. */ TEST_EQ(gyro_cal_new_bias_available(&gyro_cal), false, "%d"); return EC_SUCCESS; } /** * Tests that a temperature variation outside the acceptable range prevents a * calibration. * * @return EC_SUCCESS on success. */ static int test_gyro_cal_temperature_shift(void) { int i; struct gyro_cal gyro_cal; /* Statistics for simulated gyroscope data. */ const float sample_rate = 400.0f; /* [Hz] */ const float gyro_bias = MDEG_TO_RAD * 150.0f; /* [rad/sec] */ const float temperature_shift_kelvin = 2.6f; const uint64_t sample_interval_nanos = HZ_TO_PERIOD_NANOS(sample_rate); gyro_cal_initialization_for_test(&gyro_cal); /* * Simulates 8 seconds of sensor data (no noise, just a temperature * shift after 4 seconds). * Assumptions: The max stillness period is 6 seconds, and the * temperature delta limit is 1.5C. The shift should be detected and * exceed the limit, and no calibration should be triggered. */ for (i = 0; i < (int)(8.0f * sample_rate); i++) { const uint32_t timestamp_us = (i * sample_interval_nanos) / 1000; float temperature_kelvin = kDefaultTemperatureKelvin; /* Generate and add a accelerometer sample. */ gyro_cal_update_accel(&gyro_cal, timestamp_us, 0.0f, 0.0f, 9.81f); /* Sets the temperature value. */ if (timestamp_us > 4 * SECOND) temperature_kelvin += temperature_shift_kelvin; /* Generate and add a gyroscope sample. */ gyro_cal_update_gyro(&gyro_cal, timestamp_us, gyro_bias, gyro_bias, gyro_bias, (int)temperature_kelvin); } /* Determine that NO calibration had occurred. */ TEST_EQ(gyro_cal_new_bias_available(&gyro_cal), false, "%d"); return EC_SUCCESS; } /** * Verifies that complete sensor stillness results in the correct bias estimate * and produces the correct timestamp. * * @return EC_SUCCESS on success; */ static int test_gyro_cal_stillness_timestamp(void) { struct gyro_cal gyro_cal; int64_t time_us; /* * 10Hz update rate for 11 seconds should trigger the in-situ * algorithms. */ const float gyro_bias_x = 0.09f; const float gyro_bias_y = -0.04f; const float gyro_bias_z = 0.05f; float bias[3]; int temperature_kelvin = 273; uint32_t calibration_time_us = 0; gyro_cal_initialization_for_test(&gyro_cal); for (time_us = 0; time_us < 11 * SECOND; time_us += 100 * MSEC) { /* Generate and add a accelerometer sample. */ gyro_cal_update_accel(&gyro_cal, time_us, 0.0f, 0.0f, 9.81f); /* Generate and add a gyroscope sample. */ gyro_cal_update_gyro(&gyro_cal, time_us, gyro_bias_x, gyro_bias_y, gyro_bias_z, kDefaultTemperatureKelvin); } /* Determine if there is a new calibration. Get the calibration value. */ TEST_EQ(gyro_cal_new_bias_available(&gyro_cal), 1, "%d"); gyro_cal_get_bias(&gyro_cal, bias, &temperature_kelvin, &calibration_time_us); /* Make sure that the bias estimate is within kToleranceGyroRps. */ TEST_NEAR(gyro_bias_x - bias[0], 0.0f, 0.0001f, "%f"); TEST_NEAR(gyro_bias_y - bias[1], 0.0f, 0.0001f, "%f"); TEST_NEAR(gyro_bias_z - bias[2], 0.0f, 0.0001f, "%f"); /* Checks that the calibration occurred at the expected time. */ TEST_EQ(6 * SECOND, gyro_cal.calibration_time_us, "%u"); /* Make sure that the device was classified as 100% "still". */ TEST_NEAR(1.0f, gyro_cal.stillness_confidence, 0.0001f, "%f"); /* Make sure that the calibration temperature is correct. */ TEST_EQ(kDefaultTemperatureKelvin, temperature_kelvin, "%d"); return EC_SUCCESS; } /** * Verifies that setting an initial bias works. * * @return EC_SUCCESS on success. */ static int test_gyro_cal_set_bias(void) { struct gyro_cal gyro_cal; /* Get the initialized bias value; should be zero. */ float bias[3] = { 0.0f, 0.0f, 0.0f }; int temperature_kelvin = 273; uint32_t calibration_time_us = 10; /* Initialize the gyro calibration. */ gyro_cal_initialization_for_test(&gyro_cal); gyro_cal_get_bias(&gyro_cal, bias, &temperature_kelvin, &calibration_time_us); TEST_NEAR(0.0f, bias[0], 0.0001f, "%f"); TEST_NEAR(0.0f, bias[1], 0.0001f, "%f"); TEST_NEAR(0.0f, bias[2], 0.0001f, "%f"); TEST_EQ(0, temperature_kelvin, "%d"); TEST_EQ(0, calibration_time_us, "%d"); /* Set the calibration bias estimate. */ bias[0] = 1.0f; bias[1] = 2.0f; bias[2] = 3.0f; gyro_cal_set_bias(&gyro_cal, bias, 31, 3 * 60 * SECOND); bias[0] = 0.0f; bias[1] = 0.0f; bias[2] = 0.0f; /* Check that it was set correctly. */ gyro_cal_get_bias(&gyro_cal, bias, &temperature_kelvin, &calibration_time_us); TEST_NEAR(1.0f, bias[0], 0.0001f, "%f"); TEST_NEAR(2.0f, bias[1], 0.0001f, "%f"); TEST_NEAR(3.0f, bias[2], 0.0001f, "%f"); TEST_EQ(31, temperature_kelvin, "%d"); TEST_EQ(3 * 60 * SECOND, calibration_time_us, "%u"); return EC_SUCCESS; } /** * Verifies that the gyroCalRemoveBias function works as intended. * * @return EC_SUCCESS on success */ static int test_gyro_cal_remove_bias(void) { struct gyro_cal gyro_cal; float bias[3] = { 1.0f, 2.0f, 3.0f }; float bias_out[3]; /* Initialize the gyro calibration. */ gyro_cal_initialization_for_test(&gyro_cal); /* Set an calibration bias estimate. */ gyro_cal_set_bias(&gyro_cal, bias, kDefaultTemperatureKelvin, 5 * 60 * SECOND); /* Correct the bias, and check that it has been adequately removed. */ gyro_cal_remove_bias(&gyro_cal, bias, bias_out); /* Make sure that the bias estimate is within kToleranceGyroRps. */ TEST_NEAR(0.0f, bias_out[0], 0.0001f, "%f"); TEST_NEAR(0.0f, bias_out[1], 0.0001f, "%f"); TEST_NEAR(0.0f, bias_out[2], 0.0001f, "%f"); return EC_SUCCESS; } void run_test(int argc, char **argv) { test_reset(); RUN_TEST(test_gyro_cal_calibration); RUN_TEST(test_gyro_cal_no_calibration); RUN_TEST(test_gyro_cal_win_mean_shift); RUN_TEST(test_gyro_cal_temperature_shift); RUN_TEST(test_gyro_cal_stillness_timestamp); RUN_TEST(test_gyro_cal_set_bias); RUN_TEST(test_gyro_cal_remove_bias); test_print_result(); }