next up previous
Next: How do I rewrite Up: Background Previous: What is an Output

How is an OC Interrupt Handler Written?

You write an ISR as you would write a program function. ISR's, however, must be treated somewhat differently by the compiler and we must therefore have a way to tell the compiler that a specific function is an ISR and not a regular function. Basically, there are three things we need to do in writing an ISR. These things are:

We now discuss each of these steps below:

Initialization: Initialization of a hardware interrupt is done in the function init(). The basic things that must be done are

  1. initialization of any global variables and counters in the ISR
  2. arming the interrupt
  3. acknowledging any previously caught interrupts
The following source code shows the listing for a simplified init() function. This function is called at the start of any program you write.
  void init(void){
    asm(" sei");
    CONFIG = 0x04;
    BAUD=BAUD38K;
    SCCR1 = 0x00;
    SCCR2 = 0x0C;

    _Time=0;
    TMSK2 = 0x0D;
    TMSK1 |= OC4I;
    TFLG1 |= OC4F;
    TOC4 = TCNT + 256;
    asm(" cli");
  }
The first and last line of this function are assembly language instructions that disable/enable interrupt handling. Essentially, the first instruction asm("sei") sets the I bit in the condition code register, thereby disabling any interrupts. This prevents us from catching an interrupt while we're trying to initialize the system. The last command asm(" cli") clears the I bit, thereby re-enabling interrupt handling.

The next 4 instructions after the sei instruction are used to disable the watchdog timer (CONFIG=0x04) and to setup the asynchronous serial interface (SCI). Setting up the SCI involves declaring the agreed upon baud rate (BAUD=BAUD38K) and setting the appropriate values in the SCI's control registers, SCCR1 and SCCR2.

The following code segment from init() is specific to the initialization of the output compare 4 interrupt.

  _Time = 0;
  TMSK2 = 0x0D;
  TMSK1 |= OC4I;
  TFLG1 |= OC4F;
  TOC4 = TCNT + 256;
The first instruction zeros a global variable _Time. This global variable is a counter that keeps track of the number of times OC4han has been executed. It will be incremented each time OC4han is executed. Due to its global nature, this variable is accessible by all functions in your program. So all of your functions can use _Time as a variable holding the current real-time. The second instruction (TMSK2 = 0x0D) sets the rate at which TCNT is incremented. In particular, this choice will increment TCNT once very 407 nanoseconds. The instruction TMSK1 |= OC4 arms the output compare 4 interrupt by setting bit OC4I. The instruction TFLG1 |= OC4F acknowledges any previously received OC4 interrupts, thereby paving the way for your program to catch the next OC4 event. Finally, we set the output compare register TOC4 to the next time we want the OC4 event to occur. This new deadline is obtained by taking the current value of TCNT and adding 256 to it. So the next OC4 event should occur $256
\times 500$ nanoseconds from the current time ($128$ $\mu$seconds).

Interrupt Handler: The interrupt handler's source code will also be found in kernel.c. ISR's must be handled differently than regular functions. In particular, the compiler needs to ensure that the return from an ISR is handled by the rti assembly command.

You write the ISR as if it were an ordinary function. But you need to alert the compiler that this function is an interrupt handler. This alert is provided through a pre-compiler direction known as a pragma. The actual code in kernel.c is shown below:

  #pragma interrupt_handler OC4han()
  void OC4han(void){
    TOC4 = TOC4 + 256;
     _Time = _Time+1;
     TFLG1 |= OC4;
  }

The first line

#pragma interrupt_handler OC4han()
tells the compiler that the following function is to be treated like an interrupt handler. The body of OC4han has only three statements. In general, we want interrupt handlers to be extremely short. In particular, the line
TOC4 = TOC4 + 256;
resets the output compare register TOC4 to the next time we want this interrupt to be generated. The next line of code
 _Time = _Time + 1;
increments a global variable _Time. The final line of OC4han() is
 TFLG1 |= OC4;
This line acknowledges the interrupt by setting the appropriate bit in TFLG1 to one.

Interrupt Vector: The final thing we need to do is declare the interrupt vector associated with our interrupt handler. This is accomplished using another pragma. The code you would need to provide is:

  extern void OC4han();
  #pragma abs_address:0xffe2;
  void(* OC4_handler[])()={ OC4han };
  #pragma end_abs_address
The program used here tells the compiler to install the interrupt handler at the absolute address 0xFFE2. This absolute address is the interrupt vector for output compare 4 event (refer back to the earlier table of interrupt vectors). With these lines of code we've tied our ISR OC4han to the OC4 event.


next up previous
Next: How do I rewrite Up: Background Previous: What is an Output
Michael Lemmon 2009-02-01