Rasperry Pi Connected to DS3231

The Maxim DS3231 is an accurate, yet relatively expensive real time clock (RTC). It has an integrated temperature compensated crystal and communicates over I2C. More information can be found from the dataset. At the time of this article the chip alone cost around $8.7 in quantities of 1. I acquired a couple to use with another project. I didn't account for the fact that the footprint is the wider variation on SOP 16 and so they remained unused on the board I designed. I never redesigned the board so I had these chips sitting around unused. Recently I tried out using the Bus Pirate (BP) to communicate with the chip, though both the serial terminal interface and using some Python scripts that interface using the Bus Pirate's binary mode. Beyond just setting some test values and reading them I intend to explain here how the data in the DS3231's registers are formatted.

Hookup

To connect the Bus Pirate to the DS3231 we need to connect the following 5 connectors, as you would for any I2C device:

BP Pin IC Pin Description
+3.3V 2 VCC
VPup 2 Pull up resistors
CLK 16 SCL
MOSI 15 SDA
GND 13 GND

I used a SOP 16 breakout board as well as a little board meant for use with the Raspberry Pi.

Decoding Time

First put the Bus Pirate in I2C mode:

HiZ>m 4 1
I2C (mod spd)=( 0 0 )
Ready
I2C>

You can read the datasheet for the chip's I2C addresses or you can just use the address search feature:

I2C>(1)
Searching I2C address space. Found devices at:
0xD0(0x68 W) 0xD1(0x68 R)

Next, looking at the datasheet we see which registers to read and write the date information from and how they are formatted:

DS3231 registers

To get all the time information, we query the first register at 0x00 and read out 7 bytes. I am using binary mode here since as you can see in the above figure, the values are in binary coded decimal (BCD).

I2C>[ 0xd0 0x0 [ 0xd1 r:7 ]
I2C START BIT
WRITE: 0b11010000 ACK
WRITE: 0b00000000 ACK
I2C START BIT
WRITE: 0b11010001 ACK
READ: 0b00010110  ACK 0b00010000  ACK 0b00011001  ACK 0b00000100  ACK 0b00101001  ACK 0b00010000  ACK 0b00010100
NACK
I2C STOP BIT

We start the sequence, send to the write address which register to use, do a start reset and then read 7 bytes from the read address.

Now what does this all mean? Referring to the register's figure we can decode it, literally bit by bit. We can make good use of the BP's convert function.

The first byte contains the seconds field, with bits 4-6 being the tens place and bits 0-3 being the ones place, so:

(0b001 -> 10) + (0b0110 -> 6) = 16 seconds

For brevity, the chart below decodes the time for all fields, refer to the register's diagram above to double check my answers:

Field Binary Calculation Result
Seconds 0b00010110 (0b001 -> 10) + (0b0110 -> 6) 16
Minutes 0b00010000 (0b001 -> 10) + (0b0000 -> 0) 10
Hour 0b00011001 0b0 -> 24 hr mode, (0 -> not +20) + (0b1 -> 10) + (0b1001 -> 9) 19
Day 0b00000100 0b100 -> 4 (Sun = 1 - Sat = 7) Wednesday
Date 0b00101001 (0b10 -> 20) + (0b1001 -> 9) 29
Month 0b00010000 (0b1 -> 10) + (0b0000 -> 0) 10
Year 0b00010100 (0b0 -> 2000, from Century above) + (0b0001 -> 10) + (0b0100 -> 4) 2014

The time therefore would be Wed 29 Oct 2014 7:10:16 PM.

I double checked this against what was read by the hwclock function on my Raspberry Pi connected to the same DS2321.

As for writing a time value, we would use similarly encoded binary values. We could set this same time as above with the following command:

I2C>[ 0xd0 0x00 0b00010110 0b00010000 0b00011001 0b00000100 0b00101001 0b00010000 0b00010100 ]

Temperature

An interesting feature of the DS3231 is the on chip temperature sensor. As you can see in the register's figure, this temperature can be queried like follows:

I2C>[ 0xd0 0x11 [ 0xd1 r:2 ]
I2C START BIT
WRITE: 0b11010000 ACK
WRITE: 0b00010001 ACK
I2C START BIT
WRITE: 0b11010001 ACK
READ: 0b00011111  ACK 0b10000000
NACK
I2C STOP BIT

The temperature is encoded in two bytes using two's complement format, the first one giving the decimal part and the second byte the fractional part. The first bit of the first byte refers to the sign, since it is positive, its easy for us to decode:

0b00011111 -> 31

The fractional part uses only two bits, so it can only encode 4 possible values:

Binary Fraction
00 0.00
01 0.25
10 0.50
11 0.75

In our example the fractional part would be 0.50, resulting in a temperature of 31.5 deg C.

It should be noted that this temperature will only get updated every 64 seconds normally. But a temperature update can be forced by setting bit 5 in the control register 0xe to 1. You should query the register and only change the bit you need:

I2C>[ 0xd0 0xe [ 0xd1 r ]
I2C START BIT
WRITE: 0b11010000 ACK
WRITE: 0b00001110 ACK
I2C START BIT
WRITE: 0b11010001 ACK
READ: 0b00011100
NACK
I2C STOP BIT

Now update with just bit 5 modified:

I2C>[ 0xd0 0xe 0b00111100 ]

Using Python

Knowing how to manually decode the bits is all good and well, but of course is tedious for anything serious. Keeping in line with using the Bus Pirate, one can use Python tools to more easily access the DS3231 data. We will use a Python interface, pyBusPirateLite which can be obtained from Bus Pirate portion of the Dangerous Prototype's code archive:

svn checkout http://dangerous-prototypes-open-hardware.googlecode.com/svn/trunk/Bus_Pirate/scripts/pyBusPirateLite

There is already a good pyBusPirateLite interface to the DS3231, but for some reason it was left out when Dangerous Prototypes consolidated their code under one Subversion repository. It can still be obtained from the old repo:

wget http://the-bus-pirate.googlecode.com/svn/trunk/scripts/pyBusPirateLite/DS_RTC.py

Using it is quite easy, launch IPython or just use the regular Python REPL:

In [1]: import DS_RTC

In [2]: i2c = DS_RTC.initI2C("/dev/ttyUSB0")
Entering binmode:  OK.
Entering raw I2C mode:  OK.
Configuring I2C.

In [3]: rtc = DS_RTC.DS3231(i2c)

In [4]: rtc.get_datetime()
Out[4]: datetime.datetime(2014, 10, 29, 19, 10, 16)

All the conversions from BCD to decimal are handled for us now and converted to Python's ubiquitous datetime class. Just as an example, we can see how the bcd_to_int function handles the conversion. For example, lets start with the seconds binary value from earlier:

In [1]: bcd = 0b00010110

Next mask off the high bits and shift it to the right to get the tens place:

In [2]: digit10s = ((bcd & 0xF0) >> 4)

In [3]: digit10s
Out[3]: 1

Mask off the low bits to get the ones place:

In [4]: digit1s  = bcd & 0x0F

In [5]: digit1s
Out[5]: 6

Calculate the final answer:

In [6]: (digit10s * 10) + digit1s
Out[6]: 16

We get the same as before. For the initiated, 0xF0 is the bit pattern 11110000 and 0x0F the pattern 00001111. The actual bcd_to_int does one extra step before what is described above. It optionally shifts BCD value to the left and then back to the right to drop unnecessary bits at the MSB end through the bits keyword option. This allows the author to use the same routine despite the different BCD formats of the registers.

We can also use the Python interface to obtain the temperature value:

In [1]: temp_msb = i2c.get_byte(0x68, 0x11)

In [2]: temp_lsb = i2c.get_byte(0x68, 0x12)

In [3]: temp_msb + (temp_lsb >> 7) * 2**(-1) + ((temp_lsb & 0x40) >> 6) * 2**(-2)
Out[3]: 33.75

That last line looks complicated but it is essentially the same thing as:

temp_msb + (temp_lsb >> 7) * 0.50 + ((temp_lsb & 0x40) >> 6) * 0.25

The intention now becoming more obvious, we are using the first two bits of the temperature LSB byte to get one of the 4 fractional values mentioned earlier. Note that this code does not take into account negative temperatures. To handle this we would rewrite the expression as the following:

(temp_msb & 0x7f) + ((temp_msb & 0x80) >> 7) * -2**8 + (temp_lsb >> 7) * 2**(-1) + ((temp_lsb & 0x40) >> 6) * 2**(-2)

The added binary operations in the front uses the sign mask to subtract from 2^8 should the sign bit indicate a two's complement number.

Conclusion

The DS3231 is an excellent real time clock, if you can afford it. It is also common enough to have libraries readily available for just about every microcontroller and computer system with I2C. Here I have attempted to use it as an example to illustrate using the Bus Pirate for I2C bus interrogation and decoding of the results.