Raspberry Pi Pico als analoge Uhr

Since the Raspberry Pi Pico is still quite new to me and I need to refresh my knowledge of Python, I always use small "standard projects" to help me get to grips with new hardware or software. You have already seen this in my last blog Pico as a weather station and I would like to continue here by building an analogue clock using a RealtTimeClock and an OLED display.

I split the blog into two parts:

  1. One RealtTimeClock type DS3231 on the Pico Commissioning
  2. Show the time of the RealTimeClock on OLED display

The clock should be able to simultaneously distinguish between summer and winter time. An RTC component is required, as the Raspberry Pi Pico does not have an internal RTC.

Hardware and software required

The hardware for this experimental setup is simple, see Table 1.

Number Hardware component Annotation
1 Raspberry Pi Pico
1 0.96 inch OLED display I2C SSD1306
1 Breadboard and Jump Wire
1 Real Time Clock RTC DS3231 I2C Real Time Clock

Alternatively: DS1302 RTC

Table 1: Hardware parts for analogue clock

For the software, since it will be a programme with MicroPython, use Thonny Python IDE, which is already available with the Raspbian image and can be installed for all common operating systems.

In addition, once the Thonny Python IDE is installed and the Pico is connected, you need the urtc and microypthon-oled libraries, which are then transferred to the Pico. You can also find out how to install libraries in the article on the Pico weather station.

Putting the RTC DS3231 into operation

Once you get a new RTC DS3231 from our shop or if the RTC battery should have become empty, then the RTC DS3231 needs to be reset. This is covered in our first part of this blog. To do this, you first need to wire the Raspberry Pi Pico to the RCT DS3231, see Figure 1.

Figure 1: Wiring RTC DS3231 with Pico

Figure 1: Wiring RTC DS3231 with Pico

The whole setup basically requires four wires and the two previously mentioned components. Then, if you already have Thonny Python IDE installed, connect the Raspberry Pi Pico to the PC or the Raspberry Pi and download the required libraries. At this point I assume that the firmware is present on the Pico. Then download code 1 to the Pico. You can read how to install the firmware in this 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_PORTsda=I2C_SDAscl=I2C_SCLfreq=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: Setting the RTC DS3231

To ensure that the RTC DS3231 is also set, you must delete the preceding """ in the line """#SET TIME FROM RTC and """#End settime and enter a new date and time. Weekday is a little confusing at this point because is used here the American time format and the week starts with the Sunday day. Therefore, Sunday is 1 and Wednesday is 4 in the example. When the script is executed, the RTC DS3231 is reset. Immediately afterwards, you will see the current time and date of the RTC every second in the command line, see Figure 2.

Figure 2: Current date and time of the RTC DS3231

Figure 2: Current date and time of the RTC DS3231

Start the Pico again, then in the current case the time will be reset again, so you should insert the """ again in the appropriate places and let the code upload.

As long as the button cell is inserted in the RTC DS 3231 and is not empty, the clock will continue to run.

Analogue clock with OLED and RTC DS3231

Here comes the real project, the analogue clock on the OLED display with all the other time information. The wiring, since basically only one more component has been added, has not become much more complicated, see figure 3.

Figure 3: Wiring OLED display and RTC DS3231 with Pico

Figure 3: Wiring OLED display and RTC DS3231 with Pico

Since the RTC DS3231 and the OLED display both communicate with i2c but have different addresses, they can be put on one bus line. Once the wiring work is done, you can transfer code 2 to the 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_12ubuntu_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_PORTsda=I2C_SDAscl=I2C_SCLfreq=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(WIDTHHIGHTi2c)
 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---'xPosyPos)
     yPos+=yShift
     font.text(f'{day:02d}' + '/' + f'{month:02d}' + '/' + f'{year:02d}'xPosyPos)
     yPos+=yShift
     font.text(f'{dayName:^10}'xPosyPos#Dayname in center
     yPos+=yShift
     font.text('---Time---'xPosyPos)
     yPos+=yShift
     font.text(' ' + f'{hour:02d}' + ':' + f'{minute:02d}' + ':' + f'{second:02d}'xPosyPos)
     
     
 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(05128151)
 Intro.text("Analog Clock"205bgcolor=1color=0)
 Intro.text("(c) Joern Weise"1025)
 Intro.text("for Az-Delivery"1045)
 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: Analogue clock with OLED and RTC DS3231

I would like to explain this source code briefly in a few points. First, I added a diag flag to my source code bPrintDiag. Since the complete time is printed every second in the terminal, the pico runs a bit sluggish with it. Since the diag outputs are only needed for debugging, I made them switchable on and off. This is followed by the definition of the function Calc_Summer_Winter_Time, which checks whether it is winter or summer time. This returns the shift of the hour, which is used in the while loop as a correction to the RTC DS3231 hour. In addition, to keep the code clearer, I have outsourced the drawing of the analogue clock and the writing of the text on the OLED display to the functions Draw_Clock and Print_Date_Time. The first function is only called if there has been a change in the minutes, the latter if there has been a change in the seconds.

Draw_Clock includes the mathematics to draw the hands and the clock face. Since I am a fan of everything working exactly like a real analogue clock, the behaviour of the hour hand is also correctly reproduced. In the initial source code, the hour hand was rigid on the current hour. This is not the case with a real analogue clock, but moves bit by bit to the next hour. I realised this by calculating the angle for the hour hand with the current minute. Since some drawing functions are missing in the standard library for the OLED, I use the GFX library for drawing. This always calls the function from the pixel of oled object for drawing. The definition can be found at the beginning of the source code, see behind the comment #Definitions and init for OLED-Display.

Print_Date_Time then writes both the time and the date to the right side of the OLED display. As already mentioned, this is done every second. Nevertheless, there is still a small gimmick. The default font of the OLED is quite large, so there is the object font, which is created at the beginning of the source code. This loads the font ubuntu_mono_12, which is supplied in the library microypthon-oled.

The While loop at the end of the source code then does the rest. It takes over the corresponding checks. You can see the result in Figure 4.

Figure 4: The current date and the current time with the Pico

Figure 4: The current date and time with the Pico

Summary

The Pico is still quite new in our range, so many "old" projects can be rebuilt. Especially in terms of microcontrollers for little money, the Pico is a real alternative to a Nano V3.0 or microcontroller board ATmega328. The difference is "only" 2 euros, but the Raspberry Pi Pico is in no way inferior to the original.

Personally, I am more and more enthusiastic about the Pico, not least because of the MicroPython scripting language. The libraries for the Pico are not yet as extensive as those for the Arduino IDE, but you have to bear in mind that the Raspberry Pi Pico was only introduced on 21 January 2021 and it took a long time for the hardware to be distributed worldwide. The Nano V3.0 or the ATmega328 microcontroller board have been on the market much longer and accordingly more has already happened in the area of library development.

To personalise the project even further, you could, for example, add a second hand on the clock face, but this will also have to be redrawn every second. You can also further modify the hands with a bit of mathematics, as you wish.

Note: if you are using the DS1302 Serial Real Time Clock RTC Module instead of the RTC DS3231 I2C Real Time Clock Module, you can try this program library.

You can find more projects for Az-Delivery by me at https://github.com/M3taKn1ght/Blog-Repo

DisplaysProjects for beginnersRaspberry pi

4 comments

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.

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. Install ESP32 now from the board manager
  2. Lüftersteuerung Raspberry Pi
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1
  4. ESP32 - das Multitalent
  5. OTA - Over the Air - ESP programming via WLAN