Udelay
From Wiki
Implementing a microsecond-accurate µdelay routine for an AVR microcontroller.
Specifically, in this example, we'll be using a Atmel AT90S2313 AVR controller running with a 6.0Mhz clock crystal.
My goal was to write delay routines in C that did microsecond and millisecond accurate delay periods, without just busy-waiting on the CPU, because I wanted to continue to be able to use the chip for other things. This code is extremely useful for coding brightness controls, one wire interfaces to temperature sensors, and other things.
So, generally, the plan was to do the following:
- Use the 8-bit Timer0 to count clock cycles (remember that at 6Mhz, 1us = 6 instructions)
- Make a small setup routine to start the timer, and poll for the rest of the delay period.
- Minimize setup overhead. This leads to being able to implement very short delay periods.
- Minimize the amount of customization needed to port to different microcontrollers and different clock speeds.
So, I implemented the udelay routine, used an Tektronix 465 Oscilliscope to measure the difference between the requested delay period, and the actual delay period. This is easily accomplished by requesting a delay of 0 microseconds. Then, I incorporated that setup delay period into my code, so that at the user level, they don't have to know about the setup period.
For this code, the setup overhead ended up being about 9 microseconds. Thats roughly 6*9 = 54 instructions in assembly. Not too bad for a C program.
You can find the whole source code, makefile, etc. here: http://www.slacy.com/avr/udelay
Basically, the code looks like this:
// This interrupt (Timer0 Overflow) will happen when the Timer0 counter
// rolls over from 255 to 0. Remember that Timer0 is an 8-bit timer.
INTERRUPT(SIG_OVERFLOW0) {
delay_done--;
};
// Routine to start the time and initialize the timer counter.
INLINE void start_timer0(const enum clockdiv div, const uint8_t inital_value) {
cli(); // stop all interrupts
// set the timer count to its initial value
outb(inital_value, TCNT0);
// set the timer clock divisor
outb(div, TCCR0);
// enable the timer overflow interrupt
sbi(TIMSK, TOIE0);
sei(); // start all interrupts, which will start the timer
}
INLINE void udelay(uint16_t us) {
// subtract off our calibration value. NOTE: You will have to
// recalculate this if you change to a different AVR microcontroller, or
// a different Mhz. Oh well, its better than nothing.
if (us<9) us=9; us-=9;
// figure out how many timer overflow cycles we should wait, as well as
// what the inital value of the timer should be.
uint16_t wait_cycles = (HZ/1000000) * us;
// delay_done is the count of how many cycles we'd like to wait. We
// initalize it here, and its decremented in the timer interrupt.
delay_done = (wait_cycles>>8) + 1;
start_timer0(US_CLOCKDIV, ((wait_cycles & 0xFF)));
// wait for delay_done to reach zero
while (delay_done);
}
