© Warren Gay 2018
Warren GayAdvanced Raspberry Pihttps://doi.org/10.1007/978-1-4842-3948-3_25

25. Real-Time Clock

Warren Gay1 
(1)
St. Catharine’s, Ontario, Canada
 

The DS3231 module sold for the Arduino is perfect for the Raspberry Pi because the IC operates from a range of +2.3 to 5.5 V. This permits the Pi user to power it from the Pi’s +3.3 V supply and wire it up to the I2C bus. The module sports a battery backup, allowing it to keep accurate time when the Pi is powered off. The DS3231 includes temperature measurement and adjustment to maintain timekeeping accuracy.

This chapter will exercise the DS3231 with a C program to set and read the date/time. Additionally, there is a 1 Hz output that can be sensed from a GPIO port should you have application for it. The DS3231 RTC (real-time clock) also provides a fairly accurate temperature reading.

DS3231 Overview

A front side photo of the module is provided in Figure 25-1. The module came assembled with right angled pins, which insert nicely into the breadboard. Mine also arrived from eBay with the battery installed but don’t count on that. There are often battery shipping restrictions. You may want to purchase a battery separately.
../images/326071_2_En_25_Chapter/326071_2_En_25_Fig1_HTML.jpg
Figure 25-1

The front view of the DS3231 module inserted into the breadboard

From the list of connection labels, it is plain that this is an I2C device. Apart from the power and I2C connections, there is an output labeled SQW, which can be configured to produce a 1 Hz pulse. In this chapter, I suggest you wire it to GPIO 22 for a demonstration. Figure 25-2 illustrates the backside of the pcb.
../images/326071_2_En_25_Chapter/326071_2_En_25_Fig2_HTML.jpg
Figure 25-2

The backside view of the DS3231 with a battery installed

Tip

Buying a fresh battery ahead of time is recommended, since batteries often arrive exhausted or are not included due to shipping regulations.

Hookup

The DS3231 module also includes an AT24C32 4kx8 I2C EEPROM that can be used, but this will be left as an exercise for the reader. The wiring diagram as it affects the RTC chip is shown in Figure 25-3. The hookup of the SQW output is optional. It can be used to get a 1 Hz pulse at precise intervals.
../images/326071_2_En_25_Chapter/326071_2_En_25_Fig3_HTML.jpg
Figure 25-3

Hookup of the DS3231 to the Raspberry Pi I2C bus

The module runs at +3.3 V so this is easy to wire up because the Pi and the module share the same power source. Simply wire SDA and SCL connections and the power to the device. Don’t forget the ground connection.

With the module hooked up to the Pi’s I2C bus, you should be able to detect it.
$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

The numbers shown are hexadecimal addresses of the discovered devices. The 0x68 device is the DS3231, while 0x57 is the AT24C32 EEPROM device. If your devices fail to be discovered, shut down your Pi and recheck your wiring.

Note

The DS3231 RTC uses I2C address 0x68.

Register Map

The DS3231 is compatible with the former 5 V DS1307 chip, but there has been the addition of two alarms among other features. The declaration of the DS3231 registers in C are illustrated in Listing 25-1. Each portion of the register layout is described as a substructure. For example, struct s_00 is the layout for the register at byte offset 0x00.
0023: struct s_ds3231_regs {
0024:   struct s_00 {                  /* Seconds */
0025:           uint8_t secs_1s  : 4;   /* Ones digit: seconds */
0026:           uint8_t secs_10s : 3;   /* Tens digit: seconds */
0027:           uint8_t mbz_0    : 1;
0028:   } s00;
0029:   struct s_01 {                  /* Minutes */
0030:           uint8_t mins_1s  : 4;   /* Ones digit: minutes */
0031:           uint8_t mins_10s : 3;   /* Tens digit: minutes */
0032:           uint8_t mbz_1    : 1;
0033:   } s01;
0034:   union u_02 {                   /* Hours */
0035:           struct  {
0036:                   uint8_t hour_1s  : 4;   /* Ones digit: hours */
0037:                   uint8_t hour_10s : 1;   /* Tens digit: hours (24hr mode) */
0038:                   uint8_t ampm     : 1;   /* AM=0/PM=1 */
0039:                   uint8_t mode_1224: 1;   /* Mode bit: 12=1/24=0 hour format */
0040:           } hr12;
0041:           struct  {
0042:                   uint8_t hour_1s  : 4;   /* Ones digit: hours */
0043:                   uint8_t hour_10s : 3;   /* Tens digit: hours (24hr mode) */
0044:                   uint8_t mode_1224: 1;   /* Mode bit: 12=1/24=0 hour format */
0045:           } hr24;
0046:   } u02;
0047:   struct s_03 {                   /* Weekday */
0048:           uint8_t wkday    : 3;   /* Day of week (1-7) */
0049:           uint8_t mbz_2    : 5;
0050:   } s03;
0051:   struct s_04 {                   /* Day of month */
0052:           uint8_t day_1s   : 4;   /* Ones digit: day of month (1-31) */
0053:           uint8_t day_10s  : 2;   /* Tens digit: day of month */
0054:           uint8_t mbz_3    : 2;
0055:   } s04;
0056:   struct s_05 {                   /* Month */
0057:           uint8_t month_1s : 4;   /* Ones digit: month (1-12) */
0058:           uint8_t month_10s: 1;   /* Tens digit: month */
0059:           uint8_t mbz_4    : 2;
0060:           uint8_t century  : 1;   /* Century */
0061:   } s05;
0062:   struct s_06 {                   /* Year */
0063:           uint8_t year_1s  : 4;   /* Ones digit: BCD year */
0064:           uint8_t year_10s : 4;   /* Tens digit: BCD year */
0065:   } s06;
0066:   struct s_07 {                   /* Alarm Seconds */
0067:           uint8_t alrms01  : 4;   /* Alarm BCD 1s seconds */
0068:           uint8_t alrms10  : 3;   /* Alarm BCD 10s Seconds */
0069:           uint8_t AxM1     : 1;   /* Alarm Mask 1 */
0070:   } s07;                          /* Alarm Seconds */
0071:   struct s_08 {                   /* Alarm Minutes */
0072:           uint8_t alrmm01  : 4;   /* Alarm BCD 1s Minutes */
0073:           uint8_t alrmm10  : 3;   /* Alarm BCD 10s Minutes */
0074:           uint8_t AxM2     : 1;   /* Alarm Mask 2 */
0075:   } s08;                          /* Alarm Minutes */
0076:   union u_09 {                    /* Alarm Hours */
0077:           struct  {
0078:                   uint8_t alr_hr10 : 1;   /* Alarm 10s Hours */
0079:                   uint8_t alr_ampm : 1;   /* Alarm am=0/pm=1 */
0080:                   uint8_t alr_1224 : 1;   /* Alarm 12=1 */
0081:                   uint8_t AxM3     : 1;   /* Alarm Mask 3 */
0082:           } ampm;
0083:           struct  {
0084:                   uint8_t alr_hr10 : 2;   /* Alarm 10s Hours */
0085:                   uint8_t alr_1224 : 1;   /* Alarm 24=0 */
0086:                   uint8_t AxM3     : 1;   /* Alarm Mask 3 */
0087:           } hrs24;
0088:   } u09;                          /* Alarm 1 Hours */
0089:   union u_0A {                    /* Alarm Date */
0090:           struct  {
0091:                   uint8_t day1s    : 4;   /* Alarm 1s date */
0092:                   uint8_t day10s   : 2;   /* 10s date */
0093:                   uint8_t dydt     : 1;   /* Alarm dy=1 */
0094:                   uint8_t AxM4     : 1;   /* Alarm Mask 4 */
0095:           } dy;
0096:           struct  {
0097:                   uint8_t day1s    : 4;   /* Alarm 1s date */
0098:                   uint8_t day10    : 2;   /* Alarm 10s date */
0099:                   uint8_t dydt     : 1;   /* Alarm dt=0 */
0100:                   uint8_t AxM4     : 1;   /* Alarm Mask 4 */
0101:           } dt;
0102:   } u0A;
0103:   struct s_08 s0B;                /* Alarm 2 Minutes */
0104:   union u_09 u0C;                 /* Alarm 2 Hours */
0105:   union u_0A u0D;                 /* Alarm 2 Date */
0106:   struct s_0E {                   /* Control */
0107:           uint8_t A1IE     : 1;   /* Alarm 1 Int enable */
0108:           uint8_t A2IE     : 1;   /* Alarm 2 Int enable */
0109:           uint8_t INTCN    : 1;   /* SQW signal when 1 */
0110:           uint8_t RS1      : 1;   /* Rate select 1 */
0111:           uint8_t RS2      : 1;   /* Rate select 2 */
0112:           uint8_t CONV     : 1;   /* Temp conversion */
0113:           uint8_t BBSQW    : 1;   /* Enable square wave */
0114:           uint8_t NEOSC    : 1;   /* /EOSC: Enable */
0115:   } s0E;
0116:   struct s_0F {                   /* Control/status */
0117:           uint8_t A1F      : 1;   /* Alarm 1 Flag */
0118:           uint8_t A2F      : 1;   /* Alarm 2 Flag */
0119:           uint8_t bsy      : 1;   /* Busy flag */
0120:           uint8_t en32khz  : 1;   /* Enable 32kHz out */
0121:           uint8_t zeros    : 3;
0122:           uint8_t OSF      : 1;   /* Stop Osc when 1 */
0123:   } s0F;
0124:   struct s_10 {                   /* Aging offset */
0125:           int8_t data      : 8;   /* Data */
0126:   } s10;
0127:   struct s_11 {
0128:           int8_t temp      : 8;   /* Signed int temp */
0129:   } s11;
0130:   struct s_12 {
0131:           uint8_t mbz      : 6;
0132:           uint8_t frac     : 2;   /* Fractional temp bits */
0133:   } s12;
0134: } __attribute__((packed));
Listing 25-1

The full C language register map for the DS3231

Register 0x00 (Seconds)

The register at 0x00 consists of two bit fields: s00.secs_1s and s00.secs_10s, repeated below:
0024:   struct s_00 {                   /* Seconds */
0025:           uint8_t secs_1s  : 4;   /* Ones digit: seconds */
0026:           uint8_t secs_10s : 3;   /* Tens digit: seconds */
0027:           uint8_t mbz_0    : 1;
0028:   } s00;

For students reading this, an explanation about C language bit fields is in order. The colon and the following number specifiy the bit width of the field. The field being divided up is determined by the type, which in this case is an unsigned byte (uint8_t). The fields listed first specify the least significant bits (on the Pi), while the following bit fields represent higher numbered bits. For example, field s00.secs_1s defines bits 3-0 (rightmost), while s00.secs_10s defines for bits 6-4, and s00.mbz_0 declaring bits 7-6 (leftmost two bits). Specifying bit fields saves us from having to use bitwise and shift operations to move values in and out.

The members secs_1s and secs_10s represent BCD (binary coded decimal) digits, for the time in seconds. So a value of 0x23 (in the uint8_t) byte represents the decimal value 23. These and the other time values are automatically incremented by the RTC (real-time clock) as the DS3231 IC keeps time.

Register 0x01 (Minutes)

The minutes reading is provided at byte offset 0x01, in a format similar to the seconds component. Again, members mins_10s and mins_1s are the BCD digits of the minutes time component.
0029:   struct s_01 {                   /* Minutes */
0030:           uint8_t mins_1s  : 4;   /* Ones digit: minutes */
0031:           uint8_t mins_10s : 3;   /* Tens digit: minutes */
0032:           uint8_t mbz_1    : 1;
0033:   } s01;

Fields with names like mbz_1, are “must be zero” fields and can otherwise be ignored.

Register 0x02 (Hours)

The hours component at byte offset 0x02 is a little more interesting because it can exist in two views. The component u03.hr12 selects a 12-hour format while union member u02.hr24 selects a 24-hour format. The view that is used is determined by the member mode_1224. When member mode_1224 is a 1-bit, then the correct view to be used is u02.hr12, otherwise u02.hr24 should be used instead.
0034:   union u_02 {                    /* Hours */
0035:           struct  {
0036:                   uint8_t hour_1s  : 4;   /* Ones digit: hours */
0037:                   uint8_t hour_10s : 1;   /* Tens digit: hours (24hr mode) */
0038:                   uint8_t ampm     : 1;   /* AM=0/PM=1 */
0039:                   uint8_t mode_1224: 1;   /* Mode bit: 12=1/24=0 hour format */
0040:           } hr12;
0041:           struct  {
0042:                   uint8_t hour_1s  : 4;   /* Ones digit: hours */
0043:                   uint8_t hour_10s : 3;   /* Tens digit: hours (24hr mode) */
0044:                   uint8_t mode_1224: 1;   /* Mode bit: 12=1/24=0 hour format */
0045:           } hr24;
0046:   } u02;

The member values hours_10s and hours_1s are again BCD values representing the hourly time as decimal digits. In the 24-hour format, there is one additional bit to describe the larger hourly 10s digit.

In the 12-hour format, the value of u02.hr12.ampm represents AM when the bit is a 0-bit, otherwise PM.

Register 0x03 (Weekday)

The weekday value is found at offset 0x03 in the field s03.wkday.
0047:   struct s_03 {                   /* Weekday */
0048:           uint8_t wkday    : 3;   /* Day of week (1-7) */
0049:           uint8_t mbz_2    : 5;
0050:   } s03;

Note that valid values range from 1 for Sunday to 7 for Saturday. Unix/Linux uses the value range of 0–6 for weekday instead.

Register 0x04 (Day of Month)

Register offset 0x04 contains the day of the month.
0051:   struct s_04 {                   /* Day of month */
0052:           uint8_t day_1s   : 4;   /* Ones digit: day of month (1-31) */
0053:           uint8_t day_10s  : 2;   /* Tens digit: day of month */
0054:           uint8_t mbz_3    : 2;
0055:   } s04;

Members day_10s and day_1s are BCD values for the day of the month with a range of 1 to 31.

Register 0x05 (Month)

Register offset 0x05 holds the month of the year.
0056:   struct s_05 {                   /* Month */
0057:           uint8_t month_1s : 4;   /* Ones digit: month */
0058:           uint8_t month_10s: 1;   /* Tens digit: month */
0059:           uint8_t mbz_4    : 2;
0060:           uint8_t century  : 1;   /* Century */
0061:   } s05;

The values month_10s and months_1s are the month’s BCD digits with a range of 1 to 12. The member century is provided to indicate a century rollover, from 1999 to 2000.

Register 0x06 (Year)

The year is provided at register offset 0x06.
0062:   struct s_06 {                   /* Year */
0063:           uint8_t year_1s  : 4;   /* Ones digit: BCD year */
0064:           uint8_t year_10s : 4;   /* Tens digit: BCD year */
0065:   } s06;

The year_10s and year_1s member pair provide the BCD digits for the year.

Register 0x07 (Alarm1 Seconds)

The DS3231 chip supports two alarms. This register provides the Alarm 1 seconds value.
0066:   struct s_07 {                   /* Alarm Seconds */
0067:           uint8_t alrms01  : 4;   /* Alarm BCD 1s seconds */
0068:           uint8_t alrms10  : 3;   /* Alarm BCD 10s Seconds */
0069:           uint8_t AxM1     : 1;   /* Alarm Mask 1 */
0070:   } s07;

Members alrms01 and alrms10 form the pair of seconds digits in BCD form for Alarm 1. Bit field AxM1 is a bit, determines that the seconds must match for the alarm (AxM1=0), or that the alarm is triggered for every second (AxM1=1).

Register 0x08 (Alarm1 Minutes)

The minutes for Alarm 1 are specified by the register at offset 0x08.
0071:   struct s_08 {                   /* Alarm Minutes */
0072:           uint8_t alrmm01  : 4;   /* Alarm BCD 1s Minutes */
0073:           uint8_t alrmm10  : 3;   /* Alarm BCD 10s Minutes */
0074:           uint8_t AxM2     : 1;   /* Alarm Mask 2 */
0075:   } s08;

The members alrmm10 and alrmm01 specify the BCD pair defining the minutes for the alarm, according to the mask bit AxM2.

Register 0x09 (Alarm 1 Hours)

Register offset 0x09 holds the hour time of the alarm.
0076:   union u_09 {                    /* Alarm Hours */
0077:           struct  {
0078:                   uint8_t alr_hr10 : 1;   /* Alarm 10s Hours */
0079:                   uint8_t alr_ampm : 1;   /* Alarm am=0/pm=1 */
0080:                   uint8_t alr_1224 : 1;   /* Alarm 12=1 */
0081:                   uint8_t AxM3     : 1;   /* Alarm Mask 3 */
0082:           } ampm;
0083:           struct  {
0084:                   uint8_t alr_hr10 : 2;  /* Alarm 10s Hours */
0085:                   uint8_t alr_1224 : 1;   /* Alarm 24=0 */
0086:                   uint8_t AxM3     : 1;   /* Alarm Mask 3 */
0087:           } hrs24;

Like the union u_02 described earlier, there are two views depending upon whether 12 or 24 hour format is used. The hours apply according to the mask bit AxM3.

Register 0x0A (Alarm 1 Date)

The date of the alarm to be applied is given by the offset 0x0A in the DS1332 register file.
0089:   union u_0A {                    /* Alarm Date */
0090:           struct  {
0091:                   uint8_t day1s    : 4;   /* Alarm 1s date */
0092:                   uint8_t day10s   : 2;   /* 10s date */
0093:                   uint8_t dydt     : 1;   /* Alarm dy=1 */
0094:                   uint8_t AxM4     : 1;   /* Alarm Mask 4 */
0095:           } dy;
0096:           struct  {
0097:                   uint8_t day1s    : 4;   /* Alarm 1s date */
0098:                   uint8_t day10    : 3;   /* Alarm 10s date */
0099:                   uint8_t dydt     : 1;   /* Alarm dt=0 */
0100:                   uint8_t AxM4     : 1;   /* Alarm Mask 4 */
0101:           } dt;
0102:   } u0A;

This is a union of two views according to whether a weekday (dydt=1) or a day of the month is used (dydt=0). The pair day10s and day1s is the BCD pair specifying the date. The mask AxM4 determines how the date factors into the alarm.

Alarm 2

Alarm 2 is like Alarm 1, except that it lacks the seconds specifier.
0103:   struct s_08 s0B;                /* Alarm 2 Minutes */
0104:   union u_09 u0C;                 /* Alarm 2 Hours */
0105:   union u_0A u0D;                 /* Alarm 2 Date */

It otherwise is used the same way.

Register 0x0E (Control)

Register offset 0x0E offers some control options.
0106:   struct s_0E {                   /* Control */
0107:           uint8_t A1IE     : 1;   /* Alarm 1 Int enable */
0108:           uint8_t A2IE     : 1;   /* Alarm 2 Int enable */
0109:           uint8_t INTCN    : 1;   /* SQW signal when 1 */
0110:           uint8_t RS1      : 1;   /* Rate select 1 */
0111:           uint8_t RS2      : 1;   /* Rate select 2 */
0112:           uint8_t CONV     : 1;   /* Temp conversion */
0113:           uint8_t BBSQW    : 1;   /* Enable square wave */
0114:           uint8_t NEOSC    : 1;   /* /EOSC: Enable */
0115:   } s0E;

Member bits A1IE and A2IE enable alarm interrupts when set to a 1-bit. INTCN determines whether the chip emits an interrupt signal (active low) or a square wave output (INTCN=1). Option BBSQW must also be set to a 1-bit to enable the square wave output. Bits RS1 and RS2 when both set to zero choose a 1 Hz rate for the square wave output. CONV is used to enable the reading of the chip temperature. Finally, bit NEOSC (not EOSC) enables the oscillator when a 0-bit and otherwise stops the oscillator when true.

Reading Temperature

The DS3231 keeps accurate time in part because it monitors its own temperature and applies compensation. The temperature can be read by performing the following:
  1. 1.

    Check that the BSY flag and CONV flag is not set.

     
  2. 2.

    Set the CONV flag to begin the conversion.

     
  3. 3.

    When the CONV flag resets to zero, read the register value at offsets 0x11 and 0x12.

     

Register 0x0F (Control/Status)

More control and status bits are available at register offset 0x0F.
0116:   struct s_0F {                   /* Control/status */
0117:           uint8_t A1F      : 1;   /* Alarm 1 Flag */
0118:           uint8_t A2F      : 1;   /* Alarm 2 Flag */
0119:           uint8_t bsy      : 1;   /* Busy flag */
0120:           uint8_t en32khz  : 1;   /* Enable 32kHz out */
0121:           uint8_t zeros    : 3;
0122:           uint8_t OSF      : 1;   /* Stop Osc when 1 */
0123:   } s0F;

Flags A1F and A2F indicate when the respective alarm has been triggered. The member bsy is the device’s busy flag. Member en32khz enables a 32 kHz signal output, when combined with other options. The oscillator is stopped when flag OSF is set to a 1-bit.

Register 0x10 (Aging)

The aging value used internally by the DS3231 to adjust the timekeeping according to temperature and can be read from this register. It is a signed 8-bit number.
0124:   struct s_10 {                   /* Aging offset */
0125:           int8_t data      : 8;   /* Data */
0126:   } s10;

Register 0x11 and 0x12 (Temperature)

This pair of registers is used to read the internal temperature of the DS3231, to a quarter of a Celsius degree.
0127:   struct s_11 {
0128:           int8_t temp      : 8;   /* Signed int temp */
0129:   } s11;
0130:   struct s_12 {
0131:           uint8_t mbz      : 6;
0132:           uint8_t frac     : 2;   /* Fractional temp bits */
0133:   } s12;
The value of s11.temp contains the integer component in degrees Celsius. The s12.frac contains a pair of bits specifying a value of 0 to 3. The formation of the temperature can be determined by:
s11.temp + (float) s12.frac * 0.25;

Reading from DS3231

The full source code for the program ds3231.c is provided in the directory:
$ cd ~/RPi/ds3231
Build or force rebuilt it as follows:
$ make clobber
rm -f *.o core errs.t
rm -f ds3231
$ make
gcc -c -Wall -O0 -g ds3231.c -o ds3231.o
gcc ds3231.o -o ds3231
sudo chown root ./ds3231
sudo chmod u+s ./ds3231
The function that performs the read from the DS3231 device is illustrated in Listing 25-2.
0136: static const char *node = "/dev/i2c-1";
0137: static int i2c_fd = -1;                  /* Device node: /dev/i2c-1 */
...
0175: static bool
0176: i2c_rd_rtc(ds3231_regs_t *rtc) {
0177:   struct i2c_rdwr_ioctl_data msgset;
0178:   struct i2c_msg iomsgs[2];
0179:   char zero = 0x00;               /* Register 0x00 */
0180:
0181:   iomsgs[0].addr = 0x68;          /* DS3231 */
0182:   iomsgs[0].flags = 0;            /* Write */
0183:   iomsgs[0].buf = &zero;          /* Register 0x00 */
0184:   iomsgs[0].len = 1;
0185:
0186:   iomsgs[1].addr = 0x68;          /* DS3231 */
0187:   iomsgs[1].flags = I2C_M_RD;     /* Read */
0188:   iomsgs[1].buf = (char *)rtc;
0189:   iomsgs[1].len = sizeof *rtc;
0190:
0191:   msgset.msgs = iomsgs;
0192:   msgset.nmsgs = 2;
0193:
0194:   return ioctl(i2c_fd,I2C_RDWR,&msgset) == 2;
0195: }
Listing 25-2

The i2c_rd_rtc() function for the DS3231

This code assumes that the I2C bus has already been opened and the file descriptor saved in i2c_fd (line 137). Two messages are assembled into array iomsgs in line 178. Lines 181 through 184 prepare a write of one byte to indicate that we are addressing starting with register offset 0x00 in the DS3231. Lines 186 to 189 prepare a message to read all of the device’s register into buffer structure rtc. The read is a success when ioctl(2) returns 2, indicating that two messages were carried out successfully.

Writing to DS3231

Listing 25-3 lists the function used to write to the DS3231 device.
0198: static bool
0199: i2c_wr_rtc(ds3231_regs_t *rtc) {
0200:   struct i2c_rdwr_ioctl_data msgset;
0201:   struct i2c_msg iomsgs[1];
0202:   char buf[sizeof *rtc + 1];      /* Work buffer */
0203:
0204:   buf[0] = 0x00;                  /* Register 0x00 */
0205:   memcpy(buf+1,rtc,sizeof *rtc);  /* Copy RTC info */
0206:
0207:   iomsgs[0].addr = 0x68;          /* DS3231 Address */
0208:   iomsgs[0].flags = 0;            /* Write */
0209:   iomsgs[0].buf = buf;            /* Register + data */
0210:   iomsgs[0].len = sizeof *rtc + 1; /* Total msg len */
0211:
0212:   msgset.msgs = &iomsgs[0];
0213:   msgset.nmsgs = 1;
0214:
0215:   return ioctl(i2c_fd,I2C_RDWR,&msgset) == 1;
0216: }
Listing 25-3

The function i2c_wr_rtc() for writing to the DS3231

This function is similar to the reading function except that only one ioctl(2) message is required. The RTC values to be written are copied to buf in line 205. The first byte is set to 0x00 to indicate the register number that we want to start writing to. The message is prepared in lines 207 to 210, and the actual write is performed in line 215. The operation is a success when ioctl(2) returns the value 1, indicating one successful message was executed.

Reading Temperature

For those that like to know the temperature, the function that reads the temperature is provided in Listing 25-4.
0221: static float
0222: read_temp(void) {
0223:   ds3231_regs_t rtc;
0224:
0225:   do      {
0226:           if ( !i2c_rd_rtc(&rtc) ) {
0227:                   perror("Reading RTC for temp.");
0228:                   exit(2);
0229:           }
0230:   } while ( rtc.s0F.bsy || rtc.s0F.CONV ); /* Until not busy */
0231:
0232:   rtc.s0E.CONV = 1;               /* Start conversion */
0233:
0234:   if ( !i2c_wr_rtc(&rtc) ) {
0235:           perror("Writing RTC to read temp.");
0236:           exit(2);
0237:   }
0238:
0239:   do      {
0240:           if ( !i2c_rd_rtc(&rtc) ) {
0241:                   perror("Reading RTC for conversion.");
0242:                   exit(2);
0243:           }
0244:   } while ( rtc.s0E.CONV );       /* Until converted */
0245:
0246:   return rtc.s11.temp + (float)rtc.s12.frac * 0.25;
0247: }
Listing 25-4

Reading the DS3231 temperature

The function first loops while the s0F.bsy or s0F.CONV flag is true, indicating that the device is busy. The s0E.CONV flag is set in line 232 and then written out in line 234 to the device. After that, the DS3231 is polled to see when the s0E.CONV flag returns to zero. Once s0E.CONV is reset, we can safely read the temperature from the s11 and s12 registers (line 246).

Demo Time

Once the demonstration has been compiled and wired up according to Figure 25-3, we can exercise the C program. The -h option reports usage information:
$ ./ds3231 -h
Usage:  /ds3231 [-S time] [-f format] [-d] [-e] [-v] [-h]
where:
        -s      Set RTC clock based upon system date
        -f fmt  Set date format
        -e      Enable 1 Hz output on SQW
        -d      Disable 1 Hz output on SQW
        -t      Display temperature
        -S time Set DS3231 time from given
        -v      Verbose, show SQW register settings
        -h      This help

Read Date/Time

To read the device’s current date/time, just run the program:
$ ./ds3231
RTC time is 2018-08-05 00:54:55 (Sunday)

If you get an error, run the i2c-detect again and make sure that the device is wired correctly.

Read Temperature

To read the temperature, use the -t option:
$ ./ds3231 -t
RTC time is 2018-08-05 01:23:06 (Sunday)
Temperature is 26.75 C

The accuracy of the temperature reading is fairly decent and this fact is instrumental in its timekeeping stability.

Setting RTC

You can set the RTC time using the -S option:
$ ./ds3231 -S '2018-08-04 12:00:00'
Set RTC to 2018-08-04 12:00:00 (Saturday)
RTC time is 2018-08-04 12:00:00 (Saturday)

The second printed line is the value read back from the RTC device. If you need to use a different format for date/time, supply the -f option.

1 Hz Square Wave

To test the 1 Hz SQW output, use the -e (enable) command. The -v (verbose) option just confirms with some values displayed:
$ ./ds3231 -ev
RTC time is 2018-08-05 01:24:34 (Sunday)
 BBSQW=1 INTCN=0 RS2=0 RS1=0
Now run the evinput program that was used earlier in the book to monitor the GPIO 22 pin (assuming you wired it as per Figure 25-3). Substitute the GPIO you used, if you chose another:
$ ../evinput/evinput -g22
Monitoring for GPIO input changes:
GPIO 22 changed: 0
GPIO 22 changed: 1
GPIO 22 changed: 0
GPIO 22 changed: 1
GPIO 22 changed: 0
GPIO 22 changed: 1
GPIO 22 changed: 0

From the output, you can see that the input changes at a rate of about one half second (a full cycle requires one second).

Kernel Support

Up until now, we have played with the DS3231 device using a C program. But you might already know that Raspbian Linux has kernel module support for the DS3231. Before setting it up, you need to disable ntp (at least temporarily):
# systemctl disable systemd-timesyncd.service
To configure ds3231 module support in the kernel, perform the following:
  1. 1.

    sudo -i

     
  2. 2.

    Edit /boot/config.txt and add line “dtoverlay=i2c-rtc,ds3231” to the file at the end.

     
  3. 3.

    Uncomment or add “dtparam=i2c_arm=on” to the same file.

     
  4. 4.
    Edit file /lib/udev/hwclock-set and comment out the following three lines, by placing a hash (#) in front:
    #if [ -e /run/systemd/system ] ; then
    #    exit 0
    #fi
     
  5. 5.

    Reboot

     
After rebooting, try the following:
# hwclock -r
2018-08-04 08:13:18.719447-0400
You can check it against the ds3231 program:
# ~pi/RPi/ds3231/ds3231
RTC time is 2018-08-04 12:14:23 (Saturday)
There is a difference of four hours here but this is the difference in timezone and daylight savings time. As my Pi is configured, it keeps the time in UTC. To get the DS3231, the kernel module, and the system date all on the same page, you can perform the following:
# ~pi/RPi/ds3231/ds3231 -S '2018-08-05 02:22:00'
Set RTC to 2018-08-05 02:22:00 (Sunday)
RTC time is 2018-08-05 02:22:00 (Sunday)
root@rpi3bplus:~# hwclock -s
root@rpi3bplus:~# date
Sat Aug  4 22:22:26 EDT 2018
root@rpi3bplus:~#

This can be confusing when there is a timezone offset involved. In the ds3231 -S command, I set the date/time to the UTC date/time required. Then the command hwclock -s causes it to reset the system sense of time from the DS3231 chip. Following that, the date command reports the local time with my timezone offset.

Summary

The ds3231 is the perfect mate for the Raspberry Pi, particularly when they are not network connected. With its battery backup, the ds3231 will keep accurate time to within a few seconds every month.

The program presented gave you insight into the operation of the chip as well as practice communicating with it from C. Within the C program, a demonstration of the bit fields language feature was applied. This is something often only seen in system or device programming.

Finally, the Raspbian kernel support for the ds3231 was demonstrated so that you can use it in future Pi projects. If you’re looking for an I2C project, why not try communicating with that onboard EEPROM?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.100.42