Delay without delay() and `if` function

In this sketch one function in loop() runs once per state change. Inside this function I have a one solenoid that has to be activated and then deactivated after short delay of 150 milliseconds. When state changes counter-part-solenoid has to be activated and deactivated in the same manner. If function with millis() would not work as I have it run only once per state change, this it simply skips it. How would I go about it? is there a code that can replace delay() function without halting whole sketch?

here is a quick example:

/* Please assume there is a sensorRead and triggerValue,
 * I figured these values are not important for this example. */

const int solenoid = 10;
const int solenoid1 = 12;
int passFlag = 0;

void setup() {

  pinMode ( solenoid, OUTPUT );
  pinMode ( solenoid1, OUTPUT );

}

void loop() {

   if ( (sensorRead >= triggerValue) && (passFlag == 0) ) {
      digitalWrite(solenoid, HIGH);
      delay(150); // this is the part I need to find alternative
      digitalWrite(solenoid, LOW);
      passFlag++;
  }

  if ( (sensorRead < triggerValue) && (passFlag == 1) ) {
      digitalWrite(solenoid1, HIGH);
      delay(150);
      digitalWrite(solenoid1, LOW);
      passFlag = 0;
  }

}

Using millis() with if makes much longer "delay". Since sketch is long and has other functions to run, I assume by the time it gets back to it more than 300ms has passed. Even if I lower the value it still does not make delay shorter.

Here is an example with millis()

/* Again assume there is a sensorRead and triggerValue,
 * I figured these values are not important for this example. */

const int solenoid = 10;
const int solenoid1 = 12;
int passFlag, solenoidPassFlag = 0;
unsigned long previousTime = 0;
unsigned long currentTime = millis();
unsigned long delayMillis = 150; // I have read somewhere that adding or subtracting from millis even if value is very small it still should be unsigned long to avoid some bizarre glitches. 

void setup() {

  pinMode ( solenoid, OUTPUT );
  pinMode ( solenoid1, OUTPUT );

}

void loop() {

   if ( (sensorRead >= triggerValue) && (passFlag == 0) ) {
      if ( solenoidPassFlag == 0 ) { // this is to save previous time at the moment solenoid engaged, thus preventing stuck loop
      previousTime = currentTime;
      solenoidPassFlag++;
      }
      digitalWrite(solenoid, HIGH);
      if (currentTime - previousTime >= delayMillis ) {            
         digitalWrite(solenoid, LOW);
         passFlag++;
         solenoidPassFlag = 0;
      }      
   } 

   if ( (sensorRead < triggerValue) && (passFlag == 1) ) {
      if ( solenoidPassFlag == 0 ) {
      previousTime = currentTime;
      solenoidPassFlag++;
      }
      digitalWrite(solenoid1, HIGH);
      if (currentTime - previousTime >= delayMillis ) {            
         digitalWrite(solenoid1, LOW);
         passFlag = 0;
         solenoidPassFlag = 0;
      }      
   } 

}

Would love to hear suggestions and maybe other alternatives to delay and if. Maybe while function preventing it to skip that part and come back later. Please write your suggestions.

/* Again assume there is a sensorRead and triggerValue,
   I figured these values are not important for this example. */

const byte solenoid = 10;
const byte solenoid1 = 12;
byte passFlag = 0;
unsigned long previousTime = 0;
const unsigned long delayMillis = 150; // I have read somewhere that adding or subtracting from millis even if value is very small it still should be unsigned long to avoid some bizarre glitches.

void setup() {
  pinMode ( solenoid, OUTPUT );
  pinMode ( solenoid1, OUTPUT );
}

void loop() {

  if (sensorRead >= triggerValue && passFlag == 0) {
    previousTime = millis();
    passFlag++;
    digitalWrite(solenoid, HIGH);
  }
  if (millis() - previousTime >= delayMillis && passFlag == 1) {
    digitalWrite(solenoid, LOW);
    passFlag++;
  }

  if ( sensorRead < triggerValue && passFlag == 2) {
    previousTime = millis();
    passFlag++;
    digitalWrite(solenoid1, HIGH);
  }
  if (millis() - previousTime >= delayMillis && passFlag == 3) {
    digitalWrite(solenoid1, LOW);
    passFlag = 0;
  }

}

Just tried it and it did not work. Gets stuck in solenoid, HIGH position.

I assume because once it gets to if it just skips it.

/* Again assume there is a sensorRead and triggerValue,
   I figured these values are not important for this example. */

const byte solenoid = 10;
const byte solenoid1 = 12;
byte passFlag = 0;
unsigned long previousTime = 0;
const unsigned long delayMillis = 150; // I have read somewhere that adding or subtracting from millis even if value is very small it still should be unsigned long to avoid some bizarre glitches.

void setup() {
  pinMode ( solenoid, OUTPUT );
  pinMode ( solenoid1, OUTPUT );
}

void loop() {

  if (sensorRead >= triggerValue && passFlag == 0) {
    previousTime = millis();
    passFlag++;
    digitalWrite(solenoid, HIGH);
  }
/* It just skips the code below since the criteria is not met. 
/* Since passFlag is 1 at this point it will not come back to it. 
*/
  if (millis() - previousTime >= delayMillis && passFlag == 1) {
    digitalWrite(solenoid, LOW);
    passFlag++;
  }
/* this is a counteracting solenoid and I have 8 pairs of these guys, 
/* giving value of 2+ to passFlag would complicate things IMHO.
*/
  if ( sensorRead < triggerValue && passFlag == 2) {
    previousTime = millis();
    passFlag++;
    digitalWrite(solenoid1, HIGH);
  }
  if (millis() - previousTime >= delayMillis && passFlag == 3) {
    digitalWrite(solenoid1, LOW);
    passFlag = 0;
  }

}

You're starting to get there. micros() and millis() are designed for use with interrupt driven code; you set something, record when it is happens then use a single while( test ) for when the time is up. This allows interrupts to trigger and go to other code then return to the while for another test - there is no guarantee that the return will be exactly when the while( test ) becomes FALSE, but it will usually be very close.

If you have a semi colon after the while(test ); your main program flow holts and only interrupts will run. If you have a code block after the while( test ) { code while TRUE } that code, any functions it calls AND interrupts will appear to run at the same time.

For NASA level code the timing needs to be exact, for humans though the fastest our eyes react to change is 0.04s; that's 40ms or 40,000us. So if code run in a while( test ) { block } is shorter than this a human will never know, and even 5 times that - 0.2s usually is Ok, while 25 times - 0.5s will be more than acceptable in most applications.

bye.

My bad I did not notice if's were not nested.
I tried your version and it would just cycle solenoids

would it be something like this:

digitalWrite(solenoid, HIGH);
while (millis() - previousTime >= delayMillis) {
 //do nothing... just wait
 // I really do not know what to put in here
}
digitalWrite(solenoid, LOW);

or is there while(test) command?
Where I can read more about it?

You need to record the time that the action was performed IMMEDIATELY after you do it -

  digitalWrite( solenoid, HIGH );
  previousTime = millis();
  while( millis() - previousTime > delayMillis )        // useful version
  {
         // do some other things,
         // eg, flash an LED to show that the solenoid state is temporary
         // any iternally generated interrupts (like for the timers) will now run properly,
         // any user set interrupts will also run,
         // any calls to other functions will run, then return back to the while( test )
  }
  digitalWrite( solenoid, LOW );

  digitalWrite( solenoid, HIGH );
  previousTime = millis();
  while( ( millis() - previousTime ) > delayMillis ) ;     // just do nothing, but allows interrupts

There is no need for testing equality, just do the greater-than test, it gives smaller code and executes faster and can be compensated for by setting delayMillis 1 unit shorter.

As an aside, PLEASE adopt a coding style that is more readable -
Hiding opening braces at the end of a line is awful coding, start blocks on a line of their own and finish them with a closing brace that is vertically aligned with the starting brace.
Use readable indentations, liberal brackets and white space. The compiler will throw away redundant brackets and all white space and often optimize your code to something more efficient that gets the same job done.
Also put opening brackets against the keyword - all these make for others reading the code, or even you reading it at a later date, much easier to see where blocks start and end.

In the sample code above you can see extra whitespace after each keyword (except for functions that don't have values passed to them). If the code inside the brackets needs brackets the space makes it clear where evaluation of that code starts. In the last line the mathematical evaluation of the term to the left is simple enough for the compiler to work out that it needs to be done before the comparison with the value on the right, but it is good practice to use enclosing brackets so that YOU can see the mathematical relationship at a glance.

Also note the whitespace before the semi colon in the last while( test ) ; to make it clear that no code block follows. Readability is essential for understanding code function and makes maintainence and alteration much easier.

bye

Absolutely false. They can only be used with interrupt code with restrictions. They are normally, and universally, used in mainline code.

you set something, record when it is happens then use a single while( test ) for when the time is up.

No, this is also completely wrong. The most effective and common use, is to use an 'if' test for when the time is up. Then if it isn't, the program can move on to process additional tasks.

The code for all that is showcased in the IDE example sketch, BlinkWithoutDelay. You seem to have some programming skill, but your millis() implementation is a complete miss.

1 Like

Got it! Thank you for the tips. I am used to HTML and CSS, once "Auto Format" adjusted it for me I figured - this is the way.

Now, I have tried while in my if and it did not run at all. For some reason it completely ignored whole block of code, did not even activate digitalWrite(solenoid, HIGH);

here is an excerpt o that code(properly formatted):

   if( (sensorRead >= triggerValue) && (passFlag == 0) ) 
   {
      digitalWrite(solenoid, HIGH);
      while( ( millis() - previousTime ) > delayMillis ) ;
      digitalWrite(solenoid, LOW);
      passFlag++;
   }

any idea why?

Would you be able to point to the right direction?

Previous iterations of if code did not work properly for my case.
Possibly due to the fact it run once in loop. Maybe there is a better way instead of passFlag.

loop()
{
  if( "condition is met" && passFlag == 0 )
  {
   digitalWrite(solenoid, HIGH); // activate solenoid 
   /* code to wait a little bit, while still reading other sensors and activate other solenoids. 
    * Delay of approximately 100-300ms - depending on the solenoid. */
   digitalWrite(solenoid, LOW); // deactivate solenoid
   passFlag++; // prevent solenoid cycling while "condition is met"
  }
}

I see one big problem, the sensor must read >= trigger for the timer to run
My suggestion is to make sensor reading a function of its own that updates
a status variable. Then make activating and deactivating the solenoid a
function that runs until it's done before clearing its own run flag. And
last of all a function that combines sensor status and passFlag.

Of course we assume all variables declared and initisalized.....

Literally replacing delay with timing, changed nothing else, not debugged

   if ( (sensorRead >= triggerValue) && (passFlag == 0) ) 
  {
      digitalWrite(solenoid, HIGH);
      if ( millis() - timer1Start >= timer1Wait )
      {
          timer1Start += timer1Wait;
         digitalWrite(solenoid, LOW);
        passFlag++;
    }
  }

  if ( (sensorRead < triggerValue) && (passFlag == 1) ) 
  {
      digitalWrite(solenoid1, HIGH);
      if ( millis() - timer2Start >= timer2Wait )
      {
          timer2Start += timer2Wait;
          digitalWrite(solenoid1, LOW);
          passFlag = 0;
      }
  }

I will try that.
However despite making sensor reading a function of its own. BTW how would I pass that into an if statement: if( sensorFunction() && passFlag == 0 ) ?

I believe it will ignore code after digitalWrite(solenoid, HIGH); would that be correct assumption?

The sensor function sets a variable with the last read. That variable goes in the conditional branch "if( )" statement.
if ( sensorValue > limit )....

When your code doesn't block, every function that loop runs becomes a task that can run as parallel to the others. They share data through global variables and use static variables as private data members.
This is the way to write tasking object code in plain C on single-thread computers.

This is old EE stuff. There's a whole IPO thing about tasks; Input, Process, Output.
You can have a toolbox of tasks to build sketches with.

Given this:

/* Please assume there is a sensorRead and triggerValue,
 * I figured these values are not important for this example. */

... and the fact that neither sensorRead nor triggerValue are declared, how are you guys and or gals testing this code? Are you declaring and hardcoding some values to test those inequalities in the ifs? Would make a lot more sense for the OP @dr_cranii had done that for testing purposes.

We humans can assume and judge how the code would work, but I couldn't find a switch in the compiler preferences to set the compiler to assume :wink:

Glad it wasn't just me who thought that about what @bgennette had said.

aarg - how does your 'continue on' code get back to redo the if( test ) ? A while( test ) does it automatically. If the test is for 'already passed' a value that will be fine but if the test allows other code to be performed while waiting for the condition to become TRUE then while( test ) was designed for this.

dr_cranii - if you are testing against constants that the code will never change the C language allows for pre processor definitions that the compiler substitutes in as it builds the machine code.
You add #define symbol value BEFORE any code or variable definitions.

For your program it could be something like -
#define delayMillis 2000
#define triggerValue 220
#define debug // <-- as no value is assigned this can be used to indicate TRUE

You can even do maths -
#define completionTime 60
#define warning ( completionTime - 10 )

Be carefull with the maths, it is all done as unsigned int and processed strickly left-to-right unless you supply appropriate brackets to explicitly define the calculation order. Note that these are definitions, NOT code, so they don't end in a semi colon. Defines make debuging easy, particularly where #ifdef debug .... #endif blocks print out values during testing.

Back when I first learned C (in 1990) pre processor directives were a big thing but you had to obey the rules. At the time compilers only allowed the '#' to occur in the first column, the keyword then followed with nospacing, whitespace was allowed between the keyword, symbol and value and comments were not allowed on the same line.

Once you move on to building programs with multiple modules and multiple header files then pre processor directives are essential to prevent multiple (redundant) inclusions of code and clashes of redefinitions, etc.

bye.

Because the test is done in the loop() function which does what its name suggests, ie it loops

1 Like

Ok guys, it sounds like you have only have experience of the Arduino environment, and that's fine, its what it is there for, to get you quickly into programing. But on ts own it can lead to bad program practices like have the main loop of a program do testing. The main loop should only ever ask other functions to do tests and return boolean yes/no.
Read the bible of good programing - 'Program Design' by Peter Juliff, 1st edition 1984 (before C became really popular so many examples are in cobol and pascal). Get the mechanical work out of loop and have it ONLY evaluate the results.

bye.

Have a nice day and enjoy coding in C++.
Дайте миру шанс!

If you don't want the loop() function to do the test for a period elapsing then simply call a function to do it so instead of

substitute

the test is done in in a function called from the loop() function