Example 1: Measuring execution time of a piece of code
Before any measurements can be performed the timer has to be initialized, it should be setup as a free-running timer:
TIMER0_TCR = 2; // Stop and reset timer
TIMER0_PR = 0; // increment TC every PCLK
TIMER0_MCR = 0; // Do not stop, reset or interrupt on any Match
TIMER0_TCR = 1; // Start timer
The first line sets bit 1 of the TCR , this will reset both the TimerCounter and PrescaleCounter. On the last line, resetting bit 1 while setting bit 0 will start the timer.
To measure the time, the TIMER0_TC register has to be read. Care should be taken, the C compiler may optimize the program size or speed by reshuffling the order of instructions. For example:
start = TIMER0_TC;
temp = function();
end = TIMER0_TC;
May result in the call to function() being placed after or before the assignments and after compilation the code may be executed as:
temp = function();
start = TIMER0_TC;
end = TIMER0_TC;
This is a valid optimization, since the function does not rely on the value of start (or end) the functioncall may be performed at a different moment in time. In this situation this optimization will be performed by the compiler because it results in compacter (and faster) code. Therefor the reading of the timer will be done in a function getTimer(); the order in which functioncalls are executed will never be reshuffled by the compiler.
In the main() function the measurements are placed, first I will start with the easiest possible measurement: Initialize the timer and read it twice without doing anything else, this will show how many time it takes to perform the measurement itself:
initTimer();
start = getTimer();
end = getTimer();
After execution of this code, we print the contents of these variables: start = 15 and end = 33. This shows that it takes 18 cycle from starting the timer up to the point where the time is read. Each cycle takes 16.9 ns to complete (with a14.7456 MHz crystal with a PLL mutliplier of 4) so this is 305 ns.
Next step is to do something in between these two lines. Just to get the hang of this, let's assign a value to a variable. To make sure this variable is stored in memory, define it as being volatile - this will prevent the compiler from optimizing it as a register variable or optimizing it away since it is never read:
volatile tU32 temp;
start = getTimer();
temp = 1;
end = getTimer();
The test program shows values for the timing variables like: start = 204773 and end = 204794. So it takes only 3 cycles to execute the code for assigning a value to a variable, but this does not mean that the value is indeed placed in memory at the same time - memory operations may be affected by write-back or read-ahead buffers.
In fact, this example is a bit to short to put real figures on but it does show that some time is taken by the code that is executed.
What we can see, is the amount of time taken by the printf() that can be found between the previous measurement and this one: end=33 and start = 204773 gives a time of: (start - end) * 16.9 ns = 3.4 ms. This shows you should not use the printf() in time critical functions, timing data should therefor be stored and only printed in non critical code.
Now let's do some real stuff and call a function:
start = getTimer();
temp2 = function(string);
temp = getTimer();
temp2 = strlen(string);
end = getTimer();
Function() just calculates the string length (as does strlen). Here we get values of 327 cycles (5.5 µs) for function() and 181 (3 µs) for strlen() on the same string.
For the examples above this sounds a bit too much. Why bother if a function takes 3 or 5.5 µs?
But things do matter when you start to control I/O devices that need input within a certain amount of time - or the other way around, if there must be a minimum delay. Then we also may execute code within an interrupt function which may be a recurring event (e.g. collecting data from an A/D converter). The system has to complete it's interrupt function before the next interrupt occurs otherwise data will be lost.
|