Arduino Interrupt RTC

Good afternoon,

I’m trying to program RTC with interrupts but it’s happening a strange thing.
The following code works and shows “Interrupt” in the Serial as expected.

#include <Wire.h>
#include <RTClibExtended.h>
#include <LowPower.h>

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sabado"};

const int TimePin = 3;

void setup() {
  Serial.begin(9600);
  
  pinMode(TimePin, INPUT);
  Wire.begin();
  rtc.begin();
  rtc.adjust(DateTime(__DATE__,__TIME__));

  rtc.armAlarm(1, false);
  rtc.clearAlarm(1);
  rtc.alarmInterrupt(1, false);

  rtc.writeSqwPinMode(DS3231_OFF);
  rtc.setAlarm(ALM1_MATCH_HOURS, 11, 14, 0);   //set your wake-up time here
  rtc.alarmInterrupt(1, true);

  attachInterrupt(1, TimeFunction, FALLING); // Shower - Digital Pin 2
}

void loop() {
}

void TimeFunction() {
    Serial.println("Interrupt");
}

The following one doesn’t show anything in the Serial.

#include <Wire.h>
#include <RTClibExtended.h>
#include <LowPower.h>

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sabado"};

const int TimePin = 3;

void setup() {
  Serial.begin(9600);
  
  pinMode(TimePin, INPUT);
  Wire.begin();
  rtc.begin();
  rtc.adjust(DateTime(__DATE__,__TIME__));

  rtc.armAlarm(1, false);
  rtc.clearAlarm(1);
  rtc.alarmInterrupt(1, false);

  rtc.writeSqwPinMode(DS3231_OFF);
  rtc.setAlarm(ALM1_MATCH_HOURS, 13, 14, 0);   //set your wake-up time here
  rtc.alarmInterrupt(1, true);

  attachInterrupt(1, TimeFunction, FALLING); // Shower - Digital Pin 2
}

void loop() {
}

void TimeFunction() {
    DateTime now = rtc.now();
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
}

The following line is just for testing.

rtc.setAlarm(ALM1_MATCH_HOURS, 13, 14, 0);

I’m using a DS3231 RTC module with an Arduino Mega 2560. The SQW pin has a pullup resistor of 10k.
This is just a beginning of a project where I will put more than one alarm.
Can anyone help me and teach me why doesn’t this works ?

Using Serial.print() in an ISR is somewhat a bad idea.

Printing is actually storing what to print into a buffer and then relies on interruptions to get the data out using the serial hardware components. So printing uses interruption and interruption are disabled in the interrupt so the actual printing of what has been stored in the buffer only happens when you resume from the interruption and are back in the loop...

the challenge might happen if the print buffer is full. In that case the print function will block until some bytes have been sent out by the hardware, but since you are in an ISR with interrupts disabled, your buffer does not get emptied (IIRC the Hardware Serial class has some code to deal with interrupts disabled, but I've not looked at it recently to see if that handles this well or not)

long story short - printing in ISR is a bad idea (as anything slow or requiring interrupts), set up a flag and handle that flag in the loop

you could try serial.write

DH12043:
you could try serial.write

Curious to gain what?

The interrupt routine should set a flag which is monitored in the loop function. When the flag is on, read and print the time and turn the flag off.

Pete

volatile uint8_t RTC_flag = 0;

void TimeFunction(void)
{
  RTC_flag = 1;
}

void loop(void)
{
  if(RTC_flag) {
    RTC_flag = 0;
    DateTime now = rtc.now();
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
  }
}

Thanks for your answers!

My objetive here is a Low Power Datalogger that saves a variable (ShowerState), the time and 5 temperatures and saves in a SDCard.

The code I’m doing is this one:

#include <Wire.h>
#include <RTClibExtended.h>
#include <max6675.h>
#include <LowPower.h>
#include <SPI.h>
#include <SD.h>

RTC_DS3231 rtc;

int ktc1DO = 31;
int ktc1CS = 23;
int ktc1CLK = 22;
MAX6675 ktc1(ktc1CLK, ktc1CS, ktc1DO);

int button = 0;
int State = 0;
volatile int RTC_flag = 0;
volatile int count = 0;
volatile int Loop = 0;
volatile int ShowerState = 0;
int NowHour;
int NowMinute;
int TimeState;
int ButtonPressState;
const int ButtonPin = 2;
const int TimePin = 3;
const int chipSelect = 53;
unsigned long lastTime = 0;
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
String dataString = "";

void setup() {
  pinMode(TimePin, INPUT);
  pinMode(chipSelect, OUTPUT);
  SD.begin(chipSelect);
  Wire.begin();
  rtc.begin();
  
  Serial.begin(9600);
  delay(3000);
  
  rtc.armAlarm(1, false);
  rtc.clearAlarm(1);
  rtc.alarmInterrupt(1, false);
  rtc.armAlarm(2, false);
  rtc.clearAlarm(2);
  rtc.alarmInterrupt(2, false);

  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  DateTime now = rtc.now();
  NowHour = String(now.hour(), DEC).toInt();
  NowMinute = String(now.minute(), DEC).toInt();
  
  rtc.writeSqwPinMode(DS3231_OFF);
  rtc.setAlarm(ALM1_MATCH_HOURS, NowMinute + 1, NowHour, 0);
  rtc.alarmInterrupt(1, true);
  rtc.setAlarm(ALM2_MATCH_HOURS, NowMinute + 2, NowHour, 0);
  rtc.alarmInterrupt(2, true);
  
  attachInterrupt(1, TimeFunction, FALLING);
}

void loop() {
  //LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
  if(RTC_flag == 1){
    RTC_flag = 0;
    DataLog();
    count = 0;
  }
}

void DataLog() {
  if (count != 0 && Loop == 1) {
    Loop = 0;
    DateTime now = rtc.now();
    dataString += String(now.hour(), DEC);
    dataString += ",";
    dataString += String(now.minute(), DEC);
    dataString += ",";
    dataString += String(now.second(), DEC);
    dataString += ",";
    dataString += String(ShowerState);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    if (dataFile) {
      dataFile.println(dataString);
      dataFile.close();
      Serial.println(dataString);
    }
  }
  Loop = 0;
}


void TimeFunction() {
  cli();
  count = 1;
  Loop = 1;
  ShowerState = 0;
  RTC_flag = 1;
  sei();
}

The problem is this code is only saving the hours and only saves once. Can someone help me again ?

You need to clear each alarm so that it can interrupt again. Do this after the RTC_flag is cleared in loop().

    rtc.clearAlarm(1);
    rtc.clearAlarm(2);

You really should only clear the one that occurred. The only way I can see how to do that is to read the 0x0F register and check the A1F and A2F bits.

You do not need to call sei or cli in the interrupt routine. Interrupts are off in the interrupt routine. The cli() doesn't hurt because interrupts are off anyway. But the sei() can cause problems because it allows another interrupt to occur before this one has finished.

Another problem, if you intend to run this a long time unattended, is using Strings. The String class can run out of memory especially if you are using a NANO or UNO. Use null-terminated C strings.

BTW. The reason that you had problems with your original code is that the now() function in the extended RTC library reads the date and time directly from the RTC which previous versions of the RTC library didn't do.

Pete

The output pin on the RTC called “interrupt” does not require an interrupt input on the Arduino.

Using attachInterrupt() is just for your own education. It is not necessary here and makes your program hugely complex with strange rules on when you are and aren’t allowed to use Serial.print().

I did that because the SQW pin sends a interrupt when the alarm is triggered.
I have made some progress and now I can save everything I want.
The next feature is the LowPower mode I tried to do that this way:

void loop() {
  if(RTC_flag == 0 && Button_flag == 0){
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
  }
  else if(RTC_flag == 1 && Button_flag == 0){
    RTC_flag = 0;
    Button_flag = 0;
    DataLog();
    count = 0;
 }

When the LowPower.powerDown line is commented the code works like I want but when I uncommented this line the code doesn't save anything to the SD card.

There is some confusion over whether a FALLING interrupt can wake the processor from a power down sleep. I've been playing around with sleep modes and using interrupts and I get the best results when using CHANGE.
Try this:
Change the attachInterrupt in the setup function to this:
  attachInterrupt(digitalPinToInterrupt(TimePin), TimeFunction, CHANGE);. The alarm will now cause two interrupts so we need to detect the rising one and ignore it.
In the loop function you have this:

  else if(RTC_flag == 1 && Button_flag == 0){
    RTC_flag = 0;

Change it to:

  if(RTC_flag == 1 && Button_flag == 0){
    RTC_flag = 0;
    if(digitalRead(TimePin) == HIGH)return;  //Ignore the rising edge
    count = 1;
    Loop = 1;
    ShowerState = 0;

I don't think the else is needed.
This also moves three statements out of the TimerFunction into the loop function where they should be.
Finally, change TimeFunction back to just this:

void TimeFunction(void)
{
  RTC_flag = 1;
}

Pete

Thanks for the answers :slight_smile:

I tried to implement the changes you said but I’m getting the same results. It doens’t save with the line LowPower.PowerDown active.

I will put here the entire code so you can see better what I’m doing:

#include <Wire.h>
#include <RTClibExtended.h>
#include <max6675.h>
#include <LowPower.h>
#include <SPI.h>
#include <SD.h>

RTC_DS3231 rtc;

int ktc1DO = 31;
int ktc1CS = 23;
int ktc1CLK = 22;
MAX6675 ktc1(ktc1CLK, ktc1CS, ktc1DO);

int button = 0;
int State = 0;
volatile int RTC_flag = 0;
volatile int Button_flag = 0;
int count = 0;
int Loop = 0;
int ShowerState = 0;
int NowHour;
int NowMinute;
int TimeState;
int ButtonPressState;
const int ButtonPin = 2;
const int TimePin = 3;
const int chipSelect = 53;
unsigned long lastTime = 0;
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin
String dataString = "";

void setup() {
  pinMode(ButtonPin, INPUT);
  pinMode(TimePin, INPUT);
  pinMode(chipSelect, OUTPUT);
  SD.begin(chipSelect);
  Wire.begin();
  rtc.begin();

  Serial.begin(9600);
  delay(3000);

  rtc.armAlarm(1, false);
  rtc.clearAlarm(1);
  rtc.alarmInterrupt(1, false);
  rtc.armAlarm(2, false);
  rtc.clearAlarm(2);
  rtc.alarmInterrupt(2, false);

  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  DateTime now = rtc.now();
  NowHour = String(now.hour(), DEC).toInt();
  NowMinute = String(now.minute(), DEC).toInt();
  if (NowMinute < 45) {
    rtc.writeSqwPinMode(DS3231_OFF);
    rtc.setAlarm(ALM1_MATCH_HOURS, NowMinute + 1, NowHour, 0);
    rtc.alarmInterrupt(1, true);
    rtc.setAlarm(ALM2_MATCH_HOURS, NowMinute + 2, NowHour, 0);
    rtc.alarmInterrupt(2, true);
  }
  else {
    rtc.writeSqwPinMode(DS3231_OFF);
    rtc.setAlarm(ALM1_MATCH_HOURS, NowMinute + 3, NowHour + 1, 0);
    rtc.alarmInterrupt(1, true);
    rtc.setAlarm(ALM2_MATCH_HOURS, NowMinute + 4, NowHour + 1, 0);
    rtc.alarmInterrupt(2, true);
  }

  attachInterrupt(0, ButtonFunction, RISING);
  attachInterrupt(digitalPinToInterrupt(TimePin), TimeFunction, CHANGE);
}

void loop() {
  if (RTC_flag == 0 && Button_flag == 0) {
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
  }

  if (RTC_flag == 1 && Button_flag == 0) {
    RTC_flag = 0;
    Button_flag = 0;
    if (digitalRead(TimePin) == HIGH)return;
    Loop = 1;
    ShowerState = 0;
    count = 1;
    DataLog();
    count = 0;
    Interrupt();
  }
}

void Interrupt() {
  DateTime now = rtc.now();
  NowHour = String(now.hour(), DEC).toInt();
  NowMinute = String(now.minute(), DEC).toInt();
  if (NowMinute < 45) {
    rtc.clearAlarm(1);
    rtc.clearAlarm(2);
    rtc.setAlarm(ALM1_MATCH_HOURS, NowMinute + 1, NowHour, 0);
    rtc.alarmInterrupt(1, true);
    rtc.setAlarm(ALM2_MATCH_HOURS, NowMinute + 2, NowHour, 0);
    rtc.alarmInterrupt(2, true);
  }
  else {
    rtc.clearAlarm(1);
    rtc.clearAlarm(2);
    rtc.setAlarm(ALM1_MATCH_HOURS, NowMinute + 3, NowHour + 1, 0);
    rtc.alarmInterrupt(1, true);
    rtc.setAlarm(ALM2_MATCH_HOURS, NowMinute + 4, NowHour + 1, 0);
    rtc.alarmInterrupt(2, true);
  }
}

void DataLog() {
  if (count == 1 && Loop == 1) {
    Loop = 0;
    DateTime now = rtc.now();
    dataString += String(now.hour(), DEC);
    dataString += ",";
    dataString += String(now.minute(), DEC);
    dataString += ",";
    dataString += String(now.second(), DEC);
    dataString += ",";
    dataString += String(ShowerState);
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    if (dataFile) {
      dataFile.println(dataString);
      dataFile.close();
      dataFile.println();
    }
    dataString = "";
  }
}

void ButtonFunction() {
  cli();
  ShowerState = 1;
  RTC_flag = 0;
  Button_flag = 1;
  sei();
}

void TimeFunction() {
  RTC_flag = 1;
  Button_flag = 0;
}

Don’t mind with the ButtonFunction, I know it problably needs a Debounce.
The NowMinute + 1, + 2, etc. is just for testing. The objective here is to make interrupts every 15 and 45 minutes.

Update

With the LowPower Mode I can save the variable I want (ShowerState) but I can’t save the time it seems the Arduino stucks in the rtc.now() line.
Any ideia why?