MicroPython Driver for DS3231
Contents
Introduction
This a MicroPython driver for the DS3231 real time clock breakout board written specifically for the BBC micro:bit.
The DS3231 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_ds3231.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_ds3231.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_ds3231.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 DS3231 module
The DS3231 communicates via I2C. The micro:bit and the DS3231 module must be connected as follows:
micro:bit | DS3231 Module |
---|---|
GND | GND |
3.3V | VCC |
Pin 20 | SDA |
Pin 19 | SCL |
Any spare digital pin | SQW |
The SQW 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_ds3231 import *
my_clock = DS3231()
The constructor has no arguments. It is expected that the DS3231 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 DS3231 is 0x68 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_ds3231.py has been successfully copied to the micro:bit's filesystem as described above.
The driver provides methods that:
- Set and read the time (seconds, minutes, hour)
- Set and read the date (weekday, calendar day, month, year)
- Set and monitor an alarm (minutes, hour)
- Read the ambient temperature
While the DS3231 RTC supports both 12hr clock time and 24hr clock time this driver only supports 24hr time.
Following is a full list of methods that are user callable. Each method is described and example use given in the sections below.
Sec() | Mins() | Hr() | Wday() |
Day() | Mon() | Yr() | DateTime() |
Time() | Date() | ALARMset() | Alarmclear() |
ALARMtriggered() | Temperature() |
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_ds3231 import *
my_clock = DS3231()
# 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 DS3231.
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_ds3231 import *
my_clock = DS3231()
# 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 1 to 7
If the method is called with no argument the requested date component (year, month, day or weekday) is read and returned.
from fc_ds3231 import *
my_clock = DS3231()
# 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 DS3231.
If the method is called without providing year, month and day 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_ds3231 import *
my_clock = DS3231()
# 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_ds3231 import * my_clock = DS3231() # 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]
Set and Monitor an Alarm
The DS3231 has two separately programable alarms: Alarm 1 and Alarm 2. This driver provides limited functionality using Alarm 2.
There are three steps to using the alarm:
- Set the alarm condition(s).
- Monitor the alarm.
- Reset the alarm once it has 'fired'.
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:
- Once every minute
- Once each hour on a given minute
- 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_ds3231 import * my_clock = DS3231() # Set alarm to trigger once every minute my_clock.ALARMset() # Set alarm to trigger once every hour when minutes = 30 my_clock.ALARMset(mins=30) # Set alarm to trigger at 19:25 (7:25pm) every day my_clock.ALARMset(mins=25, 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 SQW 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_ds3231 import * from microbit import sleep my_clock = DS3231() # Set alarm to trigger once every minute my_clock.ALARMset() print('Start time:', my_clock.Time()) # Allow the alarm to trigger 3 times. for i in range(3): # Wait till the alarm has triggered while not my_clock.ALARMtriggered(): sleep(500) print('Ding Dong') print('Time now is:', my_clock.Time()) Start time: 19:29:48 Ding Dong Time now is: 19:30:00 Ding Dong Time now is: 19:31:00 Ding Dong Time now is: 19:32:00
Notice from the example above that the "once every minute" alarm actually triggers each time the clock seconds reach 0 i.e. the instance that the clock minutes increment.
Resetting the Alarm
When the conditions are met and the alarm trigger, 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 it 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 SQW interrupt pin then it is necessary to call ALARMclear() after the alarm is triggered, else the next alarm trigger won't be detected.
Read the Ambient Temperature
The DS3231 has an in-chip temperature sensor that is used for compensating the internal crystal oscillator circuit. This driver provides the user with the method Temperature() to return a good approximation of the ambient air temperature in degrees Celsius.
from fc_ds3231 import *
my_clock = DS3231()
# Read and report the temperature
print('Temperature is:', my_clock.Temperature(), 'C')
⇒ Temperature is: 23.5 C
Examples
The following code can be used to set the current time and date on the DS3231. 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 DS3231
from fc_ds3231 import *
# Instantiate a DS3231 RTC object.
ds3231 = DS3231()
# Set the clock's date and time.
ds3231.DateTime([24, 9, 30, 1, 9, 42, 5])
# Check that the date & time
# have been correctly set.
print('Date:', ds3231.Date())
print('Time:', ds3231.Time())
Output: Date: 2024-09-30 Time: 09:42:05
The meaning of values in the list used for setting the clock date and time are fairly obvious: year=2024, month=9, day=23, weekday=1 (Monday), hour=9, minutes=42, seconds=5.
The next example completes the alarm tests. It triggers the alarm on two separate alarm settings:
- When the clock minutes = 58
- When the clock time = 16:01
Example 2
# Test alarm methods on DS3231 RTC
from fc_ds3231 import *
from microbit import sleep
clock = DS3231()
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 hour & mins alarm
print('\nSet alarm: hr=16, mins=1')
clock.ALARMset(hr=16, mins=1)
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, mins=1 Time is: 15:58:00 Alarm triggered Time is: 16:01:00
Enjoy!
DS3231 Driver Code for micro:bit
Download as zip file
'''
DS3231 Real Time Clock Driver
for the BBC micro:bit
Original author: shaoziyang
Date: 2018.2
http://www.micropython.org.cn
Extended by fredscave.com
DATE: 2024.9
'''
from microbit import i2c
from micropython import const
I2C_ADDR = const(0x68)
REG_SEC = const(0x00)
REG_MIN = const(0x01)
REG_HR = const(0x02)
REG_WDAY = const(0x03)
REG_DAY = const(0x04)
REG_MON = const(0x05)
REG_YR = const(0x06)
REG_A2MIN = const(0x0B)
REG_A2HR = const(0x0C)
REG_A2DAY = const(0x0D)
REG_CTRL = const(0x0E)
REG_STA = const(0x0F)
REG_TEMP = const(0x11)
class DS3231():
def __init__(self):
self.setReg(REG_CTRL, 0x4C)
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 Sec(self, sec = None):
if sec == None:
return self.HexToDec(self.getReg(REG_SEC))
else:
self.setReg(REG_SEC, self.DecToHex(sec%60))
def Mins(self, mins = None):
if mins == None:
return self.HexToDec(self.getReg(REG_MIN))
else:
self.setReg(REG_MIN, self.DecToHex(mins%60))
def Hr(self, hr = None):
if hr == None:
return self.HexToDec(self.getReg(REG_HR))
else:
self.setReg(REG_HR, self.DecToHex(hr%24))
def Wday(self, wday = None):
if wday == None:
return self.HexToDec(self.getReg(REG_WDAY))
else:
self.setReg(REG_WDAY, self.DecToHex(wday%8))
def Day(self, day = None):
if day == None:
return self.HexToDec(self.getReg(REG_DAY))
else:
self.setReg(REG_DAY, self.DecToHex(day%32))
def Mon(self, mon = None):
if mon == None:
return self.HexToDec(self.getReg(REG_MON))
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):
m4 = self.getReg(REG_A2DAY)
m3 = self.getReg(REG_A2HR)
m2 = self.getReg(REG_A2MIN)
self.setReg(REG_A2DAY, m4 | 0b10000000)
cr = self.getReg(REG_CTRL)
self.setReg(REG_CTRL, cr | 0b00000110)
if (mins==None) and (hr==None):
self.setReg(REG_A2HR, m3 | 0b10000000)
self.setReg(REG_A2MIN, m2 | 0b10000000)
elif (hr==None):
self.setReg(REG_A2HR, m3 | 0b10000000)
self.setReg(REG_A2MIN, self.DecToHex(mins%60) & 0b01111111)
else:
self.setReg(REG_A2MIN, self.DecToHex(mins%60) & 0b01111111)
self.setReg(REG_A2HR, self.DecToHex(hr%24) & 0b01111111)
def ALARMclear(self):
status = self.getReg(REG_STA)
self.setReg(REG_STA, status & 0b11111101)
def ALARMtriggered(self):
status = self.getReg(REG_STA)
trig = (status & 0b00000010)
if trig:
self.ALARMclear()
return trig
def Temperature(self):
t1 = self.getReg(REG_TEMP)
t2 = self.getReg(REG_TEMP + 1)
if t1>0x7F:
return t1 - t2/256 -256
else:
return t1 + t2/256