Raspberry Pi Pico als analoge Uhr

Da der Raspberry Pi Pico noch recht neu für mich ist und ich mein Wissen über Python wieder auffrischen muss, nutze ich immer kleine „Standardprojekte“ damit ich mich mit neuer Hardware oder Software zurechtfinde. Dies haben Sie schon in meinem letzten Blog Pico als Wetterstation gesehen und möchte ich an dieser Stelle gerne fortführen, indem mittels einer RealTimeClock und einem OLED-Display eine analoge Uhr gebaut wird.

Den Blog teile ich in zwei Teile auf:

  1. Eine RealTimeClock Typ DS3231 am Pico Inbetriebnahme
  2. Die Zeit der RealTimeClock auf OLED-Display anzeigen

Dabei soll die Uhr gleichzeitig zwischen Sommer- und Winterzeit unterscheiden können. Nötig ist ein RTC-Bauteil, da der Raspberry Pi Pico keine intern verbaute RTC besitzt.

Benötigte Hard- und Software

Die Hardware für diesen Versuchsaufbau ist simpel, siehe Tabelle 1.

Anzahl Hardwarebauteil Anmerkung
1 Raspberry Pi Pico
1 0,96 Zoll OLED SSD1306 Display I2C
1 Breadboard und Jumper Wire
1 Real Time Clock RTC DS3231 I2C Echtzeituhr

alternativ: DS1302 RTC

Tabelle 1: Hardwareteile für analoge Uhr

Bei der Software nutzen Sie, da es ein Programm mit MicroPython wird, Thonny Python IDE, welches bei dem Raspbian Image schon zur Verfügung steht und für alle gängigen Betriebssysteme installiert werden kann.

Zusätzlich benötigen Sie, sobald die Thonny Python IDE installiert und der Pico angeschlossen ist, die Bibliotheken urtc und microypthon-oled, welche dann auf den Pico übertragen werden. Wie Sie Bibliotheken installieren, erfahren Sie auch im Beitrag zur Pico Wetterstation.

Die RTC DS3231 in Betrieb nehmen

Gerade wenn Sie sich ein neues RTC DS3231 aus unserem Shop holen oder wenn die RTC-Batterie leer geworden sein sollte, dann muss die RTC DS3231 neu eingestellt werden. Das wird in unserem ersten Teil dieses Blogs behandelt. Dazu muss zunächst der Raspberry Pi Pico mit der RCT DS3231 verdrahtet werden, siehe Abbildung 1.

Abbildung 1: Verdrahtung RTC DS3231 mit Pico

Abbildung 1: Verdrahtung RTC DS3231 mit Pico

Für den ganzen Aufbau sind im Grunde vier Drähte nötig und die beiden vorher genannten Bauteile. Im Anschluss, sofern Sie schon Thonny Python IDE installiert haben, schließen Sie den Raspberry Pi Pico am PC oder dem Raspberry Pi an und laden Sie die benötigten Bibliotheken runter. An dieser Stelle gehe ich davon aus, dass die Firmware auf dem Pico vorhanden ist. Laden Sie danach Code 1 auf den Pico. Wie Sie die Firmware installieren, können Sie in diesem Quickstart Guide nachlesen.

 

 """
 // Read/Write a DS3231-RTC via I2C
 // and write output to terminal
 // Autor:   Joern Weise
 // License: GNU GPl 3.0
 // Created: 03. Nov 2021
 // Update: 11. Nov 2021
 """
 
 from machine import Pin,I2C
 from utime import sleep
 import urtc #Lib for RTC
 
 #Init needed I2C-Interface
 I2C_PORT = 0
 I2C_SDA = Pin(0)
 I2C_SCL = Pin(1)
 i2c = I2C(I2C_PORT, sda=I2C_SDA, scl=I2C_SCL, freq=40000)
 
 #Write in terminal found addresses
 i2cScan = i2c.scan()
 counter = 0
 print('-----------------')
 print('Found I2C-Devices')
 for i in i2cScan:
     print("I2C Address " + f'{counter:03d}' + " : "+hex(i).upper())
     counter+=1
 #sleep(5) #Make as comment, if date/time should be set
 #Init RTC-DS3231
 RTC = urtc.DS3231(i2c)
 arrayDay = ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"];
 
 """#SET TIME FROM RTC
 #Uncomment only to set time to RTC
 #Create tuple with new date e.g. Wednesday 03 Nov. 2021 07:30:00 AM
 datetime = urtc.datetime_tuple(year=2021, month=11, day=3,
                                weekday=5, hour=10, minute=34,
                                second=0, millisecond=0)
 RTC.datetime(datetime) #Write new date/time to RTC
 """#END setting time
 
 lastSecond = -1
 while True: #Infinit loop to read date/time
     t = RTC.datetime() #Get date/time from RTC
     if lastSecond != int(t[6]): #Update only if seconds changed
         print('-----------------')
         #print(lastSecond) #Show last second
         #print(RTC.datetime()) #Show tuple with date-time information
         #Output current date and time from RTC to terminal
         print('Date: ' + f'{t[2]:02d}' + '/' + f'{t[1]:02d}' + '/' + f'{t[0]:02d}')
         print('Day: ' + arrayDay[t[3]-1])
         print('Time: ' + f'{t[4]:02d}'+':'+f'{t[5]:02d}'+':'+f'{t[6]:02d}')
         lastSecond = int(t[6])

Code 1: Einstellen vom RTC DS3231

Damit die RTC DS3231 auch gestellt wird, müssen Sie in der Zeile “““#SET TIME FROM RTC und “““#End settime die voranstehenden “““ löschen und ein neues Datum und Uhrzeit eintragen. Weekday ist an der Stelle etwas verwirrend, da hier das amerikanische Zeitformat verwendet wird und die Woche mit dem Tag Sonntag beginnt. Daher ist Sonntag die 1 und Mittwoch in dem Beispiel die 4. Beim Ausführen vom Skript wird dann die RTC DS3231 neu gestellt. Sie sehen direkt danach in der Kommandozeile alle Sekunde die aktuelle Uhrzeit und Datum vom RTC, siehe Abbildung 2.

Abbildung 2: Aktuelle Datum- und Zeitangabe der RTC DS3231

Abbildung 2: Aktuelle Datum- und Zeitangabe der RTC DS3231

Starten Sie den Pico noch einmal, dann wird im aktuellen Fall die Zeit wieder neu gesetzt, daher sollten Sie die “““ wieder an den entsprechenden Stellen einfügen und den Code hochladen lassen.

Solange nun die Knopfzelle in der RTC DS 3231 eingesetzt und nicht leer ist, wird die Uhr weiterlaufen.

Analoge Uhr mit OLED und RTC DS3231

Damit kommt nun das eigentliche Projekt, die analoge Uhr auf dem OLED-Display mit allen weiteren Zeitangaben. Die Verdrahtung, da im Grunde nur eine weitere Komponente hinzugekommen ist, ist nicht großartig komplizierter geworden, siehe Abbildung 3.

Abbildung 3: Verdrahtung OLED-Display und RTC DS3231 mit Pico

Abbildung 3: Verdrahtung OLED-Display und RTC DS3231 mit Pico

Da die RTC DS3231 und das OLED-Display beide mit i2c kommunizieren, aber unterschiedliche Adressen haben, können diese auf eine Bus-Leitung gelegt werden. Sind die Verdrahtungsarbeiten erledigt, können Sie Code 2 auf den Pico übertragen.

 

 """
 // Read a DS3231-RTC via I2C and
 // write output to terminal and OLED
 // Autor:   Joern Weise
 // License: GNU GPl 3.0
 // Created: 04. Nov 2021
 // Update: 05. Nov 2021
 """
 
 from machine import Pin,I2C
 import utime
 from oled import SSD1306_I2C,gfx,Write
 from oled.fonts import ubuntu_mono_12, ubuntu_mono_15
 import urtc #Lib for RTC
 import math #Everybody loves math ;)
 
 bPrintDiag = False #Bool to show terminal diag. WARNING set to True cost performance
 #Init needed I2C-Interface
 I2C_PORT = 0
 I2C_SDA = Pin(0)
 I2C_SCL = Pin(1)
 i2c = I2C(I2C_PORT, sda=I2C_SDA, scl=I2C_SCL, freq=40000)
 
 #Write in terminal found addresses
 if bPrintDiag:
     i2cScan = i2c.scan()
     counter = 0
     print('-----------------')
     print('Found I2C-Devices')
     for i in i2cScan:
         print("I2C Address " + f'{counter:03d}' + " : "+hex(i).upper())
         counter+=1
 
 #Init RTC-DS3231
 RTC = urtc.DS3231(i2c)
 arrayDay = ["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"];
 
 #Definitions and init for OLED-Display
 WIDTH = 128
 HIGHT = 64
 oled = SSD1306_I2C(WIDTH, HIGHT, i2c)
 graphics = gfx.GFX(WIDTH,HIGHT,oled.pixel)
 
 #Definitions for clock
 xCenter = 32
 yCenter = 32
 radius = 30
 
 #Definition for Font
 font = Write(oled,ubuntu_mono_12)
 Intro = Write(oled,ubuntu_mono_15)
 
 """#SET TIME FROM RTC
 #Uncomment only to set time to RTC
 #Create tuple with new date e.g. Wednesday 03 Nov. 2021 07:30:00 AM
 datetime = urtc.datetime_tuple(year=2021, month=11, day=3,
                                weekday=4, hour=7, minute=30,
                                second=0, millisecond=0)
 RTC.datetime(datetime) #Write new date/time to RTC
 """#End Sync time from RTC
 
 """
 Function: Calc_Summer_Winter_Time
 Description: Calculate the time difference
 for summer or winter time
 IN hour: Current hour
 IN minute: Current minute
 IN second: Curent second
 IN day: Current day
 IN month: Current month
 In year: Current year
 """
 def Calc_Summer_Winter_Time(hour,minute,second,day,month,year):
     HHMarch = utime.mktime((year,3 ,(14-(int(5*year/4+1))%7),1,0,0,0,0,0)) #Time of March change to DST
     HHNovember = utime.mktime((year,10,(7-(int(5*year/4+1))%7),1,0,0,0,0,0)) #Time of November change to EST
     #print(HHNovember)
     now=utime.mktime((year,month,day,hour,minute,second,0,0))
     
     if now < HHMarch :
         dst=0
     elif now < HHNovember : # we are before last sunday of october
         dst=1
     else: # we are after last sunday of october
         dst=0
     
     return(dst)
 
 """
 Function: Draw_Clock
 Description: Draw the analog clock on OLED
 IN hour: Current hour
 IN minute: Current minute
 """
 def Draw_Clock(hour,minute):
     graphics.circle(xCenter,yCenter,radius,1)
     graphics.fill_circle(xCenter,yCenter,2,1)
     oled.text('12',25,6)
     oled.text('3',52,30)
     oled.text('6',29,50)
     oled.text('9',5,28)
     MinutAngle = 180 - minute * 6
     MinShift = 5 * int(minute/10)
     #print(MinutAngle)
     if hour>=0 and hour<=12:
         HourAngle = 180 - hour * 30
     else:
         HourAngle = 180 - (hour-12) * 30
     HourAngle-=MinShift
     #print(HourAngle)
     #Obtain coordinates for minute handle
     shift_min_x = 0.8 * radius * math.sin(math.radians(MinutAngle))
     shift_min_y = 0.8 * radius * math.cos(math.radians(MinutAngle))
     graphics.line(xCenter,yCenter,round(xCenter+shift_min_x),round(yCenter+shift_min_y),1)
     #Obtain coordinates for hour handle
     shift_hour_x = 0.6 * radius * math.sin(math.radians(HourAngle))
     shift_hour_y = 0.6 * radius * math.cos(math.radians(HourAngle))
     graphics.line(xCenter,yCenter,round(xCenter + shift_hour_x),round(yCenter + shift_hour_y),1)
 
 """
 Function: Calc_Summer_Winter_Time
 Description: Calculate the time difference
 for summer or winter time
 IN hour: Current hour
 IN minute: Current minute
 IN second: Curent second
 IN mday: Written day-name
 IN day: Current day
 IN month: Current month
 In year: Current year
 """
 def Print_Date_Time(hour,minute,second,dayName,day,month,year):
     yPos = 1
     yShift = 13
     xPos = xCenter+radius+6
     font.text('---Date---', xPos, yPos)
     yPos+=yShift
     font.text(f'{day:02d}' + '/' + f'{month:02d}' + '/' + f'{year:02d}', xPos, yPos)
     yPos+=yShift
     font.text(f'{dayName:^10}', xPos, yPos) #Dayname in center
     yPos+=yShift
     font.text('---Time---', xPos, yPos)
     yPos+=yShift
     font.text(' ' + f'{hour:02d}' + ':' + f'{minute:02d}' + ':' + f'{second:02d}', xPos, yPos)
     
     
 lastSecond = -1 # Update text on OLED
 lastMinute = -1 #Update Analog clock
 lastHour = -1 #To check sommer and winter time
 TimeShift = 0 #For summer and winter time
 #Show an intro on OLED for 5 seconds
 oled.fill(0)
 graphics.fill_rect(0, 5, 128, 15, 1)
 Intro.text("Analog Clock", 20, 5, bgcolor=1, color=0)
 Intro.text("(c) Joern Weise", 10, 25)
 Intro.text("for Az-Delivery", 10, 45)
 oled.show()
 
 #Print short intro in terminal
 print('----------------')
 print(f'{"Analog Clock":^16}')
 print(f'{"(c) Joern Weise":^16}')
 print(f'{"for Az-Delivery":^16}')
 print('----------------')
 
 utime.sleep(5)
 
 while True: #Infinit loop to read date/time
     t = RTC.datetime() #Get date/time from RTC
     if lastSecond != int(t[6]): #Update only if seconds changed
         if lastHour != t[4]: #Check summer or winter time
             TimeShift = Calc_Summer_Winter_Time(t[4],t[5],t[6],t[2],t[1],t[0])
             #print('New timeshift: ' + str(TimeShift))
             lastHour = t[4]
         correctHour = t[4] + TimeShift
         #Output current date and time from RTC to terminal
         if bPrintDiag:
             print('-----------------')
             print('Date: ' + f'{t[2]:02d}' + '/' + f'{t[1]:02d}' + '/' + f'{t[0]:02d}')
             print('Day: ' + arrayDay[t[3]-1])
             print('Time: ' + f'{correctHour:02d}'+':'+f'{t[5]:02d}'+':'+f'{t[6]:02d}')
         if lastMinute != t[5]:
             oled.fill(0)
             Draw_Clock(correctHour,t[5])
             lastMinute = t[5]
         Print_Date_Time(correctHour,t[5],t[6],arrayDay[t[3]-1],t[2],t[1],t[0])
         oled.show()
         lastSecond = int(t[6])

Code 2: Analoge Uhr mit OLED und RTC DS3231

Diesen Quellcode möchte ich an einigen Stellen kurz erläutern. Zunächst habe ich ein Diag-Flag in meinem Quellcode bPrintDiag eingebaut. Da alle Sekunde die komplette Zeitangabe im Terminal ausgegeben wird, läuft der Pico damit ein bisschen träg. Da die Diag-Ausgaben nur zum Debuggen benötigt werden, habe ich diese ein- bzw. ausschaltbar gemacht. Danach folgt die Definition der Funktion Calc_Summer_Winter_Time, welche überprüft, ob Winter- oder Sommerzeit ist. Diese liefert die Verschiebung der Stunde zurück, welche in der while-Schleife als Korrektur der RTC DS3231 -Stundenangabe verwendet wird. Zusätzlich, damit der Code übersichtlicher bleibt, habe ich das Zeichnen der analogen Uhr und das Schreiben des Textes auf dem OLED-Display in die Funktionen Draw_Clock und Print_Date_Time ausgelagert. Die erste Funktion wird nur aufgerufen, wenn es eine Änderung bei der Minutenangabe gab, letztere, wenn es eine Änderung bei den Sekunden gab.

Draw_Clock enthält die Mathematik, um die Zeiger und das Uhrengesicht zu zeichnen. Da ich ein Fan davon bin, dass alles genau wie bei einer richtigen analogen Uhr funktioniert, wird auch das Verhalten des Stundenzeigers korrekt nachgebildet. Im anfänglichen Quellcode war der Stundenzeiger starr auf der aktuellen Stunde. Bei einer richtigen analogen Uhr ist das nicht der Fall, sondern wandert Stück für Stück zur kommenden Stunde. Dies habe ich dadurch realisiert, dass ich den Winkel für den Stundenzeiger mit der aktuellen Minute verrechne. Da in der Standardbibliothek für das OLED einige Zeichenfunktionen fehlen, nutze ich zum Zeichnen die GFX-Bibliothek. Diese ruft zum Zeichnen immer die Funktion vom pixel von oled-Objekt auf. Die Definition ist am Anfang vom Quellcode zu finden, siehe hinter dem Kommentar #Definitions and init for OLED-Display.

Print_Date_Time schreibt dann sowohl die Zeit als auch das Datum auf die rechte Seite des OLED-Displays. Wie schon erwähnt, erfolgt dies im Sekundentakt. Trotzdem gibt es noch ein kleines Gimmig. Die Standardschrift vom OLED ist recht groß, daher gibt es das Objekt font, was am Anfang vom Quelltext erstellt wird. Dieses Lädt das Font ubuntu_mono_12, welches in der Bibliothek microypthon-oled gleich mitgeliefert wird.

Die While-Schleife am Ende vom Quellcode erledigt dann den Rest. Diese übernimmt die entsprechenden Überprüfungen. Das Resultat sehen Sie in Abbildung 4.

Abbildung 4: Das aktuelle Datum und die aktuelle Zeit mit dem Pico

Abbildung 4: Das aktuelle Datum und die aktuelle Zeit mit dem Pico

Zusammenfassung

Der Pico ist noch recht neu in unserem Sortiment da können viele „alte“ Projekte noch einmal neu aufgebaut werden. Gerade in Bezug auf MicroController für kleines Geld, ist der Pico eine echte Alternative zu einem Nano V3.0 oder Mikrocontroller Board ATmega328. Der Unterschied beträgt zwar „nur“ 2 Euro, aber vom Prinzip steht der Raspberry Pi Pico seiner Vorlage in nichts nach.

Persönlich bin ich von dem Pico immer mehr begeistert, nicht zuletzt durch die Skriptsprache MicroPython. Zwar sind die Bibliotheken für den Pico noch nicht so umfänglich, wie es bei der Arduino IDE der Fall ist, aber man muss hier im Hinterkopf behalten, dass der Raspberry Pi Pico erst am 21. Januar 2021 vorgestellt wurde und eine weltweite Verteilung der Hardware auch gedauert hat. Der Nano V3.0 oder das Mikrocontroller Board ATmega328 sind deutlich länger auf dem Markt und dementsprechend ist schon mehr im Bereich Bibliotheksentwicklung passiert.

Um das Projekt noch weiter zu personalisieren, könnten Sie z.B. noch einen Sekundenzeiger auf dem Uhrenblatt hinzufügen, womit aber alle Sekunde auch dieses neu gezeichnet werden muss. Sie können auch die Zeiger mit ein bisschen Mathematik weiter modifizieren, wie Sie es gerne hätten.

Hinweis: sollten Sie statt des RTC DS3231 I2C Echtzeituhr Moduls das DS1302 Serial Real Time Clock RTC Echtzeituhr Modul verwenden, können Sie diese Programmbibliothek ausprobieren.

Weitere Projekte für Az-Delivery von mir, finden Sie unter https://github.com/M3taKn1ght/Blog-Repo.

DisplaysProjekte für anfängerRaspberry pi

5 Kommentare

Gerhard Uhlhorn

Gerhard Uhlhorn

Viel interessanter wäre doch einen Zeitserver abzufragen, damit sich die Uhr selbst stellt. Das scheint aber mangels Netzwerk nicht so einfach zu sein (ich suche gerade nach so einer Möglichkeit).

Alternativ könnte man beim starten des Picos vom Rechner oder bei der Scriptübergabe vielleicht die Rechnerzeit übergeben oder bei bestehender USB-Verbindung zum Rechner die Uhrzeit des Rechners auslesen. Dann bräuchte man ihn nur 2x im Jahr kurz mal in den Rechner einstecken und er stellt sich automatisch.

Hmm, ich glaube es wird wohl doch ein Arduino mit Ethernet oder WLAN.

Slyfox

Slyfox

Hallo,
hier ein Codevorschlag für Sommer Winterzeit-berechnung für MEZ/MESZ

Function: Calculates for a given date time if it is summer or winter time (MEZ/MESZ) Methode Find the last Sunday in March and October and substract the number of Days from the end of the month until that last Sunday Input Date & Time (hour,minute,second,day,month,year) Output 0 = Wintertime 1 = Summertime
def Calc_Summer_Winter_Time(hour,minute,second,day,month,year):
HHMarch = utime.mktime((year,3,31,1,0,0,0,0,0)) # int time in sec since
HHMarch2 = utime.localtime(HHMarch) # generate tuple of time
ds = (HHMarch26 + 1)%7 # take the Day of the week 6 (int) of localtime Monday = 0, add one to make Monday the first day of the week ==> Sunday becomes 7 and with modulus 7 Sunday becomes 0
HHMarch = utime.mktime((year,3,31 – ds,1,0,0,0,0,0)) # Substract day of the week from 31 and generate time in sec since for start of summertime HHNovember = utime.mktime((year,10,31,1,0,0,0,0,0)) HHNovember2 = utime.localtime(HHNovember) dw = (HHNovember26 + 1) % 7 HHNovember = utime.mktime((year,10,31-dw,1,0,0,0,0,0)) now=utime.mktime((year,month,day,hour,minute,second,0,0)) if now < HHMarch : dst = 0 elif now < HHNovember : # we are before last sunday of october dst = 1 else: dst = 0 return(dst)
Andreas Wolter

Andreas Wolter

@Slyfox: wir schauen uns das an

@Manfred sen.: dieser Beitrag war gedacht dafür zu zeigen, wie der Pico mit den Komponenten programmiert werden kann.
Es gibt bereits ein ähnliches Projekt mit ESP32 und dem AZ-Touch Mod:
https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/az-touch-mod-als-analoge-uhr-mit-anzeige-fur-sonnenauf-und-untergang

Das zeigt, wie es in der Arduino IDE programmiert werden kann.

Grüße,
Andreas Wolter

Manfred sen.

Manfred sen.

Hallo
Scheint ja ein tolles Projekt zu sein, aber leider muss man wieder eine neue Hardware kaufen und eine neue Programmiersprache lernen.
Gibt es die Möglichkeit das Projekt auch für einen Uno oä. zu schreiben?
Das wäre richtig toll.
Gruß an den Programmierer

Slyfox

Slyfox

Hallo,

nettes Projekt.
Die Sommer/Winterzeit Umschaltung ist hier aber in DST und die Formeln ergeben die amerikanischen Daten welches für die Sommerzeitumstellung der zweite Sonntag im März ist und nicht wie in Europa der letzte Sonntag im März.
USA 14.3.2021 – 7.11.2021 (1. Sonntag im November) Deutschland 28.3 – 31.10 letzter Sonntag im Oktober. Auf die Schnelle keine einfache Umstellung der Formel gefunden die für D passen würde denn der spätestens beim Übergang 2023 23.3. auf 2024 31.3 passt das nicht. Vielleicht habe ich aber auch nur den Wald vor lauter Bäumen nicht gesehen.

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert