Raspberry Pi IMU

Raspberry Pi Digital Spirit Level


In this post we show how to create a Digital Spirit Level using a Raspberry Pi and python.

The code moves that bubbles on the display in relation to the angle read from the IMU.
Parts used in this project;

Any IMU or TFT can be used, however the code would need to be updated to accommodate the different devices. It is best to use a 480×320 TFT as the images are scaled to fit this resolution.

This guide assumes that some basic understanding of an IMU(Accelerometer and Gyroscope)  is already known. And you have one already working with your Raspberry Pi.

If you don’t, we do have some guides which covers this.

BerryIMU Raspberry Pi Gyroscope Accelerometer


We have used our existing python code to read the values from the IMU, however we have removed the code related to the magnetometer as it isn’t needed for this project.

Git repository here
The code can be pulled down to your Raspberry Pi with;

pi@raspberrypi ~ $ git clone http://github.com/mwilliams03/BerryIMU.git


Placement of IMU

The IMU can be attached anywhere, however it is best to place it in the same orientation as shown below. If you do change the orientation, you will need to update the code accordingly.



Pygame is used to display the images to the TFT, so the first task that needs to be done is to install pygame.

pi@raspberrypi ~ $ sudo apt-get install python-pygame


Now we can update our IMU code and tell it to use pygame to display images.
The next pieces of code can be added anywhere before the main loop.

Import the pygame framework into the code;

import pygame

We then set our display mode to get a surface(screen) which we will draw to. And we also get the dimensions of the surface which will be used to tell pygame what area needs to be updated.

screen = pygame.display.set_mode ( ( 480,320 ))				#Set display mode
screen_rect = screen.get_rect()								#Get the rectangular area of the screen Surface

We now can initialise pygame. However, before we do this we need to specify where we want the output displayed. This can be done by setting the SDL_FBDEV environment variable.

os.putenv('SDL_FBDEV', '/dev/fb1')		#Set the display to fb1, which is the small TFT
pygame.display.init()				#Initialise pygame.  	

We then can load the images.

#Load images and convert to the correct format. Alpha is needed for PNGs to preserve alpha channel
img_background = pygame.image.load ( "spirit-level-background.jpg" ).convert()
img_Hbubble = pygame.image.load ( "spirit-level-Hbubble.png" ).convert_alpha()
img_Vbubble = pygame.image.load ( "spirit-level-Vbubble.png" ).convert_alpha()
img_Cbubble = pygame.image.load ( "spirit-level-Cbubble.png" ).convert_alpha()
img_overlay = pygame.image.load ( "spirit-level-overlay.png" ).convert_alpha()
#These images are used when only displaying the horizontal bar
img_background_H_only = pygame.image.load ( "spirit-level-H-only-background.jpg" ).convert()
img_Hbubble_H_only = pygame.image.load ( "spirit-level-H-only-bubble.png" ).convert_alpha()	
img_overlay_H_only = pygame.image.load ( "spirit-level-H-only-overlay.png" ).convert_alpha()

The mouse cursor is also disabled

pygame.mouse.set_visible(False)								#Disable the mouse cursor

Show the images on the TFT
Showing the images on the screen can be done by blitting the images to the surface. Order is important as we want the background to actually be in the background.

The second values in the functions below are the X and Y coordinates of where we blit the images on the surface. Changing the values of hBposition, vBposition cBposition is how we move the bubbles.

screen.blit(img_background, screen_rect)							
screen.blit(img_Hbubble, hBposition)							
screen.blit(img_Vbubble, vBposition)							
screen.blit(img_Cbubble, cBposition)
screen.blit(img_overlay, screen_rect)

And finally, we flip the surface so the TFT is updated


Moving  the Bubbles!

We can easily move the bubbles using the angles returned from the IMU.

This can be done by rescaling the values from an angle, which are then used to move the bubbles on the display.

Lets look at the X angle which  is represented on the display as the bottom horizontal bar in the image below.

The array hSlideLimit stores the left limit and right limit of where the bubble can move.  These correspond to the X coordinates of the display.  The horizontal bubble can only move between 62 and 252.

Raspberry Pi Spirit Level

The X axis values from -90 to +90 from the IMU will be represented by the bottom bubble.
This means that we need to convert the range of;
-90  to +90  (0 is the center)
61 to 252 (95 is the center)

This can be done with the below function.

hSlideLimit = [62,252]
angleLimit  = 90
def scaleH(value):
	min = -angleLimit
	max = angleLimit
	minScale = hSlideLimit[0]
	maxScale = hSlideLimit[1]
	scaled = minScale + (value - min)/(max-min) * (maxScale - minScale)
	return scaled

min and max is the range we will accept from the IMU, which is between -90 and +90
micScale and maxScale are the values we want to convert them to.
The function then returns the new scaled value.

Here is the code we use to call the above function;

newHvalue = int(scaleH(CFangleX))
hBposition = (newHvalue,244)
screen.blit(img_Hbubble, hBposition)

We pass CFangleX to scaleH(). And we store the returned value in newHvalue.
We also type cast it as int as a floating point value will be returned and we need a whole number to plot to the display.
hBposition is updated with the new horizontal value. 244 never changes for this bubble as this is the Y position of the bubble on the display.
The bubble is then blitted to the display with the new coordinates.

Two different display modes
There are two different display modes.
If the angle of Y is larger than 70 or smaller than -70, we only display the large horizontal bar. Otherwise we show all three.

if CFangleY > 70 or CFangleY < -70:

Low Pass filter

We use a low pass filter to reduce noise. This will stop the bubbles from bouncing around, however it will add a little delay in the movement.  This delay also makes the bubbles look more realistic when they move. The lower the low pass filter factor (LPF_FACT0R) the less noise and the larger the delay.
The filter gets applied to the raw values of the accelerometer and should be applied right after reading the values from the accelerometer.

#Apply low pass filter to reduce noise
ACCx =  ACCx  * ACC_LPF_FACTOR + oldXAccRawValue*(1 - ACC_LPF_FACTOR);
ACCy =  ACCy  * ACC_LPF_FACTOR + oldYAccRawValue*(1 - ACC_LPF_FACTOR);
ACCz =  ACCz  * ACC_LPF_FACTOR + oldZAccRawValue*(1 - ACC_LPF_FACTOR);
oldXAccRawValue = ACCx;
oldYAccRawValue = ACCy;
oldZAccRawValue = ACCz;

8 thoughts on “Raspberry Pi Digital Spirit Level”

  1. Very nice article. I have some SMD IMU chips I plan to use as soon as I get my Voltera (by August, is latest estimate), so I think your code will come in handy. The chip, no doubt, has different specs, but I’m sure I can figure it out.

    If I create a branch of your code, I’ll let you know. (I’ve not studied it.)

    This is a great project, I can already tell (by the video and the Python code in this post).

    Thanks for sharing, and I’ll check out your code more in-depth soon! I love to browse open source projects, which is why I have a few of my own. 😉

  2. I think this is a GREAT project for us to use in our RV. I have all of the required parts. I guess the only question would be is if you think that the IMU could be a unit separate from the Pi and with the use of a ZigBee Tx/Rx module (if necessary a Arduino Nano) to transmit the data to the Pi. To get an accurate reading on how level the RV is, we put the level on the floor inside the RV. Currently, I put a regular 3′ level on the floor and make adjustments and go back into the RV and check, repeating the process until it is level.
    Thanks Don

Leave a Reply