Experimente


Neopixeluhr am PicoBoy-Color


Projekte und Anleitung in CircuitPython 9.x.x



Bildbox 1 (klick hier)

Es folgt die angekündigte Neopixeluhr. Sie baut auf dem Beispiel 'temp_uhr.py' von hier auf.


Hardware / Software

- PicoBoy - Color
- Neopixelring 12 LED
- USB-C Kabel
- Firmware CircuitPython 9.x.x
- neopixel.mpy (aus dem CircuitPython Bundle)


Los gehts

Die ursprünglichen Zeilen 1 bis 41 sahen so aus:

  
  

1  import time
2  import gc
3  import rtc
4  import board
5  import busio
6  from adafruit_display_text import label
7  from adafruit_display_shapes.roundrect import RoundRect
8  import terminalio
9  import displayio
10 import keypad
11 from adafruit_st7789 import ST7789
12 from adafruit_bme280 import basic as adafruit_bme280
13
14 #I2C Pin of the PicoBoy - Color
15 SDA = board.GP20
16 SCL = board.GP21
17 # Create sensor object, using the board's default I2C bus.
18 i2c = busio.I2C(SCL, SDA)
19 bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x77)
20
21 # change this to match the location's pressure (hPa) at sea level
22 bme280.sea_level_pressure = 1030
23 
24 # 1,69 Zoll 240x280 Pixel ST7789 Display
25 # SPI-Bus,  in Landscape format
26 dc=board.GP8
27 reset=board.GP9
28 cs=board.GP10
29 sck=board.GP18
30 mosi=board.GP19
31 bl=board.GP26
32 # Release any resources currently in use for the displays
33 displayio.release_displays()
34 spi = busio.SPI(sck, mosi)
35 display_bus = displayio.FourWire(spi, command=dc, chip_select=cs, reset=reset)
36 display = ST7789(display_bus, rotation=90, width=280, height=240, backlight_pin=bl, rowstart=20, colstart=0)
37 display.brightness = 1
38 # Make the display context
39 splash = displayio.Group()
40 timeset= displayio.Group()
41 display.root_group = splash
  

Da wir jetzt keinen Temperatursensor angeschlossen haben, löschen Sie die Zeilen 12 bis 22. Zur Nutzung der Button werden folgende Zeilen ergänzt:

  
  

32  K_UP = 0x04
33  K_DOWN = 0x02
34  K_RIGHT = 0x08
35  K_LEFT = 0x01
36  K_A = 0x10
37  K_B = 0x20
38  K_Z = 0x40
39
40  class _Buttons:
41      def __init__(self):
42          self.keys = keypad.Keys((board.GP4, board.GP3, board.GP1, board.GP2, board.GP27, board.GP28, board.GP0),
43                                  value_when_pressed=False, interval=0.05)
44          self.last_state = 0
45          self.event = keypad.Event(0, False)
46          self.last_z_press = None
47
48      def get_pressed(self):
49          buttons = self.last_state
50          events = self.keys.events
51          while events:
52              if events.get_into(self.event):
53                  bit = 1 << self.event.key_number
54                  if self.event.pressed:
55                      buttons |= bit
56                      self.last_state |= bit
57                  else:
58                      self.last_state &= ~bit
59          return buttons
60  buttons = _Buttons()
61
  

Für die Anzeige der Uhrzeit auf dem Display und die Stellfunktion werden jetzt die Zeichen- und Textelemente ergänzt:

  
  

62  # Make a background color fill
63  color_bitmap = displayio.Bitmap(display.width, display.height, 3)
64  color_palette = displayio.Palette(3)
65  color_palette[0] = 0x660088
66  color_palette[1] = 0x000000
67  color_palette[2] = 0xffffff
68  bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
69  splash.append(bg_sprite)
70  # Draw a label
71  text_area = label.Label(font=terminalio.FONT, text="00:00:00", color=0xFFFF00, scale=5, line_spacing=1 )
72  text_group = displayio.Group(x=25, y=110)
73  text_group.append(text_area)
74  splash.append(text_group)
75
76  # roundrect in timeset
77  roundrect1 = RoundRect(25, 40, 230, 70, 10, fill=0x0, outline=0xffffff, stroke=3)
78  timeset.append(roundrect1)
79  # create the label for time in timeset
80  updating_label_zeit = label.Label(font=terminalio.FONT, text="00:00:00", scale=4, color=0xffffff, line_spacing=1)
81  updating_label_zeit.anchor_point = (0, 0)
82  updating_label_zeit.anchored_position = (45, 50)
83  timeset.append(updating_label_zeit)
84  #create the label for timeset function
85  updating_label = label.Label(font=terminalio.FONT, scale=2, color=0x00ff00, line_spacing=1)
86  updating_label.text = "up   : hour up\ndown : hour down\nright: min up\nleft : min down\nexit : ok"
87  updating_label.anchor_point = (0, 0)
88  updating_label.anchored_position = (40, 120)
89  timeset.append(updating_label)
90
  

Es folgen der Import der Neopixel-Bibliotheken und die Definition der Funktionen für die Effekte:

  
  

91  import neopixel
92  from rainbowio import colorwheel
93
94  num_pixels = 12
95  pixel_pin = board.GP20
96  pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.05, auto_write=False)
97  #strip1 = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.15)
98
99  sine1 = [  # These are the pixels in order of animation - 12 pixels in total:
100         0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
101
102 COLORS = (
103         (155,   0,   0),
104         (  0, 155,   0),
105         (  0,   0, 155),
106         (155, 155,   0),
107         (155,   0, 155),
108         (  0, 155, 155),
109         )
110
111 def rainbow(speed):
112     for k in range (2):
113         for j in range(255):
114             for i in range(12):
115                 pixel_index = (i * 256 // 12) + j
116                 pixels[i] = colorwheel(pixel_index & 255)
117             pixels.show()
118             time.sleep(speed)
119
120 def colorwheel(pos):
121     if pos < 0 or pos > 255:
122         return (0, 0, 0, 0)
123     if pos < 85:
124         return (255 - pos * 3, pos * 3, 0, 0)
125     if pos < 170:
126         pos -= 85
127         return (0, 255 - pos * 3, pos * 3, 0)
128     pos -= 170
129     return (pos * 3, 0, 255 - pos * 3, 0)
130
131 def rainbow_cycle(wait):
132     for j in range(255):
133         for i in range(num_pixels):
134             rc_index = (i * 256 // num_pixels) + j
135             pixels[i] = colorwheel(rc_index & 255)
136         pixels.show()
137
  

Wir kommen nun zur eigentlichen Uhr. Vor der 'While'-Schleife werden noch zwei Variable definiert (debounce und stellen) und auf den logischen Wert 'False' gesetzt. Außerdem werden alle Neopixel-LEDs ausgeschaltet.
Da der Timer des Microcontrollers ohne Synchronisation am PC mit 1.1.2020 um 0:00 Uhr startet brauchen wir eine Stellmöglichkeit. Die übernehmen wir aus dem o.g. Beispiel 'temp_uhr.py'. Im unteren Kasten sind das die Zeilen 155 bis 226. Kopieren Sie alle Zeilen zu den schon vorhandenen hinzu. Zeilen, die wir erst später nutzen sind zunächst auskommentiert. Aufgerufen wird der Stellmodus mit dem Taster A. Zum Stellen werden die Joystick-Funktionen genutzt. Verlassen wird der Stellmodus mit dem Joystick 'ok'.
Ein Tipp zum Vorgehen: Stellen Sie zuerst die aktuelle Stunde ein. Dann die Minute um eins höher als die aktuelle Minute. Drücken Sie dann im Abgleich mit einer Vergleichsuhr (Handy od. PC) auf 'ok', wenn die Sekunde von 59 auf 00 umschaltet. So kann der Start recht gut eingestellt werden. Zur Genauigkeit des Timers über einen längeren Zeitraum habe ich z.Z. noch keine Erfahrung.

  
  

138 pixels.fill((0 , 0, 0))
139 pixels.show()
140 stellen = False
141 debounce = False
142
143 while True:
144     #####
145     # Zeit stellen
146     #####
147     cur_btn_vals = buttons.get_pressed()
148     cur_up = cur_btn_vals & K_UP
149     cur_left = cur_btn_vals & K_LEFT
150     cur_right = cur_btn_vals & K_RIGHT
151     cur_down = cur_btn_vals &  K_DOWN
152     cur_a = cur_btn_vals & K_A
153     cur_b = cur_btn_vals & K_B
154     cur_exit = cur_btn_vals & K_Z
155     # Stellen aufrufen
156     if cur_a and not debounce:
157         stellen = True
158         debounce = True
159         r = rtc.RTC()
160         r.datetime = time.struct_time((2024, 11, 3, 12, minute, 0, 6, 1, -1))
161         display.root_group = timeset
162
163     #if cur_b and not debounce:
164     #    debounce = True
165     #    text_area.scale=3
166     #    text_area.text = "  fasten your\n\n  seat belt"
167     #    for wait in range(10, 1, -2):
168     #        for color in COLORS:
169     #            for i in range(len(sine1)):
170     #                # Set 'head' pixel to color:
171     #                pixels[sine1[i]] = color
172     #                # Erase 'tail,' 8 pixels back:
173     #                pixels[sine1[(i + len(sine1) - 1) % len(sine1)]] = [0, 0, 0]
174     #                pixels.write()  # Refresh LED states
175     #                time.sleep(wait/300)  # xx millisecond delay
176     #    # rainbow cycle
177     #    rainbow_cycle(0)
178     #    pixels.fill(( 0, 0, 0))
179     #    text_area.scale=5
180     # Stellen verlassen
181     if cur_exit and not debounce:
182         r = rtc.RTC()
183         r.datetime = time.struct_time((2024, 11, 3, hour, minute, 0, 6, 1, -1))
184         stellen = False
185         debounce = True
186         display.root_group = splash
187         #rainbow(0)
188         #pixels.fill(( 0, 0, 0))
189     if stellen == True:
190         updating_label_zeit.text = "{:02}:{:02}:00".format(current_time.tm_hour,current_time.tm_min)
191         # Stunden erhoehen
192         if cur_up and not debounce:
193             debounce = True
194             if hour < 23:
195                 hour += 1
196             else:
197                 hour = 0
198             r = rtc.RTC()
199             r.datetime = time.struct_time((2024, 11, 3, hour, minute, 0, 6, 1, -1))
200         # Stunden verringern
201         if cur_down and not debounce:
202             debounce = True
203             if hour > 0:
204                 hour -= 1
205             else:
206                 hour = 23
207             r = rtc.RTC()
208             r.datetime = time.struct_time((2024, 11, 3, hour, minute, 0, 6, 1, -1))
209         # Minuten erhoehen
210         if cur_right and not debounce:
211             debounce = True
212             if minute < 59:
213                 minute += 1
214             else:
215                 minute = 0
216             r = rtc.RTC()
217             r.datetime = time.struct_time((2024, 11, 3, hour, minute, 0, 6, 1, -1))
218         # Minuten verringern
219         if cur_left and not debounce:
220             debounce = True
221             if minute > 0:
222                 minute -= 1
223             else:
224                 minute = 59
225             r = rtc.RTC()
226             r.datetime = time.struct_time((2024, 11, 3, hour, minute, 0, 6, 1, -1))
227     if cur_btn_vals == 0:
228         debounce = False
229     #####
230     # Zeit anzeigen
231     #####
232     current_time = time.localtime()
233     hour = current_time.tm_hour
234     if hour > 12:
235         neohour = hour -12
236     minute = current_time.tm_min
237     second = current_time.tm_sec
238     text_area.text = "{:02}:{:02}:{:02}".format(current_time.tm_hour,current_time.tm_min,current_time.tm_sec)
239
  

Zum Abschluss werden die Neopixeleffekte hinzugefügt. Und zwar:

- Anzeige der aktuellen Stunde rot
- Anzeige der Minuten (nach jeweils 60/12 = 5 Minuten) in grün
- Anzeige der Sekunden blau (vergangene Sekunde wird schwächer, folgende Sekunde stärker)
- zu jeder vollen Minute erfolgt ein Lichteffekt
- mit der Taste B wird ein besonderer Lichteffekt aufgerufen.

Eine Besonderheit wird Ihnen auffallen. Wenn sich zwei 'Zeiger' überdecken, wird nur die grüne Minute-LED angezeigt. Diese überdeckt dann für fünf Minuten die rote Stunden-LED.

Fügen Sie den restlichen Quellcode zum Programm hinzu und speichern ihn als 'code.py'. In den Zeilen 163 bis 179 entfernen Sie die Auskommentierung, um den Effekt für die Taste B zu aktivieren. Beim Start am Rechner braucht keine Zeit eingestellt werden. Ohne Rechner ist die Einstellung anzuraten, wenn Sie nicht in der Stunde 'Null' leben wollen.

  
  

240     # Lichteffekt zur vollen Minute
241     if second == 0 :
242         rainbow(0)
243         pixels.fill(( 0, 0, 0))
244     second += 1
245     # Position Stunde, Minute, Sekunde
246     hour_pixel = neohour - 6
247     minute_pixel = int(minute / 5) - 6
248     second_pixel = int(second / 5) - 6
249     pixels[second_pixel] = (0, 0, 255)
250     # Sekunden-LED wird heller
251     if second % 5 == 1:
252         pixels[second_pixel + 1] = (0, 0, 25)
253     if second % 5 == 2:
254         pixels[second_pixel + 1] = (0, 0, 50)
255     if second % 5 == 3:
256         pixels[second_pixel + 1] = (0, 0, 75)
257     if second % 5 == 4:
258         pixels[second_pixel + 1] = (0, 0, 120)
259     if second % 5 == 5:
260         pixels[second_pixel + 1] = (0, 0, 255)
261     # Sekunden-LED wird schwaecher
262     if second % 5 == 1:
263         pixels[second_pixel] = (0, 0, 120)
264     if second % 5 == 2:
265         pixels[second_pixel] = (0, 0, 75)
266     if second % 5 == 3:
267         pixels[second_pixel] = (0, 0, 50)
268     if second % 5 == 4:
269         pixels[second_pixel] = (0, 0, 25)
270     if second % 5 == 5:
271         pixels[second_pixel] = (0, 0, 0)
272     pixels[second_pixel -1] = (0, 0, 0)
273     # Sekunde-LED wird von Minute oder Stunde ueberdeckt
274     if pixels[second_pixel] == pixels[minute_pixel]:
275         pixels[second_pixel] = (100, 100, 100)
276     if pixels[second_pixel] == pixels[hour_pixel]:
277         pixels[second_pixel] = (100, 0, 100)
278     if pixels[minute_pixel] == pixels[hour_pixel]:
279         pixels[minute_pixel] = (255, 100, 0)
280     pixels[hour_pixel] = (255, 0, 0)
281     pixels[minute_pixel] = (0, 255, 0)
282     pixels.show()
  


Hier noch ein kurzer Ausschnitt vom ersten Test.



Viel Spass und Erfolg beim Ausprobieren.