4180 final project
Dependencies: LSM9DS0 USBDevice mbed
Diff: Quaternion/Quaternion.cpp
- Revision:
- 0:ebbc3cd3a61e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Quaternion/Quaternion.cpp Sat Dec 05 18:39:33 2015 +0000 @@ -0,0 +1,302 @@ + +#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; +}