IWEUA.COM

Interfacing Arduino
with RTC DS3231
As part of a larger project I wanted to interface my DS3231 module with my Arduino using the I2C interface and
Wire library which I have used before with the I2C LCD units with great success and reliability.

The DS3231 data sheet is a little daunting, there is a lot of valuable information in the sheet regarding the I2C
bus, it's operation and specification. It took about a day to achieve working data I/O.

I will share here what I learned and add some very simple code to get you started with the I2C bus.

The
marked up DS3231 data sheet  my highlighting and notes.

The Arduino help reference is essential reading focusing here on the Wire Library and it's commands.

Powering the DS3231 / ZS-042
A note on powering the ZS-042 DS3231 module. You will find warnings about powering this unit due to the
slightly odd, potentially problematic 'charging circuit'. My unit came fitted with a long life non rechargeable CR
2032 battery which should not be charged in any way.

You will see from the charging circuit, that if the DS3231 is powered by a 5V supply a charging current will flow
through the diode and resistor into the battery with potentially dangerous results.
Even if the battery is a rechargeable type the charging current through the 200R resistor will be roughly ( 4.3V -
3.3V ) / 200R ~ 5mA, way too much for long life.

My recommendations would be to use a CR2032 battery AND remove the 200R charging resistor in the top right
of the photo. The charging cannot occur if the unit is powered from 3.3V as the diode drop alone will prevent this.
Better safe than sorry, remove the charging resistor and or diode or both to ensure trouble free operation.
Handshaking on the I2C bus

It needs a little study to understand how the I2C bus works as it is a shared bus where devices communicate and
handshake over a single wire ( SDA ).
Monitoring the SDA line with a scope shows the signals but not the originating device, as either device can be
influencing the wire. I will show some screen shots here to try to fill the gaps.

A first command : Wire.requestFrom(address, quantity)

In I2C each device has a unique 7 bit address, the DS3231 is 0x68 as detailed int he data sheet
What does Arduino command
Wire.requestFrom(0x68, 1) actually do?

In words
The Primary device, the Arduino, will toggle the I2C line, writing the address 0x68 to the line.
The DS3231, responding to the Primary device transmitting it's address to the line will respond with an ACK.
The DS3231 will then toggle the line with it's reply data, the Arduino toggling the ACK until it has received the
quantity of bytes it requested.

What does the Arduino output on a
Wire.requestFrom(0x68, 1) if it isn't connected to anything?

EXAMPLE 1 : Wire.request(0x68,1) no device attached to the Arduino
Here we see the
Arduino transmits 7 bit
Address header,
outputs a
READ = HIGH Pulse
signalling to the
DS3231 a request for
data.

In this example the
Arduino is not
connected to anything
and thus the ACK
remains HIGH as the
DS3231 cannot reply.

Let's connect a DS3231
In this example the
DS3231 is connected
and thus is able to pull
the ACK LOW.

Then,  the DS3231
generates it's own
CLOCK and DATA
signals transmitting this
first Byte to the Arduino.

Only ONE data byte
has been requested in
the example, thus the
ACK of the data BYTE
is HIGH at the end.

Let's read TWO bytes.
TWO bytes read
This last example
shows how the ACK
works :

Wire.request(0x68,2)

We do not tell the
DS3231 how many
data bytes we want it to
transmit at the outset,
we just tell it, using the
ACK to keep
transmitting data bytes,
forever until the
receiver stops pulling
ACK low at the end of
every Byte.

Data Byte 1 ACK is
pulled low by the
Arduino.
Data Byte 2 AC
K is
kept HIGH, the DS3231
thus stops transmitting
EXAMPLE 2 : Wire.request(0x68,1) Showing ACK sent from DS3231 in the Address Byte
EXAMPLE 3 : Wire.request(0x68,2) Showing ACK sent from Arduino to signal Data requested has been received
The DS3231, the address pointer and the memory map

From page 11 of the manual we can see that the DS3231 has 19 registers 0x00 - 0x12

We can just do something like

Wire.request(0x68,19);
while ( Write.available() )
 {
   char c = Wire.read();
  Serial.println ( c );
 }


This isn't great as we do not know where the DS3231's address pointer is to begin with, we need to set it.
With reference to
Figure 5 Data Write/Read (Write Pointer, Then Read) we can set the pointer by transmitting
the Address, the Word Address we wish to set the pointer to, and the address again

So we can set the pointer as below

Wire.beginTransmission(0x68) ;
Wire.write(0x00);
Wire.endTransmission();

// Then
Wire.requestFrom(0x68,7);

And we will receive the bytes 0 : 6

BUT before that we need to write some sensible time values INTO the DS3231,
watch it flip over Times, Days, Months and Years to be confident we have set the unit up correctly
Initialising the DS3231 with some real times and dates

void setup()
{  UpdateTime();
}

void UpdateTime()              //
Setting the DS3131 time to 31 December 2019 23:59:30 ( 24 Hour clock )
{  
Wire.beginTransmission(0x68) ;
Wire.write(0x00);  //RESET COUNTER POINTER TO ZERO

// Write into Registers 0x00 - 0x06

Wire.write(0x30);  // Secs / Tens  Secs / Units
Wire.write(0x59);  //01 10 Min / Min
Wire.write(0x23);  //02 10 Hour / Hour               
Wire.write(0x00);  //03  Day Count ( 1-7 ) 1 = Sunday
Wire.write(0x31);  //04 Day 10 / Day Units
Wire.write(0x12);  //05 Month Tens / Month Units
Wire.write(0x19);  //06 YEAR

Wire.endTransmission();
}  


You will note that due to the Data being held in registers as BCD, the values are Human Readable
Reading the Time of day : Printing it out

// This is very simple code designed to be easy to understand without the use of extra libraries to get you started

int ReadData()
{
Wire.requestFrom(0x68,6);

  int a = Wire.read();         
// Read Secs Byte             READ the first three bytes transmitted from the DS3231
  int b = Wire.read();         // Read Mins Byte
  int c = Wire.read();
        // Read Hours Byte
  
  Serial.print(MSB(c),HEX); Serial.print(LSB(c),HEX);
               // Print Hours Hours
  Serial.print(":");
  Serial.print(MSB(b),HEX); Serial.print(LSB(b),HEX);
               // Print Mins Mins
  Serial.print(":");
  Serial.print(MSB(a),HEX); Serial.print(LSB(a),HEX);
               // Print Secs Secs
  Serial.print("  ");

  a = Wire.read();  // Day Counter : Don't need throw the data away by reading
char a again
  a = Wire.read();  // Date Tens     Date Units
  b = Wire.read();  // Month Tens    Month Uits
  c = Wire.read();  // Year Tens     Year Units

  Serial.print(MSB(a),HEX); Serial.print(LSB(a),HEX);    
        // Print Date Date
  Serial.print(" ");
  Serial.print(MSB(b),HEX);  Serial.print(LSB(b),HEX);           
// Print Month Month
  Serial.print(" ");
  Serial.print("20");
                                                                // Print '20' Good for ~ 80 years or so                        
  Serial.print(MSB(c),HEX); Serial.print(LSB(c),HEX);  
           // Print Year Year
  Serial.println();
}

byte LSB(
int a )
{
return
( a & B00001111 );            // Awful code : no error checking, filtering or handling
}

byte MSB(
int a )
{
return a >> 4;
                               // Shift the 4 MSBs right to make an LSB
}



The entire code for a very basic DS3231 implementation

AG : July 2020