Northern Widget
Utilities

#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.

Contents


enable32kHz()

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.


enableOscillator()


/*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.


oscillatorCheck()


/*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:

  1. Power is first applied to DS3231.
  2. DS3231 switches to battery backup power, unless another flag ("EOSC", see data sheet) is set to disable oscillator. In that situation, oscillator stops and data in all of DS3231 registers is held static.
  3. The power has decreased, on both V CC and V BAT pins, to a level less than what is needed to sustain reliable operation.
  4. Certain other "external influences", such as electrical noise, cause flag to be set.

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.


getTemperature()


/*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.


Pin Change Interrupt

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 );
	}
}

References for Pin Change Interrupts