Arduino Range Measurer and Digital Spirit Level Project

In this tutorial we will learn how to make an Arduino range measurer and a digital spirit level. You can watch the following video or read the written tutorial below.

Overview

The device features an ultrasonic sensor for measuring the distance to the nearest object, an accelerometer for measuring the angle with respect to the ground, an LCD display for showing the results and a custom designed PCB on which all components are connected.

We can operate the device with just a single button. Once we power the device we need to select the unit of measurement.

Distance measuring device unit select program

By pressing the button we can toggle through the units, and if we press and hold the button for a while we will get into the first program. Here we can measure the distance and also have the ability to store the last two measurements.

Arduino Range Measurer - distance to the nearest object

For getting into the second program, again we have to press and hold the button for a while. In this program we can measure a square area by taking two perpendicular distance measurements.

Arduino Square Area Measurement

The next program is the digital spirit level and here we can measure the angle with respect to the ground.

Arduino DIY Digital Spirit Level - angle aeasurement

Using the button we can toggle between the two axes or we can measure either the Pitch or the Roll.

Circuit Schematic

Here’s the circuit schematic of this Arduino project.

Arduino DIY Digital Range Measurer and Spirit Level Circuit Schematic

Note that I already have detailed tutorials on how each of these modules work, and you can check them on the following links: Ultrasonic sensor tutorial, LCD tutorial, MEMS accelerometer tutorial.

You can get the components needed for this project from the links below:

  • Ultrasonic Sensor HC-SR04……………………………… Amazon / Banggood
  • MPU6050 3 Axis Gyroscope Accelerometer……… Amazon / Banggood
  • 16×2 Character LCD………………………………………… Amazon / Banggood
  • Arduino Nano………………………………………………… Amazon / Banggood

*Please note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!

Custom design PCB

According to the circuit schematic we need to design the custom PCB and I did that using the EasyEDA free online circuit design software.

Once we finished the design here, we can simply export the Gerber file which is used for manufacturing the PCB. You can check the EasyEDA project files of this project here.

Then we can order our PCB from JLCPCB, which is actually the sponsor of this project.

Here we can simply drag and drop the Gerber file. Once uploaded we can review our PCB in the Gerber viewer. If everything is all right then we can go on, select the properties that we want for our PCB, and then we can order our PCB at a reasonable price. Note that if it’s your first order from JLCPCB, you can get up to 10 PCBs for only $2.

Assembling the Device

Nevertheless, after several days the PCBs have arrived. The quality of the PCBs is great and everything is exactly the same as in the design.

Custom PCB design - digital distance and angle meter

Ok, now we can start assembling the electronics for this project. I started by soldering pin headers on the PCB. In this way we can easily connect and disconnect the components when needed.

Then I inserted and soldered the three resistors. Two of them are for the voltage divider for the contrast of LCD. The 1k resistor should be placed at R1 and the 220 ohms at R2. The third one is a pull-up resistor for the button.

Next I moved on with making the case for the device. I decided to use transparent acrylic because I wanted the beauty of all electronics components to be visible. I had some 5mm tick acrylic from an old project, and I used a circular for cutting it to size.

Then I had to make an opening for the LCD on the top side of the case, which is actually 4mm tick because it will fit better with the LCD. So first I made a two hole with a drill and then inserted a hacksaw through them. Using the hacksaw I roughly made the opening and then using a rasp I made fine straight lines so that the LCD can tightly fit.

Then using a forstner bit I made the openings for the power switch, the control button and the ultrasonic sensor.

driling acrylic with forstner bit

Once I had all pieces ready I used a 5 minutes epoxy to assembly to case. As for the top, I inserted and glued two bolts through which the top panel can be inserted and secured using some nuts on top of it.

acrylic case for arduino project

The case is now ready and so I continued with soldering pin headers to the LCD so I can easily attach it to the PCB. I also soldered pin headers or jump wires to the power switch, the button and the battery connector.

Finaly, I had everything ready to assembly the device. I started with inserting the ultrasonic sensor on the PCB, and then through the holes on the side panel. Next was the Arduino board, the accelerometer module as well as the battery connector.

On the top panel, I secured the LCD, the power switch and the button, and then connected them to the PCB. Lastly I inserted the 9V battery to the connector and secured the top panel with the nuts.

And that’s it, the project is done but what’s left in this video is to explain how the program works, so let’s take a look at the Arduino code.

Arduino Range Measurer and Digital Spirit Level Source Code

As the code is a bit longer, for better understanding, I will post the source code of the program in sections with description for each section. And at the end of this article I will post the complete source code.

So first we need to include the Wire.h library for the I2C communication of the accelerometer, as well as the LiquidCrystal.h library for controlling the LCD.  Then we need to define the LCD, the I2C address of the accelerometer module, the ultrasonic sensor pins, as well as some variables needed for the program below.

#include<Wire.h> // I2C communication library
#include <LiquidCrystal.h> // includes the LiquidCrystal Library

LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // Creates an LCD object. Parameters: (rs, enable, d4, d5, d6, d7)
const int MPU = 0x68; // I2C address of the MPU6050 accelerometer

#define trigPin 8
#define echoPin 9
#define selectButton 10

int16_t AcX, AcY, AcZ;
long duration;
float distance;
int program = 0;
float d = 0;
float d1 = 0;
float d2 = 0;
float area = 0;
int axis = 0;
int angle = 0;
int unitSelect = 0;
String unit = "cm";

In the setup section we need to initialize both the I2C interface of the accelerometer and the liquid crystal display, as well as define the pin modes for the ultrasonic sensor trigger and echo pins, and the button pin.

void setup() {
  // Initialize interface to the MPU6050
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);

  lcd.begin(16, 2); // Initializes the interface to the LCD screen
  
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(selectButton, INPUT_PULLUP);
}

In the main loop section we have a switch statements through which we can toggle between the different programs of our device. In the first, or case number 0, we select the unit of measurement. Using the lcd.print() function we print the text on the LCD and using the if statements we toggle through the four unit of measurements.

switch (program) {  // Switch between different programs
    case 0:  // Select unit of measurement
      lcd.setCursor(0, 0); // Sets the location at which subsequent text written to the LCD will be displayed
      lcd.print("Select Unit: ");
      lcd.setCursor(13, 0);
      lcd.print(unit);
      lcd.print("  ");
      delay(10);
      // If button is pressed - change unit
      if (digitalRead(selectButton) == 0) {
        if (unitSelect == 0) {
          unit = "in";
          unitSelect = 1;
        }
        else if (unitSelect == 1) {
          unit = "m";
          unitSelect = 2;
        }
        else if (unitSelect == 2) {
          unit = "ft";
          unitSelect = 3;
        }
        else if (unitSelect == 3) {
          unit = "cm";
          unitSelect = 0;
        }
        // If button is held longer then half a second - change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 1;
          lcd.clear();
          delay(500);
        }
      }
      break;

We should note here that the unitSelect variable takes action in the getDistance() cunstom function, where it actually tells to which unit we should convert the basic cm unit that we are getting from the ultrasonic sensor.

/ Converting the units
  if (unitSelect == 1) {
    distance = distance; // cm to cm
    unit = "cm";
  }
  else if (unitSelect == 2) {
    distance = distance *  0.393701; // cm to in
    unit = "in";
  }
  else if (unitSelect == 3) {
    distance = distance * 0.01; // cm to m
    unit = "m";
  }
  else if (unitSelect == 0) {
    distance = distance * 0.0328; // cm to ft
    unit = "ft";
  }

In order to selecting the unit of measurement and also get in the first program of the device we just have to hold the press button longer than half a second.

// If button is held longer then half a second - change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 1;
          lcd.clear();
          delay(500);
        }
      }
      break;

In case number 1 or in the distance measuring program first we get the distance to the nearest object using the getDistance() custom function.

case 1:   // Distance measuring program
      distance = getDistance(); // Distance to the nearest object
      lcd.setCursor(0, 0);
      lcd.print("Dist: ");
      lcd.print(distance); // Prints the distance value from the sensor
      lcd.print("    ");
      lcd.setCursor(14, 0);
      lcd.print(unit);
      delay(10);
      lcd.setCursor(0, 1);
      lcd.print("d:");
      lcd.setCursor(8, 1);
      lcd.print("d:");
      delay(200);
      // Save distance 1
      if (digitalRead(selectButton) == 0) {
        if (d == 0) {
          lcd.setCursor(0, 1);
          lcd.print("d:     ");
          lcd.setCursor(2, 1);
          lcd.print(distance);
          d = 1;
          delay(100);
        }
        // Save distance 2
        else if (d == 1) {
          lcd.setCursor(8, 1);
          lcd.print("d:     ");
          lcd.setCursor(10, 1);
          lcd.print(distance);
          d = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 2;
          d = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;

Let’s see how this function works.

//===== getDistance - Custom Function
float getDistance() {
  // Clears the trigPin
  digitalWrite(trigPin, LOW);
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // Reads the echoPin, returns the sound wave travel time in microseconds
  duration = pulseIn(echoPin, HIGH);
  // Calculating the distance
  distance = duration * 0.034 / 2; // distance in cm
  
  // Converting the units
  if (unitSelect == 1) {
    distance = distance; // cm to cm
    unit = "cm";
  }
  else if (unitSelect == 2) {
    distance = distance *  0.393701; // cm to in
    unit = "in";
  }
  else if (unitSelect == 3) {
    distance = distance * 0.01; // cm to m
    unit = "m";
  }
  else if (unitSelect == 0) {
    distance = distance * 0.0328; // cm to ft
    unit = "ft";
  }
  return distance;
}

Here using the trigger pin we tell sensor to generate an ultrasonic sound wave.

digital range measurer - ultrasonic sensor working principle

Then using the echo pin and the pulseIn() function we measure the duration the wave traveled from the sensor to the object and back. Considering the speed of sound and the travel time we can easily calculate the distance. So we print the measured distance along with some text on the LCD and using the “if” statements, if we press the button, we print or save last two measurements.

Next is the program for calculating area in which we are using similar methods. We need to take two perpendicular measurements and simply multiply them in order to get the area of the square that they form.

case 2:   // Area measuring program
      distance = getDistance();
      lcd.setCursor(0, 0);
      lcd.print("Area: ");
      lcd.print(area); // Prints the calculated area from the two measurements
      lcd.setCursor(12, 0);
      lcd.print(unit); // Prints the selected unit and the square sign below
      lcd.print("^2");
      delay(200);
      if ( d == 0) {
        lcd.setCursor(0, 1);
        lcd.print("d1:       ");
        lcd.setCursor(3, 1);
        lcd.print(distance);
        delay(200);
      }
      else if (d == 1) {
        lcd.setCursor(9, 1);
        lcd.print("d2:      ");
        lcd.setCursor(12, 1);
        lcd.print(distance);
        delay(200);
      }
      else if (d == 2) {
        lcd.setCursor(6, 0);
        lcd.print(area);
        delay(200);
      }
      // Save distance 1
      if (digitalRead(selectButton) == 0) {
        if (d == 0) {
          lcd.setCursor(0, 1);
          lcd.print("d1:    ");
          lcd.setCursor(3, 1);
          lcd.print(distance);
          d = 1;
          d1 = distance;
          delay(100);
        }
        // Save distance 2
        else if (d == 1) {
          lcd.setCursor(9, 1);
          lcd.print("d2:    ");
          lcd.setCursor(12, 1);
          lcd.print(distance);
          d = 2;
          d2 = distance;
          area = d1 * d2; // Calculate the area
          delay(100);
        }
        else if (d == 2) {
          lcd.clear();
          d = 0;
          area = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 3;
          d = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;

The last case, is the angle measuring program. Here we need to read the accelerometer data which is actually the strength of the Earth’s gravitational field in three different axes, X,Y, and Z. The value of each axis is stored in 2 registers, so we need to read total of 6 registers and combine them in order to get the right value.

case 3:    // Angle measuring program
      // Read the accelerometer data
      Wire.beginTransmission(MPU);
      Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H)
      Wire.endTransmission(false);
      Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
      AcX = Wire.read() << 8 | Wire.read(); // X-axis value
      AcY = Wire.read() << 8 | Wire.read(); // Y-axis value
      AcZ = Wire.read() << 8 | Wire.read(); // Z-axis value
     
      if ( axis == 0) {
        // Calculating the Pitch angle (rotation around Y-axis)
        angle = atan(-1 * AcX / sqrt(pow(AcY, 2) + pow(AcZ, 2))) * 180 / PI;
        lcd.setCursor(0, 0);
        lcd.print("Pitch");
      }
      else if (axis == 1) {
        // Calculating the Roll angle (rotation around X-axis)
        angle = atan(-1 * AcY / sqrt(pow(AcX, 2) + pow(AcZ, 2))) * 180 / PI;
        lcd.setCursor(0, 0);
        lcd.print("Roll ");
      }
      lcd.setCursor(0, 1);
      lcd.print("Angle: ");
      lcd.print(abs(angle));
      lcd.print("     ");
      lcd.setCursor(10, 1);
      lcd.print("deg");
      delay(200);

      // Change axis
      if (digitalRead(selectButton) == 0) {
        if (axis == 0) {
          axis = 1;
          delay(100);
        }
        // Save distance 2
        else if (axis == 1) {
          axis = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;

Once we have the X,Y and Z values of the accelerometer we can calculate the Pitch angle, or rotation around the Y-axis, as well as the Roll angle, or rotation around the X-axis, using the the two equations. You can find more details about these equations on Freescale Semiconductor application note, Tilt Sensing Using a Three-Axis Accelerometer. So after that we print the values along with some text on the LCD, and by pressing the button we change what we show on the LCD, whether the Pitch or the Roll angle.

Here’s the complete source code:

/*
  DIY Digital Range Measurer and Spirit Level

  by Dejan Nedelkovski,
  www.HowToMechatronics.com

*/
#include<Wire.h> // I2C communication library
#include <LiquidCrystal.h> // includes the LiquidCrystal Library

LiquidCrystal lcd(7, 6, 5, 4, 3, 2); // Creates an LCD object. Parameters: (rs, enable, d4, d5, d6, d7)
const int MPU = 0x68; // I2C address of the MPU6050 accelerometer

#define trigPin 8
#define echoPin 9
#define selectButton 10

int16_t AcX, AcY, AcZ;
long duration;
float distance;
int program = 0;
float d = 0;
float d1 = 0;
float d2 = 0;
float area = 0;
int axis = 0;
int angle = 0;
int unitSelect = 0;
String unit = "cm";

void setup() {
  // Initialize interface to the MPU6050
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);
  Wire.write(0);
  Wire.endTransmission(true);

  lcd.begin(16, 2); // Initializes the interface to the LCD screen
  
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(selectButton, INPUT_PULLUP);
}
void loop() {
  switch (program) {  // Switch between different programs
    case 0:  // Select unit of measurement
      lcd.setCursor(0, 0); // Sets the location at which subsequent text written to the LCD will be displayed
      lcd.print("Select Unit: ");
      lcd.setCursor(13, 0);
      lcd.print(unit);
      lcd.print("  ");
      delay(10);
      // If button is pressed - change unit
      if (digitalRead(selectButton) == 0) {
        if (unitSelect == 0) {
          unit = "in";
          unitSelect = 1;
        }
        else if (unitSelect == 1) {
          unit = "m";
          unitSelect = 2;
        }
        else if (unitSelect == 2) {
          unit = "ft";
          unitSelect = 3;
        }
        else if (unitSelect == 3) {
          unit = "cm";
          unitSelect = 0;
        }
        // If button is held longer then half a second - change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 1;
          lcd.clear();
          delay(500);
        }
      }
      break;
      
    case 1:   // Distance measuring program
      distance = getDistance(); // Distance to the nearest object
      lcd.setCursor(0, 0);
      lcd.print("Dist: ");
      lcd.print(distance); // Prints the distance value from the sensor
      lcd.print("    ");
      lcd.setCursor(14, 0);
      lcd.print(unit);
      delay(10);
      lcd.setCursor(0, 1);
      lcd.print("d:");
      lcd.setCursor(8, 1);
      lcd.print("d:");
      delay(200);
      // Save distance 1
      if (digitalRead(selectButton) == 0) {
        if (d == 0) {
          lcd.setCursor(0, 1);
          lcd.print("d:     ");
          lcd.setCursor(2, 1);
          lcd.print(distance);
          d = 1;
          delay(100);
        }
        // Save distance 2
        else if (d == 1) {
          lcd.setCursor(8, 1);
          lcd.print("d:     ");
          lcd.setCursor(10, 1);
          lcd.print(distance);
          d = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 2;
          d = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;

    case 2:   // Area measuring program
      distance = getDistance();
      lcd.setCursor(0, 0);
      lcd.print("Area: ");
      lcd.print(area); // Prints the calculated area from the two measurements
      lcd.setCursor(12, 0);
      lcd.print(unit); // Prints the selected unit and the square sign below
      lcd.print("^2");
      delay(200);
      if ( d == 0) {
        lcd.setCursor(0, 1);
        lcd.print("d1:       ");
        lcd.setCursor(3, 1);
        lcd.print(distance);
        delay(200);
      }
      else if (d == 1) {
        lcd.setCursor(9, 1);
        lcd.print("d2:      ");
        lcd.setCursor(12, 1);
        lcd.print(distance);
        delay(200);
      }
      else if (d == 2) {
        lcd.setCursor(6, 0);
        lcd.print(area);
        delay(200);
      }
      // Save distance 1
      if (digitalRead(selectButton) == 0) {
        if (d == 0) {
          lcd.setCursor(0, 1);
          lcd.print("d1:    ");
          lcd.setCursor(3, 1);
          lcd.print(distance);
          d = 1;
          d1 = distance;
          delay(100);
        }
        // Save distance 2
        else if (d == 1) {
          lcd.setCursor(9, 1);
          lcd.print("d2:    ");
          lcd.setCursor(12, 1);
          lcd.print(distance);
          d = 2;
          d2 = distance;
          area = d1 * d2; // Calculate the area
          delay(100);
        }
        else if (d == 2) {
          lcd.clear();
          d = 0;
          area = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 3;
          d = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;

    case 3:    // Angle measuring program
      // Read the accelerometer data
      Wire.beginTransmission(MPU);
      Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H)
      Wire.endTransmission(false);
      Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
      AcX = Wire.read() << 8 | Wire.read(); // X-axis value
      AcY = Wire.read() << 8 | Wire.read(); // Y-axis value
      AcZ = Wire.read() << 8 | Wire.read(); // Z-axis value
     
      if ( axis == 0) {
        // Calculating the Pitch angle (rotation around Y-axis)
        angle = atan(-1 * AcX / sqrt(pow(AcY, 2) + pow(AcZ, 2))) * 180 / PI;
        lcd.setCursor(0, 0);
        lcd.print("Pitch");
      }
      else if (axis == 1) {
        // Calculating the Roll angle (rotation around X-axis)
        angle = atan(-1 * AcY / sqrt(pow(AcX, 2) + pow(AcZ, 2))) * 180 / PI;
        lcd.setCursor(0, 0);
        lcd.print("Roll ");
      }
      lcd.setCursor(0, 1);
      lcd.print("Angle: ");
      lcd.print(abs(angle));
      lcd.print("     ");
      lcd.setCursor(10, 1);
      lcd.print("deg");
      delay(200);

      // Change axis
      if (digitalRead(selectButton) == 0) {
        if (axis == 0) {
          axis = 1;
          delay(100);
        }
        // Save distance 2
        else if (axis == 1) {
          axis = 0;
          delay(100);
        }
        // If button is held longer then half sencond change program
        delay(500);
        if (digitalRead(selectButton) == 0) {
          program = 0;
          lcd.clear();
          delay(500);
        }
      }
      break;
  }
}

//===== getDistance - Custom Function
float getDistance() {
  // Clears the trigPin
  digitalWrite(trigPin, LOW);
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // Reads the echoPin, returns the sound wave travel time in microseconds
  duration = pulseIn(echoPin, HIGH);
  // Calculating the distance
  distance = duration * 0.034 / 2; // distance in cm
  
  // Converting the units
  if (unitSelect == 1) {
    distance = distance; // cm to cm
    unit = "cm";
  }
  else if (unitSelect == 2) {
    distance = distance *  0.393701; // cm to in
    unit = "in";
  }
  else if (unitSelect == 3) {
    distance = distance * 0.01; // cm to m
    unit = "m";
  }
  else if (unitSelect == 0) {
    distance = distance * 0.0328; // cm to ft
    unit = "ft";
  }
  return distance;
}

 

So that would be all, I hope you enjoyed this Arduino project and learned something new. Feel free to ask any question in the comments section below.

5 Responses

  1. Pranav Desai

    Cool!
    An interesting project!
    I’m gonna make it now!
    Thanks, for your hard work putting together this website. It is really one of the best I’ve found for Arduino projects.

    Reply
  2. Arya Gajjar

    Awesome project
    I made it Was amazing but i have problem in sprit level because sometimes turn off when,I switch to the sprit level option but other thing are working nicely.

    Reply
  3. Ajith

    sir any suggestions for an additional thing that we can add to the existing project?

    Reply
    • Dejan Nedelkovski

      Maybe shrink the project down, using smaller display like 0.96″ OLED, add compensation for measuring from the back side of the case, add laser pointer to know the exact direction of what you measure ect.

      Reply

Leave a Reply

Your email address will not be published.

Join My Newsletter!

Get notified when I publish new projects and tutorials straight to your inbox.