///////////////////////////////////////////////////////////////////////////////////////
/*Terms of use
///////////////////////////////////////////////////////////////////////////////////////
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////
//Support
///////////////////////////////////////////////////////////////////////////////////////
Website: http://www.brokking.net/imu.html
Youtube: https://youtu.be/4BoIE8YQwM8
Version: 1.0 (May 5, 2016)
///////////////////////////////////////////////////////////////////////////////////////
//Connections
///////////////////////////////////////////////////////////////////////////////////////
Power (5V) is provided to the Arduino pro mini by the FTDI programmer
Gyro - Arduino pro mini
VCC - 5V
GND - GND
SDA - A4
SCL - A5
LCD - Arduino pro mini
VCC - 5V
GND - GND
SDA - A4
SCL - A5
*//////////////////////////////////////////////////////////////////////////////////////
//Include Oled Display and I2C library
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
//#define SDA_PIN 0 //wemosLolin32 I2C1
//#define SCL_PIN 4 //wemoslolin32 I2C1
#define SDA_PIN 21 //esp32 dev module
#define SCL_PIN 22 //esp32 dev module
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//Declaring some global variables
int gyro_x, gyro_y, gyro_z;
long acc_x, acc_y, acc_z, acc_total_vector;
int temperature;
long gyro_x_cal, gyro_y_cal, gyro_z_cal;
long loop_timer;
int lcd_loop_counter;
float angle_pitch, angle_roll;
int angle_pitch_buffer, angle_roll_buffer;
boolean set_gyro_angles;
float angle_roll_acc, angle_pitch_acc;
float angle_pitch_output, angle_roll_output;
void setup() {
Wire.begin(SDA_PIN, SCL_PIN); //Start I2C as master
Serial.begin(57600);
pinMode(25, OUTPUT); //Set output 25 (LED) as output
digitalWrite(25, HIGH); //Set digital output 13 high to indicate startup
setup_mpu_6050_registers(); //Setup the registers of the MPU-6050 (500dfs / +/-8g) and start the gyro
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
display.display();
delay(2);
display.clearDisplay();
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(10, 5);
display.print("MPU-6050 IMU V1.0");
display.display();
Serial.println("MPU-6050 IMU V1.0");
delay(2000);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(15, 5);
display.print("Calibrating gyro");
display.display();
for (int cal_int = 0; cal_int < 2000 ; cal_int ++) { //Run this code 2000 times
if (cal_int % 125 == 0){
display.setCursor(25,5);
display.print(".");
display.display(); } //Print a dot on the LCD every 125 readings
read_mpu_6050_data(); //Read the raw acc and gyro data from the MPU-6050
gyro_x_cal += gyro_x; //Add the gyro x-axis offset to the gyro_x_cal variable
gyro_y_cal += gyro_y; //Add the gyro y-axis offset to the gyro_y_cal variable
gyro_z_cal += gyro_z; //Add the gyro z-axis offset to the gyro_z_cal variable
delay(3); //Delay 3us to simulate the 250Hz program loop
}
gyro_x_cal /= 2000; //Divide the gyro_x_cal variable by 2000 to get the avarage offset
gyro_y_cal /= 2000; //Divide the gyro_y_cal variable by 2000 to get the avarage offset
gyro_z_cal /= 2000; //Divide the gyro_z_cal variable by 2000 to get the avarage offset
digitalWrite(25, LOW); //All done, turn the LED off
loop_timer = micros(); //Reset the loop timer
}
void loop() {
read_mpu_6050_data(); //Read the raw acc and gyro data from the MPU-6050
gyro_x -= gyro_x_cal; //Subtract the offset calibration value from the raw gyro_x value
gyro_y -= gyro_y_cal; //Subtract the offset calibration value from the raw gyro_y value
gyro_z -= gyro_z_cal; //Subtract the offset calibration value from the raw gyro_z value
//Gyro angle calculations
//0.0000611 = 1 / (250Hz / 65.5)
angle_pitch += gyro_x * 0.0000611; //Calculate the traveled pitch angle and add this to the angle_pitch variable
angle_roll += gyro_y * 0.0000611; //Calculate the traveled roll angle and add this to the angle_roll variable
//0.000001066 = 0.0000611 * (3.142(PI) / 180degr) The Arduino sin function is in radians
angle_pitch += angle_roll * sin(gyro_z * 0.000001066); //If the IMU has yawed transfer the roll angle to the pitch angel
angle_roll -= angle_pitch * sin(gyro_z * 0.000001066); //If the IMU has yawed transfer the pitch angle to the roll angel
//Accelerometer angle calculations
acc_total_vector = sqrt((acc_x * acc_x) + (acc_y * acc_y) + (acc_z * acc_z)); //Calculate the total accelerometer vector
//57.296 = 1 / (3.142 / 180) The Arduino asin function is in radians
angle_pitch_acc = asin((float)acc_y / acc_total_vector) * 57.296; //Calculate the pitch angle
angle_roll_acc = asin((float)acc_x / acc_total_vector) * -57.296; //Calculate the roll angle
//Place the MPU-6050 spirit level and note the values in the following two lines for calibration
angle_pitch_acc -= 0.0; //Accelerometer calibration value for pitch
angle_roll_acc -= 0.0; //Accelerometer calibration value for roll
if (set_gyro_angles) { //If the IMU is already started
angle_pitch = angle_pitch * 0.9996 + angle_pitch_acc * 0.0004; //Correct the drift of the gyro pitch angle with the accelerometer pitch angle
angle_roll = angle_roll * 0.9996 + angle_roll_acc * 0.0004; //Correct the drift of the gyro roll angle with the accelerometer roll angle
}
else { //At first start
angle_pitch = angle_pitch_acc; //Set the gyro pitch angle equal to the accelerometer pitch angle
angle_roll = angle_roll_acc; //Set the gyro roll angle equal to the accelerometer roll angle
set_gyro_angles = true; //Set the IMU started flag
}
//To dampen the pitch and roll angles a complementary filter is used
angle_pitch_output = angle_pitch_output * 0.9 + angle_pitch * 0.1; //Take 90% of the output pitch value and add 10% of the raw pitch value
angle_roll_output = angle_roll_output * 0.9 + angle_roll * 0.1; //Take 90% of the output roll value and add 10% of the raw roll value
write_oled(); //Write the roll and pitch values to the LCD display
while (micros() - loop_timer < 4000); //Wait until the loop_timer reaches 4000us (250Hz) before starting the next loop
loop_timer = micros(); //Reset the loop timer
}
void read_mpu_6050_data() { //Subroutine for reading the raw gyro and accelerometer data
Wire.beginTransmission(0x68); //Start communicating with the MPU-6050
Wire.write(0x3B); //Send the requested starting register
Wire.endTransmission(); //End the transmission
Wire.requestFrom(0x68, 14); //Request 14 bytes from the MPU-6050
while (Wire.available() < 14); //Wait until all the bytes are received
acc_x = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the acc_x variable
acc_y = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the acc_y variable
acc_z = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the acc_z variable
temperature = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the temperature variable
gyro_x = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the gyro_x variable
gyro_y = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the gyro_y variable
gyro_z = Wire.read() << 8 | Wire.read(); //Add the low and high byte to the gyro_z variable
}
void write_LCD() { //Subroutine for writing the LCD
//To get a 250Hz program loop (4us) it's only possible to write one character per loop
//Writing multiple characters is taking to much time
if (lcd_loop_counter == 14)lcd_loop_counter = 0; //Reset the counter after 14 characters
lcd_loop_counter ++; //Increase the counter
if (lcd_loop_counter == 1) {
angle_pitch_buffer = angle_pitch_output * 10; //Buffer the pitch angle because it will change
display.setCursor(6, 0); //Set the LCD cursor to position to position 0,0
}
if (lcd_loop_counter == 2) {
if (angle_pitch_buffer < 0)display.print("-"); //Print - if value is negative
else display.print("+"); //Print + if value is negative
}
if (lcd_loop_counter == 3)display.print(abs(angle_pitch_buffer) / 1000); //Print first number
if (lcd_loop_counter == 4)display.print((abs(angle_pitch_buffer) / 100) % 10); //Print second number
if (lcd_loop_counter == 5)display.print((abs(angle_pitch_buffer) / 10) % 10); //Print third number
if (lcd_loop_counter == 6)display.print("."); //Print decimal point
if (lcd_loop_counter == 7)display.print(abs(angle_pitch_buffer) % 10); //Print decimal number
if (lcd_loop_counter == 8) {
angle_roll_buffer = angle_roll_output * 10;
display.setCursor(6, 1);
}
if (lcd_loop_counter == 9) {
if (angle_roll_buffer < 0)display.print("-"); //Print - if value is negative
else display.print("+"); //Print + if value is negative
}
if (lcd_loop_counter == 10)display.print(abs(angle_roll_buffer) / 1000); //Print first number
if (lcd_loop_counter == 11)display.print((abs(angle_roll_buffer) / 100) % 10); //Print second number
if (lcd_loop_counter == 12)display.print((abs(angle_roll_buffer) / 10) % 10); //Print third number
if (lcd_loop_counter == 13)display.print("."); //Print decimal point
if (lcd_loop_counter == 14)display.print(abs(angle_roll_buffer) % 10); //Print decimal number
}
void write_oled () {
if (lcd_loop_counter == 14)lcd_loop_counter = 0; //Reset the counter after 14 characters
lcd_loop_counter ++;
if (lcd_loop_counter == 10){
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(2);
display.setCursor(16, 0);
display.print("MPU-6050");
display.setCursor(0, 12);
display.print("----------");
display.setCursor(16, 25);
display.print("P: "); display.print(angle_pitch_output);
display.setCursor(16, 48);
display.print("R: "); display.print(angle_roll_output);
display.display();
}
}
void setup_mpu_6050_registers() {
//Activate the MPU-6050
Wire.beginTransmission(0x68); //Start communicating with the MPU-6050
Wire.write(0x6B); //Send the requested starting register
Wire.write(0x00); //Set the requested starting register
Wire.endTransmission(); //End the transmission
//Configure the accelerometer (+/-8g)
Wire.beginTransmission(0x68); //Start communicating with the MPU-6050
Wire.write(0x1C); //Send the requested starting register
Wire.write(0x10); //Set the requested starting register
Wire.endTransmission(); //End the transmission
//Configure the gyro (500dps full scale)
Wire.beginTransmission(0x68); //Start communicating with the MPU-6050
Wire.write(0x1B); //Send the requested starting register
Wire.write(0x08); //Set the requested starting register
Wire.endTransmission(); //End the transmission
}