Sunday, March 24, 2013

Object-Oriented Interrupt Handling

Interrupt Service Routines (ISR) are traditionally written as callback functions. AVR provides a set of interrupt vector callback functions that may be defined in application code. These are defined by using the macro ISR() or SIGNAL().

Cosa approaches the challange of integrating ISR functions into an object-oriented context with a number of design patterns. The first design pattern is to define an abstract class, interface, for Interrupt Handlers; Cosa/Interrupt.hh. This is used to achieve a common virtual method prototype for interrupt callback functions. The provided mapping is basically from the ISR() function to the Interrupt Handler method on_interrupt(). The major difference is that the object-oriented method will execute with the instance as a context and have access to the instance member variables. The traditional ISR() callback function often needs global variables for the context and cannot be shared among interrupt service routines.

class Interrupt {
public:
  class Handler {
  public:
    virtual void on_interrupt() {}
    virtual void on_interrupt(uint8_t arg) {}
    virtual void on_interrupt(uint16_t arg) {}

  };
};


The second design pattern is to allow ISR() functions to access the internals of the class as if they where part of the class definition. In C++ this can be achieved with a friend declaration of the function in the class. The C functions need to be declared as extern first. Below is a snippet from the Cosa class for handling External Interrupts (Cosa/Pins.hh) to give an idea of how this works. 

extern "C" void INT0_vect(void) __attribute__ ((signal));
extern "C" void INT1_vect(void) __attribute__ ((signal));


class ExternalInterruptPin :
  public InputPin,
  public Event::Handler,
  public Interrupt::Handler {
private:
  friend void INT0_vect(void);
  friend void INT1_vect(void);

  ...
  virtual void on_interrupt();
  ...

};

The ExternalInterruptPin class uses multiple inheritance and inherits from three classes; the InputPin, Event and Interrupt Handling.

 Fig.1: ExternalInterruptPin class hierarchy

The InputPin provides that basic functionality for handling the pin, the Interrupt::Handler asynchronous events and Event::Handler synchronous events.

For the standard Arduino board external interrupt pins, AVR defines two callback function in the interrupt vector. These have the same names as in the ATmega328P documentation (INT0_vect and INT1_vect). The Cosa implementation of these functions will call the Interrupt Handler on_interrupt() method.

ISR(INT0_vect)
{
  if (ExternalInterruptPin::ext[0] != 0)
    ExternalInterruptPin::ext[0]->on_interrupt();
}


The ExternalInterruptPin constructor will register the instance. The default interrupt handler performs a translation to Event::CHANGE_TYPE. This reduces the amount of processing in the interrupt service routine.

void
ExternalInterruptPin::on_interrupt()
{
  Event::push(Event::CHANGE_TYPE, this);
}


The Cosa implementation of handling of external interrupts allows two levels of extension. The normal usage of ExternalInterruptPin is to sub-class and override the virtual methods on_interrupt() and/or on_event().

An example of usage is the Cosa IR class, Cosa/IR.hh. The IR::Receiver class uses an External Interrupt Pin to read a sequence of pulses from TSOP4838 (IR Receiver Modules for Remote Control Systems).

class IR {
public:

  class Receiver : private ExternalInterruptPin, private Link {
    ...
  private:

    ...
    /**
     * @override
     * Interrupt pin handler: Measure time periods of pulses in sequence
     * from IR receiver circuit. Push an event when a full sequence has
     * been recieved; READ_COMPLETED(this, code) where the code is the
     * recieved binary code or key if a key map was provided.
     */
    virtual void on_interrupt();
    ...

};

The Event::Handler (Cosa/Event.hh) is also an abstract class to define the callback structure for events.

class Event {
public:

  ...
  class Handler {

  public:
    /**
     * Default null event handler. Should be redefined by sub-classes.
     * Called by Event::dispatch().
     * @param[in] type the event type.
     * @param[in] value the event value.
     */
    virtual void on_event(uint8_t type, uint16_t value) {}
  };
  ...

};

The above design pattern for Object-Oriented Interrupt Handling have been applied to several of the Arduino libraries that have been ported and/or rewritten in the object-oriented style of Cosa. The most important attribute is to achieve higher levels of encapsulation, performance and code quality.

No comments:

Post a Comment