You are on page 1of 11

NDSU

Timer2 Interrupts

ECE 376

Loop Timing
Background:
The execution time for routines sometimes needs to be set. This chapter loops at several ways to set the sampling rate. Example: Write a routine which increments an 8-bit counter every 10 ms and sends this to PORTC Assembler Solution
org goto 0 Start // Subroutine Definitions void Wait(void) { unsigned int X; for (X=0; X<5000; X++) {} }

C Solution

;********************************* ; 10ms wait routine ;********************************* Wait Loop1 Loop2 movlw movwf movlw movwf 60 CNT2 138 CNT3

nop nop nop decfsz CNT3 goto Loop2 decfsz CNT2 goto Loop1 return

;********************************* ; Main Routine ;******************************* Start bcf bsf clrf bcf clrf incf movf movwf call goto STATUS,RP1 STATUS,RP0 TRISC STATUS,RP0 COUTNER COUNTER,F COUNTER,W PORTC Wait Main

// Main Routine void Main(void) { unsigned char COUNTER; COUNTER = 0; TRISC = 0; do { COUNTER += 1; PORTC = COUNTER; Wait(); } while (1>0); }

Main

JSG

- 1 -

rev 10/10/02

NDSU

Timer2 Interrupts

ECE 376

In assembler, you can compute the exact time the wait routine takes:

#clocks = ((6 138 + 5) 60 + 5) = 49, 985


200ns time = (49, 985 clocks) clock = 9.997ms

In C, you aren't sure exactly how long the routine takes since you don't know exactly how the code compiles. Trial and error can be used to tweek the number 5,000 Note that The timing is slightly off. This clock will be off by 26 seconds per day The routine is rather inefficient. 99.95% of the time is spent in a wait loop. Only 0.05% of the time is spent in the main routine. This is much less accurate in C since you aren't sure exactly how your C code compiles.

TIMER2 Interrupts
One way to improve the efficiency of this program is to use interrupts. Interrupts are similar to subroutines except that Subroutines are routines called by software (such as the 10ms wait loop from before) Interrupts are routines called by hardware (such as a certain time elapses) Three types of timing interrupts are available on the PIC chip: TIMER0: Count external events (such as turn on an LED when 10,000 pulses are received.) TIMER1: Monitor time between signal transitions (to measure the speed of a motor, say) TIMER2: Interrupt after a certain amount of time. Control the sampling rate for a pulse-width modulation (PWM) output.

Default for the PIC is to disable interrupts. You must set up the interrupt (enable and conditions for the interrupt) if you want to use them. If an interrupt occurs, The present instruction is completed The processor inserts a call 0x04 into the program At address 0x04, the interrupt service routine must be placed (or a goto InteruptService needs to be placed. This routine must Save the W and STATUS register. Since you don't know where in the program the interrupt will be called, W and STATUS may be important. Clear TMR2IF. This tells the PIC that the present interrupt has been serviced. If you don't, the interrupt will be called immediately upon return, essentially halting the processor. (optional) Do something Restore the W and STATUS registers, and Terminate with retfie for a return from interrupt.

JSG

- 2 -

rev 10/10/02

NDSU
TIMER2 SCALAR INITIALIZATION
To enable a Timer2 interupt, you need to Set T2E (in register T2CON) Set PEIE (in register INTCON) Set GIE (in register INTCON) Set TMR2IE (in register PIE1)

Timer2 Interrupts

ECE 376

T2CON (0x012) INTCON (0x00B) PIE1 (0x08C

7 7 GIE 7
PSPIE

6 A3 6

5 A2 5

4 A1 4

3 A0 3

2 T2E 2

1 C1 1

0 C0 0

PEIE T0IE INTE RBIE T0IF INTF RBIF 6


ADIE

5
RCIE

4
TXIE

3
SSPIE

2
CCP1IE

1
TMR2IE

0
TMR1IE

The rate at which the TIMER2 interrupts happen (with a 20MHz crystal / 5MHz clock) is

Time = (A B C) 0.2us
A, B, and C are defined by registers T2CON and PR2: T2CON (0x012) 7 6 A3 5 A2 4 A1 3 A0 2 T2E 1 C1 0 C0

T2E = 1 (enable Timer 2 interrupts), T2E=0 (Disable Timer2 Interrupts. PR2 (0x92) The scalar values are PostScalar A
A3:A2:A1:A0

7 B7

6 B6

5 B5

4 B4

3 B3

2 B2

1 B1

0 B0

Main Scalar B A 1 2 B7:B0 0000 0000 0000 0001 1111 1110 1111 1111 B 1 2 255 256 00 01 10 11

Prescalar C C1:C0 C 1 4 16 16

0000 0001 1110 1111

15 16

The maximum time you can set for a Timer2 interrupt is


JSG - 3 rev 10/10/02

NDSU
= 13.107ms (at 20MHz) For a 2ms interrupt rate,

Timer2 Interrupts

ECE 376

A x B x C = (16) x (256) x (16) = 65,536 clocks

A B C = (2ms) (5, 000, 000 clock/second) A x B x C = 10,000 One combination which works is C = 01 (x 4) B = 249 (x 250) A=9 or PR2 = 249 T2CON = 0x4D (x 10)

T2CON (0x012) (A=9, C=1)

7 0

6 A3 1

5 A2 0

4 A1 0

3 A0 1

2 T2E 1

1 C1 0

0 C0 1

JSG

- 4 -

rev 10/10/02

NDSU
Assembler Solution
org goto org goto 0 Start 4 IntServe

Timer2 Interrupts

ECE 376

Example: Write a routine which increments an 8-bit counter every 10 ms and sends this to PORTC C Solution
// Global Variables unsigned char COUNTER;

;********************************* ; Timer2 Interrupt Service Routine ;********************************* IntServe movwf swapf movwf incf bcf swapf movwf swapf swapf retfie Init_Timer2 W_TEMP STATUS,W STATUS_TEMP COUNTER,F PIR1,TMR2IF STATUS_TEMP.W STATUS W_TEMP,F W_TEMP,W

// Subroutine Declarations void interrupt timer2(void) @ 0x10 { COUNTER += 1; TMR2IF = 0; }

bcf STATUS,RP1 bsf STATUS,RP0 movlw 249 movwf PR2 bcf STATUS,RP0 movlw 0x4D movlw T2CON return bcf STATUS,RP1 bsf STATUS,RP0 clrf TRISC bcf STATUS,RP0 return call call bsf Init_Ports Init_Timer2 INTCON,GIE COUNTER,W PORTC Main

void Timer2_Init(void) { T2CON = 0x4D; PR2 = 249; TMR2IE = 1; PEIE = 1; TMR2ON = 0; } void Main(void) { TRISC = 0; Timer2_Init();

Init_Ports

Start

GIE = 1; do { PORTC = COUNTER; }

Main

movf movwf goto

note: In the main routine, COUNTER is sent to PORTC. Nothing in the main routine changes COUNTER. It appears to change by 'magic' (i.e. an interrupt changes COUNTER)

JSG

- 5 -

rev 10/10/02

NDSU

Timer2 Interrupts

ECE 376

The flow chart for this program is a little difficult to draw since two routines are running in parallel: The main routine which sends COUNTER to PORTC and repeats The Timer2 routine which increments COUNTER every 2ms.

Two parallel flow charts may be the best way to represent this:

Main Routine

Interrupt Service Routine Start Every 2ms

Start

Set PortC to output

Save W & STATUS

Initialize Timer2 Interrupts for 2ms

Increment COUNT (do stuff)

Clear Interrupt Flag Turn On Interrupts Restore W & STATUS Copy COUNT to PORTC

Return from Interrupt

Note that The main routine simply watches COUNTER and sends it to PORTC. The Interrupt routine is responsible for changing COUNTER every 2ms Moroover: The shaded parts of the interupt routine are common to any interrupt service routine. Only the conditions under which this routine is called (set up in the main routine) and what you do when it is called change.

JSG

- 6 -

rev 10/10/02

NDSU

Timer2 Interrupts

ECE 376

Interrupt Constraints
Background:
Timer2 interrupts are a way to keep track of time. The clock on the PIC is 5MHz (clock = Fosc/4 = 20MHz crystal / 4) Every N clocks, a Timer2 interrupt is triggered

N=A*B*C A, C are from T2CON B is from PR2


When the interrupt is triggered, the main routine stops and you run the interrupt service routine. If you plot time on the X axis, the processor is then running as follows:
Main routine halted Running the interrupt service routine In Main Routine In Main Routine Interrupt In Main Routine Interrupt In Main Routine

Timer2 Interrupt is triggered every N clocks

Once set up, the main routine has no control over the timing: hardware triggers the interrupt every N clocks. Note however, that the interrupt service routine is stealing clocks from the main routine. You can't steal more than 100%. You probably don't want to steal more than 50%.

With interrupts turned on, you essentially have 2 (or more) programs running in parallel: The main routine which supposedly does stuff, The interrupt routines which handle administration every time an event occurs. This creates several possible constraints:

Timing Constraints:
The interrupt service routine
; -------------------------------------------; TIMER2 ; Timer2 Interrupt Service Routine ; Every 2ms this routine is called. ; Each time, COUNTER is incremented. ; -------------------------------------------Timer2 movwf swapf movwf W_TEMP STATUS,W STATUS_TEMP
- 7 -

save W and STATUS

JSG

rev 10/10/02

NDSU
incf bcf swapf movwf swapf swapf retfie

Timer2 Interrupts

ECE 376

COUNTER,F PIR1, TMR2IF STATUS_TEMP,W STATUS W_TEMP,F W_TEMP,W

do stuff

; when done, clear int. flag ; restore W and STATUS

; and return from interrupt

or in C:
void interrupt timer2(void) @ 0x10 { PORTC += 1; TMR2IF = 0; }

This is much more efficient than the wait loop which wastes 10,000 clocks to wait 2ms. Instead, interrupts use 14 cycles: 4 cycles to call the subroutine and return (equivalent to two goto statements) 9 cycles with administration (save and restore W, etc.) 1 cycle doing stuff (increment COUNTER)

Since the processor can only be doing one thing at a time, the interrupt service routine 'steals' 14 cycles from the main routine every time it is called. Since it is called every 2ms, then every 2 ms 14 cycles are spent on the interrupt routine, (0.14% of the time) 9,986 cycles are spent in the main routine. (99.86% of the time) Turning on the interrupt makes the main routine 'appear' to run 0.14% slower. note that this percentage depends upon how frequently you call this interrupt service routine: Interrupt Frequency 500Hz 5kHz 50kHz 500kHz Interrupt Period 2ms 200us 20us 2us Cycles / Interrupt 14 14 14 14 Main Routine 9,986 986 86 -4 Processor 'Speed' 99.86% 98.6% 86% 0%

The faster you interrupt, the more accurate your clock. The faster you interrupt, however, the slower the main routine appears to run. Moreover, if you interrupt too frequently, you spend all of the time servicing the interrupt and never get to the main routine. This results in the following rule:
JSG - 8 rev 10/10/02

NDSU

Timer2 Interrupts

ECE 376

Keep interrupt service routines short. The interrupt service routine should take much less time to execute than the rate at which it is called.

JSG

- 9 -

rev 10/10/02

NDSU
Multiple Interrupts:

Timer2 Interrupts

ECE 376

Several types of interrupts are available on the PICF876 chip. All interrupts cause a call 0x04 command to be inserted into your code. To distinguish what type of event triggered the interrupt, you need to check the Interrupt Request Flags: TMR2IF (in PIR1): Timer2 interrupt request: TMR1IF (in PIR1): Timer1 interrupt request ADIF etc. (in PIR1): A/D conversion complete interrupt request

If several interrupts are enabled, it is probably best to turn off interrupts during an interrupt. GIE is automatically cleared on an interrupt call. GIE is reset on retfie command. Each interrupt uses up one (or two following the text example) levels of the system stack. Since you only have 8 levels of stack available, it may be wise to turn off interrupts when servicing an interrupt to avoid a stack overflow.

Two ways to check for multiple interrupts follow. Note from these The priority for each interrupt is determined by which flag you check first. The former is a little messier, but only uses one level f the stack (leaving the main routine with 7 levels) The latter is cleaner, using subroutine calls, but uses two levels of the stack (leaving only six for the main routine).

JSG

- 10 -

rev 10/10/02

NDSU

Timer2 Interrupts

ECE 376

; Interrupt Service Routine. Uses 1 level of the stack


void interrupt IntServe(void) @ 0x10 { if (TMR2IF) { // if Timer2 interrupt // insert Timer2 stuff TMR2IF = 0; } if (ADIF) { // if an A/D interupt // insert A/D stuff ADIF = 0; } if (TMR0IF) { // if a Timer0 interupt insert Timer0 stuff TMR0IF = 0; } }

Or, a neater way which uses two levels of the system stack (leaving only six for the main routine - as recommended in the text) ; Interrupt Service Routine. Uses 2 level of the stack (and may crash)
void interrupt IntServe(void) @ 0x10 { if (TMR2IF) { // if Timer2 interrupt Timer_Routine(); // call a subroutine TMR2IF = 0; } if (ADIF) { // if an A/D interupt A2D_Subroutine(); // call the A/D routine ADIF = 0; } if (TMR0IF) { // if a Timer0 interupt Timer0_Routine(); // call the Timer0 routine TMR0IF = 0; } }

JSG

- 11 -

rev 10/10/02

You might also like