Content

  1. Introduction
  2. Preparations
  3. About the RTC DS3231 Module
  4. Examples

Introduction

DS3231 is a low-cost, extremely accurate I2C real-time clock (RTC) with an integrated temperature-compensated crystal oscillator (TCXO) and crystal. The device incorporates a battery input, and maintains accurate timekeeping when main power to the device is interrupted.

In this leeson, we will show what the DS3231 RTC module is, how it works and how to use it to make a simple project with the Osoyoo Uno board. If your sensor later loses connection with controller or power cycles it can pick up time from the local RTC. The module uses a CR2032 battery for time backup which should last for about 3-4 years.

Preparations

Hardware

Software

About RTC DS3231 Module

What is RTC DS3231?

The DS3231 is a low-cost, highly accurate Real Time Clock which can maintain hours, minutes and seconds, as well as, day, month and year information. Also, it has automatic compensation for leap-years and for months with fewer than 31 days.

The module can work on either 3.3 or 5 V which makes it suitable for many development platforms or microcontrollers. The battery input is 3V and a typical CR2032 3V battery can power the module and maintain the information for more than a year.

Specifications

How Does it Work?

The DS3231 real time clock module keeps track of the time even when the module is not powered. It has a built-in 3V battery which keeps updating the time. We will get the time and date from the RTC module using the library functions and then we will compare this time with the alarm time that we have set in the code.

Most RTC’s use an external 32kHz timing crystal that is used to keep time with low current draw. And that’s all well and good, but those crystals have slight drift, particularly when the temperature changes (the temperature changes the oscillation frequency very very very slightly but it does add up!) This RTC is in a beefy package because the crystal is inside the chip! And right next to the integrated crystal is a temperature sensor. That sensor compensates for the frequency changes by adding or removing clock ticks so that the timekeeping stays on schedule.

Pinouts

Power Pins:

I2C Logic pins:

Other Pins:

Schematic

Examples

Displaying date and time in the serial monitor

Connection

Pin Wiring

Wiring the RTC module is pretty straightforward!

Pin Wiring to Arduino Uno
SCL A5
SDA A4
VCC 5V
GND GND

If you’re using other Arduino board rather than the uno, chek out what are their SCL and SDA pins.

Build the circuit as below:

fritzing-rtc

Code Program

After above operations are completed, connect the Arduino board to your computer using the USB cable. The green power LED (labelled PWR) should go on.Open the Arduino IDE and choose corresponding board type and port type for you project.

We will use a library from Henning Karlsen which is great. Thanks a lot for that! There are some basic functions such as reading time and date and writing time and date. Download the library here.

Working with the RTC requires two important steps:

Set the current time in the Real Time Clock

For setting the current time you need to change the code provided.

set-the-time

The parameters for the function are highlighted in red: seconds, minutes, hours, day of the week, date, month and year (in this order). Sunday is the day 1 of the week and Saturday is 7. Don’t forget to uncomment that line of code.

After setting the current time, you can upload the provided code with the required modifications.

The code provided was written by John Boxall from tronixstuff. You can read his tutorial here.

#include "Wire.h"
#define DS3231_I2C_ADDRESS 0x68
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val){
  return( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val){
  return( (val/16*10) + (val%16) );
}
void setup(){
  Wire.begin();
  Serial.begin(9600);
  // set the initial time here:
  // DS3231 seconds, minutes, hours, day, date, month, year
  setDS3231time(30,42,16,5,13,10,16);
}
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year){
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}
void readDS3231time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year){
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}
void displayTime(){
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
  &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10){
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10){
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(" Day of week: ");
  switch(dayOfWeek){
  case 1:
    Serial.println("Sunday");
    break;
  case 2:
    Serial.println("Monday");
    break;
  case 3:
    Serial.println("Tuesday");
    break;
  case 4:
    Serial.println("Wednesday");
    break;
  case 5:
    Serial.println("Thursday");
    break;
  case 6:
    Serial.println("Friday");
    break;
  case 7:
    Serial.println("Saturday");
    break;
  }
}
void loop(){
  displayTime(); // display the real-time clock data on the Serial Monitor,
  delay(1000); // every second
}

Retain the time in the Real Time Clock

If you don’t want to reset the time everytime the RTC is turned off, you should do the following:

comment-the-code

This is a very important step to set up the time in your RTC. If you don’t do this, everytime your RTC resets, it will display the time that you’ve set up previously and not the current time.

Demonstration

Open the serial monitor at a baud rate of 9600 and you’ll see the results.

Here’s the Serial Monitor displaying the current date and time.

Real Time Clock Module, LCD Display and Controller Time

This example shows how you can fetch current time from the controller and put it to a battery backed up Real Time Clock (RTC) module attached to your Arduino. We’ve also attached a LCD Display showing time and temperature [coming from the internal temperature sensor in the RTC module].

This example uses the external TimeLib, DS3232RTC and LiquidCrystal_I2C libraries found here. Please install them and restart the Arduino IDE before trying to compile.

Connection

RTC Module LCD Display Arduino
VCC VCC +5V
SDA SDA A4
SCL SCL A5

Code Program

After above operations are completed, connect the Arduino board to your computer using the USB cable. The green power LED (labelled PWR) should go on.Open the Arduino IDE and choose corresponding board type and port type for you project. Copy below code to your Arduino IDE, then load it to your Arduino board.

#include <Wire.h>          // For the i2c devices
#include <LiquidCrystal_I2C.h>  // For the LCD
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
#define DS3231_I2C_ADDRESS 104    // RTC is connected, address is Hex68 (Decimal 104)

// SCL - pin A5
// SDA - pin A4
// To set the clock, run the sketch and use the serial monitor.
// Enter T1124154091014; the code will read this and set the clock. See the code for full details.
//
byte seconds, minutes, hours, day, date, month, year;
char weekDay[4];
byte tMSB, tLSB;
float my_temp;
char my_array[100];            // Character array for printing something.

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  lcd.init();                      // initialize the lcd 
}

void loop()
{
  watchConsole(); 
  get3231Date();
  Serial.print(weekDay); 
  Serial.print(", "); 
  Serial.print(date, DEC); 
  Serial.print("/"); 
  Serial.print(month, DEC);
  Serial.print("/"); 
  Serial.print(year, DEC); 
  Serial.print(" - ");
  Serial.print(hours, DEC);
  Serial.print(":"); 
  Serial.print(minutes, DEC);
  Serial.print(":"); 
  Serial.print(seconds, DEC);

  my_temp = (float)get3231Temp();
 
  Serial.print(" - Temp: "); 
  Serial.println(my_temp);
  
  // NOTE: Arduino does NOT implement printing floats to a string.
  // If you use the std C function : sprintf(my_array, "Temp: %4.2f", my_temp), It will NOT CONVERT.
  // So I abandoned this, since I don't need to print the float to the LCD anyway.
  
sprintf(my_array, "Time: %d:%d:%d", hours, minutes, seconds);

// Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print(weekDay); 
  lcd.print(", ");
  lcd.print(date, DEC); 
  lcd.print("/"); 
  lcd.print(month, DEC);
  lcd.print("/"); 
  lcd.print(year, DEC);
  lcd.setCursor(0,1);
  lcd.print(my_array);
  delay(1000);
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

void watchConsole()
{
  if (Serial.available()) {      // Look for char in serial queue and process if found
    if (Serial.read() == 84) {      //If command = "T" Set Date
      set3231Date();
      get3231Date();
      Serial.println(" ");
    }
  }
}
 
void set3231Date()
{
//T(sec)(min)(hour)(dayOfWeek)(dayOfMonth)(month)(year)
//T(00-59)(00-59)(00-23)(1-7)(01-31)(01-12)(00-99)
//Example: 02-Feb-09 @ 19:57:11 for the 3rd day of the week -> T1157193020209
// T1124154091014
  seconds = (byte) ((Serial.read() - 48) * 10 + (Serial.read() - 48)); // Use of (byte) type casting and ascii math to achieve result.  
  minutes = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  hours   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  day     = (byte) (Serial.read() - 48);
  date    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  month   = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  year    = (byte) ((Serial.read() - 48) *10 +  (Serial.read() - 48));
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x00);
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.write(decToBcd(day));
  Wire.write(decToBcd(date));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.endTransmission();
}


void get3231Date()
{
  // send request to receive data starting at register 0
  Wire.beginTransmission(DS3231_I2C_ADDRESS); // 104 is DS3231 device address
  Wire.write(0x00); // start at register 0
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7); // request seven bytes

  if(Wire.available()) {
    seconds = Wire.read(); // get seconds
    minutes = Wire.read(); // get minutes
    hours   = Wire.read();   // get hours
    day     = Wire.read();
    date    = Wire.read();
    month   = Wire.read(); //temp month
    year    = Wire.read();
       
    seconds = (((seconds & B11110000)>>4)*10 + (seconds & B00001111)); // convert BCD to decimal
    minutes = (((minutes & B11110000)>>4)*10 + (minutes & B00001111)); // convert BCD to decimal
    hours   = (((hours & B00110000)>>4)*10 + (hours & B00001111)); // convert BCD to decimal (assume 24 hour mode)
    day     = (day & B00000111); // 1-7
    date    = (((date & B00110000)>>4)*10 + (date & B00001111)); // 1-31
    month   = (((month & B00010000)>>4)*10 + (month & B00001111)); //msb7 is century overflow
    year    = (((year & B11110000)>>4)*10 + (year & B00001111));
  }
  else {
    //oh noes, no data!
  }
 
  switch (day) {
    case 1:
      strcpy(weekDay, "Sun");
      break;
    case 2:
      strcpy(weekDay, "Mon");
      break;
    case 3:
      strcpy(weekDay, "Tue");
      break;
    case 4:
      strcpy(weekDay, "Wed");
      break;
    case 5:
      strcpy(weekDay, "Thu");
      break;
    case 6:
      strcpy(weekDay, "Fri");
      break;
    case 7:
      strcpy(weekDay, "Sat");
      break;
  }
}

float get3231Temp()
{
  float temp3231;
  
  //temp registers (11h-12h) get updated automatically every 64s
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0x11);
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 2);
 
  if(Wire.available()) {
    tMSB = Wire.read(); //2's complement int portion
    tLSB = Wire.read(); //fraction portion
   
    temp3231 = (tMSB & B01111111); //do 2's math on Tmsb
    temp3231 += ( (tLSB >> 6) * 0.25 ); //only care about bits 7 & 8
  }
  else {
    //oh noes, no data!
  }
   
  return temp3231;
}

Running Result

A few seconds after the upload finishes, we can see the running result as below:

Open the Serial Monitor, you can also see the output: