The DS3231 Real Time Clock - uraich/IoT4AQ GitHub Wiki
We have two Real Time Clocks (RTCs) in the system:
- an RTC integrated into the ESP32 CPU chip
- an external, battery backed DS3231 RTC with I2C interface
The RTC on the ESP32 is set to 1. Jan 1970 00:00:00 after each reset. If the ESP32 is within reach of a WiFi network, the ESP32 RTC can be set to the correct time, recovered over the Internet through NTP (the Network Time Protocol). In this case the external DS3231 RTC is not needed. If we are running in an environment without Internet, the DS3231 is the only possibility to recover the time. In this case the DS3231 must be set before, either in an environment with Internet access, or by setting it manually (you hard code the time into a program setting the DS3231 and run it at the wanted time) and of course you need battery backup to keep the time even if the ESP32 is not powered.
The ESP32Time library provides functions to set and read the time. Several calls return a value of type String. C++ implements a String class in addition to the C-type string, which we have seen before. The C string is simply an array of characters terminated by a zero, while String is a C++ class. The values of RSP32Time methods however return a C++ String class. You will have to read the C++ String documentation to learn more. There is a function c_str() which converts a C++ String into a C string:
#include <ESP32Time.h>
ESP32Time esp32RTC;
char *date_time;
date_time = (char *) esp32RTC.getTime("%H:%M:%S %d %b %Y").c_str()
(char *) is a type cast. esp32RTC.getTime() returns a const char * and we tell the compiler to interpret it as a char *. This is how date_time is defined.
Setting the ESP32 RTC using NTP is not particularly difficult:
const char* ntpServer = "pool.ntp.org"; // use this NTP server
const long gmtOffset_sec = 0; // use UTC
const int daylightOffset_sec = 0;
// connect to the WiFi network
...
// Init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
The DS3231 Real Time Clock by Maxim Integrated is an I2C chip, which keeps time and date. It runs on a standard 32.768kHz Quartz Crystal. Time and date are kept in the first 8 registers while the other registers allow to set alarms, read the current temperature and control the oscillator or the alarm interrupt / square wave output. The time and date registers are coded in BCD (Binary Coded Decimal). All registers are battery backed and keep their state even when the ESP32 is not powered. Here is a photo of the module:
and the register layout:
When accessing the DS3231 using straight I2C functions provided by the Wire library we can first check if the DS3231 is visible on the I2C bus:
#include <Wire.h>
#define DS3231_ADDRESS 0x68 // I2C address of the DS3231 chip
#define DS3231_TIME 0x00 // register start address of the time and date registers
#define DS3231_CTRL 0x0e // address of control register
#define DS3231_STATUS 0x0f // address of status register
#define TIME_SIZE 8 // number of time and date registers
... in setup():
byte err,ctrl,status;
int bytes_read = 0;
char ds1307TimeBuf[TIME_SIZE]; // save the date and time values into this buffer
char *ds1307TimeBufPtr = ds1307TimeBuf;
Wire.begin(); // initialize the Wire library
Wire.beginTransmission/DS3231_ADDRESS)
err = Wire.endTransmission(); // if we get an error, then no I2C slave with the requires address has been found
// check if the oscillator is enabled
Wire.beginTransmission(DS3231_CTRL);
Wire.write(DS3231_CTRL); // write the control register address
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDRESS,2); // ask for 1 byte
while (!Wire.available()) ; // wait until the data are ready
ctrl = Wire.read(); // get the contents of the control register
while (!Wire.available()) ; // wait until the data are ready
status = Wire.read(); // get the contents of the status register
if (ctrl & 0x80) {
Serial.println("Oscillator is not running");
return;
}
if (status & 0x80) {
Serial.println("Oscillator was switched off");
return;
}
// now we can read the time registers
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write(DS3231_TIME); // write the register address
Wire.endTransmission();
Wire.requestFrom(DS3231_ADDRESS,TIME_SIZE); // ask for 8 bytes
while (Wire.available()) {
*ds3231TimeBufPtr++ = Wire.read();
bytes_read++;
if (bytes_read > TIME_SIZE)
break;
}
Wire.endTransmission();
When working on the PMS5003 we saw binary coding of integer values in hexadecimal format. The DS3231 uses still another type of decoding: Binary Coded Decial, or BCD. BCD works in a similar fashion, only that a 4 bit value spills over to the next higher digit not at 15 (0xf), but at 9. The binary value 0010 0110 therefore represents 26. In order to convert from binary encoding to BCD and back we need conversion routines. Since the DS3231 registers only contain 8 bits, these conversion routines stay very simple. When using RTClib, it is the library which performs the conversions. When accessing the DS3231 directly, using the wire library, this is the job of the programmer.
RTClib works for a series of different RTCs:
A typical program to read the DS3231 would do this:
#include <RTClib.h>
void setup(){
RTC_DS3231 ds3231;
DateTime now;
Serial.begin(115200);
if (!ds3231.begin()) {
Serial.println("Could not find the DS3231 on the I2C bus");
Serial.println("Please check the wiring");
return;
}
if (ds3231.powerLost()) {
Serial.println("DS3231 Oscillator is switched off");
Serial.println("Please run a program to set the DS3231, which switches the oscillator on");
return;
}
// get the time from the ds3231 using the RTClib now() method
// this returns a DateTime object which can be printed using the
// toString() method
now = ds3231.now();
char timeFormat[] = "DDD DD.MMM YYYY hh:mm:ss";
Serial.print("The current date and time on the DS3231: ");
Serial.println(now.toString(timeFormat));
}
ESP32Time is the Arduino library giving access to the RTC, internal to the ESP32. Usually this RTC is set at the beginning of the program getting the current date and time from the Internet using the Network Time Protocol (NTP). This can be done with the program below:
const char* ntpServer = "pool.ntp.org"; // the NTP server from which to pick date and time
const long gmtOffset_sec = 0; // use UTC, otherwise use the offset of your time zone
const int daylightOffset_sec = 0;
... connect to WiFi
// Init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // This takes date and time from NTP and sets the ESP32 RTC
If the ESP32 has no Internet access, then we must recuperate date and time from the DS3231 and use this information to set the internal RTC. Once the RTC is correctly initialized we can use the following functions to access the current date and time: