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:
- Eine RealTimeClock Typ DS3231 am Pico Inbetriebnahme
- 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
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
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
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 Gimmick. 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
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.
8 comments
Peter Nilsson
Hi, I can’t get it to work with the code for
“DS3231-RTC via I2C and write output to terminal and OLED”
I get this output in Thonny:
>>> %run -c $EDITOR_CONTENT
Traceback (most recent call last):
File “”, line 29, in
File “/lib/oled/ssd1306.py”, line 127, in init
File “/lib/oled/ssd1306.py”, line 47, in init
File “/lib/oled/ssd1306.py”, line 74, in init_display
File “/lib/oled/ssd1306.py”, line 99, in show
File “/lib/oled/ssd1306.py”, line 137, in write_framebuf
OSError: [Errno 110] ETIMEDOUT
This is line 29: oled = SSD1306_I2C(WIDTH, HIGHT, i2c)
I guess that the firs error has to be fixed first.
It worked with the code for
“Read/Write a DS3231-RTC via I2C and write output to terminal”
Ralf Kastmann
Hallo allerseits.
Eine runde Sache wäre es noch, wenn man z.B. ein DCF77-Empfangsmodul hinzufügen würde, welches einmal pro Tag die Uhr synchronisiert. Dann entfällt auch die mühsame Berechnung von Sommer/Winterzeit. Und noch ein Hinweis zum Display: ein TFT-Display ist m.A. nach besser geeignet, da das OLED-Display schon nach recht kurzer Zeit ein Einbrennverhalten zeigt. Das bedeutet, dass alle konstant aktivierten Pixel ihre Leuchtkraft verlieren.
Und mit einem TFT-Display kann man noch ein weinig mit Farben das Gesamtbild auffrischen.
Freundliche Grüsse
Konrad
Hallo,
wo kann ich die urtc Lib gerunterladen?
Mit sonnigen Grüßen,
Konrad
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
Hallo,
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 = Summertimehier ein Codevorschlag für Sommer Winterzeit-berechnung für MEZ/MESZ
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
@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.
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
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.