Delay Without Delay

Hi,
I am doing a very timing specific project, and was wondering how to delay an Arduino board without using the delay or delayMicroseconds functions. And I'm not just talking about using millis or micros. I want to use code that the delay functions themselves use, because I'm guessing that directly coding in specific clock cycle monitoring would be more efficient than using the pre-built functions.
So how can I do it?
Thanks.

Might as well keep with the flow ( very common question ) and point out the age old example.

Check out the blink without delay sketch provided in your IDE

bwd.png

I want to use code that the delay functions themselves use

Feel free to look at the source code for delay() and delayMicroseconds().

Hello indeed

I am doing a very timing specific project

Can you tell us more about the project? What length periods are you measuring, and what precision and accuracy do you need?

Regards

Ray

Yes I did look for the delay source code in my Arduino installation folder, but I failed to find it.
What I'm doing is toggling pins on and off with precise timing and am already using direct port manipulation, used to drive some of my transformer and electromagnetic pulse experiments.
@pYro_65 no I don't think you quite understand me, I want to directly monitor clock cycles to set when to next execute commands, not just stop using delay. And then, millis and micros are still both pre-built functions that monitor clock cycles...
And I know that the code will differ for each atmega, so the code will need to suit the 328p.
Thanks

wiring.c:

void delay(unsigned long ms)
{
	uint16_t start = (uint16_t)micros();

	while (ms > 0) {
		if (((uint16_t)micros() - start) >= 1000) {
			ms--;
			start += 1000;
		}
	}
}

/* Delay for the given number of microseconds.  Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
	// 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 >= 20000000L
	// for the 20 MHz clock on rare Arduino boards

	// for a one-microsecond delay, simply wait 2 cycle and return. The overhead
	// of the function call yields a delay of exactly a one microsecond.
	__asm__ __volatile__ (
		"nop" "\n\t"
		"nop"); //just waiting 2 cycle
	if (--us == 0)
		return;

	// 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

	// account for the time taken in the preceeding commands.
	us -= 2;

#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 yields a delay of approximately 1 1/8 us.
	if (--us == 0)
		return;

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

	// account for the time taken in the preceeding commands.
	us -= 2;
#else
	// for the 8 MHz internal clock on the ATmega168

	// for a one- or two-microsecond delay, simply return.  the overhead of
	// the function calls takes more than two microseconds.  can't just
	// subtract two, since us is unsigned; we'd overflow.
	if (--us == 0)
		return;
	if (--us == 0)
		return;

	// the following loop takes half of a microsecond (4 cycles)
	// per iteration, so execute it twice for each microsecond of
	// delay requested.
	us <<= 1;
    
	// partially compensate for the time taken by the preceeding commands.
	// we can't subtract any more than this or we'd overflow w/ small delays.
	us--;
#endif

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

indeed:
Yes I did look for the delay source code in my Arduino installation folder, but I failed to find it.

One word: grep

How can anyone function without it?

How can anyone function without it?

I thought it was gorp we couldn't get along without.

Grep? (Isn't that some linux bash command?)

Anyway, so I'm still confused as to how it actually does the delayMicroseconds function. Because it seems to me as though it just executes a command that takes exactly 2 cycles, and uses that as a timing reference. Or am I just not on the right track? (Which is quite likely).
Perhaps you could give me a sample of code that would delay for a second?
Thanks

Perhaps you could give me a sample of code that would delay for a second?

Plus or minus how many nanoseconds? If your goal is to achieve better accuracy than delay() or delayMicrseconds(), you need to start with define what accuracy is needed, and how you will determine whether or not you have achieved that accuracy.

Otherwise, the simple answer is:

delay(1000);

Well I just said 1 second as an example, but really I want a delay of 1 clock cycle. I know that's probably not possible but ideally I'd like a delay as small as possible without having no delay.

Looks like you could copy the inline assembler from delaymicroseconds for a single nop.

True but

  1. I'm not exactly sure how the delayMicroseconds function works
  2. what if I want to change the delay to 3 cycles.
    However, I have come to realise that what I am wanting might not be possible. Because even if I have a 1 cycle delay between on and off, the CPU going back to the top of the loop might take a few cycles. So if I want 50% duty cycle on a 1/16000000 of a second pulse, it might not be possible...

I know that's probably not possible but ideally I'd like a delay as small as possible without having no delay.

Read the comments in the delayMicrosecond code. It shows how many clock cycles are needed for several instructions. Combine them to get the number of clock cycles you need.

A nop is a single clock cycle. Though how you can possibly need a 62.5 nanosecond delay is beyond me.

Well thanks everyone for the help I'll check out that page again and see what I can do.
Thanks

Yes I did misread your post :cold_sweat:

As paul said nop is 1 cycle:

__asm__ volatile ("nop");

If you have enough flash space left over, why not go as accurate as possible and inline as many 'nop' operations as you need!

You can use a macro to duplicate nops, or the method below works with any instruction that produces side effects. I used sbi on the unused GPIOR0 register. It produces a 2 cycle delay. As it stands, it does not work with nop instructions.

The loop uses this method and delays 10 cycles.

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif  

template< unsigned N > struct Delay2{ 
  Delay2() { sbi( GPIOR0, 1 ); }
  Delay2< N - 1 > delay2;
};
template<> struct Delay2< 0 >{ };

void setup(){}

void loop() {
  Delay2< 5 > delay10;
}

It produces:

000000a8 <loop>:
  a8:	f1 9a       	sbi	0x1e, 1	; 30
  aa:	f1 9a       	sbi	0x1e, 1	; 30
  ac:	f1 9a       	sbi	0x1e, 1	; 30
  ae:	f1 9a       	sbi	0x1e, 1	; 30
  b0:	f1 9a       	sbi	0x1e, 1	; 30
  b2:	08 95       	ret