Using millis() in for loop. Delay is not constant

Hi, I've got a timer function that decreases the time on the LCD each time it's called, then I'm calling a function that does the "Knight Rider" sort of effect on the neopixel 8x, it takes exactly 1 second till the led moves to right and one second till it comes to left. It works fine and all but it's using the delay function that pauses everything else and I need to do some other things in the main loop too, so I looked for a way to "multithread" the functions but I found out that multithreading is impossible on the arduino uno and i can only do protothreading, for which i can't really use the for loop. So i splitted the whole for loop into multiple if statements and followed the "Blink without Delay" example. However the delay is not constant, it moves 2x slower and sometimes it moves faster and so on. I'm not really sure where i made the mistake.

prop "bomb" for airsoft games.

#include <Keypad.h>
#include <LiquidCrystal.h>
#include <Adafruit_NeoPixel.h>
#define PIN 6	 // input pin Neopixel is attached to
#define NUMPIXELS     8 // number of neopixels in strip

const byte ROWS = 4; 
const byte COLS = 3; 
unsigned long prevTime = 0;
char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};

byte rowPins[ROWS] = {A5, A4, A3,A2};
byte colPins[COLS] = {A1,A0,2};

Keypad kp = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS); 
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

//LCD setup
LiquidCrystal lcd(8, 9, 10, 11, 12, 13);

void lcdSetup(){
  lcd.begin(16,2);
  lcd.clear();
}

bool bomb;
char bCode[4];
int buzzer = 7;

//int hr,min,sec;





namespace timer{

int timeArr[6];
long timeSec;
int timeRem[3];
bool finished;

long convertTimeToMs(int *input){
  long retval = 0;
  retval += (long)input[0]*60*60;
  retval += (long)input[1]*60;
  retval += input[2];
  
  return retval;
}

void bombGetTimer(int *curTimer, int *remTime){
  	bool getInput;
  	int s=0;
    lcd.clear();
    lcd.print("Time:");
    lcd.setCursor(0,1);
    lcd.print("00:00:00");
    for(int i = 0;i<6;i++){
      
      if(i<=2 && i>1)
        s=1;
      if(s==1 && i==1)
        s=0;
      if(s==2 && i==3)
        s=1;
      if(i>=4)
        s=2;      
      lcd.setCursor(i+s,1);
      lcd.print("0");
      lcd.setCursor(i+s,1);
      lcd.blink();
      
      
      getInput = false;
      while(!getInput){
        char tempKey = kp.getKey();

          
        if(tempKey != NO_KEY && tempKey != '*' && tempKey != '#'){
          	tone(buzzer, 500,10);
            curTimer[i] = (int)tempKey-48;             	
            lcd.print(tempKey);
            getInput = true;
     
        }
        
        if(tempKey == '*' && i){   
          tone(buzzer, 500,10);
           i-=2;      
           getInput = true;
        }
      }
      lcd.noBlink();
    }
  	
   
   remTime[0] = (curTimer[0]*10)+curTimer[1];
   remTime[1] = (curTimer[2]*10)+curTimer[3];
   remTime[2] = (curTimer[4]*10)+curTimer[5];
   
  
}


bool timerLogic(long &seconds){
    lcd.setCursor(0,0);
    lcd.print("REMAINING:   "); 
    lcd.setCursor(0,1);
    if(seconds/3600<10){
      lcd.print("0");
    }
    lcd.print(seconds/3600);
    lcd.print(":");
    lcd.setCursor(3,1);
    if((seconds/60)%60<10){
      lcd.print("0");
    }
    lcd.print((seconds/60)%60);
    lcd.print(":");
    lcd.setCursor(6,1);
    if(seconds%60 < 10){
      lcd.print("0");
    }
    lcd.print(seconds % 60);
    seconds--;
    if(!seconds)
       return true;
    return false;
        
      
}

};









void bombGetCode(char *code){
  	bool getInput;
    lcd.setCursor(0,0);
    lcd.print("4 Digits code");
    lcd.setCursor(0,1);
    lcd.print("XXXX");
    for(int i = 0; i<4;i++){
      getInput = false;
      while(!getInput){
        char tempKey = kp.getKey();
        if(tempKey != NO_KEY && tempKey != '*' && tempKey != '#'){
          tone(buzzer, 500,10);
          lcd.setCursor(i,1);
          code[i] = tempKey;
          lcd.print(code[i]);
          getInput = true;
        }
        
      } 
    }
}










void runMain(){
  lcd.print("Select mode.");
  lcd.setCursor(0,1);
  lcd.print("1: Bomb | 2: -->");
  while(!bomb){
    if(kp.getKey() == '1')
      bomb = true;
  }
  if(bomb){

    timer::bombGetTimer(timer::timeArr,timer::timeRem);
    timer::timeSec = timer::convertTimeToMs(timer::timeRem);
    
    delay(1000);
  	lcd.clear();
    lcd.print("BOMB SELECTED");
    delay(500);
	bombGetCode(bCode);
  }
  
  
}


void setup(){
  
  Serial.begin(9600);
  pixels.begin();
  pinMode(buzzer, OUTPUT);
  lcdSetup();
  runMain();
  
  
  
}


void movePix(int i,unsigned long currentMillis){
      
      pixels.setPixelColor(i-1, pixels.Color(0, 0, 0));
      pixels.setPixelColor(i, pixels.Color(255, 0, 0));
      pixels.show();
      prevTime = currentMillis;
}
void backPix(int i,unsigned long currentMillis){
      
      pixels.setPixelColor(i+1, pixels.Color(0, 0, 0));
      pixels.setPixelColor(i, pixels.Color(255, 0, 0));
      pixels.show();
      prevTime = currentMillis;
}

void testStripe(int delaysec){
  unsigned long currentMillis = millis();
  static int i = 0;
  static bool rep = true;
  if(rep){
    if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==0){
        movePix(i,currentMillis);
        i++;
    }
    else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==1){
        movePix(i,currentMillis);
        i++;
    }
    else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==2){
        movePix(i,currentMillis);
        i++;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==3){
        movePix(i,currentMillis);
        i++;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==4){
        movePix(i,currentMillis);
        i++;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==5){
        movePix(i,currentMillis);
        i++;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==6){
        movePix(i,currentMillis);
        i++;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==7){
        movePix(i,currentMillis);
        timer::timerLogic(timer::timeSec);
        rep = !rep;
    }
  }
  if(!rep){

    if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==7){
        backPix(i,currentMillis);
        i--;
    }
    else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==6){
        backPix(i,currentMillis);
        i--;
    }
    else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==5){
        backPix(i,currentMillis);
        i--;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==4){
        backPix(i,currentMillis);
        i--;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==3){
        backPix(i,currentMillis);
        i--;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==2){
        backPix(i,currentMillis);
        i--;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==1){
        backPix(i,currentMillis);
        i--;
    }
      else if(currentMillis-prevTime >=delaysec/NUMPIXELS && i==0){
        backPix(i,currentMillis);
        timer::timerLogic(timer::timeSec);
        rep = !rep;
    }

  }
}



void delayPixelStripe(int delaysec){
  static bool test = true;
  unsigned long currentMillis = millis();
  if(test)
  {
    for(int i=0;i<NUMPIXELS;i++){

      pixels.setPixelColor(i-1, pixels.Color(0, 0, 0));
      pixels.setPixelColor(i, pixels.Color(255, 0, 0));
      pixels.show();

      //delay(delaysec/NUMPIXELS);
    }
  }
  
  if(!test){

	for (int i = NUMPIXELS; i --> 0; ){
    if(currentMillis - prevTime >= delaysec/NUMPIXELS){
      pixels.setPixelColor(i+1, pixels.Color(0, 0, 0));
      pixels.setPixelColor(i, pixels.Color(255, 0, 0));
      pixels.show();
      //delay(delaysec/NUMPIXELS);
	  }
  }
    
  }
  test = !test;
}



void loop(){
  unsigned long curTime = millis();
  if(bomb){
    char customKey = kp.getKey();
    if(customKey)
      Serial.println("Detected");
  //timer::finished = timer::timerLogic(timer::timeSec);
	testStripe(1000/2);
    
    

  }
  



}

You will get faster and better help if you post the code as requested by the forum guidelines. Read the forum guidelines to see how to properly post code and some good information on making a good post.
Use the IDE autoformat tool (ctrl-t or Tools, Auto format) before posting code in code tags.

Hi, the code is in the pastebin link i provided below.

You can make your own kind of "Multithreading" on an Arduino microcontroller ... And a sort of Knight Rider display is not creating a significant load to the controller. So if you encounter changes in the functionality there must be a mistake in the sketch!

The code you linked is obviously incomplete. Would you mind posting your complete code? (If yes, please copy the code using the code button
image
in the menue here!).

OK, but I will not go to an outside page and download your code. Post code here if you want help here.

2 Likes

Hi, thank you for the reply, I edited the thread with the code. It worked fine with the for loop but I couldn't detect a keypad input in the loop() function as it was delayed for 1 second.

I am afraid that you posted only a part of the code. Used libraries, constant and variable declarations are missing ...

That makes support at least difficult if sometimes not possible.

P.S.: There is for example this line

timer::timerLogic(timer::timeSec);

which depends on an external timer class ...

Updated. However i doubt it changed something as the rest isn't really important to the stripe function.

the timer::timerLogic is basically decreasing a var each time its called so nothing that changes the act of the stripe function.

Sorry, I have to quit now for some time ...

I recommend you have a look at state machines instead of using nested if-clauses

Here is an example I created some time ago:

enum {
  IDLE,
  ALLON,
  ALLOFF,
  ZEROONE,
  ZEROTWOFOUR,
  ONETHREE,
  PROGRAMMED,
  TOGGLE1,
  TOGGLE2
} BlinkType;

int BlinkState = IDLE;

struct LEDType {
  unsigned long LastMillis = 0;
  unsigned long interval   = 500;
  boolean       isOn = false;
  boolean       letBlink = true;
  int           Pin;
};

const int NoOfLEDs = 5;

LEDType LED[NoOfLEDs];

void SetLED(int No, int State) {
  LED[No].isOn = State;
  digitalWrite(LED[No].Pin, LED[No].isOn);
}


void BlinkLedNo(int No) {
  if (LED[No].letBlink) {
    if (millis() - LED[No].LastMillis > LED[No].interval) {
      LED[No].LastMillis = millis();
      LED[No].isOn = !LED[No].isOn;
      SetLED(No, LED[No].isOn);
    }
  } else {
    if (LED[No].isOn) SetLED(No, LOW);
  }
}

void BlinkLEDs() {
  for (int i = 0; i < NoOfLEDs; i++) {
    BlinkLedNo(i);
  }
}

void AllLEDs(int Value) {
  for (int i = 0; i < NoOfLEDs; i++) {
    SetLED(i, Value);
  }
}

void StateMachine() {
  switch (BlinkState) {
    case IDLE  :
      break;
    case ALLOFF :
      AllLEDs(LOW);
      BlinkState = IDLE;
      break;
    case ALLON :
      AllLEDs(HIGH);
      BlinkState = IDLE;
      break;
    case ZEROONE :
      AllLEDs(LOW);
      SetLED(0, HIGH);
      SetLED(1, HIGH);
      BlinkState = IDLE;
      break;
    case ZEROTWOFOUR :
      AllLEDs(LOW);
      SetLED(0, HIGH);
      SetLED(2, HIGH);
      SetLED(4, HIGH);
      BlinkState = IDLE;
      break;
    case ONETHREE :
      AllLEDs(LOW);
      SetLED(1, HIGH);
      SetLED(3, HIGH);
      BlinkState = IDLE;
      break;
    case TOGGLE1 :

      break;
    case PROGRAMMED :
      BlinkLEDs();
      break;
    default : break;
  }
}

void AllowAllToBlink() {
  for (int i = 0; i < NoOfLEDs; i++ ) {
    LED[i].letBlink = true;
  };
}

void DisableToBlink(int No) {
  LED[No].letBlink = false;
}


void getSerialCommand() {
  if (Serial.available()) {
    char c = Serial.read();
    switch (c) {
      case 'A' :
        BlinkState = ALLON;
        break;
      case 'a' :
        BlinkState = ALLOFF;
        break;
      case 'P' :
        AllowAllToBlink();
        BlinkState = PROGRAMMED;
        break;
      case 'I' :
        BlinkState = IDLE;  // Leaves LED states as they are at time of key pressure
        break;
      case '0' :
        BlinkState = ZEROONE;
        break;
      case '1' :
        BlinkState = ZEROTWOFOUR;
        break;
      case '2' :
        BlinkState = ONETHREE;
        break;
      case 'T' :
        AllowAllToBlink();
        DisableToBlink(0);
        DisableToBlink(2);
        DisableToBlink(4);
        BlinkState = PROGRAMMED;
        break;
      case 't' :
        AllowAllToBlink();
        DisableToBlink(1);
        DisableToBlink(3);
        BlinkState = PROGRAMMED;
        break;
      default : break;
    }
    c = ' ';
  }

}

void setup() {
  Serial.begin(115200);
  // Just a quick initialization
  for (int i = 0; i < NoOfLEDs; i++ ) {
    LED[i].interval = 300 * (i + 1);
    LED[i].Pin = 9 + i; // Do this only in this case as 9 + 4 = 13 !!!
    pinMode(LED[i].Pin, OUTPUT);
  }
  AllLEDs(HIGH);
  delay(500);
  AllLEDs(LOW);
  Serial.println("Use keys A,a, P, I, 0, 1 ,2 , T, t on Serial to switch LED control");
}


void loop() {
  getSerialCommand();
  StateMachine();
}

It is controlled by Serial commands. You can play around with it on Wokwi and check it out:

https://wokwi.com/projects/323055484656419410

And this is one which I commented:

// The different states of the stae machine are defined here
// This method allows to easily add or remove states without
// taking care of their actual value; the compiler takes care
enum {
  IDLE,
  PUMPON,
  WHILESWITCHINGON1,
  WHILESWITCHINGON2,
  WAITINGFORPRESSURE,
  ALLPUMPING,
  PUMPOFF,
  WHILESWITCHINGOFF1,
  WHILESWITCHINGOFF2,
  ALLPUMPSOFF
} PumpStates;

// All relevant data for one single pump are stored together
// in this structure. That makes it easy to add/remove "pumps"
// later in an array and to write routines in a way that
// they can be used in a for-loop by using the appropriate
// index into the array

struct PumpType {
  byte Relay1;
  byte Relay2;
  unsigned long lastMillis;
  unsigned long waitTime;
  boolean isOn;
};


// This is used instead of #define NoOfPumps 3
// The compiler will use it as fixed data but
// also make sure that this variable is not re-defined 
// by mistake later in the code
// (which could happen with e.g. #define NoOfPumps 5 at a different line)
const byte NoOfPumps = 3;

// This creates the array and presets the values of
// the structure. Each of the three lines represents 
// {Relay1, Relay2, lastMillis, waitTime, isOn}
PumpType pump[NoOfPumps] = {
  { 6, 7,  0, 5000, false},
  { 8, 9,  0, 5000, false},
  {10, 11, 0, 5000, false},
};

int Pressure = 0;                     // Holds the value of analogRead(A0) which represents "the pressure"
int MinPressure = 500;                // Pressure Threshold: If Pressure is lower than this the pumps will be switched on

boolean PressureIsLow = false;        // If true, the pressure is lower than MinPressure
boolean allPumpsOff  = true;          // If not even a single pump is on, it is true

const unsigned long Wait5s = 5000;    // Another constant for 5 sec delay time in milliseconds

const int buttonPin = 12;             // the pin number of the pushbutton pin
boolean lastButtonState = true;       // Stores the last accepted (debounced) button state
boolean ButtonIsLow = false;          // Is true, when the button is pressed (pin state = LOW)
unsigned long lastButtonChange = 0;   // Holds the time in milliseconds when the button changed its state

byte counter;                         // Unused 
int lastState, state;                 // lastState = Unused // state is the recent state of the state machine

byte FirstPump = 0;                   // Holds the number of the first pump to start with (may be 0, 1 or 2)
byte ActPump   = 0;                   // Holds the number of the pump that is handled at "this" moment


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

  //SETUP OUTPUT PINS PINWISE
  for (int i = 0; i > NoOfPumps; i++) {
    pinMode(pump[i].Relay1, OUTPUT);
    digitalWrite(pump[i].Relay1, LOW);
    pinMode(pump[i].Relay2, OUTPUT);
    digitalWrite(pump[i].Relay2, LOW);
  }

  pinMode(A0, INPUT);                   // SETUP input PINS
  pinMode(buttonPin, INPUT_PULLUP);
  counter = 1;                          // Unused, could be used to count the pump events
  state = IDLE;                         // The state machine starts usually in "IDLE" 
  delay(1000);                          // May be removed ...
}


void loop() {
  handlePressure();                     // Let's read the pressure and set the PressureIsLow variable
  handleButton();                       // Let's read the buttonState and set the ButtonIsLow variable
  StateMachine();                       // Jump into the State Machine
}


// This function calculates the number of the next
// pump after "Pump"
// if we reach the end of the array, NextPump returns 0
// to wrap around 
byte NextPump(byte Pump) {
  Pump++;
  if (Pump >= NoOfPumps) Pump = 0;
  return Pump;
}

// This function uses the boolean variables which have been
// acquired and set in loop(): PressureIsLow and ButtonIsLow
// If the Pressure is NOT Low OR the button is low
// we will switch all pumps off
// We set the ActPump to FirstPump to make sure that we start
// switching off with the same pump, we started switching on
// and then we direct the state machine to jump into case PUMPOFF
// with the next loop
void CheckButtonAndPressure() {
  if (!PressureIsLow || ButtonIsLow)  {
    ActPump = FirstPump;
    state = PUMPOFF;
  }
};

void StateMachine() {
  switch (state) {
    // IDLE is the state which will be called every loop
    // while pressure is equal or above MinPressure and
    // the button is not pressed
    // The variables PressureIsLow and ButtonIsLow are refreshed
    // in loop() already before we arrive here 
    case IDLE:
        if (PressureIsLow && !ButtonIsLow) {
          // We make sure to start with the recent FirstPump (which is No 0 after start)
          // and direct the state machine to go to PUMPON in the next loop
          ActPump = FirstPump;
          state = PUMPON;
        }
        if (!allPumpsOff && ButtonIsLow){
          // If at least one pump is running AND the Button is pressed
          // we make sure to start with FirstPump and direct to PUMPOFF
          // This will even overwrite a previous call to PUMPON state 
          // as it comes later in IDLE ...
            ActPump = FirstPump;
            state = PUMPOFF;
          }
      break;
    case PUMPON:
       // Other than IDLE, PUMPON is more a "transition state", it is
       // usually called and after doing its job it immediately directs 
       // to a following state, here: WHILESWITCHINGON1
       // This makes it easy to differentiate single actions and 
       // repetitive activities. Single actions are coded in "transition states".
       // We could use boolean variables instead in one state to make sure
       // that certain activities are only done once when entering a state
       // but the use of two states may be easier to read
       // We start a pump here, set Relay1 to HIGH,
       // save the actual time in the pump's lastMillis which will
       // be used in the next state which is a "repetitive state"
       // as we will enter it many times until the waitTime is over
       // and finally we direct the state to WHILESWITCHINGON1
        Serial.print("PUMPON No. ");
        Serial.println(ActPump + 1);
        digitalWrite(pump[ActPump].Relay1, HIGH);
        pump[ActPump].isOn = true;
        allPumpsOff = false;
        pump[ActPump].lastMillis = millis();
        state = WHILESWITCHINGON1;
      break;
    case WHILESWITCHINGON1:
        // This state is entered repetitive from loop() until waitTime is over
        // If the waitTime is expired the next state is set to WHILESWITCHINGON2
        // which is another repetitive state (with a fixed wait time of 100 msec)
        // Relay2 of the actual pump is switched on
        // and the recent time is stored again in lastMillis for the next state
        if (millis() - pump[ActPump].lastMillis >= pump[ActPump].waitTime) {
          state = WHILESWITCHINGON2;
          digitalWrite(pump[ActPump].Relay2, HIGH);
          pump[ActPump].lastMillis = millis();         
        }
      break;
    case WHILESWITCHINGON2:
        // This state is entered repetitive from loop() until 100 msec are over
        // If this wait time is expired the next state is set to WAITINGFORPRESSURE
        // which is another repetitive state with a waitTime function
        // Relay1 of the actual pump is switched off
        // and the recent time is stored again in lastMillis for the next state

        if (millis() - pump[ActPump].lastMillis >= 100) {
          digitalWrite(pump[ActPump].Relay1, LOW);
          state = WAITINGFORPRESSURE;
          pump[ActPump].lastMillis = millis();       
        }
      break;
    case WAITINGFORPRESSURE:
        // This state is entered repetitive from loop() until waitTime is over
        // If the waitTime is expired the next state is set depending on PressureIsLow
        // If the pressure is still LOW after (by default 5s), ActPump gets 
        // the number of the next pump "in row". If this is equal to the FirstPump
        // again, we have already switched on all pumps. In this case we jump to
        // state ALLPUMPING. If this is not the case, we jump back to PUMPON to switch 
        // on the next pump.
        // But, if the pressure is okay now, we can switch off all pumps again
        // and therefore set ActPump to FirstPump and direct the state to PUMPOFF
        // This way we always start to switch off with the same sequence as 
        // we switched the pumps on
        // The procedure CheckButtonAndPressure() may force the state machine to immediately
        // jump to PUMPOFF if the button is pressed; it will override any other state
        // set before in WAITINGFORPRESSURE
        // If we leave this state to ALLPUMPING, we use this to write this to Serial once
        if (millis() - pump[ActPump].lastMillis >= pump[ActPump].waitTime) {
          if (PressureIsLow) {
            ActPump = NextPump(ActPump);
            if (ActPump == FirstPump) state = ALLPUMPING;
                                else  state = PUMPON;
          } else {
            ActPump = FirstPump;
            state = PUMPOFF;
          }
        }
        CheckButtonAndPressure();
        if (state == ALLPUMPING) Serial.println("ALL PUMPS ON");
      break;
    case ALLPUMPING:
        // We arrive here if all pumps are on.
        // The only way to leave this state is 
        // if the pressure goes equal/above MinPressure or
        // the button is pressed
        CheckButtonAndPressure();
      break;
    case PUMPOFF:
        // Mainly "transition state", only repeated for each single pump:
        // Pumps which have been switched on will be switched off
        // again here in this "transition state"
        // lastMillis are set to the actual time which is used in the next state
        // If the ActPump is not On, we try the next pump until we reach FirstPump
        // again 
        if (pump[ActPump].isOn) {
          Serial.print("PUMPOFF No. ");
          Serial.println(ActPump + 1);
          digitalWrite(pump[ActPump].Relay1, HIGH);
          pump[ActPump].isOn = false;
          pump[ActPump].lastMillis = millis();
          state = WHILESWITCHINGOFF1;
        } else {
          ActPump = NextPump(ActPump);
          if (ActPump == FirstPump) state = ALLPUMPSOFF;
                              else  state = PUMPOFF;
        }
      break;
    case WHILESWITCHINGOFF1:
        // This is again a "repetitive state" to delay the activity inside
        // the if-clause for 100 msec (switching off Relay2)
        // again some lastMillis-preparation for the next repetitive state
        if (millis() - pump[ActPump].lastMillis >= 100) {
          digitalWrite(pump[ActPump].Relay2, LOW);
          state = WHILESWITCHINGOFF2;
          pump[ActPump].lastMillis = millis();       
        };
      break;
    case WHILESWITCHINGOFF2:
        // This is again a "repetitive state" to delay activities 
        // Now Relay1 is switched off and we increment (or wrap around)
        // the ActPump number. If we reach FirstPump again, we know we have
        // handled all pumps and go to ALLPUMPSOFF
        // If not, we jump back to PUMPOFF for the next pump No in ActPump
        if (millis() - pump[ActPump].lastMillis >= pump[ActPump].waitTime) {
          digitalWrite(pump[ActPump].Relay1, LOW);
          ActPump = NextPump(ActPump);
          if (ActPump == FirstPump) state = ALLPUMPSOFF;
                              else  state = PUMPOFF;
        };
      break;
    case ALLPUMPSOFF:
        // Again only a "transition state" to print once and set 
        // some variables for the loop() and the state machine
        // If all pumps are off we go back to IDLE and
        // to see what the future may bring ...
        Serial.println("ALL PUMPS OFF");
        FirstPump = NextPump(FirstPump);  // New cycle starts here ...
        state = IDLE;
        allPumpsOff = true;
        Serial.println("IDLE");
      break;
    default:
      break;
  }
}

// A typical function to debounce the button with 50 msec 
// We do only use the ButtonIsLow variable in the rest 
// of the sketch
void handleButton() {
  boolean buttonState = digitalRead(buttonPin);
  if (lastButtonState != buttonState) {
    lastButtonChange = millis();
    lastButtonState = buttonState;
  }
  if (millis() - lastButtonChange > 50) {
    ButtonIsLow = !buttonState;
  }
}


// We get the recent data from A0
// and compare Pressure with the threshold MinPressure
// If the pressure value is below MinPressure we
// set PressureIsLow to true, else it is false
void handlePressure() {
  Pressure = analogRead(A0);
  PressureIsLow = (Pressure < MinPressure);
}

Also to play around on Wokwi:

https://wokwi.com/projects/327644944575496786

Actuall this is all you need to have your 8 pixels go Knight Rider style:

void testStripe(int delaysec){
  static int step = 1;
  static unsigned long curTime = millis();
  static int i = 0;
  if (millis()-curTime > delaysec/NUMPIXELS){
    curTime = millis();
    pixels.setPixelColor(i-step, pixels.Color(0, 0, 0));
    pixels.setPixelColor(i, pixels.Color(255, 0, 0));
    pixels.show();
    i += step;
    if (i > NUMPIXELS-1) {
        i = NUMPIXELS-2;
        step = -1; 
    }
    if (i < 0) {
        i = 1;
        step = 1; 
    }
  }
}

And if you like a "fading" effect you can use this ... :wink:

void Dimm(int Place, int aStep){
  if (aStep > 0) {
     if (Place-2 >= 0) pixels.setPixelColor(Place-2, pixels.Color(0, 0, 0));
     if (Place-1 >= 0) pixels.setPixelColor(Place-1, pixels.Color(127, 0, 0));
     if (Place   >= 0) pixels.setPixelColor(Place  , pixels.Color(255, 0, 0));
  } else {
     if (Place+2 < NUMPIXELS) pixels.setPixelColor(Place+2, pixels.Color(0, 0, 0));
     if (Place+1 < NUMPIXELS) pixels.setPixelColor(Place+1, pixels.Color(127, 0, 0));
     if (Place   < NUMPIXELS) pixels.setPixelColor(Place,   pixels.Color(255, 0, 0));
  }
}


void testStripe(int delaysec){
  static int step = 1;
  static unsigned long curTime = millis();
  static int i = 0;
  if (millis()-curTime > delaysec/NUMPIXELS){
    curTime = millis();
    Dimm(i,step);
    pixels.show();
    i += step;
    if (i > NUMPIXELS-1) {
        i = NUMPIXELS-2;
        step = -1; 
    }
    if (i < 0) {
        i = 1;
        step = 1; 
    }
  }
}

To call the effect you just need to run "teststripe()" in the loop().

I managed to let your code run on Wokwi (without investigating what the purpose of your sketch is ... :wink: ):

https://wokwi.com/projects/330555581594075732

No help you with your problem but your code can be simplified significantly. E.g. instead of multiple if / else if / else

  if (rep)
  {
    if (currentMillis - prevTime >= delaysec / NUMPIXELS)
    {
      if (i == 7)
      {
        movePix(i, currentMillis);
        timer::timerLogic(timer::timeSec);
        rep = !rep;
      }
      else
      {
        movePix(i, currentMillis);
        i++;
      }
    }
  }

Yea i’ve actually already did that, i was probably super tired or something on the day i wrote the topic and for some reason i didint get that thought of just making the for loop in one if statement. Thank you all anyway!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.