AVR Embedded Tutorial - Delays

From Free Pascal wiki

Deutsch (de) English (en)

Delays for ATmega328 / Arduino

... and of course other AVR controllers.

Accurate timing on the AVR controllers is actually very simple - if you do it in assembler. Simple loops in Pascal, on the other hand, cannot be precisely timed, because you never know how the compiler will build the loop next time.

For example, with compiler option -o1, a for loop with a 16bit counter variable is created, the same loop with -o2 and -o3 with 8 bit. This makes the loop almost twice as fast.

Short delays in the microsecond range

 {$ goto on} // must be specified

 procedure delay_short();
 label
   loop;
 begin
   asm
     ldi r16, 60
     loop:
       nop
       dec r16
       well loop
   end ['r16'];
 end;

You have to try out the size of the counter variable. The loop lasts 4 clock cycles, so at 8MHz 2 loops would be one microsecond. In addition, there is a jump into the routine and the compiler tinkers with some stack pointer and register backup.

Longer delays in the millisecond range

 {$ goto on} // must be specified

 // pause msec, 1 to 255 msec

 procedure delay_ms(time: uint8);
 const
   fmul = 1 * fcpu div 1000000;
 label
   loop1, loop2, loop3;
 begin
   asm
     ldd r20, time
     loop1:
       ldi r21, fmul
       loop2:      // 1000 * fmul = 1000 * 1 * 8 = 8000 cycles / 8MHz
         idi r22, 250
         loop3:    // 4 * 250 = 1000 cycles
           nop
           dec r22
           well loop3
         dec r21
         well loop2
       dec r20
       well loop1
   end ['r20', 'r21', 'r22'];
 end;

With this loop you get pretty accurate timing in the range 1 to 255msec. The loop may be interrupted by interrupts and then, of course, it will take longer.

The inner loop lasts 4 clock cycles per run, ergo 1000 cycles. The middle loop makes it 1msec depending on the clock frequency (1 pass at 1Mhz, 8 passes at 8MHz, 16 passes at 16MHz). The outer loop then corresponds to the number of milliseconds.

Caution! fmul, fcpu must be constants. Then the compiler calculates the value when compiling and uses it permanently. The compiler would insert an integer division as variables and you don't really want that - in addition, the assignment ldi to a variable goes wrong.

Even longer delays

... in seconds? Really, it doesn't have to be that way. Already 50msec can be taken at startup when the controller still has to wait for the peripherals. But long delays in the program flow are bad style and there are always better options.

See also