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

15 comments:

  1. I should point out that the Pin Change Interrupts will not interrupt on LOW. Technically they interrupt only on CHANGE, and the PinChangeInt library is designed to appear to add interrupt on RISING or FALLING as well.

    The library's interrupt routine does a bit of analysis to determine if the change that called it was on a RISING or FALLING signal.

    In any event, I'm glad to see the library getting some use. I can't claim a credit for creating it, I'm just one of the current maintainers. But I do want to keep it relevant and useful because I think it's practically a necessity for the ATmega 168/328 and its ilk.

    - GreyGnome

    ReplyDelete
  2. BTW, I like your math where you measure 68 instructions per interrupt. I have looked at the assembly output from a sample program and noticed that the compiler generates the usual push/pop instructions for preserving the registers on the stack upon entering the interrupt subroutine (ISR). (Each push takes 2 clock cycles, btw). It takes 4 clock cycles to enter the ISR. Etc., etc... I think 68 clock cycles is fair (which appears to be 68 instructions but many of them like I say are 2 clock cycles).

    I discuss this a little bit at http://code.google.com/p/arduino-pinchangeint/wiki/Usage#Public_Methods_and_Variables, under the "pinState" variable.

    ReplyDelete
  3. Hi,
    Thanks for dropping by and also for your time in maintaining the library, it is as you say almost a necessity for the 168 and 328 and doesn't get anywhere near enough coverage.

    I will update the text to 'clock cycles' rather than 'instructions' based on your feedback and also remove the reference to LOW.

    Best Regards

    Duane B

    ReplyDelete
  4. I understand how this works in Arudino. But I don't know what you're using it for. You're measuring the duty cycle of PWM from an R/C receiver? The position of the servo? Let me try to find your ISR then I will understand.

    ReplyDelete
  5. Its not really about what I am using it for, its more that many people believe that they need a Mega based Arduino for projects requiring more than two interrupts. This post shows that there is a very easy to use way to access over 20 interrupts on the less expensive eaisly built 328 based Arduinos.

    Duane B

    ReplyDelete
  6. Hi!

    I'm happy! Third day I have my first arduino and I can translate signal from my rc command to serial for my computer.

    Next step using serial data to use my rctx like a joystik on linux.

    And then, ... Ok for linux rc flight simulator


    Thanks for your job!

    Gilbert D.

    ReplyDelete
  7. it deleted on top: "PinChangeInt.h"

    ReplyDelete
  8. Hi, sory if I spamed with code which I sent before, here is link to read my code: http://www.text-upload.com/read,3885807021378

    Please help and please don't delete this post.
    So I have problems with getting signal from 4 Channel RC Receiver and reading values in serial monitor. I use to this Arduino Mega1280. I found libraries which allow me to use PinChangeInt on this board. In sketch I had to add: "#define GET_PCINT_VERSION" and "uint16_t getPCIntVersion ();". But when I had uploaded sketch to board I saw nothing in serial monitor. I'm really newbie in Arduino. Please help:(

    ReplyDelete
  9. Hi,
    If your using a Mega1280, you might have enough interrupts to use directly. The only reason I am using the pinchangeint library is because an Arduino UNO only has two external interrupts, I assume that the Mega has at least 4. To use them you will need to find out which pins there available on, then all you need to do is replace PCInt::attachInterrupt with just attachInterrupt.

    Duane

    ReplyDelete
  10. Thanks Duane! Now it works great. You,re right, Mega has at least 6interrupts (num-pin): 0-2, 1-3, 2-21, 3-20, 4-19, 5-18, so we can easy make 6channel "reader" on this board:) Here is code if sb got same problem, enjoy: http://www.text-upload.com/read,3893447244793

    ReplyDelete

  11. Hello can_I_trade

    first I want to thank all the authors of this wonderful work!

    I would like to ask you something.
    I read in your article that describes the operation of the library, as follows:
    ---------------------
    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.
    ---------------------

    I am making a control system for hydraulic dual servo valve, which must work for a long time in a safe manner.
    " remote controlled tracked transport system ".

    I would like to use your library in order to remotely control the hydraulic valves.

    the phrase written by you, I will rise some doubts and would like to understand what hardware or software problems can arise, High frequency interrupts with long service routines?

    Arduino goes to fire or stop working while my crawler is rescuing a wounded ? :-))

    thanks
    Savior

    ReplyDelete
    Replies
    1. The term 'interrupt service routine' or just 'service routine' refers to the code which runs whenever an input signal changes. An RC Channel signal changes 100 times each second. If we tried to run code which took 1 second to complete each time, we could not because we only have .01 of a second before the next change arrives. Luckily we can read a signal change in about 4micro seconds so can easily read 1000's of changes each second.

      If you are concerned about how long an Arduino can run, your batteries or fuel will run out long before anything bad happens to your Arduino.

      Duane

      Delete
  12. Hi Duane
    I am very grateful for the work you have done !
    My system working perfectly, thanks to your library, that i has simplified enormously the hardware development.

    sincerely
    Savior

    ReplyDelete
  13. Hi, thanks alot for your code! worked like a charm the first time. I wonder how i could include a limit switch on one or more channels for a speed control used on a robot?

    ReplyDelete
  14. The Arduino.cc is soooo 2013 and all is based on a blink for an UNO. Happy to find a library what works on a 2560

    ReplyDelete