Created
June 23, 2016 11:25
-
-
Save arvind-iyer/f146533f370960366e11a84cbce45bb5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MPU-6050 Accelerometer + Gyro | |
// ----------------------------- | |
// | |
// By arduino.cc user "Krodal". | |
// June 2012 | |
// Open Source / Public Domain | |
// | |
// Using Arduino 1.0.1 | |
// It will not work with an older version, | |
// since Wire.endTransmission() uses a parameter | |
// to hold or release the I2C bus. | |
// | |
// Documentation: | |
// - The InvenSense documents: | |
// - "MPU-6000 and MPU-6050 Product Specification", | |
// PS-MPU-6000A.pdf | |
// - "MPU-6000 and MPU-6050 Register Map and Descriptions", | |
// RM-MPU-6000A.pdf or RS-MPU-6000A.pdf | |
// - "MPU-6000/MPU-6050 9-Axis Evaluation Board User Guide" | |
// AN-MPU-6000EVB.pdf | |
// | |
// The accuracy is 16-bits. | |
// | |
// Temperature sensor from -40 to +85 degrees Celsius | |
// 340 per degrees, -512 at 35 degrees. | |
// | |
// At power-up, all registers are zero, except these two: | |
// Register 0x6B (PWR_MGMT_2) = 0x40 (I read zero). | |
// Register 0x75 (WHO_AM_I) = 0x68. | |
// | |
#include <Wire.h> | |
// Register names according to the datasheet. | |
// According to the InvenSense document | |
// "MPU-6000 and MPU-6050 Register Map | |
// and Descriptions Revision 3.2", there are no registers | |
// at 0x02 ... 0x18, but according other information | |
// the registers in that unknown area are for gain | |
// and offsets. | |
// | |
#define MPU6050_ACCEL_XOUT_H 0x3B // R | |
#define MPU6050_ACCEL_XOUT_L 0x3C // R | |
#define MPU6050_ACCEL_YOUT_H 0x3D // R | |
#define MPU6050_ACCEL_YOUT_L 0x3E // R | |
#define MPU6050_ACCEL_ZOUT_H 0x3F // R | |
#define MPU6050_ACCEL_ZOUT_L 0x40 // R | |
#define MPU6050_TEMP_OUT_H 0x41 // R | |
#define MPU6050_TEMP_OUT_L 0x42 // R | |
#define MPU6050_GYRO_XOUT_H 0x43 // R | |
#define MPU6050_GYRO_XOUT_L 0x44 // R | |
#define MPU6050_GYRO_YOUT_H 0x45 // R | |
#define MPU6050_GYRO_YOUT_L 0x46 // R | |
#define MPU6050_GYRO_ZOUT_H 0x47 // R | |
#define MPU6050_GYRO_ZOUT_L 0x48 // R | |
#define MPU6050_PWR_MGMT_1 0x6B // R/W | |
#define MPU6050_PWR_MGMT_2 0x6C // R/W | |
#define MPU6050_WHO_AM_I 0x75 // R | |
// Default I2C address for the MPU-6050 is 0x68. | |
// But only if the AD0 pin is low. | |
// Some sensor boards have AD0 high, and the | |
// I2C address thus becomes 0x69. | |
#define MPU6050_I2C_ADDRESS 0x68 | |
// Declaring an union for the registers and the axis values. | |
// The byte order does not match the byte order of | |
// the compiler and AVR chip. | |
// The AVR chip (on the Arduino board) has the Low Byte | |
// at the lower address. | |
// But the MPU-6050 has a different order: High Byte at | |
// lower address, so that has to be corrected. | |
// The register part "reg" is only used internally, | |
// and are swapped in code. | |
typedef union accel_t_gyro_union | |
{ | |
struct | |
{ | |
uint8_t x_accel_h; | |
uint8_t x_accel_l; | |
uint8_t y_accel_h; | |
uint8_t y_accel_l; | |
uint8_t z_accel_h; | |
uint8_t z_accel_l; | |
uint8_t t_h; | |
uint8_t t_l; | |
uint8_t x_gyro_h; | |
uint8_t x_gyro_l; | |
uint8_t y_gyro_h; | |
uint8_t y_gyro_l; | |
uint8_t z_gyro_h; | |
uint8_t z_gyro_l; | |
} reg; | |
struct | |
{ | |
int x_accel; | |
int y_accel; | |
int z_accel; | |
int temperature; | |
int x_gyro; | |
int y_gyro; | |
int z_gyro; | |
} value; | |
}; | |
// Use the following global variables and access functions to help store the overall | |
// rotation angle of the sensor | |
unsigned long last_read_time; | |
float last_x_angle; // These are the filtered angles | |
float last_y_angle; | |
float last_z_angle; | |
float last_gyro_x_angle; // Store the gyro angles to compare drift | |
float last_gyro_y_angle; | |
float last_gyro_z_angle; | |
void set_last_read_angle_data(unsigned long time, float x, float y, float z, float x_gyro, float y_gyro, float z_gyro) { | |
last_read_time = time; | |
last_x_angle = x; | |
last_y_angle = y; | |
last_z_angle = z; | |
last_gyro_x_angle = x_gyro; | |
last_gyro_y_angle = y_gyro; | |
last_gyro_z_angle = z_gyro; | |
} | |
inline unsigned long get_last_time() {return last_read_time;} | |
inline float get_last_x_angle() {return last_x_angle;} | |
inline float get_last_y_angle() {return last_y_angle;} | |
inline float get_last_z_angle() {return last_z_angle;} | |
inline float get_last_gyro_x_angle() {return last_gyro_x_angle;} | |
inline float get_last_gyro_y_angle() {return last_gyro_y_angle;} | |
inline float get_last_gyro_z_angle() {return last_gyro_z_angle;} | |
// Use the following global variables and access functions | |
// to calibrate the acceleration sensor | |
float base_x_accel; | |
float base_y_accel; | |
float base_z_accel; | |
float base_x_gyro; | |
float base_y_gyro; | |
float base_z_gyro; | |
int read_gyro_accel_vals(uint8_t* accel_t_gyro_ptr) { | |
// Read the raw values. | |
// Read 14 bytes at once, | |
// containing acceleration, temperature and gyro. | |
// With the default settings of the MPU-6050, | |
// there is no filter enabled, and the values | |
// are not very stable. Returns the error value | |
accel_t_gyro_union* accel_t_gyro = (accel_t_gyro_union *) accel_t_gyro_ptr; | |
int error = MPU6050_read (MPU6050_ACCEL_XOUT_H, (uint8_t *) accel_t_gyro, sizeof(*accel_t_gyro)); | |
// Swap all high and low bytes. | |
// After this, the registers values are swapped, | |
// so the structure name like x_accel_l does no | |
// longer contain the lower byte. | |
uint8_t swap; | |
#define SWAP(x,y) swap = x; x = y; y = swap | |
SWAP ((*accel_t_gyro).reg.x_accel_h, (*accel_t_gyro).reg.x_accel_l); | |
SWAP ((*accel_t_gyro).reg.y_accel_h, (*accel_t_gyro).reg.y_accel_l); | |
SWAP ((*accel_t_gyro).reg.z_accel_h, (*accel_t_gyro).reg.z_accel_l); | |
SWAP ((*accel_t_gyro).reg.t_h, (*accel_t_gyro).reg.t_l); | |
SWAP ((*accel_t_gyro).reg.x_gyro_h, (*accel_t_gyro).reg.x_gyro_l); | |
SWAP ((*accel_t_gyro).reg.y_gyro_h, (*accel_t_gyro).reg.y_gyro_l); | |
SWAP ((*accel_t_gyro).reg.z_gyro_h, (*accel_t_gyro).reg.z_gyro_l); | |
return error; | |
} | |
// The sensor should be motionless on a horizontal surface | |
// while calibration is happening | |
void calibrate_sensors() { | |
int num_readings = 10; | |
float x_accel = 0; | |
float y_accel = 0; | |
float z_accel = 0; | |
float x_gyro = 0; | |
float y_gyro = 0; | |
float z_gyro = 0; | |
accel_t_gyro_union accel_t_gyro; | |
//Serial.println("Starting Calibration"); | |
// Discard the first set of values read from the IMU | |
read_gyro_accel_vals((uint8_t *) &accel_t_gyro); | |
// Read and average the raw values from the IMU | |
for (int i = 0; i < num_readings; i++) { | |
read_gyro_accel_vals((uint8_t *) &accel_t_gyro); | |
x_accel += accel_t_gyro.value.x_accel; | |
y_accel += accel_t_gyro.value.y_accel; | |
z_accel += accel_t_gyro.value.z_accel; | |
x_gyro += accel_t_gyro.value.x_gyro; | |
y_gyro += accel_t_gyro.value.y_gyro; | |
z_gyro += accel_t_gyro.value.z_gyro; | |
delay(100); | |
} | |
x_accel /= num_readings; | |
y_accel /= num_readings; | |
z_accel /= num_readings; | |
x_gyro /= num_readings; | |
y_gyro /= num_readings; | |
z_gyro /= num_readings; | |
// Store the raw calibration values globally | |
base_x_accel = x_accel; | |
base_y_accel = y_accel; | |
base_z_accel = z_accel; | |
base_x_gyro = x_gyro; | |
base_y_gyro = y_gyro; | |
base_z_gyro = z_gyro; | |
//Serial.println("Finishing Calibration"); | |
} | |
void setup() | |
{ | |
int error; | |
uint8_t c; | |
Serial.begin(9600); | |
/* | |
Serial.println(F("InvenSense MPU-6050")); | |
Serial.println(F("June 2012")); | |
*/ | |
// Initialize the 'Wire' class for the I2C-bus. | |
Wire.begin(); | |
// default at power-up: | |
// Gyro at 250 degrees second | |
// Acceleration at 2g | |
// Clock source at internal 8MHz | |
// The device is in sleep mode. | |
// | |
error = MPU6050_read (MPU6050_WHO_AM_I, &c, 1); | |
/* | |
Serial.print(F("WHO_AM_I : ")); | |
Serial.print(c,HEX); | |
Serial.print(F(", error = ")); | |
Serial.println(error,DEC); | |
*/ | |
// According to the datasheet, the 'sleep' bit | |
// should read a '1'. But I read a '0'. | |
// That bit has to be cleared, since the sensor | |
// is in sleep mode at power-up. Even if the | |
// bit reads '0'. | |
error = MPU6050_read (MPU6050_PWR_MGMT_2, &c, 1); | |
/* | |
Serial.print(F("PWR_MGMT_2 : ")); | |
Serial.print(c,HEX); | |
Serial.print(F(", error = ")); | |
Serial.println(error,DEC); | |
*/ | |
// Clear the 'sleep' bit to start the sensor. | |
MPU6050_write_reg (MPU6050_PWR_MGMT_1, 0); | |
//Initialize the angles | |
calibrate_sensors(); | |
set_last_read_angle_data(millis(), 0, 0, 0, 0, 0, 0); | |
} | |
void loop() | |
{ | |
int error; | |
double dT; | |
accel_t_gyro_union accel_t_gyro; | |
/* | |
Serial.println(F("")); | |
Serial.println(F("MPU-6050")); | |
*/ | |
// Read the raw values. | |
error = read_gyro_accel_vals((uint8_t*) &accel_t_gyro); | |
// Get the time of reading for rotation computations | |
unsigned long t_now = millis(); | |
/* | |
Serial.print(F("Read accel, temp and gyro, error = ")); | |
Serial.println(error,DEC); | |
// Print the raw acceleration values | |
Serial.print(F("accel x,y,z: ")); | |
Serial.print(accel_t_gyro.value.x_accel, DEC); | |
Serial.print(F(", ")); | |
Serial.print(accel_t_gyro.value.y_accel, DEC); | |
Serial.print(F(", ")); | |
Serial.print(accel_t_gyro.value.z_accel, DEC); | |
Serial.println(F("")); | |
*/ | |
// The temperature sensor is -40 to +85 degrees Celsius. | |
// It is a signed integer. | |
// According to the datasheet: | |
// 340 per degrees Celsius, -512 at 35 degrees. | |
// At 0 degrees: -512 - (340 * 35) = -12412 | |
/* | |
Serial.print(F("temperature: ")); | |
dT = ( (double) accel_t_gyro.value.temperature + 12412.0) / 340.0; | |
Serial.print(dT, 3); | |
Serial.print(F(" degrees Celsius")); | |
Serial.println(F("")); | |
// Print the raw gyro values. | |
Serial.print(F("raw gyro x,y,z : ")); | |
Serial.print(accel_t_gyro.value.x_gyro, DEC); | |
Serial.print(F(", ")); | |
Serial.print(accel_t_gyro.value.y_gyro, DEC); | |
Serial.print(F(", ")); | |
Serial.print(accel_t_gyro.value.z_gyro, DEC); | |
Serial.print(F(", ")); | |
Serial.println(F("")); | |
*/ | |
// Convert gyro values to degrees/sec | |
float FS_SEL = 131; | |
/* | |
float gyro_x = (accel_t_gyro.value.x_gyro - base_x_gyro)/FS_SEL; | |
float gyro_y = (accel_t_gyro.value.y_gyro - base_y_gyro)/FS_SEL; | |
float gyro_z = (accel_t_gyro.value.z_gyro - base_z_gyro)/FS_SEL; | |
*/ | |
float gyro_x = (accel_t_gyro.value.x_gyro - base_x_gyro)/FS_SEL; | |
float gyro_y = (accel_t_gyro.value.y_gyro - base_y_gyro)/FS_SEL; | |
float gyro_z = (accel_t_gyro.value.z_gyro - base_z_gyro)/FS_SEL; | |
// Get raw acceleration values | |
//float G_CONVERT = 16384; | |
float accel_x = accel_t_gyro.value.x_accel; | |
float accel_y = accel_t_gyro.value.y_accel; | |
float accel_z = accel_t_gyro.value.z_accel; | |
// Get angle values from accelerometer | |
float RADIANS_TO_DEGREES = 180/3.14159; | |
// float accel_vector_length = sqrt(pow(accel_x,2) + pow(accel_y,2) + pow(accel_z,2)); | |
float accel_angle_y = atan(-1*accel_x/sqrt(pow(accel_y,2) + pow(accel_z,2)))*RADIANS_TO_DEGREES; | |
float accel_angle_x = atan(accel_y/sqrt(pow(accel_x,2) + pow(accel_z,2)))*RADIANS_TO_DEGREES; | |
float accel_angle_z = 0; | |
// Compute the (filtered) gyro angles | |
float dt =(t_now - get_last_time())/1000.0; | |
float gyro_angle_x = gyro_x*dt + get_last_x_angle(); | |
float gyro_angle_y = gyro_y*dt + get_last_y_angle(); | |
float gyro_angle_z = gyro_z*dt + get_last_z_angle(); | |
// Compute the drifting gyro angles | |
float unfiltered_gyro_angle_x = gyro_x*dt + get_last_gyro_x_angle(); | |
float unfiltered_gyro_angle_y = gyro_y*dt + get_last_gyro_y_angle(); | |
float unfiltered_gyro_angle_z = gyro_z*dt + get_last_gyro_z_angle(); | |
// Apply the complementary filter to figure out the change in angle - choice of alpha is | |
// estimated now. Alpha depends on the sampling rate... | |
float alpha = 0.96; | |
float angle_x = alpha*gyro_angle_x + (1.0 - alpha)*accel_angle_x; | |
float angle_y = alpha*gyro_angle_y + (1.0 - alpha)*accel_angle_y; | |
float angle_z = gyro_angle_z; //Accelerometer doesn't give z-angle | |
// Update the saved data with the latest values | |
set_last_read_angle_data(t_now, angle_x, angle_y, angle_z, unfiltered_gyro_angle_x, unfiltered_gyro_angle_y, unfiltered_gyro_angle_z); | |
// Send the data to the serial port | |
/*Serial.print(F("DEL:")); //Delta T | |
Serial.print(dt, DEC); | |
Serial.print(F("#ACC:")); //Accelerometer angle | |
Serial.print(accel_angle_x, 2); | |
Serial.print(F(",")); | |
Serial.print(accel_angle_y, 2); | |
Serial.print(F(",")); | |
Serial.print(accel_angle_z, 2); | |
Serial.print(F("#GYR:")); | |
Serial.print(unfiltered_gyro_angle_x, 2); //Gyroscope angle | |
Serial.print(F(",")); | |
Serial.print(unfiltered_gyro_angle_y, 2); | |
Serial.print(F(",")); | |
Serial.print(unfiltered_gyro_angle_z, 2); | |
Serial.print(F("#FIL:")); //Filtered angle | |
Serial.print(angle_x, 2); | |
Serial.print(F(",")); | |
Serial.print(angle_y, 2); | |
Serial.print(F(",")); | |
Serial.print(angle_z, 2); | |
Serial.println(F("")); | |
*/ | |
Serial.print("*");Serial.print(accel_x); | |
Serial.print("*");Serial.print(accel_y); | |
Serial.print("*");Serial.print(accel_z); | |
Serial.print("*");Serial.print(angle_x); | |
Serial.print("*");Serial.print(angle_y); | |
Serial.print("*");Serial.print(angle_z); | |
Serial.println("*"); | |
// Delay so we don't swamp the serial port | |
delay(10); | |
} | |
// -------------------------------------------------------- | |
// MPU6050_read | |
// | |
// This is a common function to read multiple bytes | |
// from an I2C device. | |
// | |
// It uses the boolean parameter for Wire.endTransMission() | |
// to be able to hold or release the I2C-bus. | |
// This is implemented in Arduino 1.0.1. | |
// | |
// Only this function is used to read. | |
// There is no function for a single byte. | |
// | |
int MPU6050_read(int start, uint8_t *buffer, int size) | |
{ | |
int i, n, error; | |
Wire.beginTransmission(MPU6050_I2C_ADDRESS); | |
n = Wire.write(start); | |
if (n != 1) | |
return (-10); | |
n = Wire.endTransmission(false); // hold the I2C-bus | |
if (n != 0) | |
return (n); | |
// Third parameter is true: relase I2C-bus after data is read. | |
Wire.requestFrom(MPU6050_I2C_ADDRESS, size, true); | |
i = 0; | |
while(Wire.available() && i<size) | |
{ | |
buffer[i++]=Wire.read(); | |
} | |
if ( i != size) | |
return (-11); | |
return (0); // return : no error | |
} | |
// -------------------------------------------------------- | |
// MPU6050_write | |
// | |
// This is a common function to write multiple bytes to an I2C device. | |
// | |
// If only a single register is written, | |
// use the function MPU_6050_write_reg(). | |
// | |
// Parameters: | |
// start : Start address, use a define for the register | |
// pData : A pointer to the data to write. | |
// size : The number of bytes to write. | |
// | |
// If only a single register is written, a pointer | |
// to the data has to be used, and the size is | |
// a single byte: | |
// int data = 0; // the data to write | |
// MPU6050_write (MPU6050_PWR_MGMT_1, &c, 1); | |
// | |
int MPU6050_write(int start, const uint8_t *pData, int size) | |
{ | |
int n, error; | |
Wire.beginTransmission(MPU6050_I2C_ADDRESS); | |
n = Wire.write(start); // write the start address | |
if (n != 1) | |
return (-20); | |
n = Wire.write(pData, size); // write data bytes | |
if (n != size) | |
return (-21); | |
error = Wire.endTransmission(true); // release the I2C-bus | |
if (error != 0) | |
return (error); | |
return (0); // return : no error | |
} | |
// -------------------------------------------------------- | |
// MPU6050_write_reg | |
// | |
// An extra function to write a single register. | |
// It is just a wrapper around the MPU_6050_write() | |
// function, and it is only a convenient function | |
// to make it easier to write a single register. | |
// | |
int MPU6050_write_reg(int reg, uint8_t data) | |
{ | |
int error; | |
error = MPU6050_write(reg, &data, 1); | |
return (error); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment