Understanding Interrupts
Interrupts (also known as traps or exceptions in some processors) are a technique of diverting the processor from the execution of the current program so that it may deal with some event that has occurred. Such an event may be an error from a peripheral, or simply that an I/O device has finished the last task it was given and is now ready for another. An interrupt is generated in your computer every time you type a key, or move the mouse. You can think of it as a hardware-generated function call.
Interrupts alleviate the processor from having to continuously check the I/O devices to determine whether they require service. Instead, the processor may continue with other tasks. The I/O devices will notify it when they require attention by asserting one of the processor’s interrupt inputs. Interrupts can be of varying priorities in some processors, thereby assigning differing importance to the events that can interrupt the processor. If the processor is servicing a low-priority interrupt, it will pause that in order to service a higher-priority interrupt. However, if the processor is servicing an interrupt and a second, lower-priority interrupt occurs, the processor will ignore that interrupt until it has finished the higher-priority service.
When an interrupt occurs, the usual procedure is for the processor to save its state by pushing its registers and program counter onto a stack (a different stack to the user stack in Forth). The processor then loads an interrupt vector into the program counter. The interrupt vector is the address at which an interrupt service routine (ISR) lies. The ISR is just a subroutine or function to handle that interrupt. Thus, loading the vector into the program counter causes the processor to begin execution of the ISR, performing whatever service the interrupting device required. The last instruction of an ISR is always a Return from Interrupt instruction. This causes the processor to reload its saved state (registers and program counter) from the stack and resume its original program. Interrupts are largely transparent to the original program. This means that the original program is completely “unaware” that the processor was interrupted, except for a lost interval of time.
Interrupts alleviate the processor from having to continuously check the I/O devices to determine whether they require service. Instead, the processor may continue with other tasks. The I/O devices will notify it when they require attention by asserting one of the processor’s interrupt inputs. Interrupts can be of varying priorities in some processors, thereby assigning differing importance to the events that can interrupt the processor. If the processor is servicing a low-priority interrupt, it will pause that in order to service a higher-priority interrupt. However, if the processor is servicing an interrupt and a second, lower-priority interrupt occurs, the processor will ignore that interrupt until it has finished the higher-priority service.
When an interrupt occurs, the usual procedure is for the processor to save its state by pushing its registers and program counter onto a stack (a different stack to the user stack in Forth). The processor then loads an interrupt vector into the program counter. The interrupt vector is the address at which an interrupt service routine (ISR) lies. The ISR is just a subroutine or function to handle that interrupt. Thus, loading the vector into the program counter causes the processor to begin execution of the ISR, performing whatever service the interrupting device required. The last instruction of an ISR is always a Return from Interrupt instruction. This causes the processor to reload its saved state (registers and program counter) from the stack and resume its original program. Interrupts are largely transparent to the original program. This means that the original program is completely “unaware” that the processor was interrupted, except for a lost interval of time.
Hardware interrupts
There are two ways of telling when an I/O device (such as a UART or SPI interface) is ready for the next sequence of data to be transferred. The first is known as busy waiting or polling, where the processor continuously checks the device’s status until the device is ready. This can be wasteful of the processor’s time, but is the simplest to implement. For some time-critical applications, polling can reduce the time it takes for the processor to respond to a change of state in a peripheral.
A better way is for the device to generate an interrupt to the processor when it is ready for a transfer to take place. Small, simple processors may only have one (or two) interrupt inputs, so several external devices may have to share the interrupt lines of the processor. When an interrupt occurs the processor must check each device to determine which one generated the interrupt. (This can also be considered a form of polling.) The advantage of interrupt polling over ordinary polling is that the polling only occurs when there is a need to service a device. Polling interrupts is only suitable in systems that have a small number of devices, otherwise the processor will spend too long trying to determine the source of the interrupt.
The other technique of servicing an interrupt is by using vectored interrupts, by which the interrupting device provides the interrupt vector that the processor is to take. Vectored interrupts reduce the time it takes the processor to determine the source of the interrupt considerably. If an interrupt request can be generated from more than one source, it is therefore necessary to assign priorities (levels) to the different interrupts. This can be done in either hardware or software, depending on the particular application.
In this scheme, the processor has numerous interrupt inputs (some external, many internal) with each interrupt corresponding to a given interrupt vector. So for example, when interrupt 7 occurs, the processor loads vector 7 into its program counter and starts executing the service routine specific for interrupt 7.
The PIC24 processor used in your Scamp has two interrupt vector tables. The second of these, known as the alternate vector table is there for your use. See Section 8 of the PIC24 datasheet for more information. There are lots of interrupt sources for the PIC24. Use the datasheet to look up the vector for the interrupt source you want to use.
A better way is for the device to generate an interrupt to the processor when it is ready for a transfer to take place. Small, simple processors may only have one (or two) interrupt inputs, so several external devices may have to share the interrupt lines of the processor. When an interrupt occurs the processor must check each device to determine which one generated the interrupt. (This can also be considered a form of polling.) The advantage of interrupt polling over ordinary polling is that the polling only occurs when there is a need to service a device. Polling interrupts is only suitable in systems that have a small number of devices, otherwise the processor will spend too long trying to determine the source of the interrupt.
The other technique of servicing an interrupt is by using vectored interrupts, by which the interrupting device provides the interrupt vector that the processor is to take. Vectored interrupts reduce the time it takes the processor to determine the source of the interrupt considerably. If an interrupt request can be generated from more than one source, it is therefore necessary to assign priorities (levels) to the different interrupts. This can be done in either hardware or software, depending on the particular application.
In this scheme, the processor has numerous interrupt inputs (some external, many internal) with each interrupt corresponding to a given interrupt vector. So for example, when interrupt 7 occurs, the processor loads vector 7 into its program counter and starts executing the service routine specific for interrupt 7.
The PIC24 processor used in your Scamp has two interrupt vector tables. The second of these, known as the alternate vector table is there for your use. See Section 8 of the PIC24 datasheet for more information. There are lots of interrupt sources for the PIC24. Use the datasheet to look up the vector for the interrupt source you want to use.
Using INT0 in Forth
External Interrupt 0, also known as INT0 in the PIC24, is the easiest external interrupt to get going. INT0 is RB7 on the processor, which is pin 9 on a Scamp2 and pin 8 on a Scamp3. A low on this pin will trigger the interrupt.
The first thing you need to do is create a word to handle (service) your interrupt source. In this trivial example, we'll just turn the amber status LED on.
The first thing you need to do is create a word to handle (service) your interrupt source. In this trivial example, we'll just turn the amber status LED on.
: myISR
[i
ledon \ do something
clearmi \ clear interrupt flag for INT0
i]
;i
Note the [i and i] … they define the interrupt handler. You need to clear the INT0 interrupt flag once you have finished servicing it. This is done with the clearmi word. Also note the word is finished off with ;i rather than just a semicolon … that works as a return from interrupt, as opposed to a normal return.
Then you set up the appropriate vector to point to your interrupt handler. See Table 8.2 the PIC24 datasheet for a list of interrupt vectors. The vector number is what you're interested in. (This is also the priority of that particular interrupt.)
Then you set up the appropriate vector to point to your interrupt handler. See Table 8.2 the PIC24 datasheet for a list of interrupt vectors. The vector number is what you're interested in. (This is also the priority of that particular interrupt.)
The ' (tick) word looks up the address of your new word myISR, and places it on the stack. Then you store that into the alternate (user) vector table using the word int!.
' myISR \ get address of my interrupt handler word
8 int! \ INT0 is vector 8
Then you’re ready to go. You just need to activate the user vector table and turn on the interrupt. aivt switches to the alternate vector table, and emi activates the pullup resistor for RB7 and enables the INT0 interrupt.
aivt \ switch to alternate vector table
emi \ enable INT0 interrupt
You can test it by connecting a momentary contact switch between GND and RB7. You should see the LED come on when you press the switch.
To switch back to the normal interrupt vector table, use the word ivt. To disable the INT0 interrupt, use the word dmi.
To switch back to the normal interrupt vector table, use the word ivt. To disable the INT0 interrupt, use the word dmi.
Using INT1..INT4
In addition to INT0, there are also four other external interrupts (INT1 .. INT4) that can be assigned to other pins on your Scamp. It takes a little more work though.
First, let's start by defining the addresses of the control registers we need to use. See table 4-5 in the PIC24 datasheet.
First, let's start by defining the addresses of the control registers we need to use. See table 4-5 in the PIC24 datasheet.
\ define applicable control registers
$0082 constant INTCON2
$0086 constant IFS1 \ INT2 and INT 1 flags
$008a constant IFS3 \ INT3 and INT 4 flags
$0096 constant IEC1
$0098 constant IEC2
$009a constant IEC3
To allocate the interrupt to a pin, we need to configure that pin as an input with a pullup resistor enabled, using the word switch. When then need to assign the pin to the appropriate Peripheral Pin Select Register. See table 4-28 in the PIC24 datasheet.
To assign a pin to an interrupt, you need to write the pin's RP number to the appropriate 8 bits of the applicable RPINx register (in table 4-28). Note that RP Numbers differ between Scamp1/2 and Scamp3, due to a different PIC24 variant used. Use the following tables to look up the RP Number you need for a given pin. Also note that all pins can be allocated to interrupts on a Scamp3, while this is not the case on a Scamp2. Also note that on a Scamp3, pin 9 is INT0 but it can also be allocated to another INT as well.
|
|
|
Example 1 - setting up INT1 on pin 2:
First, we need an ISR. This is similar to the previous (trivial) ISR, but note the different code to clear the interrupt flag in register IFS1.
: INT1ISR
[i
ledon \ do something
IFS1 c@ $ef and IFS1 c! \ clear interrupt flag for INT1
i]
;i
Then we point the vector to the ISR:
\ allocate vector
' INT1ISR \ get address of my interrupt handler
#28 int! \ vector number for INT1 is #28
aivt \ switch to alternate vector table
So, to assign INT1 to pin 2, we need to write the value 2 to the upper 8 bits of RPIN0. We then need to enable the external interrupt we are using in the INTCON2 register at address $0082. See table 4-5 in the PIC24 datasheet. The lower five bits control each of the five external interrupt sources. So to turn on INT1, we need to set bit 1 of INTCON2.
In addition, we need to set the INT1 interrupt enable bit (bit 4), in the IEC1 control register at address $0096.
In addition, we need to set the INT1 interrupt enable bit (bit 4), in the IEC1 control register at address $0096.
\ configure interrupt
2 switch \ configure as input and enable pullup
2 $38d c! \ assign INT1 to pin 2 using RPINR0
IEC1 @ $10 or IEC1 ! \ set INT1IE in IEC1
INTCON2 @ %10 or INTCON2 ! \ enable INT1 in INTCON2
Once we've done that, INT1 is active and ready to use.
Example 2 - setting up INT4 on pin 6:
: INT4ISR \ ISR for INT4
[i
ledon \ do something
IFS3 c@ $bf and IFS3 c! \ clear flag (bit 6) for INT4
i]
;i
\ allocate vector
' INT4ISR \ get address of interrupt handler
#62 int! \ vector for INT4 is #62 (table 8-2)
aivt \ switch to alternate vector table
\ configure interrupt
6 switch \ configure as input and enable pullup
6 $390 c! \ assign INT4 to pin 6 using RPINR2
IEC3 @ $40 or IEC3 ! \ set INT4IE in IEC3
INTCON2 @ $10 or INTCON2 ! \ enable INT4 in INTCON2