Showing posts with label Need More Interrupts. Show all posts
Showing posts with label Need More Interrupts. Show all posts

Saturday, April 14, 2012

How To Read Multiple RC Channels

This post builds on previous posts to show a technique for reading multiple radio control receiver channels using an Arduino.

Update 27/03/2013 -  The Arduino Leonardo and Micro use the ATMega 32u4 chip which supports interrupts on fewer pins than the ATMega 328 used in Arduino UNOs and Minis. The pins used by the sketch have been rearranged so that the code can now be run the Leonardo and Micro as well.

If you have previously used the code, please ensure that you update your connecteds by swapping the input and output pins.


The following posts provide the necessary background to this post -

In the first post in this series we looked at the nature of an RC Receiver signal and provided a non blocking interrupt based approach for reading this signal with Arduino -

In the next post we looked at the Arduino servo library and how this allows us to generate the same signals that a radio control receiver generates. We can use this library to drive upto 12 servos or electronic speed controllers -
In the final post, we looked at the pinchangeint library which provides a convenient mechanism to access over twenty interrupts with a standard Arduino UNO. This overcomes the limitation of having only two external interrupts and allows us to use a standard Arduino to read more than twenty RC Channels -


Reading RC Channel Signals

Reading a single RC channel is relatively simple and can be outlined as -

1) Attach a pin change interrupt to the signal pin.
2) Create an interrupt service routine which will be called whenever the signal pin changes from high to low or low to high.

In the interrupt service routine we need to -

1) Check if the signal pin is high or low
2) If its high, this is the rising edge of the pulse, record the time using micros()
3) If its low, this is the falling edge. We are interested in the pulse duration, so if we call micros() to get the current time and then subtract from it the time we recorded in 2) for the rising edge we get the pulse duration.

The pulse duration calculated in 3) Above represents the input signal for the channel, refer to this post for a refresher - http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html

To attach the interrupt using the pin change interrupt library we call -

PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);

The interrupt service routine can be written as follows -

void calcThrottle()
{
  // if the pin is high, its a rising edge of the signal pulse,
  // so lets record its value
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  {
    ulThrottleStart = micros();
  }
  else
  {
    // else it must be a falling edge, so lets get the time and subtract the time
    // of the rising edge this gives use the time between the rising and falling 
    // edges i.e. the pulse duration.
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);

  }
}

So all we need to do is cut and paste the attachInterrupt and interrupt service routine code three times to read three channels ?

No. Its not that simple, but its really not very complicated either.

There are four considerations which apply when using interrupts -

1) Volatile Shared Variables
2) Variable Access
3) Fast Interrupt Service Routines
4) Timing

1) Volatile Shared Variables

When you upload a program with the Arduino IDE, your code will be compiled into an executable program that can be run on your Arduino. As part of this process the compiler will optimise the executable code to be smaller and/or faster. If we are not careful, this optimization can have unexpected results when using interrupts.

Example optimisations -

1) Replacing variables with constants
One of the optimisations a compiler will attempt is to look through your functions for any variables that are not being updated, the compiler can replace these with constant values.

2) Using registers to hold variables within functions
Another optimisation is to assign a variable to a register within a function rather than reading it from memory repeatedly.



The compiler makes optimisation decisions based on blocks of code, not entire projects. A common optimisation problem with interrupts occurs when a variable is updated in an interrupt service routine and read in the main code. Here the compiler may look at the main code and decide 'hey this variable never gets updated so I am going to replace it with a constant value'. In this case whatever the ISR does to the shared variable, your main code will never be effected because as far as the compiler could see, there was nothing that could update the variable within loop and so the executable code that was downloaded to you Arduino was generated to refer to a constant value - see 1) Above. 

A similar situation occurs when a variable is optimised to be held in a register rather than read from memory. One part of your executable code will be reading a value held in memory while another part will be updating a value held in a register. The end effect is that it appears as if your variable is not getting updated despite your ISR being called correctly and your code appearing correct - see 2) above.

Fortunately there is a keyword we can use in our code which tells the compiler 'never try and store this variable in a register, or replace it with a constant' the keyword is volatile. Any variables we wish to access from both our main code and an interrupt service routine must be declared volatile.

Really ?

Yes, I recently built an Audino, its an easy to build, fun to use synthesizer based on Arduino. The loop function reads five analogue inputs which control the sound. These variables are also used in a timer based interrupt service routine which actually creates the sound.

When I first built the project it didn't work. Reading through the source code I noticed that the analog inputs read in loop and used in the ISR were not declared as volatile. Adding the volatile declaration to these five variables fixed the problem.

Interestingly, the Audino has been around for years, it is likely that it worked perfectly with earlier versions of the Arduino compiler, but newer version may be optimizing differently. Whatever the case, the volatile keyword should always be used to tell the compiler not to optimize our shared variables.


In our code we must declare the channel input values as volatile in order that we can update them in the ISR and read them in loop -

volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;


2) Variable Access

New Arduino programmers are often aware of the volatile keyword and assume that it is all that is needed to access shared variables. It isn't.

An interrupt service routine can be run at any point in your main code. Its called an interrupt because it interrupts your main code to do something else. This can lead to strange and difficult to trace errors if you do not control when your variables are being accessed.

How do these strange and difficult to trace errors happen ?

The simplest explanation is that if your main code is reading a variable when it is interrupted and the interrupting service routine changes the variable value, your main code will be left with part of the old value and part of the new value which is not what you intended or your code expected. This is often experienced as glitches blamed on hardware, but is in fact an easily solved software problem.

As a simple example a two byte integer changing from 256 to 255 in an interrupt service routine could be seen in loop as 511.

                                      Byte 1      Byte 2
Original value = 256 = 00000001 00000000
Updated value = 255 = 00000000 11111111

Loop reads byte 1 = 00000001

The ISR is called and updates the 2 byte integer from 256 to 255

Loop reads byte 2 = 11111111

Loop sees byte 1 + byte 2 as integer 0000000111111111 = 511

This unexpected value is a result of reading byte 1 before the interrupt and byte 2 after the interrupt. We must always ensure that our multi byte shared variables are read, updated or compared without any interrupts occuring.

To prevent this problem we need to ensure that whenever we are reading, writting or comparing a values larger than a single byte we cannot be interrupted until the operation is complete. The data types int, long and float are all multi byte data types ( int = 2 bytes, long = 4 bytes, float = 4 bytes) and therefore access must be controlled between the your main code and loop. The recommended approach to controlling access is to temporarily disable interrupts in order that your main code can perform a read, write or compare without being interrupted (remember the danger is that the interrupt will change part of the value that we have already read,written or compared in which case the results will be unpredictable and most likley lead to program errors - disabling interrupts for the read, write or compare prevents this).
  
To disable interrupts we call nointerrupts(), to enable then again we call interrupts().

// turn interrupts off 
noInterrupts();
    
    // Access your shared variables here ...

// finished with the shared variables, so turn interrupts back on
interrupts();

Note - While interrupts are disabled we will not be able to read incoming signals and this can effect our accuracy. See the example code where local variables as used to minimize the time that interrupts are disabled.
Using the status register directly vs interrupts() and noInterrupts()

Note - There is some criticism of noInterrupts and interrupts on the basis that the interrupts function reenables interrupts regardless of whether they were enabled or disabled before noInterrupts was called. This could cause problems where your code is called by a library which requires interrupts to be disabled. The preferred technique is to record the global status register, update the status register to disable interrupts, then restore it to its previous value - this maintains the status that was inplace before your code rather than blindly (and unsafely) restoring interrupts.

byte sregRestore = SREG;
cli() // clear the global interrupt enable flag

// Access your shared variables here ....

SREG = sregRestore; // restore the status register to its previous value


3) Fast Interrupt Service Routines

When an interrupt is triggered the microprocessor disables further interrupts before calling the interrupt service routine. While we are inside our interrupt service routine, we cannot process any new incoming events, therefore it is important for the accuracy of our input signal that we keep our interrupt service routines as short as possible.

As a guideline the interrupt service routine should record the occurrence of an event, but should not attempt to perform any extended processing in response to the event.

In our example of reading three receiver channels and outputting 3 servo signals we are processing 500 interrupts per second. The difference between an idle throttle and full throttle is only 0.5 thousandths of a second - timing is going to be important.

If we want our main code to process an event, we need some mechanism for the interrupt service routine to flag that an event has occurred, one option is the use of bit flags.

Bit Flags

We can use bit flags to signal to our main code that new information is available from an ISR. Bit flags take advantage of the fact that a micro processor can read a single byte without being interrupted.

How do but flags work ?

If we treat a byte as being made up of eight bits, b0 to b7, we can use each bit as a signal between our main code and ISRs.The bits have the decimal values 1,2,4,8,16,32,64,128 which we can use to set, test and clear the individual bit flags.

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4

// a single byte we can use to hold the update bit flags defined above
volatile uint8_t bUpdateFlagsShared;

to test, set and unset bit flags we can use -

// test to see if a flag has been set - use the bitwise AND operator &
if(bUpdateFlags & THROTTLE_FLAG)
{
   // flag was set ...
}

// set a bit flag - use the bitwise OR operator |
// set the throttle flag to indicate that a new throttle signal has been received
bUpdateFlagsShared |= THROTTLE_FLAG;

// clear a bit flag - use the bitwise XOR operator ^
bUpdateFlagsShared ^= THROTTLE_FLAG;


Bit flags provide us with a simple mechanism to communicate new events between the ISRs and loop. This is particularly important where we need to perform an extended calculation in response to the event. In the example code, we use the bit flag to tell loop that a new signal is available from a channel, loop then uses this information to updated the output for the channel.

4) Timing

We have already covered two of the issues which will effect our accuracy, we can add library functions for a more complete list as presented below -

1) Library functions that temporarily disable interrupts - some library functions will temporarily disable interrupts we cant control this but we can be careful about where we use these functions
2) Disabling interrupts in our own code - we will need to do this, but we can take care to minimise the time that interrupts are disabled.
3) Interrupt Service Routines - the microprocessor automatically disables interrupts before calling them and reenables them afterwards. Again we have to live with this but can be careful to minimize the time spent inside the ISR so that interrupts are enabled again as quickly as possible.

Putting it all together

The following code sample can be used to read three RC Channels through one set of Arduino pins and output those same channels on another set of pins. The code uses dedicated interrupt service routines for each channel, this is for clarity and could be changed to use a single interrupt service routine for all channels.

The code demonstrates the use of -

 - Bit flags to communicated events between the ISRs and Loop
 - Interrupt service routines to read rc channel signals
 - Enabling and disabling interrupts
 - Volatile shared variables
 - Taking local copies of variables to minimise the time that interrupts are disabled
 - Using the servo library to output RC Channels to servos or electronic speed controllers
 - Using the pin change interrupt library to access additional interrupts.

The sample code below the video was used in creating the video. A step by step guide to connecting the ESC, Steering servo, RC Receiver and Arduino will be provided in a followup post.


With two channels - throttle and steering - being read from the receiver and output to the car by Arduino


Sample Code - MultiChannels rcarduino.blogspot.com

// MultiChannels
//

// rcarduino.blogspot.com
//
// A simple approach for reading three RC Channels using pin change interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

// include the pinchangeint library - see the links in the related topics section above for details
#include <PinChangeInt.h>

#include <Servo.h>

// Assign your channel in pins
#define THROTTLE_IN_PIN 8
#define STEERING_IN_PIN 9
#define AUX_IN_PIN 10

// Assign your channel out pins
#define THROTTLE_OUT_PIN 5
#define STEERING_OUT_PIN 6
#define AUX_OUT_PIN 7

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoThrottle;
Servo servoSteering;
Servo servoAux;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define THROTTLE_FLAG 1
#define STEERING_FLAG 2
#define AUX_FLAG 4

// holds the update flags defined above
volatile uint8_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint16_t unThrottleInShared;
volatile uint16_t unSteeringInShared;
volatile uint16_t unAuxInShared;

// These are used to record the rising edge of a pulse in the calcInput functions
// They do not need to be volatile as they are only used in the ISR. If we wanted
// to refer to these in loop and the ISR then they would need to be declared volatile
uint32_t ulThrottleStart;
uint32_t ulSteeringStart;
uint32_t ulAuxStart;

void setup()
{
  Serial.begin(9600);
 
  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers 
  servoThrottle.attach(THROTTLE_OUT_PIN);
  servoSteering.attach(STEERING_OUT_PIN);
  servoAux.attach(AUX_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(THROTTLE_IN_PIN, calcThrottle,CHANGE);
  PCintPort::attachInterrupt(STEERING_IN_PIN, calcSteering,CHANGE);
  PCintPort::attachInterrupt(AUX_IN_PIN, calcAux,CHANGE);
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained
  // between calls to loop.
  static uint16_t unThrottleIn;
  static uint16_t unSteeringIn;
  static uint16_t unAuxIn;
  // local copy of update flags
  static uint8_t bUpdateFlags;

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
   
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
   
    if(bUpdateFlags & THROTTLE_FLAG)
    {
      unThrottleIn = unThrottleInShared;
    }
   
    if(bUpdateFlags & STEERING_FLAG)
    {
      unSteeringIn = unSteeringInShared;
    }
   
    if(bUpdateFlags & AUX_FLAG)
    {
      unAuxIn = unAuxInShared;
    }
    
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
   
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
 
  // do any processing from here onwards
  // only use the local values unAuxIn, unThrottleIn and unSteeringIn, the shared
  // variables unAuxInShared, unThrottleInShared, unSteeringInShared are always owned by
  // the interrupt routines and should not be used in loop
 
  // the following code provides simple pass through
  // this is a good initial test, the Arduino will pass through
  // receiver input as if the Arduino is not there.
  // This should be used to confirm the circuit and power
  // before attempting any custom processing in a project.
 
  // we are checking to see if the channel value has changed, this is indicated 
  // by the flags. For the simple pass through we don't really need this check,
  // but for a more complex project where a new signal requires significant processing
  // this allows us to only calculate new values when we have new inputs, rather than
  // on every cycle.
  if(bUpdateFlags & THROTTLE_FLAG)
  {
    if(servoThrottle.readMicroseconds() != unThrottleIn)
    {
      servoThrottle.writeMicroseconds(unThrottleIn);
    }
  }
 
  if(bUpdateFlags & STEERING_FLAG)
  {
    if(servoSteering.readMicroseconds() != unSteeringIn)
    {
      servoSteering.writeMicroseconds(unSteeringIn);
    }
  }
 
  if(bUpdateFlags & AUX_FLAG)
  {
    if(servoAux.readMicroseconds() != unAuxIn)
    {
      servoAux.writeMicroseconds(unAuxIn);
    }
  }
 
  bUpdateFlags = 0;
}


// simple interrupt service routine
void calcThrottle()
{
  // if the pin is high, its a rising edge of the signal pulse, so lets record its value
  if(digitalRead(THROTTLE_IN_PIN) == HIGH)
  {
    ulThrottleStart = micros();
  }
  else
  {
    // else it must be a falling edge, so lets get the time and subtract the time of the rising edge
    // this gives use the time between the rising and falling edges i.e. the pulse duration.
    unThrottleInShared = (uint16_t)(micros() - ulThrottleStart);
    // use set the throttle flag to indicate that a new throttle signal has been received
    bUpdateFlagsShared |= THROTTLE_FLAG;
  }
}

void calcSteering()
{
  if(digitalRead(STEERING_IN_PIN) == HIGH)
  {
    ulSteeringStart = micros();
  }
  else
  {
    unSteeringInShared = (uint16_t)(micros() - ulSteeringStart);
    bUpdateFlagsShared |= STEERING_FLAG;
  }
}

void calcAux()
{
  if(digitalRead(AUX_IN_PIN) == HIGH)
  {
    ulAuxStart = micros();
  }
  else
  {
    unAuxInShared = (uint16_t)(micros() - ulAuxStart);
    bUpdateFlagsShared |= AUX_FLAG;
  }
}

Coming soon - A walk through connecting the three radio channels to the Arduino and then the processed signals back out to a car, robot or other application.

Duane B

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