Northern WIdget
Alarm Functions

The DS3231 offers two, versatile, independent alarms.

Every second, a DS3231 updates the values in its "time" registers: second, minute, hour, day, date, month and year.

The hardware then instantly compares the new time values to corresponding values stored in the two alarm registers: second, minute, hour, and either day or date depending on a certain alarm setting.

When the values match for one of the alarms, the hardware writes a "flag" bit for that alarm to HIGH, that is, to logic 1.

What happens after that flag bit goes HIGH is... anything the developer can imagine!

This Library provides functions for setting, managing and detecting the alarms.

Optionally, the DS3231 can be configured to interrupt the Arduino when an alarm occurs. An Arduino program can execute specific instructions immediately upon receiving the interrupt.

Interrupts from a DS3231 enable an Arduino to perform time-sensitive tasks very accurately, without the need for any delay() statements, polling or timing loops running on the Arduino!

An example program demonstrates using an alarm-triggered interrupt to blink an LED.


Contents


Arduino Code Requirements

A program needs certain software resources to work with DS3231 alarms.

This includes a set of variables that are required in the parameter lists by some of the alarm methods. The listing below identifies the resources and suggests descriptive names a programmer could choose for the variables.

All of the code examples in this article will use the variables by these names in parameter lists, including those of functions that could otherwise use numeric constants.


/*H********************************************************************
*
**********************************************************************/
#include <DS3231.h>  // import this Library
//************************* DEFINES ************************************

//************************* PROTOTYPES ************************************
//************************* VARIABLES ************************************
DS3231 myRTC;        // declare an object for access to the alarm methods
// declare variables to use with the alarm methods
// for the hour of an alarm
bool alarmH12;       // (true) if hour is 1 - 12, (false) if hour is 00 - 23
byte alarmHour;      // 1-12 if 12-hour mode, 0-23 if 24-hour mode
bool alarmPM;        // (true) if 12-hour time is PM, (false) if AM
byte alarmMinute;    // 00 - 59
byte alarmSecond;    // 00 - 59
// for the day or date of an alarm
byte alarmDay;       // 1-7 if day of week, 1-31 if date in month
bool alarmIsDay;     // (true) if alarmDay is a day of the week, (false) if date in month
// for the frequency of an alarm
byte alarmBits;      // a bitfield, to be explained below
/*F********************************************************************
*
**********************************************************************/
void 
setup() 
{
    Wire.begin();      // establish I2C communications
}

The following code fragment illustrates setting Alarm 1 to activate at 12:00 noon once per week, on Mondays. It makes use of the software resources described above.


/*H********************************************************************
* assign values to program variables
**********************************************************************/
alarmH12 = true;  // use 12-hour time mode
alarmHour = 12; // ambiguous value in 12-hour time mode
alarmPM = true; // determines that 12 means "noon"
alarmMinute = 0;
alarmSecond = 0;
// The following assignment will be correct only if
// the programmer determined 1 to mean Sunday
// when setting the time in the DS3231 clock registers.
alarmDay = 2; // 2 = Monday
// alarmDay to be interpreted as day in the week, not as date in the month
alarmIsDay = true; 
alarmBits = 0b00000000; // alarm when day, hours, minutes and seconds match the time
// write to the DS3231 time registers for Alarm 1
myRTC.setA1Time(alarmDay, alarmHour, alarmMinute, alarmSecond, alarmBits, alarmIsDay, alarmH12, alarmPM);

Back to Contents


Alarm Bits Quick Reference

Two of the alarm values shown in the foregoing example combine to determine the frequency of an alarm:

In total, eleven different combinations of values exist for these two parameters. The following table lists the combinations and explains the result that each combination produces.

alarmIsDay alarmBits Explanation
1 (ignored) 0b00001111 Alarm 1 every second. All other settings for Alarm 1 are ignored.
2 (ignored) 0b00001110 Alarm 1 every minute, when the Alarm 1 seconds value matches the seconds value of the time. All other settings for Alarm 1 are ignored.
3 (ignored) 0b00001100 Alarm 1 every hour, when the Alarm 1 minutes and seconds values match those of the time. All other settings for Alarm 1 are ignored.
4 (ignored) 0b00001000 Alarm 1 every day, when the Alarm 1 hours, minutes and seconds values match those of the time. The time modes, 24- or 12-hour and AM/PM, are evaluated.
5 True
(Day of Week)
0b00000000 Alarm 1 every week, when the Alarm 1 day, hours, minutes and seconds values match those of the time. The time modes, 24- or 12-hour and AM/PM, are evaluated.
6 False
(Date in Month)
0b00000000 Alarm 1 every month, when the Alarm 1 date, hours, minutes and seconds values match those of the time. The time modes, 24- or 12-hour and AM/PM, are evaluated.
7 (ignored) 0b01110000 Alarm 2 every minute, when the seconds value of the time equals zero. All other settings for Alarm 2 are ignored.
8 (ignored) 0b01100000 Alarm 2 every hour, when the Alarm 2 minutes value matches that of the time and the time seconds value equals zero. All other settings for Alarm 2 are ignored.
9 (ignored) 0b01000000 Alarm 2 every day, when the Alarm 2 hours and minutes values match those of the time and the time seconds value equals zero. The time modes, 24- or 12-hour and AM/PM, are evaluated.
10 True
(Day in Week)
0b00000000 Alarm 2 every week, when the Alarm 2 day, hours and minutes values match those of the time and the time seconds value equals zero. The time modes, 24- or 12-hour and AM/PM, are evaluated.
11 False
(Date in Month)
0b00000000 Alarm 2 every month, when the Alarm 2 date, hours and minutes values match those of the time and the time seconds value equals zero. The time modes, 24- or 12-hour and AM/PM, are evaluated.

Notice that combinations 5 and 6 appear identical to 11 and 12. Fortunately, the methods for setting Alarm 1 and Alarm 2 prevent confusion. Each method sets values for just one of the alarms.

Back to Contents


Alarm Functions

The illustrations in this section assume that the program has already declared a DS3231 object named ```myRTC``. For example:

DS3231 myRTC; // create a DS3231 object

getA1Time()


/*H*******************************************************
 * Retrieves values from the Alarm 1 time registers then stores the values
 * in the external variables passed to the parameter list.
 * NOTE: previous values of the parameter variables are over-written.
 * returns: nothing (void)
 * parameters (all of these are references to external variables):
 *   byte& A1Day,      // "byte&" refers to a byte variable
 *   byte& A1Hour,     // 
 *   byte& A1Minute,   //
 *   byte& A1Second,   //
 *   byte& AlarmBits,  //
 *   bool& A1Dy,       // "bool&" refers to a boolean variable
 *   bool& A1h12,      //
 *   bool& A1PM        //
 ********************************************************/
void DS3231::
getA1Time( byte &A1Day, byte &A1Hour, byte &A1Minute
    , byte &A1Second, byte &AlarmBits, bool &A1Dy
    , bool &A1h12, bool &A1PM);

The variables passed in the parameter list must match the types of the parameters but need not match the names.

Back to Contents




getA1Time() with Option


/*H*******************************************************
 * Retrieves values from the Alarm 1 time registers then stores the values
 * in the external variables passed to the parameter list.
 * NOTE: previous values of the parameter variables are over-written.
 * Overloads getA1Time() to include clearing the AlarmBits parameter variable
 * returns: nothing (void)
 * parameters (the following are references to external variables):
 *   byte& A1Day,        // "byte&" refers to a byte variable
 *   byte& A1Hour,       // 
 *   byte& A1Minute,     //
 *   byte& A1Second,     //
 *   byte& AlarmBits,    //
 *   bool& A1Dy,         // "bool&" refers to a boolean variable
 *   bool& A1h12,        //
 *   bool& A1PM,         //
 *   // the additional parameter is passed by value,
 *   // meaning that a literal true or false keyword may be used
 *   bool clearAlarmBits // (true) = clear the AlarmBits parameter 
 ********************************************************/
void DS3231::
getA1Time( byte& A1Day, byte& A1Hour, byte& A1Minute, 
   byte& A1Second, byte& AlarmBits, bool& A1Dy, 
   bool& A1h12, bool& A1PM, bool clearAlarmBits);

The variables passed in the parameter list must match the types of the parameters but need not match the names.

Managing the Retrieved AlarmBits Value

Remember that the variable chosen to receiving the AlarmBits values is an 8-bit "bitfield".

Each of the alarm "get" functions addresses only the bits for that alarm . This means that the variable receiving the AlarmBits value will wind up containing:

Any one of the following alternative approaches can guard against irrelevant bits cluttering the AlarmBits variable. The illustrations assume the variable holding the bitfield is named "AlarmBits".

Approach 1. After calling "get", use the bitwise & operator to distill only the desired bits.

Approach 2. Before calling "get", clear the bitfield by assigning 0 to the variable.

Approach 3. Set the optional "clearAlarmBits" boolean parameter in the "get" function to "true".

Back to Contents




getA2Time()


/*H*******************************************************
 * Retrieves values from the Alarm 2 time registers then stores the values
 * in the external variables passed to the parameter list.
 * NOTE: previous values of the parameter variables are over-written.
 * returns: nothing (void)
 * parameters (all of these are references to external variables):
 *   byte& A2Day,      // "byte&" refers to a byte variable
 *   byte& A2Hour,     // 
 *   byte& A2Minute,   //
 *   byte& AlarmBits,  //
 *   bool& A2Dy,       // "bool&" refers to a boolean variable
 *   bool& A2h12,      //
 *   bool& A2PM        //
 ********************************************************/
void DS3231::
getA2Time( byte& A2Day, byte& A2Hour, byte& A2Minute
    , byte& A2Second, byte& AlarmBits, bool& A2Dy
    , bool& A2h12, bool& A2PM);

The variables passed in the parameter list must match the types of the parameters but need not match the names.

Back to Contents




getA2Time() with Option


/*H*******************************************************
 * Retrieves values from the Alarm 2 time registers then stores the values
 * in the external variables passed to the parameter list.
 * NOTE: previous values of the parameter variables are over-written.
 * Overloads getA2Time() to include clearing the AlarmBits parameter variable
 * returns: nothing (void)
 * parameters (the following are references to external variables):
 *   byte& A2Day,        // "byte&" refers to a byte variable
 *   byte& A2Hour,       // 
 *   byte& A2Minute,     //
 *   byte& A2Second,     //
 *   byte& AlarmBits,    //
 *   bool& A2Dy,         // "bool&" refers to a boolean variable
 *   bool& A2h12,        //
 *   bool& A2PM,         //
 *   // the additional parameter is passed by value,
 *   // meaning that a literal true or false keyword may be used
 *   bool clearAlarmBits // (true) = clear the AlarmBits parameter 
 ********************************************************/
void DS3231::
getA1Time( byte& A2Day, byte& A2Hour, byte& A2Minute
    , byte& A2Second, byte& AlarmBits, bool& A2Dy
    , bool& A2h12, bool& A2PM, bool clearAlarmBits);

The variables passed in the parameter list must match the types of the parameters but need not match the names.

See the discussion of Managing the Retrieved AlarmBits Value , above, for more information about the purpose and usage of this optional method.

Back to Contents




setA1Time()


/*H*******************************************************
 * transfers the parameter values to Alarm 1 registers inside the DS3231
 * returns: nothing (void)
 * parameters:
 *   byte A1Day, 
 *   byte A1Hour, 
 *   byte A1Minute, 
 *   byte A1Second
 *   byte AlarmBits, 
 *   bool A1Dy, 
 *   bool A1h12, 
 *   bool A1PM
 *
 * depends upon I2C connection to DS3231 but does not validate it
 ********************************************************/
void DS3231::
setA1Time( byte A1Day, byte A1Hour, byte A1Minute, byte A1Second
    , byte AlarmBits, bool A1Dy, bool A1h12, bool A1PM);

Alarm 1 has a parameter for seconds of time. By contrast, Alarm 2 (below) does not.

Back to Contents




setA2Time()


/*H*******************************************************
 * transfers the values to Alarm 2 registers inside the DS3231
 * returns: nothing (void)
 * parameters:
 *   byte A2Day, 
 *   byte A2Hour, 
 *   byte A2Minute, 
 *   byte AlarmBits, 
 *   bool A2Dy, 
 *   bool A2h12, 
 *   bool A2PM
 ********************************************************/
void DS3231::
setA2Time( byte A2Day, byte A2Hour, byte A2Minute, byte AlarmBits
    , bool A2Dy, bool A2h12, bool A2PM);

Alarm 2 does not have a parameter for seconds of time. By contrast, Alarm 1 (above) does.

Back to Contents




turnOnAlarm()


/*H*******************************************************
 * Modifies control register 0x0E of DS3231 to enable the chosen alarm interrupt
 * returns: nothing (void)
 * one parameter:
 *     Alarm, 1 or 2, determines which alarm interrupt to enable
********************************************************/
void 
turnOnAlarm( byte Alarm );

Consider clearing both of the alarms' flags after enabling an interrupt, even if only one alarm is in use.

The reason is that the DS3231 cannot signal an interrupt if either one of the flags is set HIGH, that is, equal to logic 1.

See the discussion of checkIfAlarm() , below.


myRTC.turnOnAlarm(1); // enable alarm 1 interrupt
myRTC.checkIfAlarm(1); // clear alarm 1 flag
myRTC.checkIfAlarm(2); // better yet, clear both of the flags

Back to Contents




turnOffAlarm()


/*H*******************************************************
 * Modifies control register 0x0E of DS3231 to disable the chosen alarm interrupt
 * returns: nothing (void)
 * one parameter:
 *     Alarm, 1 or 2, determines which alarm interrupt to disable
 ********************************************************/
void 
turnOffAlarm( byte Alarm );

Disabling interrupts for one of the alarms has no effect on the other alarm.

Back to Contents




checkAlarmEnabled()


/*H*******************************************************
 * Retrieves the interrupt status bit of the chosen alarm.
 * returns: boolean, 
 *     true = bit is set (enabled), 
 *     false = bit is clear (disabled)
 * one parameter:
 *     Alarm, 1 or 2, determines which alarm to check
 ********************************************************/
bool 
checkAlarmEnabled( byte Alarm );

Back to Contents




checkIfAlarm()


/*H*******************************************************
 * Retrieves the value of the alarm flag for the chosen alarm
 * returns: boolean,
 *     (true) if the flag is set = logic 1, 
 *     (false) if the flag is clear = logic 0
 * only one parameter:
 *     Alarm, 1 or 2, determines which alarm flag to examine
 * side effect:
 *     Clears the alarm flag after examining it, also.
 ********************************************************/
bool 
checkIfAlarm( byte Alarm );

NOTE TO PROGRAMMERS: this version of the method always overwrites the value of the alarm flag to zero. An alternate version, below, allows programs an option to preserve the value of the flag in the DS3231.

It may be desirable to do so in programs that re-use a single alarm repeatedly.

The flag value is returned to the program, where it may be assigned to a program variable for later evaluation and use.

Keep two things in mind:

  1. The alarm flag must be reset to zero (cleared) by the program before a subsequent alarm event can be detected.
  2. This method is the only one in the Library that can clear an alarm flag with certainty.

Back to Contents




checkIfAlarm() with Option


/*H*******************************************************
 * Retrieves the value of the alarm flag for the chosen alarm.
 * Adds a parameter allowing the alarm flag to be preserved in the DS3231.
 * returns: boolean,
 *     (true) if the flag is set = logic 1, 
 *     (false) if the flag is clear = logic 0
 * two parameters:
 *     Alarm, 1 or 2, determines which alarm flag to examine
 *     clearFlag, determines whether to clear the alarm flag
 *         (true) clears the flag
 *         (false) preserves the value of the flag in the DS3231
 * side effect:
 *     Clears the alarm flag only if the clearFlag parameter is equal to true
 ********************************************************/
bool 
checkIfAlarm( byte Alarm, bool clearFlag );

This version of the method may be preferable when both alarms are in use, with interrupts enabled, and the program desires to preserve the flags in the hardware temporarily.

Version 2 performs exactly like Version 1, above, clearing the flag after checking, when the "clearFlag" parameter is equal to "true".

Note that this version cannot guarantee to clear the alarm flag automatically, because it will not clear the flag if the program passes a value of "false" to the second parameter.

Back to Contents




Alarm Bits in Detail

Here is more about the magic of DS3231 alarms: the alarmBits parameter. It needs careful understanding and rewards attentive study.

It is a bitfield , that is, an array of individual bits, where each bit has a certain, definite meaning.

Actually, the AlarmBits parameter should be understood as a pair of bitfields . The least significant four bits, 0 through 3, regulate Alarm 1, while bits 4-6 apply to Alarm 2. Bit 7 is disregarded.

The parameter looks like an 8-bit integer but has no practical meaning as such. Instead, each, individual bit enables or disables a specific feature of an alarm in the DS3231.

The following table illustrates the arrangement of the two bitfields in the AlarmBits parameter.

Bit7 Alarm 2 Alarm 1
X bits 6:4 bits 3:0

Important, for Alarm 2 : locate the alarm bits in positions 6, 5 and 4 of the AlarmBits parameter for the functions that set and retrieve values for Alarm 2. This is addressed in greater detail below.

Locating the Alarm Bits

It will be helpful to name the seven bits. Inside the DS3231, each bit is stored separately, as Bit 7 in each of the 8-bit data registers storing values for the alarm.

Alarm 1
Register Bit7 Bits 6:0
0x07 A1M1 Second
0x08 A1M2 Minute
0x09 A1M3 Hour
0x0A A1M4 Day
Alarm 2
Register Bit7 Bits 6:0
0x0B A2M2 Minute
0x0C A2M3 Hour
0x0D A2M4 Day

By contrast, the bits are stored adjacently in the parameter. It is very convenient for programming purposes to arrange them into a single byte this way.

Note again that Bit 7 in the parameter is marked "X", which means it is ignored.

alarmBits Parameter Map
7 6 5 4 3 2 1 0
X A2M4 A2M3 A2M2 A1M4 A1M3 A1M2 A1M1

Clearly, the alarm setting and retrieving functions perform an elaborate but beneficial operation with these bits. They translate the bits from adjacent positions in a bitfield (the AlarmBits parameter) to separate registers in the DS3231, and vice versa.

Alarm Bits Determine Alarm Rate

The bits combine with the alarmIsDay parameter to form a "mask" that governs the Rate of an alarm. That is, how often, on which occasions, an alarm will occur.

The following table is adapted from Table 2 on page 12 of the data sheet . The column labeled DY/DT reflects the boolean value of the alarmIsDay parameter listed above. An "X" means the value of DY/DT is ignored.

Alarm 1
DY/DT Alarm 1 Mask Bits Alarm Rate
A1M4 A1M3 A1M2 A1M1
X 1 1 1 1 Alarm once per second
X 1 1 1 0 Alarm when seconds match
X 1 1 0 0 Alarm when minutes and seconds match
X 1 0 0 0 Alarm when hours, minutes and seconds match
true 0 0 0 0 Alarm when day, hours, minutes and seconds match
false 0 0 0 0 Alarm when date, hours, minutes and seconds match
Alarm 2
DY/DT Alarm 2 Mask Bits Alarm Rate
A2M4 A2M3 A2M2
X 1 1 1 Alarm when seconds == 0 (once per minute)
X 1 1 0 Alarm when minutes match
X 1 0 0 Alarm when hours and minutes match
true 0 0 0 Alarm when day, hours and minutes match
false 0 0 0 Alarm when date, hours and minutes match

The binary number format might be the most clear way to spell out a constant value for the AlarmBits parameter. The following example would prepare a parameter writing bits 6 and 5 to logic 1, and bit 4 to logic 0, so that Alarm 2 would occur when the minutes value of the time first matches that of the alarm time. It will be used in the example for advancing the alarm time, in the next section.

byte AlarmBits = 0b01100000; // alarm when minutes match

Back to Contents


How to Advance an Alarm Time

Problem : your program needs to execute a procedure at precise, ten-minute intervals. You would like to have the DS3231 generate an interrupt every ten minutes. The DS3231 hardware supports automatically repeating interrupts at intervals of one second, one minute, one hour, one day, one week or one month, but not ten minutes.

Solution : write program code to advance the alarm time by ten minutes after an alarm interrupt is received.

The code fragment listed below illustrates an approach that could be used for the purpose. It makes use of the versatile DateTime class and other resources that accompany the DS3231 class in this Library.

Please keep two thoughts in mind.

  1. Interrupts are automatically disabled globally by AVR chip hardware when entering an Interrupt Service Routine (ISR).
  2. DS3231 class methods use I2C for communications with the DS3231. I2C requires interrupts. For best results, code that needs to communicate with a DS3231 should not be executed while an ISR is running.

For this example to work, the ISR could be as short as one instruction: simply set a designated global boolean variable "true". The change can notify the idle process (called "loop()" in Arduino code) to execute the following code fragment in response to the interrupt.


DateTime alarmTime = RTClib::now();           // CAPTURE TIME ALARM OCCURRED
myRTC.turnOffAlarm( 2 );                 // DISABLE aLARM 2 INTERRUPT OUTPUT
  // CLEAR BOTH ALARM FLAGS BEFORE DS3231 CAN OUTPUT ANOTHER ALARM INTERRUPT
    // CAPTURE ALARM 1 FLAG FOR LATER ASSESSMENT AND AUTO CLEAR ALARM 1 FLAG
bool alarm1Flag = myRTC.checkIfAlarm( 1 );
myRTC.checkIfAlarm( 2 );      // CLEAR ALARM 2 FLAG. NO NEED TO RETAIN VALUE
                    // PREPARE PARAMETER VALUES FOR SETTING A NEW ALARM TIME
alarmBits = 0b01100000;                        // ALARM 2 WHEN MINUTES MATCH
alarmH12 = false;                          // INTERPRET HOUR IN 24-HOUR MODE
alarmPM = false;         // IRRELEVANT IN 24-HOUR MODE, BUT IT NEEDS A VALUE
alarmIsDay = false;                // INTERPRET "DAY" VALUE AS DATE IN MONTH
uint32_t nextAlarm = alarmTime.unixtime() + 600;   // ADD 600 SECS (10 MINS)
alarmTime = DateTime( nextAlarm );              // UPDATE VALUES IN dATEtIME
 // SET NEXT TIME FOR ALARM 2 NOTE THAT ONLY "MINUTES" VALUE IS SIGNIFICANT,
      // MUST SUPPLY DAY AND HOUR ALSO, AND DOES NO HARM TO SUPPLY REAL ONES
myRTC.setA2Time (                   // GET THESE VALUES FROM DATETIME OBJECT
    alarmTime.day(),           // FROM A dATEtIME, WILL BE DATE OF THE MONTH
    alarmTime.hour(),          // FROM A dATEtIME, WILL BE IN 24-HOUR FORMAT
    alarmTime.minute(),
    alarmBits,                                 // THESE WERE ASSIGNED, ABOVE
    alarmIsDay,
    alarmH12,
    alarmPM );
myRTC.turnOnAlarm( 2 );                   // ENABLE ALARM 2 INTERRUPT OUTPUT

An example program demonstrates using an alarm-triggered interrupt to blink an LED and print a message to the screen at 3-second intervals.

Back to Contents


How (and Why) to Prevent an Alarm Entirely

An alarm that your program is not using can covertly block the output of an interrupt by the DS3231.

The unused alarm can produce this unwanted effect silently. All it takes is for a "match" to occur between the time registers and the settings in effect for the unused alarm.

When a match occurs, the DS3231 will write logic 1 to the flag bit for the unused alarm. While that flag remains at logic 1, it will prevent the DS3231 from sending out a FALLING interrupt signal.

It is not enough merely to clear the unused alarm flag. A better plan would be to prevent the flag from ever being raised in the first place.

One way to do it is to break a rule: assign a nonsensical value to an alarm time register. Select a value that cannot match the time. 255 (0xFF) will fit nicely in an 8-bit unsigned integer. The following code fragment uploads 0xFF to the A2Minute register.

Then it assigns the value 0b01100000 to the AlarmBits parameter, which means to activate Alarm 2 when the alarm minutes value matches that of the time. The match will never occur because the minutes value of time will not exceed 59.

    // these parameters need to be provided
    // but their values will not be evaluated
    alarmDay = 1;
    alarmHour = 1;
    alarmDayIsDay = false;
    alarmH12 = false;
    alarmPM = false
    // these parameters may prevent Alarm 2 from activating
    alarmMinute = 0xFF; // a value that will never match the time
    alarmBits = 0b01100000; // Alarm 2 when minutes match, in this case, never
    // Upload the parameters to prevent Alarm 2 entirely
    myRTC.setA2Time(
        alarmDay, alarmHour, alarmMinute,
        alarmBits, alarmDayIsDay, alarmH12, alarmPM);
    // disable Alarm 2 interrupt
    myRTC.turnOffAlarm(2);
    // clear Alarm 2 flag
    myRTC.checkIfAlarm(2);

Back to Contents