Digitaluhr mit Schrittzähler

Rundes 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel
- RP2040, 6-Achsen-Sensor - Waveshare 22668
- RP2350, 6-Achsen-Sensor - Waveshare 28986

Anleitung getestet mit CircuitPython 10.0.0-beta


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 mit
- RP Pico 2040 oder RP Pico 2350
- 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 für den RP 2040 die Seite:
https://circuitpython.org/board/waveshare_rp2040_lcd_1_28/ ,

beim RP 2350 die Seite (hierfür gibt es z.Z. keine Waveshareseite zum Download):
https://circuitpython.org/board/raspberry_pi_pico2/
und lade die jeweils aktuelle uf2-Datei herunter:


2. Nach dem Download der uf2-Datei muss diese auf das Display mit dem MC übertragen werden. Im ersten Schritt nehmen Sie das Display im ausgeschalteten Zustand zur Hand und drücken die Boot-Taste (siehe Beschriftung auf der Platinenrückseite). Die wird gehalten und das Display 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 bzw. RP2350. Öffnen Sie jetzt auf dem Rechner den Download-Ordner und ziehen die uf2-Datei mit der CircuitPython Firmware auf den Massenspeicher RPI-RP2 bzw. RP2350. Sobald die Firmware-Datei auf das Display 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 10.0.0-beta.0 on 2025-07-15; Waveshare RP2040-LCD-1.28 with rp2040" bzw. "Adafruit CircuitPython 10.0.0-beta.0 on 2025-07-15; Raspberry Pi Pico 2 with rp2350"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 import fourwire
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.GP10, MOSI=board.GP11)
22 display_bus = fourwire.FourWire(spi, command=board.GP8, chip_select=board.GP9, reset=board.GP12)
23 display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.GP25)
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.

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

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.2000 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 135 bis 144 sind dafür zuständig.
Normal ist das Dateisystem schreibgeschützt. Damit es beschrieben werden kann, braucht es die Datei 'boot.py', welche beim Systemstart geladen wird. Achten Sie also darauf, dass Sie diese aus der entpackten zip-Datei mit auf das Displayboard kopiert haben. Falls nicht, führen Sie das jetzt durch, entfernen dann die Stromzufuhr am Board und stellen sie für den Neustart wieder her.

  
  
64  # Create a new progress_bar object at (x, y)
65  progress_bar = HorizontalProgressBar((x, y+1), (width, height), direction=HorizontalFillDirection.LEFT_TO_RIGHT,
66                                       bar_color=0x3BD81C, fill_color= 0xd12929, outline_color=None, margin_size=False)
67  main.append(progress_bar)
68  circle1 = Circle(50, 140, 18, fill=0x3BD81C, outline=None)
69  main.append(circle1)
70  circle2 = Circle(190, 140, 18, fill=0xd12929, outline=None)
71  main.append(circle2)
72
73  #roundrect_f= RoundRect(30,120,160,40,20,fill=0x009900, outline=0x00ff00)
74  roundrect_f= RoundRect(31,121,178,38,19,fill=None, outline=0x00ff00)
75  main.append(roundrect_f)
76
77  #current_value = progress_bar.minimum
78  current_value = step/ziel * 100
79
80  # create the label for the time-hour and minute
81  updating_label_time = label.Label(font=terminalio.FONT, text="00:00", scale=3, color=0xffffff, line_spacing=1)
82  # set label position on the display and add label
83  updating_label_time.anchor_point = (0, 0)
84  updating_label_time.anchored_position = (60, 40)
85  main.append(updating_label_time)
86
87  # create the label for the time-second
88  updating_label_sec = label.Label(font=terminalio.FONT, text=":00", scale=2, color=0xffffff, line_spacing=1)
89  # set label position on the display and add label
90  updating_label_sec.anchor_point = (0, 0)
91  updating_label_sec.anchored_position = (155, 50)
92  main.append(updating_label_sec)
93
94  # create the label for the steps
95  updating_label_steps = label.Label(font=terminalio.FONT, text="", scale=2, color=0xffffff, line_spacing=1)
96  # set label position on the display and add label
97  updating_label_steps.anchor_point = (0, 0)
98  updating_label_steps.anchored_position = (65, 127)
99  main.append(updating_label_steps)
100 # create the label for the steps
101 text_group_step = displayio.Group(scale=1, x=90, y=170)
102 text_area1 = label.Label(terminalio.FONT, text="Schritte", color=0xffffff)
103 text_group_step.append(text_area1)
104 main.append(text_group_step)
105
106 # create the label for the meter/cal
107 updating_label_meter = label.Label(font=terminalio.FONT, text="", scale=2, color=0xffff00, line_spacing=1)
108 updating_label_meter.anchor_point = (0, 0)
109 updating_label_meter.anchored_position = (50, 180)
110 main.append(updating_label_meter)
111 updating_label_cal = label.Label(font=terminalio.FONT, text="", scale=2, color=0xffff00, line_spacing=1)
112 updating_label_cal.anchor_point = (0, 0)
113 updating_label_cal.anchored_position = (140, 180)
114 main.append(updating_label_cal)
115
116 current_time = time.localtime()
117 year = current_time.tm_year
118 hour = current_time.tm_hour
119 minute = current_time.tm_min
120 second = current_time.tm_sec
121 month = current_time.tm_mon
122 day = current_time.tm_mday
123 weekday = current_time.tm_wday
124
125 if current_time.tm_year > 2000:
126     with open("/time.txt", "w") as f:
127         f.write(str(step)+"\n")
128         f.write(str(year)+"\n")
129         f.write(str(month)+"\n")
130         f.write(str(day)+"\n")
131         f.write(str(hour)+"\n")
132         f.write(str(minute)+"\n")
133         f.write(str(weekday)+"\n")
134     f.close()
135 if current_time.tm_year == 2000:
136     with open("time.txt", "r") as f:
137         step = int(f.readline())
138         year = int(f.readline())
139         month = int(f.readline())
140         day = int(f.readline())
141         hour = int(f.readline())
142         minute = int(f.readline())
143         weekday = int(f.readline())
144     f.close()
145     second = current_time.tm_sec
146     r = rtc.RTC()
147     r.datetime = time.struct_time((year, month, day, hour, minute, 0, weekday, 1, -1))
148
149 # create the label for the date
150 text_group_day = displayio.Group(scale=2, x=25, y=95)
151 text_area1 = label.Label(terminalio.FONT, text="{:02}.{:02}. ".format(day,month) + wdays[weekday], color=0xffffff)
152 text_group_day.append(text_area1)
153 main.append(text_group_day)
154
155 start = ticks_ms()
156 hell = ticks_ms()
157 dunkel = False
158 x_alt = 0
159 y_alt = 0
160 z_alt = 0
161
  

Jetzt folgt die 'while'-Schleife. In den Zeilen 163 bis 170 werden die 'ACC-Werte' des Sensors gelesen. Zeile 164 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).
In den Zeilen 171 bis 181 wird das Display hell bzw. dunkel geschaltet. Immer wenn eine Bewegung erfogt, schaltet das Display ein und nach ca. 5 Sekunden im Ruhezustand wieder ab.
Ab Zeile 182 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.

  
  
162 while True:
163     #read QMI8658
164     xyz=sensor.Read_XYZ()
165     # Display wird rel. zur x-Achse bewegt
166     wert_x = (10)*xyz[0]
167     # Display wird rel. zur y-Achse bewegt
168     wert_y = (10)*xyz[1]
169     # Display wird rel. zur z-Achse bewegt
170     wert_z = (10)*xyz[2]
171     # Display hell/dunkel
172     if abs(wert_x - x_alt) >= 1 or abs(wert_y - y_alt) >= 1:
173         display.brightness = 1
174         hell = ticks_ms()
175         dunkel = False
176     if (ticks_ms() - hell)/1000 > 5 and dunkel == False:
177         for i in range(50,1,-1):
178             display.brightness = i/50
179             time.sleep(0.05)
180         display.brightness = 0.01
181         dunkel = True
182     # Zeit holen
183     current_time = time.localtime()
184     hour = current_time.tm_hour
185     minute = current_time.tm_min
186     second = current_time.tm_sec
187     if hour == 0 and minute == 0 and second == 1:
188         month = current_time.tm_mon
189         day = current_time.tm_mday
190         weekday = current_time.tm_wday
191     if second == 59:
192         with open("/time.txt", "w") as f:
193             f.write(str(step)+"\n")
194             f.write(str(year)+"\n")
195             f.write(str(month)+"\n")
196             f.write(str(day)+"\n")
197             f.write(str(hour)+"\n")
198             f.write(str(minute)+"\n")
199             f.write(str(weekday)+"\n")
200         f.close()
201         for current_value in range(progress_bar.minimum, step/ziel * 100 + 1, 1):
202             progress_bar.value = current_value
203             time.sleep(0.01)
204     #
205     # Zeitstring zur Anzeige aufbereiten
206     #
207     updating_label_time.text = "{:02}:{:02}".format(hour,minute)
208     updating_label_sec.text = ":{:02}".format(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.

  
  
209     #
210     # Arm noch oben bewegen
211     #
212     if (abs(wert_x) < 7 and abs(wert_y) > 3 or abs(wert_x) < 7 and abs(wert_z) > 3) and stepstop == 0:
213         start = ticks_ms()
214         step += 1
215         stepstop = 1
216         updating_label_steps.text = str(step-1)+"/"+ str(ziel)
217         current_value = step/ziel * 100
218         progress_bar.value = current_value
219         meter = int((step-1)*0.762)
220         cal = int(int((step-0.38)*0.38)*4.18)/1000
221         updating_label_meter.text = str(meter) + "       " + "\n  m"
222         updating_label_cal.text = str(cal) + "       " + "\n kJ"
223     #
224     # Arm wieder nach unten
225     #
226     if (abs(wert_x) > 7 and abs(wert_y) < 3 or abs(wert_x) > 7 and abs(wert_z) < 3) and stepstop == 1:
227         if (ticks_ms() - start)> 1000:
228             stepstop = 0
229     updating_label_steps.text = str(step)+"/"+ str(ziel)
230     progress_bar.value = current_value
231     #
232     # Schritte zuruecksetzen
233     #
234     if step == ziel:
235         step = 0
236         progress_bar.value = progress_bar.minimum
237     time.sleep(0.2)
238
239     x_alt = wert_x
240     y_alt = wert_y
241
242     gc.collect()
243     #print(gc.mem_free())
  




Viel Spass und Erfolg beim Ausprobieren.