#DS3231
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.
The 32K output pin of a DS3231 can supply a highly accurate timer for interrupt-driven Arduino programming. Connect pin to an interrupt-enabled Arduino input pin, enable 32K output, and explore benefits of programming with timer-driven interrupts, without complexities of configuring timers built into Arduino's microcontroller.
/*H*******************************************************
* Turn on or off 32.768 kHz output of a DS3231 RTC
* returns: nothing (void)
* one parameter: TF, boolean
* effect:
* TF == true, turns output on
* TF = false, turns output off
********************************************************/
void enable32kHz( bool TF );
// EXAMPLE OF USE
myRTC.enable32kHz( true );
/*H******************************************************* * Turn on or off 32.768 kHz output of a DS3231 RTC * returns: nothing (void) * one parameter: TF, boolean * effect: * TF == true, turns output on * TF = false, turns output off ********************************************************/ void enable32kHz( bool TF ); myRTC.enable32kHz( true ); // EXAMPLE OF USE
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 DS3231 makes these voltage levels available to other devices. The Arduino can use signal as a timer for interrupt-drive applications. It takes just a few steps:
Following example toggles an LED on or off 16 times per second, using output of a DS3231 oscillator. Notice that there is no code in idle process, also known as loop() .
Significantly, alarms of DS3231 do not come into play here. Instead, Arduino program determines interval between actions.
The ease of using such a simple timer could support a very nice pathway into learning timer/counter interrupt programming techniques.
/*H*******************************************************
* 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 32K pin on DS3231
********************************************************/
#import <DS3231.h>
DS3231 myRTC; // SET UP ACCESS TO DS3231
#define PIN32K 3 // PIN TO RECEIVE SIGNAL FROM DS3231
void blinky(); // PROTOTYPE FOR INTERRUPT SERVICE ROUTINE
/*F********************************************************************
*
**********************************************************************/
void
setup()
{
Wire.begin(); // ESTABLISH I2C COMMUNICATIONS
pinMode( LED_BUILTIN, OUTPUT ); // CONFIGURE I/O PINS ON ARDUINO
pinMode( PIN32K, INPUT );
/********************************************************
* CONFIGURE INTERRUPT TO DETECT 32K OSCILLATOR.
* NOTE THAT INTERRUPTING ON CHANGE WILL GENERATE 2 INTERRUPTS PER CYCLE,
* ONCE WHEN 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);
myRTC.enable32kHz( true ); // ENABLE OUTPUT ON 32K PIN OF DS3231
}
/*F********************************************************************
*
**********************************************************************/
void
loop()
{
// NO CODE HERE
}
/*F********************************************************************
* 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 COUNTER AND TEST IT. PERFORM AN ACTION IF TEST RESULT IS TRUE
// EXAMPLES OF SEVERAL TESTS ARE DEFINED AS MACROS HERE
#define BLINK_ONCE (++counter == 0) // 1 / SEC, WHEN CNTR ROLLS OVER
#define BLINK_TWICE (++counter >> 15) // 2 / SEC, WHEN CNTR 2^15
#define BLINK_FOUR (++counter >> 14)// 4 / PER SEC, WHEN CNTR 2^14
#define BLINK_EIGHT (++counter >> 13) // 8 / SEC, WHEN CNTR 2^13
#define BLINK_16 (++counter >> 12) // 16 / SEC, WHEN CNTR 2^12
if( BLINK_16 ) // WRITE-IN MACRO YOU WISH TO EVALUATE
{
state = !state; // FLIPFLOP STATE
digitalWrite( LED_BUILTIN, state ); // FLIPFLOP LED
tCount = 0; // START COUNT OVER
}
}
/*H******************************************************* * 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 32K pin on DS3231 ********************************************************/ #import <DS3231.h> DS3231 myRTC; // Set up access to DS3231 #define PIN32K 3 // pin to receive signal from DS3231 void blinky(); // prototype for interrupt service routine /*F******************************************************************** * **********************************************************************/ void setup() { Wire.begin(); // ESTABLISH I2C COMMUNICATIONS pinMode( LED_BUILTIN, OUTPUT ); // CONFIGURE I/O PINS ON ARDUINO pinMode( PIN32K, INPUT ); /*H******************************************************* * Configure interrupt to detect 32K oscillator. * Note that interrupting on CHANGE will generate 2 interrupts per cycle, * once when 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 32K PIN OF DS3231 myRTC.enable32kHz( true ); } /*F******************************************************************** * **********************************************************************/ void loop() { // NO CODE HERE } /*F******************************************************************** * 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 COUNTER AND TEST IT. pERFORM AN ACTION IF TEST RESULT IS TRUE // EXAMPLES OF SEVERAL TESTS ARE DEFINED AS MACROS HERE #define BLINK_ONCE (++counter == 0) // 1 / SEC, WHEN CNTR ROLLS OVER #define BLINK_TWICE (++counter >> 15) // 2 / SEC, WHEN CNTR 2^15 #define BLINK_FOUR (++counter >> 14) // 4 / SEC, WHEN CNTR 2^14 #define BLINK_EIGHT (++counter >> 13) // 8 / SEC, WHEN CNTR 2^13 #define BLINK_16 (++counter >> 12) // 16 / SEC, WHEN CNTR 2^12 if( BLINK_16 ) // WRITE-IN MACRO YOU WISH TO EVALUATE { state = !state; // FLIPFLOP STATE digitalWrite(LED_BUILTIN, state); // FLIPFLOP LED tCount = 0; // START COUNT OVER } }
Let's follow math in that example.
The oscillator frequency, 32.768 kHz lends itself easily to binary arithmetic because number of cycles is a power of two: 2 15 = 32768, to be precise.
Each cycle is made up of two voltage changes, e.g., HIGH to LOW, then LOW back to HIGH. Thus, oscillator produces 2 15 × 2 = 2 16 voltage changes per second.
Each voltage change triggers an interrupt because code specifies CHANGE to be trigger. Thus, interrupt service routine (ISR) will be invoked 2 16 times each second.
A counter variable is incremented upon each interrupt. An unsigned 16-bit counter being incremented 2 16 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 section on Pin Change Interrupts , below.
Note that output on 32K pin is independent of that on INT/SQW pin. The two pins can separately drive interrupts to two, different input pins on an Arduino.
/*H*******************************************************
* Regulates output on INT/SQW pin
* returns: nothing (void)
* three parameters:
* TF, boolean
* (true) output a square wave on INT/SQW pin
* (false) output alarm interrupts on 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 frequency of 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 )
/*F*******************************************************
* 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);
/******************************************************** * Regulates output on INT/SQW pin * * returns: nothing (void) * three parameters: * TF, boolean * (true) output a square wave on INT/SQW pin * (false) output alarm interrupts on 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 frequency of 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) /*H******************************************************* * 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.
By default when power is first applied to DS3231, INT/SQW pin is configured to operate in Interrupt mode.
Note that output on INT/SQW pin is independent of that on 32K pin. The two pins can separately drive interrupts to two, different input pins on an Arduino.
/*F*******************************************************
* Detects trouble that might have made time inaccurate in DS3231.
* returns: boolean,
* (false) means DS3231 oscillator has stopped running for some reason
* in which case time in DS3231 could be inaccurate.
* (true) means that DS3231 oscillator has not stopped since flag was last cleared
*
* parameters: none
********************************************************/
bool
oscillatorCheck()
// EXAMPLE OF USAGE
bool rtcRemainsHealthy = myRTC.oscillatorCheck();
/*H******************************************************* * Detects trouble that might have made time inaccurate in DS3231. * returns: boolean, * (false) means DS3231 oscillator has stopped running for some reason * in which case time in DS3231 could be inaccurate. * (true) means that DS3231 oscillator has not stopped since flag was last cleared * parameters: none ********************************************************/ bool oscillatorCheck() // EXAMPLE OF USAGE bool rtcRemainsHealthy = myRTC.oscillatorCheck();
A flag named Oscillator Stop Flag (OSF)in DS3231 gets written to logic 1 by hardware whenever one of following four conditions occurs:
The timekeeping data in DS3231 should be viewed with doubt in event 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, function that writes OSF to zero is setSeconds() method of DS3231 class.
The setEpoch() method invokes setSeconds(), which means that setEpoch() will clear OSF flag also.
Reminder: OSF flag will be written to 1 and oscillator will not be running when power is first applied to DS3231. Setting time, specifically setting seconds value of time, writes OSF to zero and starts oscillator which drives timekeeping process.
/*H*******************************************************
* Retrieve internal temperature of DS3231
*
* returns: floating-point value of 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 RETURNED VALUE
}
else
{
// value returned is not valid
}
/*H******************************************************* * Retrieve internal temperature of DS3231 * returns: floating-point value of 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 returned value } else { // value returned is not valid }
Why would a clock chip contain a temperature sensor? The answer helps to understand how 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 oscillator, based on measured voltage level from a temperature sensor.
Long story short, temperature sensor is there to help maintain accuracy of clock. The sensor's measurement is updated at 64-second intervals in two memory registers.
This function retrieves values in those two registers and combines them into a floating-point value.
According to data sheet, temperature values stored in DS3231 registers claim to be accurate within a range of three degrees Celsius above or below actual temperature.
The oscillating output from 32K pin of a DS3231 makes an excellent source of timer input for Pin Change Interrupt capability of AVR-based Arduino boards.
All of I/O pins on popular models such as 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 32K output of a DS3231 to a Pin Change Interrupt-enabled input pin of an Arduino.
It falls outside scope of this article to explain interrupt-based programming in general. Many books and articles cover topic very well.
Pin Change Interrupts may be a lesser-known feature of AVR microcontrollers. Their usage is documented in different data sheets for each model of controller. The example below was written for 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 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 applicable numbers of steps and interval lengths.
Comments in example provide additional documentaiton.
/*H********************************************************************
*
**********************************************************************/
#include <DS3231.h>
DS3231 myRTC;
/*F********************************************************************
*
**********************************************************************/
void
setup()
{
pinMode( LED_BUILTIN, OUTPUT ); // SET UP LED BLINK PIN
Wire.begin(); // START I2C AND ENABLE 32.768 KHz OUTPUT FROM DS3231
myRTC.enable32kHz( true );
pinMode( A2, INPUT ); // ENABLE PIN A2 TO RECEIVE DS32231 32.768 KHz OUTPUT
// CONFIGURE PIN A2 TO TRIGGER INTERRUPTS EACH TIME VOLTAGE LEVEL CHANGES
// BETWEEN LOW AND HIGH FOLLOWING EXPLICITLY USES HARDWARE ADDRESS NAMES
// DEFINED IN ATmega328P DATASHEET TO MODIFY RELEVANT BITS IN RELEVANT
// REGISTERS, AVOIDS 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 INTERRUPT FLAG BIT FOR A2
sei(); // RE-ENABLE INTERRUPTS GLOBALLY
}
/*F********************************************************************
*
**********************************************************************/
void
loop()
{
// NO CODE IN LOOP!
}
/*F*******************************************************
* 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 65,536 interrupts into ten segments
* of very nearly 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)
interval -= 1; // TIME INTERVAL NOT EXPIRED, DECREMENT COUNTER AND EXIT
else
{
// STEP LENGTH COUNTER HAS TIMED OUT ( == 0 ), WHICH MEANS INTERVAL
// OF TIME HAS EXPIRED. FIRST, TAKE CARE OF HOUSEKEEPING RENEW 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;
next_step += 1; // INCREMENT STEP NUMBER
if( next_step > 9) // BUT ROLL IT OVER TO 0 AFTER STEP 9
next_step = 0;
// HOUSEKEEPING IS COMPLETE. NOW EXECUTE CODE FOR 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 );
}
}
DS3231 myRTC; /*F******************************************************************** * **********************************************************************/ void setup() { // SET UP LED BLINK PIN pinMode( LED_BUILTIN, OUTPUT ); Wire.begin(); // START I2C AND ENABLE 32.768 KHz OUTPUT FROM DS3231 myRTC.enable32kHz( true ); // ENABLE PIN A2 TO RECEIVE 32.768 KHz OUTPUT FROM DS3231 pinMode( A2, INPUT ); // NO INPUT_PULLUP // CONFIGURE PIN A2 TO TRIGGER INTERRUPTS, EACH TIME VOLTAGE LEVEL // CHANGES BETWEEN LOW AND HIGH, FOLLOWING EXPLICITLY USES HARDWARE // ADDRESS NAMES DEFINED IN ATmega328P DATASHEET, TO MODIFY RELEVANT // BITS IN RELEVANT REGISTERS IT AVOIDS 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 INTERRUPT FLAG BIT FOR A2 sei(); // RE-ENABLE INTERRUPTS GLOBALLY } /*F******************************************************************** * **********************************************************************/ void loop() { // NO CODE IN LOOP! } /*F******************************************************* * 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 65,536 interrupts into ten segments * of very nearly 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 ) // TIME INTERVAL NOT EXPIRED interval -= 1; // DECREMENT COUNTER AND EXIT ISR else { // STEP LENGTH COUNTER HAS TIMED OUT ( == 0 ) // WHICH MEANS INTERVAL OF TIME HAS EXPIRED // fIRST, TAKE CARE OF HOUSEKEEPING // RENEW 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 STEP NUMBER next_step += 1; // BUT ROLL IT OVER TO 0 AFTER STEP 9 if( next_step > 9) next_step = 0; // HOUSEKEEPING COMPLETE // NOW EXECUTE CODE FOR 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 ); } }
Almy, Tom. Far Inside 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.