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 as all the device outputs, on the bus are Open Collector.
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?
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
READ = HIGH Pulse
signalling to the
DS3231 a request for
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
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
Data Byte 1 ACK is
pulled low by the
Data Byte 2 ACK 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
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
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 UpdateTime() // Setting the DS3131 time to 31 December 2019 23:59:30 ( 24 Hour clock )
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
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
// Download link for full sketch at base of page
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(MSB(b),HEX); Serial.print(LSB(b),HEX); // Print Mins Mins
Serial.print(MSB(a),HEX); Serial.print(LSB(a),HEX); // Print Secs Secs
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(MSB(b),HEX); Serial.print(LSB(b),HEX); // Print Month Month
Serial.print("20"); // Print '20' Good for ~ 80 years or so
Serial.print(MSB(c),HEX); Serial.print(LSB(c),HEX); // Print Year Year
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