PicoBoy - elektronische Wasserwaage

technische Daten wie beim RPi Pico
MC RP2040 mit OLED-Display 128x64 Pixel

Anleitung für die Firmware CircuitPython



Hardware

- PicoBoy
- USB-A zu USB-C Kabel

Software

- CircuitPython 9.0.0-beta oder neuer
- Bibliotheken im 'lib'-Ordner

Heute geht es um eine echt praktische Anwendung. Mit Hilfe des Pico-Boys im Gehäuse aus dem ersten Beitrag wird eine elektronische Wasserwaage realisiert, welche jederzeit einsatzbereit ist. Die dabei genutzten Progressbalken zeigen mit ihrem Ausschlag nach links oder rechts an, wenn der Untergrund nicht in der Waage ist. Durch die relativ kurze Zeit, die das 'Gerät' eingeschaltet sein muss und den integrierten 'Ruhemodus' , reicht auch die Kapazität der Knopfzelle für einen längeren Zeitraum.

Los gehts

Um die Abweichung aus der Waagerechten zu erfassen, wird der Beschleunigungssensor des Pico-Boys genutzt. Wie man das macht, habe ich in diesem Beitrag auf meiner Seite beschrieben. Grundlage dafür bietet der von mir in CircuitPython angepasste Treiber 'my_stk8ba58.py'. Daraus folgt der im unteren Kasten dargestellt Programmanfang:

  
  

1  import time
2  import board
3  import busio
4  import displayio
5  import digitalio
6  import terminalio
7  import fourwire
8  from adafruit_display_text import label
9  from adafruit_display_shapes.roundrect import RoundRect
10 from adafruit_display_shapes.circle import Circle
11 import adafruit_displayio_sh1106
12 import my_stk8ba58
13 from adafruit_progressbar.horizontalprogressbar import (
14     HorizontalProgressBar, HorizontalFillDirection,)
15
16 #rote LED
17 led_red = digitalio.DigitalInOut(board.GP5)
18 led_red.direction = digitalio.Direction.OUTPUT
19 #grüne LED
20 led_green = digitalio.DigitalInOut(board.GP7)
21 led_green.direction = digitalio.Direction.OUTPUT
22 #JOY_CENTER = GP0
23 center = digitalio.DigitalInOut(board.GP0)
24 center.direction = digitalio.Direction.INPUT
25 center.pull = digitalio.Pull.UP
26
27 # Compatibility with both CircuitPython 8.x.x and 9.x.x.
28 # Remove after 8.x.x is no longer a supported release.
29 try:
30     from fourwire import FourWire
31 except ImportError:
32     from displayio import FourWire
33
34 # built-in a display
35 displayio.release_displays()
36 # Make the displayio SPI bus and the sh1106 display
37 spi = busio.SPI(board.GP18, board.GP19)
38 #
39 # Circuit 8.x.x
40 #display_bus = displayio.FourWire(spi, command=board.GP8, chip_select=board.GP10, reset=board.GP9, baudrate=1000000)
41 #display =adafruit_displayio_sh1106.SH1106(display_bus, width=132, height=64, rotation=0)
42 #
43 # Circuit 9.x.x
44 display_bus = FourWire(spi, command=board.GP8, chip_select=board.GP10, reset=board.GP9, baudrate=1000000)
45 display =adafruit_displayio_sh1106.SH1106(display_bus, width=132, height=64, rotation=0)
46
47 # Make the display context
48 splash = displayio.Group()
49 sleep = displayio.Group()
50
51 # ACC-Sensor initialisieren
52 sensor=my_stk8ba58.STK8BA58()
53
  

Hinweisen möchte ich hier noch einmal besonders auf die Zeilen 27 bis 46. Wie ich bereits an früherer Stelle erwähnt habe, gibt es bei der Version CircuitPython 9.x.x gegenüber Version 8.x.x einige Änderungen, die nicht kompatibel sind. Solange Version 9.0.0 noch als beta-Version läuft, werde ich auch die Benutzung von CircuitPython 8.x.x mit berücksichtigen, so dass möglichst keine Programmabbrüche erfolgen.

In den beiden Programmzeilen 48 und 49 werden zwei (!) Displaybereiche definiert

- splash = displayio.Group() und
- sleep = displayio.Group()

zwischen denen beim Betätigen der Joystick-Taste umgeschaltet wird. Der Befehl dazu lautet jetzt display.root_group = sleep bzw. display.root_group = splash und ersetzt den früheren Befehl display.show(xxxxx), der ab Version 9.x.x nicht mehr vorkommt. Was will ich mit den beiden Anzeigebereichen erreichen? Beim Einschalten des Pico-Boys ist der Bereich 'sleep' zu sehen.



Bei einem Druck auf den Joystick-Button wird in den 'Messmodus' gewechselt bzw. aus dem 'Messmodus' wieder in den 'Ruhemodus' zurückgeschaltet.



Dazu werden vorab Textlabel und Zeichenelemente definiert, wie sie im Kasten in den Zeilen 54 bis 99 dargestellt sind. Im Anschluss werde ich die Benutzung der 'ProgressBar' erklären:

  
  

54 # Make the background bitmap when it sleeps
55 color_bitmap = displayio.Bitmap(128, 64, 1)
56 color_palette = displayio.Palette(1)
57 color_palette[0] = 0x000000
58 bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
59 sleep.append(bg_sprite)
60 # create the label, when it sleeps
61 text_label = label.Label(font=terminalio.FONT, text="PicoBoy", scale=2, color=0xffffff, line_spacing=1)
62 text_label.anchor_point = (0, 0)
63 text_label.anchored_position = (30, 0)
64 sleep.append(text_label)
65 text_label = label.Label(font=terminalio.FONT, text="press Joystick\n    to start", scale=1, color=0xffffff, line_spacing=1)
66 text_label.anchor_point = (0, 0)
67 text_label.anchored_position = (30, 40)
68 sleep.append(text_label)
69
70 rect1 = RoundRect(1,1,130,63,10,fill=0xffffff, outline=None)
71 splash.append(rect1)
72 rect2 = RoundRect(3,3,126,59,8,fill=0x000000, outline=None)
73 splash.append(rect2)
74
75 # Accelerometer Properties. Normally accelerometer well calibrated
76 # Will give a maximum output of 10 mts / s**2
77 # We create our bar displays
78 left_horizontal_bar = HorizontalProgressBar(
79     (20, 20),(50, 25), min_value=0, max_value=2,
80     direction=HorizontalFillDirection.RIGHT_TO_LEFT,
81     bar_color=0x00cc00, outline_color=0xffffff, fill_color=0x606060)
82 splash.append(left_horizontal_bar)
83
84 right_horizontal_bar = HorizontalProgressBar(
85     (70, 20), (50, 25), min_value=0, max_value=2,
86     direction=HorizontalFillDirection.LEFT_TO_RIGHT,
87     bar_color=0x00cc00, outline_color=0xffffff, fill_color=0x606060)
88 splash.append(right_horizontal_bar)
89
90
91 # create the label
92 text_label = label.Label(font=terminalio.FONT, text="  PicoBoy\n\n\n\nspirit-level", scale=1, color=0xffffff, line_spacing=0.95)
93 text_label.anchor_point = (0, 0)
94 text_label.anchored_position = (35, 4)
95 splash.append(text_label)
96
97 on = 0
98 display.root_group = sleep
99
  

Die 'ProgressBars' werden unterschieden nach

- HorizontalProgressBar und
- VerticalProgressBar

In unserem Fall verwende ich eine left_horizontal_bar (Zeile 78) und eine right_horizontal_bar (Zeile 84), um den Ausschlag nach links bzw. rechts anzuzeigen. Als Parameter werden:

- die Koordinaten der oberen linken Ecke (bzw. rechten Ecke) der Fortschrittsleiste erwartet,
- die Größe (Breite, Höhe) des Fortschrittsbalkens in Pixeln,
- der niedrigste und höchste Wert min_value = xx und max_value = yy und
- weiterhin direction, bar_color, outline_color und fill_color.

Beachte: Nicht der Name left_horizontal_bar bzw. right_horizontal_bar legt die Richtung des Ausschlags fest, sondern der Parameter direction=HorizontalFillDirection.RIGHT_TO_LEFT bzw. direction=HorizontalFillDirection.LEFT_TO_RIGHT.

In Zeile 97 wird die Variable 'on=0' gesetzt und der 'Ruhemodus' angezeigt. Nun folgt die 'while-Schleife'.

  
  

100 while True:
101     if center.value == False and on == 1:
102         on = 0
103         display.root_group = sleep
104         led_red.value = False
105         led_green.value = False
106         time.sleep(1)
107     if center.value == False and on == 0:
108         on = 1
109         display.root_group = splash
110         time.sleep(1)
111     if on == 1:
112         #read STK8BA58
113         wert_y = sensor.yAcc() * 10
114         #wert_y etwas kleiner kalibrieren laut Versuchen
115         wert_y = wert_y - 0.16
116         if wert_y >= 0 and wert_y < 0.2 or wert_y < 0 and wert_y >= -0.2 :
117             led_green.value=True
118             led_red.value=False
119         # Ausschlag nach links und rechts
120         if wert_y > 0 and wert_y <= 2:
121             if wert_y >= 0.2:
122                 led_red.value = True
123                 led_green.value = False
124             left_horizontal_bar.value = wert_y
125             right_horizontal_bar.value = 0
126             left_horizontal_bar.bar_color=0x00cc00
127         if wert_y < 0 and wert_y >= -2:
128             if wert_y <= -0.2:
129                 led_red.value = True
130                 led_green.value = False
131             right_horizontal_bar.value = -wert_y
132             left_horizontal_bar.value = 0
133             right_horizontal_bar.bar_color=0x00cc00
134         time.sleep(0.2)
  

Erläuterungen:
Zeilen 101 bis 106: Der Messmodus ist an und es wurde der Joystick gedrückt. Darauf wird in den Ruhemodus umgeschaltet und die rote und grüne LED ausgeschaltet. Die Sekunde 'time.sleep(1) dient dem Entprellen des Tasters.

Zeilen 107 bis 110: Es wird aus dem Ruhemodus in den Messmodus umgeschaltet.

Die Zeilen 110 bis 134 beschreiben die Abarbeitung im Messmodus. Als Sensorwert wird wert_y = sensor.yAcc() * 10 gesetzt. Versuche haben ergeben, dass es genauer war, diesen um 0.16 zu verringern (evtl. ist das Gehäuse nicht exakt gleich hoch auf beiden Seiten). Wenn sich nun die Werte zwischen -0.2 und +0.2 befinden (Zeile 116), wird fast kein Ausschlag angezeigt und die grüne LED ist an (rote LED aus). Damit wird der Zustand 'waagerecht' wiedergegeben. Alle anderen Werte zwischen -2 und +2 zeigen den entsprechenden Ausschlag nach links bzw. rechts und die rote LED ist an (Zeilen 120 bis 133). D.h. der Untergrund ist nach der entsprechenden Seite 'schief'.

Fazit:
Das Ziel bei der Messung ist also, dass die rote LED möglichst nicht und die grüne LED durchgehend leuchtet. Dann liegt das Gehäuse des Pico-Boy in der Waage bzw. der Untergrund ist waagerecht ausgerichtet. Durch einen kurzen Druck auf die Joysticktaste wird das Gerät in den Ruhemodus geschaltet, um Strom zu sparen und die Lebensdauer der Knopfzelle zu verlängern.


Viel Spass und Erfolg beim Ausprobieren.