I made a spirit level once with the accelerometer: Het Nederlandstalig Arduino forum - Bekijk onderwerp - Gebruik MPU-9250 9-DOF Gyro
// Waterpas 2
// ----------
// The most simple "spirit level" or "bubble level" or "angle measurement".
// No selftest, no calibration, only basic offset compensation.
// Not fully 3D, only for about maximum 45 tilting.
// With Arduino IDE 1.8.1, Arduino Mega 2560 + I2C level shifter + MPU-6050 / MPU-9250
// February 2017, Public Domain
//
// Tilt Sensing calculation:
// NXP Freescale Application Note AN3461
// The calculation seems to be called a "Farrell equation".
// Discussion here:
// http://stackoverflow.com/questions/3755059/3d-accelerometer-calculate-the-orientation
// Code based on: MPU-6050 Short Example Sketch
// http://playground.arduino.cc/Main/MPU-6050#short
//
#include <Wire.h>
#include <EEPROM.h>
const int MPU_addr = 0x68; // I2C address of MPU-6050 / MPU-9250
float x, y, z; // the acceleration
int16_t x_offset, y_offset, z_offset;
const uint16_t MARKER_ID = 0x23A2; // a marker and a version for the EEPROM data
void setup()
{
Serial.begin( 9600);
Wire.begin();
while(!Serial); // For Leonardo/Micro/M0
Serial.println();
Serial.println(F( "Waterpas 2"));
Serial.println(F( "Warning: this sketch is Quick and Dirty"));
Serial.println(F( "Command 'c' to calibrate the offsets."));
Serial.println(F( "Command 'w' to wipe the EEPROM."));
uint16_t marker;
EEPROM.get( 0, marker);
EEPROM.get( 2, x_offset);
EEPROM.get( 4, y_offset);
EEPROM.get( 6, z_offset); // z-axis offset is not used at the moment
if( marker != MARKER_ID)
{
Serial.println(F( "The level is not offset compensated yet."));
x_offset = 0;
y_offset = 0;
z_offset = 0;
}
Serial.print(F( "Offset (raw) x, y = "));
Serial.print( x_offset);
Serial.print(F( ", "));
Serial.println( y_offset);
init_sensor();
// Start the filter with measured values.
// This is to make the output accurate, right from the first sample.
int16_t xi, yi, zi;
get_accel( xi, yi, zi);
x = float( xi - x_offset);
y = float( yi - y_offset);
z = float( zi);
}
void loop()
{
if( Serial.available())
{
char c = Serial.read();
if( isprint( c))
{
switch( c)
{
case 'c':
Calibrate();
// Reset the filter, after the calibration.
// Assuming the angle is close to zero degrees now.
x = 0.0;
y = 0.0;
break;
case 'w':
Serial.println(F( "EEPROM data wiped."));
for( int i=0; i<8; i++)
{
EEPROM.write( i, 0xFF);
}
// Clear the offset as well.
x_offset = 0;
y_offset = 0;
z_offset = 0;
break;
}
}
}
int16_t xi, yi, zi;
get_accel( xi, yi, zi);
xi -= x_offset;
yi -= y_offset;
// z-axis is not used with offset ! It is pointing up.
// Dampen the binary values of the sensor with a filter.
// Very simple filter for now.
// Perhaps a moving average is better ?
// Perhaps filtering the Roll and Pitch is better ?
x = 0.99 * x + 0.01 * float( xi);
y = 0.99 * y + 0.01 * float( yi);
z = 0.99 * z + 0.01 * float( zi);
// Calculate the Roll and Pitch
float Roll = atan2( y, z) * 180.0 / M_PI;
float Pitch = atan2( x, sqrt( y*y + z*z)) * 180.0 / M_PI;
// Update display with slower rate.
static int count;
count++;
if( count >= 40)
{
count = 0;
// Serial.print( "Roll=");
Serial.print( Roll, 3);
Serial.print( ", ");
// Serial.print( "Pitch=");
Serial.println( Pitch, 3);
}
delay(10);
}
void init_sensor()
{
Wire.beginTransmission( MPU_addr);
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050 / MPU-9250)
Wire.endTransmission();
}
void get_accel(int16_t & AcX, int16_t & AcY, int16_t & AcZ)
{
Wire.beginTransmission( MPU_addr);
Wire.write( 0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
Wire.endTransmission();
Wire.requestFrom( MPU_addr, 6); // request only the accel registers
AcX=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
AcY=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
AcZ=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
}
void Calibrate()
{
const int num_samples = 1000;
Serial.println(F( "Calibrating, please wait"));
// Use long variables for averaging
long xl = 0;
long yl = 0;
long zl = 0;
for( int i=0; i<num_samples; i++)
{
int16_t xi, yi, zi;
get_accel( xi, yi, zi);
xl += long( xi);
yl += long( yi);
zl += long( zi);
delay(1);
}
// Store the average offset in the global variables and in EEPROM.
x_offset = int( xl / num_samples);
y_offset = int( yl / num_samples);
z_offset = int( zl / num_samples);
EEPROM.put( 0, MARKER_ID);
EEPROM.put( 2, x_offset);
EEPROM.put( 4, y_offset);
EEPROM.put( 6, z_offset);
Serial.println(F( "Done"));
}
In Wokwi simulation: Spirit Level version 2 - Wokwi ESP32, STM32, Arduino Simulator
When the simulation is running, click on the MPU-6050 module to change the settings.
I think that it almost reaches the 90 and -90 degrees.
A real spirit level uses the earth gravity to keep the bubble pointing upward.
Therefor you only need the accelerometer to make a spirit level in my opinion.
Sorry, I don't know.
@jremington Do you know code for a spirit level that shows the angle of one axis all around in 360 degrees (or at least from -90 to +90) while the result is not too slow ?