#include "IMU.h" void calibrateMagn(); void imuAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz); float invSqrt(float x); /****************************************************************************** * IMU module * ******************************************************************************/ // #define S_SCL 33 // #define S_SDA 32 AK09918_err_type_t err; QMI8658 qmi8658_; AK09918 magnetometer_; int16_t offset_x = -12, offset_y = 0, offset_z = 0; int16_t x, y, z; // Find the magnetic declination at your location // http://www.magnetic-declination.com/ double declination_shenzhen = -3.22; #define Kp 4.50f // proportional gain governs rate of convergence to accelerometer/magnetometer #define Ki 1.0f // integral gain governs rate of convergence of gyroscope biases float angles[3]; float q0, q1, q2, q3; float exInt = 0.0f; float eyInt = 0.0f; float ezInt = 0.0f; uint32_t lastFilterUpdateUs = 0; namespace { constexpr float kDegToRad = 0.01745329251994329577f; constexpr float kDefaultSampleDt = 0.01f; constexpr float kMaxSampleDt = 0.1f; float clampUnit(float value) { if (value > 1.0f) { return 1.0f; } if (value < -1.0f) { return -1.0f; } return value; } void resetFilterState() { q0 = 1.0f; q1 = 0.0f; q2 = 0.0f; q3 = 0.0f; exInt = 0.0f; eyInt = 0.0f; ezInt = 0.0f; lastFilterUpdateUs = micros(); } float getSampleDeltaSeconds() { const uint32_t nowUs = micros(); if (lastFilterUpdateUs == 0) { lastFilterUpdateUs = nowUs; return kDefaultSampleDt; } const uint32_t elapsedUs = nowUs - lastFilterUpdateUs; lastFilterUpdateUs = nowUs; const float deltaSeconds = elapsedUs * 1.0e-6f; if (deltaSeconds <= 0.0f || deltaSeconds > kMaxSampleDt) { return kDefaultSampleDt; } return deltaSeconds; } bool normalizeVector(float *xAxis, float *yAxis, float *zAxis) { const float normSquared = (*xAxis * *xAxis) + (*yAxis * *yAxis) + (*zAxis * *zAxis); if (normSquared <= 1.0e-12f) { return false; } const float norm = invSqrt(normSquared); *xAxis *= norm; *yAxis *= norm; *zAxis *= norm; return true; } } // namespace void imuInit() { // Wire.begin(S_SDA, S_SCL); // Serial.begin(115200); if (qmi8658_.begin() == 0) Serial.println("qmi8658_init fail"); if (magnetometer_.initialize()) Serial.println("AK09918_init fail") ; magnetometer_.switchMode(AK09918_CONTINUOUS_100HZ); err = magnetometer_.isDataReady(); int retry_times = 0; while (err != AK09918_ERR_OK) { Serial.println(err); Serial.println("Waiting Sensor"); delay(100); magnetometer_.reset(); delay(100); magnetometer_.switchMode(AK09918_CONTINUOUS_100HZ); err = magnetometer_.isDataReady(); retry_times ++; if (retry_times > 10) { break; } } // Serial.println("Start figure-8 calibration after 1 seconds."); // delay(1000); // calibrate(10000, &offset_x, &offset_y, &offset_z); // calibrateMagn(); resetFilterState(); } void imuDataGet(EulerAngles *pstAngles, IMU_ST_SENSOR_DATA_FLOAT *pstGyroRawData, IMU_ST_SENSOR_DATA_FLOAT *pstAccelRawData, IMU_ST_SENSOR_DATA *pstMagnRawData) { if (pstAngles == nullptr || pstGyroRawData == nullptr || pstAccelRawData == nullptr || pstMagnRawData == nullptr) { return; } float acc[3] = {0.0f, 0.0f, 0.0f}; float gyro[3] = {0.0f, 0.0f, 0.0f}; float MotionVal[9]; const AK09918_err_type_t magErr = magnetometer_.getData(&x, &y, &z); if (magErr == AK09918_ERR_OVERFLOW) { Serial.println("AK09918 overflow detected, keeping last valid magnetic sample."); } pstMagnRawData->s16X = x- offset_x; pstMagnRawData->s16Y = y- offset_y; pstMagnRawData->s16Z = z- offset_z; // qmi8658_.GetEulerAngles(&pstAngles->pitch,&pstAngles->roll,&pstAngles->yaw,acc,gyro); qmi8658_.read_sensor_data(acc,gyro); // pstAngles->roll = atan2((float)acc[1], (float)acc[2]); // pstAngles->pitch = atan2(-(float)acc[0], sqrt((float)(acc[1] * acc[1]) + (float)(acc[2] * acc[2]))); // double Xheading = pstMagnRawData->s16X * cos(pstAngles->pitch) + pstMagnRawData->s16Y * sin(pstAngles->roll) * sin(pstAngles->pitch) + pstMagnRawData->s16Z * cos(pstAngles->roll) * sin(pstAngles->pitch); // double Yheading = pstMagnRawData->s16Y * cos(pstAngles->roll) - pstMagnRawData->s16Z * sin(pstAngles->pitch); // pstAngles->yaw = /*180 + */57.3 * atan2(Yheading, Xheading) + declination_shenzhen; // pstAngles->roll = atan2((float)acc[1], (float)acc[2]) * 57.3; // pstAngles->pitch = atan2(-(float)acc[0], sqrt((float)(acc[1] * acc[1]) + (float)(acc[2] * acc[2]))) * 57.3; MotionVal[0]=gyro[0]; MotionVal[1]=gyro[1]; MotionVal[2]=gyro[2]; MotionVal[3]=acc[0]; MotionVal[4]=acc[1]; MotionVal[5]=acc[2]; MotionVal[6]=pstMagnRawData->s16X; MotionVal[7]=pstMagnRawData->s16Y; MotionVal[8]=pstMagnRawData->s16Z; imuAHRSupdate((float)MotionVal[0] * kDegToRad, (float)MotionVal[1] * kDegToRad, (float)MotionVal[2] * kDegToRad, (float)MotionVal[3], (float)MotionVal[4], (float)MotionVal[5], (float)MotionVal[6], (float)MotionVal[7], MotionVal[8]); pstAngles->pitch = asinf(clampUnit(-2.0f * q1 * q3 + 2.0f * q0 * q2)) * 57.2957795f; pstAngles->roll = atan2f(2.0f * q2 * q3 + 2.0f * q0 * q1, -2.0f * q1 * q1 - 2.0f * q2 * q2 + 1.0f) * 57.2957795f; pstAngles->yaw = atan2f(-2.0f * q1 * q2 - 2.0f * q0 * q3, 2.0f * q2 * q2 + 2.0f * q3 * q3 - 1.0f) * 57.2957795f; pstGyroRawData->X = gyro[0]; pstGyroRawData->Y = gyro[1]; pstGyroRawData->Z = gyro[2]; pstAccelRawData->X = acc[0]; pstAccelRawData->Y = acc[1]; pstAccelRawData->Z = acc[2]; return; } bool imuRecalibrate() { qmi8658_.autoOffsets(); resetFilterState(); return true; } void imuGetMagnOffsets(IMU_ST_SENSOR_DATA *pstMagnOffset) { if (pstMagnOffset == nullptr) { return; } pstMagnOffset->s16X = offset_x; pstMagnOffset->s16Y = offset_y; pstMagnOffset->s16Z = offset_z; } void imuSetMagnOffsets(int16_t inputX, int16_t inputY, int16_t inputZ) { offset_x = inputX; offset_y = inputY; offset_z = inputZ; resetFilterState(); } float imuGetTemperature() { return qmi8658_.read_temperature(); } void imuAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) { const float sampleDt = getSampleDeltaSeconds(); const float halfT = 0.5f * sampleDt; float hx = 0.0f, hy = 0.0f, hz = 0.0f, bx = 0.0f, bz = 0.0f; float vx = 0.0f, vy = 0.0f, vz = 0.0f, wx = 0.0f, wy = 0.0f, wz = 0.0f; float ex = 0.0f, ey = 0.0f, ez = 0.0f; float q0q0 = q0 * q0; float q0q1 = q0 * q1; float q0q2 = q0 * q2; float q0q3 = q0 * q3; float q1q1 = q1 * q1; float q1q2 = q1 * q2; float q1q3 = q1 * q3; float q2q2 = q2 * q2; float q2q3 = q2 * q3; float q3q3 = q3 * q3; const bool accelValid = normalizeVector(&ax, &ay, &az); const bool magnValid = normalizeVector(&mx, &my, &mz); if (accelValid) { vx = 2.0f * (q1q3 - q0q2); vy = 2.0f * (q0q1 + q2q3); vz = q0q0 - q1q1 - q2q2 + q3q3; ex += (ay * vz - az * vy); ey += (az * vx - ax * vz); ez += (ax * vy - ay * vx); } if (magnValid) { hx = 2.0f * mx * (0.5f - q2q2 - q3q3) + 2.0f * my * (q1q2 - q0q3) + 2.0f * mz * (q1q3 + q0q2); hy = 2.0f * mx * (q1q2 + q0q3) + 2.0f * my * (0.5f - q1q1 - q3q3) + 2.0f * mz * (q2q3 - q0q1); hz = 2.0f * mx * (q1q3 - q0q2) + 2.0f * my * (q2q3 + q0q1) + 2.0f * mz * (0.5f - q1q1 - q2q2); bx = sqrtf((hx * hx) + (hy * hy)); bz = hz; wx = 2.0f * bx * (0.5f - q2q2 - q3q3) + 2.0f * bz * (q1q3 - q0q2); wy = 2.0f * bx * (q1q2 - q0q3) + 2.0f * bz * (q0q1 + q2q3); wz = 2.0f * bx * (q0q2 + q1q3) + 2.0f * bz * (0.5f - q1q1 - q2q2); ex += (my * wz - mz * wy); ey += (mz * wx - mx * wz); ez += (mx * wy - my * wx); } if (accelValid || magnValid) { exInt += ex * Ki * sampleDt; eyInt += ey * Ki * sampleDt; ezInt += ez * Ki * sampleDt; gx = gx + Kp * ex + exInt; gy = gy + Kp * ey + eyInt; gz = gz + Kp * ez + ezInt; } const float q0Prev = q0; const float q1Prev = q1; const float q2Prev = q2; const float q3Prev = q3; q0 = q0Prev + (-q1Prev * gx - q2Prev * gy - q3Prev * gz) * halfT; q1 = q1Prev + (q0Prev * gx + q2Prev * gz - q3Prev * gy) * halfT; q2 = q2Prev + (q0Prev * gy - q1Prev * gz + q3Prev * gx) * halfT; q3 = q3Prev + (q0Prev * gz + q1Prev * gy - q2Prev * gx) * halfT; const float normSquared = q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3; if (normSquared <= 1.0e-12f) { resetFilterState(); return; } const float norm = invSqrt(normSquared); q0 = q0 * norm; q1 = q1 * norm; q2 = q2 * norm; q3 = q3 * norm; } float invSqrt(float x) { if (x <= 0.0f) { return 0.0f; } float halfx = 0.5f * x; union { float f; uint32_t i; } conv = { x }; conv.i = 0x5f3759df - (conv.i >> 1); float y = conv.f; y = y * (1.5f - (halfx * y * y)); return y; } void calibrateMagn(void) { int16_t temp[9]; Serial.printf("keep 10dof-imu device horizontal and it will read x y z axis offset value after 4 seconds\n"); delay(4000); Serial.printf("start read all axises offset value\n"); magnetometer_.getData(&x, &y, &z); temp[0] = x; temp[1] = y; temp[2] = z; Serial.printf("rotate z axis 180 degrees and it will read all axises offset value after 4 seconds\n"); delay(4000); Serial.printf("start read all axises offset value\n"); magnetometer_.getData(&x, &y, &z); temp[3] = x; temp[4] = y; temp[5] = z; Serial.printf("flip 10dof-imu device and keep it horizontal and it will read all axises offset value after 4 seconds\n"); delay(4000); Serial.printf("start read all axises offset value\n"); magnetometer_.getData(&x, &y, &z); temp[6] = x; temp[7] = y; temp[8] = z; offset_x = (temp[0]+temp[3])/2; offset_y = (temp[1]+temp[4])/2; offset_z = (temp[5]+temp[8])/2; }