Do Loop running over RTC comparison

I am trying to run a do loop which will operate a motor for a given length of time. I am using RTClib.h so I can compare actual times. I read in the current time add a length of time in seconds to it to get an end time and start the loop. The problem is the end time is never being reached and the do while loop is ending. I don't want to use a delay because this value is not consistent.

#include <Wire.h>
#include <RTClib.h>


RTC_DS1307 rtc;

//Inputs & Outputs

int Dirty=5;   //Pressure Transducer N/O
int Filter=9;  //Filter Motor Advance Output

//Time Vars
DateTime Now;                 //Current time read in
DateTime FilterAdv;           //Time to stop advancing filter (calculated)

//Filter advance time in seconds
int FilterAdd = 30;

//showDate necessary only for output to Serial for checking code
// use: ex. [showDate("dt", dt);] to see calculated time in unix
void showDate(const char* txt, const DateTime& dt) {
    Serial.print(txt);
    Serial.print(' ');
    Serial.print(dt.year(), DEC);
    Serial.print('/');
    Serial.print(dt.month(), DEC);
    Serial.print('/');
    Serial.print(dt.day(), DEC);
    Serial.print(' ');
    Serial.print(dt.hour(), DEC);
    Serial.print(':');
    Serial.print(dt.minute(), DEC);
    Serial.print(':');
    Serial.print(dt.second(), DEC);

    Serial.print(" = ");
    Serial.print(dt.unixtime());
    Serial.print("s / ");
    Serial.print(dt.unixtime() / 86400L);
    Serial.print("d since 1970");

    Serial.println();
}

void setup() 
{
  pinMode(Dirty, INPUT_PULLUP);  
  pinMode(Filter, OUTPUT);
    digitalWrite(Filter, LOW);


  Serial.begin(9600);

  //Initialize DS3231 clock
  rtc.begin();
}

void loop() 
{
  if (digitalRead(Dirty) == LOW)
  {
    Serial.println("Advance Paper");
    AdvancePaper();
  }     
}

void AdvancePaper()
{
  Now = rtc.now();
  FilterAdv = (Now.unixtime() + FilterAdd);
  showDate("Now", Now.unixtime());
  showDate("FilterAdv", FilterAdv.unixtime());

  do
  {
    //Serial.println("Looping");
    digitalWrite(Filter, HIGH);
    Now = rtc.now();
  } while (Now.unixtime() < FilterAdv.unixtime());

  
  Serial.println("Leave While Loop");
  digitalWrite(Filter, LOW);

  showDate("Now", Now.unixtime());
  
}

Hello catguy

Welcome to the best Arduino forum ever :slight_smile:

What´s the meaning of "value is not consistent"?

1 Like

So you want to run the thing for the FilterAdd=30 seconds interval after you press the time you button?

Why not measure the 30 second interval directly from the time of the button-press, and use the RTC formatting the times for the UI?

this is blocking (and there is no need to repeat the digitalWrite)

So it's the same as writing

  digitalWrite(Filter, HIGH);
  delay(1000ul * FilterAdd);

if you don't want blocking code, then you need to take a different approach. you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

1 Like

Works for me:

Maybe I don't understand how:

1 Like

If you care about the time being exacly 30 seconds, you'll do better with delay() - the code that works for us (THX for the link @DaveX) but not for you (?) will enter the seconds somewhere in the middle, so you'll get a varying period of 29 .. 30 seconds. Or 30 .. 31, I'm too lazy but it will average N + 0.5 seconds.

Using delay will give you very close to 30 seconds every time. And be off by the same insignificant anount.

a7

1 Like

With:

and:

I see a non-blocking state-machine-based solution to as something like:

...with the times changed to const uint32_t LedDelayTime = 0, LedOnTime = 30000 ; plus the RTC-enabled time reporting at the transitions.

1 Like
  int buttonOn = digitalRead(OnPin) == LOW;

make buttonOn a bool if you assign a truth value :slight_smile:

1 Like

Done:

void loop() {
  // Input phase of https://en.wikipedia.org/wiki/IPO_model
  uint32_t now = millis();
  bool buttonOn = digitalRead(OnPin) == LOW;

  ...

Hello catguy

What is the task of the programme in real life?

Did you leave the building?

When I run the code this is what happens between the Arduino and Serial Monitor:

08:09:12.268 -> Advance Paper
08:09:12.268 -> Now 2025/3/12 8:8:53 = 1741766933s / 20159d since 1970
08:09:12.388 -> FilterAdv 2025/3/12 8:9:23 = 1741766963s / 20159d since 1970
08:09:12.388 -> Leave While Loop
08:09:12.388 -> Now 2025/3/12 8:8:53 = 1741766933s / 20159d since 1970

J-M-L : Thank you for pointing out the digitalWrite(Filter, HIGH); inside the do loop I have moved it out just before the do so now I have this:

digitalWrite(Filter, HIGH);
  do
  {
    //Serial.println("Looping");
    
    Now = rtc.now();
  } while (Now.unixtime() < FilterAdv.unixtime());

The reason I need to get this figured out with RTC is I am running another motor in another portion of the code that needs to run for 2 hours after the last time motion has been detected, so the shut off time has to be dynamic.

I am currently just using the Serial Monitor for trouble shooting the code ...

Even if I modify the do loop to introduce a counter and delay it still stops short of the goal:

 digitalWrite(Filter, HIGH); 
  int count = 0;
  do
  {
    //Serial.println("Looping");
    count++;
    delay(1000ul);
    Now = rtc.now();
  } while (Now.unixtime() < FilterAdv.unixtime() && (count <= 30));

  Serial.println("Leave While Loop");
  Serial.println(count);
  Now = rtc.now();
  showDate("Now", Now.unixtime());
  
  digitalWrite(Filter, LOW);
08:56:31.662 -> Advance Paper
08:56:31.662 -> Now 2025/3/12 8:56:12 = 1741769772s / 20159d since 1970
08:56:31.744 -> FilterAdv 2025/3/12 8:56:42 = 1741769802s / 20159d since 1970
08:56:52.755 -> Leave While Loop
08:56:52.825 -> 21
08:56:52.825 -> Now 2027/3/12 8:56:33 = 1804841793s / 20889d since 1970

08:56:52.825 -> Now 2027/3/12 8:56:33 = 1804841793s / 20889d since 1970 ?

1 Like

Agree with @ZX80.

This debugging data looks like the RTC might be giving you garbage data.

Does the RTC work reliably if you try one of the RTClib example scripts for the DS1307?

Anecdotally, my DS3231 did not work well when read continuously in a loop.

DaveX, example scripts from RTClib for DS1307 appear to be working with consistent results. I did notice an improvement with my loop after inserting delay(1000ul); in the middle to slow it down. I ordered some DS3231, which should arrive the first of next week, to see if it may be the DS1307 that was causing my issues. I find it interesting that the code works in the simulator that you shared, but not on my hardware. Which makes me wonder if it isn't in the components.

I appreciate all of the quick responses and suggestions and will post what I find after receiving the replacement parts.

That still doesn't look like it really needs the RTC do loop. It seems like it could be done with a FSM. So far, it sounds like the system is either Waiting for the DirtyFilter signal, running MotorA for 30 seconds, or running MotorB for 2 hours. If the system needs to be paying attention to other things while a delay(30000) or delay(7200000) is happening, (such as counting down or displaying the time, or acting on other signals, integrating Pasteurization Units, or monitoring millis() accuracy with the RTC), then millis()-based timing should be able to enable doing several things at the same time.

1 Like

Definitely a good candidate for a FSM.

1 Like

No, that doesn't mean you need an RTC.

What it does mean it's that you can't use the do-loop you are trying to work with. If the 2 hours are up while this loop is running, the Arduino won't realise and shut off the motor.

The built-in millis() function should be accurate to less than a second over these periods. An RTC is only actuate to 1 second. Forget the RTC and use millis() instead.

1 Like

After receiving and testing a different RTC module, I think that was the problem on my end.
This was why I was not getting proper results back from the code.

After looking into DaveX advice and going down the FSM and millis() path of code this is what I came up with:

#include <Wire.h>
#include <RTClib.h>

RTC_DS3231 rtc;

//Inputs & Outputs
int Dirty=5;   //Pressure Transducer N/O
int Filter=9;  //Filter Motor Advance Output

//Time Vars
DateTime Now;                 //Current time read in
DateTime FilterAdv;           //Time to stop advancing filter (calculated)

//Filter advance time in seconds
int FilterAdd = 30;

//Vars
bool FilterOn = false;
bool FilterDirty = false;

//showDate necessary only for output to Serial for checking code
// use: ex. [showDate("dt", dt);] to see calculated time in unix
void showDate(const char* txt, const DateTime& dt) {
    Serial.print(txt);
    Serial.print(' ');
    Serial.print(dt.year(), DEC);
    Serial.print('/');
    Serial.print(dt.month(), DEC);
    Serial.print('/');
    Serial.print(dt.day(), DEC);
    Serial.print(' ');
    Serial.print(dt.hour(), DEC);
    Serial.print(':');
    Serial.print(dt.minute(), DEC);
    Serial.print(':');
    Serial.print(dt.second(), DEC);
    Serial.print(" = ");
    Serial.print(dt.unixtime());
    Serial.print("s / ");
    Serial.print(dt.unixtime() / 86400L);
    Serial.print("d since 1970");
    Serial.println();
}

void setup() 
{
  pinMode(Dirty, INPUT_PULLUP);  
  pinMode(Filter, OUTPUT);
    digitalWrite(Filter, LOW);

  Serial.begin(9600);

  //Initialize DS3231 clock
  rtc.begin();
}

void loop() 
{
  Now = rtc.now(); 
  if ((digitalRead(Dirty) == LOW) && (FilterDirty == false))
  {
    FilterDirty = true;    
    Serial.println("Advance Paper");
  }
  if (FilterDirty == true)
  {
    AdvancePaper();
  }     
}

void AdvancePaper()
{ 
  if (FilterOn == false)
  {
    FilterAdv = (Now.unixtime() + FilterAdd);
    showDate("Start", Now.unixtime());
    showDate("FilterAdv", FilterAdv.unixtime());
    digitalWrite(Filter, HIGH); 
    FilterOn = true;
  }
 
  if (Now.unixtime() >= FilterAdv.unixtime())
  {
    showDate("Stop", Now.unixtime());  
    digitalWrite(Filter, LOW);
    FilterOn = false;
    FilterDirty = false;
  }  
}

I agree by removing the do...while loop I will be able to monitor for any other problems and be able to act upon it without being stuck in the loop. I may use millis() in my final code but I haven't fully decided on that yet. I do see the benefit and it would be one less component to tuck into a control box.

If instead of comparing uint32_t values directly, you subtract the start time from both sides, you could then compare intervals and you won't have weird issues when it rolls over in Y2038:

#include <Wire.h>
#include <RTClib.h>

RTC_DS3231 rtc;

//Inputs & Outputs
int Dirty=5;   //Pressure Transducer N/O
int Filter=9;  //Filter Motor Advance Output

//Time Vars
DateTime Now;                 //Current time read in
DateTime FilterAdvStart;           //Time to Start advancing filter (calculated)

//Filter advance time in seconds
int FilterAdd = 30;

//Vars
bool FilterOn = false;
bool FilterDirty = false;

//showDate necessary only for output to Serial for checking code
// use: ex. [showDate("dt", dt);] to see calculated time in unix
void showDate(const char* txt, const DateTime& dt) {
    Serial.print(txt);
    Serial.print(' ');
    Serial.print(dt.year(), DEC);
    Serial.print('/');
    Serial.print(dt.month(), DEC);
    Serial.print('/');
    Serial.print(dt.day(), DEC);
    Serial.print(' ');
    Serial.print(dt.hour(), DEC);
    Serial.print(':');
    Serial.print(dt.minute(), DEC);
    Serial.print(':');
    Serial.print(dt.second(), DEC);
    Serial.print(" = ");
    Serial.print(dt.unixtime());
    Serial.print("s / ");
    Serial.print(dt.unixtime() / 86400L);
    Serial.print("d since 1970");
    Serial.println();
}

void setup() 
{
  pinMode(Dirty, INPUT_PULLUP);  
  pinMode(Filter, OUTPUT);
    digitalWrite(Filter, LOW);

  Serial.begin(9600);

  //Initialize DS3231 clock
  rtc.begin();
}

void loop() 
{
  Now = rtc.now(); 
  if ((digitalRead(Dirty) == LOW) && (FilterDirty == false))
  {
    FilterDirty = true;    
    Serial.println("Advance Paper");
  }
  if (FilterDirty == true)
  {
    AdvancePaper();
  }     
}

void AdvancePaper()
{ 
  if (FilterOn == false)
  {
    FilterAdvStart = Now.unixtime() ;
    showDate("Start", Now.unixtime());
    showDate("FilterAdvStart", FilterAdvStart.unixtime());
    showDate("FilterAdvStop", FilterAdvStart.unixtime()+FilterAdd);
    digitalWrite(Filter, HIGH); 
    FilterOn = true;
  }
 
  if (Now.unixtime() - FilterAdvStart.unixtime() >= FilterAdd)
  {
    showDate("Stop", Now.unixtime());  
    digitalWrite(Filter, LOW);
    FilterOn = false;
    FilterDirty = false;
  }  
}