Combining multiple non-blocking codes

Combining two codes (or more) is relatively painless if the codes to be combined are non-blocking.

For example:

image
https://wokwi.com/projects/410224559941422081

It runs three steppers, 1 servo, 5 LEDs, 1 strip of Neopixels, and is reactive to 4 buttons at any time.

This simulation is a combination of three programs:

If the component programs are written in a cooperative non-blocking style, you can fairly easily combine a number of them into a single program. If your source programs are not written cooperatively, they will likely require a rewrite to share the processor with other programs.

Combined code for the above simulation
// https://wokwi.com/projects/410224559941422081
// For https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/118
// Example of combining three non-blocking codes, cut-and-pasted together:
/*
  1) https://wokwi.com/projects/410122241449166849 -- Mobatools Reference2 Example by @MicroBahner
  2) https://wokwi.com/projects/366664987128970241 -- SeveralThingsAtTheSameTime by @Robin2
  3) https://wokwi.com/projects/410037772389117953 -- Some Fastleds by @DaveX

*/
////////// Example Reference for MoToStepper - attaching a bipolar stepper with step/dir and enable ////////////
// https://github.com/MicroBahner/MobaTools/blob/master/examples/_Stepper/Stepper_Reference_2/Stepper_Reference_2.ino
// https://wokwi.com/projects/410122241449166849
// for https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/6


/* An Example with 3 steppers ( 2 unipolar, 1 bipolar ).
    A reference run is started in setup.  2 limit switches are required for this.
    This example does not run on ESP8266
*/


//==============   Declarations

//======================================================
//================= Declarations First program
//======================================================

#include <MobaTools.h>
MoToTimer Pause;
MoToStepper Step_X(4096);                    // X-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Y(4096);                    // Y-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Z(200, STEPDIR);            // Z-Achse, bipolarer Schrittmotor mit Treiber wie A4988, DRV8825 oder vergleichbare
const byte pinRef[] = { A0, A1 };            // pinRef_X, pinRef_Y
const byte nbrOfButtons = sizeof(pinRef);    // Anzahl der angeschlossenen Taster
enum {X_AXIS = 0, Y_AXIS = 1};

//======================================================
//================= Declarations Second program
//======================================================
//Several Things At The Same Time
// -----LIBRARIES

#include <Servo.h>

// ----CONSTANTS (won't change)

const int onBoardLedPin =  14;      // the pin numbers for the LEDs
const int led_A_Pin = 15;
const int led_B_Pin = 16;
const int buttonLed_Pin = 17;

const int buttonPin = 20; // the pin number for the button

const int servoPin = 19; // the pin number for the servo signal

const int onBoardLedInterval = 500; // number of millisecs between blinks
const int led_A_Interval = 2500;
const int led_B_Interval = 4500;

const int blinkDuration = 500; // number of millisecs that Led's are on - all three leds use this

const int buttonInterval = 300; // number of millisecs between button readings

const int servoMinDegrees = 20; // the limits to servo movement
const int servoMaxDegrees = 150;

//------- VARIABLES (will change)

byte onBoardLedState = LOW;             // used to record whether the LEDs are on or off
byte led_A_State = LOW;           //   LOW = off
byte led_B_State = LOW;
byte buttonLed_State = LOW;

Servo myservo;  // create servo object to control a servo

int servoPosition = 90;     // the current angle of the servo - starting at 90.
int servoSlowInterval = 80; // millisecs between servo moves
int servoFastInterval = 10;
int servoInterval = servoSlowInterval; // initial millisecs between servo moves
int servoDegrees = 2;       // amount servo moves at each step
//    will be changed to negative value for movement in the other direction

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousOnBoardLedMillis = 0;   // will store last time the LED was updated
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

//======================================================
//==== Declarations  Third program
//======================================================
//
// fastled DaveX from https://wokwi.com/projects/410037772389117953
//
#include "FastLED.h"

const int DaveXButton = 21;
const int DaveXLED = 18;
#define NUM_LEDS1 26
#define DATA_PIN1 35
CRGB array_LED_sconce [NUM_LEDS1];
uint32_t interval = 1000, timeout;
// subset of ledstrip to color
int ledBlink[] = {4, 12, 20, 0, 8, 16, 24};

int fastledState;
CHSV daveXringColor = CHSV(70, 255, 255);

//======================================================
//================= Arduino minimal program requirements
//======================================================

void setup() {
  Serial.begin(115200);
  Serial.println("Starting a Combination of Three Programs");
  setupMoba();
  setupSTATST();
  setupDaveX();
}

void loop() {
  loopMoba();
  loopSTATST();
  loopDaveX();
}

//======================================================
//======== renamed functions from component programs
//======================================================

void setupMoba() {
  Serial.println("MobaTools Reference 2 Example  https://wokwi.com/projects/410123341513466881");  // so we know what sketch is running

  for (byte i = 0; i < nbrOfButtons; i++)  {
    pinMode(pinRef[i], INPUT_PULLUP);        // gedrückt = LOW
  }
  Step_X.attach( 12, 11, 10, 9 );            // IN1, IN2, IN3, IN4
  Step_Y.attach( 5, 4, 3, 2 );               // IN1, IN2, IN3, IN4

  Step_Z.attach( 7, 8 );                     // STEPpin, DIRpin
  Step_Z.setSpeed( 800 );                    // = 80 U/Min (motorspezifisch)
  Step_Z.setRampLen( 50 );                   // Beschleunigung (motorspezifisch)
  Step_Z.write(360);                         // Winkel 360 Grad drehen
 // Pause.setTime( 2500 );                     // Dreh- und Pausenzeit
}

void loopMoba() {
  enum {SEEK_ZERO, MOVE_FORWARD, MOVE_BACWARD};
  static byte step_X = SEEK_ZERO;          // Schrittkettenstatus X-Achse
  static byte step_Y = SEEK_ZERO;          // Schrittkettenstatus Y-Achse
  static int16_t angle = 360;

  //-- X-Achse
  switch (step_X) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_X, X_AXIS)) {
        step_X = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_X.setSpeed( 200 );              // = 20 U/Min (motorspezifisch)
        Step_X.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_X.writeSteps(4096);
        step_X = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_X.writeSteps(-4096);
        step_X = MOVE_FORWARD;
      }
      break;
  }
  //-- Y-Achse
  switch (step_Y) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_Y, Y_AXIS)) {
        step_Y = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_Y.setSpeed( 150 );              // = 20 U/Min (motorspezifisch)
        Step_Y.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_Y.writeSteps(4096);             // Bewegung starten
        step_Y = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_Y.writeSteps(-4096);            // Bewegung starten
        step_Y = MOVE_FORWARD;
      }
      break;
  }
  //-- Z-Achse
  if ( !Step_Z.moving() ) {                  // warten bis die Bewegung abgeschlossen ist
    if ( !Pause.running() ) {                // warten bis Ablauf der Zeit
      Pause.setTime( 5000 );                 // Zeit für Bewegung und Pause
      angle *= -1;                          // andere Richtung
      Step_Z.write(angle);                  // Bewegung starten
    }
  }

}

bool seekZeropoint(MoToStepper &Step, byte axis) {     // keine Kopie, sondern eine Referenz des Schrittmotorobjektes
  enum {REFLEAVE, REFREACH, REACHZERO};
  static byte step[] = {REFLEAVE, REFLEAVE};       // Schrittkettenstatus

  switch (step[axis]) {
    case REFLEAVE:
      Step.setSpeed( 50 );                   // = 5 U/Min (motorspezifisch) Schleichfahrt
      Step.setRampLen( 5 );                  // Beschleunigung (motorspezifisch)
      Step.rotate(1);                        // vom Referentpunkt runterbewegen
      if (digitalRead(pinRef[axis])) {      // Referenzsensor nicht (mehr) betätigt
        Step.doSteps(100);                   // mit etwas Entfernung anhalten
        step[axis]++;
      }
      break;
    case REFREACH:
      if ( !Step.moving() ) {                // warten bis die Bewegung abgeschlossen ist
        Step.rotate(-1);                     // Richtung Referentpunkt bewegen
        step[axis]++;
      }
      break;
    case REACHZERO:
      if (!digitalRead(pinRef[axis])) {     // Ref_X betätigt
        Step.setZero(100);                   // setze Nullpunkt 100 Schritte von Referenzpunkt entfernt
        Step.writeSteps(0);                  // zum Nullpunkt bewegen
        step[axis]++;
      }
      break;
    default:
      step[axis] = REFLEAVE;
      return true;
  }
  return false;
}

//======================================================
//================= Second program functions
//======================================================

//###########################################################

// SeveralThingsAtTheSameTimeRev1.ino
// from:
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/1
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/2
// implemented on a Wokwi simulation per
// https://forum.arduino.cc/t/circuit-with-1-button-and-3-leds/1134897/3
// Wokwi: https://wokwi.com/projects/366664987128970241

// Note that the Adafruit series at https://learn.adafruit.com/multi-tasking-the-arduino-part-1/all-together-now
// expands on this to do two steppers using classes
// An implementation of the class-based Adafruit one in Wokwi is at
// https://wokwi.com/projects/397172393690134529

// An expansion of the BlinkWithoutDelay concept to illustrate how a script
//  can appear to do several things at the same time

// this sketch does the following
//    it blinks the onboard LED (as in the blinkWithoutDelay sketch)
//    it blinks two external LEDs (LedA and LedB) that are connected to pins 12 and 11.
//    it turns another Led (buttonLed connected to pin 10) on or off whenever a button
//       connected to pin 7 is pressed
//    it sweeps a servo (connected to pin 5) back and forth at different speeds

//  One leg of each LED should be connected to the relevant pin and the other leg should be connected to a
//   resistor of 470 ohms or more and the other end of the resistor to the Arduino GND.
//   If the LED doesn't light its probably connected the wrong way round.

//  On my Uno and Mega the "button" is just a piece of wire inserted into pin 7.
//   Touching the end of the wire with a moist finger is sufficient to cause the switching action
//   Of course a proper press-on-release-off button switch could also be used!

//  The Arduino is not capable of supplying enough 5v power to operate a servo
//    The servo should have it's own power supply and the power supply Ground should
//      be connected to the Arduino Ground.

// The sketch is written to illustrate a few different programming features.
//    The use of many functions with short pieces of code.
//       Short pieces of code are much easier to follow and debug
//    The use of variables to record the state of something (e.g. onBoardLedState) as a means to
//       enable the different functions to determine what to do.
//    The use of millis() to manage the timing of activities
//    The definition of all numbers used by the program at the top of the sketch where
//       they can easily be found if they need to be changed

//=======


//========

void setupSTATST() {

  //  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running

  // set the Led pins as output:
  pinMode(onBoardLedPin, OUTPUT);
  pinMode(led_A_Pin, OUTPUT);
  pinMode(led_B_Pin, OUTPUT);
  pinMode(buttonLed_Pin, OUTPUT);

  // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);

  myservo.write(servoPosition); // sets the initial position
  myservo.attach(servoPin);

}

//=======

void loopSTATST() {

  // Notice that none of the action happens in loop() apart from reading millis()
  //   it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  readButton();               // call the functions that do the work
  updateOnBoardLedState();
  updateLed_A_State();
  updateLed_B_State();
  switchLeds();
  servoSweep();

}

//========

void updateOnBoardLedState() {

  if (onBoardLedState == LOW) {
    // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
      // time is up, so change the state to HIGH
      onBoardLedState = HIGH;
      // and save the time when we made the change
      previousOnBoardLedMillis += onBoardLedInterval;
      // NOTE: The previous line could alternatively be
      //              previousOnBoardLedMillis = currentMillis
      //        which is the style used in the BlinkWithoutDelay example sketch
      //        Adding on the interval is a better way to ensure that succesive periods are identical

    }
  }
  else {  // i.e. if onBoardLedState is HIGH

    // if the Led is on, we must wait for the duration to expire before turning it off
    if (currentMillis - previousOnBoardLedMillis >= blinkDuration) {
      // time is up, so change the state to LOW
      onBoardLedState = LOW;
      // and save the time when we made the change
      previousOnBoardLedMillis += blinkDuration;
    }
  }
}

//=======

void updateLed_A_State() {

  if (led_A_State == LOW) {
    if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
      led_A_State = HIGH;
      previousLed_A_Millis += led_A_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_A_Millis >= blinkDuration) {
      led_A_State = LOW;
      previousLed_A_Millis += blinkDuration;
    }
  }
}

//=======

void updateLed_B_State() {

  if (led_B_State == LOW) {
    if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
      led_B_State = HIGH;
      previousLed_B_Millis += led_B_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_B_Millis >= blinkDuration) {
      led_B_State = LOW;
      previousLed_B_Millis += blinkDuration;
    }
  }
}

//========

void switchLeds() {
  // this is the code that actually switches the LEDs on and off

  digitalWrite(onBoardLedPin, onBoardLedState);
  digitalWrite(led_A_Pin, led_A_State);
  digitalWrite(led_B_Pin, led_B_State);
  digitalWrite(buttonLed_Pin, buttonLed_State);
}

//=======

void readButton() {

  // this only reads the button state after the button interval has elapsed
  //  this avoids multiple flashes if the button bounces
  // every time the button is pressed it changes buttonLed_State causing the Led to go on or off
  // Notice that there is no need to synchronize this use of millis() with the flashing Leds

  if (millis() - previousButtonMillis >= buttonInterval) {

    if (digitalRead(buttonPin) == LOW) {
      buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH
      //   and to HIGH if it was LOW
      previousButtonMillis += buttonInterval;
    }
  }

}

//========

void servoSweep() {

  // this is similar to the servo sweep example except that it uses millis() rather than delay()

  // nothing happens unless the interval has expired
  // the value of currentMillis was set in loop()

  if (currentMillis - previousServoMillis >= servoInterval) {
    // its time for another move
    previousServoMillis += servoInterval;

    servoPosition = servoPosition + servoDegrees; // servoDegrees might be negative

    if (servoPosition <= servoMinDegrees) {
      // when the servo gets to its minimum position change the interval to change the speed
      if (servoInterval == servoSlowInterval) {
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoSlowInterval;
      }
    }
    if ((servoPosition >= servoMaxDegrees) || (servoPosition <= servoMinDegrees))  {
      // if the servo is at either extreme change the sign of the degrees to make it move the other way
      servoDegrees = - servoDegrees; // reverse direction
      // and update the position to ensure it is within range
      servoPosition = servoPosition + servoDegrees;
    }
    // make the servo move to the next position
    myservo.write(servoPosition);
    // and record the time when the move happened
  }
}

//=====END


//======================================================
//================= Third program functions
//======================================================
// Non-blocking Fastled animation with a button-led function
//
// https://wokwi.com/projects/410037772389117953
// https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/89?u=davex

void setupDaveX()
{
  FastLED.addLeds<WS2812B, DATA_PIN1, GRB>(array_LED_sconce, NUM_LEDS1);
  FastLED.setBrightness(255);
  // Serial.begin(115200);
  pinMode(DaveXLED, OUTPUT);
  pinMode(DaveXButton, INPUT_PULLUP);
  Serial.println("DaveX FastLEDs https://wokwi.com/projects/410037772389117953");  // so we know what sketch is running

}

void loopDaveX()
{
  wiperNB();
  somethingElse();
}

void somethingElse() {
  auto now = millis();
  const typeof(now) interval = 100;
  static typeof(now) last = -interval;
  if (now - last >= interval) {
    last += interval;
    digitalWrite(DaveXLED, !digitalRead(DaveXButton));
  }
}

void wiperNB() {
  // size 7 gives 0-6 up 7 blank, 8-14 down, 15 blank
  const int rangeLen = sizeof(ledBlink) / sizeof(ledBlink[0]);
  const int maxState = 2 * rangeLen + 1;
  if (millis() - timeout > interval) { // wait for interval
    timeout += interval; // restart the "lap" timer
  //  Serial.print(fastledState); Serial.print("/");
  //  Serial.print(maxState); Serial.print(" ");
    switch (fastledState) {
      case 0 ... rangeLen-1: // wiping up
        ledOnProcessing(ledBlink[fastledState]);
        break;
      case rangeLen:
        allLedsOff();
        break;
      case rangeLen+1 ... maxState-1:  // wiping down
        {
          const int LastLedIndex = rangeLen - 1;
          int upwardStep = fastledState - (rangeLen + 1);
          int ledToChange = LastLedIndex - upwardStep;
          ledOnProcessing(ledBlink[ledToChange]);
        }
        break;
      case maxState:
        allLedsOff();
        break;
      default:
        Serial.print(fastledState);
    }
    ++fastledState; // prepare for next step
    if (fastledState > maxState) { // move to next cycle
      fastledState = 0;
    }
    FastLED.show();
  }
}

void ledOnProcessing(int fastledState) {
  array_LED_sconce[fastledState] = daveXringColor;
}

void allLedsOff() {
  fill_solid(array_LED_sconce, NUM_LEDS1, CHSV(0, 0, 0));
}

The core idea of this combination is this:

...
void setup() {
  Serial.begin(115200);
  Serial.println("Starting a Combination of Three Programs");
  setupMoba();
  setupSTATST();
  setupDaveX();
}

void loop() {
  loopMoba();
  loopSTATST();
  loopDaveX();
}
...

This was a quick cut-and-paste job, but since the component programs were each written to be cooperative non-blocking programs, all that needed was a little adjustment to make sure the function names and pins didn't conflict.

The basic process was to:

  1. Move all the globals and declarations to the top of the program
  2. rename the setup() and loop() functions to not conflict with each other.
  3. Put calls to the newly named setups and loops into a global setup() and loop().
  4. Reassign the pins so they do not conflict.

The main requirement is that your component programs ought to work cooperatively, as explained in these:

Also see this collection of Arduino Built-In-Examples with Wokwi simulations:

5 Likes

This one builds upon the last and combines 5 separate programs, adding a performance measure and serial parsing to interact with the program:

// https://wokwi.com/projects/410224559941422081
// For https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/118
// Example of combining five non-blocking codes, cut-and-pasted together:
/*
  1) https://wokwi.com/projects/410122241449166849 -- Mobatools Reference2 Example by @MicroBahner
  2) https://wokwi.com/projects/366664987128970241 -- SeveralThingsAtTheSameTime by @Robin2
  3) https://wokwi.com/projects/410037772389117953 -- Some Fastleds by @DaveX
  4) https://wokwi.com/projects/410296132612048897 -- Loop Timer https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/122?u=davex
  5) https://wokwi.com/projects/409963275966630913 -- Serial Command Parser by @nickgammon
*/
////////// Example Reference for MoToStepper - attaching a bipolar stepper with step/dir and enable ////////////
// https://github.com/MicroBahner/MobaTools/blob/master/examples/_Stepper/Stepper_Reference_2/Stepper_Reference_2.ino
// https://wokwi.com/projects/410122241449166849
// for https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/6


/* An Example with 3 steppers ( 2 unipolar, 1 bipolar ).
    A reference run is started in setup.  2 limit switches are required for this.
    This example does not run on ESP8266
*/


//==============   Declarations

//======================================================
//================= Declarations First program
//======================================================

#include <MobaTools.h>
MoToTimer Pause;
MoToStepper Step_X(4096);                    // X-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Y(4096);                    // Y-Achse, unipolarer Schrittmotor 28BYJ-48
MoToStepper Step_Z(200, STEPDIR);            // Z-Achse, bipolarer Schrittmotor mit Treiber wie A4988, DRV8825 oder vergleichbare
const byte pinRef[] = { A0, A1 };            // pinRef_X, pinRef_Y
const byte nbrOfButtons = sizeof(pinRef);    // Anzahl der angeschlossenen Taster
enum {X_AXIS = 0, Y_AXIS = 1};

//======================================================
//================= Declarations Second program
//======================================================
//Several Things At The Same Time
// -----LIBRARIES

#include <Servo.h>

// ----CONSTANTS (won't change)

const int onBoardLedPin =  14;      // the pin numbers for the LEDs
const int led_A_Pin = 15;
const int led_B_Pin = 16;
const int buttonLed_Pin = 17;

const int buttonPin = 20; // the pin number for the button

const int servoPin = 19; // the pin number for the servo signal

const int onBoardLedInterval = 500; // number of millisecs between blinks
const int led_A_Interval = 2500;
const int led_B_Interval = 4500;

const int blinkDuration = 500; // number of millisecs that Led's are on - all three leds use this

const int buttonInterval = 300; // number of millisecs between button readings

const int servoMinDegrees = 20; // the limits to servo movement
const int servoMaxDegrees = 150;

//------- VARIABLES (will change)

byte onBoardLedState = LOW;             // used to record whether the LEDs are on or off
byte led_A_State = LOW;           //   LOW = off
byte led_B_State = LOW;
byte buttonLed_State = LOW;

Servo myservo;  // create servo object to control a servo

int servoPosition = 90;     // the current angle of the servo - starting at 90.
int servoSlowInterval = 80; // millisecs between servo moves
int servoFastInterval = 10;
int servoInterval = servoSlowInterval; // initial millisecs between servo moves
int servoDegrees = 2;       // amount servo moves at each step
//    will be changed to negative value for movement in the other direction

unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousOnBoardLedMillis = 0;   // will store last time the LED was updated
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;

unsigned long previousButtonMillis = 0; // time when button press last checked

unsigned long previousServoMillis = 0; // the time when the servo was last moved

//======================================================
//==== Declarations  Third program
//======================================================
//
// fastled DaveX from https://wokwi.com/projects/410037772389117953
//
#include "FastLED.h"

const int DaveXButton = 21;
const int DaveXLED = 18;
#define NUM_LEDS1 26
#define DATA_PIN1 35
CRGB array_LED_sconce [NUM_LEDS1];
uint32_t interval = 1000, timeout;
// subset of ledstrip to color
int ledBlink[] = {4, 12, 20, 0, 8, 16, 24};

int fastledState;
CHSV daveXringColor = CHSV(70, 255, 255);

//======================================================
//================= Fourth program Declarations
//======================================================

extern volatile unsigned long timer0_millis; // might be faster than millis()

//======================================================
//================= Fifth program Declarations
//======================================================

// the possible states of the state-machine
typedef enum {  NONE, GOT_R, GOT_S, GOT_G } statesSerial;

// current state-machine state
statesSerial state = NONE;
// current partial number
unsigned int currentValue;


//======================================================
//================= Interaction program Declarations
//======================================================

unsigned long rpm = 20, speed = 100, garrulous = 0;
bool dirtyInteractions = false; // flag for handling interactions
long X_oscillationSpeed = 200;
long Y_oscillationSpeed = 150;

const char help[] PROGMEM = 
  ""
  "// Commands:\n"
  "// Rnnnn sets the Z-axis RPM\n"
  "// Snnn set the X and Y speeds proportionally\n"
  "// G0 disables the loop speed testing\n"
  "// G1 (or nonzero) enables the testing.\n"
  "[Rnnn][Snnn][Gnnn]?\n"
;
// per https://forum.arduino.cc/t/printing-from-progmem-in-simplest-fashion/525923/5
#define CF(x) ((const __FlashStringHelper *)x)

//======================================================
//================= Arduino minimal program requirements
//======================================================

void setup() {
  Serial.begin(115200);
  Serial.println("Starting a Combination of Four Programs");
  setupGammonSerial();
  setupMoba();
  setupSTATST();
  setupDaveX();
  Serial.print(CF(help));
}

void loop() {
  loopGammonSerial();
  loopMoba();
  loopSTATST();
  loopDaveX();
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
  handleInteractions();
}

//======================================================
//======== renamed functions from component programs
//======================================================

void setupMoba() {
  Serial.println("MobaTools Reference 2 Example  https://wokwi.com/projects/410123341513466881");  // so we know what sketch is running

  for (byte i = 0; i < nbrOfButtons; i++)  {
    pinMode(pinRef[i], INPUT_PULLUP);        // gedrückt = LOW
  }
  Step_X.attach( 12, 11, 10, 9 );            // IN1, IN2, IN3, IN4
  Step_Y.attach( 5, 4, 3, 2 );               // IN1, IN2, IN3, IN4

  Step_Z.attach( 7, 8 );                     // STEPpin, DIRpin
  Step_Z.setSpeed( 800 );                    // = 80 U/Min (motorspezifisch)
  Step_Z.setRampLen( 50 );                   // Beschleunigung (motorspezifisch)
  Step_Z.write(360);                         // Winkel 360 Grad drehen
  // Pause.setTime( 2500 );                     // Dreh- und Pausenzeit
}

void loopMoba() {
  enum {SEEK_ZERO, MOVE_FORWARD, MOVE_BACWARD};
  static byte step_X = SEEK_ZERO;          // Schrittkettenstatus X-Achse
  static byte step_Y = SEEK_ZERO;          // Schrittkettenstatus Y-Achse
  static int16_t angle = 360;

  //-- X-Achse
  switch (step_X) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_X, X_AXIS)) {
        step_X = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        //        Step_X.setSpeed( 200 );              // = 20 U/Min (motorspezifisch)
        Step_X.setSpeed( X_oscillationSpeed );              // = 20 U/Min (motorspezifisch)
        Step_X.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_X.writeSteps(4096);
        step_X = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_X.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_X.writeSteps(-4096);
        step_X = MOVE_FORWARD;
      }
      break;
  }
  //-- Y-Achse
  switch (step_Y) {
    case SEEK_ZERO:
      if (seekZeropoint(Step_Y, Y_AXIS)) {
        step_Y = MOVE_FORWARD;
      }
      break;
    case MOVE_FORWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        //        Step_Y.setSpeed( 150 );              // = 20 U/Min (motorspezifisch)
        Step_Y.setSpeed( Y_oscillationSpeed );              // = 20 U/Min (motorspezifisch)
        Step_Y.setRampLen( 100 );            // Beschleunigung (motorspezifisch)
        Step_Y.writeSteps(4096);             // Bewegung starten
        step_Y = MOVE_BACWARD;
      }
      break;
    case MOVE_BACWARD:
      if ( !Step_Y.moving() ) {              // warten bis die Bewegung abgeschlossen ist
        Step_Y.writeSteps(-4096);            // Bewegung starten
        step_Y = MOVE_FORWARD;
      }
      break;
  }
  //-- Z-Achse
  if ( !Step_Z.moving() ) {                  // warten bis die Bewegung abgeschlossen ist
    if ( !Pause.running() ) {                // warten bis Ablauf der Zeit
      Pause.setTime( 5000 );                 // Zeit für Bewegung und Pause
      angle *= -1;                          // andere Richtung
      Step_Z.write(angle);                  // Bewegung starten
    }
  }

}

bool seekZeropoint(MoToStepper &Step, byte axis) {     // keine Kopie, sondern eine Referenz des Schrittmotorobjektes
  enum {REFLEAVE, REFREACH, REACHZERO};
  static byte step[] = {REFLEAVE, REFLEAVE};       // Schrittkettenstatus

  switch (step[axis]) {
    case REFLEAVE:
      Step.setSpeed( 50 );                   // = 5 U/Min (motorspezifisch) Schleichfahrt
      Step.setRampLen( 5 );                  // Beschleunigung (motorspezifisch)
      Step.rotate(1);                        // vom Referentpunkt runterbewegen
      if (digitalRead(pinRef[axis])) {      // Referenzsensor nicht (mehr) betätigt
        Step.doSteps(100);                   // mit etwas Entfernung anhalten
        step[axis]++;
      }
      break;
    case REFREACH:
      if ( !Step.moving() ) {                // warten bis die Bewegung abgeschlossen ist
        Step.rotate(-1);                     // Richtung Referentpunkt bewegen
        step[axis]++;
      }
      break;
    case REACHZERO:
      if (!digitalRead(pinRef[axis])) {     // Ref_X betätigt
        Step.setZero(100);                   // setze Nullpunkt 100 Schritte von Referenzpunkt entfernt
        Step.writeSteps(0);                  // zum Nullpunkt bewegen
        step[axis]++;
      }
      break;
    default:
      step[axis] = REFLEAVE;
      return true;
  }
  return false;
}

//======================================================
//================= Second program functions
//======================================================

//###########################################################

// SeveralThingsAtTheSameTimeRev1.ino
// from:
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/1
// https://forum.arduino.cc/t/demonstration-code-for-several-things-at-the-same-time/217158/2
// implemented on a Wokwi simulation per
// https://forum.arduino.cc/t/circuit-with-1-button-and-3-leds/1134897/3
// Wokwi: https://wokwi.com/projects/366664987128970241

// Note that the Adafruit series at https://learn.adafruit.com/multi-tasking-the-arduino-part-1/all-together-now
// expands on this to do two steppers using classes
// An implementation of the class-based Adafruit one in Wokwi is at
// https://wokwi.com/projects/397172393690134529

// An expansion of the BlinkWithoutDelay concept to illustrate how a script
//  can appear to do several things at the same time

// this sketch does the following
//    it blinks the onboard LED (as in the blinkWithoutDelay sketch)
//    it blinks two external LEDs (LedA and LedB) that are connected to pins 12 and 11.
//    it turns another Led (buttonLed connected to pin 10) on or off whenever a button
//       connected to pin 7 is pressed
//    it sweeps a servo (connected to pin 5) back and forth at different speeds

//  One leg of each LED should be connected to the relevant pin and the other leg should be connected to a
//   resistor of 470 ohms or more and the other end of the resistor to the Arduino GND.
//   If the LED doesn't light its probably connected the wrong way round.

//  On my Uno and Mega the "button" is just a piece of wire inserted into pin 7.
//   Touching the end of the wire with a moist finger is sufficient to cause the switching action
//   Of course a proper press-on-release-off button switch could also be used!

//  The Arduino is not capable of supplying enough 5v power to operate a servo
//    The servo should have it's own power supply and the power supply Ground should
//      be connected to the Arduino Ground.

// The sketch is written to illustrate a few different programming features.
//    The use of many functions with short pieces of code.
//       Short pieces of code are much easier to follow and debug
//    The use of variables to record the state of something (e.g. onBoardLedState) as a means to
//       enable the different functions to determine what to do.
//    The use of millis() to manage the timing of activities
//    The definition of all numbers used by the program at the top of the sketch where
//       they can easily be found if they need to be changed

//=======


//========

void setupSTATST() {

  //  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running

  // set the Led pins as output:
  pinMode(onBoardLedPin, OUTPUT);
  pinMode(led_A_Pin, OUTPUT);
  pinMode(led_B_Pin, OUTPUT);
  pinMode(buttonLed_Pin, OUTPUT);

  // set the button pin as input with a pullup resistor to ensure it defaults to HIGH
  pinMode(buttonPin, INPUT_PULLUP);

  myservo.write(servoPosition); // sets the initial position
  myservo.attach(servoPin);

}

//=======

void loopSTATST() {

  // Notice that none of the action happens in loop() apart from reading millis()
  //   it just calls the functions that have the action code

  currentMillis = millis();   // capture the latest value of millis()
  //   this is equivalent to noting the time from a clock
  //   use the same time for all LED flashes to keep them synchronized

  readButton();               // call the functions that do the work
  updateOnBoardLedState();
  updateLed_A_State();
  updateLed_B_State();
  switchLeds();
  servoSweep();

}

//========

void updateOnBoardLedState() {

  if (onBoardLedState == LOW) {
    // if the Led is off, we must wait for the interval to expire before turning it on
    if (currentMillis - previousOnBoardLedMillis >= onBoardLedInterval) {
      // time is up, so change the state to HIGH
      onBoardLedState = HIGH;
      // and save the time when we made the change
      previousOnBoardLedMillis += onBoardLedInterval;
      // NOTE: The previous line could alternatively be
      //              previousOnBoardLedMillis = currentMillis
      //        which is the style used in the BlinkWithoutDelay example sketch
      //        Adding on the interval is a better way to ensure that succesive periods are identical

    }
  }
  else {  // i.e. if onBoardLedState is HIGH

    // if the Led is on, we must wait for the duration to expire before turning it off
    if (currentMillis - previousOnBoardLedMillis >= blinkDuration) {
      // time is up, so change the state to LOW
      onBoardLedState = LOW;
      // and save the time when we made the change
      previousOnBoardLedMillis += blinkDuration;
    }
  }
}

//=======

void updateLed_A_State() {

  if (led_A_State == LOW) {
    if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
      led_A_State = HIGH;
      previousLed_A_Millis += led_A_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_A_Millis >= blinkDuration) {
      led_A_State = LOW;
      previousLed_A_Millis += blinkDuration;
    }
  }
}

//=======

void updateLed_B_State() {

  if (led_B_State == LOW) {
    if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
      led_B_State = HIGH;
      previousLed_B_Millis += led_B_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_B_Millis >= blinkDuration) {
      led_B_State = LOW;
      previousLed_B_Millis += blinkDuration;
    }
  }
}

//========

void switchLeds() {
  // this is the code that actually switches the LEDs on and off

  digitalWrite(onBoardLedPin, onBoardLedState);
  digitalWrite(led_A_Pin, led_A_State);
  digitalWrite(led_B_Pin, led_B_State);
  digitalWrite(buttonLed_Pin, buttonLed_State);
}

//=======

void readButton() {

  // this only reads the button state after the button interval has elapsed
  //  this avoids multiple flashes if the button bounces
  // every time the button is pressed it changes buttonLed_State causing the Led to go on or off
  // Notice that there is no need to synchronize this use of millis() with the flashing Leds

  if (millis() - previousButtonMillis >= buttonInterval) {

    if (digitalRead(buttonPin) == LOW) {
      buttonLed_State = ! buttonLed_State; // this changes it to LOW if it was HIGH
      //   and to HIGH if it was LOW
      previousButtonMillis += buttonInterval;
    }
  }

}

//========

void servoSweep() {

  // this is similar to the servo sweep example except that it uses millis() rather than delay()

  // nothing happens unless the interval has expired
  // the value of currentMillis was set in loop()

  if (currentMillis - previousServoMillis >= servoInterval) {
    // its time for another move
    previousServoMillis += servoInterval;

    servoPosition = servoPosition + servoDegrees; // servoDegrees might be negative

    if (servoPosition <= servoMinDegrees) {
      // when the servo gets to its minimum position change the interval to change the speed
      if (servoInterval == servoSlowInterval) {
        servoInterval = servoFastInterval;
      }
      else {
        servoInterval = servoSlowInterval;
      }
    }
    if ((servoPosition >= servoMaxDegrees) || (servoPosition <= servoMinDegrees))  {
      // if the servo is at either extreme change the sign of the degrees to make it move the other way
      servoDegrees = - servoDegrees; // reverse direction
      // and update the position to ensure it is within range
      servoPosition = servoPosition + servoDegrees;
    }
    // make the servo move to the next position
    myservo.write(servoPosition);
    // and record the time when the move happened
  }
}

//=====END


//======================================================
//================= Third program functions
//======================================================
// Non-blocking Fastled animation with a button-led function
//
// https://wokwi.com/projects/410037772389117953
// https://forum.arduino.cc/t/millis-instead-of-delay-on-array-of-ledstrip/1304862/89?u=davex

void setupDaveX()
{
  FastLED.addLeds<WS2812B, DATA_PIN1, GRB>(array_LED_sconce, NUM_LEDS1);
  FastLED.setBrightness(255);
  // Serial.begin(115200);
  pinMode(DaveXLED, OUTPUT);
  pinMode(DaveXButton, INPUT_PULLUP);
  Serial.println("DaveX FastLEDs https://wokwi.com/projects/410037772389117953");  // so we know what sketch is running

}

void loopDaveX()
{
  wiperNB();
  somethingElse();
}

void somethingElse() {
  auto now = millis();
  const typeof(now) interval = 100;
  static typeof(now) last = -interval;
  if (now - last >= interval) {
    last += interval;
    digitalWrite(DaveXLED, !digitalRead(DaveXButton));
  }
}

void wiperNB() {
  // size 7 gives 0-6 up 7 blank, 8-14 down, 15 blank
  const int rangeLen = sizeof(ledBlink) / sizeof(ledBlink[0]);
  const int maxState = 2 * rangeLen + 1;
  if (millis() - timeout > interval) { // wait for interval
    timeout += interval; // restart the "lap" timer
    //  Serial.print(fastledState); Serial.print("/");
    //  Serial.print(maxState); Serial.print(" ");
    switch (fastledState) {
      case 0 ... rangeLen-1: // wiping up
        ledOnProcessing(ledBlink[fastledState]);
        break;
      case rangeLen:
        allLedsOff();
        break;
      case rangeLen+1 ... maxState-1:  // wiping down
        {
          const int LastLedIndex = rangeLen - 1;
          int upwardStep = fastledState - (rangeLen + 1);
          int ledToChange = LastLedIndex - upwardStep;
          ledOnProcessing(ledBlink[ledToChange]);
        }
        break;
      case maxState:
        allLedsOff();
        break;
      default:
        Serial.print(fastledState);
    }
    ++fastledState; // prepare for next step
    if (fastledState > maxState) { // move to next cycle
      fastledState = 0;
    }
    FastLED.show();
  }
}

void ledOnProcessing(int fastledState) {
  array_LED_sconce[fastledState] = daveXringColor;
}

void allLedsOff() {
  fill_solid(array_LED_sconce, NUM_LEDS1, CHSV(0, 0, 0));
}


void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set && garrulous) // 1 second
  {
    //    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
    //    Serial.write('\t');
    Serial.print( count ); // 32-bit binary into decimal text, load of cycles!
    Serial.println(" loop/sec");
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set; //  changes 0<==>1
  }
}


//======================================================
//================= Fifth program functions
//======================================================
// Non-blocking Serial command parsing
//
// Code from https://gammon.com.au/serial

// Example state machine reading serial input
// Author: Nick Gammon
// Date: 17 December 2011


//======================================================
//================= Fifth program functions
//======================================================

// Declarations above^^^

void setupGammonSerial ()
{
  // Serial.begin (115200);
  state = NONE;
  Serial.println("Serial commands Rnnnn Snnnn Gnnnn.");
}  // end of setup

void processRPM (const unsigned int value)
{
  // do something with RPM
  Serial.print ("Z RPM = ");
  Serial.println (value);
  rpm = value;
  dirtyInteractions = true;
} // end of processRPM

void processSpeed (const unsigned int value)
{
  // do something with speed
  Serial.print ("XY Speed = ");
  Serial.println (value);
  speed = value;
  dirtyInteractions = true;
} // end of processSpeed

void processGear (const unsigned int value)
{
  // do something with gear
  Serial.print ("Garrulous/Verbose = ");
  Serial.println (value);
  garrulous = value;
  dirtyInteractions = true;
} // end of processGear

void handlePreviousState ()
{
  switch (state)
  {
    case GOT_R:
      processRPM (currentValue);
      break;
    case GOT_S:
      processSpeed (currentValue);
      break;
    case GOT_G:
      processGear (currentValue);
      break;
  }  // end of switch

  currentValue = 0;
}  // end of handlePreviousState

void processIncomingByte (const byte c)
{
  if (isdigit (c))
  {
    currentValue *= 10;
    currentValue += c - '0';
  }  // end of digit
  else
  {

    // The end of the number signals a state change
    handlePreviousState ();

    // set the new state, if we recognize it
    switch (c)
    {
      case 'R':
        state = GOT_R;
        break;
      case 'S':
        state = GOT_S;
        break;
      case 'G':
        state = GOT_G;
        break;
      default:
        state = NONE;
        break;
    }  // end of switch on incoming byte
  } // end of not digit

} // end of processIncomingByte

void loopGammonSerial ()
{
  while (Serial.available ())
    processIncomingByte (Serial.read ());

  // do other stuff in loop as required

}  // end of loop

//======================================================
//================= Interaction functions
//======================================================

void handleInteractions() {
  // These use the values from the serialGammon parsing
  // to set the speeds of the steppers and the garruloousness
  // and verbosity of the program


  if (dirtyInteractions) {
    // Mobatools takes RPM in tenths of RPM.
    Step_Z.setSpeed( rpm * 10 );
    X_oscillationSpeed = speed * 10;
    Y_oscillationSpeed = speed * 5;
    //immediate
    Step_X.setSpeed(X_oscillationSpeed);
    Step_Y.setSpeed(Y_oscillationSpeed);
    dirtyInteractions = false;
    Serial.println("[Rnn][Snnn][Gnnn]?");
  }
}

Again, this demonstraion is a quick hack made possible by the excellent non-blocking example code provided by several others.

Non-blocking code makes it easy to "combine codes". If you want to combine a couple sketches/scripts/programs, look for non-blocking exmaples to start from anf follow @robin2's excellent planning advice:

2 Likes

For many of us here the way to not do a project is to write the whole program and then go about debugging it. In my time with many hard experiences that I developed better ways, I can say that for sure the write all then fix method takes longer. The more unknowns you have, the more debug time you get. But that does tend to teach seeking more flexible code techniques so not a total loss.

Non-blocking code modules written to flex and fit together have advantages here. Each one can be written in a separate program that only deals with the module and makes it easier to test and refine along the way. The smaller program makes quicker debugging and if it needs work to fit a whole better, do it as a stand-alone module with knowledge of what it needs to be.

Consider how many libraries you might use in big code and how many of those you rewrite.. not. Most of the lines compiled into my big projects of the past; I didn't write, the libraries added.

With non-blocking code it is easier to build parts to combine and if you have a team, different members can work on different parts but CAVEAT, the spec has to be tight and well worked out!

I like modules, especially small Input and Output Task modules tailored to hardware and requirements. They make good toolbox parts, especially the generalized ones. They make changes for hardware more easy as the differences hammer down to how they interface with other parts.

The bigger final program gets down to combine, test and debug to knock unknowns out then add the next and keep unknowns to a minimum throughout the process.

The loop counter helps to show impact of changes. But with serial monitor output, it does wreck formatting. If you use an ANSI terminal or emulation, the count can fit somewhere on-screen and not scroll other output off. That's what I was used to having before, display screens with cursor control and maybe some day Arduino will have that easy but for now it takes extra steps to have pro-level display terminal IO. Not a beginner thing but not impossible, think of your monitor working more like a big LCD!

1 Like

Hello forum members

I am a great friend of the KISS philosophy. The C programming language offers the use of function pointers, which support this philosophy perfectly.
My contribution to this valuable thread provides a sketch that shows the use of function pointers for the combination of several tasks.
Just try it out and add a new task for testing.

// https://forum.arduino.cc/t/combining-multiple-non-blocking-codes/1305885/1
// area for global variable & co

// make function pointers for setups
void (*setups[])() {
  setupTask0, setupTask1, setupTask2, // add name of setup() here
};
// make function pointers for loops
void (*loops[])() {
  loopTask0, loopTask1, loopTask2, // add name of loop() here
};
// make code for application setup here
void  setupTask0()
{
  Serial.println(__func__);
}
void  setupTask1()
{
  Serial.println(__func__);
}
void  setupTask2()
{
  Serial.println(__func__);
}
// make code for application loop here
void  loopTask0()
{
  Serial.println(__func__);
}
void  loopTask1()
{
  Serial.println(__func__);
}
void  loopTask2()
{
  Serial.println(__func__);
}
//---------------------------------------------------------------------------------------
void setup()
{
  Serial.begin(115200);
  Serial.println("combining-multiple-non-blocking-codes");
  for (auto setup : setups) setup();
}

void loop ()
{
  for (auto loop : loops) loop(), delay(1000); // delay for testing only
}
//---------------------------------------------------------------------------------------

Have a nice day and enjoy coding in C++.

1 Like

and then you put in a delay()

oi

// delay for testing only
1 Like

That's cool.

Oh, and you could use namespaces or classes or something to insulate the support functions for each setup/loop pair from the others pairs.

Hello DaveX

This example provides a caller function. Each task is able to call a task as needed.

// https://forum.arduino.cc/t/combining-multiple-non-blocking-codes/1305885/1
// area for global variable & co
// make task names
enum TaskName {TaskZero, TaskOne, TaskTwo};
uint8_t taskName=TaskZero; // start task
// make function pointers for setups
void (*setups[])() {
  setupTask0, setupTask1, setupTask2, // add name of setup() here
};
// make function pointers for loops
uint8_t (*loops[])() {
  loopTask0, loopTask1, loopTask2, // add name of loop() here
};
// make code for application setup here
void setupTask0()
{
  Serial.println(__func__);
}
void  setupTask1()
{
  Serial.println(__func__);
}
void  setupTask2()
{
  Serial.println(__func__);
}
// make code for application loop here
uint8_t loopTask0()
{
  Serial.println(__func__);
  return TaskOne;
}
uint8_t loopTask1()
{
  Serial.println(__func__);
  return TaskTwo;
}
uint8_t loopTask2()
{
  Serial.println(__func__);
  return TaskZero;
}

void setup()
{
  Serial.begin(115200);
  Serial.println("combining-multiple-non-blocking-codes via a caller ");
  for (auto setup : setups) setup();
}

void loop ()
{
  taskName = loops[taskName](), delay(1000); // delay for testing only
}

Have a nice day and enjoy coding in C++.