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 480x320 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.
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;
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
Pygame is used to display the images to the TFT, so the first task that needs to be done is to install 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
pygame.display.flip()
[wp_ad_camp_4]
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.
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)
to
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 ACC_LPF_FACTOR = 0.05 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;
[wp_ad_camp_3]
Very nice practical work. Thanks for sharing. Rgds.
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. 😉
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
Has your Project worked out well? leveling your RV?
I would like to use it in my european Hobby Caravan.
knd regards
ad van der ende
Right there with you….. I immediately thought, This will be perfect for leveling the RV….. Just a matter of getting the display remote…. I was thinking if it could be a dynamic web page served by the pi, I could access it from the smartphone in my car. Going to have to build this, then tinker to see what I can do to get the visual remotely. Of course if anyone here more familiar with this would care to share their knowledge and point me in the right direction, it would be greatly appreciated 😉
Actually, on 2nd thought, I think the best way to do this would be with 2 Pi Zero’s….. 1 completely headless with the IMU and the other with the display screen…. send the data from the IMU to the pi with the display screen using MQTT
I should be able to build the equivalent of a LevelMate Pro leveling system for about $50-$60
Think it would be possible to output the display as a web page instead? I’d really love to be able to use this to help in leveling my RV.. I could have the Pi permanently mounted in the RV, and connect via smartphone screen to view how level I am from the cab of the truck as I drive up onto the leveling blocks.
Has anyone worked on this project yet? I just got a 30 foot travel trailer and would love to build this. However, I am not a programer and have no idea where to even start. I have a Pi Zero and thats about it. If someone is working on this please let me know and I would be happy to help as much as I could testing it.
Did you start this project yet? I have been looking for anyone who has done this but haven’t found any results except f or this one guy here (https://www.youtube.com/watch?v=aCiFrJ4rooo)
but he has not posted how he made it. I asked him if he could post the directions but no response yet 🙁
This would work brilliantly for all sorts of applications. A pi zero, headless with the IMU on, running a web server with a small rechargeable (solar?) battery pack would be immense.
Then you could log onto the webserver with any phone and see the level. You could also program this to inform you of the need for changes. Front right up, read right down, etc..
Hello,
I like to use your BerryIMU v3 with the raspberry pi pico
and micro_python.
Unfortunately I am unable to read the magnetometer data s.
IMU_SPI.py only reads accelerometer and and gyroscope.
Thanks for your help
Wilhelm