How do you set an alarm on the DS3231? I'm totally lost!!

I'm working on a clock made with the SparkFun e-paper display and a DS3231.

I have everything running just like it should. I also have it running off a LiPo battery. I figured to save battery power, I'd just update the clock display when then minutes change.

I thought I could set the DS3231 to alarm on the minute (when the seconds hit zero), and have my code update the display when the alarm goes off. I can't for the life of me figure out how to do it. I've spent 2 days on it and I'm no closer to figuring it out than I was 2 days ago! Very frustrating!

Can someone baby-step me through it? What library should I be using? For now I'm just talking to the DS3231 with the Wire library.

I pulled out all the code that I used to set the time and date to make it easier to read. Here's my code:

#include <Wire.h>
#include <ePaper.h>

int EIO1pin = 9;     // Input/output pin for chip selection
int XCKpin = 10;     // Clock input pin for taking display data
int LATCHpin = 11;   // Latch pulse input pin for display data
int SLEEPBpin = 12;  // Sleep Pin for the display
int DI0pin = 13;     // Input pin for display data
int VCCpin = 8;

//setup display with pin definitions
ePaper epaper = ePaper(EIO1pin, XCKpin, LATCHpin, SLEEPBpin, DI0pin);

int seconds; 
int minutes; 
int hours;
int dayOfWeek;
int dayOfMonth;
int month;
int year;

char *space;
char *suffix;
char *dayOfWeekChar;
char *seperator; 

char *bottomLineData;

void setup()
{

  Wire.begin();
  Serial.begin(9600);

  pinMode (VCCpin, OUTPUT);
  digitalWrite(VCCpin, HIGH);

  //////////////////////////////// force time setting:
  /*
    seconds = 00;
   minutes = 56;
   hours = 19; //in 24 format
   dayOfWeek = 7;
   dayOfMonth = 25;
   month = 05;
   year = 25;
   initChrono();
   */
  ///////////////////////////////

}

void loop() {

  //poll the DS3231 for the date and time
  get_time();
  get_date();

  // time
  //display hours
  if (hours > 11) {
    suffix = " PM ";
  } 
  else {
    suffix = " AM ";
  }

  if (hours > 12) {
    hours -= 12;
  } 
  else if (hours == 0) {
    hours = 12;
  }

  //figure out leading spaces
  if (hours > 9) {
    space = " "; //1 space
  } 
  else {
    space = "  "; //2 spaces
  }

  if (minutes < 10) {
    seperator = "-0";
  } 
  else {
    seperator = "-";
  }

  //construct the string to be displayed
  char timeChr[10];
  sprintf(timeChr, "%s%d%s%d%s", space, hours, seperator, minutes, suffix);

  /////////////////////////////////////////////////////
  //date
  switch(dayOfWeek){
  case 1: 
    dayOfWeekChar ="Sun";
    break;
  case 2: 
    dayOfWeekChar ="Mon";
    break;
  case 3: 
    dayOfWeekChar ="Tue";
    break;
  case 4: 
    dayOfWeekChar ="Wed";
    break;
  case 5: 
    dayOfWeekChar ="Thu";
    break;
  case 6: 
    dayOfWeekChar ="Fri";
    break;
  case 7: 
    dayOfWeekChar ="Sat";
    break;
  }

  //construct the string to be displayed
  char dateChr[10];

  if ((month < 10) && (dayOfMonth < 10)) {
    sprintf(dateChr, " %s  %d/%d   ", dayOfWeekChar, month, dayOfMonth); //add an extra space to center text when month and DOM are < 10; extra spaces are intentional so no weird char pop in at the end
  }
  else {
    sprintf(dateChr, " %s %d/%d   ", dayOfWeekChar, month, dayOfMonth); //extra spaces are intentional so no weird char pop in at the end
  }

  epaper.writeTop(timeChr);
  epaper.writeBottom(dateChr);
  epaper.writeDisplay();

} //end of loop

///////////////////////////////////////////////////////////////////////////////////////////
//DS3231 RTC interface
void initChrono()
{
  set_time();
  set_date();
}

void set_date()
{
  Wire.beginTransmission(104);
  Wire.write(3);
  Wire.write(decToBcd(dayOfWeek));
  Wire.write(decToBcd(dayOfMonth));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.endTransmission();
}

void get_date()
{
  Wire.beginTransmission(104); 
  Wire.write(3);//set register to 3 (day)
  Wire.endTransmission();
  Wire.requestFrom(104, 4); //get 5 bytes(day,date,month,year,control);
  dayOfWeek   = bcdToDec(Wire.read());
  dayOfMonth  = bcdToDec(Wire.read());
  month = bcdToDec(Wire.read());
  year  = bcdToDec(Wire.read());
}

void set_time()
{
  Wire.beginTransmission(104);
  Wire.write(0);
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.endTransmission();
}

void get_time()
{
  Wire.beginTransmission(104); 
  Wire.write(0);//set register to 0
  Wire.endTransmission();
  Wire.requestFrom(104, 3);//get 3 bytes (seconds,minutes,hours);
  seconds = bcdToDec(Wire.read() & 0x7f);
  minutes = bcdToDec(Wire.read());
  hours = bcdToDec(Wire.read() & 0x3f);
}

///////////////////////////////////////////////////////////////////////
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

Any help or guidance would be much appreciated

The DS has got 2 alarms (two sets of registers). Simply write in the time of the required alarm, enable the DS' alarm interrupt, connect the DS' /INT output to an arduino's interrupt input, and the DS will fire the interrupt at time specified.
You have to process the incoming interrupt within your code then.

Funny how you say "simply do this, this, this, and this". Obviously, it's not "simple" since I'm asking how to do it. Clearly there is a big difference between telling someone "what" to do and "how" to do it.

2 Likes

You have to set the registers for BOTH alarms at the same time. The way I do it is like this:

#define ALRM1_MATCH_EVERY_SEC  0b1111  // once a second
#define ALRM1_MATCH_SEC        0b1110  // when seconds match
#define ALRM1_MATCH_MIN_SEC    0b1100  // when minutes and seconds match
#define ALRM1_MATCH_HR_MIN_SEC 0b1000  // when hours, minutes, and seconds match
byte ALRM1_SET = ALRM1_MATCH_SEC;

#define ALRM2_ONCE_PER_MIN     0b111   // once per minute (00 seconds of every minute)
#define ALRM2_MATCH_MIN        0b110   // when minutes match
#define ALRM2_MATCH_HR_MIN     0b100   // when hours and minutes match
byte ALRM2_SET = ALRM2_ONCE_PER_MIN;

I declare the various bytes for the different modes, for each of alarm 1 and alarm 2.

Then, I add them together before passing it to my .setA1Time() and .setA2Time() routines:

      // Set AlarmBits, ALRM2 first, followed by ALRM1
      AlarmBits = ALRM2_SET;
      AlarmBits <<= 4;
      AlarmBits |= ALRM1_SET;

Then set each alarm:

      // set both alarms to :00 and :30 seconds, every minute
      // Format: .setA*Time(DoW|Date, Hour, Minute, Second, 0x0, DoW|Date, 12h|24h, am|pm)
      //                    |                                    |         |        |
      //                    |                                    |         |        +--> when set for 12h time, true for pm, false for am
      //                    |                                    |         +--> true if setting time based on 12 hour, false if based on 24 hour
      //                    |                                    +--> true if you're setting DoW, false for absolute date
      //                    +--> INTEGER representing day of the week, 1 to 7 (Monday to Sunday)
      //
      Clock.setA1Time(Clock.getDoW(), Clock.getHour(h12, PM), Clock.getMinute(), 30, AlarmBits, true, false, false);
      Clock.setA2Time(Clock.getDate(), Clock.getHour(h12, PM), Clock.getMinute(), AlarmBits, false, false, false);
      // Turn alarms on
      Clock.turnOnAlarm(1);
      Clock.turnOnAlarm(2);

The above example sets the alarms where one of them fires every minute at the top of the minute (:00 seconds mark) and the other at the bottom of it (:30 seconds mark.)

What library are you using? I just get 'Clock...... was not declared in this scope.

Do you have

// Set AlarmBits, ALRM2 first, followed by ALRM1
      AlarmBits = ALRM2_SET;
      AlarmBits <<= 4;
      AlarmBits |= ALRM1_SET;

and

// set both alarms to :00 and :30 seconds, every minute
      // Format: .setA*Time(DoW|Date, Hour, Minute, Second, 0x0, DoW|Date, 12h|24h, am|pm)
      //                    |                                    |         |        |
      //                    |                                    |         |        +--> when set for 12h time, true for pm, false for am
      //                    |                                    |         +--> true if setting time based on 12 hour, false if based on 24 hour
      //                    |                                    +--> true if you're setting DoW, false for absolute date
      //                    +--> INTEGER representing day of the week, 1 to 7 (Monday to Sunday)
      //
      Clock.setA1Time(Clock.getDoW(), Clock.getHour(h12, PM), Clock.getMinute(), 30, AlarmBits, true, false, false);
      Clock.setA2Time(Clock.getDate(), Clock.getHour(h12, PM), Clock.getMinute(), AlarmBits, false, false, false);
      // Turn alarms on
      Clock.turnOnAlarm(1);
      Clock.turnOnAlarm(2);

in setup() or loop()?

Ok, I linked to Eric Ayar's DS3231 library. I think that's the one you used. I updated my code:

#include <Wire.h>
#include <ePaper.h>
#include <DS3231.h>

#define ALRM1_MATCH_EVERY_SEC  0b1111  // once a second
#define ALRM1_MATCH_SEC        0b1110  // when seconds match
#define ALRM1_MATCH_MIN_SEC    0b1100  // when minutes and seconds match
#define ALRM1_MATCH_HR_MIN_SEC 0b1000  // when hours, minutes, and seconds match
byte ALRM1_SET = ALRM1_MATCH_SEC;

#define ALRM2_ONCE_PER_MIN     0b111   // once per minute (00 seconds of every minute)
#define ALRM2_MATCH_MIN        0b110   // when minutes match
#define ALRM2_MATCH_HR_MIN     0b100   // when hours and minutes match
byte ALRM2_SET = ALRM2_ONCE_PER_MIN;

DS3231 Clock;
bool Century=false;
bool h12;
bool PM;
byte ADay, AHour, AMinute, ASecond, ABits;
bool ADy, A12h, Apm;

byte year, month, date, DoW, hour, minute, second;

int EIO1pin = 9;     // Input/output pin for chip selection
int XCKpin = 10;     // Clock input pin for taking display data
int LATCHpin = 11;   // Latch pulse input pin for display data
int SLEEPBpin = 12;  // Sleep Pin for the display
int DI0pin = 13;     // Input pin for display data
int VCCpin = 8;
int alarmPin = 7;

int nextButton = 5; //pos "T" on 3 way nav
int upButton = 4;  //pos "1" on 3 way nav
int downButton = 6;  //pos "2" on 3 way nav

//setup display with pin definitions
ePaper epaper = ePaper(EIO1pin, XCKpin, LATCHpin, SLEEPBpin, DI0pin);

char *space;
char *suffix;
char *dayOfWeekChar;
char *seperator; 

int buttonCount = 0; //start at zero starts clock in run mode

//unsigned long previousMillis = 0; // last time update
//long interval = 10000; // interval at which to do something (milliseconds)

void setup()
{

  Wire.begin();
  Serial.begin(57600);

  pinMode (nextButton, INPUT); 
  pinMode (upButton, INPUT);
  pinMode (downButton, INPUT);

  pinMode (VCCpin, OUTPUT);
  digitalWrite(VCCpin, HIGH);

  pinMode (alarmPin, INPUT);

  //force time setting:
  /*
   second = 00;
   minute = 02;
   hour = 13; //in 24 format
   DoW = 1;
   date = 26;
   month = 05;
   year = 13;
   initChrono();
   */
 
  epaper.writeTop("  EPAPER  ");
  epaper.writeBottom(" Clock v1 ");
  epaper.writeDisplay();
  delay(5000);
  epaper.deleteDisplay(); 

  setAlarms();
      
}

void loop()
{
  //get time & date
  Clock.getTime(year, month, date, DoW, hour, minute, second);   
  
if (digitalRead(alarmPin) == HIGH) {    ///don't really know what I'm doing here. Do I have to toggle the alarm off and then on again?
  Serial.print("Alarm is active");
  Serial.print('\n');
  //alarm went off......update the display......
  //code......

} else {
  Serial.print("Alarm is not active");
  Serial.print('\n');
  
}

  //display set menu if nextButton is pressed
  if (digitalRead(nextButton) == LOW) {

    buttonCount = buttonCount + 1;

    if (buttonCount == 7) {
      buttonCount = 0;
    }

    //debounce
    delay(600);
  }

      //Display time/date - default operation
    Serial.print("Button Count = 0");
    // time
    //display hours
    if (hour > 11) {
      suffix = " PM ";
    } 
    else {
      suffix = " AM ";
    }

    if (hour > 12) {
      hour -= 12;
    } 
    else if (hour == 0) {
      hour = 12;
    }

    //figure out leading spaces
    if (hour > 9) {
      space = " "; //1 space
    } 
    else {
      space = "  "; //2 spaces
    }

    if (minute < 10) {
      seperator = "-0";
    } 
    else {
      seperator = "-";
    }

    //construct the string to be displayed
    char timeChr[10];
    sprintf(timeChr, "%s%d%s%d%s", space, hour, seperator, minute, suffix);

    //date
    switch(DoW){
    case 1: 
      dayOfWeekChar ="Sun";
      break;
    case 2: 
      dayOfWeekChar ="Mon";
      break;
    case 3: 
      dayOfWeekChar ="Tue";
      break;
    case 4: 
      dayOfWeekChar ="Wed";
      break;
    case 5: 
      dayOfWeekChar ="Thu";
      break;
    case 6: 
      dayOfWeekChar ="Fri";
      break;
    case 7: 
      dayOfWeekChar ="Sat";
      break;
    }

    //construct the string to be displayed
    char dateChr[10];

    if ((month < 10) && (date < 10)) {
      sprintf(dateChr, " %s  %d/%d   ", dayOfWeekChar, month, date); //add an extra space to center text when month and DOM are < 10; extra spaces are intentional so no weird char pop in at the end
    }
    else {
      sprintf(dateChr, " %s %d/%d   ", dayOfWeekChar, month, date); //extra spaces are intentional so no weird char pop in at the end
    }

    epaper.writeTop(timeChr);
    epaper.writeBottom(dateChr);
    epaper.writeDisplay();
  }
 
   ///set code menu removed to make this easier to read...

} 

//DS3231 RTC interface
void initChrono() {
  set_time();
  set_date();
}

void set_date() {
  Clock.setYear(year);
  Clock.setMonth(month);
  Clock.setDate(date);
  Clock.setDoW(DoW);

}

void set_time() {
  Clock.setHour(hour);
  Clock.setMinute(minute);
  Clock.setSecond(second);
}

void setAlarms() {
    
  // Set AlarmBits, ALRM2 first, followed by ALRM1
      int AlarmBits = ALRM2_SET;
      AlarmBits <<= 4;
      AlarmBits |= ALRM1_SET;
      
   // set both alarms to :00 and :30 seconds, every minute
      // Format: .setA*Time(DoW|Date, Hour, Minute, Second, 0x0, DoW|Date, 12h|24h, am|pm)
      //                    |                                    |         |        |
      //                    |                                    |         |        +--> when set for 12h time, true for pm, false for am
      //                    |                                    |         +--> true if setting time based on 12 hour, false if based on 24 hour
      //                    |                                    +--> true if you're setting DoW, false for absolute date
      //                    +--> INTEGER representing day of the week, 1 to 7 (Monday to Sunday)
      //
      Clock.setA1Time(Clock.getDoW(), Clock.getHour(h12, PM), Clock.getMinute(), 30, AlarmBits, true, false, false);
      Clock.setA2Time(Clock.getDate(), Clock.getHour(h12, PM), Clock.getMinute(), AlarmBits, false, false, false);
      // Turn alarms on
      Clock.turnOnAlarm(1);
      Clock.turnOnAlarm(2);
}

So now I can set the time and date per the library. I can read the alarms too. It looks like they're being set 1 minute in the future, but the alarm pin never goes high. From the console:

14:31:31
Alarm 1: 1 DoW 14 31 30 enabled
Alarm 2: 26 Date 14 31 enabled
Alarm is not active

The time is 14:31:31 and the alarm is set to 14:31:30 but nothing happened. The Alarm pin never went high. Am I missing something?

The way I would solve the original problem is, I wouldn't use alarms. Instead, I would do this:

  • Create a variable called "old_minutes" or something. Initialize it to, let's say, 99 (or anything too big to be the actual minutes of the time).
  • Before you update the display, check to see if the minutes of the current time are the same as old_minutes. If they are the same, then the time hasn't changed, so don't bother.
  • When -- and only when -- you do update the display, change the value of old_minutes to whatever the new minutes are.

I don't know if that'll work in my case. I need to get alarms working.

What I want to eventually do is put the Atmega328 to sleep, attach an interrupt, and have it wake up and update the display when the alarm on the DS3231 goes off when the seconds hit "00".

I can't believe this is this difficult to figure out.

I may ditch this library all together. I found this little snippet over at SeeedStudio for their Seeeduino Stalker board.

//Interrupts for Battery management/saving using MCU power down mode. /INT from DS3231 is connected to INT0 of MCU.

#include <avr/sleep.h>
#include <Wire.h>
#include "DS3231.h"

DS3231 RTC; //Create the DS3231 object
static uint8_t prevSecond=0; 

void setup () 
{
     /*Initialize INT0 for accepting interrupts */
     PORTD |= 0x04; 
     DDRD &=~ 0x04;
  
     Serial.begin(57600);
     Wire.begin();
    
     RTC.begin();
     attachInterrupt(0, INT0_ISR, FALLING); 
     
     //Enable Interrupt 
     RTC.enableInterrupts(EveryMinute); //interrupt at  EverySecond, EveryMinute, EveryHour
     // or this
     //RTC.enableInterrupts(18,4,0);    // interrupt at (h,m,s)
}

void loop () 
{

    DateTime now = RTC.now(); //get the current date-time    
    if((now.second()) !=  prevSecond )
    {
    //print only when there is a change in seconds
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.date(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println(' ');
    }
    prevSecond = now.second();
    RTC.clearINTStatus();
 
} 
  
//Interrupt service routine for external interrupt on INT0 pin conntected to /INT
void INT0_ISR()
{
  //Keep this as short as possible. Possibly avoid using function calls
  
   Serial.println(" External Interrupt detected ");
}

I loaded it only my board at it successfully triggers and interrupt when the seconds hit '00'. I think I'll try and use that to trigger a display update. After banging my head against the keyboard for 3 days, I might be onto something.

ryemac3:
So now I can set the time and date per the library. I can read the alarms too. It looks like they're being set 1 minute in the future, but the alarm pin never goes high. From the console:

14:31:31
Alarm 1: 1 DoW 14 31 30 enabled
Alarm 2: 26 Date 14 31 enabled
Alarm is not active

The time is 14:31:31 and the alarm is set to 14:31:30 but nothing happened. The Alarm pin never went high. Am I missing something?

You need to connect the Alarm interrupt to one of the INT0 (pin 2) or INT1 (pin 3) on the 328p if you plan on using the DS3231's interrupt function. Then tell your code to watch for the interrupt.

The DS3231 interrupt pin is an active low interrupt. So it requires an external pull-up resistor to 5.5V (or less). When the alarm fires it briefly gets pulled low. This change is what the 328p's interrupt will detect and react on.

If you read the last bit of code you posted, in the setup() function you'll see where they configure the interrupt and use attachInterrupt() to detect the falling edge (when the DS3231 pulls it low.)

If you applied those code changes properly to the snippets I gave you, you'd notice it working just fine. I have a DS3231 turning on/off a PSU that in turn drives a window LED display, every day, on the dot at 6pm (ON) and 6am (OFF).

Strictly FWIW, here's a library I recently wrote for the DS3232 and DS3231. Unfortunately I haven't completed the documentation and there are no examples. Have a look, try it or borrow the part of the code you need. If you can't make any sense of it, drop me an IM and maybe that will provide me with the motivation to do some doc :smiley:

I still have to put the finishing touches on the code. I still never implemented the "update the display on a 1 minute alarm from the DS3231" thing. For now, the display just constantly updates. I'll tweak the code later.

For now...check it out! I got my PCBs in and put it all together:

I have a full write-up on my hobby blog. Full source is listed too.

If you want to see the project from start to finish, hit up the links at the bottom of the page.

ryemac3:
I still have to put the finishing touches on the code. I still never implemented the "update the display on a 1 minute alarm from the DS3231" thing. For now, the display just constantly updates. I'll tweak the code later.

For now...check it out! I got my PCBs in and put it all together:

I have a full write-up on my hobby blog. Full source is listed too.

If you want to see the project from start to finish, hit up the links at the bottom of the page.

Sweet job on the clock, and nice blog with some good stuff too! I'm disappointed though, Sparkfun no longer sells the E-Paper display.