PicoBoy - Digitaluhr mit Weckfunktion

technische Daten wie beim RPi Pico
MC RP2040 mit OLED-Display 128x64 Pixel

Anleitung für die Firmware CircuitPython



Hardware

- PicoBoy
- USB-A zu USB-C Kabel

Nachdem im vorigen Teil die Benutzung des Joysticks und die Ansteuerung der LED's erklärt wurde, soll hier zunächst die Ansteuerung des Lautsprechers gezeigt werden.

Los gehts

Angesteuert wird der kleine Lautsprecher auf der PicoBoy Platine mit dem GPIO 15. Über die GPIO Ports können keine analogen Signale ausgegeben werden, sondern nur digitale Zustände 0 oder 1. Man kann aber mit Hilfe der s.g. Puls-Weiten-Modulation (PWM) z.B. gedimmte LEDs und variable Motordrehzahlen realisieren. Oder wie in unserem Fall den Lautsprecher zur Abgabe von Tönen bringen. Per Software erzeugt man ein pulsweitenmoduliertes Signal mithilfe einer Schleife, in der der Ausgang den Zustand ständig wechselt und zwischendurch etwas pausiert. Das Beispiel im unteren Kasten zeigt, wie der Lautsprecher (Leisesprecher) und die rote LED angesprochen werden. Dabei ertönt der Lautsprecher zehn mal und die LED wird mit ansteigender Frequenz heller, bis sie ausgeschaltet wird. Wenn die Variable den Wert 10 erreicht hat, wird die while-Schleife mit 'break' abgebrochen. Zuvor wird aber der Lautsprecher mit 'deinit' abgeschaltet, damit der GPIO strommäßig nicht überlastet wird, worauf der Hersteller hinweist.

  
  

1  import time
2  import board
3  import busio
4  import displayio
5  import fourwire
6  import digitalio
7  import adafruit_displayio_sh1106
8  import pwmio
9
10 # GP15: Boardeigener Speaker, duty_cycle: zwischen 0 und 65535, frequency zwischen 1 Hz und 48 MHz
11 speaker = pwmio.PWMOut(board.GP15, frequency=1000, variable_frequency=True)
12 led_red = pwmio.PWMOut(board.GP5)
13
14 i = 0
15
16 while True:
17     for cycle in range(0, 65535, +2):  # Cycles through the full PWM range from 0 to 65535
18         led_red.duty_cycle = cycle
19         speaker.duty_cycle = 32768
20     time.sleep(0.2)
21     speaker.duty_cycle = 0
22     time.sleep(0.2)
23     i += 1
24     if i == 10:
25         led_red.duty_cycle = 0
26         speaker.deinit()
27         break
  

Damit sind nun alle 'Zutaten' beisammen, um das Programm in der versprochenen Art zu erweitern. Es soll eine Uhrfunktion mit:

     digitale Anzeige der Uhrzeit
     Anzeige des Datums
     Anzeige des Wochentages

und einer

     Weckfunktion,

bei welcher der Lautsprecher einen Ton gibt und die rote LED blinkt.

Hier kommt der erste Teil des Programms, welcher überwiegend in den vorangegangenen Beispielen vorkam. Ergänzt wurde er um DisplayGroups (Zeilen 41 bis 49)und die entsprechenden Label für das 'Stellmenü' und die 'Untermenüs' (Zeilen 103 bis 121). Die Definition des Kreises in Zeile 108 und 109 stellt im 'Stellmenü' den 'Optionbutton' dar.

  
  

1   import time
2   import board
3   import busio
4   import rtc
5   import displayio
6   import fourwire
7   import digitalio
8   import terminalio
9   from adafruit_display_text import label
10  from adafruit_display_shapes.rect import Rect
11  from adafruit_display_shapes.circle import Circle
12  import adafruit_displayio_sh1106
13  import pwmio
14
15  # Compatibility with both CircuitPython 8.x.x and 9.x.x.
16  # Remove after 8.x.x is no longer a supported release.
17  try:
18      from fourwire import FourWire
19  except ImportError:
20      from displayio import FourWire
21
22  #DC = board.GP8
23  #Reset = board.GP9
24  #CS = board.GP10
25  #SCK = board.GP18
26  #MOSI = board.GP19
27
28  # built-in a display
29  displayio.release_displays()
30  # Make the displayio SPI bus and the sh1106 display
31  spi = busio.SPI(board.GP18, board.GP19)
32  #
33  # Circuit 8.x.x
34  #display_bus = displayio.FourWire(spi, command=board.GP8, chip_select=board.GP10, reset=board.GP9, baudrate=1000000)
35  #display =adafruit_displayio_sh1106.SH1106(display_bus, width=132, height=64, rotation=0, brightness = 1)
36  #
37  # Circuit 9.x.x
38  display_bus = FourWire(spi, command=board.GP8, chip_select=board.GP10, reset=board.GP9, baudrate=1000000)
39  display =adafruit_displayio_sh1106.SH1106(display_bus, width=132, height=64, rotation=0, brightness = 1)
40
41  # Make the display contexts
42  splash = displayio.Group()
43  display.root_group = splash
44  setup = displayio.Group()
45  display.root_group = setup
46  set_hour_min = displayio.Group()
47  display.root_group = set_hour_min
48  set_datum = displayio.Group()
49  display.root_group = set_datum
50
51  #rote LED
52  #led_red = digitalio.DigitalInOut(board.GP5)
53  #led_red.direction = digitalio.Direction.OUTPUT
54  led_red = pwmio.PWMOut(board.GP5)
55  speaker = pwmio.PWMOut(board.GP15)
56  #gelbe LED
57  led_yellow = digitalio.DigitalInOut(board.GP6)
58  led_yellow.direction = digitalio.Direction.OUTPUT
59  #grüne LED
60  led_green = digitalio.DigitalInOut(board.GP7)
61  led_green.direction = digitalio.Direction.OUTPUT
62
63  ## Joystick
64  #JOY_UP = GP1
65  up = digitalio.DigitalInOut(board.GP1)
66  up.direction = digitalio.Direction.INPUT
67  up.pull = digitalio.Pull.UP
68  #JOY_DOWN = GP3
69  down = digitalio.DigitalInOut(board.GP3)
70  down.direction = digitalio.Direction.INPUT
71  down.pull = digitalio.Pull.UP
72  #JOY_LEFT = GP4
73  left = digitalio.DigitalInOut(board.GP4)
74  left.direction = digitalio.Direction.INPUT
75  left.pull = digitalio.Pull.UP
76  #JOY_RIGHT = GP2
77  right = digitalio.DigitalInOut(board.GP2)
78  right.direction = digitalio.Direction.INPUT
79  right.pull = digitalio.Pull.UP
80  #JOY_CENTER = GP0
81  center = digitalio.DigitalInOut(board.GP0)
82  center.direction = digitalio.Direction.INPUT
83  center.pull = digitalio.Pull.UP
84
85  zeit = "00:00:00"
86  wotage = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
87  alarm = [6,0]
88  option = 1
89
90  # create the label
91  updating_zeit = label.Label(font=terminalio.FONT, text="", scale=2, color=0xffffff, line_spacing=1)
92  # set label position on the display
93  updating_zeit.anchor_point = (0, 0)
94  updating_zeit.anchored_position = (15, 20)
95  # add label to group that is showing on display
96  splash.append(updating_zeit)
97
98  updating_label = label.Label(font=terminalio.FONT, text="", scale=1, color=0xffffff, line_spacing=1)
99  updating_label.anchor_point = (0, 0)
100 updating_label.anchored_position = (45, 3)
101 splash.append(updating_label)
102
103 updating_setup = label.Label(font=terminalio.FONT, text="Uhr stellen\n     Uhrzeit\n     Datum\n     Alarm\n     Exit", scale=1, color=0xffffff, line_spacing=1)
104 updating_setup.anchor_point = (0, 0)
105 updating_setup.anchored_position = (30, 0)
106 setup.append(updating_setup)
107
108 circle_auswahl = Circle(50, 17, 3, fill=0xffff00, outline=0x000000 )
109 setup.append(circle_auswahl)
110
111 # Stunde und Minute stellen
112 updating_stunde_minute = label.Label(font=terminalio.FONT, text="00:00:00", scale=2, color=0xffffff, line_spacing=1)
113 updating_stunde_minute.anchor_point = (0, 0)
114 updating_stunde_minute.anchored_position = (15, 20)
115 set_hour_min.append(updating_stunde_minute)
116
117 # Datum stellen
118 updating_datum = label.Label(font=terminalio.FONT, text="01:01:2024", scale=1, color=0xffffff, line_spacing=1)
119 updating_datum.anchor_point = (0, 0)
120 updating_datum.anchored_position = (45, 25)
121 set_datum.append(updating_datum)
122
  

Die einzelnen 'Menüs' sind in den Funktionen
def Uhrzeit_stellen(year, month, day, hour, minute, tag),
def Datum_stellen(year, month, day, hour, minute, tag) und
def Alarmzeit_stellen(a_hour, a_minute)
enthalten, denen die Variable in den Klammern übergeben werden. Mit Hilfe des Joystick lassen sich durch
hoch - runter die Stunden und mit
rechts - links die Minuten stellen.
Bei Joystick gedrückt wird der Vorgang aufgerufen bzw. beendet. Interessant sind dabei die Zeilen 156, 157 und 193, 194. Hier wird der Timer verändert, indem die neuen Werte für time.localtime() als time.struct_time() geschrieben werden. Im unteren Kasten sind die Funktionen zum Kopieren enthalten:

  
  

123 def Datum_stellen(year, month, day, hour, minute, tag):
124     display.root_group = set_datum
125     while True:
126         if up.value == False:
127             day += 1
128             if day == 32:
129                 day = 1
130         if down.value == False:
131             day -= 1
132             if day == -1:
133                 day = 31
134         if right.value == False:
135             month += 1
136             if month == 13:
137                 month = 1
138                 year +=1
139         if left.value == False:
140             month -= 1
141             if month == -1:
142                 month = 12
143                 year -= 1
144         time.sleep(0.2)
145         # Anzeige von Datum
146         if day < 10:
147             datum = "0" + str(day) + "."
148         else:
149             datum = str(day) + "."
150         if month < 10:
151             datum = datum + "0" + str(month) + "." + str(year)
152         else:
153             datum = datum + str(month) + "." + str(year)
154         updating_datum.text = datum
155         if center.value == False:
156             r = rtc.RTC()
157             r.datetime = time.struct_time((year, month, day, hour, minute, 0, tag, 1, -1))
158             time.sleep(0.2)
159             display.root_groop = splash
160             break
161
162 def Uhrzeit_stellen(year, month, day, hour, minute, tag):
163     display.root_group = set_hour_min
164     while True:
165         if up.value == False:
166             hour += 1
167             if hour == 24:
168                 hour = 0
169         if down.value == False:
170             hour -= 1
171             if hour == 0:
172                 hour = 23
173         if right.value == False:
174             minute += 1
175             if minute == 60:
176                 minute = 0
177         if left.value == False:
178             minute -= 1
179             if minute == -1:
180                 minute = 59
181         time.sleep(0.2)
182         ## Anzeige beim Stellen der Uhr
183         if hour < 10:
184             zeit = "0" + str(hour)
185         else:
186             zeit = str(hour)
187         if minute < 10:
188             zeit = zeit + ":0" + str(minute) + ":00"
189         else:
190             zeit = zeit + ":" + str(minute) + ":00"
191         updating_stunde_minute.text = zeit
192         if center.value == False:
193             r = rtc.RTC()
194             r.datetime = time.struct_time((year, month, day, hour, minute, 0, tag, 1, -1))
195             time.sleep(0.2)
196             display.root_groop = splash
197             break
198
199 def Alarmzeit_stellen(a_hour, a_minute):
200     display.root_group = set_hour_min
201     while True:
202         if up.value == False:
203             a_hour += 1
204             if a_hour == 24:
205                 a_hour = 0
206         if down.value == False:
207             a_hour -= 1
208             if a_hour == 0:
209                 a_hour = 23
210         if right.value == False:
211             a_minute += 1
212             if a_minute == 60:
213                 a_minute = 0
214         if left.value == False:
215             a_minute -= 1
216             if a_minute == -1:
217                 a_minute = 59
218         time.sleep(0.2)
219         ## Anzeige beim Stellen der Uhr
220         if a_hour < 10:
221             zeit = "0" + str(a_hour)
222         else:
223             zeit = str(a_hour)
224         if a_minute < 10:
225             zeit = zeit + ":0" + str(a_minute)
226         else:
227             zeit = zeit + ":" + str(a_minute)
228         updating_stunde_minute.text = zeit
229         if center.value == False:
230             alarm[0] = a_hour
231             alarm[1] = a_minute
232             display.root_groop = splash
233             led_green.value = True
234             break
235
  

Die Funktionen werden später aus der while-Schleife heraus aufgerufen. In den folgenden Programmzeilen 236 bis 250 werden die Variable 'hour', 'minute', 'second', 'year', 'month', 'day' sowie 'tag' für Wochentag beim ersten Programmstart mit Hilfe von 'time.localtime()' mit Werten belegt. Wenn 'year == 2020' ist, wird davon ausgegangen, dass der Start nicht am Rechner erfolgte und die Variable 'stellen = 1' für das Stellmenü gesetzt.

  
  

236 current_time = time.localtime()
237 hour = current_time.tm_hour
238 minute = current_time.tm_min
239 second = current_time.tm_sec
240 year = current_time.tm_year
241 month = current_time.tm_mon
242 day = current_time.tm_mday
243 tag = current_time.tm_wday
244 datum = str(day) + "." + str(month) + "." + str(year)
245 if current_time.tm_year > 2020:
246     stellen = 0
247 if current_time.tm_year == 2020:
248     year = 2024
249     stellen = 1
250
  

Der Wert für das Jahr 2024 in Zeile 248 wurde gewählt, damit man beim Stellen nicht immer durch die ohnehin zurückliegenden Jahre 'scrollen' muss. Wird das Programm länger genutzt, sollte der Wert angepasst werden.
Es folgt die while-Schleife mit der Programmabarbeitung.

  
  

252 while True:
252     if stellen == 0:
253         display.root_group = splash
254         current_time = time.localtime()
255         hour = current_time.tm_hour
256         minute = current_time.tm_min
257         second = current_time.tm_sec
258         year = current_time.tm_year
259         month = current_time.tm_mon
260         day = current_time.tm_mday
261         tag = current_time.tm_wday
262         datum = str(day) + "." + str(month) + "." + str(year)
263     if stellen == 1:
264         display.root_group = setup
265         if (down.value) == False:
266             circle_auswahl.fill = 0x000000
267             circle_auswahl.y = circle_auswahl.y + 12
268             time.sleep(0.4)
269             circle_auswahl.fill = 0xffff00
270             if option < 5:
271                 option +=1
272             if option == 5:
273                 circle_auswahl.y = 15
274                 option = 1
275         if up.value == False:
276             circle_auswahl.fill = 0x000000
277             circle_auswahl.y = circle_auswahl.y - 12
278             time.sleep(0.4)
279             circle_auswahl.fill = 0xffff00
280             if option > 0:
281                 option -=1
282             if option == 0:
283                 circle_auswahl.y = 51
284                 option = 4
285         if center.value == False:
286             time.sleep(0.4)
287             if option == 1:
288                 Uhrzeit_stellen(year, month, day, hour, minute, tag)
289                 stellen = 0
290             if option == 2:
291                 Datum_stellen(year, month, day, hour, minute, tag)
292                 stellen = 0
293             if option == 3:
294                 a_hour = alarm[0]
295                 a_minute = alarm[1]
296                 Alarmzeit_stellen(a_hour, a_minute)
297                 stellen = 0
298                 display.root_groop = splash
299             if option == 4:
300                 stellen = 0
301                 option = 1
302                 display.root_groop = splash
303     if center.value == False and stellen == 0:
304         stellen = 1
305         time.sleep(0.6)
306     if hour < 10:
307         zeit = "0" + str(hour)
308     else:
309         zeit = str(hour)
310     if minute < 10:
311         zeit = zeit + ":0" + str(minute)
312     else:
313         zeit = zeit + ":" + str(minute)
314     if second < 10:
315         zeit = zeit + ":0" + str(second)
316     else:
317         zeit = zeit + ":" + str(second)
318     ## Wochentag, Datum und Zeit anzeigen
319     updating_zeit.text = zeit
320     updating_label.text = wotage[tag] + "\n\n\n\n" + datum
321     if alarm[0] == hour and alarm[1] == minute:
322         led_green.value = False
323         for cycle in range(0, 65535, +2):  # Cycles through the full PWM range from 0 to 65535
324             led_red.duty_cycle = cycle  # Cycles the LED pin duty cycle through the range of values
325             speaker.duty_cycle = 32678
326         for cycle in range(65534, 0, -2):  # Cycles through the PWM range backwards from 65534 to 0
327             led_red.duty_cycle = cycle  # Cycles the LED pin duty cycle through the range of values
328             speaker.duty_cycle = 0
329
  

Auch hierzu noch ein paar Erläuterungen:
Die Zeilen 252 bis 262 aktualisieren bei jedem Schleifendurchlauf die Variable für die Anzeige von Zeit und Datum, während ab Zeile 263 das 'Setup-Menü' zum Stellen aufgerufen wird, falls die Bedingung erfüllt ist (siehe unteres Bild). Die leuchtende grüne LED zeigt an, dass bereits eine Alarmzeit eingestellt wurde.


Mit dem Joystick navigiert man zur gewünschten Option und bestätigt diese durch Drücken mittig. Es öffnet sich das entsprechende 'Untermenü' im dem Uhrzeit, Datum oder Alarmzeit eingestellt werden können. Mit einem Druck auf den Joystick mittig, wird jede Aktion abgeschlossen. Auch über 'Exit' kann das Menü verlassen werden.


Wenn die eingestellte Alarmzeit erreicht ist, meldet sich die blinkende rote LED und das etwas mickrig klingende Signal des Minilautsprechers für eine Minute. Eine Abstellmöglichkeit ist z.Z. nicht vorgesehen.


Damit ist der PicoBoy in Verbindung mit dem Gehäuse aus dem 3-D Drucker eine richtige kleine Digitaluhr z.B. für den Schreibtisch. Obwohl ich nur an wenigen Stellen Kommentarzeilen eingefügt habe, hoffe ich dennoch, dass die Anleitung verständlich war. Wenn Sie meinen, man kann das Programm an vielen Stellen noch weiter verkürzen und optimieren, sind Sie gerne zu weiteren eigenen Veränderungen aufgerufen. Sie können den Quellcode für private Zwecke vollständig oder teilweise nutzen und beliebig verändern. Falls es doch Probleme gibt, nutzen Sie den kompletten Quellcode in Form der zip-Datei von hier, und erst dann wenden Sie sich bitte per e-Mail an mich. Konkrete Fragen zum Thema PicoBoy, CircuitPython oder RPI-Pico werde ich gerne beantworten.


Viel Spass und Erfolg beim Ausprobieren.