First Commit

This commit is contained in:
MindCreeper03
2025-02-27 19:31:50 +01:00
parent bcbb6aff9a
commit e490df1715
2470 changed files with 1479965 additions and 0 deletions

View File

@@ -0,0 +1,761 @@
#DS3231 Library
## Functions for Alarms
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](https://github.com/NorthernWidget/DS3231/tree/master/examples/AlarmInterrupt) demonstrates using an alarm-triggered interrupt to blink an LED.
## Contents
* [Arduino Code Requirements](#arduino-code-requirements)
* [Alarm Bits Quick Reference](#alarm-bits-quick-reference)
* [The DS3231 Library Alarm Functions](#alarm-functions)
* [getA1Time()](#get-a1-time)
* [getA1Time() with Option](#get-a1-time-option)
* [getA2Time()](#get-a2-time)
* [getA2Time() with Option](#get-a2-time-option)
* [setA1Time()](#set-a1-time)
* [setA2Time()](#set-a2-time)
* [turnOnAlarm()](#turn-on-alarm)
* [turnOffAlarm()](#turn-off-alarm)
* [checkAlarmEnabled()](#check-alarm-enabled)
* [checkIfAlarm()](#check-if-alarm)
* [checkIfAlarm() with Option](#check-if-alarm-option)
* [Alarm Bits in Detail](#alarm-bits-in-detail)
* [How to Advance an Alarm Time](#how-to-advance-an-alarm-time)
* [How (and Why) to Prevent an Alarm Entirely](#prevent-alarm)
## 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.
```
#include <DS3231.h> // import this Library
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
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.
```
// 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](#contents)
## Alarm Bits Quick Reference
Two of the alarm values shown in the foregoing example combine to determine the frequency of an alarm:
* alarmIsDay, and
* alarmBits.
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<br>(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<br>(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<br>(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<br>(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](#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```
---
### <a id="get-a1-time">getA1Time()</a>
```
/*
* 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](#contents)
---
### <a id="get-a1-time-option">getA1Time() with Option</a>
```
/*
* 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".
* The bits for alarm 1 occupy positions 0-3 in the bitfield.
* Those for alarm 2 are found in positions 4-6. Bit 7 has no meaning.
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:
* the actual bits for the targeted alarm in one-half of the field, plus
* any leftover bits that might have been lingering in the other half.
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.
* Alarm 1: ```AlarmBits &= 0b00001111;```
* Alarm 2: ```AlarmBits &= 0b01110000;```
Approach 2. *Before* calling "get", clear the bitfield by assigning 0 to the variable.
* ```AlarmBits = 0;```
Approach 3. Set the optional "clearAlarmBits" boolean parameter in the "get" function to "true".
* "true" tells the "get" function to perform Approach 2 internally.
* "false" or omitted leaves management of the bitfield in the hands of the program writer.
[Back to Contents](#contents)
---
### <a id="get-a2-time">getA2Time()</a>
```
/*
* 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](#contents)
---
### <a id="get-a2-time-option">getA2Time() with Option</a>
```
/*
* 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](#managing-the-retrieved-alarmbits-value), above, for more information about the purpose and usage of this optional method.
[Back to Contents](#contents)
---
### <a id="set-a1-time">setA1Time()</a>
```
/*
* 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](#contents)
### <a id="set-a2-time">setA2Time()</a>
```
/*
* 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](#contents)
---
### <a id="turn-on-alarm">turnOnAlarm()</a>
```
/*
* 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);
```
* Enables the interrupt for alarm 1 if and only if the Alarm parameter is equal to 1.
* For all other values of the Alarm parameter, it sets the interrupt for alarm 2.
* The Alarm parameter is required.
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()](#check-if-alarm), 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](#contents)
---
### <a id="turn-off-alarm">turnOffAlarm()</a>
```
/*
* 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) ;
```
* Disables the interrupt for alarm 1 if and only if the Alarm parameter is equal to 1.
* For all other values of the Alarm parameter, it disables the interrupt for alarm 2.
* The Alarm parameter is required.
Disabling interrupts for one of the alarms has no effect on the other alarm.
[Back to Contents](#contents)
---
### <a id="check-alarm-enabled">checkAlarmEnabled()</a>
```
/*
* 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](#contents)
---
### <a id="check-if-alarm">checkIfAlarm()</a>
```
/*
* 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](#contents)
---
### <a id="check-if-alarm-option">checkIfAlarm() with Option</a>
```
/*
* 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](#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](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf). 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
<table>
<tr> <!-- Top header row -->
<th rowspan="2">DY/DT</th>
<th colspan="4">Alarm 1 Mask Bits</th>
<th rowspan="2">Alarm Rate</th>
</tr>
<tr> <!-- Second header row -->
<th>A1M4</th>
<th>A1M3</th>
<th>A1M2</th>
<th>A1M1</th>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>Alarm once per second</td>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>Alarm when seconds match</td>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>Alarm when minutes and seconds match</td>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>Alarm when hours, minutes and seconds match</td>
</tr>
<tr> <!-- Data row -->
<td style="text-style:italic;">true</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>Alarm when day, hours, minutes and seconds match</td>
</tr>
<tr> <!-- Data row -->
<td style="text-style:italic;">false</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>Alarm when date, hours, minutes and seconds match</td>
</tr>
</table>
##### Alarm 2
<table>
<tr> <!-- Top header row -->
<th rowspan="2">DY/DT</th>
<th colspan="3">Alarm 2 Mask Bits</th>
<th rowspan="2">Alarm Rate</th>
</tr>
<tr> <!-- Second header row -->
<th>A2M4</th>
<th>A2M3</th>
<th>A2M2</th>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>1</td>
<td>1</td>
<td>Alarm when seconds == 0 (once per minute) </td>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>1</td>
<td>0</td>
<td>Alarm when minutes match</td>
</tr>
<tr> <!-- Data row -->
<td>X</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>Alarm when hours and minutes match</td>
</tr>
<tr> <!-- Data row -->
<td style="text-style:italic;">true</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>Alarm when day, hours and minutes match</td>
</tr>
<tr> <!-- Data row -->
<td style="text-style:italic;">false</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>Alarm when date, hours and minutes match</td>
</tr>
</table>
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](#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.
```
// capture the time the alarm occurred
DateTime alarmTime = RTClib::now();
// disable Alarm 2 interrupt output
myRTC.turnOffAlarm(2);
// We need to clear both of the alarm flags
// before DS3231 can output another alarm interrupt.
// Capture Alarm 1 flag for later assessment
// and automatically clear Alarm 1 flag
bool alarm1Flag = myRTC.checkIfAlarm(1);
// Clear Alarm 2 flag. No need to retain its value.
myRTC.checkIfAlarm(2);
// 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 a date in the month
// add 600 seconds (10 minutes)
uint32_t nextAlarm = alarmTime.unixtime() + 600;
// update values in the DateTime
alarmTime = DateTime(nextAlarm);
// Set the next time for Alarm 2.
// Note that only the "minutes" value is significant,
// yet we must supply the day and hour also,
// and it does no harm to supply real ones.
myRTC.setA2Time (
// get these values from the 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(),
// these were assigned, above, by the program
alarmBits,
alarmIsDay,
alarmH12,
alarmPM
);
// enable Alarm 2 interrupt output
myRTC.turnOnAlarm(2);
```
An [example program](https://github.com/NorthernWidget/DS3231/tree/master/examples/AdvanceAlarm) demonstrates using an alarm-triggered interrupt to blink an LED and print a message to the screen at 3-second intervals.
[Back to Contents](#contents)
## <a id="prevent-alarm">How (and Why) to Prevent an Alarm Entirely</a>
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](#contents)

View File

@@ -0,0 +1,196 @@
# DS3231 Library
## DateTime
DateTime is a C++ class included within this DS3231 Library. Program code can store and manipulate date and time information in DateTime variables.
Date and time information can be entered in a variety of formats, not only as numbers but also as specially formatted strings.
The library even provides a function to transfer date and time data from the DS3231 hardware into a DateTime object, where it can be accessed for subsequent analysis and calculations.
Methods (another name for Functions) of a DateTime variable enable programs to access date and time data in different number formats. The values for years, months, days, hours, minutes and seconds can be retrieved either individually or as a "timestamp", a large integer number of seconds, and vice versa.
Timestamps make it easy to do arithmetic with dates and times.
DateTime variables afford convenient data management and are easy to use. Even so, please keep one limitation in mind when writing them into your code:
---
*DateTime objects are defined in this Library to work with dates of 01 January 2000 through 31 December 2099, only*.
---
## Contents
* [DateTime As a Data Type](#datetime-as-a-data-type)
* [DateTime() As a Function ](#datetime-as-a-function)
* [Uses and Limitations of the Timestamp](#uses-and-limitations-of-the-timestamp)
## DateTime As a Data Type
Simply use the class name as the type to declare a DateTime variable, for example:
```DateTime myDT;```
Date and time data can be stored in a DateTime variable by adding a parameter list to the declaration. The parameter list will invoke one of the "constructor functions" to initialize the variable's date and time values.
The following examples initialize three ways to initialize a DateTime named ```myDT``` so that it contains 4:50:59 P.M. on Sep 08 2022 16:50:59.
### Example using strings
```DateTime myDT("Sep 08 2022", "16:50:59");```
The constructor function prototype is:
```DateTime(const char* date, const char* time);```
* Strings must be formatted as "Mmm dd yyyy" and "hh:mm:ss".
* Valid month abbreviations are: "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov" and "Dec".
* The year must be four characters long.
* Space delimiters are required in the date, as are the colon delimiters in the time.
* Hours in the time are given in 24-hour format, i.e., "00" through "23".
### Example using six integer values
```DateTime myDT(2022, 09, 08, 16, 50, 59);```
The constructor function prototype is:
```
DateTime (uint16_t year, uint8_t month, uint8_t day,
uint8_t hour =0, uint8_t min =0, uint8_t sec =0);
```
* The year, month and day parameters are required.
* The time parameters are optional and are given default values of zero (0).
* The value for the year can be entered in the range 2000 through 2099, or in the range 0 through 99.
* The year will be stored internally as an offset from 2000. In the example above, 22 would be stored.
* It is the programmer's responsibility to provide sensible values for all of the parameters.
### Example using a Unix-style timestamp
```DateTime myDT(1662655859);```
The constructor function prototype is:
```DateTime myDT(uint32_t t);```
Note that this constructor function will treat the timestamp fed into it as a Unix-style timestamp, that is, the number of seconds elapsed since midnight (time 00:00:00) the morning of January 1, 1970.
As the DateTime class is limited by design to the years 2000 through 2099, the range of unambiguous values for timestamps to be used with this constructor includes:
* minimum: 946684801 = 00:00:01 (a.m.) January 1, 2000
* maximum: 4102444799 = 23:59:59 (p.m.) December 31, 2099
However, a DateTime variable does not keep track of which time zone its data actually represent. It makes no allowance for Daylight Savings Time, either.
The program writer is responsible for adjusting the date and time information, if necessary to respect a certain time zone.
---
<p style="font-style: italic;">Regardless of the method used to create it, DateTime variables store the components of date and time internally as six, distinct, 8-bit, unsigned integers:</p>
<ul style="font-style: italic;">
<li>yOff : offset from year 2000, i.e., 00 through 99</li>
<li>m : month, 1 through 12</li>
<li>d : date of the month, 1 through 28, 29, 30 or 31, depending on the month and year</li>
<li>hh : hour, 00 through 23, in 24-hour format</li>
<li>mm : minute, 00 through 59</li>
<li>ss : second, 00 through 59</li>
</ul>
---
## DateTime() As a Function
Constructor functions are called automatically when a new variable is declared, as shown above.
Your code can call a constructor directly, also, when it wants to update the values stored in a DateTime variable. Simply set the old variable equal to the return value of the chosen constructor function.
For example, consider the variable named ```myDT``` that was declared in the preceding examples. It was initialized to contain a date of September 8. Here is how to change the date to September 10, while holding all the other values constant (by repeating their previous values):
```myDT = DateTime(2022, 09, 10, 16, 50, 59);```
Compare the parameter list above to the one used to declare the variable: (2022, 09, 08, 16, 50, 59).
Notice that changing even one date or time value in a DateTime requires providing relevant information about all of the values.
This Library provides a fourth function designed to transfer date and time information directly from a DS3231 device into a DateTime variable. Its prototype is:
```DateTime RTClib::now()```
The following example transfers data from a DS3231 device into the ```myDT``` variable, assuming the DS3231 is in communication with the program via I2C:
```myDT = RTClib::now();```
Look closely at the name of that function. Write it just that way in your code.
### Summary
---
*Use a constructor function to modify the values stored inside a DateTime variable.*
---
## Retrieving Date and Time Data
DateTime objects provide seven different methods for retrieving their information. None of the methods takes any parameters.
The function names and the types of values they return are listed below:
* ```uint16_t year(); // returns 2000 + yOff```
* ```uint8_t month();```
* ```uint8_t day();```
* ```uint8_t hour();```
* ```uint8_t minute();```
* ```uint8_t second();```
* ```uint32_t unixtime(); // returns the date and time in the format of a Unix Timestamp```
The data access methods listed above are invoked from a DateTime variable with the dot (".") operator. For example, the following code segment would print the time information contained within a DateTime object named ```myDT```.
```
Serial.print(myDT.hour());
Serial.print(":");
Serial.print(myDT.minute());
Serial.print(":");
Serial.println(myDT.second());
```
## Uses and Limitations of the Timestamp
### Uses
The timestamp returned by the *unixtime()* method of a DateTime is an unsigned, 32-bit integer. It can be very useful for adding or subtracting an interval of time measured in seconds.
Suppose you want to add one day to to an existing DateTime variable. It could be cumbersome to do this by adjusting first the date, then perhaps the month, then perhaps the year.
A better procedure might be:
1. retrieve the DateTime data as a timestamp,
2. add 86,400 seconds, then
3. use the timestamp constructor to update the variable.
The following code segment illustrates the procedure:
```
#define SECONDS_IN_ONE_DAY 86400
uint32_t timeStamp = myDT.unixtime();
timeStamp += SECONDS_IN_ONE_DAY;
myDT = DateTime(timeStamp);
```
### Limitations
DateTime variables do not maintain information about time zones.
The idea of a timestamp originated in the notion of a "system time" accumulated inside a computer running the Unix operating system. It was just an integer that began at zero when the computer started up. It would keep count of how many seconds the computer had been running. A timestamp is just the value of the system time at a given instant.
A "Unix Epoch" timestamp is like a system time that began at zero, exactly at 00:00:00 o'clock midnight on the morning of January 1, 1970 on the Prime Meridian of longitude, which runs through Greenwich, a suburb of London, England.
Hence, Unix Timestamps represent Greenwich Meridian Time, or GMT. Adjusting a Unix timestamp to other time zones involves adding or subtracting a suitable number of seconds.
DateTime relaxes that definition. It deals with *Unix-style* timestamps. That is, it follows the Unix algorithm for converting between dates/times and timestamps for GMT, but makes no adjustment for a time zone.
A timestamp retrieved by the *unixtime()* method of a DateTime variable is best understood as just a (rather large) number of seconds.
### Fun Facts about Unix Timestamps
Did you know that some systems using *signed 32-bit values* for Unix Timestamps may require re-configuration before the 21st Century grows much older? For entertaining reading, look up "the 2038 Problem."
Fortunately, the DateTime class defined in this Library uses *unsigned 32-bit values* for its timestamps. In this way it postpones the 2038 Problem beyond December 2099, perhaps even through the year 2137.
Keep in mind, however: the DateTime class defined in this Library is designed to work correctly only with dates between January 1, 2000 and December 31, 2099.

View File

@@ -0,0 +1,178 @@
# DS3231 Library
## Time Retrieval Functions
The following methods of the DS3231 object use I2C hardware connection and the Wire library to read data from certain registers in the DS3231. The Library assumes that the DS3231 has an I2C address of 0x68.
The DS3231 register addresses mentioned below are documented on page 11 of the [manufacturer's datasheet](https://datasheets.maximintegrated.com/en/ds/DS3231-DS3231S.pdf).
The examples provided below assume a variable has been declared as follows:
```DS3231 myRTC;```
<ul>
<li><a href="#getSecond">getSecond&#40;&#41;</a></li>
<li><a href="#getMinute">getMinute&#40;&#41;</a></li>
<li><a href="#getHour">getHour&#40;&#41;</a></li>
<li><a href="#getDow">getDoW&#40;&#41;</a></li>
<li><a href="#getDate">getDate&#40;&#41;</a></li>
<li><a href="#getMonth">getMonth&#40;&#41;</a></li>
<li><a href="#getYear">getYear&#40;&#41;</a></li>
<h3 id="getSecond">getSecond&#40;&#41;</h3>
```
/*
* returns: byte = 0 to 59
* parameters: none
* asserts: none
* side effects: none
* DS3231 register addressed: 0x00
*/
byte theSecond = myRTC.getSecond();
```
<h3 id="getMinute">getMinute&#40;&#41;</h3>
```
/*
* returns: byte = 0 to 59
* parameters: none
* asserts: none
* side effects: none
* DS3231 register addressed: 0x01
*/
byte theMinute = myRTC.getMinute();
```
<h3 id="getHour">getHour&#40;&#41;</h3>
```
/*
* returns: byte, value depending on mode settings in the DS3231
* either 1 to 12 (12-hour mode)
* or 0 to 23 (24-hour mode)
* parameters: two boolean variables, passed by reference
* parameter #1: 12/24-hour flag
* parameter #2: AM/PM flag
* Note: must provide the variable names, not constants
* asserts: none
*
* side effects:
* the two boolean parameters are set or cleared
* according to the state of the corresponding flags
* in the DS3231 hardware register
* 12/24 hour flag = true if DS3231 is in 12-hour mode
* AM/PM flag = false if AM, true if PM, in 12-hour mode
*
* DS3231 register addressed: 0x02
*/
// declare global variables to be passed into the function
bool h12;
bool hPM;
byte theHour = myRTC.getHour(h12, hPM);
// example of printing the hour to the Serial monitor
Serial.print("The hour is ");
Serial.print( theHour ); // the value returned by the function
// test the values altered by side-effects of the function
if (h12 == true) { // 12-hour mode
if (hPM == true) {
Serial.println(" PM.");
} else {
Serial.println(" AM.");
}
} else { // 24-hour mode
Serial.println(" in 24-hour mode.");
}
```
Note that supplying boolean constants as parameters will halt program compilation with an error. The parameters must be the names of boolean variables defined in the program code.
<h3 id="getDoW">getDoW&#40;&#41;</h3>
```
/*
* returns: byte = 1 to 7
* parameters: none
* asserts: none
* side effects: none
* DS3231 register addressed: 0x03
*/
byte theWeekday = myRTC.getDoW();
```
Note that the meaning of the day-of-week value is determined by the user when the time is *set* on the DS3231. See the documentation for setDoW(). In other words, "1" can signify any day of the week that the code writer chooses it to mean when setting the time. The values "2" through "7" then refer to the succeeding days, in their usual order.
<h3 id="getDate">getDate&#40;&#41;</h3>
```
/*
* returns: byte = 1 to 28, 29, 30 or 31, depending on the month and year
* parameters: none
* asserts: none
* side effects: none
* DS3231 register addressed: 0x04
*/
byte theDate = myRTC.getDate();
```
<h3 id="getMonth">getMonth&#40;&#41;</h3>
```
/*
* returns: byte = 1 to 12
* parameters: one boolean variable, passed by reference
* asserts: none
* side effects:
* the boolean parameter is set or cleared
* according to the value of the "Century" flag
* in the hardware register of the DS3231
* DS3231 register addressed: 0x05
*/
// declare a variable to receive the Century bit
bool CenturyBit;
byte theDate = myRTC.getMonth(CenturyBit);
```
Note: according to the datasheet, "The century bit (bit 7 of the month register) is toggled when the years register overflows from 99 to 00."
Note also that supplying a boolean constant value of *true* or *false* as the parameter will halt program compilation with an error. The parameter must be the name of a boolean variable defined in the program code.
The "Contemplations", below, further discuss the Century Bit.
<h3 id="getYear">getYear&#40;&#41;</h3>
```
/*
* returns: byte = 00 to 99
* parameters: none
* asserts: none
* side effects: none
* DS3231 register addressed: 0x06
*/
byte theDate = myRTC.getYear();
```
### Contemplations of An Aging Documentarian
The Century bit may supply useful information when operating the DS3231 near the end of a century. For example, the bit would have toggled when the year changed from 1999 to 2000. It would have been important to recognize that a year "00" actually represented an *increase* of time compared to the year "99".
The bit will toggle again when the year changes from 2099 to 2100, and so forth.
For reasons best understood by its designers, the Century bit is stored in the "month" register of the DS3231, rather than in the "year" register.
It might have been nicer if the DS3231 afforded the capacity to maintain a 4-digit year value.
We users of the device might find little use for the Century bit during the years 2000 through 2098 or so. Anyone planning to use this Library with a DS3231 in the year 2099 may wish to experiment with code to evaluate and correctly use the Century bit.
My beard will probably not grow long enough for me to reach that future era. Even so, by then I would probably look for a different RTC chip. The reason is the DS3231 makes no promise to handle Leap Years correctly in or after the year 2100.

View File

@@ -0,0 +1,240 @@
# DS3231 Library
## Time Setting Functions
The following methods of the DS3231 object use I2C hardware connection and the Wire library to transfer data into certain hardware registers of the DS3231.
<ul>
<li><a href="#setClockMode">setClockMode&#40;&#41;</a></li>
<li><a href="#setSecond">setSecond&#40;&#41;</a></li>
<li><a href="#setMinute">setMinute&#40;&#41;</a></li>
<li><a href="#setHour">setHour&#40;&#41;</a></li>
<li><a href="#setDoW">setDoW&#40;&#41;</a></li>
<li><a href="#setDate">setDate&#40;&#41;</a></li>
<li><a href="#setMonth">setMonth&#40;&#41;</a></li>
<li><a href="#setYear">setYear&#40;&#41;</a></li>
<li><a href="#setEpoch">setEpoch&#40;&#41;</a></li>
</ul>
The Library assumes that the DS3231 has an I2C address of 0x68.
The DS3231 register addresses mentioned below are documented on page 11 of the [manufacturer's datasheet](https://datasheets.maximintegrated.com/en/ds/DS3231-DS3231S.pdf).
The examples provided below assume a variable has been declared as follows:
```DS3231 myRTC;```
### Note to Developers
It is the code developer's sole responsibility to upload only "sensible" values to the DS3231. For example:
* 0 to 59 for minutes or seconds,
* 1 to 7 for Day of the Week,
* 30 but not 31 for the month of June.
The Library does not check the values to be set. The compiler only checks that the value is of the right size, i.e., 8 bits. A large value such as 117 will fit in 8 bits and would be passed along by the Library. Yet it makes no sense for any part of a time or a date. Code with care!
<h3 id="setClockMode">void setClockMode(bool h12)</h3>
```
/*
* returns: nothing (void)
* parameters: bool = true or false
* effect: sets the clock mode, 12-hour (`true`) or 24-hour (`false`), in the DS3231
* DS3231 register addressed: 0x02
*/
// Illustrate passing the clock mode as a boolean variable
bool mode12 = true; // use 12-hour clock mode
myRTC.setClockMode(mode12); // uploads 'true' (1) to bit 6 of register 0x02
```
The clock mode should certainly be set prior to setting the hour. In other words, invoke this function before invoking ```setHour()``` or ```setEpoch()```. Best practice would be simply to set the mode before setting the date and time.
<h3 id="setSecond">void setSecond(byte Second)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 0 to 59
* effects:
* 1. writes the seconds to the DS3231
* 2. clears the Oscillator Stop Flag in the DS3231 hardware
* DS3231 registers addressed: 0x00, 0x0F
*/
// Illustrate passing the seconds value in a variable
byte theSecond = 42; // 42 seconds
myRTC.setSecond(theSecond); // uploads 42 to register 0x00
// side-effect: also clears bit 7 of register 0x0F
```
Note: the oscillator stop flag is an informational data item indicating that the DS3231's oscillator stopped at some point in the past. The flag does not affect the operation of the DS3231 hardware. Clearing the flag makes good housekeeping sense when setting the time.
<h3 id="setMinute">void setMinute(byte Minute)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 0 to 59
* effects: writes the minutes to the DS3231
* DS3231 register addressed: 0x01
*/
// Illustrate passing the minutes as a literal value
myRTC.setMinute(17); // uploads 17 to register 0x01
```
<h3 id="setHour">void setHour(byte Hour)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 0 to 23 (use 24-hour mode here)
* effect: writes the hour to the DS3231
* DS3231 register addressed: 0x02
*/
// Illustrate passing the hour in a variable
byte theHour = 19; // equal to 7:00 p.m.
myRTC.setHour(theHour); // uploads 19 to register 0x02
```
The setHour() function will convert the hour to 12-hour mode if the DS3231 is set to operate in 12-hour mode. The function does not change how the mode is set on the DS3231 hardware.
Rather, the mode should be set by another function, setClockMode(), prior to invoking the setHour() function.
<h3 id="setDoW">void setDoW(byte DoW)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 1 to 7
* effect: writes the Day of Week to the DS3231
* DS3231 register addressed: 0x03
*/
// Illustrate passing the Day of the Week as a literal value
myRTC.setDoW(1); // uploads 1 to register 0x03
```
The Day of Week value is user-determined. For example, if one chooses Sunday to be the first day of the week, then the DoW values would be:
1. Sunday
2. Monday
3. Tuesday. and so forth.
On the other hand, if Monday were selected as the start of a week, then:
1. Monday
2. Tuesday
3. Wednesday, etcetera.
<h3 id="setDate">void setDate(byte Date)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 1 to 28, 29, 30, or 31, depending on the month and year
* effect: writes the day of the month to the DS3231
* DS3231 register addressed: 0x04
*/
// Illustrate passing the date in a variable
byte theDate = 5; // the 5th day of the month
myRTC.setDate(theDate); // uploads 5 to register 0x04
```
Reminder: it is the code developer's responsibility to ensure that a sensible value be supplied for the date.
<h3 id="setMonth">void setMonth(byte Month)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 1 to 12
* effect: writes the month to the DS3231
* DS3231 register addressed: 0x05
*/
// Illustrate passing the month in a variable
byte theMonth = 6; // the 6th month of the year; in English, June
myRTC.setMonth(theMonth); // uploads 6 to register 0x05
```
<h3 id="setYear">void setYear(byte Year)</h3>
```
/*
* returns: nothing (void)
* parameters: byte = 00 to 99
* effect: writes the year to the DS3231
* DS3231 register addressed: 0x06
*/
// Illustrate passing the year as a literal value
myRTC.setYear(22); // uploads 22 to register 0x06
```
<h3 id="setEpoch">void setEpoch(time_t epoch = 0, bool flag_localtime = false)</h3>
Keep in mind that DS3231 has no concept of time zones or daylight savings time. The value of the "epoch" parameter will be interpreted as a Unix Epoch "timestamp", defined as the number of seconds elapsed since time 00:00:00 (midnight) on January 1, 1970, GMT.
The parameter is set to a value of zero (0) by default.
Note that this function does not verify that the *epoch* value is in fact the correct GMT time. The function relies upon the program writer to supply a value that is correct for the purposes of the program.
Developers are reminded that both the DS3231 hardware and this library are designed to work best with dates and times in the years 2000 through 2099. For this reason, code writers are strongly urged to limit the range of values for the "epoch" parameter as follows:
* Minimum: 946684801 (00:00:01 a.m. on January 1, 2000)
* Maximum: 4102444799 (23:59:59 p.m. on December 31, 2099)
Unexpected results may follow from the use of parameter values less than the recommended minimum or greater than the maximum.
#### A Note About the Second Parameter, *flag_localtime*
This parameter exists because the function makes calls deep into the C++ standard library, where "local time" and "GMT time" can be treated differently. Some hardware compatible with Arduino IDE may be sensitive to this difference.
"False" ensures that the value provided for "epoch" will be treated as representing GMT.
The function sets the parameter to *false* by default.
Both of the parameters are optional because default values are defined for them. Even so, it may be a best practice to specify both parameters when calling this function.
```
/*
* returns: nothing (void)
*
* parameters:
* 1. a time_t value (unsigned long)
* containing a "epoch" time value
* defined as the number of seconds
* that have elapsed since time 00:00:00,
* on the date January 1, 1970 GMT.
* 2. a boolean variable indicating whether
* to interpret the value as "local time" (true)
* or as GMT time (false).
*
* effects:
* calculates and uploads values
* into the DS3231 registers for
* seconds, minutes, hours,
* day of week (where Sunday = 1),
* date, month and year.
*
* DS3231 registers addressed: 0x00 through 0x06
*
* Worked Example
* Suppose a web site tells you that the "epoch" time
* at 12:25:00 p.m. GMT on August 7, 2022 was 1659875100.
*/
// Initialize a (unsigned long) variable to contain that value.
time_t epochNow = 1659875100UL;
// Set the time on the DS3231 using that variable.
myRTC.setEpoch(epochNow, false);
```
The reader is encouraged to experiment with this function. Approach it playfully and check the results until you feel satisfied with your own understanding of what to expect from it on the hardware you plan to use.
The DS3231 data sheet mentions that the device can track leap years accurately "up to (the year) 2100." Perhaps that capacity will suffice for most present-day needs.
After 2099? Not our problem. The kids will have changed everything by then anyway.

View File

@@ -0,0 +1,389 @@
#DS3231
## Utility Functions
The versatile DS3231 is more than just an alarm clock. It supplies a timer and a temperature sensor, also. This Library includes utility functions giving access to these other capabilities.
### Contents
* [enable32kHz()](#32k)
* [enableOscillator()](#enable-oscillator)
* [oscillatorCheck()](#oscillator-check)
* [getTemperature()](#temperature)
* [Pin Change Interrupt](#pin-change-interrupt)
### <a id="32k">enable32kHz()</a>
The 32K output pin of a DS3231 can supply a highly accurate timer for interrupt-driven Arduino programming. Connect the pin to an interrupt-enabled Arduino input pin, enable the 32K output, and explore the benefits of programming with timer-driven interrupts, without the complexities of configuring timers built into the Arduino's microcontroller.
```
/*
* Turn on or off the 32.768 kHz output of a DS3231 RTC
*
* returns: nothing (void)
* one parameter: TF, boolean
* effect:
* TF == true, turns the output on
* TF = false, turns the output off
*/
void enable32kHz(bool TF);
/* example of use */
myRTC.enable32kHz( true );
```
The heart of a DS3231 Real Time Clock device is an oscillator switching a voltage from HIGH to LOW and back again at an exquisitely maintained frequency of 32,768 cycles per second, or 32.768 kHz.
An output pin on the DS3231 makes these voltage levels available to other devices. The Arduino can use the signal as a timer for interrupt-drive applications. It takes just a few steps:
* Connect an interrrupt-capable input pin to the 32K output.
* Set a few bits in certain Arduino registers to enable the interrupt.
* Write program code to "service" the interrupt, that is, to be executed after each interrupt occurs.
The following example toggles an LED on or off 16 times per second, using the output of a DS3231 oscillator. Notice that there is *no code* in the idle process, also known as *loop()*.
Significantly, the alarms of the DS3231 do not come into play here. Instead, the Arduino program determines the interval between actions.
The ease of using such a simple timer could support a very nice pathway into learning timer/counter interrupt programming techniques.
```
/*
* Control an LED from an interrupt service routine
* activated by a 32.768 kHz oscillator in a DS3231 RTC
*
* Hardware setup for Uno/Nano (ATmega328P-based):
* Connect digital pin 3 to the 32K pin on the DS3231
*
*/
#import <DS3231.h>
DS3231 myRTC; // Set up access to the DS3231
#define PIN32K 3 // the pin to receive signal from DS3231
void blinky(); // prototype for the interrupt service routine
void setup() {
Wire.begin(); // establish I2C communications
// configure I/O pins on the Arduino
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PIN32K, INPUT);
/*
* Configure the interrupt to detect the 32K oscillator.
* Note that interrupting on CHANGE will generate 2 interrupts per cycle,
* once when the voltage changes to LOW from HIGH,
* then again when it changes to HIGH from LOW,
* for a total of 32768 x 2 = 65536 interrupts per second
*/
attachInterrupt(digitalPinToInterrupt(PIN32K), blinky, CHANGE);
// enable output on the 32K pin of the DS3231
myRTC.enable32kHz(true);
}
void loop() {
// no code here
}
// the interrupt service routine
void blinky() {
static byte state = 0; // for turning LED on and off
static uint16_t counter = 0; // accumulates count of interrupts received
// Increment the counter and test it. Perform an action if the test result is true.
// Examples of several tests are defined as macros here.
#define BLINK_ONCE (++counter == 0) // once per second, when counter rolls over
#define BLINK_TWICE (++counter >> 15) // twice per second, when counter reaches 2^15
#define BLINK_FOUR (++counter >> 14) // four times per second, when counter reaches 2^14
#define BLINK_EIGHT (++counter >> 13) // eight per second, when counter reaches 2^13
#define BLINK_16 (++counter >> 12) // 16 times per second, when counter reaches 2^12
if (BLINK_16) // Write-in the macro you wish to evaluate.
{
state = !state; // flipflop the state
digitalWrite(LED_BUILTIN, state); // flipflop the LED
tCount = 0; // start the count over
}
}
```
Let's follow the math in that example.
The oscillator frequency, 32.768 kHz lends itself easily to binary arithmetic because the number of cycles is a power of two: 2<sup>15</sup> = 32768, to be precise.
Each cycle is made up of two voltage changes, e.g., HIGH to LOW, then LOW back to HIGH. Thus, the oscillator produces 2<sup>15</sup> × 2 = 2<sup>16</sup> voltage changes per second.
Each voltage change triggers an interrupt because the code specifies CHANGE to be the trigger. Thus, the interrupt service routine (ISR) will be invoked 2<sup>16</sup> times each second.
A counter variable is incremented upon each interrupt. An unsigned 16-bit counter being incremented 2<sup>16</sup> times per second will "roll over" to a value of zero exactly once every second.
The ISR can test for and act upon lesser values. It means programmers can design ISRs to perform actions at intervals shorter than one second. For an example of an interval that is not a power of two, see the section on [Pin Change Interrupts](#pin-change-interrupt), below.
Note that the output on the 32K pin is independent of that on the INT/SQW pin. The two pins can separately drive interrupts to two, different input pins on an Arduino.
### <a id="enable-oscillator">enableOscillator()</a>
```
/*
* Regulates the output on the INT/SQW pin
*
* returns: nothing (void)
* three parameters:
* TF, boolean
* (true) output a square wave on the INT/SQW pin
* (false) output alarm interrupts on the INT/SQW pin
* battery, boolean
* (true) allow square wave output when running on battery
* (false) do not output square wave if running on battery
* frequency, 8-bit unsigned integer, select the frequency of the square wave output
* 0: 1 Hz = one cycle per second
* 1: 1.024 kHz
* 2: 4.096 kHz
* 3 - 255: 8.192 kHz
*/
void enableOscillator(bool TF, bool battery, byte frequency)
/*
* example of use
* output square wave at frequency 1 Hz even when running on battery
*/
bool outputSQW = true;
bool batteryUseAllowed = true;
byte outputFrequency = 0; // select 1 Hz
myRTC.enableOscillator(outputSQW, batteryUseAllowed, outputFrequency);
```
The INT/SQW output pin on a DS3231 can operate in either one of two, different modes.
* Interrupt mode is described in the documentation for Alarms. It:
* uses the pin to generate an interrupt signal when an alarm flag goes HIGH.
* is ideal for occasional signals under program control.
* can automate a repeating series of pulses at long intervals ranging from one second up to one month.
* Oscillator mode is described below. It:
* generates a series of pulses that repeat automatically.
* offers programs a choice of four, rapid frequencies ranging from one second (1 Hz) to 8.192 KHz.
By default when power is first applied to the DS3231, the INT/SQW pin is configured to operate in Interrupt mode.
Note that the output on the INT/SQW pin is independent of that on the 32K pin. The two pins can separately drive interrupts to two, different input pins on an Arduino.
### <a id="oscillator-check">oscillatorCheck()</a>
```
/*
* Detects trouble that might have made the time inaccurate in the DS3231.
*
* returns: boolean,
* (false) means the DS3231 oscillator has stopped running for some reason
* in which case the time in the DS3231 could be inaccurate.
*
* (true) means that the DS3231 oscillator has not stopped since the flag was last cleared
*
* parameters: none
*/
bool oscillatorCheck()
/* example of usage */
bool rtcRemainsHealthy = myRTC.oscillatorCheck();
```
A flag named Oscillator Stop Flag (OSF)in the DS3231 gets written to logic 1 by the hardware whenever one of the following four conditions occurs:
1. Power is first applied to the DS3231.
2. DS3231 switches to battery backup power, unless another flag ("EOSC", see the data sheet) is set to disable the oscillator. In that situation, the oscillator stops and data in all of the DS3231 registers is held static.
3. The power has decreased, on both the V<sub>CC</sub> and V<sub>BAT</sub> pins, to a level less than what is needed to sustain reliable operation.
4. Certain other "external influences", such as electrical noise, cause the flag to be set.
The timekeeping data in the DS3231 should be viewed with doubt in the event the OSF flag is found to be at logic level 1.
The flag will remain at logic level 1 until it is written to zero by program code. In this Library, the function that writes OSF to zero is the setSeconds() method of the DS3231 class.
The setEpoch() method invokes setSeconds(), which means that setEpoch() will clear the OSF flag also.
Reminder: the OSF flag will be written to 1 and the oscillator will not be running when power is first applied to the DS3231. Setting the time, specifically setting the seconds value of the time, writes OSF to zero and starts the oscillator which drives the timekeeping process.
### <a id="temperature">getTemperature()</a>
```
/*
* Retrieve the internal temperature of the DS3231
*
* returns: the floating-point value of the temperature
*
* error return: 9999, if a valid temperature could not be retrieved
*
* parameters: none
*
*/
float getTemperature()
/* example of usage */
float rtcTemp = myRTC.getTemperature();
if (float > -9999)
{
// it may be OK to use the returned value
}
else
{
// the value returned is not valid
}
```
Why would a clock chip contain a temperature sensor? The answer helps to understand how the DS3231 can maintain a very high level of accuracy.
Your friendly neighborhood Documentarian will make a non-engineer's attempt to explain.
An oscillator's frequency can vary with temperature. It can also vary with small changes in electrical capacitance.
The DS3231 hardware includes an array of tiny capacitors that become engaged or disengaged in regulating the oscillator, based on the measured voltage level from a temperature sensor.
Long story short, the temperature sensor is there to help maintain the accuracy of the clock. The sensor's measurement is updated at 64-second intervals in two memory registers.
This function retrieves the values in those two registers and combines them into a floating-point value.
According to the data sheet, the temperature values stored in the DS3231 registers claim to be accurate within a range of three degrees Celsius above or below the actual temperature.
### Pin Change Interrupt
The oscillating output from the 32K pin of a DS3231 makes an excellent source of timer input for the Pin Change Interrupt capability of AVR-based Arduino boards.
All of the I/O pins on popular models such as the Uno and Nano can be used to generate interrupts in response to a fluctuating voltage. Each change, whether to HIGH from LOW or vice versa, will trigger an interrupt.
65,536 interrupts will result from connecting the 32K output of a DS3231 to a Pin Change Interrupt-enabled input pin of an Arduino.
It falls outside the scope of this article to explain interrupt-based programming in general. Many books and articles cover the topic very well.
Pin Change Interrupts may be a lesser-known feature of AVR microcontrollers. Their usage is documented in the different data sheets for each model of controller. The example below was written for the ATmega328P chip found on Arduino Uno and Nano models. Refer to that data sheet for more information.
The example also illustrates a solution to a general problem of working with powers of 2: how to handle remainders following division by numbers other than 2.
An algorithm is developed to divide the 65,536 interrupts into ten, nearly-equal intervals of time, while completing all ten intervals in exactly one second.
Dividing by other values can be approached similarly. The program writer would need to work out the applicable numbers of steps and interval lengths.
Comments in the example provide additional documentaiton.
```
#include <DS3231.h>
DS3231 myRTC;
void setup() {
// set up the LED blink pin
pinMode(LED_BUILTIN, OUTPUT);
// start I2C and enable 32.768 KHz output from the DS3231
Wire.begin();
myRTC.enable32kHz(true);
// enable pin A2 to receive the 32.768 KHz output from DS3231
pinMode(A2, INPUT); // not INPUT_PULLUP
// Configure pin A2 to trigger interrupts
// each time the voltage level changes between LOW and HIGH.
//
// The following explicitly uses hardware address names
// defined in the ATmega328P datasheet
// to modify relevant bits in relevant registers.
// It avoids the use of special libraries, by programmer's preference.
PCMSK1 = (1<<PCINT10); // select pin A2 for pin change interrupt
cli(); // temporarily disable interrupts globally
PCICR |= (1<<PCIE1); // enable pin change interrupt for A2
PCIFR |= (1<<PCIF1); // clear the interrupt flag bit for A2
sei(); // re-enable interrupts globally
}
void loop() {
// no code in the loop!
}
/*
* The Interrupt Service Routine initiates an action
* at intervals very close to 1/10 second in length.
* The DS3231 outputs precisely 65,536 voltage level changes per second.
* Arduino hardware generates 65,536 interrupts per second in response.
* Alas, 65536 does not divide evenly by 10.
* However, a well-designed algorithm can accurately
* separate the 65,536 interrupts into ten segments
* of very nearly the same length, as follows:
* four segments of 6553
* plus six segments of 6554.
* This is accurate because ( 4 × 6553) + (6 × 6554) = 65536.
* Humans cannot discern a difference of less than 1/65000th of a second.
*
*/
ISR(PCINT1_vect) {
// housekeeping variables
static uint16_t interval = 0; // step length counter
static uint8_t led_state = 0;
static uint8_t next_step = 0; // step number
if (interval > 0) {
// The interval of time as not yet expired.
// Decrement the counter and exit the ISR.
interval -= 1;
} else {
// The step length counter has timed out ( == 0 )
// which means the interval of time has expired.
// First, take care of the housekeeping.
// Renew the step length counter
// assign value of 6553 on steps 0 through 3
// but value of 6554 on steps 4 through 9
interval = (next_step < 4) ? 6553 : 6554;
// Increment the step number
next_step += 1;
// but roll it over to 0 after step 9.
if (next_step > 9) next_step = 0;
// The housekeeping is complete.
// Now execute code for the action to be taken
// after an interval of time expires.
// In this example, we merely toggle an LED.
// "Real" code could initiate almost any action.
led_state = ! led_state;
digitalWrite(LED_BUILTIN, led_state);
}
}
```
#### References for Pin Change Interrupts
* Almy, Tom. *Far Inside the Arduino*. 2020. pp 54-83
* Williams, Elliot. *Make: AVR Programming". 2014. Maker Media Inc. pp 155-165.
* "Engineer, Wandering". *Arduino Pin Change Interrupts*. 2014. Web page: https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/. Accessed Sept 17, 2022.