Analog-Digital Uhr

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

Anleitung für die Firmware CircuitPython 9.0.x



Hardware

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


Nachdem wir den mathematischen Teil hinter uns haben, realisieren wir in dieser Folge eine erste (einfache) Uhrfunktion auf dem runden Display. Dabei nutzen wir aus der zweiten Folge die Darstellung von Kreisen für die Hintergrundgestaltung und drei sich im Kreis bewegende Punkte für die Darstellung der Sekunden, Minuten und Stunden. In der Mitte werden die Stunden und Minuten digital angezeigt. Vieleicht haben Sie schon einmal eine s.g. 'Magneto-Uhr' für 150 bis 200 Euro gesehen. Wir werden darauf achten, daß unser Beispiel dem nicht zu ähnlich wird, da wir keine Post von deren Anwälten bekommen wollen.

Los gehts

Kopieren Sie den Code aus dem unteren Kasten ins Thonny und schauen sich die hinzugekommenen Teile an. Zunächst wird in Zeile 15 das Modul zum Zeichnen von Linien importiert. (Bei Ihnen ist evtl. ganz oben eine Leerzeile, die Sie löschen). Damit stellen wir kurze Striche zur Veranschaulichung der Minuten und Stunden dar. Dies geschieht in den Zeilen 43 bis 58. Dazwischen ordnen wir gelbe Ringe an. Wenn Sie das Programm starten, können Sie den Displayaufbau gut beobachten. Die Ausgabe des freien Speichers im Terminal zeigt, dass gar nicht mehr viel übrig ist. In Zeile 76 steht jetzt 'time.sleep(1)'. Damit wird vorläufig der Sekundentakt erzeugt, während der kleine weisse Punkt im äußeren Ring läuft.

  
  

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 time
6   import gc
7   import board
8   import busio
9   from busio import I2C
10  import displayio
11  import terminalio
12  import gc9a01
13  from adafruit_display_text import label
14  from adafruit_display_shapes.circle import Circle
15  from adafruit_display_shapes.line import Line
16  import random
17  import math
18
19  # Release any resources currently in use for the displays
20  displayio.release_displays()
21
22  # Make the displayio SPI bus and the GC9A01 display
23  spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN)
24  display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST)
25  display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL)
26
27  # Make the display context
28  group1 = displayio.Group()
29  display.root_group = group1
30
31  width = 240
32  height = 240
33  xpos = 120
34  ypos = 120
35  radius = 120
36  i = 0
37
38  # Make some circles and lines:
39  circle = Circle(xpos, ypos, 120, fill=0xae2323, outline=0x000000)
40  group1.append(circle)
41  circle = Circle(xpos, ypos, 115, fill=0x000000, outline=0x000000)
42  group1.append(circle)
43  # Sekundenstriche
44  for i in range(60):
45      line = Line(xpos+int(105*math.cos(i*math.pi/30)),ypos-int(105*math.sin(i*math.pi/30)),
46                  xpos+int(110*math.cos(i*math.pi/30)), ypos-int(110*math.sin(i*math.pi/30)),0xffffff)
47      group1.append(line)
48      display.root_group = group1
49  circle = Circle(xpos, ypos, 100, fill=0x726b6b, outline=0x726b6b)
50  group1.append(circle)
51  circle = Circle(xpos, ypos, 95, fill=0x282323, outline=0x000000)
52  group1.append(circle)
53  # Stundenstriche
54  for i in range(12):
55      line = Line(xpos+int(95*math.cos(i*math.pi/6)),ypos-int(95*math.sin(i*math.pi/6)),
56                  xpos+int(90*math.cos(i*math.pi/6)), ypos-int(90*math.sin(i*math.pi/6)),0xffffff)
57      group1.append(line)
58      display.root_group = group1
59  circle = Circle(xpos, ypos, 80, fill=0x726b6b, outline=0x726b6b)
60  group1.append(circle)
61  circle = Circle(xpos, ypos, 75, fill=0x282323, outline=0x000000)
62  group1.append(circle)
63  circle = Circle(xpos, ypos, 60, fill=0xae2323, outline=0x726b6b)
64  group1.append(circle)
65
66  circle_sec = Circle(xpos, ypos, 5, fill=0xffffff, outline=0xffffff)
67  group1.append(circle_sec)
68
69  while True:
70    for i in range (60):
71        xpos_neu = int(width/2 + 108*math.cos((i -15)*math.pi/30))
72        delta_x = xpos_neu - xpos
73        xpos = xpos_neu
74        ypos_neu = int(height/2 + 108*math.sin((i -15)*math.pi/30))
75        delta_y = ypos_neu - ypos
76        ypos = ypos_neu
77        circle_sec.x = circle_sec.x + delta_x
78        circle_sec.y = circle_sec.y + delta_y
79        time.sleep(1)
80        gc.collect()
81        print(gc.mem_free())
82
  

Im nächsten Schritt brauchen wir eine Uhrzeit. Dazu nutzen wir, solange die Uhr am PC angeschlossen ist time.localtime() und definieren hour, minute, second als Variable für die Stunden, Minuten und Sekunden. Diese Ergänzungen finden Sie im unteren Kasten ab Zeile 69. In den Zeilen 74 bis 77 werden noch die Minuten- und Stundenpunkte festgelegt. Die 'while'-Schleife beginnt jetzt in Zeile 80. Kopieren Sie den Code und dann sehen wir uns die Veränderungen in der Schleife an.

  
  

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 time
6   import gc
7   import board
8   import busio
9   from busio import I2C
10  import displayio
11  import terminalio
12  import gc9a01
13  from adafruit_display_text import label
14  from adafruit_display_shapes.circle import Circle
15  from adafruit_display_shapes.line import Line
16  import random
17  import math
18
19  # Release any resources currently in use for the displays
20  displayio.release_displays()
21
22  # Make the displayio SPI bus and the GC9A01 display
23  spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN)
24  display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST)
25  display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL)
26
27  # Make the display context
28  group1 = displayio.Group()
29  display.root_group = group1
30
31  width = 240
32  height = 240
33  xpos = 120
34  ypos = 120
35  radius = 120
36  i = 0
37
38  # Make some circles and lines:
39  circle = Circle(xpos, ypos, 120, fill=0xae2323, outline=0x000000)
40  group1.append(circle)
41  circle = Circle(xpos, ypos, 115, fill=0x000000, outline=0x000000)
42  group1.append(circle)
43  # Sekundenstriche
44  for i in range(60):
45      line = Line(xpos+int(105*math.cos(i*math.pi/30)),ypos-int(105*math.sin(i*math.pi/30)),
46                  xpos+int(110*math.cos(i*math.pi/30)), ypos-int(110*math.sin(i*math.pi/30)),0xffffff)
47      group1.append(line)
48      display.root_group = group1
49  circle = Circle(xpos, ypos, 100, fill=0x726b6b, outline=0x726b6b)
50  group1.append(circle)
51  circle = Circle(xpos, ypos, 95, fill=0x282323, outline=0x000000)
52  group1.append(circle)
53  # Stundenstriche
54  for i in range(12):
55      line = Line(xpos+int(95*math.cos(i*math.pi/6)),ypos-int(95*math.sin(i*math.pi/6)),
56                  xpos+int(90*math.cos(i*math.pi/6)), ypos-int(90*math.sin(i*math.pi/6)),0xffffff)
57      group1.append(line)
58      display.root_group = group1
59  circle = Circle(xpos, ypos, 80, fill=0x726b6b, outline=0x726b6b)
60  group1.append(circle)
61  circle = Circle(xpos, ypos, 75, fill=0x282323, outline=0x000000)
62  group1.append(circle)
63  circle = Circle(xpos, ypos, 60, fill=0xae2323, outline=0x726b6b)
64  group1.append(circle)
65
66  circle_sec = Circle(xpos, ypos, 4, fill=0xffffff, outline=0xffffff)
67  group1.append(circle_sec)
68
69  xpos_min = 120
70  ypos_min = 33
71  xpos_hour = 120
72  ypos_hour = 52
73  # Minutenpunkt in gruen
74  circle_min = Circle(xpos_min,ypos_min, 5, fill=0x00cc00, outline=0x000000)
75  group1.append(circle_min)
76  # Stundenpunkt in rot
77  circle_hour = Circle(xpos_hour, ypos_hour, 5, fill=0xff0000, outline=0xff0000)
78  group1.append(circle_hour)
79
80  while True:
81      current_time = time.localtime()
82      hour = current_time[3]
83      minute = current_time[4]
84      second = current_time[5]
85      # minute setzen
86      xpos_min_neu = int(width/2 + 88*math.cos((minute-15)*math.pi/30))
87      delta_min_x = xpos_min_neu - xpos_min
88      xpos_min = xpos_min_neu
89      ypos_min_neu = int(height/2 + 88*math.sin((minute-15)*math.pi/30))
90      delta_min_y = ypos_min_neu - ypos_min
91      ypos_min = ypos_min_neu
92      circle_min.x = circle_min.x + delta_min_x
93      circle_min.y = circle_min.y + delta_min_y
94      gc.collect()
95      # hour
96      xpos_hour_neu = int(width/2 + int(68*math.cos((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
97      delta_hour_x = xpos_hour_neu - xpos_hour
98      xpos_hour = xpos_hour_neu
99      ypos_hour_neu = int(height/2 + int(68*math.sin((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
100     delta_hour_y = ypos_hour_neu - ypos_hour
101     ypos_hour = ypos_hour_neu
102     circle_hour.x = circle_hour.x + delta_hour_x
103     circle_hour.y = circle_hour.y + delta_hour_y
104     for i in range (60):
105         second *= 1
106         xpos_neu = int(width/2 + 108*math.cos((i -15)*math.pi/30))
107         delta_x = xpos_neu - xpos
108         xpos = xpos_neu
109         ypos_neu = int(height/2 + 108*math.sin((i -15)*math.pi/30))
110         delta_y = ypos_neu - ypos
111         ypos = ypos_neu
112         circle_sec.x = circle_sec.x + delta_x
113         circle_sec.y = circle_sec.y + delta_y
114         time.sleep(1)
115         gc.collect()
116         print(gc.mem_free())
  

Mathematisch kommt es jetzt auf die exakte Berechnung der Positionen der Minuten- und Stundenpunkte an.

Minuten: Da eine Stunde 60 Minuten hat und der Zeiger dabei 360 Grad zurücklegt, entspricht 1 Minute 360 / 60 = 6 Grad (also pi / 30). Für die Berechnung ergibt sich daraus:
xpos_min_neu = int(width/2 + 88*math.cos((minute-15)*math.pi/30))
Die 88 steht für den Radius und die -15 wegen der Displaydrehung um 90 Grad (also 15 Minuten). Analog gilt das für die y-Position.
ypos_min_neu = int(height/2 + 88*math.sin((minute-15)*math.pi/30))
Aus diesen neuen Positionen und den alten Positionen wird die Differenz 'delta' bestimmt und in den Zeilen 90, 91 an den Kreis übergeben. So wird der Minutenpunkt in den Zeilen 86 bis 94 aktualisiert.

Stunden: Für den Stundenpunkt ist das Ganze noch etwas aufwendiger, da sich der Stundenzeiger während einer Stunde kontinuierlich mitbewegt. Grundsätzlich gilt: 12 Stunden sind 360 Grad. Also 360 / 12 = 30 Grad (also pi / 6). Für die Berechnung ergibt sich daraus:
xpos_hour_neu = int(width/2 + int(68*math.cos((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
und
ypos_hour_neu = int(height/2 + int(68*math.sin((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
Die 68 steht für den Radius und die -pi/2 für die Displaydrehung. Der übrige Teil des Ausdrucks sorgt dafür, dass sich der Stundenzeiger in Abhängikeit von den Minuten mitbewegt. So wird der Stundenpunkt von Zeile 96 bis 103 aktualisiert.

In den Zeilen 104 bis 115 wird der Sekundenpunkt wie gehabt gesetzt.

Mit ein paar Ergänzungen fügen wir dem Programm noch eine digitale Anzeige, wie auf dem Bild ganz oben, hinzu. Dazu wird ein Rechteck mit abgerundeten Ecken 'roundrect' und ein 'label', welches die Zeit als String ausgibt, angelegt. Jetzt neu in den Zeilen 81 bis 91. Auch in der 'while'-Schleife sind die Zeilen 98 und 99 jetzt neu. Die nachfolgenden Zeilen verschieben sich dadurch nach hinten. Zur besseren Übersicht gebe ich den geänderten Code noch einmal komplett zum kopieren an.

  
  

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 time
6   import gc
7   import board
8   import busio
9   from busio import I2C
10  import displayio
11  import terminalio
12  import gc9a01
13  from adafruit_display_text import label
14  from adafruit_display_shapes.circle import Circle
15  from adafruit_display_shapes.line import Line
16  from adafruit_display_shapes.roundrect import RoundRect
17  import random
18  import math
19
20  # Release any resources currently in use for the displays
21  displayio.release_displays()
22
23  # Make the displayio SPI bus and the GC9A01 display
24  spi = busio.SPI(clock=board.LCD_CLK, MOSI=board.LCD_DIN)
25  display_bus = displayio.FourWire(spi, command=board.LCD_DC, chip_select=board.LCD_CS, reset=board.LCD_RST)
26  display = gc9a01.GC9A01(display_bus, width=240, height=240, rotation=90, backlight_pin=board.LCD_BL)
27
28  # Make the display context
29  group1 = displayio.Group()
30  display.root_group = group1
31
32  width = 240
33  height = 240
34  xpos = 120
35  ypos = 120
36  radius = 120
37  i = 0
38
39  # Make some circles and lines:
40  circle = Circle(xpos, ypos, 120, fill=0xae2323, outline=0x000000)
41  group1.append(circle)
42  circle = Circle(xpos, ypos, 115, fill=0x000000, outline=0x000000)
43  group1.append(circle)
44  # Sekundenstriche
45  for i in range(60):
46      line = Line(xpos+int(105*math.cos(i*math.pi/30)),ypos-int(105*math.sin(i*math.pi/30)),
47                  xpos+int(110*math.cos(i*math.pi/30)), ypos-int(110*math.sin(i*math.pi/30)),0xffffff)
48      group1.append(line)
49      display.root_group = group1
50  circle = Circle(xpos, ypos, 100, fill=0x726b6b, outline=0x726b6b)
51  group1.append(circle)
52  circle = Circle(xpos, ypos, 95, fill=0x282323, outline=0x000000)
53  group1.append(circle)
54  # Stundenstriche
55  for i in range(12):
56      line = Line(xpos+int(95*math.cos(i*math.pi/6)),ypos-int(95*math.sin(i*math.pi/6)),
57                  xpos+int(90*math.cos(i*math.pi/6)), ypos-int(90*math.sin(i*math.pi/6)),0xffffff)
58      group1.append(line)
59      display.root_group = group1
60  circle = Circle(xpos, ypos, 80, fill=0x726b6b, outline=0x726b6b)
61  group1.append(circle)
62  circle = Circle(xpos, ypos, 75, fill=0x282323, outline=0x000000)
63  group1.append(circle)
64  circle = Circle(xpos, ypos, 60, fill=0xae2323, outline=0x726b6b)
65  group1.append(circle)
66
67  circle_sec = Circle(xpos, ypos, 4, fill=0xffffff, outline=0xffffff)
68  group1.append(circle_sec)
69
70  xpos_min = 120
71  ypos_min = 33
72  xpos_hour = 120
73  ypos_hour = 52
74  # Minutenpunkt in gruen
75  circle_min = Circle(xpos_min,ypos_min, 5, fill=0x00cc00, outline=0x000000)
76  group1.append(circle_min)
77  # Stundenpunkt in rot
78  circle_hour = Circle(xpos_hour, ypos_hour, 5, fill=0xff0000, outline=0xff0000)
79  group1.append(circle_hour)
80
81  # Rechteck mit abgerundeten Ecken
82  roundrect1 = RoundRect(65, 100, 110, 45, 10, fill=0x282323, outline=0xae2323, stroke=3)
83  group1.append(roundrect1)
84
85  ## create the time-label
86  updating_label1 = label.Label(font=terminalio.FONT, text="", scale=2, color=0xffffaa,line_spacing=1)
87  # set label position on the display and add label
88  updating_label1.anchor_point = (0, 0)
89  updating_label1.anchored_position = (92, 110)
90  group1.append(updating_label1)
91  display.root_group = group1
92
93  while True:
94      current_time = time.localtime()
95      hour = current_time[3]
96      minute = current_time[4]
97      second = current_time[5]
98      zeit = "{:02}:{:02}".format(hour,minute)
99      updating_label1.text = zeit
100     # minute setzen
101     xpos_min_neu = int(width/2 + 88*math.cos((minute-15)*math.pi/30))
102     delta_min_x = xpos_min_neu - xpos_min
103     xpos_min = xpos_min_neu
104     ypos_min_neu = int(height/2 + 88*math.sin((minute-15)*math.pi/30))
105     delta_min_y = ypos_min_neu - ypos_min
106     ypos_min = ypos_min_neu
107     circle_min.x = circle_min.x + delta_min_x
108     circle_min.y = circle_min.y + delta_min_y
109     gc.collect()
110     # hour
111     xpos_hour_neu = int(width/2 + int(68*math.cos((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
112     delta_hour_x = xpos_hour_neu - xpos_hour
113     xpos_hour = xpos_hour_neu
114     ypos_hour_neu = int(height/2 + int(68*math.sin((hour)*math.pi/6 + minute/2*math.pi/180 - math.pi/2)))
115     delta_hour_y = ypos_hour_neu - ypos_hour
116     ypos_hour = ypos_hour_neu
117     circle_hour.x = circle_hour.x + delta_hour_x
118     circle_hour.y = circle_hour.y + delta_hour_y
119     for i in range (60):
120         second *= 1
121         xpos_neu = int(width/2 + 108*math.cos((i -15)*math.pi/30))
122         delta_x = xpos_neu - xpos
123         xpos = xpos_neu
124         ypos_neu = int(height/2 + 108*math.sin((i -15)*math.pi/30))
125         delta_y = ypos_neu - ypos
126         ypos = ypos_neu
127         circle_sec.x = circle_sec.x + delta_x
128         circle_sec.y = circle_sec.y + delta_y
129         time.sleep(1)
130         gc.collect()
131         #print(gc.mem_free())
  




Viel Spass und Erfolg beim Ausprobieren.