NU32v2: Counters and Timers

From Mech
Revision as of 09:02, 27 January 2011 by Lynch (talk | contribs) (→‎Overview)
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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 cascade two timers to make a single 32-bit timer (Timer 23 and Timer 45), and the ability to trigger an analog-to-digital conversion when the timer rolls over. This latter capability is useful when collecting analog input signals at fixed time intervals without CPU intervention.

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 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 prescaler, specify if the clock is internal or external and change mode to 32-bit timer. Other options can be seen in the PIC32 reference manual.
  • TMRx: This register stores the 16-bit count. It resets to 0 when it reaches the number stored in PRx.
  • PRx: 16-bit value where TMRx rolls over (resets to zero).

Counter interrupts are controlled with the following SFRs:

  • TxIE: This SFR sets up the interrupt associated with the timer. An interrupt can be generated when the counter resets.
  • 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 the priority of the interrupt associated with the timer.
  • TxIS: This SFR sets the subpriority of the interrupt.

Given a clock source (internal or external) and a prescaler (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 resets. This means that PRx determines the frequency of the interrupts. If the clock source is internal (peripheral bus), the period of the interrupt is given by

Period = [(PRx + 1) Tpb (TMR_Prescaler_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 than 2^16 - 1 if using only one timer or less than 2^32 - 1 if combining two timers. See examples 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 (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