pico-watch
auf rundem 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel
- Waveshare 22668
Anleitung für die Firmware CircuitPython 8.2.4
erstellt: 13.4.2023, letztes update: 06.08.2025
Hardware
- Rundes 1,28-Zoll-IPS-LCD-Display (WS 22668)
- USB-A zu USB-C Kabel
pico-watch
auf rundem 1.28-Zoll-IPS-LCD-Display 240 x 240 Pixel- Waveshare 22668
Anleitung für die Firmware CircuitPython 8.2.4
erstellt: 13.4.2023, letztes update: 06.08.2025

Mit dieser Anleitung schlagen wir ein neues Kapitel bei der Programmierung des runden Pico-LCD-Displays für alle Hobbyprogrammierer auf. Nachdem bisher überwiegend
vielsagende Bilder von Anwendungen auf dem runden Pico-LCD-Display im Netz zu sehen waren, die von zugegebenermaßen Experten in C programmiert wurden, wird jetzt
ein Zugang für alle Interessierten in CircuitPython geschaffen. Eine oft gewünschte Anwendung ist dabei z.B. ein Display mit einer Analoguhr. In der folgenden
Anleitung geht es genau darum. Dabei stehen nicht das schönste Zifferblatt oder attraktive Zeiger, sondern die Funktionen im Mittelpunkt.
Diese werden genau erklärt, so dass jeder seine eigene Uhr designen kann.
Los gehts
Als erstes wird die Firmeware 'CircuitPython' für den Pico benötigt. Da die bei der Weiterentwicklung stets etwas mehr Speicherplatz
beansprucht, kommt es bei dem sehr begrenzten Angebot des RP2040 beim Start des Programms zum Memory-Error und damit Programmabruch. Deshalb
verwenden Sie die zum Zeitpunkt der Erstellung aktuelle Firmware CircuitPython 8.2.4 und die dazu gehörenden
Bibliotheken. Die Firmeware 8.2.4 können Sie sich
hier herunterladen,
entpacken und auf dem Board installieren.
Weiterhin brauchen Sie im 'lib-Ordner' die Bibliotheken adafruit_imageload, adafruit_display_shapes, adafruit_display_text sowie den
Displaytreiber gc9a01.py. Die können Sie bei mir als
zip-Datei
herunterladen, entpacken und in den 'lib_Ordner' kopieren. In der zip_Datei ist auch ein Ordner 'images'. Darin sind die Bitmaps mit
dem Zifferblatt und den Zeigern. Ziehen Sie diesen Ordner ins Stammverzeichnis von 'CIRCUITPY'. Ebenso die Datei 'boot.py', welche beim
Speichern der Uhrzeit auf dem Picodisplay eine Rolle spielt, auf die ich später noch eingehe.
Jetzt gehts richtig los.
Im unteren Kasten sehen Sie, wie das Display initialisiert wird. Kopieren Sie die Zeilen 1 bis 58 ins Thonny. Sie können auch schon mal 'start'
drücken. Es erscheint das Zifferblatt. Speichern Sie die Datei als 'code.py'. Die Rückrage zum Überschreiben
der vorhanden 'code.py' beantworten Sie mit 'ja'.
1 # SPDX-FileCopyrightText : 2023 Detlef Gebhardt, written for CircuitPython 8.2.4 2 # SPDX-FileCopyrightText : Copyright (c) 2023 Detlef Gebhardt 3 # SPDX-Filename : pico-watch 4 # SPDX-License-Identifier: update 06.08.2025 5 import time 6 import gc 7 import board 8 import busio 9 import displayio 10 import digitalio 11 import terminalio 12 import bitmaptools 13 import math 14 import adafruit_imageload 15 from adafruit_display_text import label 16 from adafruit_display_shapes.circle import Circle 17 import gc9a01 18 19 # Bitmap-Dateien fuer Hintergrund und Zeiger 20 zifferblatt = "/images/zifferblatt.bmp" 21 second_zeiger = "/images/second.bmp" 22 minute_zeiger = "/images/minute.bmp" 23 hour_zeiger = "/images/hour.bmp" 24 # 25 # Display initialisieren 26 # 27 # Ressourcen freigeben fuer das Display 28 displayio.release_displays() 29 # displayio SPI-Bus und GC9A01 Display 30 spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN) 31 display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST) 32 display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL) 33 main = displayio.Group() 34 display.show(main) 35 36 # Ziffernblatt als Hintergrund 37 bg_bitmap,bg_pal = adafruit_imageload.load(zifferblatt) 38 bg_tile_grid = displayio.TileGrid(bg_bitmap, pixel_shader=bg_pal) 39 main.append(bg_tile_grid) 40 41 # Stundenzeiger 30x140 42 bitmap_pointer_hour, palette_pointer = adafruit_imageload.load(hour_zeiger, bitmap=displayio.Bitmap,palette=displayio.Palette) 43 palette_pointer.make_transparent(0) 44 # Blank bitmap vom Stundenzeiger 45 bitmap_pointer_blank_hour = displayio.Bitmap(bitmap_pointer_hour.width, bitmap_pointer_hour.height, 1) 46 47 # Minutenzeiger 30x140 48 bitmap_pointer_min, palette_pointer = adafruit_imageload.load(minute_zeiger, bitmap=displayio.Bitmap,palette=displayio.Palette) 49 palette_pointer.make_transparent(0) 50 # Blank bitmap vom Minutenzeiger 51 bitmap_pointer_blank_min = displayio.Bitmap(bitmap_pointer_min.width, bitmap_pointer_min.height, 1) 52 53 # Sekundenzeiger 30x140 pointer 54 bitmap_pointer_sec, palette_pointer = adafruit_imageload.load(second_zeiger, bitmap=displayio.Bitmap,palette=displayio.Palette) 55 palette_pointer.make_transparent(0) 56 # Blank bitmap vom Sekundenzeiger 57 bitmap_pointer_blank_sec = displayio.Bitmap(bitmap_pointer_sec.width, bitmap_pointer_sec.height, 1) 58
Im nächsten Schritt werden die transparenten Overlays für die zu drehenden Zeiger angelegt. Schließlich werden zwei kleine Kreise als zentraler Mittelpunkt definiert. Fügen Sie auch diese Zeilen (59 bis 74) im Thonny ein.
59 # Transparentes Overlay fuer 'rotozoom' 60 # Zeiger fuer die Drehung 61 bitmap_scribble_hour = displayio.Bitmap(display.width, display.height, len(palette_pointer)) 62 tile_grid = displayio.TileGrid(bitmap_scribble_hour, pixel_shader=palette_pointer) 63 main.append(tile_grid) 64 bitmap_scribble_min = displayio.Bitmap(display.width, display.height, len(palette_pointer)) 65 tile_grid = displayio.TileGrid(bitmap_scribble_min, pixel_shader=palette_pointer) 66 main.append(tile_grid) 67 bitmap_scribble_sec = displayio.Bitmap(display.width, display.height, len(palette_pointer)) 68 tile_grid = displayio.TileGrid(bitmap_scribble_sec, pixel_shader=palette_pointer) 69 main.append(tile_grid) 70 circle1 = Circle(120, 120, 10, fill=0xff0000, outline=None) 71 main.append(circle1) 72 circle2 = Circle(120, 120, 5, fill=0x3D55CD, outline=0x0) 73 main.append(circle2) 74
Es folgt ein Teil, der für die korrekte Uhrzeit wichtig ist. Wenn das Display am Rechner angeschlossen ist,
holt es sich die Zeit von 'time.localtime()' und speichert sie in den Zeilen 80 bis 84 in die Datei 'time.txt'. Ist das Display
hingegen nicht am Rechner, dann beginnt 'localtime()' am 1.1.2020 um 00:00 Uhr die Sekunden zu zählen. Für diesen Fall
lesen wir die letzte bekannte Stunde und Minute aus 'time.txt'. Zieht man z.B. das Display vom Rechner ab und schließt es
sofort an eine andere Stromversorgung (z.B. Powerbank) an, "verliert" man zwar einige Sekunden, denn der MC zählt die Sekunde wieder
bei Null beginnend. Ist ein Akku angeschlossen, läuft das Display ohne Unterbrechung weiter. Die manuelle Einstellung der jeweiligen
Uhrzeit ohne angeschlossenen PC wird in einem späteren Beitrag gezeigt.
Hier kommt jetzt die Rolle der Datei 'boot.py' zum Tragen. Das Picodisplay sucht beim Bootvorgang nach dieser Datei.
Ist dort der Befehl storage.remount("/", False) vorhanden, dann wird der interne Speicher zum Beschreiben aktiviert.
Ansonsten ist das Pico-Laufwerk schreibgeschützt. So wird im Normalfall verhindert, dass ein anderer Rechner auf den Pico schreibt.
Jede Änderung in der Datei 'boot.py', wird erst nach einem erneuten Bootvorgang wirksam (also z.B. Strom unterbrechen und wieder
anschließen). Wenn Sie das nach dem Übertragen der Bibliotheksdateien (s.o.) nocht nicht getan haben, sollten
Sie das jetzt tun, damit später nicht der Fehler: "Laufwerk schreibgeschützt" kommt.
75 current_time = time.localtime() 76 hour = current_time.tm_hour 77 minute = current_time.tm_min 78 second = current_time.tm_sec 79 80 if current_time.tm_year > 2020: 81 with open("/time.txt", "w") as f: 82 f.write(str(hour)+"\n") 83 f.write(str(minute)+"\n") 84 f.close() 85 if current_time.tm_year == 2020: 86 with open("time.txt", "r") as f: 87 hour = int(f.readline()) 88 minute = int(f.readline()) 89 f.close() 90 second = current_time.tm_sec 91
In der 'while'-Schleife ab Zeile 92 wird jetzt bei jedem Durchlauf die aktuelle Sekunde bestimmt. Die Stunde und die Minute wird aus dem Bereich vor der Schleife bzw. den Zeilen 103 u. 104 verwendet. In den Zeilen 95 bis 100 werden die Winkel der Zeiger in Abhängigkeit von den Sekunden, Minuten und Stunden (im Bogenmaß) berechnet. Dazu finden Sie Erklärungen in der Anleitung 6 (hier). Die Zeilen 96, 98 und 100 stellen die Bitmaps der Zeiger in entsprechend gedrehter Position dar. Ab Zeile 101 werden immer bei Sekunde 0 die Werte für 'Minute' und 'Stunde' in der Datei 'time.txt' aktualisiert (Zeilen 106 bis 109). Dabei hat die Zeit des Speichervorgangs keinen Einfluss auf die Ganggenauigkeit, denn die Sekunden werden ja vom MC unabhängig weitergezählt.
92 while True: 93 current_time = time.localtime() 94 second = current_time.tm_sec 95 alpha_rad_sec = math.pi/30 * second 96 bitmaptools.rotozoom( bitmap_scribble_sec, bitmap_pointer_sec, angle = alpha_rad_sec, px=15,py=107) 97 alpha_rad_hour = math.pi/6 * hour + math.pi/180 * minute/2 98 bitmaptools.rotozoom( bitmap_scribble_hour, bitmap_pointer_hour, angle = alpha_rad_hour, px=15,py=107) 99 alpha_rad_min = math.pi/30 * minute 100 bitmaptools.rotozoom( bitmap_scribble_min, bitmap_pointer_min, angle = alpha_rad_min, px=15,py=105) 101 if second == 0: 102 current_time = time.localtime() 103 hour = current_time.tm_hour 104 minute = current_time.tm_min 105 second = current_time.tm_sec 106 with open("/time.txt", "w") as f: 107 f.write(str(hour)+"\n") 108 f.write(str(minute)+"\n") 109 f.close() 110 display.refresh() 111 112 gc.collect() 113 #print(gc.mem_free())
Ihnen ist sicher der Befehl gc.collect() in Zeile 112 aufgefallen. Bei jedem Schleifendurchlauf
sorgt er dafür, nicht mehr benötigten RAM freizugeben. Wenn Sie diesen Befehl auskommentieren und den angezeigten
freien Speicher beobachten, werden Sie sehen, dass es schnell knapp werden kann. Deshalb achten Sie auch darauf, dass Sie bei
eigenen Hintergründen und Zeigern eher etwas knauserig mit der Farbtiefe der Bitmaps sind. Lieber nur 16 Farben statt 256.
Viel Spass und Erfolg beim Ausprobieren.
Viel Spass und Erfolg beim Ausprobieren.