Thursday, January 1, 2015

Interrupts Part 1, Introduction to Interrupts

WHAT IS AN INTERRUPT

Thus far in our lessons and program examples, we have been working under the mindset that our programs run in a defined sequence from one instruction to the next.  When we reach the end of the sequence of instructions, the program goes back to the beginning and starts again (Figure 1A).  What if we need a special piece of code to be executed no matter where in the program we are?  Some event occurs like a button is pressed, or a peripheral feature reaches a designated state?  We could write our code in such a way that continuously checks the status of the button or peripheral feature, which is a practice called "polling", but polling is redundant and needlessly uses up program memory.  This is where interrupts come in.

An interrupt is triggered by some event, whether internal or external, that interrupts normal program flow.  This triggering event is called an Interrupt Request (IRQ).  When an interrupt request occurs, the program halts normal program flow and jumps to a special piece of code called the Interrupt Service Routine (ISR).  The ISR contains whatever code you want executed when an IRQ is initiated.  After the interrupt service routine completes execution, the program jumps back to whatever point in the main program it was when the IRQ occurred (Figure 1B).  The interrupt request can happen at any time during normal program flow, not just at points where some status is polled.

Figure 1.
THE PROGRAM COUNTER, AND THE STACK

Figure 2 is taken from the PIC12F629 datasheet, and shows the device's program memory map and stack.  At the top of this map you can see the label PC<12:0>.  This is the Program Counter, and is 13 bits wide from bit 12 to bit 0.  The program counter holds the memory address of the current instruction being executed.  As the program runs through the instructions, the program counter automatically increments to the next instruction in line during execution of the current instruction.
Figure 2.

The PIC12F629 microcontroller is implemented with 1K of program memory space, which has a range of 000h to 03FFh (hexadecimal value).  In decimal form, this range extends from 0 to 1,023.  The program counter is used to point to an address within this range.

You will notice however, that beyond the On-chip Program Memory range, there is a grayed-out area labeled 0400h to 1FFFh (1,024 to 8,191).  With its 13-bit width, the program counter is capable of addressing from 000h to 1FFFh.  The PIC12F629 only utilizes the first 1K of this range, and the rest is "unimplemented".  You might ask why that is the case.  The answer simply, is so that Microchip (the maker of PIC MCUs) can limit the number of internal hardware designs across multiple MCUs.  Back to our Program Counter...

You might be asking, what does any of this have to do with interrupts?  Well, as you can see on the program memory map, the address 0004 holds the Interrupt Vector, or the location in the program memory where the program counter jumps to when an interrupt request occurs.  Below the Program Counter label on the map is the Stack.  When an instruction is executed that causes our program to break normal program flow, such as with an interrupt request, the current instruction address of the program counter is placed on the stack.  The program counter is then loaded with the address of the jump-to instruction (in the case of interrupts, address 0004).  The program now jumps to the new memory location to execute the instructions there.  When those instructions are complete, the address being held in the stack is returned to the program counter, and program flow returns to where it left off before the jump.
Figure 3.

Figure 3 provides a visual reference of this process.  As the program is running through normal program flow, the program counter is loaded with the next instruction address in line.  When an Interrupt Request occurs, the current address on the program counter gets pushed onto the stack.  The program counter is then loaded with the interrupt vector address so that the next instruction that gets executed is the one at the beginning of the Interrupt Service Routine.  At the end of the ISR, a return from interrupt instruction is given which recalls the memory address being held by the stack.  That address is returned to the program counter so that the next instruction executed is the one which would have been executed next, had the interrupt not happened.

There are other events which cause the program counter to jump to another memory location, pushing an address onto the stack.  These instructions are noted in Figure 2 in Assembly language, which are CALL, RETURN, RETFIE, and RETLW.  You will notice also in Figure 2 that the stack is represented in eight different levels, from Stack Level 1, to Stack Level 8.  If normal program flow is jumped, and an address is pushed onto the stack, it goes to stack level 1.  If before returning to normal program flow, the program jumps again, another address is loaded into stack level 1, and the address that was at level 1 gets moved to level 2.  If we keep jumping program flow without returning from the jump first, the stack gradually fills up until all eight stack levels are holding an address.  If we jump one more time, the address at stack level 8 is lost forever, and cannot be recalled.  This event is called Stack Overflow, and it can be problematic.  If we are writing a program which causes the program counter to jump repeatedly, all the while pushing addresses onto the stack which causes an overflow, the program will not be able to return to where it originally started jumping from.  You will find as you grow in your programming that you have to be aware of how deep into the stack your programs are going.  Not to worry for now though!

Is knowing any of this necessary in order to write working programs?  No.  Is it necessary to know if you want to be a good programmer?  Yes!

INTERRUPT REQUEST SOURCES

Across their product line, Microchip PICs contain numerous interrupt request sources.  Not every PIC has every interrupt source though.  To learn what interrupts our specific PIC has, we must again reference the device datasheet, which contains a section dedicated to the device's interrupts.  For the PIC12F629, section 9.4 tells us we have six interrupt sources available.  These are External Interrupt on pin GP2, Timer 0 Overflow Interrupt, GPIO Port Change Interrupt, Comparator Interrupt, Timer 1 Overflow Interrupt, and EEPROM Data Write Interrupt.  A seventh interrupt source is available for the PIC12F675 which is identical to the PIC12F629 except that it includes an Analog to Digital Converter module.

Interrupts are controlled by Special Function Registers in the microcontroller.  These registers are the Interrupt Control register (INTCON), and the Peripheral Interrupt Register (PIR) (Figure 4).
Figure 4.
Some microcontrollers have more than one Peripheral Interrupt Register, which is why the PIC12F629 PIR is labeled as PIR1.  These registers contain Interrupt Enable bits to turn on interrupts, and also Interrupt Flag bits, which are used to show what interrupt specifically occurred.

The INTCON register contains:
GIE - Global Interrupt Enable bit
PEIE - Peripheral Interrupt Enable bit
T0IE - Timer0 Interrupt Enable bit
INTE - GP2/INT External Interrupt Enable bit
GPIE - GPIO Port Change Interrupt Enable bit
T0IF - Timer0 Overflow Interrupt Flag bit
INTF - GP2/INT External Interrupt Flag bit
GPIF - GPIO Port Change Interrupt Flag bit

The PIR1 register contains:
EEIF - EEPROM Write Operation Interrupt Flag bit
ADIF - A/D Converter Interrupt Flag bit (PIC12F675 only)
CMIF - Comparator Module Interrupt Flag bit
TMR1IF - Timer1 Overflow Interrupt Flag bit

To turn on interrupts and make them so they can be used, the enable bit for that interrupt must be set (made 1).  When the interrupt actually occurs, the corresponding Interrupt Flag bit is automatically set so that the program knows what caused the interrupt.  During the Interrupt Service Routine, the programmer must be sure to clear (make 0) the Interrupt Flag bit so that the program doesn't continue to jump back into the ISR after it has been executed.  The Global Interrupt Enable bit (GIE) is used to turn on/off all interrupts.  In order for a specific interrupt to be used, the specific interrupt enable bit and the global interrupt enable bit must be set.

In this lesson, we discovered what interrupts are and what they do to program flow.  We also got introduced to their control registers and the Enable and Flag bits.  Interrupts are very easy to use and extremely useful, it only takes a while to cover all of the information needed to understand how to use them.  In the next lesson, Interrupts Part 2, we will talk about how to set up an interrupt and how to write an Interrupt Service Routine.  Don't worry that we haven't talked about peripheral features such as Timers and the Comparator yet.  We are only getting used to understanding interrupts.  Remember, we build our knowledge one step at a time.

No comments:

Post a Comment