Difference between revisions of "NU32v2: Counters and Timers"

From Mech
Jump to navigationJump to search
 
(45 intermediate revisions by 4 users not shown)
Line 1: Line 1:
'''THIS PAGE REFERS TO A PRE-RELEASE VERSION OF THE NU32 PIC32 DEVELOPMENT BOARD. FOR INFORMATION, SAMPLE CODE, AND VIDEOS RELATED TO THE PRODUCTION VERSION (2016 AND LATER), AND TO THE CORRESPONDING BOOK "EMBEDDED COMPUTING AND MECHATRONICS WITH THE PIC32 MICROCONTROLLER," VISIT [[NU32|THE NU32 PAGE]].'''
Timers are the simplest way to generate accurate time-based events such as flashing an LED at a given frequency. They are also useful for counting external pulses or accurate timing measurements of events.


The basic function of counters is quite simple: they count the number of pulses they have received since they were last reset (or last "rolled over"). If the pulses come from a fixed frequency clock, they can be thought of as timers, hence the words counter and timer are often used interchangeably (as in this article). I prefer to call them "counters," but Microchip's documentation mostly refers to them as timers, so we'll adopt that terminology. The pulses that are being counted may come from the internal peripheral bus clock or external sources.

== Overview ==
== Overview ==


The PIC32 is equipped with 5 16-bit timers (Timer1, Timer2, Timer3, Timer4, and Timer5). A timer increments based on a clock signal, which may come from an internal source (the peripheral bus clock, 80 MHz for us) or an external source (such as an encoder). Each timer x has an associated pin TxCK for the external source. In addition, a ''prescaler'' determines how many clock pulses must be received before the counter increments. If the prescaler is set to 1:1, the counter increments on each clock pulse; if it is set to 1:4, it increments every fourth pulse. Both the clock source type (internal and external) and the prescaler value are set in software.
The PIC32 is equipped with 5 16-bit timers (Timer1, Timer2, Timer3, Timer4, and Timer5). The basic idea of a timer is that it increments based on a clock signal.


Each 16-bit timer can count from 0 to 2^16 - 1 = 65535 = 0xFFFF, at which point it "rolls over" to 0 again. The timer can also be made to roll over at any count less than 0xFFFF by setting the period of the timer roll-over in software. We can also configure the timer to generate an interrupt when this roll-over event occurs. In this way, by using the peripheral bus clock as the clock source, we can generate precisely timed events, such as the execution of a control law in feedback control.
In software, the user selects whether the timer increments using an internal clock or an external clock. The internal clock is based on the frequency of the peripheral bus. For the NU32v2, the peripheral bus frequency is set at 80 MHz. The external clock would be used if you wanted to count external pulses such as from an encoder. Each of the timers has a pin labeled 'TXCK' that can be used for the external clock. Additionally, the user selects a prescalar that determines how many pulses the timer receives from the clock before it adds one to the existing value.


These features are common to each of the timers. However, we can separate the timers into two types: Type A and Type B. Added features of each type of timer are indicated below
Each timer can be configured to generate interrupts at a given priority.


* '''Type A Timer''': Only Timer 1 is a Type A timer, and it can take prescaler values of 1:1, 1:8, 1:64, and 1:256. A Type A timer is capable of counting external pulses ''asynchronously.'' This means that the counter can be configured to run independently of the peripheral bus clock---the counter can increment even in the absence of the PB clock. (This is in contrast to ''synchronous'' counting, where even if the events being counted are external, the counter only increments on the PB clock rising edge,i.e., the external rising edge is "synchronized" to the PB clock.) One use of asynchronous counting is to count even when the CPU is in SLEEP mode and not providing a PB clock signal. Thus a Type A timer can be used in conjunction with the Real-Time Clock and Calendar (RTCC) to keep track of seconds, minutes, hours, days, and years, even when the PIC is in SLEEP mode. To do this, the secondary oscillator pins SOSCO and SOSCI should be connected to a 32.768 kHz oscillator (2^15 pulses every second). We will rarely need the asynchronous counting capability.
The PIC32 has two types of timers:

*'''Timer A''': (Timer1) is an asynchronous timer with a built in oscillator, can operate in sleep mode and has prescalars of 1:1, 1:8, 1:64, and 1:256
* '''Type B Timer''': Timers 2, 3, 4, and 5 are Type B, and can take prescaler values of 1:1, 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, and 1:256. Type B timers add two useful capabilities: the ability to trigger an analog-to-digital conversion when the timer rolls over, and the ability to cascade two timers to make a single 32-bit timer (Timer 23 and Timer 45). The former capability is useful when collecting analog input signals at fixed time intervals without CPU intervention. The latter capability allows us to count up to 2^32 > 4 billion. In this case, the even numbered timer (Timer 2 or 4) is the master timer, and its roll-over signal is the input to the slave timer (Timer 3 or 5). Thus the even numbered timer contains the least significant 16 bits and the odd numbered timer contains the most significant 16 bits.
*'''Timer B''': (Timer2, Timer3, Timer4, Timer5) has the ability to form a a 32-bit timer, has prescalars of 1:1, 1:2, 1:4, 1:16, 1:32, 1:64 and 1:256 and can have event triggers.


== Details ==
== Details ==
The functions of the DIO pins are controlled by special function registers (SFRs). Each of these SFRs has 32 bits on the PIC32, but for many, only 16 of the bits are relevant. Depending on whether these bits are set to 0 or 1, different actions or settings are performed for Timer x:
The functions of the timers are controlled by special function registers (SFRs). Each of these SFRs has 32 bits on the PIC32, but for many, not all of the bits are relevant. Depending on whether these bits are 0 or 1, different actions or settings are performed for Timer x:
*'''TxCON''': This SFR sets up the timer. There are many different ways to configure the timers. The main options for TxCON are to turn on or off the timer, choose the pre-scalar, specify if the clock is internal or external and change mode to 32-bit timer. Other options can be seen on the PIC32 reference manual.
*'''TxCON''': This SFR configures the behavior of the timer, including whether it is on or not, the prescaler value, the source of the clock (internal or external). For Type A timers, it also indicates whether it will operate in synchronous or asynchronous mode. For even-numbered Type B timers, this SFR determines whether two timers are cascaded to create a 32-bit timer.
*'''TMRx''': This register stores the 16-bit counts. It resets to 0 when it reaches the number stored in PRx.
*'''TMRx''': This register stores the 16-bit count. It resets to 0 when the timer reaches the number stored in PRx.
*'''PRx''': 16-bit value to reset TMRx.
*'''PRx''': 16-bit "period register": the value where TMRx rolls over (resets to zero).


Timer interrupts are controlled with the following SFRs:
Counter interrupts are controlled with the following SFRs:
*'''IEC0''': Five bits of this SFR are relevant to the timers, one for each timer. If the bit is 1, the interrupt is enabled and an interrupt will be generated at timer roll-over. If the bit is 0, no interrupts will be generated. (Other bits in this SFR are reserved for other interrupt sources.)
*'''TxIE''': This SFR sets up the interrupt associated with the timer.
*'''IFS0''': Five bits of this SFR are relevant to the timers, one for each timer. If the bit is 1, the timer has generated an interrupt request. If the bit is 0, it has not. Your ISR must set this bit back to zero ("clear the flag").
*'''TxIF''': This SFR corresponds to the interrupt Flag Status bit which is either 0 or 1 depending on whether or not the interrupt has been generated.
*'''IPCx''': Three bits of this SFR specify the priority of the interrupt associated with timer x, and two bits set the subpriority of the interrupt. The priority can take values 1..7, and higher numbers mean higher priority. Thus if a timer with priority 2 generates an interrupt and its interrupt service routine (ISR) is called, then a timer with priority 4 generates an interrupt, the CPU will jump to the higher priority ISR, finish its execution, and then return to the original ISR.
*'''TxIP''': This SFR sets up the priority of the interrupt associated with the timer.
*'''TxIS''': This SFR sets up the subpriority of the interrupt.


Given a clock source (internal or external) and a prescalar (1:n), the timer increments by 1 every n pulses from the clock. The timer is reset to zero when the timer value equals the PRx value. If interrupts are enabled, the timer interrupt is generated when the timer is reset. This means that PRx determines the frequency of the interrupts. If the clock source is internal (peripheral bus), the period of the timer interrupt is given by
If interrupts are enabled, a timer interrupt is generated when the timer rolls over. Thus if the clock input is a fixed frequency clock (like the PB clock), then the period register PRx, together with the timer prescaler (N >=1), determines the frequency of the interrupts. The period of the interrupt is
Period = (PRx+1)*Tpb*N,
where Tpb is the period of the peripheral bus. Thus for an 80 MHz PB clock, a maximum prescaler value of N=256, and a 16-bit timer, the maximum timer period between interrupts is 65536*(1/80 M)*256 = 0.21 seconds. For a 32-bit timer, it increases to nearly 4 hours.


More details can be found in Section 14 of the Reference Manual.
Period = [(PRx + 1) Tpb (TMR_Prescalar_value)],
where Tpb is the period of the peripheral bus.

This means that if we want to specify the frequency of the interrupt, we need to determine PRx. Note that PRx needs to be less that 2^16 - 1 if using only 1 timer or less than 2^32 - 1 if combining two timers. See example below.


== Library Functions ==
== Library Functions ==
The peripheral library offers a number of function calls to simplify setting up and using the Timers. The declarations of these functions can be found in pic32-libs/include/peripheral/timer.h
The peripheral library offers a number of function calls to simplify setting up and using the timers. The declarations of these functions can be found in pic32-libs/include/peripheral/timer.h.


Each Timer X (ie replace X with the timer number you wish to use) has the following functions:
Each timer X (replace X with the timer number you wish to use) has the following functions:
*OpenTimerX(config, period): where config is a value selected by using | with the constants given in the header file. period is the desired PRx value which determines the roll-over // interrupt frequency.
*OpenTimerX(config, period): where config is a value selected by using bitwise OR | with the constants given in the header file. config chooses whether the time is on or off, the prescaler, the source (internal or external), and whether this timer is the master timer of a 32-bit timer pair. period is the desired PRX value which determines the roll-over period.
*CloseTimerX(): this disables the timer
*CloseTimerX(): this disables the timer
*ConfigIntTimerX(config): this function configures the interrupt based on config which is a value selected by using | with the constants given in the header file.
*ConfigIntTimerX(config): this function configures the interrupt based on config which is a value selected by using | with the constants given in the header file.
Line 43: Line 45:
*ReadTimerX(): returns the value of the timer
*ReadTimerX(): returns the value of the timer
*WriteTimerX(value): loads a given value to the timer
*WriteTimerX(value): loads a given value to the timer
*ReadPeriodX(): returns the PRx value
*ReadPeriodX(): returns the PRX value
*WritePeriodX(value): loads a given value to the PRx register
*WritePeriodX(value): loads a given value to the PRX register


Timers 2 and 3 as well as 4 and 5 can be combined to create 32-bit timers. If using a 32-bit timer, replace the X in the functions above with 23 or 45.
Timers 2 and 3 as well as 4 and 5 can be combined to create 32-bit timers. If using a 32-bit timer, replace the X in the functions above with 23 or 45.
Line 51: Line 53:


=== Accurate Timing ===
=== Accurate Timing ===

Here's a simple example of code to time how many cycles it takes to compute a code snippet (provided it's less than 2^16 cycles; otherwise we need to cascade two timers).

<pre>
int elapsed; float time; int i;
OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_8, 0xFFFF); // will overflow in 1/(80000000Hz/8/65535) = 0.0066s

WriteTimer2(0);
for (i=0;i<10000;i++) {}
elapsed = ReadTimer2();
time = (float)elapsed / 80000000.0 * 8.0;
sprintf(RS232_Out_Buffer, "counting to 10000 takes %d timer cycles \tor %f s\r\n", elapsed, time);
WriteString(UART3, RS232_Out_Buffer);
</pre>


=== Time-based Interrupts ===
=== Time-based Interrupts ===
Line 65: Line 81:
}
}
</pre>
</pre>
where vector refers to a constant defined in '''(find this location)''', iplx is the interrupt priority level where x is the level. yHandler can be anything you want, for example we can call the timer2 handler as Timer2Handler. There is a function associated with each interrupt that is used to clear the flag, which allows the program to return to where it was before the interrupt was generated. Without clearing the flag, the program will never exit the interrupt.
where vector refers to a constant defined in the .h file for your particular processor (p32mx795f512l.h), iplx is the interrupt priority level where x is the level (1..7, higher numbers indicating higher priority). yHandler can be anything you want, for example we can call the timer2 handler as Timer2Handler. There is a function associated with each interrupt that is used to clear the flag, which allows the program to return to where it was before the interrupt was generated. Without clearing the flag, the program will never exit the interrupt.


This example is going to toggle 2 LEDs at a given frequency using interrupts with timer 2. We are going to use the functions OpenTimer2() and ConfigIntTimer2() to initialize the timer and interrupts. You will see in the example code that there is also a function INTEnableSystemMultiVectoredInt() which is used to set up multi-vectored interrupts '''(whatever that means)'''.
This example is going to toggle 2 LEDs at a given frequency using interrupts with timer 2. We are going to use the functions OpenTimer2() and ConfigIntTimer2() to initialize the timer and interrupts. You will see in the example code that there is also a function INTEnableSystemMultiVectoredInt() which is used to set up multi-vectored interrupts.

<pre>
/* Timer_Simple.c
*
* This example toggles 2 LEDs on and off using a timer interrupt.
*
*/
#include <plib.h>
#define SYS_FREQ 80000000 // clock frequency set by bootloader
#define LED_0 LATGbits.LATG12 // mnemonic constants for LEDs on NU32v2
#define LED_1 LATGbits.LATG13

// Define constants for the interrupt frequency (Hz) and the prescalar
// Note that (SYS_FREQ / INT_FREQ / PRE_SCALER - 1) needs to be less than 2^16-1
#define INT_FREQ 5
#define PRE_SCALER 256

void initLEDs(void);

//int count = 0;

int main(void) {
// SYSTEMConfig() optimizes performance for our clock frequency.
// in Microchip\MPLAB C32\pic32-libs\include\peripheral\system.h
SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL);
initLEDs(); // set LED pins as outputs

LED_0 = 1; // low output turns an LED on; see NU32v2 circuit
LED_1 = 0; // high output turns an LED off
// configure Timer 2 using internal clock with given prescaler
OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_256, SYS_FREQ/INT_FREQ/PRE_SCALER - 1);

// set up the timer interrupt with a priority of 2
ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);

// enable multi-vector interrupts
INTEnableSystemMultiVectoredInt();

while(1) { // infinite loop
}
return 0;
} // end main

/* initLEDs()
*
* initLEDs sets RG12 and RG13 to outputs to control the LEDs on the NU32v2.
*/
void initLEDs(void) {
LATG |= 0x3000; // Initializes G12 and G13 to 1 (off for NU32v2 board)
TRISG &= 0xCFFF; // set the two pins to inputs
}

/* ISR for Timer 2*/
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){
LED_0 = ~LED_0; // toggle LED_0
LED_1 = ~LED_1; // toggle LED_1

// clear the interrupt flag
mT2ClearIntFlag();
}
</pre>

From this example, we can see how to set up timer 2 at a frequency of 5 Hz with an interrupt priority of 2.

Now let's modify this simple time-based ISR to toggle the leds at two fixed time periods, one at period T, other at period kT, where k is an integer, by using a counter i that increments each time the ISR is called, and when i reaches k, does the less frequent code and sets i back to zero. For this example, create a global int count and initialize it to zero.

<pre> int count = 0;
</pre>

Use this ISR instead of the previous, in order to have LED0 toggle at 5 Hz and LED1 toggle at 1 Hz.
<pre>/* ISR for Timer 2*/
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){
LED_0 = ~LED_0; // toggle LED_0
count++;
if(count == 5){
LED_1 = ~LED_1; // toggle LED_1
count = 0;
}
// clear the interrupt flag
mT2ClearIntFlag();
}

</pre>

Imagine that we wanted to toggle the LEDs at 1 Hz by using a 32-bit timer. We can combine timers 2 and 3 or timers 4 and 5 to create a 32-bit timer. Let's use a prescaler of 1:1, the period register then should be PR = (1 sec) (80000000 Hz) (1) -1 = 79999999 < 2^32 - 1.

Replace the timer initialization with
<pre> // configure Timer 2 as 32-bit timer using internal clock, 1:1 prescaler
OpenTimer23(T23_ON | T23_SOURCE_INT | T23_PS_1_1, 79999999);

// set up the timer interrupt with a priority of 2
ConfigIntTimer23(T23_INT_ON | T23_INT_PRIOR_2);
</pre>

Replace the ISR with
<pre>
// ISR for Timer 23
// Note that the interrupt vector needs to be _TIMER_3_VECTOR
void __ISR(_TIMER_3_VECTOR, ipl2) Timer23Handler(void) {
LED_0 = ~LED_0; // toggle LED_0
LED_1 = ~LED_1; // toggle LED_1

// clear the interrupt flag
mT23ClearIntFlag();
}
</pre>

In summary, this example shows you how to set up a time based interrupt with a given frequency and prescaler. It also shows you how to create a 32-bit timer.

=== External Counter ===

[[Media:NU32v2_external_count_example.c | This code]] is going to set up an external counter to count the number of high pulses on the T5CK pin. The counts will be displayed in binary on the NU32v2 board and will reset after 4 button pushes. You need a simple circuit consisting of 3.3V connected to a switch connected to a resistor (1K) connected to ground. Connect a wire from T5CK to the junction of the resistor and the switch.

The main elements of this code are the initialization
<pre>
OpenTimer5( T5_ON | T5_PS_1_1 | T5_SOURCE_EXT, 0xFFFF);
</pre>
which sets up Timer 5 with an external clock source and a prescaler of 1:1. The PR value is the value that you want the counter to roll-over / generate an interrupt. We will not be setting up interrupts, so this number does not really matter.

We will read the counter with the function
<pre>
ReadTimer5()
</pre>

We will reset the counter to zero with
<pre>
WriteTimer5(0);
</pre>


== More Information ==
== More Information ==

There is another timer available for our use: the ''core timer'' associated with the CPU. This counter increments by one every two clock cycles (see Section 2 of the Reference Manual). The current value can be accessed using ReadCoreTimer(), which returns an unsigned int. We can also write a value to the counter using WriteCoreTimer(unsigned int). You can set the period of the timer in the COMPARE register using OpenCoreTimer(unsigned int), and create interrupts on the period overflow.

Latest revision as of 05:26, 16 January 2016

THIS PAGE REFERS TO A PRE-RELEASE VERSION OF THE NU32 PIC32 DEVELOPMENT BOARD. FOR INFORMATION, SAMPLE CODE, AND VIDEOS RELATED TO THE PRODUCTION VERSION (2016 AND LATER), AND TO THE CORRESPONDING BOOK "EMBEDDED COMPUTING AND MECHATRONICS WITH THE PIC32 MICROCONTROLLER," VISIT THE NU32 PAGE.


The basic function of counters is quite simple: they count the number of pulses they have received since they were last reset (or last "rolled over"). If the pulses come from a fixed frequency clock, they can be thought of as timers, hence the words counter and timer are often used interchangeably (as in this article). I prefer to call them "counters," but Microchip's documentation mostly refers to them as timers, so we'll adopt that terminology. The pulses that are being counted may come from the internal peripheral bus clock or external sources.

Overview

The PIC32 is equipped with 5 16-bit timers (Timer1, Timer2, Timer3, Timer4, and Timer5). A timer increments based on a clock signal, which may come from an internal source (the peripheral bus clock, 80 MHz for us) or an external source (such as an encoder). Each timer x has an associated pin TxCK for the external source. In addition, a prescaler determines how many clock pulses must be received before the counter increments. If the prescaler is set to 1:1, the counter increments on each clock pulse; if it is set to 1:4, it increments every fourth pulse. Both the clock source type (internal and external) and the prescaler value are set in software.

Each 16-bit timer can count from 0 to 2^16 - 1 = 65535 = 0xFFFF, at which point it "rolls over" to 0 again. The timer can also be made to roll over at any count less than 0xFFFF by setting the period of the timer roll-over in software. We can also configure the timer to generate an interrupt when this roll-over event occurs. In this way, by using the peripheral bus clock as the clock source, we can generate precisely timed events, such as the execution of a control law in feedback control.

These features are common to each of the timers. However, we can separate the timers into two types: Type A and Type B. Added features of each type of timer are indicated below

  • Type A Timer: Only Timer 1 is a Type A timer, and it can take prescaler values of 1:1, 1:8, 1:64, and 1:256. A Type A timer is capable of counting external pulses asynchronously. This means that the counter can be configured to run independently of the peripheral bus clock---the counter can increment even in the absence of the PB clock. (This is in contrast to synchronous counting, where even if the events being counted are external, the counter only increments on the PB clock rising edge,i.e., the external rising edge is "synchronized" to the PB clock.) One use of asynchronous counting is to count even when the CPU is in SLEEP mode and not providing a PB clock signal. Thus a Type A timer can be used in conjunction with the Real-Time Clock and Calendar (RTCC) to keep track of seconds, minutes, hours, days, and years, even when the PIC is in SLEEP mode. To do this, the secondary oscillator pins SOSCO and SOSCI should be connected to a 32.768 kHz oscillator (2^15 pulses every second). We will rarely need the asynchronous counting capability.
  • Type B Timer: Timers 2, 3, 4, and 5 are Type B, and can take prescaler values of 1:1, 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, and 1:256. Type B timers add two useful capabilities: the ability to trigger an analog-to-digital conversion when the timer rolls over, and the ability to cascade two timers to make a single 32-bit timer (Timer 23 and Timer 45). The former capability is useful when collecting analog input signals at fixed time intervals without CPU intervention. The latter capability allows us to count up to 2^32 > 4 billion. In this case, the even numbered timer (Timer 2 or 4) is the master timer, and its roll-over signal is the input to the slave timer (Timer 3 or 5). Thus the even numbered timer contains the least significant 16 bits and the odd numbered timer contains the most significant 16 bits.

Details

The functions of the timers are controlled by special function registers (SFRs). Each of these SFRs has 32 bits on the PIC32, but for many, not all of the bits are relevant. Depending on whether these bits are 0 or 1, different actions or settings are performed for Timer x:

  • TxCON: This SFR configures the behavior of the timer, including whether it is on or not, the prescaler value, the source of the clock (internal or external). For Type A timers, it also indicates whether it will operate in synchronous or asynchronous mode. For even-numbered Type B timers, this SFR determines whether two timers are cascaded to create a 32-bit timer.
  • TMRx: This register stores the 16-bit count. It resets to 0 when the timer reaches the number stored in PRx.
  • PRx: 16-bit "period register": the value where TMRx rolls over (resets to zero).

Counter interrupts are controlled with the following SFRs:

  • IEC0: Five bits of this SFR are relevant to the timers, one for each timer. If the bit is 1, the interrupt is enabled and an interrupt will be generated at timer roll-over. If the bit is 0, no interrupts will be generated. (Other bits in this SFR are reserved for other interrupt sources.)
  • IFS0: Five bits of this SFR are relevant to the timers, one for each timer. If the bit is 1, the timer has generated an interrupt request. If the bit is 0, it has not. Your ISR must set this bit back to zero ("clear the flag").
  • IPCx: Three bits of this SFR specify the priority of the interrupt associated with timer x, and two bits set the subpriority of the interrupt. The priority can take values 1..7, and higher numbers mean higher priority. Thus if a timer with priority 2 generates an interrupt and its interrupt service routine (ISR) is called, then a timer with priority 4 generates an interrupt, the CPU will jump to the higher priority ISR, finish its execution, and then return to the original ISR.

If interrupts are enabled, a timer interrupt is generated when the timer rolls over. Thus if the clock input is a fixed frequency clock (like the PB clock), then the period register PRx, together with the timer prescaler (N >=1), determines the frequency of the interrupts. The period of the interrupt is

Period = (PRx+1)*Tpb*N,

where Tpb is the period of the peripheral bus. Thus for an 80 MHz PB clock, a maximum prescaler value of N=256, and a 16-bit timer, the maximum timer period between interrupts is 65536*(1/80 M)*256 = 0.21 seconds. For a 32-bit timer, it increases to nearly 4 hours.

More details can be found in Section 14 of the Reference Manual.

Library Functions

The peripheral library offers a number of function calls to simplify setting up and using the timers. The declarations of these functions can be found in pic32-libs/include/peripheral/timer.h.

Each timer X (replace X with the timer number you wish to use) has the following functions:

  • OpenTimerX(config, period): where config is a value selected by using bitwise OR | with the constants given in the header file. config chooses whether the time is on or off, the prescaler, the source (internal or external), and whether this timer is the master timer of a 32-bit timer pair. period is the desired PRX value which determines the roll-over period.
  • CloseTimerX(): this disables the timer
  • ConfigIntTimerX(config): this function configures the interrupt based on config which is a value selected by using | with the constants given in the header file.
  • EnableIntTX(): enables the interrupt
  • DisableIntTX(): disables the interrupt
  • SetPriorityIntX(priority): sets the priority of the interrupt
  • ReadTimerX(): returns the value of the timer
  • WriteTimerX(value): loads a given value to the timer
  • ReadPeriodX(): returns the PRX value
  • WritePeriodX(value): loads a given value to the PRX register

Timers 2 and 3 as well as 4 and 5 can be combined to create 32-bit timers. If using a 32-bit timer, replace the X in the functions above with 23 or 45.

Sample Code

Accurate Timing

Here's a simple example of code to time how many cycles it takes to compute a code snippet (provided it's less than 2^16 cycles; otherwise we need to cascade two timers).

  int elapsed; float time; int i;
  OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_8, 0xFFFF); // will overflow in 1/(80000000Hz/8/65535) = 0.0066s

  WriteTimer2(0);
  for (i=0;i<10000;i++) {}
  elapsed = ReadTimer2();
  time = (float)elapsed / 80000000.0 * 8.0;
  sprintf(RS232_Out_Buffer, "counting to 10000 takes %d timer cycles \tor %f s\r\n", elapsed, time);
  WriteString(UART3, RS232_Out_Buffer);

Time-based Interrupts

For each interrupt, we need to write an interrupt service routine (ISR), which basically means when an interrupt is generated jump to and execute a given set of code before returning. The standard way to write an ISR is the following

void __ISR(vector, iplx) yHandler(void){

// code to execute

// clear the interrupt flag
   function to clear interrupt flag
}

where vector refers to a constant defined in the .h file for your particular processor (p32mx795f512l.h), iplx is the interrupt priority level where x is the level (1..7, higher numbers indicating higher priority). yHandler can be anything you want, for example we can call the timer2 handler as Timer2Handler. There is a function associated with each interrupt that is used to clear the flag, which allows the program to return to where it was before the interrupt was generated. Without clearing the flag, the program will never exit the interrupt.

This example is going to toggle 2 LEDs at a given frequency using interrupts with timer 2. We are going to use the functions OpenTimer2() and ConfigIntTimer2() to initialize the timer and interrupts. You will see in the example code that there is also a function INTEnableSystemMultiVectoredInt() which is used to set up multi-vectored interrupts.

/* Timer_Simple.c
 *
 * This example toggles 2 LEDs on and off using a timer interrupt. 
 * 
 */
#include <plib.h>
					
#define SYS_FREQ  	80000000		// clock frequency set by bootloader
#define LED_0		LATGbits.LATG12 // mnemonic constants for LEDs on NU32v2
#define LED_1		LATGbits.LATG13

// Define constants for the interrupt frequency (Hz) and the prescalar
// Note that (SYS_FREQ / INT_FREQ / PRE_SCALER - 1) needs to be less than 2^16-1
#define INT_FREQ	5
#define PRE_SCALER	256		

void initLEDs(void);

//int count = 0;

int main(void) {
  // SYSTEMConfig() optimizes performance for our clock frequency.
  // in Microchip\MPLAB C32\pic32-libs\include\peripheral\system.h
  SYSTEMConfig(SYS_FREQ, SYS_CFG_ALL);
    
  initLEDs(); 	// set LED pins as outputs

  LED_0 = 1; // low output turns an LED on; see NU32v2 circuit
  LED_1 = 0; // high output turns an LED off
	
  // configure Timer 2 using internal clock with given prescaler
  OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_256, SYS_FREQ/INT_FREQ/PRE_SCALER - 1);

  // set up the timer interrupt with a priority of 2
  ConfigIntTimer2(T2_INT_ON | T2_INT_PRIOR_2);

  // enable multi-vector interrupts
  INTEnableSystemMultiVectoredInt();  

  while(1) {		// infinite loop
  }
  return 0;
} // end main

/* initLEDs()
 *
 * initLEDs sets RG12 and RG13 to outputs to control the LEDs on the NU32v2.
 */
void initLEDs(void) {
  LATG |= 0x3000;		// Initializes G12 and G13 to 1 (off for NU32v2 board)
  TRISG &= 0xCFFF;	// set the two pins to inputs
				
}

/* ISR for Timer 2*/
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){
    
  LED_0 = ~LED_0; // toggle LED_0
  LED_1 = ~LED_1;	// toggle LED_1

  // clear the interrupt flag
  mT2ClearIntFlag();
}

From this example, we can see how to set up timer 2 at a frequency of 5 Hz with an interrupt priority of 2.

Now let's modify this simple time-based ISR to toggle the leds at two fixed time periods, one at period T, other at period kT, where k is an integer, by using a counter i that increments each time the ISR is called, and when i reaches k, does the less frequent code and sets i back to zero. For this example, create a global int count and initialize it to zero.

 int count = 0;

Use this ISR instead of the previous, in order to have LED0 toggle at 5 Hz and LED1 toggle at 1 Hz.

/* ISR for Timer 2*/
void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){
    
  LED_0 = ~LED_0; // toggle LED_0  
  
  count++;
	
  if(count == 5){
    LED_1 = ~LED_1;	// toggle LED_1
		count = 0;
		}
  // clear the interrupt flag
  mT2ClearIntFlag();
}

Imagine that we wanted to toggle the LEDs at 1 Hz by using a 32-bit timer. We can combine timers 2 and 3 or timers 4 and 5 to create a 32-bit timer. Let's use a prescaler of 1:1, the period register then should be PR = (1 sec) (80000000 Hz) (1) -1 = 79999999 < 2^32 - 1.

Replace the timer initialization with

 // configure Timer 2 as 32-bit timer using internal clock, 1:1 prescaler
OpenTimer23(T23_ON | T23_SOURCE_INT | T23_PS_1_1, 79999999);	

// set up the timer interrupt with a priority of 2
ConfigIntTimer23(T23_INT_ON | T23_INT_PRIOR_2);

Replace the ISR with

// ISR for Timer 23	
// Note that the interrupt vector needs to be _TIMER_3_VECTOR
void __ISR(_TIMER_3_VECTOR, ipl2) Timer23Handler(void) {
		
  LED_0 = ~LED_0; // toggle LED_0
  LED_1 = ~LED_1; // toggle LED_1

  // clear the interrupt flag
  mT23ClearIntFlag();
}

In summary, this example shows you how to set up a time based interrupt with a given frequency and prescaler. It also shows you how to create a 32-bit timer.

External Counter

This code is going to set up an external counter to count the number of high pulses on the T5CK pin. The counts will be displayed in binary on the NU32v2 board and will reset after 4 button pushes. You need a simple circuit consisting of 3.3V connected to a switch connected to a resistor (1K) connected to ground. Connect a wire from T5CK to the junction of the resistor and the switch.

The main elements of this code are the initialization

OpenTimer5( T5_ON | T5_PS_1_1 | T5_SOURCE_EXT, 0xFFFF);

which sets up Timer 5 with an external clock source and a prescaler of 1:1. The PR value is the value that you want the counter to roll-over / generate an interrupt. We will not be setting up interrupts, so this number does not really matter.

We will read the counter with the function

 ReadTimer5()

We will reset the counter to zero with

WriteTimer5(0);

More Information

There is another timer available for our use: the core timer associated with the CPU. This counter increments by one every two clock cycles (see Section 2 of the Reference Manual). The current value can be accessed using ReadCoreTimer(), which returns an unsigned int. We can also write a value to the counter using WriteCoreTimer(unsigned int). You can set the period of the timer in the COMPARE register using OpenCoreTimer(unsigned int), and create interrupts on the period overflow.