Digitaluhr mit Schrittzähler

Rundes 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel
- RP2040, 6-Achsen-Sensor - Waveshare 22668
Anleitung für CircuitPython 9.0.x


Bildbox 1 (klick hier)

Diese Seite hat seit dem 23.08.2023 ein Update. Nachdem ich den Treiber für die Sensoren in CircuitPython fertig hatte, wurde dieses Projekt erweitert. Genaue Hinweise zur QMI8658-library finden Sie in der Anleitung (hier). Das oben gezeigte Bild in der Bildbox hatte ja so wie abgebildet noch nicht funktioniert. Jetzt ändern wir das.

Der ursprüngliche Text bleibt farblich unverändert. Der hinzugefügte Text ist jetzt durchgehend blau dargestellt.


Hardware

- Rundes 1,28-Zoll-IPS-LCD-Display
- RP Pico 2040 (inclusive)
- USB-A zu USB-C Kabel


Überall tauchen diese kleinen runden Displays von Waveshare jetzt zum Kaufen auf. Das was die Abbildungen zeigen, sieht sehr vielversprechend aus. Ein rundes LCD-1.28 Display mit einer Auflösung von 240x240 Pixeln, IPS-Bildschirm und 65K RGB-Farben. Da fallen mir sofort eine Menge Anwendungsmöglichkeiten ein.

Sucht man im Netz aber nach lauffähigen 'Examples' vergleichbar zu den Bildern von den Kaufangeboten des Displays, ist man aufgeschmissen. Außer ein paar sehr einfachen Beispielen unter MicroPython findet man nichts für den Hobbyprogrammierer. Selbst von Waveshare existiert z.Z. nur ein MicroPython-Beispiel, welches die Funktion der Sensoren zeigt. Damit kann man noch nicht wirklich viel anfangen. Nur ein MicroPython Beispiel für eine Analoguhr von Tony Goodhew ist für Anwender , die unter MicroPython arbeiten, verwertbar.

Im einem ersten Artikel hatte ich die Inbetriebnahme zur grundlegenden Funktion des runden LCD-Displays-1.28 beschreiben, um dann später ebenfalls eine Uhrfunktion unter CircuitPython, wie in den beiden unteren Bildern dargestellt, zu realisieren. Um in das Thema erfolgreich einzusteigen, können Sie die folgende zip-Datei herunterladen, entpacken und später auf den Pico übertragen, wenn es um die Beispiele geht. Enthalten sind auch die notwendigen Bibliotheken, welche im 'lib-Ordner' vorhanden sein müssen.


(Auf die Gehäuse gehe ich an anderer Stelle noch ein)


Los gehts

Das Display und der Pico sind von Hause aus verbunden. Es versteht sich, dass bisher keine Spannung angelegt ist! Als erstes wird die Firmeware für den Pico benötigt.

1. Bevor Sie den Pico erstmalig an die Stromversorgung anschließen, muss zunächst die CircuitPython-Firmware mit der Ansteuerung für das runde Pico Display übertragen werden. Ich nutze dazu die Seite von CircuitPython und lade die uf2-Datei für das runde Display von hier herunter:
https://circuitpython.org/board/waveshare_rp2040_lcd_1_28/

2. Nach dem Download der uf2-Datei muss diese auf das Display mit dem Pico übertragen werden. Im ersten Schritt nehmen Sie den Pico im ausgeschalteten Zustand zur Hand und drücken die Boot-Taste (siehe Beschriftung auf der Platinenrückseite). Die wird gehalten und der Pico erst dann (!!) über das USB-A zu USB-C Kabel mit dem Rechner verbunden. Nach dem Verbinden kann die Boot Taste losgelassen werden. Es öffnet sich im Filesystem ein Explorer-Fenster mit einem neuen Massenspeicher RPI-RP2. Öffnen Sie jetzt auf dem Rechner den Download-Ordner und ziehen die uf2-Datei mit der CircuitPython Firmware auf den Massenspeicher RPI-RP2. Sobald die Firmware-Datei auf den Pico kopiert ist, wird die Firmware ausgeführt und ein neuer Massenspeicher 'CIRCUITPY' im Explorerfenster angezeigt. Damit ist die Programmiersprache CircuitPython für die Ansteuerung des runden Pico-Displays installiert und die Programmierung kann über Thonny beginnen.

3. Falls bei der ersten Benutzung von Thonny kein Board erkannt wird, muss in den Interpreter-Einstellungen die Port-Auswahl überprüft werden und ggf. auf CircuitPython (generic) eingestellt werden. Klicken Sie dazu ganz unten rechts auf den 'Button'. Das Fenster sollte jetzt so aussehen:

In der unteren Kommandozeile wird der Controler z.B. mit "Adafruit CircuitPython 8.2.0 on 2023-07-05; Waveshare RP2040-LCD-1.28 with rp2040" angezeigt.

Jetzt öffnen Sie über das Thonny-Menü die Datei 'display_mit_text.py' und können sie ausführen. Wenn Sie die noch nicht heruntergeladen haben (siehe oben), sollten Sie das jetzt tun oder Sie kopieren den Code aus dem Kasten in die Zwischenablage indem Sie auf den Button 'Code kopieren' klicken und in Thonny einsetzen.
Sehen wir uns das Programm an und erklären die Details, die für unsere eigenen Anwendungen später wichtig sind.

  
  

1  # SPDX-FileCopyrightText: 2023 Detlef Gebhardt, written for CircuitPython
2  # SPDX-FileCopyrightText: Copyright (c) 2023 Detlef Gebhardt
3  #
4  # SPDX-License-Identifier: GEBMEDIA
5  import board
6  import busio
7  from busio import I2C
8  import displayio
9  import terminalio
10 import gc9a01
11 from adafruit_display_text import label
12
13 # Release any resources currently in use for the displays
14 displayio.release_displays()
15
16 #    imu_int1 = board.IMU_INT1 # Beschleunigungssensor
17 #    imu_int2 = board.IMU_INT2 # Beschleunigungssensor
18 #    bat_adc = board.BAT_ADC   # Baterieanzeige
19
20 # Make the displayio SPI bus and the GC9A01 display
21 spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN)
22 display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST)
23 display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL)
24
25 # Make the display context
26 splash = displayio.Group()
27 display.root_group = splash
28 # Make the background bitmap
29 color_bitmap = displayio.Bitmap(240, 240, 1)
30 color_palette = displayio.Palette(1)
31 color_palette[0] = 0x03C2FC
32 bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
33 splash.append(bg_sprite)
34
35 def text_show():
36     text_group_t1 = displayio.Group(scale=2, x=20, y=70)
37     text1 = " rundes Display\n\n" + "1.28-Zoll-IPS-LCD\n\n" + "  240x240 Pixel"
38     text_area1 = label.Label(terminalio.FONT, text=text1, color=0x0000ff)
39     text_group_t1.append(text_area1)
40     splash.append(text_group_t1)
41
42 text_show()
43
44 while True:
45     pass
  

In den Zeilen 5 bis 11 werden alle benötigten Module importiert. Wichtig ist, dass sich die Module 'adafruit_display_text' und 'gc9a01' (Treiber für das runde Display) im lib-Ordner befinden, sonst kommt später eine Fehlermeldung. Die Zeilen 13 bis 23 initialisieren das Display und kommen in der Form in jedem späteren Programm vor. Die auskommentierten Zeilen 16, 17, 18 nutzen später die Sensoren. Ein Hinweis sei auch zu Zeile 23 gegeben. Die Anweisung 'rotation=90' dreht die Darstellung auf dem Display. Es sind nur 90 Grad Schritte möglich.

In Zeile 26 und 27 wird die Gruppe 'spash' definiert und für die Darstellung auf dem Display bereitgestellt. Die Zeilen 29 bis 33 erzeugen das farbige Bitmap als Hintergrund und fügen es der Gruppe 'splash' hinzu. Jedes Objekt, welches auf dem Display dargestellt werden soll, wird also zunächst defieniert und im Speicher abgelegt.

In gleicher Weise bin ich mit dem Text verfahren. Nur habe ich dafür eine Funktion geschrieben (Zeilen 35 bis 40) und aus Zeile 42 aufgerufen. Da in unserem Fall noch nichts weiter passiert, steht in der 'while' Schleife die Anweisung 'pass', damit kein Fehler erzeugt wird. Später erfolgen hier weitere Befehle.

Ein allgemeiner Hinweis scheint mir noch wichtig. Jedes definierte und im Speicher abgelegt Objekt belegt Speicherplatz im RAM und der ist ja beim PICO nicht gerade reichlich. Gehen Sie also sehr bewußt damit um. Ich werde an entsprechenden Stellen noch darauf verweisen. Mit dem Befehl 'print(gc.mem_free())' können Sie sich den freien Speicher anzeigen lassen und mit 'gc.collect()' nicht mehr benötigten RAM freigeben. Wenn Sie das ausprobieren wollen, müssen Sie am Programmanfang mit 'import gc' das Modul importieren.

Als nächstes aktivieren wir den ACC-Gyro-Sensor und entwickeln schrittweise eine Fitnessuhr, wie in der oberen Bildbox gezeigt. Dazu kopieren Sie den unteren Quelltext und setzen ihn in ein neues Fenster im Thonny ein. Dazu ein paar Erläuterungen:
- In den Zeilen 7 und 8 werden die Treiber für das Display und den Sensor importiert.
- Da wir die Schritte später veranschaulichen wollen, wird eine 'Progressbar' verwendet. Dazu importieren wir den Treiber von Adafruit in Zeile 14.
- Damit auch unabhängig vom PC Datum, Wochentag und bereits gezählte Schritte erhalten bleiben, speichern wir diese jetzt ab und lesen sie beim Programmstart aus der Datei (Zeilen 17 bis 22).
- In den Zeilen 33 bis 42 werden das Display und der Sensor initialisiert.
- Der Rest ist Hintergrundgestaltung. Bei einem Klick auf 'Start' sehen Sie diesen, aber weiter noch nichts.

  
  

1  import time
2  import gc
3  import board
4  import busio
5  import displayio
6  import terminalio
7  import gc9a01
8  import my_qmi8658
9  from adafruit_display_text import label
10 from adafruit_ticks import ticks_ms
11 from adafruit_display_shapes.rect import Rect
12 from adafruit_display_shapes.roundrect import RoundRect
13 from adafruit_display_shapes.line import Line
14 from adafruit_progressbar.horizontalprogressbar import (HorizontalProgressBar, HorizontalFillDirection)
15
16
17 wdays = ["Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag"]
18 # Schritte lesen
19 step = 0
20 with open("time.txt", "r") as f:
21     step = int(f.readline())
22 f.close()
23 ziel = 6000
24 meter = 0
25 cal = 0
26 stepstop = 0
27 #progress
28 width = 160
29 height = 40
30 x = 40
31 y = 120
32
33 # Release any resources currently in use for the displays
34 displayio.release_displays()
35
36 # Make the displayio SPI bus and the GC9A01 display
37 spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN)
38 display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST)
39 display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL)
40
41 # acc initialisieren
42 sensor=my_qmi8658.QMI8658()
43
44 # Make the display context
45 main = displayio.Group()
46 display.show(main)
47
48 # make bitmap that spans entire display, with 2 colors
49 background = displayio.Bitmap(240, 240, 1)
50 mypal = displayio.Palette(3)
51 mypal[0] = 0x330019
52 mypal[1] = 0xff9999
53 background.fill(0)
54 main.append(displayio.TileGrid(background, pixel_shader=mypal))
55 background2 = Rect(0, 140, 240, 100, fill=0x00994c )
56 main.append(background2)
57 line = Line(120,195,120,230,color=0x000000)
59 main.append(line)
60
61
  

Im 2. Teil werden die Progressbar, die Anzeigen der Uhrzeit, die Schritte und die entsprechenden Meter und verbrauchten KJoule im Speicher hinzugefügt. Außerdem wird die Erfassung der Uhrzeit (Display am PC oder externe Stromversorgung) organisiert. Ich habe dazu bereits früher erläutert, dass 'localtime()' bei externer Stromversorgung am 1.1.2020 um 00:00 Uhr beginnt, die Sekunden zu zählen. Deshalb erfolgt bei jedem Start am PC die Erfassung der aktuellen Werte und Speicherung. Bei Bedarf wird dann auf die letzten bekannten Werte aus der Datei 'time.txt' zurückgegriffen. Die Zeilen 100 bis 126 sind dafür zuständig. Die Zeilen 128 bis 132 legen den String mit der Datumsanzeige und dem Wochentag (wie im Deutschen üblich) im Speicher ab. Auch bis dahin können Sie sich den Programmablauf schon anschauen.

  
  

61  # Create a new progress_bar object at (x, y)
62  progress_bar = HorizontalProgressBar((x, y), (width, height), direction=HorizontalFillDirection.LEFT_TO_RIGHT)
63  main.append(progress_bar)
64  #current_value = progress_bar.minimum
65  current_value = step/ziel * 100
66
67  # create the label for the time-hour and minute
68  updating_label_time = label.Label(font=terminalio.FONT, text="00:00", scale=3, color=0xffffff, line_spacing=1)
69  # set label position on the display and add label
70  updating_label_time.anchor_point = (0, 0)
71  updating_label_time.anchored_position = (70, 40)
72  main.append(updating_label_time)
73
74  # create the label for the time-second
75  updating_label_sec = label.Label(font=terminalio.FONT, text="00", scale=2, color=0xffffff, line_spacing=1)
76  # set label position on the display and add label
77  updating_label_sec.anchor_point = (0, 0)
78  updating_label_sec.anchored_position = (165, 50)
79  main.append(updating_label_sec)
80
81  # create the label for the steps
82  updating_label_steps = label.Label(font=terminalio.FONT, text="", scale=2, color=0x0000ff, line_spacing=1)
82  # set label position on the display and add label
84  updating_label_steps.anchor_point = (0, 0)
85  updating_label_steps.anchored_position = (70, 127)
86  main.append(updating_label_steps)
87  # create the label for the steps
88  text_group_step = displayio.Group(scale=1, x=90, y=170)
89  text_area1 = label.Label(terminalio.FONT, text="Schritte", color=0xffffff)
90  text_group_step.append(text_area1)
91  main.append(text_group_step)
92
93  # create the label for the meter/cal
94  updating_label_meter = label.Label(font=terminalio.FONT, text="", scale=1, color=0xffff00, line_spacing=1)
95  # set label position on the display and add label
96  updating_label_meter.anchor_point = (0, 0)
97  updating_label_meter.anchored_position = (80, 190)
98  main.append(updating_label_meter)
99
100 current_time = time.localtime()
101 hour = current_time.tm_hour
102 minute = current_time.tm_min
103 second = current_time.tm_sec
104 month = current_time.tm_mon
105 day = current_time.tm_mday
106 weekday = current_time.tm_wday
107
108 if current_time.tm_year > 2020:
109     with open("/time.txt", "w") as f:
110         f.write(str(step)+"\n")
111         f.write(str(hour)+"\n")
112         f.write(str(minute)+"\n")
113         f.write(str(day)+"\n")
114         f.write(str(month)+"\n")
115         f.write(str(weekday)+"\n")
116     f.close()
117 if current_time.tm_year == 2020:
118     with open("time.txt", "r") as f:
119         step = int(f.readline())
120         hour = int(f.readline())
121         minute = int(f.readline())
122         day = int(f.readline())
123         month = int(f.readline())
124         weekday = int(f.readline())
125     f.close()
126     second = current_time.tm_sec
127
128 # create the label for the date
129 text_group_day = displayio.Group(scale=2, x=25, y=95)
130 text_area1 = label.Label(terminalio.FONT, text="{:02}.{:02}. ".format(day,month) + wdays[weekday], color=0xffffff)
131 text_group_day.append(text_area1)
132 main.append(text_group_day)
133
134 start = ticks_ms()
135
  

Jetzt folgt die 'while'-Schleife. In den Zeilen 137 bis 144 werden die 'ACC-Werte' des Sensors gelesen. Zeile 138 holt mit 'xyz = sensor.Read_XYZ()' aus der Liste die Werte xyz[0], xyz[1], xyz[2]. Diese werden den Variablen 'wert_x', 'wert_y' und 'wert_z' zugewiesen (aber noch nicht, wie im Demobeispiel angezeigt).
Ab Zeile 145 geht es nur um die Uhrzeit. Jede Minute wird diese hochgezählt und die Progressbar läuft einmal bis zum bereits erreichten Schrittwert (z.Z. die Zahl aus der Datei 'time.txt'). Wenn Sie das Programm starten, können Sie das sehen.

  
  

136 while True:
137     #read QMI8658
138     xyz=sensor.Read_XYZ()
139     # Display wird rel. zur x-Achse bewegt
140     wert_x = (10)*xyz[0]
141     # Display wird rel. zur y-Achse bewegt
142     wert_y = (10)*xyz[1]
143     # Display wird rel. zur z-Achse bewegt
144     wert_z = (10)*xyz[2]
145     # Zeit
146     current_time = time.localtime()
147     second = current_time.tm_sec
148     if second == 59:
149         minute += 1
150         with open("/time.txt", "w") as f:
151             f.write(str(step)+"\n")
152             f.write(str(hour)+"\n")
153             f.write(str(minute)+"\n")
154             f.write(str(day)+"\n")
155             f.write(str(month)+"\n")
156             f.write(str(weekday)+"\n")
157         f.close()
158         for current_value in range(progress_bar.minimum, step/ziel * 100 + 1, 1):
159             progress_bar.value = current_value
160             time.sleep(0.01)
161         if minute == 60:
162             minute = 0
163             hour += 1
164             if hour == 24:
165                 hour = 0
166     #
167     # Zeitstring zur Anzeige aufbereiten
168     #
169     zeit = str(hour) + ":" + str(minute)# + ":" + str(second)
170     if second < 10:
171         zeit = str(hour) + ":" + str(minute)# + ":" + "0" + str(second)
172     if minute < 10:
173         zeit = str(hour) + ":" + "0" + str(minute)# + ":" + str(second)
174     updating_label_time.text = zeit
175     if second < 10:
176        updating_label_sec.text = ":0"+str(second)
177     else:
178        updating_label_sec.text = ":"+str(second)
  

Der entscheidende Teil kommt jetzt mit dem 4. Schritt hinzu. Im unteren Kasten werden jetzt die Schritte registriert und angezeigt. Diesen Teil habe ich aus meinem Sensortest, den ich unter Micropython durchgeführt habe, übernommen. Den können Sie hier noch einmal ansehen. Mit den Werten kann man durchaus noch ein wenig experimentieren, aber grundsätzlich funktioniert alles und mein Hauptanliegen, den Sensor unter CircuitPython ans 'Laufen' zu bringen, ist erreicht. Besonders gefällt mir persönlich die 'Progressbar' von Adafruit, die sicher auch andere Projekte optisch aufwertet.

  
  

179     #
180     # Arm noch oben bewegen
181     #
182     if (abs(wert_x) < 7 and abs(wert_y) > 3 or abs(wert_x) < 7 and abs(wert_z) > 3) and stepstop == 0:
183         start = ticks_ms()
184         step += 1
185         stepstop = 1
186         updating_label_steps.text = str(step-1)+"/"+ str(ziel)
187         current_value = step/ziel * 100
188         progress_bar.value = current_value
189         meter = int((step-1)*0.762)
190         cal = int(int((step-0.38)*0.38)*4.18)/1000
191         updating_label_meter.text = str(meter) + "       " + str(cal) + "\nMeter    KJoule"
192     #
193     # Arm wieder nach unten
194     #
195     if (abs(wert_x) > 7 and abs(wert_y) < 3 or abs(wert_x) > 7 and abs(wert_z) < 3) and stepstop == 1:
196         if (ticks_ms() - start)> 1000:
197             stepstop = 0
198     updating_label_steps.text = str(step)+"/"+ str(ziel)
199     progress_bar.value = current_value
200
201     if step == ziel:
202         step = 0
203         progress_bar.value = progress_bar.minimum
204     time.sleep(0.2)
205     gc.collect()
206     #print(gc.mem_free())
207
  




Viel Spass und Erfolg beim Ausprobieren.