PIC1684A interrupt map.
Fig. 1 PIC1684A interrupt map.

Programming PIC16F84A-PIC16F628A Interrupts Tutorial

by Lewis Loflin

  
  

The Microchip PIC series of microcontrollers have several sources of hardware interrupts. These are RISC microcontrollers with 35 instructions. To configure interrupts or other hardware functions are setup by configuring various bits in selected registers, in particular here the INTCOM register. While I use the PIC16F84A as an example, this works exactly the same in the PIC16F628A, etc. Also see PIC16F628A interrupt map.

Here I'll start with hardware interrupts, which add incredible power to these low-cost devices. Fig. 1 illustrates the interrupt control structure in a PIC16F84A. By setting and clearing selected bits in the INTCON register we enable a specific device or any combination of them. In this series I'm interested in the PORTB pullups great for detecting switch closings, and TMR0 excellent is for setting up time delays. This is a general discussion of hardware interrupts.



Interrupt control register Microchip PIC.
Fig. 2

To enable any interrupt the SET bit 7 to 1 in the interrupt control register. (BSF INTCON, 7) Then it's a matter of selecting which specific interrupt we want. SET bit 5 for the timer 0 (TMR0) overflow. To determine if an overflow occurred with TMR0 check bit 2 for an overflow condition if SET or 1. This is often referred to as a "flag" bit for testing. Once SET no further interrupts can occur until the bit is cleared to 0. (BCF INTCON, 2)

To enable the interrupt function on PORTB internal pullups SET bit 3 (BSF INTCON, 3) to 1. This will generate an interrupt if any HIGH-LOW or LOW-HIGH transition occurs on PORTB PB4-PB7. Testing the state of bit 0 if 1 shows the interrupt occurred. No further interrupts can occur until bit 0 is cleared to 0. (BCF INTCON, 0)

Great, we have the interrupts programmed and ready to go what's next? When an interrupt occurs the program counter executing the main program saves the PC in the STACK a FIFO buffer used for temporary storage then jumps to what is called an interrupt service routine.


	ORG 0 ; reset vector
	GOTO setup
	
	ORG 0x04 ; interrupt vector
	; do whatever 
	RETFIE ; return from interrupt
	
setup 
	; program the hardware
	GOTO loop
	
loop 
	; main program
	GOTO loop

If one has done Arduino programming the above should be somewhat familiar. This is what really goes on "under the hood" of microcontrollers. It differs between them but all have "vectors" that point to particular memory locations during resets and interrupts.


#define LED 9
volatile byte state = LOW;

void toggle() {
  state = !state;
}

void setup() {
  pinMode(LED, OUTPUT);
  attachInterrupt(0, toggle, RISING);
}

void loop() {
  digitalWrite(LED, state);
}

Above is the Arduino version but in C++.



PIC16F84A memory map.
Fig. 3 PIC16F84A Memory Map.

	ORG     0x004      ; interrupt vector location
	movwf   w_temp     ; save W register contents
	movf    STATUS, w   ; move status register into W 
	movwf	status_temp  ; save STATUS register

  ; isr code can go here or be located 
  ;as a call subroutine elsewhere

	MOVLW   0x01 ; PORTB bit to change
	CALL toggle

 	CALL DELAY_100mS
	BCF INTCON, RBIF ; CLR IRQ flag

	movf    status_temp,w     ; retrieve STATUS register
	movwf	STATUS            ; restore STATUS register 
	swapf   w_temp,f
	swapf   w_temp,w          ; restore W register 
	retfie                    ; return from interrupt

Above is a sample ISR routine called when a switch was pressed on say PB7. The program counter after being saved on the STACK jumps to location 0x04 and executes the code that follows until it encounter a "RETFIE" command then retrieves the original PC and resumes execution of whatever code it was processing at the time of the interrupt. Note: this code illustrates saving the W and STATUS registers on entering the ISR then restoring before the "RETFIE" command. I'd highly advise doing that.

This ISR simply called a separate subroutine after loading 0x01 into the W register that simply inverted the state on a LED at PORTB PB0. It then called subroutine DELAY_100mS, then set to 0 in this case INTCON bit 0 interrupt flag, restored the original W and STATUS values.

That completes this basic introduction to the PIC16F84A interrupts. I use that part because it's easier to illustrate, but everything here applies to other PICs that simply have more interrupt devices to choose from. I'll be looking direct examples of what we just discussed elsewhere.

PIC16F628A connected to 4 LEDs and 4 switches.