MicroPython Driver for PCF8563
Contents
Introduction
This a MicroPython driver for the PCF8563 real time clock breakout board written specifically for the BBC micro:bit.
The PCF8563 is discussed in some detail here. This reference also contains link details to the original driver file.
The driver can be:
- Copied from this webpage onto the clipboard then paste into the MicroPython editor e.g. the Mu Editor. It should be saved as fc_pcf8563.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_pcf8563.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_pcf8563.py file to the computer it should be copied to the small filesystem on the micro:bit. The examples on this page will not work if this step is omitted. Any MicroPython editor that recognises the micro:bit will have an option to do this.
The Mu Editor is recommended for its simplicity. It provides a Files button on the toolbar for just this purpose.
Connecting the PCF8563 module
The PCF8563 communicates via I2C. The micro:bit and the PCF8563 module must be connected as follows:
micro:bit | PCF8563 Module |
---|---|
GND | GND |
3.3V | VCC |
Pin 20 | SDA |
Pin 19 | SCL |
Any spare digital pin | INT |
The INT pin provides an interrupt signal when the alarm conditions are met. The driver provides a separate method that can be called to determine if the alarm has 'fired'. So connecting this pin is optional.
Driver Overview
The driver is implemented as a class. The first thing to do is call the constructor. This provides an instance of the class.
from fc_pcf8563 import *
my_clock = PCF8563()
The constructor has no arguments. It is expected that the PCF8563 pins (SCL, SDA) will be wired to the default I2C pins of the micro:bit as described in the previous section.
The I2C slave address of the PCF8563 is 0x51 and is fixed at the point of manufacture. There are no options on the chip to change this address. It is defined as a constant (I2C_ADDR) at the top of the driver code file.
This assumes that the file fc_pcf8563.py has been successfully copied to the micro:bit's filesystem as described above.
The driver provides methods that:
- Checks the clock status
- Set and read the time (seconds, minutes, hour)
- Set and read the date (weekday, calendar day, month, year)
- Set and monitor the alarm (minutes, hour)
- Set and monitor the countdown timer (seconds)
The PCF8563 RTC only supports 24hr clock time. The user will need to provide a conversion function if 12hr time is required.
Following is a full list of methods that are user callable. Each method is described and example use given in the sections below.
CLOCKstatus() | Sec() | Mins() | Hr() |
Wday() | Day() | Mon() | Yr() |
DateTime() | Time() | Date() | ALARMset() |
ALARMtriggered() | ALARMclear() | ALARMoff() | TIMERset() |
TIMERtriggered() | TIMERclear() | TIMERoff() |
Set and Read the Time
The hour (in 24Hr format), minutes and seconds can be set (and read) with the following methods:
- Sec(sec = None) ; valid values 0 to 59
- Mins(mins = None) ; valid values 0 to 59
- Hr(hr = None) ; valid values 0 to 23
If the method is called with no argument the requested time component (hours, minutes or seconds) is read and returned.
from fc_pcf8563 import *
my_clock = PCF8563()
# Set hours
my_clock.Hr(10)
# Read hours
print('Hour is:', my_clock.Hr())
⇒ Hour is: 10
The following method sets (and reads) hours, minutes and seconds in a single call:
- Time(h=None, m=None, s=None)
This method is very flexible. Any combination of hours, minutes and seconds can be set on the PCF8563.
If the method is called with an empty argument list then the time is returned as a string in the format "HH:MM:SS". This string format is understood by spreadsheet programs such as Microsoft Excel and is useful for exporting logged data for further analysis.
from fc_pcf8563 import *
my_clock = PCF8563()
# Set hours, minutes and seconds
my_clock.Time(h=11, m=9, s=0)
# Read time and return formatted string
print('Time is:', my_clock.Time())
# Set minutes only
my_clock.Time(m=31)
# Read time and return formatted string
print('New time is:', my_clock.Time())
Time is: 11:09:00
New time is: 11:31:00
Set and Read the Date
The year, month, day and weekday can be set (and read) with the following methods:
- Yr(yr = None) ; valid values 0 to 99
- Mon(mon = None) ; valid values 1 to 12
- Day(day = None) ; valid values 1 to 31
- Wday(wday = None) ; valid values 0 to 6
If the method is called with no argument the requested date component (year, month, day or weekday) is read and returned.
from fc_pcf8563 import *
my_clock = PCF8563()
# Set month
my_clock.Mon(9)
# Read month
print('Month is:', my_clock.Mon())
⇒ Month is: 9
The following method sets (and reads) year, month and day in a single call:
- Date(y=None, m=None, d=None, fmt=1)
This method is very flexible. Any combination of year, month and day can be set on the PCF8563.
If the method is called without any arguments then the date is returned as a string in the format defined by the argument fmt.
- fmt = 1 ; "YYYY-MM-DD"
- fmt = 2 ; "DD-MM-YYYY"
- fmt = 3 ; "MM-DD-YYYY"
These string formats are understood by spreadsheet programs such as Microsoft Excel and is useful for exporting logged data for further analysis.
from fc_pcf8563 import *
my_clock = PCF8563()
# Set year, month and day
my_clock.Date(y=24, m=9, d=23)
# Read date and return formatted string
print('Date is:', my_clock.Date(fmt=1))
# Set day only
my_clock.Date(d=20)
# Read date and return formatted string
print('New date is:', my_clock.Date(fmt=2))
Date is: 2024-09-23
New date is: 20-09-2024
The Universal DateTime() Method
The DateTime() method allows all seven date and time parameters to be set (and read) in a single call.
Syntax: DateTime(dat = None) Where: dat : A list containing (in the order given): [Year, Month, Day, Weekday, Hour, Minutes, Seconds] Example: from fc_pcf8563 import * my_clock = PCF8563() # Set date/time 2024-09-23 15:26:00 dt = [24, 9, 23, 1, 15, 30, 0] my_clock.DateTime(dt) print(my_clock.DateTime()) ⇒ [2024, 9, 23, 1, 15, 30, 0]
Check the Clock Status
If there is a voltage supply level issue on the chip then the date and time may not be set or retrieved reliably from the respective registers. The method CLOCKstatus() returns a boolean value indicating whether clock data can be considered reliable or otherwise.
This method can also return False if the time hasn't been set on the clock.
from fc_pcf8563 import *
my_clock = PCF8563()
# Set the time
my_clock.Time(16, 58, 5)
# Check the clock status
good_data = my_clock.CLOCKstatus()
if good_data:
print('Clock status is good')
else:
print('Clock status is bad')
⇒ Clock status is good
Set and Monitor the Alarm
The PCF8563 has a single programable alarm. This driver provides limited but useful functionality.
There are four steps to using the alarm:
- Set the alarm condition(s).
- Monitor the alarm.
- Reset the alarm once it has 'fired'.
- Turn off the alarm when no longer needed.
Set the Alarm Condition(s)
The alarm is set with the ALARMset() method. Three types of conditions can be set. The alarm can be set to trigger:
- On a given minute every hour
- On a given hour every 24hr period.
- Once each 24Hr period at a given time (hour and minutes)
Syntax: ALARMset(mins=None, hr=None) Where: mins : minutes in range 0 to 59 hr : hours in range 0 to 23 Examples: from fc_pcf8563 import * my_clock = PCF8563() # Set alarm to trigger once every hour # when minutes = 30 my_clock.ALARMset(mins=30) # Set alarm to trigger once every 24hr # when hour = 19 my_clock.ALARMset(hr=19) # Set alarm to trigger at 19:30 # (7:30pm) every day my_clock.ALARMset(mins=30, hr=19)
Monitor the Alarm
The alarm triggers when the condition set with the ALARMset() method matches the clock settings. There are two ways to detect if the alarm has "fired":
- Call the ALARMtriggered() method
- Monitor the INT pin which will be pulled LOW on the alarm being triggered
Syntax: ALARMtriggered() Notes: Returns True if the alarm has triggered. Returns False if the alarm is not in a triggered state. Example: from fc_pcf8563 import * from microbit import sleep my_clock = PCF8563() # Set the clock time my_clock.Time(h=19, m=28) print('Time is:', my_clock.Time()) # Set alarm to trigger when minutes=30 print('Setting the alarm: minutes=30') my_clock.ALARMset(mins=30) # Wait till the alarm has triggered while not my_clock.ALARMtriggered(): sleep(500) print('Ding Dong') print('Time is:', my_clock.Time()) Time is: 19:28:25 Setting the alarm: minutes=30 Ding Dong Time is: 19:30:00
Resetting the Alarm
When the conditions are met and the alarm has triggered, it must be reset. If the reset is not done then the alarm will remain in a triggered state. When the triggered alarm is reset the PCF8563 will continue to monitor the alarm conditions. It will "fire" again when the alarm conditions match the clock values.
The ALARMclear() method will clear a triggered alarm. It is not necessary to call this method if the ALARMtriggered() method is being used to monitor the alarm. A triggered alarm is automatically reset by the ALARMtriggered() method.
If the alarm is being monitored by the INT interrupt pin then it is necessary to call ALARMclear() after the alarm is triggered, else the next alarm trigger won't be detected.
Turning the Alarm Off
After the alarm has been set with the ALARMset() method it will remain active until the pcf8563 loses power or the alarm is programmatically turned off. The alarm is turned off with the ALARMoff() method.
Example:
from fc_pcf8563 import *
my_clock = PCF8563()
# Set the alarm
my_clock.ALARMset(mins=30)
# Turn the alarm off
my_clock.ALARMoff()
Set and Monitor the Countdown Timer
The PCF8563 has a countdown timer - a feature not often found on other popular real time clock chips. The timer is given a value in the range of 1 to 255 seconds. When initiated, it will count down in seconds till 0 is reached.
The initial value will be reloaded and the count down repeated. This sequence is repeated till the timer is turned off or the chip is powered down.
Setting the Timer
The TIMERset() method passes an initial countdown value to the timer and initiates the countdown.
Monitor the Timer Countdown
When the countdown has reached 0 then a timer flag is set by the PCF8563. The set flag is detected by calling the method TIMERtriggered(). The completion of the count down can also be detected by monitoring the INT pin which will be pulled LOW.
Resetting the Timer Flag
The timer flag must be reset after a count down has completed otherwise the next event won't be captured. The TIMERtriggered() method automatically resets the flag at the end of a count down. However if the count down is being monitored with the INT pin then the timer flag must be reset with the TIMERreset() method.
Turning Off the Timer
The timer is disabled by calling the TIMERoff() method.
Timer Methods TIMERset(s) s: The number of seconds to count down from. Must be an integer in the range 1 to 255. TIMERtriggered() Returns True if the timer has reached 0 seconds, otherwise False. TIMERreset() Resets the timer flag if the count down has reached 0 seconds. Only needs to be called if the count down is being monitored with the INT pin. TIMERoff() Disables the timer. Example # Test timer function on PCF8563 RTC from fc_pcf8563 import * from microbit import sleep clock = PCF8563() # Set the timer print('Set timer = 3 sec') clock.TIMERset(3) count = 0 print('Time is:', clock.Time()) # Allow the timer to count down five times. while count < 5: if clock.TIMERtriggered(): count += 1 print('Timer triggered:', count, 'times') sleep(500) print('Time is:', clock.Time()) # Turn timer off. This saves power. clock.TIMERoff() print('Timer is turned off') Set timer = 3 sec Time is: 19:31:44 Timer triggered: 1 times Timer triggered: 2 times Timer triggered: 3 times Timer triggered: 4 times Timer triggered: 5 times Time is: 19:31:59 Timer is turned off
Examples
The following code can be used to set the time and date on the PCF8563. Make the obvious edits to add your current date and time. Flash the program to the micro:bit.
Example 1
# Set time and date on PCF8563
from fc_pcf8563 import *
# Instantiate a PCF8563 RTC object.
pcf8563 = PCF8563()
# Set the clock's date and time.
pcf8563.DateTime([24, 9, 30, 1, 9, 42, 5])
# Check that the date & time
# have been correctly set.
print('Date:', pcf8563.Date())
print('Time:', pcf8563.Time())
Output: Date: 2024-09-30 Time: 09:42:05
The meaning of values in the list passed to the method DateTime() are fairly obvious: year=2024, month=9, day=30, weekday=1 (Monday), hour=9, minutes=42, seconds=5.
The next example demonstrates some of the methods that might be used after the date and time has been set on the PCF8563.
Copy and flash this code to the micro:bit. The program takes six minutes to run to completion.
Example 2
# Test alarm methods on PCF8563 RTC
from fc_pcf8563 import *
from microbit import sleep
clock = PCF8563()
clock.DateTime([24, 9, 30, 1, 15, 56, 5])
# Test minutes alarm
print('Set alarm: mins=58')
clock.ALARMset(mins=58)
finished = False
print('Time is:', clock.Time())
while not finished:
if clock.ALARMtriggered():
finished = True
print('Alarm triggered')
sleep(500)
print('Time is:', clock.Time())
# Test hours alarm
print('\nSet alarm: hr=16')
clock.ALARMset(hr=16)
finished = False
print('Time is:', clock.Time())
while not finished:
if clock.ALARMtriggered():
finished = True
print('Alarm triggered')
sleep(500)
print('Time is:', clock.Time())
# Test hour & mins alarm
print('\nSet alarm: hr=16, mins=2')
clock.ALARMset(hr=16, mins=2)
finished = False
print('Time is:', clock.Time())
while not finished:
if clock.ALARMtriggered():
finished = True
print('Alarm triggered')
sleep(500)
print('Time is:', clock.Time())
Output: Set alarm: mins=58 Time is: 15:56:05 Alarm triggered Time is: 15:58:00 Set alarm: hr=16 Time is: 15:58:00 Alarm triggered Time is: 16:00:00 Set alarm: hr=16, mins=2 Time is: 16:00:00 Alarm triggered Time is: 16:02:00
Enjoy!
PCF8563 Driver Code for micro:bit
Download as zip file
'''
PCF8563 Real Time Clock Driver
for the BBC micro:bit
Based on DS3231 driver by: shaoziyang
Date: 2018.2
http://www.micropython.org.cn
Modified & Extended by fredscave.com
DATE: 2024.9
'''
from microbit import i2c
from micropython import const
I2C_ADDR = const(0x51)
REG_CTRL2 = const(0x01)
REG_SEC = const(0x02)
REG_MIN = const(0x03)
REG_HR = const(0x04)
REG_DAY = const(0x05)
REG_WDAY = const(0x06)
REG_MON = const(0x07)
REG_YR = const(0x08)
REG_ALMIN = const(0x09)
REG_ALHR = const(0x0A)
REG_ALDAY = const(0x0B)
REG_ALWDAY = const(0x0C)
REG_TIMER = const(0x0F)
REG_TIMER_CTRL = const(0x0E)
class PCF8563():
def __init__(self):
self.setReg(REG_CTRL2, 0b00000011)
def DecToHex(self, dat):
return (dat//10) * 16 + (dat%10)
def HexToDec(self, dat):
return (dat//16) * 10 + (dat%16)
def setReg(self, reg, dat):
i2c.write(I2C_ADDR, bytearray([reg, dat]))
def getReg(self, reg):
i2c.write(I2C_ADDR, bytearray([reg]))
return i2c.read(I2C_ADDR, 1)[0]
def CLOCKstatus(self):
if (self.getReg(REG_SEC) >> 7) == 0:
return True
else:
return False
def Sec(self, sec = None):
if sec == None:
return self.HexToDec(self.getReg(REG_SEC) & 0b01111111)
else:
self.setReg(REG_SEC, self.DecToHex(sec%60))
def Mins(self, mins = None):
if mins == None:
return self.HexToDec(self.getReg(REG_MIN) & 0b01111111)
else:
self.setReg(REG_MIN, self.DecToHex(mins%60))
def Hr(self, hr = None):
if hr == None:
return self.HexToDec(self.getReg(REG_HR) & 0b00111111)
else:
self.setReg(REG_HR, self.DecToHex(hr%24))
def Wday(self, wday = None):
if wday == None:
return self.HexToDec(self.getReg(REG_WDAY) & 0b00000111)
else:
self.setReg(REG_WDAY, self.DecToHex(wday%7))
def Day(self, day = None):
if day == None:
return self.HexToDec(self.getReg(REG_DAY) & 0b00111111)
else:
self.setReg(REG_DAY, self.DecToHex(day%32))
def Mon(self, mon = None):
if mon == None:
return self.HexToDec(self.getReg(REG_MON) & 0b00011111)
else:
self.setReg(REG_MON, self.DecToHex(mon%13))
def Yr(self, yr = None):
if yr == None:
return self.HexToDec(self.getReg(REG_YR)) + 2000
else:
self.setReg(REG_YR, self.DecToHex(yr%100))
def DateTime(self, dat = None):
if dat == None:
return [self.Yr(), self.Mon(),
self.Day(),self.Wday(),
self.Hr(), self.Mins(), self.Sec()]
else:
self.Yr(dat[0])
self.Mon(dat[1])
self.Day(dat[2])
self.Wday(dat[3])
self.Hr(dat[4])
self.Mins(dat[5])
self.Sec(dat[6])
def Time(self, h=None, m=None, s=None):
if (h==None) and (m==None) and (s==None):
str_sec = "%02d" % self.Sec()
str_min = "%02d" % self.Mins()
str_hr = "%02d" % self.Hr()
return str_hr + ':' + str_min + ':' + str_sec
if (s != None):
self.Sec(s)
if (m != None):
self.Mins(m)
if (h != None):
self.Hr(h)
def Date(self, y=None, m=None, d=None, fmt=1):
# fmt = 1; YMD
# fmt = 2; DMY
# fmt = 3; MDY
if (y==None) and (m==None) and (d==None):
str_day = "%02d" % self.Day()
str_mon = "%02d" % self.Mon()
str_yr = "%04d" % self.Yr()
if fmt == 1:
return str_yr + '-' + str_mon + '-' + str_day
if fmt == 2:
return str_day + '-' + str_mon + '-' + str_yr
if fmt == 3:
return str_mon + '-' + str_day + '-' + str_yr
return None
if (d != None):
self.Day(d)
if (m != None):
self.Mon(m)
if (y != None):
self.Yr(y)
def ALARMset(self, mins=None, hr=None):
if (mins == None) and (hr == None):
return
self.ALARMclear()
self.ALARMoff()
if mins != None:
self.setReg(REG_ALMIN, self.DecToHex(mins%60))
if hr != None:
self.setReg(REG_ALHR, self.DecToHex(hr%24))
def ALARMclear(self):
reg = self.getReg(REG_CTRL2)
self.setReg(REG_CTRL2, reg & 0b00000111)
def ALARMtriggered(self):
trig = (self.getReg(REG_CTRL2) & 0b00001000) >> 3
if trig:
self.TIMERclear()
return trig
def ALARMoff(self):
self.setReg(REG_ALMIN, 0b10000000)
self.setReg(REG_ALHR, 0b10000000)
self.setReg(REG_ALDAY, 0b10000000)
self.setReg(REG_ALWDAY, 0b10000000)
def TIMERset(self, s=None):
if s == None:
return
self.TIMERoff()
self.setReg(REG_TIMER, s)
self.setReg(REG_TIMER_CTRL, 0b10000010)
def TIMERtriggered(self):
trig = (self.getReg(REG_CTRL2) & 0b00000100) >> 2
if trig:
self.TIMERclear()
return trig
def TIMERoff(self):
self.setReg(REG_TIMER_CTRL, 0b00000011)
self.setReg(REG_TIMER, 0x00)
self.TIMERclear()
def TIMERclear(self):
reg = self.getReg(REG_CTRL2)
self.setReg(REG_CTRL2, reg & 0b00001011)