Handling GPIO Interrupts within the .NET Micro Framework

My previous .NET Micro Framework example demonstrated how to determine the state of a GPIO input signal. This blog entry outlines how to implement an improved application which instead makes use of an interrupt event handler, for improved performance and power efficiency.

The problem with the InputPort based example was that it utilised a polling mechanism to constantly determine the current state of the GPIO port, this mode of operation is not ideal for a number of reasons, including:

  • Power Management – The polling implementation may become a busy loop, spinning around as quickly as possible, burning CPU cycles to check the state of the input which only occasionally changes state.
  • Responsiveness – A common resolution to the power management issue is to only periodically probe the hardware. The example application from the last blog entry sampled the GPIO state once per second. This has the side effect of introducing latency, since we will only detect a change in state once the current delay has completed. We may also miss a change completely if it happens too quickly.

To resolve some of these issues we can utilise the InterruptPort class. This class derives from the InputPort class mentioned in the previous blog entry, and hence by default inherits all of the InputPort’s properties and functionality.

The main difference is an additional constructor parameter which specifies when an ‘OnInterrupt’ event associated with the port should fire.

Determining when the interrupt should trigger
The additional constructor parameter specifies the conditions under which the interrupt should trigger. You can use one of the following Port.InterruptMode enumeration values:

  • InterruptEdgeBoth – The interrupt is triggered on both rising and falling edges.
  • InterruptEdgeHigh – The interrupt is triggered on the rising edge.
  • InterruptEdgeLow – The interrupt is triggered on the falling edge.
  • InterruptEdgeLevelHigh – The interrupt is triggered whenever the input signal is high.
  • InterruptEdgeLevelLow – The interrupt is triggered whenever the input signal is low.

Clearing Interrupt flags
Edge interrupts trigger when the specified signal transition occurs and automatically re-enable themself, ready to trigger on the next state transition. Level based interrupts however behave slightly differently.

Whenever a level interrupt (InterruptEdgeLevelHigh or InterruptEdgeLevelLow) is triggered it sets a flag which disables the interrupt from re-triggering until the flag is explicitly cleared by means of the ClearInterrupt method.

The main purpose for this is to stop a constant stream of interrupts occurring when the specified level is reached on the GPIO signal. By disabling the interrupt once it has triggered, the software can decide when it wants to be interrupted again. When the ClearInterupt method is invoked, it is possible that the interrupt event handler will fire immediately, if the specified level is still held on the signal.

Example Application
The following is a simple example of how the InterruptPort class can be utilised to produce asynchronous behavior.

The main loop of the program, simply prints the message “House Keeping Ping…” to the debug console every 60 seconds. This is to provide an indication of when the main thread is running. In a typical embedded device this main loop may do menial house keeping tasks, such as timer management, and status LED flashing etc.

While the main loop is sleeping (or printing debug messages), the hardware is constantly monitoring the state of the GPIO_Pin_3 input (attached to the Select button within the Sample Emulator). Whenever the signal goes low, an interrupt is triggered, which causes our button_OnInterrupt event handler to execute on a second thread.

You will notice that button presses are detected immediately even while the main thread is sleeping, unlike the polling example which only detected the button press when the main thread woke up.

using System; 
using System.Threading; 
using Microsoft.SPOT; 
 
using Microsoft.SPOT.Hardware;namespace InterruptPortApplication 
{ 
  public class Program 
  { 
    public static void Main() 
    { 
      // Specify the GPIO pin we want to use as an interrupt 
      // source, specify the edges the interrupt should trigger on 
      InterruptPort button = new InterruptPort(Cpu.Pin.GPIO_Pin3, true, 
        Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeLow); 
 
      // Hook up an event handler (delegate) to the OnInterrupt event 
      button.OnInterrupt += new GPIOInterruptEventHandler(button_OnInterrupt); 
 
      while (true) 
      { 
        // The main thread can now essentially sleep 
        // forever. 
        Thread.Sleep(60 * 1000); 
        Debug.Print("House Keeping Ping..."); 
      } 
    } 
 
    static void button_OnInterrupt(Cpu.Pin port, bool state, TimeSpan time) 
    { 
      // This method is called whenever an interrupt occurrs
      Debug.Print("The button is pressed"); 
    } 
  } 
}

The three parameters in the GPIOInterruptEventHandler delegate type are as follows:

  • pin – a reference to the pin that caused the interrupt to occur. This is useful because you can hook up the same event handler to more than one InterruptPort instance.
  • state – the logic level of the GPIO signal when the interrupt occurred.
  • timestamp – a relative time-stamp which allows you to accurately determine when the interrupt occurred.

Performance
The InterruptPort class is not designed for use in hard real time applications. In my mind it is more a Deferred Procedure Call (DPC) implementation triggered by hardware events than a true Interrupt Service Routine (ISR).

Since I have no physical .NET Micro Framework hardware yet, it is a bit difficult for me to tell exactly how the performance characteristics of interrupt handlers within the .NET Micro Framework stack up. This is defiantly an area I would like to characterise further once my hardware arrives.

In the mean time here are some links to low level performance related material that I have managed to find. Both articles are written by Chris Tacke and give an excellent insight into the performance characteristics of the .NET Micro Framework (and managed code in general) for embedded development.

Leave a Reply