Bubble Leveler
Uses accelerometers sensor to detect tilt and display leveling information digitally for precise alignment.
Introduction
This Bubble Leveler app turns your game module into a leveling tool using the built-in accelerometer. It helps you check if surfaces are flat or tilted. You can use it for adjusting tables, mounting shelves, setting up tripods, or aligning small projects.
This Bubble Leveler app uses a small bubble in the center of the screen. As you tilt the device, the bubble moves in the opposite direction. To level a surface, adjust until the bubble stays in the middle of both the x and y axis.
Programming Design

Source Code
By default, your device comes with the necessary firmware and libraries preinstalled. If you have modified or replaced the firmware or libraries, make sure the following files are present (download if need by click the link)
Below is the main program file along with any additional files required for this specific app.
Main.py
import time
import random
from breakout_colourlcd240x240 import BreakoutColourLCD240x240
from machine import I2C, Pin,ADC
from board import pin_cfg
from time import sleep
from mma7660 import MMA7660
from ws2812b import PixelDisplay
import framebuf
from math import atan,sqrt,sin,cos
#about adc
buttonB = Pin(5,Pin.IN, Pin.PULL_UP) #B
buttonA = Pin(6,Pin.IN, Pin.PULL_UP) #A
k=0.5#kalman
#about mma7660
i2c1 = I2C(1, scl=Pin(pin_cfg.i2c1_scl), sda=Pin(pin_cfg.i2c1_sda))
acc = MMA7660(i2c1)
acc.on(True)
d = bytearray(3)
r = [0 for x in range(3)]
def twos_compliment(n, nbits):
sign_bit = 1 << nbits - 1
sign = 1 if n & sign_bit == 0 else -1
val = n & ~sign_bit if sign > 0 else sign * ((sign_bit << 1) - n)
#print((n,val))
return val
#about LCD240x240
width = BreakoutColourLCD240x240.WIDTH
height = BreakoutColourLCD240x240.HEIGHT
display_buffer = bytearray(width * height * 2) # 2-bytes per pixel (RGB565)
display = BreakoutColourLCD240x240(display_buffer)
display.set_backlight(0)
class Ball:
def __init__(self, x, y, r, dx, dy, pen):
self.x = x
self.y = y
self.r = r
self.dx = dx
self.dy = dy
self.pen = pen
# initialise shapes
balls = []
#big circle
BigCircle_x=100
BigCircle_y=140
BigCircle_r=99
BigCircle_r1=49
BigCircle_r2=51
BigCircle_r3=9
BigCircle_r4=11
BigCircle_r5=97
BigCircle_dx=0
BigCircle_dy=0
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0,0,0),)
)
#circle0
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r5,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0x00,0xff,0x00),)
)
#circle1
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r2,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0,0,0),)
)
#circle2
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r1,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0x00,0xff,0x00),)
)
#circle3
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r4,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0,0,0),
)
)
#circle4
balls.append(
Ball(
BigCircle_x,
BigCircle_y,
BigCircle_r3,
BigCircle_dx,
BigCircle_dy,
display.create_pen(0x00,0xff,0x00),
)
)
#circle5
Ball_x=100
Ball_y=140
Ball_r=10
Ball_dx=0
Ball_dy=0
Ball_color=(0x00,0x00,0xff)
balls.append(
Ball(
Ball_x,
Ball_y,
Ball_r,
Ball_dx,
Ball_dy,
display.create_pen(0x00,0x00,0xff),)
)
#ball
balls.append(
Ball(
Ball_x,
Ball_y,
Ball_r,
Ball_dx,
Ball_dy,
display.create_pen(0x00,0x00,0xff),)
)
#ball up
balls.append(
Ball(
Ball_x,
Ball_y,
Ball_r,
Ball_dx,
Ball_dy,
display.create_pen(0x00,0x00,0xff),)
)
#ball right
def kalman(dx_now,dx_previous,k):
return dx_now*k+(1-k)*dx_previous
while True:
buttonValueA = buttonA.value()
buttonValueB = buttonB.value()
if buttonValueA==0:
k=round((k+0.1)%1.1,1)
if buttonValueB==0:
k=round((k-0.1)%1.1,1)
#set number-k
display.set_pen(0xff, 0x00, 0x00)
display.clear()
display.set_pen(0)
display.rectangle(0,40,200,200)#mid
display.rectangle(198,0,42,82)#right up
display.set_pen(0xff,0x00,0x00)
display.rectangle(2,42,196,196)
#get the data
acc.getSample(d)
for i in range(3):
if r[2]==0:
r[2]=0.001
if r[0]==0:
r[0]=0.001
if r[1]==0:
r[1]=0.001
r[i] = twos_compliment(d[i], 6)
#get the ball from the data
#print((r[0],r[1],r[2]))
Range=90
rx=180/3.1415926*atan(r[0]/sqrt(r[1]*r[1]+r[2]*r[2]))
ry=-180/3.1415926*atan(r[1]/sqrt(r[0]*r[0]+r[2]*r[2]))
rz=180/3.1415926*atan(r[2]/sqrt(r[1]*r[1]+r[0]*r[0]))
rx=rx*90/Range
ry=ry*90/Range
rz=int(rz*90/Range)
#start kalman
a=rx
rx=ry
ry=a#turn x to y,turn y to x
dy=ry-balls[6].y
dx=rx-balls[6].x
balls[6].dy=kalman(dy,balls[6].dy,k)
balls[6].dx=kalman(dx,balls[6].dx,k)
balls[6].y=balls[6].y+balls[6].dy
balls[6].x=balls[6].x+balls[6].dx#record the angle
ball_y=balls[6].y
ball_x=balls[6].x
#print((ball_x,ball_y))
#end kalman
if ball_x*ball_x+ball_y*ball_y>=BigCircle_r*BigCircle_r:
if ball_x==0:
ball_x=0.001
theta=atan(ball_y/ball_x)
balls[6].x=int(BigCircle_r*cos(theta))+100
balls[6].y=int(BigCircle_r*sin(theta))+140
else:
balls[6].y=int(ball_y+140)
balls[6].x=int(ball_x+100)
for i in range (9):
#print(i)
display.set_pen(balls[i].pen)
display.circle(balls[i].x, balls[i].y, balls[i].r)
if i==4:
display.set_pen(0)
display.rectangle(0,139,200,2)
display.rectangle(99,40,2,200)
#two lines
display.text("45",155,145,0,1)
display.text("-45",35,145,0,1)
display.text("45",90,80,0,1)
display.text("-45",90,200,0,1)
display.text("x",185,125,0,2)
display.text("y",90,40,0,2)
display.set_pen(0xff,0xff,0xff)
display.rectangle(200,0,40,80)
#right up rectangle
display.set_pen(0)
display.text("k:"+str(k),201,60,0,2)
display.text("x:"+str(balls[6].x-100),201,0,0,2)
display.text("y:"+str(-(balls[6].y-140)),201,20,0,2)
display.text("z:"+str(-rz),201,40,0,2)
#add the region lines-start
display.set_pen(0,0,0)
display.rectangle(48,8,104,24)#up
display.rectangle(208,88,24,104)#right
#add the region lines-end
display.set_pen(0,0xff,0)
display.rectangle(50,10,100,20)
display.rectangle(210,90,20,100)
#up and right rectangle
balls[7].x=int((balls[6].x-100)/2+100)
balls[7].y=20
balls[8].y=int((balls[6].y-140)/2+140)
balls[8].x=220
display.set_pen(0)
display.rectangle(100,10,2,20)
display.rectangle(75,10,2,20)
display.rectangle(125,10,2,20)
display.rectangle(210,140,20,2)
display.rectangle(210,115,20,2)
display.rectangle(210,165,20,2)
#up and right -lines
display.update()
time.sleep(0.1)
ws2812b.py
import array, time, math
from machine import Pin
import rp2
LED_COUNT = 12 # number of LEDs in ring light
PIN_NUM = 18 # pin connected to ring light
brightness = 1.0 # 0.1 = darker, 1.0 = brightest
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
autopull=True, pull_thresh=24) # PIO configuration
def ws2812():
T1 = 2
T2 = 5
T3 = 3
wrap_target()
label("bitloop")
out(x, 1) .side(0) [T3 - 1]
jmp(not_x, "do_zero") .side(1) [T1 - 1]
jmp("bitloop") .side(1) [T2 - 1]
label("do_zero")
nop() .side(0) [T2 - 1]
wrap()
state_mach = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
state_mach.active(1)
pixel_array = array.array("I", [0 for _ in range(LED_COUNT)])
def update_pix(brightness_input=brightness): # dimming colors and updating state machine (state_mach)
dimmer_array = array.array("I", [0 for _ in range(LED_COUNT)])
for ii,cc in enumerate(pixel_array):
r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
dimmer_array[ii] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
state_mach.put(dimmer_array, 8) # update the state machine with new colors
time.sleep_ms(10)
def set_24bit(ii, color): # set colors to 24-bit format inside pixel_array
color = hex_to_rgb(color)
pixel_array[ii] = (color[1]<<16) + (color[0]<<8) + color[2] # set 24-bit color
def hex_to_rgb(hex_val):
return tuple(int(hex_val.lstrip('#')[ii:ii+2],16) for ii in (0,2,4))
def on(n, color = "#ffffff"):
if not ((n >= 1 and n <= 12) and isinstance(n, int)):
print("arg error")
return
set_24bit((n - 1) % 12, color)
update_pix()
def off(n, color = "#000000"):
if not ((n >= 1 and n <= 12) and isinstance(n, int)):
print("arg error")
return
set_24bit((n - 1) % 12, color)
update_pix()
def on_all(color = "#ffffff"):
for i in range(0,12):
set_24bit(i, color)
update_pix()
def off_all(color = "#000000"):
for i in range(0,12):
set_24bit(i, color)
update_pix()
def light_value(l):
if l > 255: l = 255
elif l < 0: l = 0
return "#{0:02x}{1:02x}{2:02x}".format(l, l, l)
class PixelDisplay():
def __init__(self):
self.pixel_array = array.array("I", [0 for _ in range(12)])
def set_color(self, n, color):
"""set the color of pixel 'n
n - 1...12
color - color tuple"""
self.pixel_array[(n - 1) % LED_COUNT] = (color[1]<<16) + (color[0]<<8) + color[2]
def get_color(self, n):
v = self.pixel_array[(n - 1) % LED_COUNT]
return ((v >> 8) & 0xff, (v >> 16) & 0xff, v & 0xff)
def fill(self, c):
for i in range(1, LED_COUNT + 1):
self.set_color(i, c)
def dim(self, brightness_input = 1, n = None):
if n is not None:
cc = self.pixel_array[n - 1]
r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
self.pixel_array[n - 1] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
else:
for ii,cc in enumerate(self.pixel_array):
r = int(((cc >> 8) & 0xFF) * brightness_input) # 8-bit red dimmed to brightness
g = int(((cc >> 16) & 0xFF) * brightness_input) # 8-bit green dimmed to brightness
b = int((cc & 0xFF) * brightness_input) # 8-bit blue dimmed to brightness
self.pixel_array[ii] = (g<<16) + (r<<8) + b # 24-bit color dimmed to brightness
def rainbow(self, offset = 0):
for i in range(1, LED_COUNT + 1):
rc_index = (i * 256 // LED_COUNT) + offset
self.set_color(i, wheel(rc_index & 255))
def render(self):
state_mach.put(self.pixel_array, 8)
def wheel(pos):
"""Input a value 0 to 255 to get a color value.
The colours are a transition r - g - b - back to r."""
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
Demos
Last updated
Was this helpful?