4180 final project
Dependencies: LSM9DS0 USBDevice mbed
Quaternion/Quaternion.cpp
- Committer:
- jlee887
- Date:
- 2015-12-05
- Revision:
- 0:ebbc3cd3a61e
File content as of revision 0:ebbc3cd3a61e:
#include "Quaternion.h" #include "mbed.h" #define M_PI 3.14159265 Timer t; /** * Default constructor. **/ Quaternion::Quaternion() { q0 = 1.0f; q1 = 0.0f; q2 = 0.0f; q3 = 0.0f; twoKp = twoKpDef; twoKi = twoKiDef; sampleFreq = 0.0f; lastUpdate = 0L; now = 0L; integralFBx = 0.0f; integralFBy = 0.0f; integralFBz = 0.0f; t.start(); } /** * Updates the sample frequency based on the elapsed time. **/ void Quaternion::updateSampleFrequency() { now = t.read(); sampleFreq = 1.0 / ((now - lastUpdate)); lastUpdate = now; } /** * Returns the quaternion representation of the orientation. **/ void Quaternion::getQ(float * q) { q[0] = q0; q[1] = q1; q[2] = q2; q[3] = q3; } /** * Gets the linear acceleration by estimating gravity and then subtracting it. All accelerations * need to be scaled to 1 g. So if at 1 g your accelerometer reads 245, divide it by 245 before passing it * to this function. * @param *linearAccel, pointer to float array for linear accelerations, * @param ax, the scaled acceleration in the x direction. * @param ay, the scaled acceleration in the y direction. * @param az, the scaled acceleration in the z direction. **/ void Quaternion::getLinearAcceleration(float * linearAccel, float ax, float ay, float az) { float gravity[3]; getGravity(gravity); float xwog = ax - gravity[0]; float ywog = ay - gravity[1]; float zwog = az - gravity[2]; linearAccel[0] = xwog * 9.8; linearAccel[1] = ywog * 9.8; linearAccel[2] = zwog * 9.8; } /** * Returns the euler angles psi, theta and phi. **/ void Quaternion::getEulerAngles(float * angles) { angles[0] = atan2(2 * q1 * q2- 2 * q0 * q3, 2 * q0*q0 + 2 * q1 * q1 - 1) * 180/M_PI; // psi angles[1] = -asin(2 * q1 * q3 + 2 * q0 * q2) * 180/M_PI; // theta angles[2] = atan2(2 * q2 * q3 - 2 * q0 * q1, 2 * q0 * q0 + 2 * q3 * q3 - 1) * 180/M_PI; // phi } /** * Returns the yaw pitch and roll of the device. **/ void Quaternion::getYawPitchRoll(double * ypr) { ypr[0] = atan2(double(2*q1*q2 + 2*q0*q3), double(q0*q0 + q1*q1 - q2*q2 - q3*q3)) * 180/M_PI; //yaw ypr[1] = -asin(2*q0*q2 - 2*q1*q3) * 180/M_PI; // pitch ypr[2] = -atan2(2*q2*q3 + 2*q0*q1, -q0*q0 + q1*q1 + q2*q2 - q3*q3) * 180/M_PI; //roll } /** * Gets an estimation of gravity based on quaternion orientation representation. **/ void Quaternion::getGravity(float * gravity) { float q[4]; getQ(q); gravity[0] = 2 * (q[1] * q[3] - q[0] *q[2]); gravity[1] = 2 * (q[0] * q[1] + q[2] * q[3]); gravity[2] = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]; } /** * Calculates the quaternion representation based on a 6DOF sensor. * @param gx, the rotation about the x axis in rad/sec * @param gy, the rotation about the y axis in rad/sec * @param gz, the rotation about the z axis in rad/sec * @param ax, the raw acceleration in the x direction. * @param ay, the raw acceleration in the y direction. * @param az, the raw acceleration in the z direction. **/ void Quaternion::update6DOF(float gx, float gy, float gz, float ax, float ay, float az) { updateSampleFrequency(); float recipNorm; float halfvx, halfvy, halfvz; float halfex, halfey, halfez; float qa, qb, qc; // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) { // Normalise accelerometer measurement recipNorm = invSqrt(ax * ax + ay * ay + az * az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // Estimated direction of gravity and vector perpendicular to magnetic flux halfvx = q1 * q3 - q0 * q2; halfvy = q0 * q1 + q2 * q3; halfvz = q0 * q0 - 0.5f + q3 * q3; // Error is sum of cross product between estimated and measured direction of gravity halfex = (ay * halfvz - az * halfvy); halfey = (az * halfvx - ax * halfvz); halfez = (ax * halfvy - ay * halfvx); // Compute and apply integral feedback if enabled if(twoKi > 0.0f) { integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki integralFBy += twoKi * halfey * (1.0f / sampleFreq); integralFBz += twoKi * halfez * (1.0f / sampleFreq); gx += integralFBx; // apply integral feedback gy += integralFBy; gz += integralFBz; } else { integralFBx = 0.0f; // prevent integral windup integralFBy = 0.0f; integralFBz = 0.0f; } // Apply proportional feedback gx += twoKp * halfex; gy += twoKp * halfey; gz += twoKp * halfez; } // Integrate rate of change of quaternion gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors gy *= (0.5f * (1.0f / sampleFreq)); gz *= (0.5f * (1.0f / sampleFreq)); qa = q0; qb = q1; qc = q2; q0 += (-qb * gx - qc * gy - q3 * gz); q1 += (qa * gx + qc * gz - q3 * gy); q2 += (qa * gy - qb * gz + q3 * gx); q3 += (qa * gz + qb * gy - qc * gx); // Normalise quaternion recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= recipNorm; q1 *= recipNorm; q2 *= recipNorm; q3 *= recipNorm; } /** * Calculates the quaternion representation based on a 9DOF sensor. * @param gx, the rotation about the x axis in rad/sec * @param gy, the rotation about the y axis in rad/sec * @param gz, the rotation about the z axis in rad/sec * @param ax, the raw acceleration in the x direction. * @param ay, the raw acceleration in the y direction. * @param az, the raw acceleration in the z direction. * @param mx, the raw magnometer heading in the x direction. * @param my, the raw magnometer heading in the y direction. * @param mz, the raw magnometer heading in the z direction. **/ void Quaternion::update9DOF(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) { //update the frequency first. updateSampleFrequency(); float recipNorm; float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3; float hx, hy, bx, bz; float halfvx, halfvy, halfvz, halfwx, halfwy, halfwz; float halfex, halfey, halfez; float qa, qb, qc; // Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation) if((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) { update6DOF(gx, gy, gz, ax, ay, az); return; } // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) { // Normalise accelerometer measurement recipNorm = invSqrt(ax * ax + ay * ay + az * az); ax *= recipNorm; ay *= recipNorm; az *= recipNorm; // Normalise magnetometer measurement recipNorm = invSqrt(mx * mx + my * my + mz * mz); mx *= recipNorm; my *= recipNorm; mz *= recipNorm; // Auxiliary variables to avoid repeated arithmetic q0q0 = q0 * q0; q0q1 = q0 * q1; q0q2 = q0 * q2; q0q3 = q0 * q3; q1q1 = q1 * q1; q1q2 = q1 * q2; q1q3 = q1 * q3; q2q2 = q2 * q2; q2q3 = q2 * q3; q3q3 = q3 * q3; // Reference direction of Earth's magnetic field hx = 2.0f * (mx * (0.5f - q2q2 - q3q3) + my * (q1q2 - q0q3) + mz * (q1q3 + q0q2)); hy = 2.0f * (mx * (q1q2 + q0q3) + my * (0.5f - q1q1 - q3q3) + mz * (q2q3 - q0q1)); bx = sqrt(hx * hx + hy * hy); bz = 2.0f * (mx * (q1q3 - q0q2) + my * (q2q3 + q0q1) + mz * (0.5f - q1q1 - q2q2)); // Estimated direction of gravity and magnetic field halfvx = q1q3 - q0q2; halfvy = q0q1 + q2q3; halfvz = q0q0 - 0.5f + q3q3; halfwx = bx * (0.5f - q2q2 - q3q3) + bz * (q1q3 - q0q2); halfwy = bx * (q1q2 - q0q3) + bz * (q0q1 + q2q3); halfwz = bx * (q0q2 + q1q3) + bz * (0.5f - q1q1 - q2q2); // Error is sum of cross product between estimated direction and measured direction of field vectors halfex = (ay * halfvz - az * halfvy) + (my * halfwz - mz * halfwy); halfey = (az * halfvx - ax * halfvz) + (mz * halfwx - mx * halfwz); halfez = (ax * halfvy - ay * halfvx) + (mx * halfwy - my * halfwx); // Compute and apply integral feedback if enabled if(twoKi > 0.0f) { integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki integralFBy += twoKi * halfey * (1.0f / sampleFreq); integralFBz += twoKi * halfez * (1.0f / sampleFreq); gx += integralFBx; // apply integral feedback gy += integralFBy; gz += integralFBz; } else { integralFBx = 0.0f; // prevent integral windup integralFBy = 0.0f; integralFBz = 0.0f; } // Apply proportional feedback gx += twoKp * halfex; gy += twoKp * halfey; gz += twoKp * halfez; } // Integrate rate of change of quaternion gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors gy *= (0.5f * (1.0f / sampleFreq)); gz *= (0.5f * (1.0f / sampleFreq)); qa = q0; qb = q1; qc = q2; q0 += (-qb * gx - qc * gy - q3 * gz); q1 += (qa * gx + qc * gz - q3 * gy); q2 += (qa * gy - qb * gz + q3 * gx); q3 += (qa * gz + qb * gy - qc * gx); // Normalise quaternion recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= recipNorm; q1 *= recipNorm; q2 *= recipNorm; q3 *= recipNorm; } /** * Super fast inverted square root. **/ float Quaternion::invSqrt(float x) { float halfx = 0.5f * x; float y = x; long i = *(long*)&y; i = 0x5f3759df - (i>>1); y = *(float*)&i; y = y * (1.5f - (halfx * y * y)); return y; }