Set Alarm interrupt with DS3231

I’m trying to make a battery operated bird feeder for a friend in Texas. I have the servo for the door/hatch working and the electronics are all doing what I want, the only issue I am running into is setting up the alarm on my ds3231 to actually go off at a specific time each day.

I recognize that there are some extraneous bits in there as I am basically just following various tutorials and it looks like a Frankenstein’s monster but I hope I can get some help figuring out the last bit of code I need so I can package everything up and get it postmarked.

#include "Arduino_DS3231_Wakeup.h"
#include "Arduino.h"
#include <avr/sleep.h>
#include <config.h>
#include <ds3231.h>
#include <Wire.h>
#include <Servo.h>

#define sleepPin 4  // When low, makes 328P go to sleep
#define wakePin 3   // when low, makes 328P wake up, must be an interrupt pin (2 or 3 on ATMEGA328P)
#define ledPin 2    // output pin for the LED (to show it is awake)
int led = 13;

Servo myservo;// create servo object to control a servo
// twelve servo objects can be created on most boards

int pos = 0;    // variable to store the servo position
int startPos = 80;
int openAngle = startPos-30;

// DS3231 alarm time
uint8_t wake_HOUR;
uint8_t wake_MINUTE;
uint8_t wake_SECOND;
uint8_t wake_DAY;
#define BUFF_MAX 256

///* A struct is a structure of logical variables used as a complete unit
// struct ts {
//    uint8_t sec;         /* seconds */
//    uint8_t min;         /* minutes */
//    uint8_t hour;        /* hours */
//    uint8_t mday;        /* day of the month */
//    uint8_t mon;         /* month */
//    int16_t year;        /* year */
//    uint8_t wday;        /* day of the week */
//    uint8_t yday;        /* day in the year */
//    uint8_t isdst;       /* daylight saving time */
//    uint8_t year_s;      /* year in short notation*/
//#ifdef CONFIG_UNIXTIME
//    uint32_t unixtime;   /* seconds since 01.01.1970 00:00:00 UTC*/
//#endif
//};
struct ts t;


void setup() {
  Serial.begin(9600);
  Serial.println("Setup started");

  // Latch the power
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);

  //attach servo to its pin location
  myservo.attach(9);

  // Clear the current alarm (puts DS3231 INT high)
  Wire.begin();
  DS3231_init(DS3231_CONTROL_INTCN);
  DS3231_clear_a1f();

  // Set date/time if not already set
//  DS3231_get(&t);
//  
//  if (t.year < 2021)
//    {
//      t.sec = 0;
//      t.min = 32;
//      t.hour = 11;
//      t.mday = 1;
//      t.mon = 12;
//      t.year = 2020;
//      DS3231_set(t);
//    }

  // Keep pins high until we ground them
  pinMode(sleepPin, INPUT_PULLUP);
  pinMode(wakePin, INPUT_PULLUP);

  // Flashing LED just to show the µController is running
  digitalWrite(ledPin, LOW);
  pinMode(ledPin, OUTPUT);
  pinMode(led,OUTPUT);

  Serial.println("Setup completed.");
}

// The loop just blinks an LED when not in sleep mode
void loop() {
  static uint8_t oldSec = 99;
  char buff[BUFF_MAX];

  // Just blink LED twice to show we're running
  doBlink();

  // Is the "go to sleep" pin now LOW?
  if (digitalRead(sleepPin) == LOW) {

    // Turn off L-LED
    digitalWrite(led, LOW);

    // Set the DS3231 alarm to wake up in X seconds
    Serial.println("Setting alarm, switching off");
    setNextAlarm();
    delay(1000);
    digitalWrite(13, LOW);

    static byte prevADCSRA = ADCSRA;
    ADCSRA = 0;

    set_sleep_mode (SLEEP_MODE_PWR_DOWN);
    sleep_enable();

    MCUCR = bit (BODS) | bit (BODSE);

    // The BODS bit is automatically cleared after three clock cycles so we better get on with it
    MCUCR = bit (BODS);

    noInterrupts();
    attachInterrupt(digitalPinToInterrupt(wakePin), sleepISR, LOW);

    // Send a message just to show we are about to sleep
    Serial.println("Good night!");
    Serial.flush();

    // Allow interrupts now
    interrupts();

    // And enter sleep mode as set above
    sleep_cpu();

    // --------------------------------------------------------
    // µController is now asleep until woken up by an interrupt
    // --------------------------------------------------------

    // Wakes up at this point when wakePin is brought LOW - interrupt routine is run first
    Serial.println("I'm awake!");

    // Clear existing alarm so int pin goes high again
    DS3231_clear_a1f();

    // Re-enable ADC if it was previously running
    ADCSRA = prevADCSRA;

    //Turn back on L-LED
    digitalWrite(led, HIGH);

    DS3231_get(&t);

    if (t.sec != oldSec)
      {
      // display current time
      snprintf(buff, BUFF_MAX, "%d.%02d.%02d %02d:%02d:%02d\n", t.year,
        t.mon, t.mday, t.hour, t.min, t.sec);
       Serial.print(buff);

       oldSec = t.sec;
      }

    //Write all the other code right here...

  myservo.write(startPos);
  delay(500);

  for (pos = startPos; pos >= openAngle; pos -= 1) { // goes from 80 degrees to 50 degrees
    // in steps of 1 degree
    myservo.write(pos);
    delay(10);
  }
  delay(400);
  
  for (pos = openAngle; pos <= startPos; pos += 1)
  {
    myservo.write(pos);
    delay(10);
  }
  delay(2000);
    
    //delay(8000);
    //digitalWrite(sleepPin, LOW);
  }
  else
  {
      //Get the time
      DS3231_get(&t);

      //If the seconds has changed, display the (new) time
      if (t.sec != oldSec)
      {
      // display current time
      snprintf(buff, BUFF_MAX, "%d.%02d.%02d %02d:%02d:%02d\n", t.year,
          t.mon, t.mday, t.hour, t.min, t.sec);
      Serial.print(buff);
      oldSec = t.sec;
    }
  }
}

// When wakePin is brought LOW this interrupt is triggered FIRST (even in PWR_DOWN sleep)
void sleepISR() {
  // Prevent sleep mode, so we don't enter it again, except deliberately, by code
  sleep_disable();

  // Detach the interrupt that brought us out of sleep
  detachInterrupt(digitalPinToInterrupt(wakePin));

  // Now we continue running the main Loop() just after we went to sleep
}


// Double blink just to show we are running. Note that we do NOT
// use the delay for final delay here, this is done by checking
// millis instead (non-blocking)
void doBlink() {
  static unsigned long lastMillis = 0;

  if (millis() > lastMillis + 2000) {
    digitalWrite(ledPin, HIGH);
    delay(10);
    digitalWrite(ledPin, LOW);
    delay(200);
    digitalWrite(ledPin, HIGH);
    delay(10);
    digitalWrite(ledPin, LOW);
    lastMillis = millis();
  }
}

// Set the next alarm
void setNextAlarm(void)
    {
  // flags define what calendar component to be checked against the current time in order
  // to trigger the alarm - see datasheet
  // A1M1 (seconds) (0 to enable, 1 to disable)
  // A1M2 (minutes) (0 to enable, 1 to disable)
  // A1M3 (hour)    (0 to enable, 1 to disable)
  // A1M4 (day)     (0 to enable, 1 to disable)
  // DY/DT          (dayofweek == 1/dayofmonth == 0)
  uint8_t flags[5] = { 1, 0, 0, 0, 1 };

  // get current time so we can calc the next alarm
  DS3231_get(&t);

  wake_SECOND = t.sec;
  wake_MINUTE = t.min;
  wake_HOUR = t.hour;
  wake_DAY = t.mday;

  // Add a some seconds to current time. If overflow increment minutes etc.
  wake_SECOND = wake_SECOND + 10;
  if (wake_SECOND > 59)
  {
    wake_MINUTE++;
    wake_SECOND = wake_SECOND - 60;

    if (wake_MINUTE > 59)
    {
      wake_HOUR++;
      wake_MINUTE -= 60;

      if (wake_HOUR > 23)
      {
        wake_DAY++;
        wake_HOUR -= 24;
      }
    }
  }

  // Set the alarm time (but not yet activated)
  DS3231_set_a1(wake_SECOND, wake_MINUTE, wake_HOUR, wake_DAY, flags);

  // Turn the alarm on
  DS3231_set_creg(DS3231_CONTROL_INTCN | DS3231_CONTROL_A1IE);
}

It's not likely to happen until you explain what is wrong with the "last bit of code". You didn't even tell us if the program works or not.

It looks like you are enabling the interrupt. What problem are you having?

Did you get Ralph's original code to run before modifying it? I think that would be the first step just to make sure the connections are right.

Sorry, I'll try to go through it. The code does work, the only thing I am struggling with is fixing it so that the Arduino goes to sleep after running the servo's for loop around line 191 and then waking up either a specified time later or after a specific length of time, I can work with either.

After all the includes, I define the pins I want to use, set up the servo and then add in the variables I want for its starting position and angle of rotation. (sleepPin, startPos, openAngle, etc.)

I then set up the variables for keeping track of time based on the library that I am using (ds3231 by peter rodan)
DS3231 Library

wake_HOUR/MINUTE/SECOND/DAY

After that is where is gets a bit more confusing. Around line 66 is when I start initializing the DS3231 and set the correct date/time as well as get the sleepPin/wakePin ready for input. As the code is written right now, the Arduino is waiting for a digitalWrite(sleepPin) = LOW to come from somewhere to turn it off and go into deep sleep. Currently I am doing that with a button press to pin 4 to make sure everything else works and then again to pin 3 to wake it up and run the rest of the code in the main loop.

The section I believe I need help with is in the setNextAlarm() function at the bottom of the code. As it stands, I have attempted to get it to wake itself up after 10 seconds by incrementing the wake_SECOND + 10 but I am missing think I am missing something in the code because whenever I turn off the arduino, again, I have to push another button to get it to turn back on. Do I just need a line in there for the wakePin to be shifted high after the determined increment happens?

I actually got most of his code to work, the only thing I never got to work was the 10 second "sleep" that he showed, it would always just stay off unless I physically reset the arduino.

Do you have the DS3231's INT/SQW pin connected to your wakeup pin? That's how the alarm triggers a wakeup.

I do have that SQW pin in slot 3, but whenever I run his script with no changes I just get repeating:

Setup completed.
I’m awake!
Good Night!

every 10 seconds or so, which means something is working, but then the time display isn’t working and its not waiting for an input to go to sleep, it is just is going to sleep.

His code:

#include "Arduino.h"
#include <avr/sleep.h>
#include <Wire.h>
#include <config.h>
#include <ds3231.h>

#define sleepPin 9  // When low, makes 328P go to sleep
#define wakePin 3   // when low, makes 328P wake up, must be an interrupt pin (2 or 3 on ATMEGA328P)
#define ledPin 2    // output pin for the LED (to show it is awake)

// DS3231 alarm time
uint8_t wake_HOUR;
uint8_t wake_MINUTE;
uint8_t wake_SECOND;
#define BUFF_MAX 256

///* A struct is a structure of logical variables used as a complete unit
// struct ts {
//    uint8_t sec;         /* seconds */
//    uint8_t min;         /* minutes */
//    uint8_t hour;        /* hours */
//    uint8_t mday;        /* day of the month */
//    uint8_t mon;         /* month */
//    int16_t year;        /* year */
//    uint8_t wday;        /* day of the week */
//    uint8_t yday;        /* day in the year */
//    uint8_t isdst;       /* daylight saving time */
//    uint8_t year_s;      /* year in short notation*/
//#ifdef CONFIG_UNIXTIME
//    uint32_t unixtime;   /* seconds since 01.01.1970 00:00:00 UTC*/
//#endif
//};
struct ts t;

// Standard setup( ) function
void setup() {
  Serial.begin(9600);

  // Keep pins high until we ground them
  pinMode(sleepPin, INPUT_PULLUP);
  pinMode(wakePin, INPUT_PULLUP);

  // Flashing LED just to show the µController is running
  digitalWrite(ledPin, LOW);
  pinMode(ledPin, OUTPUT);

  // Clear the current alarm (puts DS3231 INT high)
  Wire.begin();
  DS3231_init(DS3231_CONTROL_INTCN);
  DS3231_clear_a1f();

  Serial.println("Setup completed.");
}

// The loop blinks an LED when not in sleep mode
void loop() {
  static uint8_t oldSec = 99;
  char buff[BUFF_MAX];

  // Just blink LED twice to show we're running
  doBlink();

  // Is the "go to sleep" pin now LOW?
  if (digitalRead(sleepPin) == LOW)
  {
    // Set the DS3231 alarm to wake up in X seconds
    setNextAlarm();

    // Disable the ADC (Analog to digital converter, pins A0 [14] to A5 [19])
    static byte prevADCSRA = ADCSRA;
    ADCSRA = 0;

    /* Set the type of sleep mode we want. Can be one of (in order of power saving):
     SLEEP_MODE_IDLE (Timer 0 will wake up every millisecond to keep millis running)
     SLEEP_MODE_ADC
     SLEEP_MODE_PWR_SAVE (TIMER 2 keeps running)
     SLEEP_MODE_EXT_STANDBY
     SLEEP_MODE_STANDBY (Oscillator keeps running, makes for faster wake-up)
     SLEEP_MODE_PWR_DOWN (Deep sleep)
     */
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable()
    ;

    // Turn of Brown Out Detection (low voltage)
    // Thanks to Nick Gammon for how to do this (temporarily) in software rather than
    // permanently using an avrdude command line.
    //
    // Note: Microchip state: BODS and BODSE only available for picoPower devices ATmega48PA/88PA/168PA/328P
    //
    // BODS must be set to one and BODSE must be set to zero within four clock cycles. This sets
    // the MCU Control Register (MCUCR)
    MCUCR = bit (BODS) | bit(BODSE);

    // The BODS bit is automatically cleared after three clock cycles so we better get on with it
    MCUCR = bit(BODS);

    // Ensure we can wake up again by first disabling interupts (temporarily) so
    // the wakeISR does not run before we are asleep and then prevent interrupts,
    // and then defining the ISR (Interrupt Service Routine) to run when poked awake
    noInterrupts();
    attachInterrupt(digitalPinToInterrupt(wakePin), sleepISR, LOW);

    // Send a message just to show we are about to sleep
    Serial.println("Good night!");
    Serial.flush();

    // Allow interrupts now
    interrupts();

    // And enter sleep mode as set above
    sleep_cpu()
    ;

    // --------------------------------------------------------
    // µController is now asleep until woken up by an interrupt
    // --------------------------------------------------------

    // Wakes up at this point when wakePin is brought LOW - interrupt routine is run first
    Serial.println("I'm awake!");

    // Clear existing alarm so int pin goes high again
    DS3231_clear_a1f();

    // Re-enable ADC if it was previously running
    ADCSRA = prevADCSRA;
  }
  else
  {
    // Get the time
    DS3231_get(&t);

    // If the seconds has changed, display the (new) time
    if (t.sec != oldSec)
    {
      // display current time
      snprintf(buff, BUFF_MAX, "%d.%02d.%02d %02d:%02d:%02d\n", t.year,
          t.mon, t.mday, t.hour, t.min, t.sec);
      Serial.print(buff);
      oldSec = t.sec;
    }
  }
}

// When wakePin is brought LOW this interrupt is triggered FIRST (even in PWR_DOWN sleep)
void sleepISR() {
  // Prevent sleep mode, so we don't enter it again, except deliberately, by code
  sleep_disable()
  ;

  // Detach the interrupt that brought us out of sleep
  detachInterrupt(digitalPinToInterrupt(wakePin));

  // Now we continue running the main Loop() just after we went to sleep
}

// Double blink just to show we are running. Note that we do NOT
// use the delay for the final delay here, this is done by checking
// millis instead (non-blocking)
void doBlink() {
  static unsigned long lastMillis = 0;

  if (millis() > lastMillis + 1000)
  {
    digitalWrite(ledPin, HIGH);
    delay(10);
    digitalWrite(ledPin, LOW);
    delay(200);
    digitalWrite(ledPin, HIGH);
    delay(10);
    digitalWrite(ledPin, LOW);
    lastMillis = millis();
  }
}

// Set the next alarm
void setNextAlarm(void)
    {
  // flags define what calendar component to be checked against the current time in order
  // to trigger the alarm - see datasheet
  // A1M1 (seconds) (0 to enable, 1 to disable)
  // A1M2 (minutes) (0 to enable, 1 to disable)
  // A1M3 (hour)    (0 to enable, 1 to disable)
  // A1M4 (day)     (0 to enable, 1 to disable)
  // DY/DT          (dayofweek == 1/dayofmonth == 0)
  uint8_t flags[5] = { 0, 0, 0, 1, 1 };

  // get current time so we can calc the next alarm
  DS3231_get(&t);

  wake_SECOND = t.sec;
  wake_MINUTE = t.min;
  wake_HOUR = t.hour;

  // Add a some seconds to current time. If overflow increment minutes etc.
  wake_SECOND = wake_SECOND + 10;
  if (wake_SECOND > 59)
  {
    wake_MINUTE++;
    wake_SECOND = wake_SECOND - 60;

    if (wake_MINUTE > 59)
    {
      wake_HOUR++;
      wake_MINUTE -= 60;
    }
  }

  // Set the alarm time (but not yet activated)
  DS3231_set_a1(wake_SECOND, wake_MINUTE, wake_HOUR, 0, flags);

  // Turn the alarm on
  DS3231_set_creg(DS3231_CONTROL_INTCN | DS3231_CONTROL_A1IE);
}

Ok, got his code to work, wasn't looking and accidentally had the servo data pin plugged into the sleepPin and viceversa. ::slight_smile: