Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
FusionAhrs.c
00001 /** 00002 * @file FusionAhrs.c 00003 * @author Seb Madgwick 00004 * @brief The AHRS sensor fusion algorithm to combines gyroscope, accelerometer, 00005 * and magnetometer measurements into a single measurement of orientation 00006 * relative to the Earth (NWU convention). 00007 * 00008 * The algorithm behaviour is governed by a gain. A low gain will decrease the 00009 * influence of the accelerometer and magnetometer so that the algorithm will 00010 * better reject disturbances causes by translational motion and temporary 00011 * magnetic distortions. However, a low gain will also increase the risk of 00012 * drift due to gyroscope calibration errors. A typical gain value suitable for 00013 * most applications is 0.5. 00014 * 00015 * The algorithm allows the application to define a minimum and maximum valid 00016 * magnetic field magnitude. The algorithm will ignore magnetic measurements 00017 * that fall outside of this range. This allows the algorithm to reject 00018 * magnetic measurements that do not represent the direction of magnetic North. 00019 * The typical magnitude of the Earth's magnetic field is between 20 uT and 00020 * 70 uT. 00021 * 00022 * The algorithm can be used without a magnetometer. Measurements of 00023 * orientation obtained using only gyroscope and accelerometer measurements 00024 * can be expected to drift in the yaw component of orientation only. The 00025 * application can reset the drift in yaw by setting the yaw to a specified 00026 * angle at any time. 00027 * 00028 * The algorithm provides the measurement of orientation as a quaternion. The 00029 * library includes functions for converting this quaternion to a rotation 00030 * matrix and Euler angles. 00031 * 00032 * The algorithm also provides a measurement of linear acceleration and Earth 00033 * acceleration. Linear acceleration is equal to the accelerometer measurement 00034 * with the 1 g of gravity removed. Earth acceleration is a measurement of 00035 * linear acceleration in the Earth coordinate frame. 00036 */ 00037 00038 //------------------------------------------------------------------------------ 00039 // Includes 00040 00041 #include "FusionAhrs.h" 00042 #include <float.h> // FLT_MAX 00043 #include <math.h> // atan2f, cosf, sinf 00044 00045 //------------------------------------------------------------------------------ 00046 // Definitions 00047 00048 /** 00049 * @brief Initial gain used during the initialisation period. The gain used by 00050 * each algorithm iteration will ramp down from this initial gain to the 00051 * specified algorithm gain over the initialisation period. 00052 */ 00053 #define INITIAL_GAIN (10.0f) 00054 00055 /** 00056 * @brief Initialisation period (in seconds). 00057 */ 00058 #define INITIALISATION_PERIOD (3.0f) 00059 00060 //------------------------------------------------------------------------------ 00061 // Functions 00062 00063 /** 00064 * @brief Initialises the AHRS algorithm structure. 00065 * @param fusionAhrs AHRS algorithm structure. 00066 * @param gain AHRS algorithm gain. 00067 */ 00068 void FusionAhrsInitialise(FusionAhrs * const fusionAhrs, const float gain) { 00069 fusionAhrs->gain = gain; 00070 fusionAhrs->minimumMagneticFieldSquared = 0.0f; 00071 fusionAhrs->maximumMagneticFieldSquared = FLT_MAX; 00072 fusionAhrs->quaternion = FUSION_QUATERNION_IDENTITY; 00073 fusionAhrs->linearAcceleration = FUSION_VECTOR3_ZERO; 00074 fusionAhrs->rampedGain = INITIAL_GAIN; 00075 fusionAhrs->zeroYawPending = false; 00076 } 00077 00078 /** 00079 * @brief Sets the AHRS algorithm gain. The gain must be equal or greater than 00080 * zero. 00081 * @param gain AHRS algorithm gain. 00082 */ 00083 void FusionAhrsSetGain(FusionAhrs * const fusionAhrs, const float gain) { 00084 fusionAhrs->gain = gain; 00085 } 00086 00087 /** 00088 * @brief Sets the minimum and maximum valid magnetic field magnitudes in uT. 00089 * @param fusionAhrs AHRS algorithm structure. 00090 * @param minimumMagneticField Minimum valid magnetic field magnitude. 00091 * @param maximumMagneticField Maximum valid magnetic field magnitude. 00092 */ 00093 void FusionAhrsSetMagneticField(FusionAhrs * const fusionAhrs, const float minimumMagneticField, const float maximumMagneticField) { 00094 fusionAhrs->minimumMagneticFieldSquared = minimumMagneticField * minimumMagneticField; 00095 fusionAhrs->maximumMagneticFieldSquared = maximumMagneticField * maximumMagneticField; 00096 } 00097 00098 /** 00099 * @brief Updates the AHRS algorithm. This function should be called for each 00100 * new gyroscope measurement. 00101 * @param fusionAhrs AHRS algorithm structure. 00102 * @param gyroscope Gyroscope measurement in degrees per second. 00103 * @param accelerometer Accelerometer measurement in g. 00104 * @param magnetometer Magnetometer measurement in uT. 00105 * @param samplePeriod Sample period in seconds. This is the difference in time 00106 * between the current and previous gyroscope measurements. 00107 */ 00108 void FusionAhrsUpdate(FusionAhrs * const fusionAhrs, const FusionVector3 gyroscope, const FusionVector3 accelerometer, const FusionVector3 magnetometer, const float samplePeriod) { 00109 #define Q fusionAhrs->quaternion.element // define shorthand label for more readable code 00110 00111 // Calculate feedback error 00112 FusionVector3 halfFeedbackError = FUSION_VECTOR3_ZERO; // scaled by 0.5 to avoid repeated multiplications by 2 00113 do { 00114 // Abandon feedback calculation if accelerometer measurement invalid 00115 if ((accelerometer.axis.x == 0.0f) && (accelerometer.axis.y == 0.0f) && (accelerometer.axis.z == 0.0f)) { 00116 break; 00117 } 00118 00119 // Calculate direction of gravity assumed by quaternion 00120 const FusionVector3 halfGravity = { 00121 .axis.x = Q.x * Q.z - Q.w * Q.y, 00122 .axis.y = Q.w * Q.x + Q.y * Q.z, 00123 .axis.z = Q.w * Q.w - 0.5f + Q.z * Q.z, 00124 }; // equal to 3rd column of rotation matrix representation scaled by 0.5 00125 00126 // Calculate accelerometer feedback error 00127 halfFeedbackError = FusionVectorCrossProduct(FusionVectorFastNormalise(accelerometer), halfGravity); 00128 00129 // Abandon magnetometer feedback calculation if magnetometer measurement invalid 00130 const float magnetometerMagnitudeSquared = FusionVectorMagnitudeSquared(magnetometer); 00131 if ((magnetometerMagnitudeSquared < fusionAhrs->minimumMagneticFieldSquared) || (magnetometerMagnitudeSquared > fusionAhrs->maximumMagneticFieldSquared)) { 00132 break; 00133 } 00134 00135 // Compute direction of 'magnetic west' assumed by quaternion 00136 const FusionVector3 halfWest = { 00137 .axis.x = Q.x * Q.y + Q.w * Q.z, 00138 .axis.y = Q.w * Q.w - 0.5f + Q.y * Q.y, 00139 .axis.z = Q.y * Q.z - Q.w * Q.x 00140 }; // equal to 2nd column of rotation matrix representation scaled by 0.5 00141 00142 // Calculate magnetometer feedback error 00143 halfFeedbackError = FusionVectorAdd(halfFeedbackError, FusionVectorCrossProduct(FusionVectorFastNormalise(FusionVectorCrossProduct(accelerometer, magnetometer)), halfWest)); 00144 00145 } while (false); 00146 00147 // Ramp down gain until initialisation complete 00148 if (fusionAhrs->gain == 0) { 00149 fusionAhrs->rampedGain = 0; // skip initialisation if gain is zero 00150 } 00151 float feedbackGain = fusionAhrs->gain; 00152 if (fusionAhrs->rampedGain > fusionAhrs->gain) { 00153 fusionAhrs->rampedGain -= (INITIAL_GAIN - fusionAhrs->gain) * samplePeriod / INITIALISATION_PERIOD; 00154 feedbackGain = fusionAhrs->rampedGain; 00155 } 00156 00157 // Convert gyroscope to radians per second scaled by 0.5 00158 FusionVector3 halfGyroscope = FusionVectorMultiplyScalar(gyroscope, 0.5f * FusionDegreesToRadians(1.0f)); 00159 00160 // Apply feedback to gyroscope 00161 halfGyroscope = FusionVectorAdd(halfGyroscope, FusionVectorMultiplyScalar(halfFeedbackError, feedbackGain)); 00162 00163 // Integrate rate of change of quaternion 00164 fusionAhrs->quaternion = FusionQuaternionAdd(fusionAhrs->quaternion, FusionQuaternionMultiplyVector(fusionAhrs->quaternion, FusionVectorMultiplyScalar(halfGyroscope, samplePeriod))); 00165 00166 // Normalise quaternion 00167 fusionAhrs->quaternion = FusionQuaternionFastNormalise(fusionAhrs->quaternion); 00168 00169 // Calculate linear acceleration 00170 const FusionVector3 gravity = { 00171 .axis.x = 2.0f * (Q.x * Q.z - Q.w * Q.y), 00172 .axis.y = 2.0f * (Q.w * Q.x + Q.y * Q.z), 00173 .axis.z = 2.0f * (Q.w * Q.w - 0.5f + Q.z * Q.z), 00174 }; // equal to 3rd column of rotation matrix representation 00175 fusionAhrs->linearAcceleration = FusionVectorSubtract(accelerometer, gravity); 00176 00177 #undef Q // undefine shorthand label 00178 } 00179 00180 /** 00181 * @brief Updates the AHRS algorithm. This function should be called for each 00182 * new gyroscope measurement. 00183 * @param fusionAhrs AHRS algorithm structure. 00184 * @param gyroscope Gyroscope measurement in degrees per second. 00185 * @param accelerometer Accelerometer measurement in g. 00186 * @param samplePeriod Sample period in seconds. This is the difference in time 00187 * between the current and previous gyroscope measurements. 00188 */ 00189 void FusionAhrsUpdateWithoutMagnetometer(FusionAhrs * const fusionAhrs, const FusionVector3 gyroscope, const FusionVector3 accelerometer, const float samplePeriod) { 00190 00191 // Update AHRS algorithm 00192 FusionAhrsUpdate(fusionAhrs, gyroscope, accelerometer, FUSION_VECTOR3_ZERO, samplePeriod); 00193 00194 // Zero yaw once initialisation complete 00195 if (FusionAhrsIsInitialising(fusionAhrs) == true) { 00196 fusionAhrs->zeroYawPending = true; 00197 } else { 00198 if (fusionAhrs->zeroYawPending == true) { 00199 FusionAhrsSetYaw(fusionAhrs, 0.0f); 00200 fusionAhrs->zeroYawPending = false; 00201 } 00202 } 00203 } 00204 00205 /** 00206 * @brief Gets the quaternion describing the sensor relative to the Earth. 00207 * @param fusionAhrs AHRS algorithm structure. 00208 * @return Quaternion describing the sensor relative to the Earth. 00209 */ 00210 FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs * const fusionAhrs) { 00211 return FusionQuaternionConjugate(fusionAhrs->quaternion); 00212 } 00213 00214 /** 00215 * @brief Gets the linear acceleration measurement equal to the accelerometer 00216 * measurement with the 1 g of gravity removed. 00217 * @param fusionAhrs AHRS algorithm structure. 00218 * @return Linear acceleration measurement. 00219 */ 00220 FusionVector3 FusionAhrsGetLinearAcceleration(const FusionAhrs * const fusionAhrs) { 00221 return fusionAhrs->linearAcceleration; 00222 } 00223 00224 /** 00225 * @brief Gets the Earth acceleration measurement equal to linear acceleration 00226 * in the Earth coordinate frame. 00227 * @param fusionAhrs AHRS algorithm structure. 00228 * @return Earth acceleration measurement. 00229 */ 00230 FusionVector3 FusionAhrsGetEarthAcceleration(const FusionAhrs * const fusionAhrs) { 00231 #define Q fusionAhrs->quaternion.element // define shorthand labels for more readable code 00232 #define A fusionAhrs->linearAcceleration.axis 00233 const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations 00234 const float qwqx = Q.w * Q.x; 00235 const float qwqy = Q.w * Q.y; 00236 const float qwqz = Q.w * Q.z; 00237 const float qxqy = Q.x * Q.y; 00238 const float qxqz = Q.x * Q.z; 00239 const float qyqz = Q.y * Q.z; 00240 const FusionVector3 earthAcceleration = { 00241 .axis.x = 2.0f * ((qwqw - 0.5f + Q.x * Q.x) * A.x + (qxqy - qwqz) * A.y + (qxqz + qwqy) * A.z), 00242 .axis.y = 2.0f * ((qxqy + qwqz) * A.x + (qwqw - 0.5f + Q.y * Q.y) * A.y + (qyqz - qwqx) * A.z), 00243 .axis.z = 2.0f * ((qxqz - qwqy) * A.x + (qyqz + qwqx) * A.y + (qwqw - 0.5f + Q.z * Q.z) * A.z), 00244 }; // transpose of a rotation matrix representation of the quaternion multiplied with the linear acceleration 00245 return earthAcceleration; 00246 #undef Q // undefine shorthand label 00247 #undef A 00248 } 00249 00250 /** 00251 * @brief Reinitialise the AHRS algorithm. 00252 * @param fusionAhrs AHRS algorithm structure. 00253 */ 00254 void FusionAhrsReinitialise(FusionAhrs * const fusionAhrs) { 00255 fusionAhrs->quaternion = FUSION_QUATERNION_IDENTITY; 00256 fusionAhrs->linearAcceleration = FUSION_VECTOR3_ZERO; 00257 fusionAhrs->rampedGain = INITIAL_GAIN; 00258 } 00259 00260 /** 00261 * @brief Returns true while the AHRS algorithm is initialising. 00262 * @param fusionAhrs AHRS algorithm structure. 00263 * @return True while the AHRS algorithm is initialising. 00264 */ 00265 bool FusionAhrsIsInitialising(const FusionAhrs * const fusionAhrs) { 00266 return fusionAhrs->rampedGain > fusionAhrs->gain; 00267 } 00268 00269 /** 00270 * @brief Sets the yaw component of the orientation measurement provided by the 00271 * AHRS algorithm. This function can be used to reset drift in yaw when the 00272 * AHRS algorithm is being used without a magnetometer. 00273 * @param fusionAhrs AHRS algorithm structure. 00274 * @param yaw Yaw angle in degrees. 00275 */ 00276 void FusionAhrsSetYaw(FusionAhrs * const fusionAhrs, const float yaw) { 00277 #define Q fusionAhrs->quaternion.element // define shorthand label for more readable code 00278 fusionAhrs->quaternion = FusionQuaternionNormalise(fusionAhrs->quaternion); // quaternion must be normalised accurately (approximation not sufficient) 00279 const float inverseYaw = atan2f(Q.x * Q.y + Q.w * Q.z, Q.w * Q.w - 0.5f + Q.x * Q.x); // Euler angle of conjugate 00280 const float halfInverseYawMinusOffset = 0.5f * (inverseYaw - FusionDegreesToRadians(yaw)); 00281 const FusionQuaternion inverseYawQuaternion = { 00282 .element.w = cosf(halfInverseYawMinusOffset), 00283 .element.x = 0.0f, 00284 .element.y = 0.0f, 00285 .element.z = -1.0f * sinf(halfInverseYawMinusOffset), 00286 }; 00287 fusionAhrs->quaternion = FusionQuaternionMultiply(inverseYawQuaternion, fusionAhrs->quaternion); 00288 #undef Q // undefine shorthand label 00289 } 00290 00291 //------------------------------------------------------------------------------ 00292 // End of file
Generated on Tue Jul 12 2022 21:08:03 by
1.7.2