Perform time-based task once only, at set times per hour, every hour

I read time from a DS3231, and want to perform a specific task just once, but at different times per hour.

For example: perform task A (contained in void actionTime() in code below) at 10 minutes past the hour, at 20 minutes past the hour and at 40 minutes past the hour, every hour, but each time this task may be performed just once.

What would be the most appropriate way to do this without using delay, without using alarm functions and using the setup code below:

(EDIT: removed erroneous delay function in code below; sorry!)

EDIT 2: task A is pretty large, it would be preferred to have all time conditions tested in one if

/*
// https://tronixlabs.com.au/news/tutorial-using-ds1307-and-ds3231-realtime-clock-modules-with-arduino/
// uses no library

#include "Wire.h"
#define DS3231_I2C_ADDRESS 0x68
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val / 10 * 16) + (val % 10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  // set the initial time here:
  // DS3231 seconds, minutes, hours, day, date, month, year
  // setDS3231time(30,17,15,7,18,11,17);
}

void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
                   dayOfMonth, byte month, byte year)
{
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}
void readDS3231time(byte *second,
                    byte *minute,
                    byte *hour,
                    byte *dayOfWeek,
                    byte *dayOfMonth,
                    byte *month,
                    byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}
void actionTime()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
                 &year);
  static byte trigger = 1;

/*
 * How to write code to perform a specific task just once, but each time at specific times in an hour, and every hour?
 * For example: execute task just once, at 10 minutes past the hour, at 20 minutes past the hour and at 40 minutes past the hour.
 * 
 */

}


void loop()
{
  actionTime(); // display the real-time clock data on the Serial Monitor,

}

You want to do something when the minute changes to whatever value you choose, so

static byte lastMinute=0;

  if( minute == lastMinute ){
    return;
  }
  lastMinute= minute;   // remember what minute was last time we looked

  // minute has changed... what is it now?
  if( minute == ??? ){
     do something
  }

Yours,
TonyWilk

Pseudo code:

if(taskAflag == true && minute == 10)
{
taskAflag = false; //disable, do only one taskA
. . .
}

if(taskAflag == false && minute == 11)
{
taskAflag = true; //enable taskA for next time
}

TonyWilk:
You want to do something when the minute changes to whatever value you choose, so

static byte lastMinute=0;

if( minute == lastMinute ){
    return;
  }
  lastMinute= minute;  // remember what minute was last time we looked

// minute has changed... what is it now?
  if( minute == ??? ){
    do something
  }




Yours,
TonyWilk

The issue here I think is that "do something" will be performed for as long as the "minute == ???" condition is valid, and not just once?

larryd:
Pseudo code:

if(taskAflag == true && minute == 10)
{
taskAflag = false; //disable, do only one taskA
. . .
}

if(taskAflag == false && minute == 11)
{
taskAflag = true; //enable taskA for next time
}

So by the same token, if I have task A to be performed once at 3 instances per hour, is the following correct?

if((taskAflag == true && minute == 10) || (taskAflag == true && minute == 20) || (taskAflag == true && minute == 40))
{
taskAflag = false; //disable, do only one taskA
. . .
}

if((taskAflag == false && minute == (10+1)) || (taskAflag == false && minute == (20+1)) || (taskAflag == false && minute == (40+1)))
{
taskAflag = true; //enable taskA for next time
}

brice3010:
The issue here I think is that "do something" will be performed for as long as the "minute == ???" condition is valid, and not just once?

No, when minute changes, say from 9 to 10, then "minute == lastMinute" is not true, so we go on to set lastMinute= minute; (which is now 10) and then do e.g. if( minute == 10 ){ do something }

Next time thru, "minute == lastMinute" is true, we're still in minute 10, so the function return;'s

Yours,
TonyWilk

TonyWilk:
No, when minute changes, say from 9 to 10, then "minute == lastMinute" is not true, so we go on to set lastMinute= minute; (which is now 10) and then do e.g. if( minute == 10 ){ do something }

Next time thru, "minute == lastMinute" is true, we're still in minute 10, so the function return;'s

Yours,
TonyWilk

Hi Tony, so, if on for example at three time-instances (say at 10, at 20 and at 40 values for minute) the task is to be performed, the code should be like following? (I am not sure)

static byte lastMinute=0;

  if( minute == lastMinute ){
    return;
  }
  lastMinute= minute;   // remember what minute was last time we looked

  // minute has changed... what is it now?
  if( minute == 10 || minute == 20 || minute == 40 ){
     do something
  }

brice3010:
Hi Tony, so, if on for example at three time-instances (say at 10, at 20 and at 40 values for minute) the task is to be performed, the code should be like following? (I am not sure)

Yes, spot on !

Yours,
TonyWilk

TonyWilk:
No, when minute changes, say from 9 to 10, then "minute == lastMinute" is not true, so we go on to set lastMinute= minute; (which is now 10) and then do e.g. if( minute == 10 ){ do something }

Next time thru, "minute == lastMinute" is true, we're still in minute 10, so the function return;'s

Yours,
TonyWilk

Hi Tony, just to understand correctly: the "return" in the first function inhibits execution of code after that statement? Meaning, if minute == lastMinute then the code behind that "if" is not executed?

What an elegant piece of code!!!
Thanks,
Erik

When the return statement is encountered the code stops at that point and the program immediately returns back to where it was called. If this happens in the loop() function then it effectively causes the loop() function to restart. What actually happens is that the program returns to the hidden main() function that called the loop() function in the first place and loop() is called again immediately.

without using alarm functions

Why not use the alarms?

Pete

el_supremo:
Why not use the alarms?

Pete

Have you seen how simple it is now?

When you originally posted your question you didn't know how to do it nor whether it would be easy to do, yet you excluded use of alarms.

Pete

el_supremo:
When you originally posted your question you didn't know how to do it nor whether it would be easy to do, yet you excluded use of alarms.

Pete

Because I knew it was going to be simple :slight_smile:

What would be the advantage of using alarms instead?

It must be nice to be psychic.

I didn't say alarms would necessarily be easier but all it would require is an alarm interrupt every minute. The code in loop() would look almost the same except that the alarm must be reset and there's no need to test if minute == lastMinute.

Pete

el_supremo:
It must be nice to be psychic.

It helps.. and a good glass of wine is a start :grinning:

el_supremo:
I didn't say alarms would necessarily be easier but all it would require is an alarm interrupt every minute. The code in loop() would look almost the same except that the alarm must be reset and there's no need to test if minute == lastMinute.

Pete

And you would need a library?

You've already got functions to read and set the time in the RTC. A little bit of browsing through the DS3231 datasheet would show you how to set and reset the alarms. Or you could google for code to do it.
Or use a library. No big deal.

Pete

brice3010:
So by the same token, if I have task A to be performed once at 3 instances per hour, is the following correct?

if((taskAflag == true && minute == 10) || (taskAflag == true && minute == 20) || (taskAflag == true && minute == 40))

{
taskAflag = false; //disable, do only one taskA
. . .
}

if((taskAflag == false && minute == (10+1)) || (taskAflag == false && minute == (20+1)) || (taskAflag == false && minute == (40+1)))
{
taskAflag = true; //enable taskA for next time
}

The good thing about the Arduino platform is you can test things out :wink:

Sketch to check out algorithm.

bool taskAflag = true;

void setup()
{
  Serial.begin(9600);
  pinMode(13, OUTPUT);
}
void loop()
{
  for (int x = 0; x < 6000; x++)
  {
    if (taskAflag == true && (x == 1000 ||  x == 2000  ||  x == 4000 ))
    {
      taskAflag = false; //disable, do only one taskA
      //call the task 
      task(x);
      //. . .
    }

    if (taskAflag == false && (x == 1001 ||  x == 2001 ||  x == 4001 ))
    {
      taskAflag = true; //enable taskA for next time
    }
  }
}// END of loop()

void task(int x)
{
  //print test time 
  Serial.println(x);
  //toggle LED
  digitalWrite(13, !digitalRead(13));
  delay(200);
  digitalWrite(13, !digitalRead(13));
}

Edit, updated code.

larryd:
The good thing about the Arduino platform is you can test things out :wink:

Sketch to check out algorithm.

bool taskAflag = true;

void setup()
{
  Serial.begin(9600);
  pinMode(13, OUTPUT);
}
void loop()
{
  for (int x = 0; x < 6000; x++)
  {
    if (taskAflag == true && (x == 1000 ||  x == 2000  ||  x == 4000 ))
    {
      taskAflag = false; //disable, do only one taskA
      //call the task
      task(x);
      //. . .
    }

if (taskAflag == false && (x == 1001 ||  x == 2001 ||  x == 4001 ))
    {
      taskAflag = true; //enable taskA for next time
    }
  }
}// END of loop()

void task(int x)
{
  //print test time
  Serial.println(x);
  //toggle LED
  digitalWrite(13, !digitalRead(13));
  delay(200);
  digitalWrite(13, !digitalRead(13));
}




Edit, updated code.

Hi larryd, thanks for your input; from what I read you do not use the DS3231? Or am I confused?

The example I gave is a quick and dirty way for checking the algorithm.

Yes I do, exclusively, use the DS3231 RTCs.

They are much much better than the DS1307.

You can change the 'x' in the sample sketch to 'minute' or whatever.

If you test the code out, you should find it does what you what you asked.