How to implement delay functions in Keil ARM MDK

How to implement delay function in Keil ARM MDK that waits given time in microseconds that can be recompiled to any reasonable clock speed defined in Keil project settings ? And all of this without using timers.

-
Why don't you use a timer? – Leon Heller Nov 24 '10 at 17:22
@Leon - I've had cases where no more timers were available. I've also had cases where the interrupt latency was greater than the desired delay time. – semaj Nov 24 '10 at 17:59
Using a timer doesn't necessarily mean using an interrupt - you can usually just read the timer directly for short timings, and that's much better than a software-timed loop. But obviously if you've run out of timers, then that's not an option. – Will Dean Nov 24 '10 at 21:01
Previously I used IAR Embedded Workbench for AVR. It has very nice intrinsic __delay_cycles() kind of macro function which compiler substituted with blocking delay of exact processor cycles time. Using #define FCPU 5000000UL for example I could implement clock independent fixed delays like this __delay_cycles(FCPU*1e-3) compiler substituted for correct 1ms delay. If I needed that part of code to work for another clock speed, I just changed FCPU and recompiled. AVR-GCC has delay.h with functions _delay_us() and _delay_ms() with simular functionality but with several limitations. – x4mer Nov 25 '10 at 6:08
@x4mer, what I have written is what they are doing in the background. They may be doing it in assembly, but it is almost the same in C. There is a decent bit of work to add something like this and get it working perfectly, this is no simple command that C has. – Kortuk Nov 27 '10 at 21:05

The best approach IMHO is to have one hardware timer which is never written to but simply free-runs. This will allow one to easily emulate an arbitrary of poll-able timers for durations up to half the length of the hardware timer. To "start" a timer, simply compute the value of the hardware timer when it should expire. To see if a timer has expired yet, subtract the expected expiration time from the current time (regarding both quantities as unsigned), and see if the (unsigned) result exceeds half the maximum timer value. If so, the timer has not expired.

Alternatively, if one wants a timer for measuring time since a certain event occurred, one can simply latch the timer value when the event occurred, and later compute the difference between the latched value and the present time.

-
Keil give an example of this in their documentation keil.com/support/man/docs/gsac/gsac_gpio.htm. I think the hardware SysTick timer is common to all Cortex-M0 and Cortex-M3, so the approach is portable. – Toby Jaffey May 31 '11 at 23:13
One problem with using SysTick for that purpose is that it is often used with a programmable period value which will often be shorter than the intervals one would want to time, and is not a power of two. On the other hand, if the rate one has a assigned for the SysTick timer would represent an acceptable precision for a longer-term timer, one can simply have SysTick itself maintain a free-running 32-bit ticks counter and use that to time other things. – supercat Jun 1 '11 at 14:40

You will have to determine the length of a NOP, then using #Defines make it so that based on clock speed it inserts the correct number.

Specifically you want to know the exact number of clock cycles it takes to get 1 loop to be completed of the style:

for(long i=0;i<NUMBER_OF_LOOPS_REQUIRED;i++)
{
__NOP();
}


By making the #define for number of loops required reconfigure itself to be automatic based on the requested delay and the clock speed you are currently running at. This can all be wrapped up in a macro.

-
On that note, when you get complete clock speed indifference in a module someone wrote it is because they spent a large amount of time designing for you to have the feature. – Kortuk Nov 24 '10 at 17:38
This will be very dependent on compiler, if you change versions for example the delay may change as well. – Thomas O Nov 24 '10 at 18:11
this would likely work better if you coded the loop in assembly too. As the optimiser might not always pick the same branch instructions – Toby Jaffey Nov 24 '10 at 20:37
@Thomas O, Yes, when you update versions of a compiler many other things can go out the window also, normally this type of thing is kept consistent, but the first step after upgrading compilers(which I would need a reason to do for a project) it testing that the system still works. – Kortuk Nov 24 '10 at 20:45
@JobyTaffey, That is an option, most of the nicer compilers, like iar.com for example, allow you to change optimization settings per file. I would disable them for this function. If you macro it in you will have to make sure your compiler does not optimize it out. IAR when set to max optimization will detect this loop and remove it. The best way to do timing is a timer function, be the asker wanted to avoid it for some reason. – Kortuk Nov 24 '10 at 20:46
#if _Main_Crystal == 25000000
#define LOOP_DELAY 400
#elif  _Main_Crystal == 16000000
#define LOOP_DELAY 256
#else
#error microsecond delay must be adjusted!
#endif
void usDelay( void )
{
for (int i = 0; i < LOOP_DELAY; i++)
{
SERVICE_WATCH_DOG();
}
}


where SERVICE_WATCH_DOG() is a macro to service the watch dog timer. This can also be replaced with a NOP.

Use a test function to figure out what your LOOP_DELAY constants need to be for each clock frequency:

void TestDelay( void )
{
SetIOPin();    // Use a scope to start measuring elapsed time here.
usDelay();
ResetIOPin();  // use a scope to end measuring elapsed time here.
}

-
Thanks, but it is obvious and inconvenient if you are writing an interface module to be compiled for unknown and various clock speed in the future. – x4mer Nov 25 '10 at 6:11
@x4mer Couldn't you just use: #Define LOOP_DELAY (_Main_Crystal / 1000000) – Madcowswe Jan 7 '12 at 0:19

A hardware timer reference is preferred because a good optimizer could remove the loop altogether or alter it based on settings. The other problem with timing loops not mentioned here is if an interrupt occurs during your timing loop, it could take much longer. Interrupts should be short anyway but not every system follows that mantra.

-