RTC Time Not Correct Inside Interrupt

Hi all,

I am learning about using the RTC of an ESP32.

I have got it working perfectly in the "main loop" but when I try using the exact same code in the interrupt it outputs "2165-165-165T165:165:85Z" every time, all the time.

My code is:

#include "RTClib.h"

RTC_DS3231 rtc;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

/* LOG A SENSOR READING */
void IRAM_ATTR logSensorReading() {
  portENTER_CRITICAL_ISR(&timerMux);
  /* GET THE CURRENT TIME */
  DateTime now = rtc.now();
  /* AND PUT THE CURRENT YEAR, MONTH, DAY, HOURS, MINUTES, SECONDS INTO ONE STRING */
  int currentYear = now.year(); //put the current year into a variable
  Serial.println(currentYear);
  int currentMonth = now.month(); //put the current month into a variable
  Serial.println(currentMonth);
  int currentDay = now.day(); //put the current day into a variable
  Serial.println(currentDay);
  int currentHour = now.hour(); //put the current hours into a variable
  Serial.println(currentHour);
  int currentMinute = now.minute(); //put the current minutes into a variable
  Serial.println(currentMinute);
  int currentSecond = now.second(); //put the current seconds into a variable
  Serial.println(currentSecond);

  String receivedTimeDate = (String(currentYear) + "-" + toStringAddZero(currentMonth) + "-" + toStringAddZero(currentDay)) + ("T") + (toStringAddZero(currentHour) + ":" + toStringAddZero(currentMinute) + ":" + toStringAddZero(currentSecond)) + ("Z");

  Serial.println(receivedTimeDate);

  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup () {

  Serial.begin(115200);

  delay(3000); // wait for console opening

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    rtc.adjust(DateTime(2019, 5, 5, 11, 25, 1));
  }

  /* CREATE AN INTERRUPT FOR LOGGING A DATA READING */
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &logSensorReading, true);
  timerAlarmWrite(timer, 5000000, true);
  timerAlarmEnable(timer);
}

void loop () {

}

/* toStringAddZero() this function adds a zero to a date string if its a single digit for example if data = 1, then the function will return 01
    this makes the date and time string's consistant size for display */
String toStringAddZero(int data)
{
  String st = "";
  if (data < 10)
  {
    st = "0" + String(data);
  }
  else
  {
    st = String(data);
  }
  return st;
}

I know I should not have Serial.println so I have tried removing all Serial.println but the same thing still happens.

My .h and .cpp files are attached if that is any use :slight_smile:

Does anyone know what the problem is?

Thanks very much,

Zeb

RTClib.cpp (35.3 KB)

RTClib.h (11.4 KB)

i don't know

in general, interrupts should be short and used to capture events or perform brief time critical operations

why don't you try capturing the time, "now" in the interrupt, setting a flag (byte) that prevents it from being changed again while in the interrupt and displaying "now" in loop() when flag is set. clear flag afterwards in loop()

Have a read of this thread: interrupts and RTC - Programming Questions - Arduino Forum

It explains things to save me repeating everything.

Please make help links clickable, there are tags for that.

aarg:
Please make help links clickable, there are tags for that.

Done, the other forum I frequent does it by default so I, unfortuantely, often forget here.

Hi all, thanks for your help!

Unfortunately flagging it and getting the time in the main loop won't work because I have several interrupts come in when doing another task and the times need to be correct to the second.

Thanks for the link countrypaul :slight_smile:

I guess I could record current millis and convert it to seconds and then minus it from the RTC time in the loop, but that would be pretty complex because of handling minutes and multiple storage of millis.

Is there anyway to make the interrupt get the correct RTC at that time?

Thanks,

Zeb

ZebH:
Unfortunately flagging it and getting the time in the main loop won't work because I have several interrupts come in when doing another task and the times need to be correct to the second.

I don't understand why that is a problem.

The ISR will have recorded the time and when your code in loop() gets around to reading it it will see the time at which the ISR was triggered. Get the ISR to record the time to a global variable. Perhaps all you need is

void IRAM_ATTR logSensorReading() {
  portENTER_CRITICAL_ISR(&timerMux);
                /* GET THE CURRENT TIME */
     ISRtriggeredAt = rtc.now();
  portEXIT_CRITICAL_ISR(&timerMux);
}

By the way I have no idea what the line portENTER_CRITICAL... does

...R

I Believe than portENTER_CRITICAL_ISR disables interupts and uses a mutex to prevent concurrent access to any variables etc. whilst the section of code between entering and exiting that crtical_isr is executed.

It maybe one solution is to get the time from the rtc just before executing the portENTER_CRITICAL_ISR.

ZebH:
Unfortunately flagging it and getting the time in the main loop won't work because I have several interrupts come in when doing another task and the times need to be correct to the second.

If you are doing it as suggested in reply #1, my question to you would be, what times have to be correct? Any time that you capture and flag in the ISR will reflect exactly the time that the interrupt occurred, no matter how long you wait to service the flag in the main loop.

aarg:
If you are doing it as suggested in reply #1, my question to you would be, what times have to be correct? Any time that you capture and flag in the ISR will reflect exactly the time that the interrupt occurred, no matter how long you wait to service the flag in the main loop.

Additionally, if the loop() function is not executing many, many times faster than once per second, then something is very wrong.

Having just reread this thread, why are you using the portENTER_CRITICAL_ISR at all, when the only thing you are doing is requesting the time from the RTC?

Hi all,

Robin2:
I don't understand why that is a problem.

The ISR will have recorded the time and when your code in loop() gets around to reading it it will see the time at which the ISR was triggered. Get the ISR to record the time to a global variable. Perhaps all you need is

void IRAM_ATTR logSensorReading() {

portENTER_CRITICAL_ISR(&timerMux);
                /* GET THE CURRENT TIME */
    ISRtriggeredAt = rtc.now();
  portEXIT_CRITICAL_ISR(&timerMux);
}




By the way I have no idea what the line portENTER_CRITICAL... does

...R

The reason I think that this code won't work is that let's say it's doing something in the .cpp file. While it's doing it's thing 3 pulses come in 1 second apart and you have to log the time each pulse came in to the millisecond. I just don't see how the code you have provided could handle more than 1 pulse before having to be cleared..... Is this correct?

As for the reason I use portENTER_CRITICAL_ISR and portEXIT_CRITICAL_ISR all the examples I saw used these. Is that not necessary?

Thanks,

Zeb

Unless are you saying that rtc.now() gets entered into a queue each time the interrupt is triggeres and the queue gets cleared when it is next in the "main loop"?

ZebH:
let's say it's doing something in the .cpp file. While it's doing it's thing 3 pulses come in 1 second apart and you have to log the time each pulse came in to the millisecond.

If you are saying that the situation can arise where some piece of code blocks for more than 3 seconds before returning to loop() then the program needs a complete redesign . This was already mentioned in Reply #9.

The program should be designed so that everything returns control to loop() in a short enough time to allow loop() to repeat at least 10 times per second and 100 times or 1000 times per second would be more usual, especially as you are using a fast ESP32. If the program is designed properly then it will easily be able to respond to pulses that are 1 second apart.

Have a look at how the code is organized in Several Things at a Time

Note how each function runs very briefly and returns to loop() so the next one can be called. None of the functions tries to complete a task in one call. And there may be dozens of calls to a function before it is actually time for it to do anything.

...R

The big program I am working on takes a sensor reading and then does a HTTPS Posts with it (from .cpp file of a custom library) so I need it to have the interrupt trigger when other voltage pulses come in, record the current time and add it to the queue for when the program next gets to the loop.

Would it be a wise idea to return to the loop from the .cpp file?

ZebH:
Would it be a wise idea to return to the loop from the .cpp file?

I have no idea - I don't know what's in the CPP file. (And that is not a request to post it here :slight_smile: )

so I need it to have the interrupt trigger when other voltage pulses come in, record the current time and add it to the queue

Why not have a short array into which the ISR can save the times. When the code in loop() reads the array it can reset the index back to 0, The ISR can increment the index after every write (up to the permitted max). Perhaps like this

void IRAM_ATTR logSensorReading() {
  portENTER_CRITICAL_ISR(&timerMux);
                /* GET THE CURRENT TIME */
     if (arrayNdx < maxItemsAllowed) {
         ISRtriggeredAt[arrayNdx] = rtc.now();
         arrayNdx ++;
     }
  portEXIT_CRITICAL_ISR(&timerMux);
}

OK, thanks very much Robin2!

Hi all,

I am now back working trying to solve this problem.

I have written this piece of code to simply get the rtc time every 5 seconds and then adjust and display in the main loop:

#include "RTClib.h"

RTC_DS3231 rtc;
DateTime now;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

/* LOG A SENSOR READING */
void IRAM_ATTR logSensorReading() {
  /* GET THE CURRENT TIME */
  now = rtc.now();
}

void setup () {
  Serial.begin(115200);
  delay(3000); // wait for console opening

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    rtc.adjust(DateTime(2019, 5, 5, 11, 25, 1));
  }

  /* CREATE AN INTERRUPT FOR LOGGING A DATA READING */
  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &logSensorReading, true);
  timerAlarmWrite(timer, 5000000, true);
  timerAlarmEnable(timer);
}

void loop () {
  /* AND PUT THE CURRENT YEAR, MONTH, DAY, HOURS, MINUTES, SECONDS INTO ONE STRING */
  int currentYear = now.year(); //put the current year into a variable
  Serial.println(currentYear);
  int currentMonth = now.month(); //put the current month into a variable
  Serial.println(currentMonth);
  int currentDay = now.day(); //put the current day into a variable
  Serial.println(currentDay);
  int currentHour = now.hour(); //put the current hours into a variable
  Serial.println(currentHour);
  int currentMinute = now.minute(); //put the current minutes into a variable
  Serial.println(currentMinute);
  int currentSecond = now.second(); //put the current seconds into a variable
  Serial.println(currentSecond);

  String receivedTimeDate = (String(currentYear) + "-" + toStringAddZero(currentMonth) + "-" + toStringAddZero(currentDay)) + ("T") + (toStringAddZero(currentHour) + ":" + toStringAddZero(currentMinute) + ":" + toStringAddZero(currentSecond)) + ("Z");

  Serial.println(receivedTimeDate);
  delay(2000);
}

/* toStringAddZero() this function adds a zero to a date string if its a single digit for example if data = 1, then the function will return 01
    this makes the date and time string's consistant size for display */
String toStringAddZero(int data)
{
  String st = "";
  if (data < 10)
  {
    st = "0" + String(data);
  }
  else
  {
    st = String(data);
  }
  return st;
}

But I am still getting this result from the RTC:

2165-165-165T165:165:85Z

This problem is really frustrating. Do you guys have any suggestions?

Thanks, Zeb

ZebH:
I have written this piece of code to simply get the rtc time every 5 seconds

Why would you need an interrupt for something that only happens once every 5 seconds?

...R

Before exitingsetup whether puting a call: now=rtc.now(); might help clarify what is going on? If the first pass through Loop() then gives a reasonable answer it would indicate whether the ISR is successfully using the RTC or not.