Interrupts in embedded systems are much like subroutines, but they are generated by hardware events rather than software calls. When a hardware condition generates an interrupt, such as a Port H interrupt when sw2 is pushed, it tells the CPU to halt normal operation and jump to a specific location in memory called an Interrupt Service Routine (ISR). The ISR is just like a subroutine and contains code that is executed once the ISR is entered. Once the code in the ISR is completed, control is passed back to the main program.
Almost all (but the simplest toy processors) have interrupts. Check out your PC. You will see references to ISRs or IRQs in the Windows hardware support.
The hcs12 has a specific way to locate interrupt service routines once an interrupt occurs. First, when a particular interrupt is activated, the program jumps to the Interrupt Vector Table, which is a region of memory that contains specific interrupt vectors. There is one interrupt vector for each type of interrupt that can occur, and each interrupt vector is located at a unique memory address in the table. Each interrupt vector contains the address of the start of the ISR that will run for that interrupt. The CPU will then load the address of the ISR in the Program Counter Register (PC), so that the program then starts executing at the beginning of the ISR. So, on an interrupt, the hcs12 first goes to one address (the interrupt vector address for that interrupt) which in turn contains another address which is that of the actual ISR itself - its like a pointer to a pointer in C jargon. Note that the interrupt vector addresses are fixed by the hardware, whereas the ISR addresses in each interrupt vector are changeable values determined by the programmer.
As an example, in the timer demo program located here , the Interrupt Vector address for a Port H interrupt is $3E4C-$3E4D. When a Port H interrupt occurs, the hcs12 stops normal execution and goes to address $3E4C-$3E4D, the interrupt vector address for a Port H interrupt. It then reads the 16 bit value there (two 8 bit values in each memory location), which is the memory address where the ISR starts. It then starts execution at this new memory address. When I built the program below and loaded it into the Dragon12, I checked the contents of memory address $3E4C-$3E4D using the "md" command in DBug12, and saw that it contains $107B, which is the address of our ISR. Using the command "asm 107b" I then noted that the machine code located here corresponds to "movb #$00, 1100", which is the first line of code from the ISR_PTH routine. Note that the name "counter" is actually a synonym for the value $1100 as noted in the data definition section. When we use the "ORG $3E4C" directive followed by the "dc.w ISR_PTH" directive, we told the assembler to place the actual ISR_PTH starting address ($107B) in the interrupt vector starting at address $3E4C (ie, the contents of address $3E4C is $107B).
So, we could summarize the interrupt action as noted below:
1. The program is running, and could be executing any instruction, which we call instruction M.
2. A switch tied to Port H is pushed and activates a Port H interrupt.
3. The Port H interrupt vector is located at address $3E4C-$3E4D. The program finishes the current instruction M and then jumps to the address of the interrupt vector.
4. Now at address $3E4C-$3E4D, the program reads the values $107B stored there and then jumps to this new address.
5. The code beginning at address $107B is the beginning of our interrupt service routine (ISR). The command "movb #$00, 1100" is thus executed.
6. Program continues to sequentially execute the instructions in the ISR following the "movb #$00, 1100" command. Program execution continues in the ISR until the RTI (return from interrupt) instruction is hit.
7. Once we hit the RTI instruction, the program jumps out of the ISR and back to the regular program. The position in the program will be the next instruction N that follows instruction M that we were executing when the interrupt occurred.
8. The schematic below outlines these steps:
The interrupt vectors are located at unique addresses for each interrupt. If you look on page 77 of the MC9S12DP256B Device User Guide (9S12DP256BDGV2.pdf) you will see the Interrupt Vector Table, which defines the interrupt vectors (addresses) for each interrupt. This vector table is for a standard hcs12 chip which doesn't have any other programs running on it. Since we always have D-Bug12 running when we use the Dragon12 board, the interrupt vectors are re-mapped to new addresses. For instance, the Port H interrupt is at $FFCC- $FFCD in the standard hardware setup, and this is mapped to $3E4C-3E4D when running DBug12. In our programs we set up the ISRs using the DBug12 interrupt vector mappings. These mappings can be found on page 12 of the DBug12 v.4 Reference Guide (DB12RG4.pdf).
To use the interrupts, we must configure several registers. First, we must LOCALLY enable the individual interrupts themselves, such as by setting the appropriate bits in the Port H Interrupt Enable Register at address $0266. Finally, we must GLOBALLY enable interrupts using the "CLI" instruction. This clears the "I" flag of the CCR register. Once the program is running and we have an interrupt event, it will set an interrupt flag in the appropriate register. In the ISR, we can check this flag to see which unit caused the interrupt (for instance, was it sw2 or sw3 that caused the Port H interrupt?). We ***MUST*** clear the interrupt flag bit before we leave the ISR or else it will remain set, and the ISR will be stuck in an infinite loop. This is referred to as “acknowledging the interrupt”. Usually we clear an interrupt flag by writing a "1" to it. This may seem counterintuitive as you would expect to clear a flag by writing a "0" to it, but it doesn't work that way.
Also note that we can enable an interrupt to act on a low-to-high event (rising edge) or high-to-low event (falling edge) by setting the polarity in the relevant interrupt register. For instance, in the code below we set the Port H interrupt polarity to "falling edge", meaning that the interrupt is activated as we go from high to low (logical 1 to 0). Our switch buttons are tied to Port H, so a Port H pin tied to a switch will sit at +5V (high or logical 1) until we push it. Once we push it, this take the Port H pin to ground (low or logical 0) and thus activate our interrupt. See lab 4 and the Dragon12 Schematic 4 for more info on the Port H pins.
ISR's use the stack to temporarily store the internal CPU registers, so we must always initialize the stack pointer with an "LDS" type instruction. Note that the hardware automatically takes care of the pushes/pulls from the stack, and the programmer doesn't need to explicitly write any of the stack push/pull instructions. The ISR's must always end with an "RTI" statement to transfer control back to the main program.
Although some books suggest using the SEI/CLI instructions within an ISR to make them "atomic", this is misleading. The CCR "I" flag is automatically set on entering and ISR and cleared on leaving it (remember, to allow maskable interrupts we must make the "I" flag = 0). Adding the SEI/CLI instructions within an ISR is redundant.
Interrupts have priorities. If two interrupts go off at the same time, that with the highest priority is serviced first. Once it is finished, the next highest priority interrupt is serviced, and so on. On the HCS12, you can see the default interrupt priority in interrupt vector table (Table 5-1 of the MC9S12DP256B Device User Guide).
Note that there are several types of interrupts. The ones we use most often are those triggered by peripherals, such as a timer overflow interrupt or the Port H interrupt. These are built in to the hcs12 chip. There are also external interrupts that can be used by devices external to the hcs12. The IRQ pin is an external interrupt that can be used for general purpose cases. For instance, if a device connected to the IRQ pin sends out a falling-edge logic pulse (i.e. takes the IRQ pin from high to low), it will set off an IRQ interrupt which will then cause the IRQ interrupt service routine to be executed. The XIRQ pin is a non-maskable interrupt. It is called non-maskable because, in general, it can't be turned off or masked, so it is always armed (with a few minor exceptions). It is used to service interrupts that have extremely urgent service requests. On the Dragon12 board, for instance, the XIRQ pin is tied to the yellow "abort" switch on the lower right part of the board. The XIRQ and IRQ have higher priorities than any of the regular peripheral interrupts. The RESET interrupt pin has the highest priority of all interrupts. On the Dragon12 board, this pin is tied to the yellow "reset" button.
The concept of interrupts is further explored in the timer demo.