In this guide, we shall use STM32 to develop a driver to set and get the time store in the DS3231.
In this guide, we shall cover the following:
- DS3231 Module.
- Connection with STM32F411 Nucleo-64.
- Developing the header file.
- Developing the source file.
- Code.
- Results.
1. DS3231 Module
The DS3231 is a low-cost, extremely accurate I 2 C 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. The integration of the crystal resonator enhances the long-term accuracy of the device as well as reduces the piece-part count in a manufacturing line.
The RTC maintains seconds, minutes, hours, day, date, month, and year information. The date at the end of the month is automatically adjusted for months with fewer than 31 days, including corrections for leap year. The clock operates in either the 24-hour or 12-hour format with an AM/PM indicator. Two programmable time-of-day alarms and a programmable square-wave output are provided. Address and data are transferred serially through an I 2 C bidirectional bus.
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.
2. Connection with STM32F411 Nucleo-64:
3. Developing the header file:
Before starting the driver development, I2C multiread and multiwrite is needed in order to get the DS3231 to work.
You can get those two functions from here:
We start off by creating new header file with name of ds3231.h.
Within the header file, we shall declare a structure as following :
typedef struct ts { uint8_t sec; /* seconds */ uint8_t min; /* minutes */ uint8_t hour; /* hours */ uint8_t mday; /* day of the month */ uint8_t mon; /* month */ int16_t year; /* year */ uint8_t wday; /* day of the week */ uint8_t yday; /* day in the year */ uint8_t isdst; /* daylight saving time */ uint8_t year_s; /* year in short notation*/ }ts;
The structure will hold the following:
- Seconds.
- Minutes.
- Hours.
- Day of the month.
- Month.
- Year.
- Other data needed for the calculation.
Also, declare the following three functions:
void DS3231_set(ts t); void DS3231_get(ts *t); void print_time(ts *t);
First one is to set the time and data and take the structure as argument.
Second one is to read the time from DS3231 and takes a pointer to the structure.
Third function which will print the time.
Hence, the entire header file as following:
#ifndef DS3231_H_ #define DS3231_H_ #include "stdint.h" typedef struct ts { uint8_t sec; /* seconds */ uint8_t min; /* minutes */ uint8_t hour; /* hours */ uint8_t mday; /* day of the month */ uint8_t mon; /* month */ int16_t year; /* year */ uint8_t wday; /* day of the week */ uint8_t yday; /* day in the year */ uint8_t isdst; /* daylight saving time */ uint8_t year_s; /* year in short notation*/ }ts; void DS3231_set(ts t); void DS3231_get(ts *t); void print_time(ts *t); #endif /* DS3231_H_ */
4. Developing the source code.
Create a new source code with name of ds3231.c.
Within this declare the following macro:
#define DS3231_I2C_ADDR 0x68 // time keeping registers #define DS3231_TIME_CAL_ADDR 0x00 #define DS3231_ALARM1_ADDR 0x07 #define DS3231_ALARM2_ADDR 0x0B #define DS3231_CONTROL_ADDR 0x0E #define DS3231_STATUS_ADDR 0x0F #define DS3231_AGING_OFFSET_ADDR 0x10 #define DS3231_TEMPERATURE_ADDR 0x11 // control register bits #define DS3231_CONTROL_A1IE 0x1 /* Alarm 2 Interrupt Enable */ #define DS3231_CONTROL_A2IE 0x2 /* Alarm 2 Interrupt Enable */ #define DS3231_CONTROL_INTCN 0x4 /* Interrupt Control */ #define DS3231_CONTROL_RS1 0x8 /* square-wave rate select 2 */ #define DS3231_CONTROL_RS2 0x10 /* square-wave rate select 2 */ #define DS3231_CONTROL_CONV 0x20 /* Convert Temperature */ #define DS3231_CONTROL_BBSQW 0x40 /* Battery-Backed Square-Wave Enable */ #define DS3231_CONTROL_EOSC 0x80 /* not Enable Oscillator, 0 equal on */ // status register bits #define DS3231_STATUS_A1F 0x01 /* Alarm 1 Flag */ #define DS3231_STATUS_A2F 0x02 /* Alarm 2 Flag */ #define DS3231_STATUS_BUSY 0x04 /* device is busy executing TCXO */ #define DS3231_STATUS_EN32KHZ 0x08 /* Enable 32KHz Output */ #define DS3231_STATUS_OSF 0x80 /* Oscillator Stop Flag */
Also, two functions:
First one that converts the dec to BCD:
static uint8_t dectobcd(const uint8_t val) { return ((val / 10 * 16) + (val % 10)); }
The second one is to convert BCD to dec:
static uint8_t bcdtodec(const uint8_t val) { return ((val / 16 * 10) + (val % 16)); }
In order to set the time and date:
void DS3231_set(ts t) { uint8_t i, century; century = 0x80; t.year_s = t.year - 2000; uint8_t TimeDate[7] = { t.sec, t.min, t.hour, t.wday, t.mday, t.mon, t.year_s }; for (i=0;i<=6;i++) { TimeDate[i] = dectobcd(TimeDate[i]); if(i == 5){TimeDate[5] |= century;} } i2c_WriteMulti(DS3231_I2C_ADDR,DS3231_TIME_CAL_ADDR,(char*)TimeDate,7); }
Since we are already beyond 2000, hence we shall set the century bit to 1:
and subtract 2000 from the given year.
Then convert the variables to BCD and send the variables to DS3231 in order to set the time and date.
In order to get the time:
void DS3231_get(ts *t) { uint8_t TimeDate[7]; //second,minute,hour,dow,day,month,year uint8_t i; uint16_t year_full; i2c_ReadMulti(DS3231_I2C_ADDR,DS3231_TIME_CAL_ADDR,7,(char*)TimeDate); for (i = 0; i <= 6; i++) { if (i == 5) { TimeDate[5] = bcdtodec(TimeDate[i] & 0x1F); } else TimeDate[i] = bcdtodec(TimeDate[i]); } year_full = 2000 + TimeDate[6]; t->sec = TimeDate[0]; t->min = TimeDate[1]; t->hour = TimeDate[2]; t->mday = TimeDate[4]; t->mon = TimeDate[5]; t->year = year_full; t->wday = TimeDate[3]; t->year_s = TimeDate[6]; }
For getting the time, it is similar to setting the time but using BCD to dec and adding 2000 to the year.
To print the time:
void print_time(ts *t) { printf("Current DS3231 Time: %d:%d:%d\r\n",t->hour,t->min,t->sec); printf("Current DS3231 Date: %d/%d/%d\r\n",t->year,t->mon,t->mday); }
Hence, the source code as following:
#include "ds3231.h" #include "i2c.h" #define DS3231_I2C_ADDR 0x68 // time keeping registers #define DS3231_TIME_CAL_ADDR 0x00 #define DS3231_ALARM1_ADDR 0x07 #define DS3231_ALARM2_ADDR 0x0B #define DS3231_CONTROL_ADDR 0x0E #define DS3231_STATUS_ADDR 0x0F #define DS3231_AGING_OFFSET_ADDR 0x10 #define DS3231_TEMPERATURE_ADDR 0x11 // control register bits #define DS3231_CONTROL_A1IE 0x1 /* Alarm 2 Interrupt Enable */ #define DS3231_CONTROL_A2IE 0x2 /* Alarm 2 Interrupt Enable */ #define DS3231_CONTROL_INTCN 0x4 /* Interrupt Control */ #define DS3231_CONTROL_RS1 0x8 /* square-wave rate select 2 */ #define DS3231_CONTROL_RS2 0x10 /* square-wave rate select 2 */ #define DS3231_CONTROL_CONV 0x20 /* Convert Temperature */ #define DS3231_CONTROL_BBSQW 0x40 /* Battery-Backed Square-Wave Enable */ #define DS3231_CONTROL_EOSC 0x80 /* not Enable Oscillator, 0 equal on */ // status register bits #define DS3231_STATUS_A1F 0x01 /* Alarm 1 Flag */ #define DS3231_STATUS_A2F 0x02 /* Alarm 2 Flag */ #define DS3231_STATUS_BUSY 0x04 /* device is busy executing TCXO */ #define DS3231_STATUS_EN32KHZ 0x08 /* Enable 32KHz Output */ #define DS3231_STATUS_OSF 0x80 /* Oscillator Stop Flag */ static uint8_t dectobcd(const uint8_t val) { return ((val / 10 * 16) + (val % 10)); } static uint8_t bcdtodec(const uint8_t val) { return ((val / 16 * 10) + (val % 16)); } void DS3231_set(ts t) { uint8_t i, century; century = 0x80; t.year_s = t.year - 2000; uint8_t TimeDate[7] = { t.sec, t.min, t.hour, t.wday, t.mday, t.mon, t.year_s }; for (i=0;i<=6;i++) { TimeDate[i] = dectobcd(TimeDate[i]); if(i == 5){TimeDate[5] |= century;} } i2c_WriteMulti(DS3231_I2C_ADDR,DS3231_TIME_CAL_ADDR,(char*)TimeDate,7); } void DS3231_get(ts *t) { uint8_t TimeDate[7]; //second,minute,hour,dow,day,month,year uint8_t i; uint16_t year_full; i2c_ReadMulti(DS3231_I2C_ADDR,DS3231_TIME_CAL_ADDR,7,(char*)TimeDate); for (i = 0; i <= 6; i++) { if (i == 5) { TimeDate[5] = bcdtodec(TimeDate[i] & 0x1F); } else TimeDate[i] = bcdtodec(TimeDate[i]); } year_full = 2000 + TimeDate[6]; t->sec = TimeDate[0]; t->min = TimeDate[1]; t->hour = TimeDate[2]; t->mday = TimeDate[4]; t->mon = TimeDate[5]; t->year = year_full; t->wday = TimeDate[3]; t->year_s = TimeDate[6]; } void print_time(ts *t) { printf("Current DS3231 Time: %d:%d:%d\r\n",t->hour,t->min,t->sec); printf("Current DS3231 Date: %d/%d/%d\r\n",t->year,t->mon,t->mday); }
Within main.c file:
include the header file as following:
#include "ds3231.h"
Declare the sturcture:
ts ds3231_data;
Then set the time and date within the structure:
ds3231_data.sec=0; ds3231_data.min=55; ds3231_data.hour=7; ds3231_data.mday=7; ds3231_data.mon=11; ds3231_data.year=2022;
Call DS3231_set function:
DS3231_set(ds3231_data);
In while 1 loop :
call get time function, then call print time function and wait for a half of second:
DS3231_get(&ds3231_data); print_time(&ds3231_data); delay(500);
5. Code:
You many download the code from here:
6. Results:
After compile and load the code to STM32F411 and open serial terminal application, you should get something similar to what you set:
Happy coding 🙂
2 Comments
Could you please make a tutorial about STM32’s RTC and power management? I’m on a project that stm32 is powered by batteries and wakeup by RTC
check out our advanced bare metal course.
https://study.embeddedexpert.io/courses/enrolled/1812977
Add Comment