NU32v2: Counters and Timers

From Mech
Revision as of 05:22, 26 January 2011 by Lynch (talk | contribs)
Jump to navigationJump to search

Counters are 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. The pulses may come from internal events (e.g., the peripheral bus clock) or external sources.

Overview

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.

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 prescaler that determines how many pulses the timer must receive from the clock source before adding one to the counter value.

Each timer can be configured to generate interrupts at a given priority.

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 prescalers of 1:1, 1:8, 1:64, and 1:256
  • Timer B: (Timer2, Timer3, Timer4, Timer5) has the ability to form a a 32-bit timer, has prescalers of 1:1, 1:2, 1:4, 1:16, 1:32, 1:64 and 1:256 and can have event triggers.

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:

  • 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.
  • TMRx: This register stores the 16-bit counts. It resets to 0 when it reaches the number stored in PRx.
  • PRx: 16-bit value to reset TMRx.

Timer interrupts are controlled with the following SFRs:

  • TxIE: This SFR sets up the interrupt associated with the timer.
  • 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.
  • 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

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

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:

  • 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.
  • 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

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 (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.

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).

/* Timer_Simple.c
 *
 * This example toggles 2 LEDs on and off using a timer interrupt. 
 * 
 */
#include <plib.h>
					
#define SYS_FREQ  	(80000000L)		// 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_SCALAR - 1) needs to be less than 2^16-1
#define INT_FREQ	5
#define PRE_SCALAR	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_WAIT_STATES | SYS_CFG_PCACHE);
    
  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 prescalar
  OpenTimer2(T2_ON | T2_SOURCE_INT | T2_PS_1_256, SYS_FREQ/INT_FREQ/PRE_SCALAR - 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 prescalar 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 prescalar
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 prescalar. It also shows you how to create a 32-bit timer.

More Information