Friday, March 23, 2012

Need More Interrupts To Read More RC Channels ?

One limitation of the Arduino platform is that it only supplies two external interrupts, yet many RC Systems support 8 or more channels. Even in a two channel system you may want additional interrupts to handle inputs, so what can we do to interface an Arduino with these additional channels and sensors ?

The answer is to use the ATMega Pin Change Interrupt facility. If you want to read up on it, its covered in the datasheet, however you will quickly find that its limited.

Limitations of Pin Change Interrupts - 

1) The interrupt is handled per port, so groups of pins share a single interrupt handler
2) You will need to write your own code to work out which of the group of pins caused the interrupt
3) The interrupt is triggered for any pin change so you have no control over the interrupt 'sense' as you do with the Arduino function attachInterrupt which supports the following - RISING, CHANGE, FALLING and LOW senses.

It is possible to code around these limitations to build equivalent functionality to the 2 standard interrupts, and that is just what a group has been collaborating to produce. The pinChangeInt library provides near identical functionality to the built in interrupts (INT0,INT1) but provides this function for any of the Arduino IO pins, this gives us up to a total of 21 external interrupts.

Features of PinChangeInt - 

1) Available as a library to download from google code group
2) Provides individual interrupt handler functions for upto 21 external interrupts
3) The pin number is available for access inside the interrupt handler
4) The pin state is available inside the interrupt handler
5) The functionality is near identical with the Arduino attachInterrupt function including the ability to set the interrupt sense (RISING,FALLING,CHANGE,LOW)

So how do we use this library ?

Part 1) Installation

1) Download the zip file from the google code group http://code.google.com/p/arduino-pinchangeint/
2) Copy the three directories cppfix, MemoryFree and PinChangeInt into the libraries folder of your Arduino directory, mine is here - 'C:\arduino-1.0-windows\arduino-1.0\libraries\PinChangeIn'

Part 2) Adding the library to a project

1) To add the library to a project, open the project in the IDE
2) In the 'Sketch' menu you should see a menu item 'Import Library'
3) Select import library and you should see a drop down list of the libraries you have installed, from this list select 'pinChangeInt'
4) You should now see the line #include <PinChangeInt.h> added near the start of you sketch

The library is now installed and included in your project ready for use.

Using and Comparing PinChangeInt with Arduino attachInterrupt

The project group have worked hard in designing the library to work just like the built-in attachInterrupt function. The only major difference in usage is that PCIntPort::attachInterrupt is defined as a static member function of a class PCIntPort. This class does the work of figuring out which interrupt service routine to call for which pin. The use of a static function within a class means that we need to call the attachInterrupt function with the following syntax PCintPort::attachInterrupt.

If your interested to see the work that is being done for you, open the PinChangeInt.h file in a text editor and have a look.


Usage Comparison

// standard Arduino attachInterrupt function
attachInterrupt(THROTTLE_IN_PIN,captThrottle,CHANGE);
attachInterrupt(STEERING_IN_PIN,captSteering,CHANGE);
// stop here, cant add any more channels, 
// we have used all 2 of our external interrupts !
 

// PinChangeInt library attachInterrupt function
PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE);
PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
 
PCintPort::attachInterrupt(FRONT_LEFT_PIN, captFrontLeft,RISING);
PCintPort::attachInterrupt(FRONT_RIGHT_PIN, captFrontRight,RISING);
PCintPort::attachInterrupt(REAR_LEFT_PIN, captRearLeft,RISING);
PCintPort::attachInterrupt(REAR_RIGHT_PIN, captRearRight,RISING);
// we can keep on going until we run out of pins
// or need to keep some pins for outputs !


Whats the cost ?

Using the prefix PCIntPort:: seems like a very low price to pay for getting access to ten times as many interrupts.

There must be some cost right ?

No, not really.

I have tested the library performance by using the Servo library to generate two servo signals. One of the servo signals goes to four Arduino pins to represent one each of the wheel speed sensors on my traction control project. The other servo signal is fed to three Arduino pins to represent the throttle, steering and auxiliary channels of my RC Transmitter. The impact of monitoring these seven inputs using the pinChangeInt library was measured at less than one percent (0.7%) of the available Arduino processing power.

I double checked the results with a quick calculation - 

The maths - 

Servo signal frequency 50Hz
Number of servo inputs 7
Number of interrupts  = 50 * 7 * 2 (2 = one for rising edge, one for  falling edge)

=  700 interrupts per second

Available Arduino Instructions in 1 second = 16,000,000
Possible available instructions per interrupt = 16,000,000/700 = 22,857

Measured impact using a timer and a busy loop suggested a 0.7% impact on available processing power while processing 700 pin change interrupts per second.

0.7% of 22,857 = 68.5 which suggests around 68 instructions are used to handle each pin change interrupt using the pinChangeInt library.

Conclusion

The minimal impact of the pinChangeInt library is a very reasonable trade off for access to more than 20 external interrupts.

Update: In subsequent tests, the performance impact of attaching an interrupt with PCintPort::attachInterrupt has been measured to be near negligible. In the worst case test there was a 5% overhead (over INT0,INT1 ), in the best case it was actually 15% faster. This out performance of PCintPort happens when one or more interrupts trigger more or less at the same instant. The pinChangeInt library is able to handle these within a single hardware interrupt. It will loop through the pins that have changed calling the user supplied interrupt service routines before passing control back to the hardware. This is more efficient than completing a single routine and then passing control back to the hardware each time. Note that this is a special case and most applications will not see any advantage from this. 

Notes - 

1) The interrupt service routing used in testing is very small as it should be. High frequency interrupts with long service routines are not a good idea with this or any other approach.

2) The four simulated wheel sensors are triggering interrupts, but they are attached with the 'RISING' sense so the library is trapping and ignoring them 50% of the time.

3) My assumptions and or maths may contain errors.

Stay Tuned ...

Duane B

No comments:

Post a Comment