From: Malcolm Wiles Subject: Re: Interrupts and PCLATH Date: 20 January 2003 08:01 As used in Readout March '03 Dear EPE, I'm grateful to John Waller for drawing my attention (in private correspondence) to a point that isn't fully covered in my "Interrupts" articles in EPE Mar/Apr 2002, and which may cause confusion. In any program that's longer than 2K bytes it's likely that PCLATH is having to be manipulated in order that CALL and GOTO instructions end up in the right places. If the program also uses interrupts, then the program's ISR will need to preserve and restore PCLATH as part of the program's context. Having saved PCLATH, the ISR should then reset it for its own use, as explained in the article. Until the ISR has done this, it is not safe to use any CALL or GOTO instructions in the ISR itself, because at the time the interrupt is taken PCLATH may be wrongly set to address the ISR's code. This restriction **INCLUDES** the "GOTO ISR" instruction which is often assembled into page 0 location 4, the interrupt vector. To re-emphasise, if PCLATH bits 4-3 may be non-zero when an interrupt occurs, then a "GOTO ISR" instruction in page 0 location 4 cannot be used as the first instruction of the ISR. Code must be assembled in contiguous locations starting at location 4 to preserve the context, including PCLATH, and then reset PCLATH, before any CALL or GOTO instructions can be used. It's somewhat analagous to the Bank switching discussion following Experiment 2, except that here the problem is to do with the Page select bits, not the Bank select bits. This issue doesn't arise on 16x8x processors, which have a maximum of 2K program memory and which ignore the setting of bits 4-3 of PCLATH. For readers who would like more details, a new test program, intproga.asm, can be downloaded from the /pub/PICS/interrupts directory of the EPE ftp site, together with instructions on how to run it in the file intproga.txt. This test program illustrates the effect of getting an interrupt when PCLATH is non-zero, and how to fix it. --- end of Readout --- ; Interrupts program A ; Demo how to code the ISR when PCLATH may be non-zero on interrupt ; Version of intprog5 for PIC16F877 ; Flash a led on RA0 at about 1Hz ; Interrupts on RB0/INTF list p=16F877,r=dec ; Macros #define BANK0 BCF 0x03,5 #define BANK1 BSF 0x03,5 #define SPEED 5 ; controls led flash rate: higher no = slower ; Equates for registers include p16f877.inc ; Data locations SAVEW equ 0x20 ; preserve W during interrupts SAVES equ 0x21 ; preserve STATUS during interrupts SAVEPL equ 0x24 ; preserve PCLATH during interrupts COUNT equ 0x22 ; count of timer ticks ICOUNT equ 0x23 ; number of interrupts ; code org 0 goto init org 4 goto ISR ; delete this statement to make the prog work! ; Interrupt service routine ISR: movwf SAVEW ; save W swapf STATUS,W movwf SAVES ; save STATUS movf PCLATH,W movwf SAVEPL ; save PCLATH as part of context ; now establish context for the ISR BANK0 ; ensure bank 0 set clrf PCLATH ; set PCLATH for PAGE 0 btfss INTCON,INTF ; test INTF goto POP ; not an RB0 interrupt ; there is an INTF interrupt nop ; nothing to do for this demo! CLR: bcf INTCON,INTF ; clear the interrupt incf ICOUNT,F ; bump count of interrupts ; restore the main program's context POP: movf SAVEPL,W movwf PCLATH swapf SAVES,W ; restore STATUS movwf STATUS swapf SAVEW,F ; restore W swapf SAVEW,W retfie ; exit ISR ; initialise PIC init: clrwdt ; clear watchdog timer bcf INTCON,GIE ; disable ints clrf PORTA ; initialise all port outputs to zero bsf PORTA,0 clrf PORTB BANK1 clrf TRISA ; RA0 - RA4 all outputs movlw 0x06 movwf ADCON1 ; set PORTA digital movlw b'00000001' ; RB0 input, rest outputs movwf TRISB movlw b'00000111' ; enable PORTB pullups, PSA to TMR, prescale /256 movwf OPTION_REG BANK0 ; interrupt setup bsf INTCON,INTE ; enable INTF interrupt bsf INTCON,GIE ; enable global interrupts ; data initialisation bsf PORTA,0 ; turn on the led LGOTO main ; main is in page 1 ; ensure the PIC "dies" if it gets to the wrong place! org 0x0805 stop: LGOTO stop ; main loop, in PAGE 1 org 0x0A00 main: clrf TMR0 ; clear internal clock count-up clrf COUNT ; clear ticks count movlw 0x08 ; set PCLATH for code PAGE 1 movwf PCLATH A2: bcf INTCON,T0IF ; clear the TMR0 wrapped flag A1: btfss INTCON,T0IF ; has TMR0 wrapped yet? goto A1 ; no, loop till it has incf COUNT,F ; bump count of wraps movlw SPEED ; about 10 bumps/sec at 2MHz clock xorwf COUNT,W ; sets Z if COUNT = W btfss STATUS,Z ; test Z goto A2 ; Z not set so COUNT != SPEED movlw 1 ; wraps COUNT = SPEED so xorwf PORTA,F ; toggle RA0 goto main ; and repeat main loop end ; of program