Developing in C for the ATmega328: Marking Time and Measuring Time

5 minute read

Where I discuss marking time and measuring program execution time.

Introduction

The Arduino has two time routines, millis() and micros(). The former provides the ability to measure time in milliseconds for a long period of time (50 days) and the latter provides the ability to measure in microseconds (kinda) for a far shorter period of time, 70 minutes. The kinda arises from this comment from the arduino reference on micros()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.

AVR C Precise (nsec) Time Measurement - ticks()

The AVR_C approach is slightly different. It’s most fundamental time element is a tick, which is the clock period of the processor. On a 16MHz ATmega328P, this is 62.5 nanoseconds. To determine elapsed ticks, one needs to call ticks() twice, once before the event and once after, then subtract the former from the latter to derive the number of ticks which have passed. There are two things to understand:

  1. Dividing the number of ticks by 16 gets you 1 microsecond, which is the same as shifting to the right, 4 times. So n usecs = (ticks() » 4).
  2. There was a micros() routine which has since been deprecated. It returned the number of micros() elapsed, however it was a derivative of ticks(), and returned ticks() shifted to the right four times (see point 1.) When this was done there were rounding errors:
Testing System Ticks (1 tick = 62.5ns)
Delay of 1 ms
ticks() -> micros:      1001 1001 1001 1001 1001 1001 1001 1001 1001 1001  Complete
micros():               1003 62443 1003 1003 1003 62443 1003 1003 1003 1003  Complete

AVR C (msec) Time Measurement - ticks_ro() and millis()

The routine ticks() only counts to 65,535, so it rolls over at approximately 4.096 milliseconds. To access the roll over count, there is ticks_ro(), it tracks the number of times ticks() has rolled over (or think of it as a counter of every 4.096msecs). This counter will roll over at 65,535 4.096ms or 4.47 minutes.

You may also use millis() which provides millisecond counter which can be valuable as well.

Usage

I typically use ticks() for my execution time measurements as the processor is typically doing something which takes less than 4.096ms. I’ll use millis() to measure something in the physical world, such as reading a sensor or multi-tasking. I use ticks_ro() as a check on ticks() to ensure I haven’t began to measure something which is taking longer than 4.096ms.

All three functions will require initialization using the sysclock.h header and calling:

  • sys_clock_1() for ticks() and ticks()_ro
  • sys_clock_2()) for millis() and it is used for button debouncing

And all three functions follow the same process:

  1. call timing function before the event, (now)
  2. call timing function after the event (elapsed)
  3. subtract (now) from (elapsed) to derive the number of time elements which have passed.

As an example, here is ticks() to time math calculations in the mapping entry and math entry. It is an example of how I demonstrated accuracy and speed of execution in the math entry. (all lines shown are required):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include "sysclock.h"
	uint16_t now;
	uint16_t elapsed;
	uint16_t exec_time;
	sys_clock_1();

	now = ticks();
	pow_32_uf = (uint32_t)rand1 * (rand1 * rand2 * rand2);
	elapsed = ticks();
	exec_time = ((elapsed - now) >> 4);
	printf("32 bit test(%3u): (u)%3u * (%3u * %3u * %3u) = %8lu\n",\
	    exec_time, rand1, rand1, rand2, rand2, pow_32_uf);

  • Line 10: I am shifting to the right by 4 for a divide by 16 to arrive at microseconds.

I recommend taking a similar approach when you are attempting to evaluate either execution speed or an optimal approach. For example, in the math entry, I found that while float delivers the right answer, it can be as much as 8X slower than a 32-bit unsigned calculation. In situations where I might want to sample and calculate at a fast rate, this is critically important.

Using millis()

For completeness, I want to include an example of using millis(). In this case, I’ll use millis() to demonstrate that both millis() and delay() will result in the same amount of time. For example when I time a delay for 1000ms or 1 second, millis() will report 1000ms have elapsed. Note: With a 3-4ms overhead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// millis - demonstrate time counter using a system clock
#include <avr/io.h>
#include <stdio.h>
#include "uart.h"
#include "delay.h"
#include "sysclock.h"

int main (void)
{
    // init_sysclock_2 is required to initialize the counter for millis()
    init_sysclock_2 ();
    init_serial();
    uint16_t delay_time = 1000;

    printf("Testing millis()\n");

    for (;;)  {         
        uint16_t prior_ticks = millis();
        delay(delay_time);
        uint16_t delta_ticks = millis() - prior_ticks;

        printf("Delay time was %u and delta in millis() was %u\n", delay_time, delta_ticks);
    }
    return (0);
}

Using ticks() and ticks_ro()

Here is the example showing how to use both ticks() and ticks_ro(). Using them together provides a clock which is far more precise than micros() and allows for greater accuracy.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ticks() clock test
// ticks() is a high speed clock which counts at 62.5ns per tick or 16ticks/1us
// At this rate, it is easy for a counter to rollover, as the counter is 16bits
// ticks_ro() counts the number of rollovers and it is also 16bits
// Between ticks() and ticks_ro(), one can count to approx 4.5minutes
// Use the wait time below to better understand the relationship between ticks()
// and ticks_ro()

// Example times:
// for wait = 1: ticks_ro delta: 0 + ticks delta: 16059
// for wait = 4: ticks_ro delta: 0 + ticks delta: 64122
// for wait = 10: ticks_ro delta: 2 + ticks delta: 29178
// for wait = 100: ticks_ro delta: 24 + ticks delta: 28962
// for wait = 1000: ticks_ro delta: 244 + ticks delta: 26760
 
#include <stdio.h>
#include "uart.h"
#include "sysclock.h"
#include "delay.h"


int main(void) {    

    init_serial();
    init_sysclock_1 ();
    uint16_t wait = 1000;
    int16_t value;

    puts("ticks() and ticks_ro() test loop");
    printf("Initial test is for %u seconds\n", wait);
    while(1)
    {
        uint16_t delta = 0;
        uint16_t delta_ro = 0;
        uint16_t now = ticks();
        uint16_t now_ro = ticks_ro();
        delay(wait);
        uint16_t elapsed = ticks();
        uint16_t elapsed_ro = ticks_ro();
        delta = now > elapsed ? 65535 - now + elapsed : elapsed - now;
        delta_ro = now > elapsed ? elapsed_ro - now_ro - 1 : elapsed_ro - now_ro;

        printf("for wait = %u: ticks_ro delta: %u + ticks delta: %u\n",\
            wait, delta_ro, delta);        

        puts("enter the number of milliseconds, you wish to test (<9999)");
        scanf("%4i", &value);
        wait = value;
    } 
    return 0;
}

Comments powered by Talkyard.