5 Techniques to Delay Code Execution

An interesting problem that often comes up in embedded software implementation is figuring out how to delay code execution. Sometimes a developer might just want there to be a 10-microsecond delay to allow an I/O line to stabilize before reading it or may want a specified time period between reads to debounce it. In this post, we will explore five techniques for delaying code execution.

 

Technique #1 – A conditional loop

The first technique, which is probably the most used and simplest is to use a conditional loop. A conditional loop delay will often use a for, while or do while loop to execute a no operation (NOP) instruction repeatedly. For example:

for(int i = 0; i < 100000; i++)
{
   __NOP();
}

A conditional delay can be useful in a pinch but it’s hardly accurate or effective. If a developer were to adjust the clock frequency for a different operational mode such as low power operation, the delay time will be completely different. Plus, there is always a question about how much of a delay is that really? One might think it’s 100,000 instructions but each time through the loop there will be additional instructions to check the loop variables and increment i. These timing loops are just too unpredictable for use in any production code.

 

Technique #2 – Use a timer

A second technique that can be used is to leverage a hardware timer built into the microcontroller. There are often several different hardware timers that can be used to keep track of system time, generate waveforms, capture inputs and general purpose. If a developer needs a delay such as 10 microseconds, a hardware timer could be loaded with the count value that would represent 10 microseconds. In this instance, the timer would be setup as a one-shot timer. The code would start the timer and wait for the timer overflow flag to be set which would then indicate that the time has elapsed.

An abstracted version of this code could look something like the following:

Timer_Reload(DELAY_VALUE);

while(Timer_Expired() == false)
{
   __NOP();
}

This technique is much more robust than the conditional loop we looked at earlier. It’s also more portable and can be more easily tuned for the desired delay period. In fact, the API can be reused through-out the code to allow a single timer to be used for any number of delays that are required.

 

Technique #3 – Use a system tick (HAL Example)

There may be instances where a dedicated hardware timer is not available or setting up a one-shot timer is undesirable. In these circumstances, developers can leverage an onboard system tick to create the delay. Even bare-metal systems usually have a background timer that acts as a system tick so that the software has a time reference from the moment the microcontroller has started. Usually these system ticks are set to occur every 1 or 10 milliseconds in a typical system.

The system usually employs some API that allows a developer to access the current system tick such as SysTick_Get(). Developers can leverage this to create a delay similar to the following:

TimeStart = SysTick_Get();

do
{
   TimeNow = SysTick_Get();
   TimeDelta = TimeNow – TimeStart;
}while(TimeDelta < DelayTime);


Developers just need to make sure that if they do something like this that they will not run into wrap around calculation issues or other potential issues so the boundary conditions should be checked.

 

Technique #4 – Use RTOS yield function

In a more advanced system that is using a real-time operating system (RTOS), developers can leverage built in RTOS API calls for yielding a task to create a delay. For example, if a developer were using FreeRTOS, within their task they could use code like the following:

VTaskDelay(1);

 

This delay function will cause the task to yield the current task for one RTOS tick. The RTOS tick may be set to one millisecond or ten depending on the configuration. There may be a problem with using a delay mechanism like this because the task will yield the CPU for that period but there is no guarantee that the task will be the highest priority task once the system tick period has expired! The task will only run immediately after the delay if the task is the highest priority task that is ready to run so there could be some jitter to the delay time.

 

Technique #5 – Use RTOS objects

The last technique that we will discuss today is the use of other RTOS objects to delay time. If you carefully look at the API’s for objects such as semaphores, mutexes and queues within your favorite RTOS, you’ll notice that most API calls that wait will also include a delay time. This delay time can be used to cause a delay in the application as well.

Related to the RTOS objects is that most RTOS’s also include soft timers. These are software-based timers that are triggered from a running hardware timer. Techniques similar to those shown in technique #2 and technique #3 can then be used with those soft timers to create a delay in the code execution.

 

Conclusions

As we have seen in today’s post, there are several different techniques that are available to developers who want to delay their codes execution. The technique that is used will depend on the software and hardware resources available in the system. Developers can then decide how sophisticated a solution they want to use. At the end of the day though, there are certainly several mechanisms that can help to delay code execution by a defined time period.

4 thoughts on “5 Techniques to Delay Code Execution”

  1. Technique #1 – A conditional loop: “These timing loops are just too unpredictable for use in any production code.”

    Exactly. A compiler will likely (depending on -O level) optimize the loop so an overal delay will be very close to zero ;-). Disabling all compiler’s optimizations by a #pragma before/after the code may come in handy.

    Even simple MCUs has cache (1 or 2) levels built-in, so overal precission will be lower then assumed (runtime dependent).

    Also IRQs (soemtimes that’s a statistical process, for e.g. DMA, ADC, Systick, etc, or even SD card related transations started previously) will likely show up in a meantime and their processing time will be add into overal delay produced.

    Checking the functionality by a scope/probe is needed espacially when moving between MCU models (even within the same vendor/family) or compilers.

    But it may still be good as one-liner code for led-blinker-demo on new platform when we do not know or do not have working APIs for that yet.

    1. Hi Andrzej, you beat me to the punch, I too discourage the use of delays as I go by the mantra “if you’re using a delay you probably did something wrong”. But when I’m starting a new project I always create a couple delay functions (e.g. sysDelay_100us, sysDelay_1ms etc) for early work and I calibrate the loop size using an oscilloscope. You have to be careful that the delay loop values don’t change due to compiler optimizations (NOPs in a loop usually get removed so I increment the loop variable in the loop code) or hardware changes, but now you have simple test functions to verify things. It never hurts to measure code with an oscilloscope as I often learn something unexpected with delay functions.

  2. In the MIPS / PIC32 world we have a core counter that runs at 1/2 the CPU clock speed. This counter can be read and used to make a delay down to very low levels of time. For instance if your PIC32MZ clock is running at 200 MHz, the core counter will be ticking along at 100 MHz or 10 nS per tic. This counter is an excellent code profiler too since it has such fine time resolution. Too bad all MCU’s don’t have this sort of counter…

  3. Any of the techniques with a “while” loop are horribly power inefficient, and a terrible idea if you are trying to optimize for battery operation.
    Instead, write the process that requires the delay as a state machine. When you need the delay, set a timer (or something similar) and exit the state. The processor can go into sleep mode or at least do something else useful. When the timer fires, it calls the state machine which then moves to the next step in the process. State machines are VERY easy to debug and almost self-documenting, assuming you use clear names for each state, such as ‘WAIT_FOR_RELAY” or “WAIT_FOR_SERVO_COMPLETION”.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.