Skip to main content
Engineering LibreTexts

23.1: delay()

  • Page ID
  • or How to Waste Time

    Sometimes our code needs to wait for things or time events. For example, we might want to turn an LED on for a few seconds and then turn it off. We’ve seen how to control an LED with digitalWrite() but how do we wait for a few seconds? One simple method is to create an empty loop. This is a loop that really does nothing but waste time. For example, if we know that simply incrementing, testing and branching in a loop takes a microsecond, we could write a function like this:

    void CheesyDelay( unsigned long msec )
        volatile unsigned long i;
        unsigned long endmsec = msec * 1000;
        for( i=0; i<endmsec; i++ );

    Note that we specify the number of milliseconds we’d like to waste. Since each iteration of the loop takes one microsecond, we multiply by 1000 to achieve milliseconds. The volatile modifier is important here. This tells the compiler not to aggressively optimize the code for us because I could be changed by code running elsewhere (for example, in an interrupt). Otherwise, the compiler might figure out that it can achieve the same end result by ignoring the loop and doing a simple addition. The problem with this function is that the resulting delay is highly dependent on the microcontroller used and its clock frequency. If you just need a quick and dirty delay this will work fine, but a far more accurate delay is available with the delay() function and its sibling delayMicroseconds(), whose reference material is repeated below.

    Figure \(\PageIndex{1}\): delay docs



    Pauses the program for the amount of time (in miliseconds) specified as parameter. (There are 1000 milliseconds in a second.)


    delay( ms )


    ms: the number of milliseconds to pause (unsigned long)



    int ledPin = 13;                       // LED connected to digital pin 13
    void setup()
        pinMode(ledPin, OUTPUT);           // sets the digital pin as output
    void loop()
        digitalWrite(ledPin, HIGH);        // sets the LED on
        delay(1000);                       // waits for a second
        digitalWrite(ledPin, LOW);         // sets the LED off
        delay(1000);                       // waits for a second

    While it is easy to create a blinking LED with the delay() function, and many sketches use short delays for such tasks as switch debouncing, the use of delay() in a sketch has significant drawbacks. No other reading of sensors, mathematical calculations, or pin manipulation can go on during the delay function, so in effect, it brings most other activity to a halt. For alternative approaches to controlling timing see the millis() function and the sketch sited below. More knowledgeable programmers usually avoid the use of delay() for timing of events longer than 10’s of milliseconds unless the Arduino sketch is very simple.

    Certain things do go on while the delay() function is controlling the Atmega chip however, because the delay function does not disable interrupts. Serial communication that appears at the RX pin is recorded, PWM (analogWrite) values and pin states are maintained, and interrupts will work as they should.

    See also




    Pauses the program for the amount of time (in microseconds) specified as parameter. There are a thousand microseconds in a millisecond, and a million microseconds in a second.

    Currently, the largest value that will produce an accurate delay is 16383. This could change in future Arduino releases. For delays longer than a few thousand microseconds, you should use delay() instead.




    us: the number of microseconds to pause (unsigned int)



    Caveats and Known Issues

    This function works very accurately in the range 3 microseconds and up. We cannot assure that delayMicroseconds will perform precisely for smaller delay-times.

    As of Arduino 0018, delayMicroseconds() no longer disables interrupts.

    These functions are also tied in with two other functions, micros() and millis(), which are repeated below:

    Figure \(\PageIndex{2}\): millis and micros docs



    Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.




    Number of milliseconds since the program started (unsigned long)


    Note that the parameter for millis is an unsigned long, errors may be generated if a programmer tries to do math with other datatypes such as ints




    Returns the number of microseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 70 minutes. On 16 MHz Arduino boards (e.g. Duemilanove and Nano), this function has a resolution of four microseconds (i.e. the value returned is always a multiple of four). On 8 MHz Arduino boards (e.g. the LilyPad), this function has a resolution of eight microseconds.




    Number of microseconds since the program started (unsigned long)

    All of these functions rely on the Arduino system configuring the timers the moment the board is reset. One of these will be used to generate an interrupt when the counter overflows. The time to overflow will take a predetermined amount of time based on the clock speed. The interrupt will in turn update three global variables that will keep track of how long the program has been running.

    First let’s consider the initialization code along with some definitions and global variable declarations. Besides this timer, the init code also sets up the other timers for pulse width modulation duties (via the analogWrite() function). The code is reasonably well commented and is presented without further explanation, save for a reminder that sbi() is a macro that will reduce to a single assembly language instruction that sets a specific register bit.

    #include "wiring_private.h"
    // the prescaler is set so that timer0 ticks every 64 clock cycles, and the
    // the overflow handler is called every 256 ticks.
    #define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
    // the whole number of milliseconds per timer0 overflow
    // the fractional number of milliseconds per timer0 overflow. we shift right
    // by three to fit these numbers into a byte. (for the clock speeds we care
    // about - 8 and 16 MHz - this doesn't lose precision.)
    #define FRACT_MAX (1000 >> 3)
    volatile unsigned long timer0_overflow_count = 0;
    volatile unsigned long timer0_millis = 0;
    static unsigned char timer0_fract = 0;
    void init()
        // this needs to be called before setup() or some functions won't
        // work there
        // set timer 0 prescale factor to 64
        sbi(TCCR0B, CS01);
        sbi(TCCR0B, CS00);
        // enable timer 0 overflow interrupt
        sbi(TIMSK0, TOIE0);
        // timers 1 and 2 are used for phase-correct hardware pwm
        // this is better for motors as it ensures an even waveform
        // note, however, that fast pwm mode can achieve a frequency of up
        // 8 MHz (with a 16 MHz clock) at 50% duty cycle
        TCCR1B = 0;
        // set timer 1 prescale factor to 64
        sbi(TCCR1B, CS11);
        sbi(TCCR1B, CS10);
        // put timer 1 in 8-bit phase correct pwm mode
        sbi(TCCR1A, WGM10);
        // set timer 2 prescale factor to 64
        sbi(TCCR2B, CS22);
        // configure timer 2 for phase correct pwm (8-bit)
        sbi(TCCR2A, WGM20);
        // set a2d prescale factor to 128
        // 16 MHz / 128 = 125 KHz, inside the desired 50-200 KHz range.
        // XXX: this will not work properly for other clock speeds, and
        // this code should use F_CPU to determine the prescale factor.
        sbi(ADCSRA, ADPS2);
        sbi(ADCSRA, ADPS1);
        sbi(ADCSRA, ADPS0);
        // enable a2d conversions
        sbi(ADCSRA, ADEN);
        // the bootloader connects pins 0 and 1 to the USART; disconnect
        // them here so they can be used as normal digital i/o;
        // they will be reconnected in Serial.begin()
        UCSR0B = 0;

    Now let’s take a look at the interrupt service routine. Each time the counter overflows (i.e. the 8 bit counter tries to increment 255 and wraps back to 0) it generates an interrupt which calls this function. Basically, all it does is increment the global variables declared earlier.

    SIGNAL( TIMER0_OVF_vect )
        // copy these to local variables so they can be stored in
        // registers (volatile vars are read from memory on every access)
        unsigned long m = timer0_millis;
        unsigned char f = timer0_fract;
        m += MILLIS_INC;
        f += FRACT_INC;
        if (f >= FRACT_MAX)
            f -= FRACT_MAX;
            m += 1;
        timer0_fract = f;
        timer0_millis = m;

    As you might now guess, all the millis() and micros() functions do is access these global variables and return their values. Because an interrupt can occur during this process, the value of the status register (SREG) is copied, the status register’s global interrupt enable bit is cleared with the cli() call, the access performed (plus a little extra calculation for micros()) and the status register returned to its prior state. The retrieved value is then returned to the caller.

    unsigned long millis()
        unsigned long m;
        uint8_t oldSREG = SREG;
        // disable interrupts while we read timer0_millis or we might get
        // an inconsistent value (e.g. in the middle of a write to
        // timer0_millis)
        m = timer0_millis;
        SREG = oldSREG;
        return m;
    unsigned long micros()
        unsigned long m;
        uint8_t t, oldSREG = SREG;
        m = timer0_overflow_count;
        t = TCNT0;
        if ((TIFR0 & _BV(TOV0)) && (t < 255)) m++;
        SREG = oldSREG;
        return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());

    So the delay() function itself is pretty straightforward. It simply retrieves the current time since reset and then goes into a “busy wait” loop, constantly checking and rechecking the time until the difference between the two reaches the requested value.

    void delay(unsigned long ms)
        uint16_t start;
        start = (uint16_t)micros();
        while (ms > 0)
            if (((uint16_t)micros() - start) >= 1000)
                start += 1000;

    In a way, this is just a slightly more sophisticated version of our initial cheesy delay function. It is more precise because it uses the accurate internal counters which are operating from a known clock frequency. The microseconds version of the delay is a little trickier, especially for short delays. This also does a busy wait but does so using in-line assembly code. Even with this, the delays are not particularly accurate for periods of only a few microseconds. In the in-line comments are instructive:

    /* Delay in microseconds. Assumes 8 or 16 MHz clock. */
    void delayMicroseconds(unsigned int us)
        // 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)
        // 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 preceding commands.
        us -= 2;
        // busy wait
        __asm__ __volatile__ (
            "1: sbiw %0,1" "\n\t" // 2 cycles
            "brne 1b" : "=w" (us) : "0" (us) // 2 cycles

    The major problem with using delay() is noted in its on-line documentation, namely, that during a busy wait loop no other work can be done. The controller is effectively “spinning its wheels”. A more effective way to delay is to make direct use of the millis() function. The basic idea is to check the time using millis() and then do what you need to do inside a loop, checking the elapsed time on each iteration. Here is a snippet of example code.

    unsigned long currentMillis, previousMillis, intervalToWait;
    // intervalToWait could be a passed variable, global or define
    // initialize to current time
    previousMillis = millis();
    currentMillis = millis();
    while ( currentMillis - previousMillis < intervalToWait )
        // do whatever you need to do here
        currentMillis = millis();

    In essence you’ve built your own “kind of” busy wait loop but with requisite code inside.