Arduino & MPU6050 Based Digital Spirit Level

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).


Be the First to Share


    • Rice & Grains Challenge

      Rice & Grains Challenge
    • Puzzles Challenge

      Puzzles Challenge
    • Lamps Challenge

      Lamps Challenge



    1 year ago

    Thank you. I built a new circuit using a Nano, but I can't get past the error message triggered by
    I get
    no matching function for call to 'LiquidCrystal_I2C::begin()'
    which isn't what's there. So I change the line to LiquidCrystal_I2C.begin(); with or without semicolon the response is the same
    'LiquidCrystalI2C' was not declared in this scope or
    expected unqualified-id before ',' token
    Lastly, I tried LiquidCrystal_I2C::begin; and got the response
    invalid use of non-static member function 'void LiquidCrystal_I2C::begin(uint8_t, uint8_t, uint8_t)'
    Is there somewhere we can go to find out what these error messages mean, and what we should look for, to correct the errors?
    Thank you for your advice!
    P.S. I'm going to try the built-in examples until I get SOMETHING that works. ;-)


    Reply 1 year ago

    Hi Tim. So sorry to hear this is still throwing an error. It definately sounds like a problem between the LCD and the library code. I think trying the examples with nothing more than the LCD would be a start. It might pay to look through the libraries folder for your IDE and see if you have multiple copies of the LCD library. Alternatively, your library may be different to mine (unfortunately there are multiple libraries for these which are all slightly different in their approach). If you look at the example code, it will show you how it initialises the LCD, etc. It may be that instead of lcd.begin(); you may need to put lcd.init(); for example. The example code will certainly help. Perhaps share a screenshot of one of the examples you have for your library.


    Question 1 year ago on Step 3

    Great video, and I think I followed it properly. I'm using an UNO rather than a Nano, and I ran the MPU6050 to the A4 and A5, while I ran the screen to the SCL and SDA pins, and I had to use the LiquidCrystal_I2C.h and change the screen parameters to 20 and 4. It loads and blinks the LEDs in order from Pin 7 downwards. Then it settles into a non-backlit screen mode with gibberish on it, and nothing ever happens after that, whether I press the switch or not.
    The only time I strayed from your build, other than using the 20 x 4 display, was to gang the LEDs up to a single bus, with a single 220ohm resistor to ground, rather than putting five resistors upstream of the LEDs.
    (I tried both 115200 baud and 9600 baud; it's the same either way.)
    After loading, the display displays gibberish on the top and third line only; it changes during the LED array, then settles to different gibberish. The gibberish does not repeat on subsequent uploads, even though I do not move any of the components. I do notice, though, that when things settle down after each upload, there is one character that is blinking, and it moves one space to the right each time. It is preceded by a pattern of / and a hollow square box, half the width of a character, in the upper left corner of the character position. (I looked in an ASCII table but could not find it listed.) Other characters consist of upper and lower-case o, <- and -> (aligned center), umlaut lower-case o, and five parallel horizontal bars that, with their four spaces in-between, take up the whole character.
    There are no errors that stopped processing, but I do get some "Invalid library" messages for libraries that aren't included in this sketch, plus one for the _I2C file that says, "no header files."
    Invalid library found in C:\Program Files (x86)\Arduino\libraries\LiquidCrystal_I2C: no headers files (.h) found in C:\Program Files (x86)\Arduino\libraries\LiquidCrystal_I2C

    But the sketch loads and no errors show up during verifying.
    I'm way over my head here. What should I be checking?
    Thank you.


    Reply 1 year ago

    Sorry to hear you are having problems with your build. I am not going to pretend that I will give you a definative answer to solve your problem, but my initial thoughts are the connections between the mpu6050/LCD and the Uno.
    I don't know whether you are using a genuine uno or a clone, but I have in the past, had issues whereby the A4 and A5 do not have the same function as the SCL and SDA pins, even though they should. It might pay to try and connect both the MPU6050 and the LCD to either the SCL/SCA pins, OR the A4/A5 pins. You may need to go via a breadboard initially to try it out.
    If you connect just the LCD to the Uno and load one of the example sketches that come with the liquidcrystal library, that may also help.
    This may or may not be the problem. Please let me know how you get on.