How to simultaneously execute two tasks?

Hello, I'm having problems with executing two tasks at the same time, using cyclic executive. So I have two simple tasks:

  • task 1: turn on red LED for 1 second, execute every 4 seconds
  • task 2: turn on green LED for 1 second, execute every 10 seconds

I've drawn a timeline of the expected output (see below):

As you can see, at second 20, tasks 1 and 2 should execute at the same time, i.e. both LEDs should turn on together. But in reality, with my code, only the red LED turns on at second 20. Apart from that, my code produces correct results (see code below).

/*
  task 1: turn on red LED for 1 second, execute every 4 seconds
  task 2: turn on green LED for 1 second, execute every 10 seconds
*/
const long eventTime_Red = 4000;    //execute every 4 seconds
const long eventTime_Green = 10000; //execute every 10 seconds

const long red_period = 1000; //keep red LED on for 1 second
const long green_period = 1000; //keep green LED on for 1 second

unsigned long previousTime_Red = 0;
unsigned long previousTime_Green = 0;

const byte LED_Red = 15;  //LED red connected to pin 15
const byte LED_Green = 21; //LED green connected to pin 21

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

  pinMode(LED_Red, OUTPUT);  //configure pin 15 to behave as an output
  pinMode(LED_Green, OUTPUT); //configure pin 21 to behave as an output

  digitalWrite(LED_Red, LOW);  //have the red LED initially off
  digitalWrite(LED_Green, LOW); //have the green LED initially off
}

void loop() {
  unsigned long presentTime = millis();

  //for task 1
  if (presentTime - previousTime_Red >= eventTime_Red) {

    while (millis()  <= (presentTime + red_period)) {
      //keep Red on for 1 sec
      digitalWrite(LED_Red, HIGH);
      Serial.print(presentTime / 1000);
      Serial.println("Red ON");
    }
    digitalWrite(LED_Red, LOW);
    previousTime_Red = presentTime;
  }

  //for task 2
  if (presentTime - previousTime_Green >= eventTime_Green) {
    while (millis()  <= (presentTime + green_period)) {
      //keep Red on for 1 sec
      digitalWrite(LED_Green, HIGH);
      Serial.print(presentTime / 1000);
      Serial.println("Green ON");
    }
    previousTime_Green = presentTime;
    digitalWrite(LED_Green, LOW);
  }
}

I tried using the millis() function instead of delay() to avoid other instructions from being blocked.
I'm using ESP32 and see below for the circuit.

How can I fix my code so that the two LEDs can turn on at the same time when they need to?

Any help would be highly appreciated. Thank you :slight_smile:

    while (millis()  <= (presentTime + red_period))
    {
      //keep Red on for 1 sec
      digitalWrite(LED_Red, HIGH);
      Serial.print(presentTime / 1000);
      Serial.println("Red ON");
    }

This while loop blocks execution of any other code until the period elapses

For the proper way to use millis() see Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

1 Like

Did you see and understand the BlinkWithoutDelay IDE example?

consider

struct Event {
    byte            pin;
    unsigned long   period;
    unsigned long   duration;
    unsigned long   msecLst;
};

Event events [] = {
    { 13, 1500, 250 },
    { 12, 2000, 250 },
};
#define N_EVENT (sizeof(events)/sizeof(Event))

enum { Off = HIGH, On = LOW };

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

    Event *e = & events [0];
    for (unsigned n = 0; n < N_EVENT; n++, e++)  {
        if (Off == digitalRead (e->pin))  {
            if ((msec - e->msecLst) >= e->period)
                digitalWrite (e->pin, On);
        }
        else
            if ((msec - e->msecLst) >= (e->period + e->duration))  {
                e->msecLst = msec;
                digitalWrite (e->pin, Off);
            }
    }
}

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

    for (unsigned n = 0; n < N_EVENT; n++)  {
        pinMode      (events [n].pin, OUTPUT);
        digitalWrite (events [n].pin, On);
    }
}

Thanks for the replies. But from what I can understand, Using millis() for timing. A beginners guide - Using Arduino / Introductory Tutorials - Arduino Forum and the BlinkWithoutDelay IDE example only take care of scheduling the tasks, which is something I've done using the instruction
if (presentTime - previousTime_Red >= eventTime_Red)
my problem is not how to schedule tasks without using delay, it's how to execute two tasks at once.

UKHeliBob, I tried using 'if' instead of 'while' and it appears to instruct both LEDs to turn on (from what I see on the serial monitor) however, in reality, I can't see either of the LEDs turn on, ever.

presumably you know that a processor can only execute one instruction at a time and that multi-tasking is relative

what do you mean by "at once" -- within 1sec, 1 msec, 1usec, 1nsec ??

As I pointed out earlier, this code

while (millis()  <= (presentTime + red_period))
    {
      //keep Red on for 1 sec
      digitalWrite(LED_Red, HIGH);
      Serial.print(presentTime / 1000);
      Serial.println("Red ON");
    }

Change the while to an if so that loop() can run freely so that you can be timing 2 periods in every interation of loop()

I think you made a mistake at the end of your diagram. Red led comes on at 31 instead of 32.

The esp32 has 2 cores so could do 2 tasks at the same time. I am sure @Idahowalker could come up with a solution without millis().

Actually, that is your problem. You are using while-loops to make the code stay in the while-loop until millis() hits a particular value. That's what the delay() function does. So your code behaves like you are using delay().

2 Likes

isn't it 4 events? turning on and turning off two LEDs?

Take a look at Arduino Protothreads. It's a lightweight solution. It does have some limitations but overall it's pretty usable for basic multitasking. The example I linked can be used directly for blinking two LEDs.

The way I thought of it was that keeping the LED on for one-second counts as one event. Its default state is to be off.

Yes, I realised but I tried using 'if' instead of 'while' and now I can't see either of the LEDs turn on, at all.

what if the other LED needs to be turned on (and possibly off), while one LED is on?

a task generally uses processors cycles to accomplish something -- not wait? that's a feature of multi-tasking and use of interrupts, while one task has nothing to do, such as waiting for input, another task can run

Your diagram does not match your description

Should the wait before the red LED first comes on be 3 seconds or 4 seconds ?

Not tested

#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void setup() 
{

  gpio_config_t io_cfg = {}; // initialize the gpio configuration structure
  io_cfg.mode = GPIO_MODE_OUTPUT; // set gpio mode
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_4) | (1ULL << GPIO_NUM_5) ); //bit mask of the pins to set
  gpio_config(&io_cfg); // configure the gpio based upon the parameters as set in the configuration structure

xTaskCreatePinnedToCore( Task1, "Task1", 7000,  NULL, 3, NULL, 0 );
  xTaskCreatePinnedToCore( Task2, "Task2", 7000, NULL, 3, NULL, 1 );

}

void Task1 ( void *pvParameters )
{
//task 1: turn on red LED for 1 second, execute every 4 seconds

for {,,}
{
  gpio_set_level( GPIO_NUM_4, HIGH );
  vTaskDelay( 1000 );
  gpio_set_level( GPIO_NUM_4, LOW );
  vTaskDelay( 4000 );
}
vTaskDelete( NULL );
}
//
void Task2 ( void *pvParameters )
{
//task 2: turn on green LED for 1 second, execute every 10 seconds

for {,,}
{
    gpio_set_level( GPIO_NUM_5, HIGH );
  vTaskDelay( 1000 );
  gpio_set_level( GPIO_NUM_5, LOW );
  vTaskDelay( 10000 );
}
vTaskDelete( NULL );
}
////

void loop() {}

notice each task is on a different core.

The most simple basic use of millis() is in my opinion a millis-timer of 1 second and two counters.

I have not thought it through, but it could look like this:

const unsigned long interval = 1000UL;

void loop()
{
  if( currentMillis - previousMillis >= interval)
  {
    previousMillis += interval;      // this is to keep the millis-timer in sync with the time

    if( countToFour == 3)            // the drawing is not consistant, start at 4 means also start at 0 ?
      digitalWrite( redLedPin, HIGH);
    
    countToFour++;
    if( countToFour >= 4)
      countToFour = 0;

    countToTen++;
  }
}

Huh. Didn't realize ESP32 supported FreeRTOS. Good to know!

API Reference - ESP32 - — ESP-IDF Programming Guide latest documentation (espressif.com)