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)
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:
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.
Viel Spass und Erfolg beim Ausprobieren.