Testing the INT and IOC interrupts on the 12F675
An interrupt is the temporary suspension of the normal course of program execution while the processor executes a certain body of instructions, the interrupt service routine (ISR). It occurs at an arbitrary time, determined by an external stimulus or the occurrence of a certain event within the microprocessor system. The processor finishes the instruction currently being executed, and saves the address of the next instruction normally executed on a first-in, last-out stack. In the PIC, this is a special array of eight locations. The processor then jumps to a certain program memory location (0x0004 in the PIC), where the ISR begins, while clearing the global interrupt enable bit GIE so that further interrupts are prevented. At the end of the ISR, execution of the instruction retfie loads the address at the top of the stack into the program counter, and execution proceeds from that point, which is where the processor was interrupted. The GIE bit is set, so that further interrupts may occur.
Other processors may handle this in different ways, but on the same principles. The return address may be saved on a data stack, and the W register and the status may also be saved, and restored automatically when the ISR returns. On the PIC, this must be done explicitly by the programmer. Also, because there is no way to remove addresses from the PIC return stack, every interrupt must have a matching retfie, just as every subroutine call must have a matching return. With some MCU's, interrupts from different sources may jump to different addresses (as with the AVR), which is called a vectored interrupt. With the PIC, the first thing to do in an ISR is to determine the source of the interrupt by polling the interrupt flags.
Memory locations must be defined for storing W and STATUS during an interrupt. Because of memory banking, these locations must be bank-independent. On the 12F675 and 16F676 this is no problem because all the 64 RAM locations are bank-independent. On larger MCU's, these locations should be in the range 70-7F, which are bank-independent. The beginning of the ISR should save W using movwf, then STATUS with movf,w followed by movwf. At the end of the ISR, STATUS can be restored with the same two instructions. Care must be taken when restoring W, since movf may alter the Z flag. The way to do this is with swapf,f followed by swapf,w. This restores the saved value of W without altering any flags. These instructions are shown in the .asm template files.
Each interrupt source has an interrupt flag that is set when the conditions for the interrupt occur. If the interrupt enable bit for this interrupt is set, then the interrupt will occur if any more general interrupt enable flags that apply are set. There is a general interrupt enable flag GIE in a PIC, which we have already mentioned, that applies to all interrupts and disables them when clear. PIC interrupts fall into two classes, core and peripheral. The peripheral interrupts, concerning the comparator, AD converter, serial ports and so forth, have their own general interrupt enable, PEIE (there is no group flag PEIF). There are also enable bits and flags for each source. The core interrupts, for the INT input, changes on PortA pin levels, and the T0 timer, have only their individual interrupt enables.
In the initialization section of a program, the interrupt enable bits for the desired interrupts are set, and any necessary configuration should be done. Then all the associated interrupt flags should be explicitly cleared, even if they are supposed to be clear on a power-up reset. Finally, the GIE bit is set and the main section of the program is entered.
There is an alternative method of using the interrupts that does not involve actual interrupts. This consists of polling the interrupt flags while the program is executing. When a set flag is discovered, it is cleared and the program calls a routine that specifically carries out the desired actions. This method removes any chance of an interrupt occurring at an inconvenient time, and allows multiple interrupts to be prioritized.
As a concrete example, we shall use the PIC12F675 8-pin MCU. This MCU has 1024 words of program memory, 64 RAM locations and 64 EEPROM locations, ample for many small applications. It has 8-bit and 16-bit counters, an AD converter with four inputs, and a comparator. If more I/O pins are needed, the PIC16F676 is identical except for the addition of 6 more pins as a Port C. The RAM locations are not bank-dependent, which is convenient.
These MCU's have seven interrupt sources: the INT pin (PA2), interrupt-on-change (IOC) for any of the port A pins, timer 0, AD done, comparator, timer 1 and EEPROM write complete. There is only one enable bit, GPIE, and one flag, GPIF for the IOC interrupt, but it can be enabled or disabled for each pin separately.
Let us connect an LED to GP5 (pin 2) and cause it to toggle on an interrupt request from GP2/INT at pin 5, as shown in the figure at the right. INT is only available at GP5. An interrupt is requested when a rising or falling edge is detected at INT, as selected by bit 6 of OPTION_REG. If this bit is set, a rising edge is detected, while if it is clear, a falling edge is detected. This pin must be configured as a digital input in ANSEL (ANS2 = 0) and as an input in TRISIO (bit 2 = 1). GP5 must be configured as a digital output, TRISIO,5 = 0. A 330Ω resistor should be in series with the LED. Pin 5 should be pulled up by a 4.7kΩ resistor.
A pushbutton can be connected to pin 5 to ground it when pressed. If the rising edge is selected for INT, the interrupt will be requested when the pushbutton is released. If this is done, you will notice that multiple interrupts are requested when the button is pressed. The contacts bounce when the button is pressed, and the MCU can complete the ISR and return before the bounce is finished. One way of solving this problem (which usually occurs ehen interrupts are requested by a mechanical contact) is to put a delay routine at the start of the ISR. Now the contacts bounce while the MCU is busy here, but the button must still be released during the delay, or two interrupts will be requested, one from the bounces and the other when the button is released. The latter problem can be cured by selecting a negative edge, so there will be no interrupt when the button is released.
A better solution is to use an electronic debouncing circuit consisting of two cross-coupled NAND gates. The first contact will cause a change of state, and further contacts will have no effect. In the circuit shown, if the contact bounces in the position shown, the output of the upper gate will not change, because the other input is at 0. If the contact moves to the other position, the output of the lower gate will change to 1, and the output of the upper gate will fall to 0 This will trigger a falling-edge interrupt. Once again, if the contact bounces in the new position, there will be no change int he outputs. If you use this circuit, you will see how much more reliable it is than the simple SPST pushbutton.
The complete program is shown in the figures. If the electronic debouncing circuit is used, the debouncing delay can be omitted. However, it is instructive to observe how a simple pushbutton behaves. If there are other interrupts enabled, the instructions to handle them follows the "notint" label. The instructions for saving and restoring W and STATUS are also shown, as is the oscillator calibration. These appear in the template file for the MCU.
An interrupt can be generated whenever the voltage on any of the GPIO pins changes. If IOC is enabled for one (or more) of the GPIO pins by setting the corresponding bit in the IOC register, the processor compares the level on the pin with the level last read from the pin, and if they differ, the GPIF flag is set, and if the GPIE bit is set, an interrupt is generated. This is a level-sensitive interrupt, in distinction to the edge-sensitive INT. If more than one pin has IOC enabled, the enabled pins must be polled to see which one generated the interrupt.
Let us connect a second LED to GP4, and toggle it when the level on GP0 changes. We can pull up GP0 with a 4.7kΩ resistor, and change its level by using a wire to ground it when desired. First of all, we must set bit 0 in IOC to enable this pin, but that is not all. This pin is an AD input by default, so we must clear ANS0 in ANSEL as well. Also, it is a comparator input, so we must write 0x07 to CMCOM to turn the comparator off and allow GP0-GP2 to be digital I/O. GP4 must be made a digital output by clearing its bit in TRISIO, while the bit for GP0 should remain set, as it is by default. This is all done in the initialization part of the program, together with setting GPIE and clearing GPIF before setting GIE.
The modifications to the ISR should be clear. After checking INTF, we must test GPIF, and if it is set we must toggle GP4 then clear GPIF. We must also read GP0 (by reading GPIO into W) to store the new level on the pin in the output latch. For some reason, toggling GP4 does not do this, and if it is not done, we will simply get another interrupt.
The LED on GP5 will behave as before. If we turn on the GP4 LED when starting, and ground GP0, then if we remove the ground, the LED will go out. If we restore the ground, the LED will light again.
Composed by J. B. Calvert
Created 22 September 2010
Last revised 23 September 2010