Using a while() loop inside a for() loop, efficiency matter

I am wondering if below code is any diffrent then using delayMicroseconds(pulseFreq);

What i am trying to do: write a digital output HIGH in a consistent and timely manner in the scope of mili- to microseconds.
All of this inside a for loop.
When the while condition has not been met yet the for loop may not continue to the next iteration.
as soon as the while condition is met it may only execute the {} code one time.
While the condition inside the while() function is not met yet, it may not hold up the rest of the program for example inputs should still be able to be read and acted upon while this myfunc is being executed.
when i would use delay() or delayMicroseconds() that is exactl what it would do.
is this an efficient and proper way of doing this?

void myfunc() {
  unsigned long StartingMicros = micros();
  int pulseFreq = 200;
  
  for(int x = 0; x < 500; x++) 
  {
    digitalWrite(1Pin, LOW);
    while( (micros() - StartingMicros) >= (pulseFreq*x) )
    {
      digitalWrite(1Pin, HIGH);
      break;
    }
  }
}

NO.
Read the millis tutorials.

1 Like

Look at the code you posted. How is any other code being executed, when your program is stalled in the while() code???

1 Like
if (condition) {
   doSomething;
}

is much simpler than

while(condition) {
  doSomething;
  break;
}

And does not break good programming practices

The while condition is unlikely to ever be true except for the first iteration of the for loop.

thank you @david_2018 and @mancera1979 .

I have read in a related topic that this way is never going to give efficient results and i need to convert my functionality to be included in the loop, the function i was trying to make needs to be set up in a completely diffrent approach using the loop function instead of trying to do everything in that single call

As you were told in post #3.
If we could see the rest of your code, or if you presented us with a higher level description of what it is you want your program to do, advice would be forthcoming. As it is, good luck!

1 Like

If you really want to know, here is the delayMicroseconds() function

/* Delay for the given number of microseconds.  Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
void delayMicroseconds(unsigned int us)
{
	// call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable)

	// calling avrlib's delay_us() function with low values (e.g. 1 or
	// 2 microseconds) gives delays longer than desired.
	//delay_us(us);
#if F_CPU >= 24000000L
	// for the 24 MHz clock for the aventurous ones, trying to overclock

	// zero delay fix
	if (!us) return; //  = 3 cycles, (4 when true)

	// the following loop takes a 1/6 of a microsecond (4 cycles)
	// per iteration, so execute it six times for each microsecond of
	// delay requested.
	us *= 6; // x6 us, = 7 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 22 (24) cycles above, remove 5, (5*4=20)
	// us is at least 6 so we can substract 5
	us -= 5; //=2 cycles

#elif F_CPU >= 20000000L
	// for the 20 MHz clock on rare Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call takes 18 (20) cycles, which is 1us
	__asm__ __volatile__ (
		"nop" "\n\t"
		"nop" "\n\t"
		"nop" "\n\t"
		"nop"); //just waiting 4 cycles
	if (us <= 1) return; //  = 3 cycles, (4 when true)

	// the following loop takes a 1/5 of a microsecond (4 cycles)
	// per iteration, so execute it five times for each microsecond of
	// delay requested.
	us = (us << 2) + us; // x5 us, = 7 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 26 (28) cycles above, remove 7, (7*4=28)
	// us is at least 10 so we can substract 7
	us -= 7; // 2 cycles

#elif F_CPU >= 16000000L
	// for the 16 MHz clock on most Arduino boards

	// for a one-microsecond delay, simply return.  the overhead
	// of the function call takes 14 (16) cycles, which is 1us
	if (us <= 1) return; //  = 3 cycles, (4 when true)

	// the following loop takes 1/4 of a microsecond (4 cycles)
	// per iteration, so execute it four times for each microsecond of
	// delay requested.
	us <<= 2; // x4 us, = 4 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 19 (21) cycles above, remove 5, (5*4=20)
	// us is at least 8 so we can substract 5
	us -= 5; // = 2 cycles,

#elif F_CPU >= 12000000L
	// for the 12 MHz clock if somebody is working with USB

	// for a 1 microsecond delay, simply return.  the overhead
	// of the function call takes 14 (16) cycles, which is 1.5us
	if (us <= 1) return; //  = 3 cycles, (4 when true)

	// the following loop takes 1/3 of a microsecond (4 cycles)
	// per iteration, so execute it three times for each microsecond of
	// delay requested.
	us = (us << 1) + us; // x3 us, = 5 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 20 (22) cycles above, remove 5, (5*4=20)
	// us is at least 6 so we can substract 5
	us -= 5; //2 cycles

#elif F_CPU >= 8000000L
	// for the 8 MHz internal clock

	// for a 1 and 2 microsecond delay, simply return.  the overhead
	// of the function call takes 14 (16) cycles, which is 2us
	if (us <= 2) return; //  = 3 cycles, (4 when true)

	// the following loop takes 1/2 of a microsecond (4 cycles)
	// per iteration, so execute it twice for each microsecond of
	// delay requested.
	us <<= 1; //x2 us, = 2 cycles

	// account for the time taken in the preceeding commands.
	// we just burned 17 (19) cycles above, remove 4, (4*4=16)
	// us is at least 6 so we can substract 4
	us -= 4; // = 2 cycles

#else
	// for the 1 MHz internal clock (default settings for common Atmega microcontrollers)

	// the overhead of the function calls is 14 (16) cycles
	if (us <= 16) return; //= 3 cycles, (4 when true)
	if (us <= 25) return; //= 3 cycles, (4 when true), (must be at least 25 if we want to substract 22)

	// compensate for the time taken by the preceeding and next commands (about 22 cycles)
	us -= 22; // = 2 cycles
	// the following loop takes 4 microseconds (4 cycles)
	// per iteration, so execute it us/4 times
	// us is at least 4, divided by 4 gives us 1 (no zero delay bug)
	us >>= 2; // us div 4, = 4 cycles
	

#endif

	// busy wait
	__asm__ __volatile__ (
		"1: sbiw %0,1" "\n\t" // 2 cycles
		"brne 1b" : "=w" (us) : "0" (us) // 2 cycles
	);
	// return = 4 cycles
}


1 Like

it's too bad you did try running the code because you would have seen it doesn't do what you expect, much less efficiently

  • it didn't compile because 1PIN is not a valid variable name because it starts with a digit
  • the while loop will only execute when the difference -- micros() - StartingMicros is greater that the computed interval and therefore the for loop rapidly increments x
  • there's certainly no need for the break
  • making the test for the period to expire an if statement and incrementing x within it at least achieve a periodic output, but the LOW pulse is very short
void myfunc() {
    unsigned long StartingMicros = millis();
    unsigned      pulseFreq = 200;

    for(int x = 0; x < 10; ) {
        digitalWrite(Pin, LOW);
        if ((millis () - StartingMicros) >= (pulseFreq*x) ) {
            digitalWrite(Pin, HIGH);
            x++;
            Serial.println (millis ());
        }
    }
}
  • toggling the pin would generate a square wave
void myfunc() {
    unsigned long StartingMicros = millis();
    unsigned      pulseFreq = 200;
    byte          state;

    for(int x = 0; x < 10; ) {
        if ((millis () - StartingMicros) >= (pulseFreq*x/2) ) {
            state = ! state;
            digitalWrite(Pin, state);
            x++;
            Serial.println (millis ());
        }
    }
}
  • the processor hardware might be able to generate pulses without any software, but then it looks like you only want a limited # of pulses
1 Like

you are correct, I havent set up a virtual board yet to run these quick kind of tests, i still need to look into that. it's probably very simple and straightforward if one knows how to do it.
/*
EDIT: Found an online emulator service wich could work for these kinds of tests:

*/

thank you for you elaborate and understandable explanation, this certainly helped alot.
I do want only a limited amount of pulses.

if I may ask another follow up question, wich some will label a dumb question but whatever;
when doing these timechecks inside the for loop inside the callable function, when i would call the myfunc from inside the main loop, will the main loop wait its continuation to the next lines of code until the myfunc is fully completed(finished its for loop)? or will it continue even if the myfunc call hasnt been fully ended?

my guess is yes, it will, hence i had to move the entire myfunc to the loop section to fix this, that's what im going to do now.

kind regards.

myfunc() runs until complete

but you might prefer an approach that waits precisely until the period has expired

void myfunc() {
    unsigned long StartingMicros = millis();
    unsigned      pulseFreq = 200;
    byte          state;

    for(int x = 0; x < 10; ) {
        while ((millis () - StartingMicros) < (pulseFreq*x/2) )
            ;

        state = ! state;
        digitalWrite(Pin, state);
        x++;
        Serial.println (millis ());
    }
}
1 Like

Absolutely, but my timing can vary from 100's of microseconds to 10's of miliseconds so to get that accuracy I need micros() no? also the overflow event won't cause problems when all parameters in the evaluation are unsigned long, and for those that are'nt i need to use the (unsigned long) tag to identify them as one. Seeing the short rates of the pulses i will add a 2-4microsecond delay after every high pulse, to make sure they are registered that and possibly some other things but my doubts where about the efficiency

It is the for loop that is blocking, moving it won't change that.

Fortunately you don't need the for loop because we have the loop() function.
Changing to a if statement and a way to control the "x" variable makes it non blocking.
You just need the rest of the code to also be non blocking.

bool start = false;

void loop() {
  
  myfunc(); // call as often as possible

  // conditional statement to set "start" to true when desired
  if( startWanted) {
  start = true;
  }
}

void myfunc() {
  unsigned long StartingMicros = millis();
  unsigned      pulseFreq = 200;
  byte          state;
  byte  x = 10; // prevents starting until wanted    

  if(start) {
      x = 0;
      start = false;
  }

  If(x < 10) {
    if ((millis () - StartingMicros) >= (pulseFreq * x / 2) ) {
      state = ! state;
      digitalWrite(Pin, state);
      x++;
      Serial.println (millis ());
    }    
  }
}

EDIT: cleaned up the code to be clearer

You should probably know that the Arduino micros() clock only ticks once every 4 microseconds. It counts by 4, the low 2 bits are always zero. If you want to get closer, use asm NOP's at 16 per usec.

cough

why? seems you don't understand what the posted code is doing (yes replace millis() with micros())

unlike the code you posted where you intended to maintain a HIGH output for the duration of the period and momentarily set the output LOW each cycle, the posted code toggles the output each half-cycle.

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