Simultaneous loops

Hello, I would like to have two "void loop()"-functions that would run independantly but simultaneously.
Like that in one loop the could would read a buttons state every twenty milliseconds. then on the second loop it would control ports 2-5 like this:

void loop1(){
 button = digitalRead(9); //read push-button
  if (buttonmode == HIGH && beforebutton == LOW) {
    mode++; 
    if (mode > 2) { 
      mode = 1;
    }
  }
  beforebutton = buttonmode;
 delay(20);
}
void loop2() {
//----potentiometers 1 and two----
 int pot1 = analogRead(A0); 
 int pot2 = analogRead(A1); 
//----potentiometers 1 and two----
 if (mode==1) {
 digitalWrite(2, HIGH);
 digitalWrite(3, HIGH);
 digitalWrite(4, HIGH);
 digitalWrite(5, HIGH);
 delay(pot1); 
 digitalWrite(2, LOW);
 digitalWrite(3, LOW);
 digitalWrite(4, LOW);
 digitalWrite(5, LOW);
 delay(pot2);
}
 else if (mode==2){

 digitalWrite(2,HIGH); 
 delay(pot1+pot2); 
 digitalWrite(2,LOW); 
 digitalWrite(3,HIGH);
 delay(pot1+pot2);
 digitalWrite(3,LOW);
 digitalWrite(4,HIGH);
 delay(pot1+pot2);
 digitalWrite(4,LOW);
 digitalWrite(5,HIGH);
 delay(pot1+pot2);
 digitalWrite(5,LOW);
 }

}

So at the start mode 1 starts running and it puts ports 2-5 on HIGH, waits for potentiometer one ms (0-1023ms), puts ports 2-5 on LOW and waits for potentiometer two (0-1023)
when you press the button it would then switch onto mode 2 what firsts but pin 2 on HIGH, waits for potentiometer one+two ms (0-20246ms), puts pin two on LOW, waits for 0-2s, puts pin 3 on HIGH, waits for two seconds, puts pin 3 on LOW etc.
and it would be nice that the code reads on what state the button is at pretty fast rate like 20ms. but if you let it run simultaneously pot1 and pot2 could extend it to like 4 second or so

Is there any easy way to do it?

Which board are we talking about?
You're asking for something that would require some sort of "multithreading," so it depends on your requirements, as basic Arduino boards don't support any multithreading (and only have a single CPU). I'm a bit confused about what you're trying to achieve, sorry, but very often things that seem to require true multitasking can also be achieved with Arduino UNO through appropriate structures like creating a finite state machine (FSM) and, most importantly, avoiding the use of "delay()" (as I see in your example code).

Try to describe the flow more schematically, using a FSM if possible, and the code will probably be almost directly implementable using that scheme.

Look for a topic like “Blink without delay” and “Do several things at the same time “. They show how to what You ask for.

Which arduino are you using? This is much easier on a newer 32 bit processor like the Giga or ESP32

Really for human pressed buttons you don't need two loops or to manage the button through a timer or whatever. The secret is to not use delay in the loop so that you can come back and check the button often.

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

and for examples dealing with non blocking code, look at


if you really want to deal with the button as a "background task" and you are on an UNO, Classic Nano or Mega, you could look at @vileroi's library MTObjects

it smartly uses a timer to handle a regular background checks of the buttons (the library does more than this, you can use it to drive servomotors or steppers) and when the button is pressed or released a callback function is triggered - this way your loop can just loop happily.

The code would look like this for your example

(untested as wokwi is 100% busy)

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects

const byte buttonPin = 9;
const byte ledPins[] = {2, 3, 4, 5};
const byte ledCnt = sizeof ledPins / sizeof * ledPins;
const byte pot1Pin = A0;
const byte pot2Pin = A1;

volatile bool blinkMode = true; // modified through button ISR

void onPress() {blinkMode = !blinkMode;}
MTbutton Bouton(buttonPin, onPress, nullptr); // no need for an onRelease callback

void setup() {
  for (byte aPin : ledPins)pinMode(aPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  int pot1 = analogRead(pot1Pin);
  int pot2 = analogRead(pot2Pin);
  if (blinkMode) { // atomic read for a byte size flag
    for (byte aPin : ledPins) digitalWrite(aPin, HIGH);
    delay(pot1);
    for (byte aPin : ledPins) digitalWrite(aPin, LOW);
    delay(pot2);
  } else {
    for (byte i = 0; i < ledCnt; i++) {
      digitalWrite(ledPins[i], HIGH);
      delay(pot1 + pot2);
      digitalWrite(ledPins[i], LOW);
    }
  }
}


and as it's raining outside, here is a state machine based version (fully untested)

// ------------- A very simplified button class -----------
class Button {
public:
  Button(uint8_t pin)
    : pin(pin) {}

  void begin() {
    pinMode(pin, INPUT_PULLUP);
  }

  operator bool() {
    bool current = digitalRead(pin) == LOW;
    if (current != prevState && (millis() - prevMillis >= 20)) {
      prevState = current;
      prevMillis = millis();
      return current;
    }
    return false;
  }

private:
  uint8_t pin;
  bool prevState = true;
  uint32_t prevMillis = 0;
};
// --------------------------------------------------

const byte buttonPin = 9;
const byte ledPins[] = { 2, 3, 4, 5 };
const byte ledCnt = sizeof ledPins / sizeof *ledPins;
const byte pot1Pin = A0;
const byte pot2Pin = A1;

Button button(buttonPin);

// ---- State machine ----------------------------------
enum Mode : uint8_t { BLINK, CHASE};
enum Phase : uint8_t { LED_ON, LED_OFF};

Mode mode = BLINK;
Phase phase = LED_ON;

uint32_t stateStart = 0;  // when the current phase began
byte chaseIdx = 0;        // which LED is lit in CHASE mode

void allLeds(bool isLedON) {
  for (byte pin : ledPins) digitalWrite(pin, isLedON ? HIGH : LOW);
}

void setup() {
  for (byte pin : ledPins) pinMode(pin, OUTPUT);
  button.begin();
  stateStart = millis();
  allLeds(true);
}

void loop() {
  // --- Handle button press (toggle mode, reset state machine)
  if (button) {
    mode = (mode == BLINK) ? CHASE : BLINK;
    phase = LED_ON;
    chaseIdx = 0;
    stateStart = millis();
    allLeds(false);
    if (mode == BLINK) {
      allLeds(true);
    } else {
      allLeds(false);
      digitalWrite(ledPins[chaseIdx], HIGH);
    }
  }

  // --- Read pots every loop (non-blocking)
  uint32_t onTime = analogRead(pot1Pin);
  uint32_t offTime = analogRead(pot2Pin);

  uint32_t now = millis();
  uint32_t elapsed = now - stateStart;

  if (mode == BLINK) {
    // Two-phase: ON → OFF → ON → …
    if (phase == LED_ON && elapsed >= onTime) {
      allLeds(false);
      phase = LED_OFF;
      stateStart = now;
    } else if (phase == LED_OFF && elapsed >= offTime) {
      allLeds(true);
      phase = LED_ON;
      stateStart = now;
    }

  } else {
    // CHASE: one LED on for (pot1+pot2) ms, then advance
    uint32_t stepTime = onTime + offTime;
    if (elapsed >= stepTime) {
      digitalWrite(ledPins[chaseIdx], LOW);
      chaseIdx = (chaseIdx + 1) % ledCnt;
      digitalWrite(ledPins[chaseIdx], HIGH);
      stateStart = now;
    }
  }
}

You can see how easy it is by studying @J-M-L's sketch.

Very easy.

Or you could restructure your code just a wee bit, and get what you wrote to work as well as what you wrote can.

I'll just sketch the sketch, so to speak, it's called pseudocode which just means I'm leaving the deets for you

loop
   the button controls the mode variable

   if the mode is one, call function1

   if the mode is two, call function2

function1
   read any pots you need
   all that loop2 stuff from the first if block

function2
  all that loop2 stuff from the second if block

It's not clear that you even know you can writer your own functions. If not, start here

https://docs.arduino.cc/learn/programming/functions/




But.

What you may have already noticed and may not like is that while the attention of the processor is in either function, nothing is looking at the button.

I'd use a toggle switch, makes waiting easier. Or keep your finger on the button…

There are a few ways to get around the blocking (inattention) if you want a more responsive change by button.

Here's a function you can use like delay

bool myDelay(unsigned long delayTime)
{
  unsigned long elapsed = 0;

  while (elapsed < delayTime)
  {
    // Check button
    if (digitalRead(digiPin) == LOW)
    {
      return true;  // Button pressed, we outta here
    }

    delay(50);      // Wait 50 milliseconds
    elapsed += 50;  // Keep track of time passed
  }

  return false;     // Time ran out, button not pressed
}

and instead of calling delay(pot1), for example, do this

  if (myDelay(pot1)) return;

so you are noticing the early exit from myDelay() because the button is pressed, and return from, say, function1() early, back to the one real,loop().

It's hack, and it may horrify ppl who really want you to go full textbook on this, but it's worth working out how it works.

BTW I too want you to eventually spin code like the heavies, but am not horrified. :expressionless:

a7

And you need to take the early exit into account and not go straight into the next delay

Are you suggesting a goto for that :slight_smile:

Early exit from any instance of this method of delaying.

So TBC, every

  delay(whatEver);

is changed to

  if (myDelay(whatEver)) return;   // 

Effecting early exits from the blocking functions.

a7

1. You can use Arduio_FreeRTOS.h Libray to engage UNO R3 to execute your tasks concurretly.

2. Example of concurrent blinkings of two leds at different rates:


Figure-1:

Sketch:

#include <Arduino_FreeRTOS.h>

TaskHandle_t myTaskA;
TaskHandle_t myTaskB;
#define 	LED1	5
#define	LED2	6

void setup()
{
  pinMode(LED1, OUTPUT); 		//LED1
  pinMode(LED2, OUTPUT);  		//LED3

  xTaskCreate(myTaskFunctionA, "MyTaskA", 128, NULL, 1, &myTaskA);
  xTaskCreate(myTaskFunctionB, "MyTaskB", 128, NULL, 1, &myTaskB);
}

void loop()   
{
  //leave it empty for Arduino UNO
}

void myTaskFunctionA(void *pvParameters)//LED1
{
  for (;;)
  {
    digitalWrite(LED1, HIGH);
    vTaskDelay(pdMS_TO_TICKS(500)); // Delay for 1/2 second
    digitalWrite(LED1, LOW);
    vTaskDelay(pdMS_TO_TICKS(500)); 
  }
}

void myTaskFunctionB(void *pvParameters)//LED2
{
  for (;;)
  {
    digitalWrite(LED2, HIGH);
    vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for 1 second
    digitalWrite(LED2, LOW);
    vTaskDelay(pdMS_TO_TICKS(1000)); 
  }
}

3. More examples:

IDE ----> File ----> Examples ----> FreeRTOS

MTobjects is written for simple and common tasks

With MTobjects, you can write:

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects

const uint8_t LED1 = 5;
const uint8_t LED2 = 6;



//LED1 ***********************************************

MTsoftPWM Blink(LED1, inpulses_width 500000 micro_seconds, period_width 1000000 micro_seconds);

// end LED1 ***********************************************



//LED2 ***********************************************

void setup()
{
  pinMode(LED2, OUTPUT);
}

void loop()
{
  // Blink with delay
  digitalWrite(LED2, HIGH);
  delay(1100 milli_seconds);
  digitalWrite(LED2, LOW);
  delay(1100 milli_seconds);
}

/ end LED2 ***********************************************

Or more simple:

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects

const uint8_t LED1 = 5;
const uint8_t LED2 = 6;

MTsoftPWM Blink1(LED1, inpulses_width 500000 micro_seconds, period_width 1000000 micro_seconds);
MTsoftPWM Blink2(LED2, inpulses_width 1100000 micro_seconds, period_width 2000000 micro_seconds);

void setup(){}

void loop(){}

loop1 uses a very standard button. It's defined in MTobjects (as in all button libraries).

If the button can be read every 16ms instead of every 20ms (MTobjects' clock speed), and if the device is an AVR (Nano, Uno, or Mega), you could do something like this (untested):

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects


// loop1
const uint8_t PIN_BUTTON = 9; // Button wired between GND and 9
void push(void)
{
    mode++; 
    if (mode > 2) mode = 1;
}

MTbutton MyButton(PIN_BUTTON, push); // Button setup (pinMode is unnecessary, it's handled by MTobjects)



// loop2
void loop() {
//----potentiometers 1 and two----
 int pot1 = analogRead(A0); 
 int pot2 = analogRead(A1); 
//----potentiometers 1 and two----
 if (mode==1) {
 digitalWrite(2, HIGH);
 digitalWrite(3, HIGH);
 digitalWrite(4, HIGH);
 digitalWrite(5, HIGH);
 delay(pot1); 
 digitalWrite(2, LOW);
 digitalWrite(3, LOW);
 digitalWrite(4, LOW);
 digitalWrite(5, LOW);
 delay(pot2);
}
 else if (mode==2){

 digitalWrite(2,HIGH); 
 delay(pot1+pot2); 
 digitalWrite(2,LOW); 
 digitalWrite(3,HIGH);
 delay(pot1+pot2);
 digitalWrite(3,LOW);
 digitalWrite(4,HIGH);
 delay(pot1+pot2);
 digitalWrite(4,LOW);
 digitalWrite(5,HIGH);
 delay(pot1+pot2);
 digitalWrite(5,LOW);
 }

}

We can also use an MTcheckButton which directly calculates "mode":

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects


// loop1
const uint8_t PIN_BUTTON = 9; // Button wired between GND and 9
MTcheckButton MyButton(PIN_BUTTON); // Button setup (pinMode is unnecessary, it's handled by MTobjects)



// loop2
void loop() {
//----potentiometers 1 and two----
 int pot1 = analogRead(A0); 
 int pot2 = analogRead(A1); 
//----potentiometers 1 and two----
 if (MyButton.select) {  // Amended **********************
 digitalWrite(2, HIGH);
 digitalWrite(3, HIGH);
 digitalWrite(4, HIGH);
 digitalWrite(5, HIGH);
 delay(pot1); 
 digitalWrite(2, LOW);
 digitalWrite(3, LOW);
 digitalWrite(4, LOW);
 digitalWrite(5, LOW);
 delay(pot2);
}
 else if (!MyButton.select){ // Amended **********************

 digitalWrite(2,HIGH); 
 delay(pot1+pot2); 
 digitalWrite(2,LOW); 
 digitalWrite(3,HIGH);
 delay(pot1+pot2);
 digitalWrite(3,LOW);
 digitalWrite(4,HIGH);
 delay(pot1+pot2);
 digitalWrite(4,LOW);
 digitalWrite(5,HIGH);
 delay(pot1+pot2);
 digitalWrite(5,LOW);
 }

}

Instead of:

const uint8_t PIN_BUTTON = 9; // Button wired between GND and 9
MTcheckButton MyButton(PIN_BUTTON); // Button setup (pinMode is unnecessary, it's handled by MTobjects)

you can also put:

MTcheckButton MyButton(9); // Button setup (pinMode is unnecessary, it's handled by MTobjects)

Just a note that works only because the current AVR implémentation of main() and init() does not modify the pin states and so the pull-up that is set in your constructor before main() is called is not impacted.

It would not be always true if Arduino were to change this or if you were to port your code to different boards (the oneButton library had this bug too).

if you don't want this "risk", you could add a begin() method used once in the setup that would set the pin as INPUT_PULLUP.

That's true, but I'm using timer 0, and a change in its use would also force me to change the code, even with a begin().

I'm using timer 0 completely, and another microprocessor might not have the same functions for that timer. The code isn't directly portable.

The AVR timers are initialized after my objects are declared. I could have used begin(), but I preferred to delay object initialization so they are initialized after the start of loop().

For example:

#include <MTobjects.h> // https://github.com/OlivierPcheux/MTobjects

const uint8_t LED = 9;

MThardPWM Blink(LED, inpulses_width 500000 micro_seconds, period_width 1000000 micro_seconds); 

void setup(){}

void loop()
{
   //delay(100);
   TCCR1A = 0; // Destroy the pwm because LED use the timer 1
   while(true);
}

Since the initialization of the timers is "destroyed" just before loop(), Blink's initialization occurs 16ms after the start of loop(). This is how the program works. We could do the same for the initializations of other objects if necessary, but this would increase the code size.


But we're getting a bit off-topic for this post...

Its uncertain from OPs question whether what is needed is multitasking or actual multi-threading.

Giving the appearance of running multiple tasks simultaneously can be accomplished quite simply using millis(). The following video gives a simple introduction into how that works:

Once that has been understood, then the follow-up video progresses to building a state machine to monitor e.g. button states and take actions without blocking:

Thought I would throw this in for what its worth. It helped me some time ago to understand using millis() to do multitasking.