Arduino Multitasaking


Hi, I'm in need to control four relays with different on and off timings. I'm new to arduino and I'm confused on how to achieve this function. Need help to do this. Thanks in advance.

The simple answer is look at the "blink without delay" example in the "examples" tab in the IDE.
For "LED", read "relay"

1 Like

You can get some ideas and understanding from e.g. the following links

  1. Using millis() for timing. A beginners guide
  2. Flashing multiple LEDs at the same time
  3. https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time
  4. Overview | Multi-tasking the Arduino - Part 1 | Adafruit Learning System
1 Like

this isn't multi-tasking it's simply multiple timers

look this over

struct Led {
    const byte PinLed;

    const unsigned long MsecStart;
    const unsigned long MsecOn;
    const unsigned long MsecOff;

    unsigned long msecLst;
    unsigned long msecPeriod;
};

Led leds [] = {
    { 10,  300, 1000, 1000 },
    { 11,  500,  300, 2000 },
    { 12, 2000, 1000,  100 },
};

const int Nled = sizeof(leds) / sizeof(Led);

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    Led *p = leds;
    for (int n = 0; n < Nled; n++, p++)  {
        // check for led timeout
        if (msec - p->msecLst >= p->msecPeriod)  {
            p->msecLst = msec;

            if (On == digitalRead (p->PinLed))  {
                digitalWrite (p->PinLed, Off);
                p->msecPeriod = p->MsecOff;
            }
            else  {
                digitalWrite (p->PinLed, On);
                p->msecPeriod = p->MsecOn;
            }
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    Led *p = leds;
    for (int n = 0; n < Nled; n++, p++)  {
        pinMode      (p->PinLed, OUTPUT);
        digitalWrite (p->PinLed, Off);
        p->msecPeriod = p->MsecStart;
    }
}
1 Like

the timers in the code i posted are periodic.

it's certainly possible to make them one shots using other code to configure them to start at specific time

Does your sketch create the output depicted in the screen shot, or is it just a demonstration of multiple timers?

Knowing would help anyone trying to figure out your code. It's easier to udnerstand something if you are fully aware of what it actually does. Figuring out both what and how at once is, well, harder.

Forget whether that pattern is meant to repeat or be execute just once.

a7

it just demonstrate multiple timers with arbitrary on/off periods and start time

i believe table values in this code match the diagram assuming its periodic every 600 units using a scaling factor, K, of 100 resulting in a 60 sec cycle

struct Led {
    const byte PinLed;

    const unsigned long MsecStart;
    const unsigned long MsecOn;
    const unsigned long MsecOff;

    const char *desc;

    unsigned long msecLst;
    unsigned long msecPeriod;
};

Led leds [] = {
    { 10,    0,  300,  300, "MV1" },
    { 11,   10,  240,  360, "EV2" },
    { 12,  246,    4,  296, "DRAIN" },
    { 13,  310,  240,  360, "EV1" },
};

const int Nled = sizeof(leds) / sizeof(Led);

const int K = 100;                  // scale timing for testing

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();

    Led *p = leds;
    for (int n = 0; n < Nled; n++, p++)  {
        // check for led timeout
        if (msec - p->msecLst >= p->msecPeriod)  {
            p->msecLst = msec;

            if (On == digitalRead (p->PinLed))  {
                digitalWrite (p->PinLed, Off);
                p->msecPeriod = p->MsecOff * K;
            }
            else  {
                digitalWrite (p->PinLed, On);
                p->msecPeriod = p->MsecOn * K;
            }
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);

    Led *p = leds;
    for (int n = 0; n < Nled; n++, p++)  {
        pinMode      (p->PinLed, OUTPUT);
        digitalWrite (p->PinLed, Off);
        p->msecPeriod = p->MsecStart * K;
    }
}

Yes. And your second data set looks plausible. THC.

@vignesh_skp welcome to the forum and could you say whether the pattern depicted in your screen shot is to repeat endlessly?

Or should it be issued once as informed by some other event?

Or should it continue while some condition is true and then stop, in which case should the logic finish any single sequence that is in progress, or halt immediately, in which further case would it be appropriate to take all the signals low?

I know, I know, a thousand questions. Without planning, the code will provide answers to unasked questions; they may not be the answers you want.

a7

Wow gcjr,

you used multiple advanced coding techniques and managed to use non self-explaining variables names and zero explaining comments. Do you really think this is helpful?

Was your intention The TO seems to be asking very short and very demanding present him a code that demands for a lot of knowledge and a lot of time of learning to understand the code?

Can you point to what you consider "advanced techniques"?

What is "The TO"?

Some examples and code have been presented. Do you have any code that you've attempted to write that does or does not work? Starting simply just turning a relay off and then on every 10 seconds would be a good start in my opinion.

This requires no multitasking! Not as you have described it. A single task can perform this sequence. All the timing appears to be fixed.

The solutions presented by the other forum members are excellent, but much more complex than you need.

Unless there is more detail you have not yet described...

    Led *p = leds;
    for (int n = 0; n < Nled; n++, p++)  {

I eat stuff like that for breakfast, but the above def qualifies. There are more noob-friendly ways to express that, and even friendlier ways to accomplish the same thing.

   p->msecLst = msec;

Please.

a7

Your time base is: 550 sec.
Relay DRAIN: OFF from 0 - 246 sec; ON from 246-250 sec; OFF from 250-546 sec; ON from 546-550 sec.

Relay MVI: ON from 0 - 300 sec; OFF from 300 - 550 sec.

Relay EV1: OFF from 0 - 310 sec; ON from 310-550 sec.

Relay EV2: OFF from 0 - 10 sec; ON from 10 - 250 sec; OFF from 250-550 sec.

You may explore the hardware timers of ATmega328P of the Arduino UNO Board to generate these timing functions.

re-edited.

The name of function loop() is "program". Function loop() is looping.
Your timing-diagram shows periods of time within a 600 seconds long main-period.
This means the switch-on-off pattern repeats after 600 seconds.

The basic concept is to use a variable that counts up one by one each second.
0,1,2,3,4,.......600, 0,1,2,3,4,.......600, 0,1,2,3,4,.......600,

This variable is named "secondsCounter"

And then compare the value of variable "secondsCounter" with that number of seconds where a relay shall be switched on or off

This is done by an if-condition

  if (secondsCounter == DrainOn1_Cnt) {
    digitalWrite(Drain_Pin, On); // switch relay drain on
  }

The code below shows how to switch on / off the drain-relay
As exercise for you expand this pattern to the other relays and their switch on / switch-off-times

int DrainOn2_Cnt  = 546;
int DrainOff2_Cnt = 550;

int MVOn_Cnt  = 0;
int MVOff_Cnt = 300;

int EV1On_Cnt  = 310;
int EV1Off_Cnt = 550;

int EV2On_Cnt  = 10;
int EV2Off_Cnt = 250;

This is the complete sketch

const byte Drain_Pin = 4; // IO-Pin-number Relays "Drain" is connected to
const byte MV1_Pin   = 5; // IO-Pin-number Realys "MV1 is connected to
const byte EV1_Pin   = 6; // IO-Pin-number Realys "EV1 is connected to
const byte EV2_Pin   = 7; // IO-Pin-number Realys "EV2 is connected to

unsigned long myTimer; // variable used for non-blocking timing

unsigned long SecondsOfPeriod = 600; 

unsigned long secondsCounter = 0;


// variables that contain the number of seconds 
// when to switch on/off the relays
int DrainOn1_Cnt  = 246;
int DrainOff1_Cnt = 250;

int DrainOn2_Cnt  = 546;
int DrainOff2_Cnt = 550;

int MVOn_Cnt  = 0;
int MVOff_Cnt = 300;

int EV1On_Cnt  = 310;
int EV1Off_Cnt = 550;

int EV2On_Cnt  = 10;
int EV2Off_Cnt = 250;

const byte OFF = LOW;
const byte On  = HIGH;

void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  digitalWrite(Drain_Pin, OFF); 
  digitalWrite(MV1_Pin,   OFF);
  digitalWrite(EV1_Pin,   OFF);
  digitalWrite(EV2_Pin,   OFF);

  // configure IO-pin as OUTPUT
  pinMode(Drain_Pin, OUTPUT); 
  pinMode(MV1_Pin, OUTPUT);
  pinMode(EV1_Pin, OUTPUT);
  pinMode(EV2_Pin, OUTPUT);
}

void loop() {

  // check if 1000 milliseconds of time have passed by
  if ( TimePeriodIsOver(myTimer,1000) ) {
    // when REALLY 1000 milliseconds HAVE passed by
    secondsCounter++; // increment counter by 1
  }


  // if value of variable "secondsCounter" 
  // matches the value of variable "DrainOn1_Cnt"
  if (secondsCounter == DrainOn1_Cnt) {
    digitalWrite(Drain_Pin, On); // switch relay drain on
  }

  if (secondsCounter == DrainOff1_Cnt) {
    digitalWrite(Drain_Pin, OFF); // switch relay drain off
  }


  // check if period is over 
  if (secondsCounter == SecondsOfPeriod) {
    // when period IS over 
    secondsCounter = 0; // set counter back to zero
  }
  
}

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

best regards Stefan

You've removed the "ignore this for now" comment from your brilliant function, but that does not make it any better. Functions, arguments, call by reference no less. Bzzzt!

Why not use your talents at teaching to slip in the idiomatic you are not going to survive without knowing the pattern a standard idiom lifted from blink without delay that just uses current/last/millis()?

Pot kettle black.

a7

The Topic Owner, AKA the OP (Original Poster).

In a real situation, I think it is different.
For example: check the pressure when a valve is open, check a limit switch when a relay is on, check the rpm of a motor when it is turned on.
That requires a "Finite State Machine", which is a fancy term for a logical way to make this.

Another way to solve this is a time table in which every command can be be dropped:

enum command
{
  IDLE,
  START,
  PRINT,
  DRAIN_ON,
  DRAIN_OFF,
  MV1_ON,
  MV1_OFF,
  EV1_ON,
  EV1_OFF,
  EV2_ON,
  EV2_OFF,
  BUZZER_ON,
  BUZZER_OFF,
};

struct action
{
  unsigned long t;
  command c;
};

// The 'index' is only increasing, keep the times in order
action action_table[] = 
{
  {   0, PRINT},
  {   0, MV1_ON},
  {   0, BUZZER_ON},
  { 500, BUZZER_OFF},
  {2000, EV2_ON},
  {5000, EV2_OFF},
  {6000, MV1_OFF},
  {7000, START}
};

int index;
unsigned long previousMillis;

void setup() 
{
  Serial.begin(115200);

  // start the action table
  index = 0;
  previousMillis = millis();
}

void loop() 
{
  unsigned long currentMillis = millis();
  int n = sizeof(action_table) / sizeof(action);
  
  if( currentMillis - previousMillis >= action_table[index].t)
  {
    // A valid action has been found
    switch(action_table[index].c)
    {
      case IDLE:
        Serial.println("Idle");
        break;
      case START:
        Serial.println("Start");
        index = 0;
        previousMillis = millis();
        break;
      case PRINT:
        Serial.println("Print");
        break;
      case DRAIN_ON:
        Serial.println("Drain ON");
        break;
      case DRAIN_OFF:
        Serial.println("Drain OFF");
        break;
      case MV1_ON:
        Serial.println("MV1 ON");
        break;
      case MV1_OFF:
        Serial.println("MV1 OFF");
        break;
      case EV1_ON:
        Serial.println("EV1 ON");
        break;
      case EV1_OFF:
        Serial.println("EV1 OFF");
        break;
      case EV2_ON:
        Serial.println("EV2 ON");
        break;
      case EV2_OFF:
        Serial.println("EV2 OFF");
        break;
      case BUZZER_ON:
        Serial.println("Buzzer ON");
        break;
      case BUZZER_OFF:
        Serial.println("Buzzer OFF");
        break;
      default:
        Serial.println("Error, unknown command");
        break;
    }
    index++;

    if( index >= n)
    {
      Serial.println("Error, end of table reached");
    }
  }
}

I hope you all forgive me to not have a Wokwi project for it.

1 Like

You spoke too soon.

The graph on top shows signals up to 600 seconds. So I assume that it repeats after 600 seconds. Then 'previousMillis' is set to millis() at 600 seconds, and everything in between can use the relative time with "currentMillis-previousMillis". If that is 246 then turn on the DRAIN and so on.

Try my code, test it.
I did not test my code (I assume it works).