Comme le Raspberry Pi Pico est encore assez nouveau pour moi et que j'ai besoin de rafraîchir mes connaissances sur Python, j'utilise toujours de petits "projets standard" pour me familiariser avec un nouveau matériel ou logiciel. Vous l'avez déjà vu dans mon dernier blog Pico comme station météo et j'aimerais continuer ici en réalisant un horloge analogique à l'aide d'un RealtTimeClock et d'un écran OLED.
Je partage le blog en deux parties :
- Un RealtTimeClock de type DS3231 sur le Pico Mise en service
- Afficher l'heure de RealTimeClock sur l'écran OLED
L'horloge devrait pouvoir distinguer simultanément l'heure d'été et l'heure d'hiver. Un composant RTC est nécessaire, car le Raspberry Pi Pico ne possède pas de RTC interne.
Matériel et logiciel nécessaires
Le matériel nécessaire pour ce montage expérimental est simple, voir tableau 1.
Nombre | Composant matériel | Note |
---|---|---|
1 | Raspberry Pi Pico | |
1 | 0,96 pouce OLED I2C Display 128 x 64 | |
1 | Breadboard Kit - 3x Jumper Wire | |
1 | Horloge en temps réel RTC DS3231 I2C Alternativement: DS1302 RTC |
Tableau 1 : composants matériels pour horloge analogique
Pour le logiciel, comme il s'agira d'un programme avec MicroPython, vous utiliserez Thonny Python IDE, qui est déjà disponible avec l'image Raspbian et peut être installé pour tous les systèmes d'exploitation courants.
De plus, une fois l'IDE Thonny Python installé et le Pico connecté, vous aurez besoin des bibliothèques urtc et microypthon-oled, qui seront ensuite transférées sur le Pico. Pour savoir comment installer les bibliothèques, consultez également l'article sur la station météo Pico.
Mettre en service RTC DS3231
C'est justement lorsque vous vous procurez un nouveau RTC DS3231 dans notre boutique ou lorsque la pile du RTC est déchargée que le RTC DS3231 doit être réglé à nouveau. C'est ce que nous allons faire dans la première partie de ce blog. Pour cela, il faut d'abord câbler le Raspberry Pi Pico avec le RCT DS3231, voir figure 1.
Figure 1 : Câblage RTC DS3231 avec Pico
Pour l'ensemble du montage, il faut en fait quatre fils et les deux composants mentionnés précédemment. Ensuite, si vous avez déjà installé Thonny Python IDE, connectez le Raspberry Pi Pico au PC ou au Raspberry Pi et téléchargez les bibliothèques nécessaires. A ce stade, je suppose que le firmware est présent sur le Pico. Téléchargez ensuite le code 1 sur le Pico. Pour savoir comment installer le firmware, consultez cette Quickstart Guide.
"""
// 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 : Réglage du RTC DS3231
Pour que le RTC DS3231 soit réglé, il faut supprimer les """ précédents dans les lignes """#SET TIME FROM RTC et """#End settime et entrer une nouvelle date et une nouvelle heure. Weekday est un peu déroutant à cet endroit, car le format d'heure américain est utilisé ici et la semaine commence par dimanche. Le dimanche est donc le 1 et le mercredi le 4 dans l'exemple. Lors de l'exécution du script, l'horloge RTC DS3231 est réinitialisée. Immédiatement après, l'heure et la date actuelles du RTC s'affichent toutes les secondes sur la ligne de commande, voir figure 2.
Figure 2 : Date et heure actuelles de l'horloge RTC DS3231.
Si vous redémarrez le Pico, dans le cas actuel, l'heure sera à nouveau réinitialisée, il faut donc remettre les """ aux endroits appropriés et laisser le code se charger.
Tant que la pile bouton est insérée dans la RTC DS 3231 et qu'elle n'est pas vide, l'horloge continue sa marche.
Horloge analogique avec OLED et RTC DS3231
Nous en arrivons maintenant au projet proprement dit, l'horloge analogique sur l'écran OLED avec toutes les autres indications de temps. Le câblage, puisqu'il ne s'agit en fait que d'un composant supplémentaire, n'est pas devenu beaucoup plus compliqué, voir figure 3.
Figure 3 : Câblage de l'écran OLED et de l'horloge RTC DS3231 avec Pico
Comme le RTC DS3231 et l'écran OLED communiquent tous deux avec i2c, mais ont des adresses différentes, ils peuvent être placés sur une seule ligne de bus. Une fois les travaux de câblage terminés, vous pouvez transférer le code 2 sur le Pico.
"""
// 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 : Horloge analogique avec OLED et RTC DS3231.
J'aimerais expliquer brièvement ce code source à quelques reprises. Tout d'abord, j'ai ajouté un indicateur de diag dans mon code source bPrintDiag. Comme l'heure complète est affichée toutes les secondes dans le terminal, le Pico marche un peu au ralenti. Comme les sorties de diag ne sont nécessaires que pour le débogage, j'ai fait en sorte qu'elles soient activées ou désactivées. Vient ensuite la définition de la fonction Calc_Summer_Winter_Time, qui vérifie si l'on est en heure d'hiver ou d'été. Celle-ci renvoie le décalage de l'heure, qui est utilisé dans la boucle while comme correction de l'heure RTC DS3231. De plus, pour que le code reste plus clair, j'ai déplacé le dessin de l'horloge analogique et l'écriture du texte sur l'écran OLED dans les fonctions Draw_Clock et Print_Date_Time. La première fonction n'est appelée que s'il y a eu un changement dans les minutes, la seconde s'il y a eu un changement dans les secondes.
Draw_Clock contient les mathématiques permettant de dessiner les aiguilles et le visage de la montre. Comme je suis fan du fait que tout marche exactement comme sur une vraie montre analogique, le comportement de l'aiguille des heures est également reproduit correctement. Dans le code source initial, l'aiguille des heures était figée sur l'heure actuelle. Dans une vraie montre analogique, ce n'est pas le cas, mais elle se déplace petit à petit vers l'heure suivante. J'ai réalisé cela en calculant l'angle pour l'aiguille des heures avec la minute actuelle. Comme il manque quelques fonctions de dessin dans la bibliothèque standard pour l'OLED, j'utilise la bibliothèque GFX pour dessiner. Celle-ci appelle toujours la fonction du pixel de l'objet oled pour dessiner. La définition se trouve au début du code source, voir après le commentaire #Definitions and init for OLED-Display.
Print_Date_Time écrit alors aussi bien l'heure que la date sur le côté droit de l'écran OLED. Comme nous l'avons déjà mentionné, cela se fait toutes les secondes. Mais il y a encore un petit problème. La police standard de l'OLED est assez grande, c'est pourquoi il existe un objet font, qui est créé au début du texte source. Celui-ci charge la police ubuntu_mono_12, qui est fournie dans la bibliothèque microypthon-oled.
La boucle While à la fin du code source fait le reste. Elle se charge des vérifications correspondantes. Le résultat est présenté dans la figure 4.
Figure 4 : La date et l'heure actuelles avec le Pico
Résumé
Le Pico est encore très récent dans notre gamme de produits, ce qui permet de reconstruire de nombreux "vieux" projets. En ce qui concerne les microcontrôleurs à petit prix, le Pico est une véritable alternative à un Nano V3.0 ou à une carte microcontrôleur ATmega328. La différence n'est "que de 2 euros", mais le principe du Raspberry Pi Pico n'a rien à envier à son modèle.
Personnellement, je suis de plus en plus fasciné par le Pico, notamment grâce au langage de script MicroPython. Il est vrai que les bibliothèques pour le Pico ne sont pas encore aussi complètes que celles de l'IDE Arduino, mais il faut garder à l'esprit que le Raspberry Pi Pico n'a été présenté que le 21 janvier 2021 et qu'une distribution mondiale du matériel a également pris du temps. Le Nano V3.0 ou la carte microcontrôleur ATmega328 sont sur le marché depuis bien plus longtemps et, par conséquent, il s'est déjà passé plus de choses dans le domaine du développement de bibliothèques.
Pour personnaliser encore plus le projet, vous pouvez par exemple ajouter une aiguille des secondes sur le cadran de la montre, ce qui implique de le redessiner toutes les secondes. Vous pouvez également modifier les aiguilles avec un peu de mathématiques, comme vous le souhaitez.
Remarque : si vous utilisez le module RTC DS1302 au lieu du module RTC DS3231 I2C, vous pouvez essayer cette bibliothèque de programmes.
Vous trouverez d'autres projets pour AZ-Delivery de ma part sous https://github.com/M3taKn1ght/Blog-Repo
8 commentaires
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.