Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/* Copyright (C) 2015 Kristian Sloth Lauszus. All rights reserved.
This software may be distributed and modified under the terms of the GNU
General Public License version 2 (GPL2) as published by the Free Software
Foundation and appearing in the file GPL2.TXT included in the packaging of
this file. Please note that GPL2 Section 2[b] requires that all works based
on this software must also be made publicly available under the terms of
the GPL2 ("Copyleft").
Contact information
-------------------
Kristian Sloth Lauszus
Web : http://www.lauszus.com
e-mail : lauszus@gmail.com
*/
// Inspired by: https://github.com/cleanflight/cleanflight/blob/78a4476506c06315d7296a010a2c7ba003146b44/src/main/flight/imu.c
#include <stdint.h>
#include <math.h>
#include "IMU.h"
#include "LowPassFilter.h"
#include "MPU6500.h"
#if UART_DEBUG
#include "utils/uartstdio.h" // Add "UART_BUFFERED" to preprocessor
#endif
static float calculateHeading(angle_t *angle, sensor_t *mag);
// Accelerometer readings can be in any scale, but gyro rate needs to be in deg/s
// Make sure that roll increases when tilting quadcopter to the right, pitch increases
// when pitching quadcopter upward and yaw increases when rotating quadcopter clockwise.
void getAngles(mpu6500_t *mpu6500, sensor_t *mag, angle_t *angle, float dt) {
static const float gyro_cmpf_factor = 600.0f;
#ifdef DEBUG
const float invGyroComplimentaryFilterFactor = (1.0f / (gyro_cmpf_factor + 1.0f));
#else
static const float invGyroComplimentaryFilterFactor = (1.0f / (gyro_cmpf_factor + 1.0f));
#endif
static low_pass_t acc_low_pass[3] = { // Cutoff frequency in Hz - TODO: Set in Android app
{ .Fc = 53.05f },
{ .Fc = 53.05f },
{ .Fc = 53.05f },
};
sensor_t gyro; // Gyro readings in rad/s
sensor_t accLPF; // Accelerometer values after low pass filter
float accMagSquared = 0; // Accelerometer magnitude squared
for (uint8_t axis = 0; axis < 3; axis++) {
gyro.data[axis] = mpu6500->gyroRate.data[axis] * DEG_TO_RAD; // Convert from deg/s to rad/s
accLPF.data[axis] = applyLowPass(&acc_low_pass[axis], mpu6500->acc.data[axis], dt); // Apply low-pass filter
accMagSquared += accLPF.data[axis] * accLPF.data[axis]; // Update magnitude
}
angle_t deltaAngle = { .data = { gyro.axis.X * dt, gyro.axis.Y * dt, gyro.axis.Z * dt } };
rotateV(&mpu6500->accBodyFrame, &deltaAngle); // Rotate body frame according to delta angle given by gyro reading
accMagSquared /= mpu6500->accScaleFactor * mpu6500->accScaleFactor; // Convert readings to g's
if (0.72f < accMagSquared && accMagSquared < 1.32f) { // Check if < 0.85G or > 1.15G, if so we just skip new accelerometer readings
for (uint8_t axis = 0; axis < 3; axis++)
mpu6500->accBodyFrame.data[axis] = (mpu6500->accBodyFrame.data[axis] * gyro_cmpf_factor + accLPF.data[axis]) * invGyroComplimentaryFilterFactor; // Complimentary filter accelerometer gyro readings
}
// Source: http://www.freescale.com/files/sensors/doc/app_note/AN3461.pdf eq. 25 and eq. 26
// atan2 outputs the value of -p to p (radians) - see http://en.wikipedia.org/wiki/Atan2
// Note that the app-note assumes that the accelerometer is reading +1g when aligned with gravity, so the accelerations are inverted
// It is then converted from radians to degrees
#if 0 // Set to 0 to restrict roll to +-90deg instead - please read: http://www.freescale.com/files/sensors/doc/app_note/AN3461.pdf
// Eq. 25 and 26
angle->axis.roll = atan2f(-mpu6500->accBodyFrame.axis.Y, -mpu6500->accBodyFrame.axis.Z);
angle->axis.pitch = atan2f(mpu6500->accBodyFrame.axis.X, sqrtf(mpu6500->accBodyFrame.axis.Y * mpu6500->accBodyFrame.axis.Y + mpu6500->accBodyFrame.axis.Z * mpu6500->accBodyFrame.axis.Z)); // Use atan2 here anyway, to prevent division by 0
#else
// Eq. 28 and 29
angle->axis.roll = atan2f(-mpu6500->accBodyFrame.axis.Y, sqrtf(mpu6500->accBodyFrame.axis.X * mpu6500->accBodyFrame.axis.X + mpu6500->accBodyFrame.axis.Z * mpu6500->accBodyFrame.axis.Z)); // Use atan2 here anyway, to prevent division by 0
angle->axis.pitch = atan2f(mpu6500->accBodyFrame.axis.X, -mpu6500->accBodyFrame.axis.Z);
#endif
#if USE_MAG
static const float gyro_cmpfm_factor = 250.0f;
#ifdef DEBUG
const float invGyroComplimentaryFilter_M_Factor = (1.0f / (gyro_cmpfm_factor + 1.0f));
#else
static const float invGyroComplimentaryFilter_M_Factor = (1.0f / (gyro_cmpfm_factor + 1.0f));
#endif
static sensor_t magBodyFrame; // Magnitude of the earth magnetic field in the body frame
rotateV(&magBodyFrame, &deltaAngle); // Rotate body frame according to delta angle given by the gyro reading
for (uint8_t axis = 0; axis < 3; axis++) // TODO: Should this only be done when there is actual new magnetometer data?
magBodyFrame.data[axis] = (magBodyFrame.data[axis] * gyro_cmpfm_factor + mag->data[axis]) * invGyroComplimentaryFilter_M_Factor; // Use complimentary filter on magnetometer values
angle->axis.yaw = calculateHeading(angle, &magBodyFrame); // Get heading in degrees
#else
rotateV(mag, &deltaAngle); // Rotate body frame according to delta angle given by the gyro reading
angle->axis.yaw = calculateHeading(angle, mag); // Get heading in degrees
#endif
// Convert readings to degrees
angle->axis.roll *= RAD_TO_DEG;
angle->axis.pitch *= RAD_TO_DEG;
#if 0 && UART_DEBUG
static angle_t gyroAngle;
for (uint8_t axis = 0; axis < 3; axis++)
gyroAngle.data[axis] += mpu6500->gyroRate.data[axis] * dt; // Gyro angle is only used for debugging
// Make sure the data goes in the same direction
UARTprintf("%d\t%d\t", (int16_t)gyroAngle.axis.roll, (int16_t)angle->axis.roll);
UARTprintf("%d\t%d\t", (int16_t)gyroAngle.axis.pitch, (int16_t)angle->axis.pitch);
UARTprintf("%d\t%d\n", (int16_t)gyroAngle.axis.yaw, (int16_t)angle->axis.yaw);
UARTFlushTx(false);
#endif
}
// See: http://www.freescale.com/files/sensors/doc/app_note/AN4248.pdf eq. 22
static float calculateHeading(angle_t *angle, sensor_t *mag) {
#if USE_MAG
#ifndef DEBUG
// Calculate magnetic declination
static const int16_t magDeclinationFromConfig = -317; // Get your local magnetic declination here: http://magnetic-declination.com (Mine was +3 deg 17')
static const int16_t deg = magDeclinationFromConfig / 100;
static const int16_t min = magDeclinationFromConfig % 100;
static const float magneticDeclination = (deg + ((float)min * (1.0f / 60.0f)));
#else
static const float magneticDeclination = -3.28f;
#endif
#endif
float cosx = cosf(angle->axis.roll);
float sinx = sinf(angle->axis.roll);
float cosy = cosf(angle->axis.pitch);
float siny = sinf(angle->axis.pitch);
float Bfy = mag->axis.Z * sinx - mag->axis.Y * cosx;
float Bfx = mag->axis.X * cosy + mag->axis.Y * siny * sinx + mag->axis.Z * siny * cosx;
#if USE_MAG
float heading = atan2f(Bfy, Bfx) * RAD_TO_DEG + magneticDeclination; // Return heading
#else
float heading = atan2f(Bfy, Bfx) * RAD_TO_DEG; // Return heading
#endif
if (heading < 0) // Convert heading range to 0-360
heading += 360;
return heading;
}
// Rotate accelerometer coordinate axis by a delta angle from gyroscope
// Rotates from NED to body frame using the xyz-convention
// See: http://mathworld.wolfram.com/RotationMatrix.html,
// http://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Euler_angles_.28x-y-z_extrinsic.29_.E2.86.92_Rotation_matrix and
// https://engineering.purdue.edu/~bethel/rot2.pdf
void rotateV(sensor_t *v, const angle_t *angle) {
sensor_t v_tmp = *v;
float cosx = cosf(angle->axis.roll);
float sinx = sinf(angle->axis.roll);
float cosy = cosf(angle->axis.pitch);
float siny = sinf(angle->axis.pitch);
float cosz = cosf(angle->axis.yaw);
float sinz = sinf(angle->axis.yaw);
float coszcosx = cosz * cosx;
float sinzcosx = sinz * cosx;
float coszsinx = sinx * cosz;
float sinzsinx = sinx * sinz;
float mat[3][3];
mat[0][0] = cosz * cosy;
mat[0][1] = -cosy * sinz;
mat[0][2] = siny;
mat[1][0] = sinzcosx + (coszsinx * siny);
mat[1][1] = coszcosx - (sinzsinx * siny);
mat[1][2] = -sinx * cosy;
mat[2][0] = (sinzsinx) - (coszcosx * siny);
mat[2][1] = (coszsinx) + (sinzcosx * siny);
mat[2][2] = cosy * cosx;
v->axis.X = v_tmp.axis.X * mat[0][0] + v_tmp.axis.Y * mat[1][0] + v_tmp.axis.Z * mat[2][0];
v->axis.Y = v_tmp.axis.X * mat[0][1] + v_tmp.axis.Y * mat[1][1] + v_tmp.axis.Z * mat[2][1];
v->axis.Z = v_tmp.axis.X * mat[0][2] + v_tmp.axis.Y * mat[1][2] + v_tmp.axis.Z * mat[2][2];
}
33ba1246aThis file is pinned to the selected release. Download the original to open it in your design tool.