Introduction: Arduino & MPU6050 Based Digital Spirit Level

About: Kitchen fitter by trade, project builder and lifelong learner by choice.

Welcome to my first ever instructable! I hope that you find it informative. Please feel free to leave feedback whether positive or negative.

This project is to make an arduino & MPU6050 based digital spirit level. Whilst the finished design and code is mine, the original concept and much of the code I have worked from are not. I am not into plagiarism, so I am more than happy to give credit to those who's ideas I have built upon. The 2 main people I want to give a shout out to are YouTuber's Paul McWhorter and DroneBot Workshop. I include links to them in my youtube useful links PDF. Thanks also to EEEnthusiast for his informative video on using the MPU6050 including setting up and reading from the module without an external library (his link is in the same PDF).

The project I have produced, works 'as is' and is fairy accurate, certainly up to 45% in either direction. You can use it exactly as I have designed it, or you can tailor it to your own tastes. The more astute of you will notice that my project looks almost identical to the one produced by the DroneBot workshop, but rest assured, there are significat differences, especially when it comes to the code for calculating angles, plus the facility to store calibration values in Eeprom!

Some feature to whet your appetite:

Pitch and roll angles available to within 0.1 of a degree.

Auto detect of orientation of gyro unit (horizontal or vertical)

Full calibration with results automatically stored to eeprom

LED indication from -2 to +2 degrees (changable in code)

Additional audible indication of level (can be turned on/off on nthe fly)

Compact curcuit requiring minimal components

Lets get started.


This project (as is) uses the following items:

1 x Arduino nano (mine is a clone)

1 x MPU6050 gyro/accelerometer module

1 x LCD - 16 x 2 + I2C connection

1 x Push to make switch

1 x Piezo buzzer

1 x Green LED

2 x Yellow LED's

2 x Red LED's

5 x 220 ohm resistors

Various jumper cables


Power supply (mine used a 5v USB power bank, when not connected to my PC, but you could use a battery appropriately connected)

Step 1: The Circuit

Assuming you have all the components, you will need to build your breadboard.

I show my setup as a guide, but connections are as follows:

Arduino pin D2 connects to 1 side of push switch. The other side of the push switch connects to ground

Arduino pin D3 connects to 1 side of 220 ohm resistor. Other side of resistor connects to annode of Red LED. Cathode of Red LED goes to ground.

Arduino pin D4 connects to 1 side of 220 ohm resistor. Other side of resistor connects to annode of Yellow LED. Cathode of Yellow LED goes to ground.

Arduino pin D5 connects to 1 side of 220 ohm resistor. Other side of resistor connects to annode of Green LED. Cathode of Green LED goes to ground.

Arduino pin D6 connects to 1 side of 220 ohm resistor. Other side of resistor connects to annode of Yellow LED. Cathode of Yellow LED goes to ground.

Arduino pin D7 connects to 1 side of 220 ohm resistor. Other side of resistor connects to annode of Red LED. Cathode of Red LED goes to ground.

Arduino pin D8 connects to one side of Piezo buzzer. Other side of buzzer connects to ground.

Arduino pin A4 connects to SDA pins on the MPU6050 AND the LCD.

Arduino pin A5 connects to the SCL pins on the MPU6050 AND the LCD

5v power and Gnd for MPU6050 and LCD come from the Arduino Nano 5v and GND pins respectively.

Once complete, it should be similar to my setup shown. I put blu tak under the MPU6050 to stop it moving and also on the LCD to keep it on the edge of the breadboard.

Step 2: The Code

The code attached is the code I have used for this project. The only library you may have an issue with is the

LiquidCrystal_I2C.h library as I imported this when I first started working with LCD's. Unfortunately, there are a few libraries that use the same #include statement, but are slightly different. If you have issues with yours, find another LCD code that works for you and alter the code accordingly. It is only likely to be the setup that differes. All 'print' commands should work the same.

All the code has been commented and assuming I have done it right, there will also be a video explaining everything, but here are a few points to note:

LiquidCrystal_I2C lcd(0x27, 16, 2);

The above code is the setup for my LCD. If your library is different, you may need to change not only your library, but also this line.

 {<br>      lcd.setCursor(0,1);<br>      lcd.print("Horizontal!");<br>      orientation = HORIZONTAL;<br>      //Read the raw acc and gyro data from the MPU-6050 1000 times                                          <br>      for (int cal_int = 0; cal_int < 1000 ; cal_int ++)<br>      {                  <br>        read_mpu_6050_data(); <br>        //Add the gyro x offset to the gyro_x_cal variable                                            <br>        gyro_x_cal += gyro_x;<br>        //Add the gyro y offset to the gyro_y_cal variable                                              <br>        gyro_y_cal += gyro_y; <br>        //Add the gyro z offset to the gyro_z_cal variable                                             <br>        gyro_z_cal += gyro_z; <br>        //Add the acc x offset to the acc_x_cal variable                                            <br>        acc_x_cal += acc_x;<br>        //Add the acc y offset to the acc_y_cal variable                                            <br>        acc_y_cal += acc_y;                                                          <br>      }<br>    <br>      // Divide all results by 1000 to get average offset<br>      gyro_x_cal /= 1000.0;                                                 <br>      gyro_y_cal /= 1000.0;                                                 <br>      gyro_z_cal /= 1000.0;<br>      acc_x_cal /= 1000.0;<br>      acc_y_cal /= 1000.0;<br><br>      horizonalCalibration = 255;<br>      eeprom_address = 0;<br>      EEPROM.put(eeprom_address, horizonalCalibration);<br>      eeprom_address += sizeof(int);<br>      EEPROM.put(eeprom_address, gyro_x_cal);<br>      eeprom_address += sizeof(float);<br>      EEPROM.put(eeprom_address, gyro_y_cal);<br>      eeprom_address += sizeof(float);<br>      EEPROM.put(eeprom_address, gyro_z_cal);<br>      eeprom_address += sizeof(float);<br>      EEPROM.put(eeprom_address, acc_x_cal);<br>      eeprom_address += sizeof(float);<br>      EEPROM.put(eeprom_address, acc_y_cal);<br>      eeprom_address += sizeof(float);<br>      //Note we are not storing an offset for acc_z, due to gravity!<br><br>      delay(500);<br>    }<br>

The above block of code executes to calibration routine. This code is for the horizontal calibration. There is near identical code for the vertical calibration (note, the code knows whether your MPU6050 is mounted horizontally or vertically!). MPU6050, is read 1000 times. appropriate values are cumulatively added then divided by 1000 to give an average 'offset' value. These values are then stored to the Nano eeprom. All horizontal calibration values are stored beginning at eeprom address 0. All vertical values are stored beginnig at eeprom address 24. The calibration MUST be done on a completely level surface, otherwise they mean nothing.

 /*<br>     * The next few lines process the raw data to change it into angles that can be output to the LCD and LED's.<br>     * The value of 4096, which the acceleration data is divided by is taken from the MPU6050 datasheet and is based on sample rate.<br>     * The value of 9.8 is gravity<br>     * The atan2 function is from the math module and is used to calculate the angles from the given data<br>     */<br>    thetaM =-atan2((acc_x/4096.0)/9.8 , (acc_z/4096.0)/9.8)/2/3.141592656 * 360;  //Raw data<br>    phiM =-atan2((acc_y/4096.0)/9.8 , (acc_z/4096.0)/9.8)/2/3.141592656 * 360;  //Raw data<br>  <br>    dt=(millis()-millisOld)/1000.;<br>    millisOld=millis();<br>    /*<br>     * This section uses the gyro data to make the system more responsive<br>     * the value of 65.5, which the gyro data is divided by is taken from the MPU6050 datasheet and is based on the sample rate<br>     */<br>    theta=(theta+(gyro_y/65.5)*dt)*.96 + thetaM*.04;  //Low pass filter<br>    phi=(phi+(gyro_x/65.5)*dt)*.96 + phiM*.04;  //Low pass filter<br>

The above code is the stuff that calculates the angles. Hopefully the comments give a little insight into how it works, but for in depth explanation, check out Paul McWhorters video linked to in the attached PDF. What I will say though is that you can change the sample rate for the gyro and Accelerometer (which is done in the setup MPU6050 subroutine at the bottom of my code). If you change the sample rate, you also have to change how much the raw data is divided by. For the accelerometer data, the current value is 4096. For the gyro, the current value is 65.5.

Refer to the attached data sheets and the video by EEEntusiast (link in the attached PDF) for more in depth information of how the sampling and offset values are found.

Step 3: Next Steps

By this point will have hopefully made this project, but what now?

Firstly, why not actually build it into a spirit level that you can use. You can buy a cheap spirit level (make sure it is the box type) that you can adapt, or if you have the kit, print your own level/box.

Perhaps have a play around with the gyro and accelerometer sample rates to see if they work better at one rate than another.

Try to refine the code further. For example, at present, beyond 45 degrees, the stated angle is rough to say the least. Is there a way around it?

If you have any questions, no matter how simple they may seem, please ask. If I can help, I will.

If you like this instructable, please give it a like, so that I know.

If you make this, please show me (especially if it is in a working case).