Toggling a switch without delay

Hello everyone,

I would like to know how can I toggle a switch for a specific amount of time without a delay function to waste my controllers time.

For example

valve=HIGH; // Turn on valve for a bit
delay(150);
valve=LOW; // Turn off for a bit more
 delay(400);

This valve doesn't support PWM so how can I do this without using delay function ?

1 Like

That seems to be one of the most asked questions here in the forum :wink:

The usual answer is: use millis()

Hello
Take a view into the IDE examples and find a matching sketch.
Have a nice day and enjoy coding in C++.

Thanks I forgot about millis will have a look

That has nothing to do with it

  • Detect when the input becomes HIGH (not when it is HIGH)
  • save the value of milis() ay that time as the start time
  • set a boolean to true to flag the fact that timing is taking place
  • later in loop() if the boolean is true and the current millis() - start time is greater than the required period then the period is over set the boolean to false to stop timing and do whatever you want
  • do whatever else you need in loop() but don't use any blocking code
1 Like

Here is a quick example you may check as it may give you some further hints:

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

const int NoOfLEDs = 2;

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 (millis() - LED[No].LastMillis > LED[No].interval) {
      LED[No].LastMillis = millis();
      LED[No].isOn = !LED[No].isOn;
      SetLED(No, LED[No].isOn);
    }
}

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

void setup() {
  Serial.begin(115200);
  // Just a quick initialization
  for (int i = 0; i < NoOfLEDs; i++ ) {
    LED[i].interval = 300 * (i + 1);  // Interval for 0 -> 300 msec, for 1 -> 600 msec
    LED[i].Pin = 9 + i; // Only valid for max. 5 Led, as 9 + 4 = 13 (Pin 13)
    pinMode(LED[i].Pin, OUTPUT);
  }
  SetLED(0,LOW);
  SetLED(1,LOW);

}

void loop() {
  BlinkLeds();
}

Can be tested on https://wokwi.com/arduino/projects/323845744834380370

A more "advanced" example I have written using a state machine concept is on

https://wokwi.com/arduino/projects/323055484656419410

I posted this more comprehensive code already in another thread ... Maybe it is too much for your recent purpose.

1 Like

How about something like:

#define VALVE_PIN 4
#define VALVE_ON_TIME 150
#define VALVE_OFF_TIME 400

void setup() {
  Serial.begin(115200);
  pinMode(VALVE_PIN, OUTPUT);
}

void loop() {
  static uint32_t timeCapture;
  static bool valveOn;
  if ((millis() - timeCapture) >= (valveOn ? VALVE_ON_TIME : VALVE_OFF_TIME)) {
    timeCapture = millis();
    digitalWrite(VALVE_PIN, valveOn = !valveOn);
    // The next line is for debug/workbenching only! Remove before using. Casting to 'String' is a bad idea.
    Serial.println((String)"Time(ms): " + millis() + " " + (valveOn ? "Valve On" : "Valve Off"));
  }
}

Output:

Time(ms): 400 Valve On
Time(ms): 550 Valve Off
Time(ms): 950 Valve On
Time(ms): 1100 Valve Off
Time(ms): 1500 Valve On
Time(ms): 1650 Valve Off
Time(ms): 2050 Valve On
Time(ms): 2200 Valve Off
Time(ms): 2600 Valve On
Time(ms): 2750 Valve Off

It's how one uses millis(), rather than millis() itself. The use case is almost exactly the classic https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay

...but you might need to toggle interval between 150 and 400 while you toggle your switch.

I notice that the offered examples make multiple calls to millis().

It may never make a difference, but the number returned can differ within the loop(), by 1 in the case of a nice fast loop, but changed.

I put

 unsigned long now = millis();

at the beginning of the loop, which is also BTW where I like to have any digitalRead() calls that can go there without harm.

Subsequent I use now as the current time.

I like it logically, one value, no surprises from a difference of 1 possible.

It’s nicer to read also, “now - lastTime” &c.

I se the example @DaveX links has

unsigned long currentMillis = millis();

I suppose I just don’t like to type so picked now instead.

a7

1 Like

Hello viski3118
See below the answer.
This example sketch uses class-less OOP by meaning to have objects for timing, button and led and a method to perform the desired function.

/* BLOCK COMMENT
  ATTENTION: This Sketch contains elements of C++.
  https://www.learncpp.com/cpp-tutorial/
  Many thanks to LarryD
  https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
  https://forum.arduino.cc/t/toggling-a-switch-without-delay/960308
*/
#define ProjectName "Toggling a switch without delay"
// HARDWARE AND TIMER SETTINGS
// YOU MAY NEED TO CHANGE THESE CONSTANTS TO YOUR HARDWARE AND NEEDS
constexpr byte ButtonPins[] {A0};      // portPin o---|button|---GND
constexpr byte LedPins[] {9};          // portPin o---|220|---|LED|---GND
// CONSTANT DEFINITION
enum {One};
// VARIABLE DECLARATION AND DEFINITION
unsigned long currentTime;
struct TIMER {              // has the following members
  unsigned long duration;   // memory for interval time
  unsigned long stamp;      // memory for actual time
  bool onOff;               // control for start/stop
};
struct BUTTON {             // has the following members
  byte pin;                 // port pin
  bool statusQuo;           // current state
  TIMER scan;               // see timer struct
};
struct BUTTON2LED {
  byte led;
  TIMER   wait;
  BUTTON knop;
};
BUTTON2LED button2Leds[] {
  {LedPins[One], 150, 0, false, ButtonPins[One], false, 20, 0, true},
};
// -------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
  Serial.println(F("."));
  Serial.print(F("File   : ")), Serial.println(__FILE__);
  Serial.print(F("Date   : ")), Serial.println(__DATE__);
  Serial.print(F("Project: ")), Serial.println(ProjectName);
  pinMode (LED_BUILTIN, OUTPUT);  // used as heartbeat indicator
  //  https://www.learncpp.com/cpp-tutorial/for-each-loops/
  for (auto Input : ButtonPins) pinMode(Input, INPUT_PULLUP);
  for (auto Output_ : LedPins) pinMode(Output_, OUTPUT);
}
void loop () {
  currentTime = millis();
  digitalWrite(LED_BUILTIN, (currentTime / 500) % 2);
  for (auto &button2Led : button2Leds) {
    if (currentTime - button2Led.knop.scan.stamp >= button2Led.knop.scan.duration) {
      button2Led.knop.scan.stamp = currentTime;
      int stateNew = !digitalRead(button2Led.knop.pin);
      if (button2Led.knop.statusQuo != stateNew) {
        button2Led.knop.statusQuo = stateNew;
        if (stateNew)  digitalWrite( button2Led.led, HIGH), button2Led.wait.onOff = true, button2Led.wait.stamp = currentTime;
      }
      if (currentTime - button2Led.wait.stamp >= button2Led.wait.duration && button2Led.wait.onOff )button2Led.wait.onOff = false, digitalWrite( button2Led.led, LOW);
    }
  }
}

Have a nice day, enjoy coding in C++ and feel free to reply for any help.

I'll accept and agree with this! Probably best to just take millis in once and then use it everywhere needed.

That's of course a valid comment (also that it might only be an issue in situations where one goes really down to the millisecond).

I have changed my example to take care of this (only once taking millis() per loop()), including automatically switching onTime and offTime as well as a possible offset for each single valve. The Wokwi uses two Leds instead of valves ... Could be done more sophisticated, but may show a proof of concept:

struct ValveType {
  unsigned long LastMillis = 0;
  unsigned long interval   = 150;
  unsigned long onTime     = 150;
  unsigned long offTime    = 400;
  unsigned long offSet     = 0;

  boolean       isOn = false;
  int           Pin;
};

const int NoOfValves = 2;

ValveType Valve[NoOfValves];

void SwitchIntervalOfValve(int No){
    if (Valve[No].isOn) Valve[No].interval = Valve[No].onTime + Valve[No].offSet;
                  else  Valve[No].interval = Valve[No].offTime+ Valve[No].offSet;
}


void SetValve(int No, int State) {
  Valve[No].isOn = State;
  SwitchIntervalOfValve(No);
  digitalWrite(Valve[No].Pin, Valve[No].isOn);
}

void InitValve(int No, int State, unsigned long offset = 0) {
  Valve[No].offSet = offset;
  SetValve(No, State);
}

void SwitchValveNo(int No, unsigned long now) {
    if (now - Valve[No].LastMillis > Valve[No].interval) {
      Valve[No].LastMillis = now;
      Valve[No].isOn = !Valve[No].isOn;
      SetValve(No, Valve[No].isOn);
    }
}

void SwitchValves() {
  unsigned long now = millis();
  for (int i = 0; i < NoOfValves; i++ ) SwitchValveNo(i, now);
}

void setup() {
  Serial.begin(115200);
  // Just a quick initialization
  for (int i = 0; i < NoOfValves; i++ ) {
    Valve[i].Pin = 9 + i; // Only valid for max. 5 "Valves" = Leds, as 9 + 4 = 13 (Pin 13)
    pinMode(Valve[i].Pin, OUTPUT);
  }
  InitValve(0,LOW,0);
  InitValve(1,LOW,200);

}

void loop() {
  SwitchValves();
}


See the code running on https://wokwi.com/arduino/projects/323852162461336148

Sorry, just recognized a "glitch" in the sketch, I will update this here in a minute ...

Done ...

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