Showing posts with label RC Receiver. Show all posts
Showing posts with label RC Receiver. Show all posts

Thursday, April 25, 2013

Reading RC Channels with Arduino Due

Update 27/04/2013

The following code was originally presented as a test sketch to demonstrate a glitch in the Arduino Due micros function which is being resolved. See the following post for details of the glitch and resolution.

http://arduino.cc/forum/index.php/topic,162787.0/topicseen.html

With this resolution in place the code presented below can be used to read 8 RC Channels and output them to a combination of upto 8 RC Servos or ESCs.

The code can be operated in two configurations -

1) Loop Back Test - Here 8 servo outputs are created and given fixed values from 1100 to 1800, these pulse values are output on pins 10 to 17 and can be read back in through the interrupts attached to pins 2 to 9. This is intended to give the user confidence that the code is able to read multiple RC Channels before moving to configuration 2 - Pass Through

2) Pass Through -  This is similar to 1) above however the input is now connected to the output such that a change in an incoming signal will cause a corresponding change in the servo output signal. To implement pass  through on any channel, simply remove the comment form the start of the servo.writeMicrosecond command for that channel for example

Change this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }
to this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }




The code has been ported from an original project based on the Arduino UNO, follow the links in the comments for the background and detailed explanation.

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading and writing eight RC Channels using Arduino Due 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 "Servo.h"

// Assign your channel in pins
#define CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN 14
#define CHANNEL6_OUT_PIN 15
#define CHANNEL7_OUT_PIN 16
#define CHANNEL8_OUT_PIN 17

// 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 servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// holds the update flags defined above
volatile uint32_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 uint32_t unChannel1InShared;
volatile uint32_t unChannel2InShared;
volatile uint32_t unChannel3InShared;
volatile uint32_t unChannel4InShared;
volatile uint32_t unChannel5InShared;
volatile uint32_t unChannel6InShared;
volatile uint32_t unChannel7InShared;
volatile uint32_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  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
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // attach the interrupts used to read the channels
  attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

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 uint32_t unChannel1In;
  static uint32_t unChannel2In;
  static uint32_t unChannel3In;
  static uint32_t unChannel4In;
  static uint32_t unChannel5In;
  static uint32_t unChannel6In;
  static uint32_t unChannel7In;
  static uint32_t unChannel8In;
 
  // local copy of update flags
  static uint32_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 & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }
   
    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
    // 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 unChannel1, unChannel2, unChannel3, unChannel4, unChannel5, unChannel6, unChannel7, unChannel8
  // variables unChannel1InShared, unChannel2InShared, etc are always owned by the
  // the interrupt routines and should not be used in loop
 
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println();
      Serial.print(unChannel1In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL4_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel4.writeMicroseconds(unChannel4In);
    Serial.print(unChannel4In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel5.writeMicroseconds(unChannel5In);
    Serial.print(unChannel5In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel6.writeMicroseconds(unChannel6In);
    Serial.print(unChannel6In);
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel7.writeMicroseconds(unChannel7In);
    Serial.print(unChannel7In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel8.writeMicroseconds(unChannel8In);
    Serial.print(unChannel8In);
  }
 
  bUpdateFlags = 0;
}

void calcChannel1()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL1_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL2_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL3_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL4_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL5_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL6_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL7_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL8_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}


Wednesday, April 24, 2013

Problem reading RC Channels - The RCArduino Loop Back Test

Many people experience problems adding additional channels to the RCArduino test sketches or porting them to new devices, its usually down to mistakes in the way the additional channels are coded

This post presents an example of managing 8 input/output channels, its a very simple sketch which outputs 8 RC Channels and reads them right back in again.

The sketch can be used in three steps -

1) Initially the sketch outputs fixed values for the servos and reads them back in as a loop back test.

2) Once you are comfortable with the code, you can replace one or more of the loop back connections to start reading RC Channels. 

3) Once you are confident that the sketch is reading your channels correctly you can connect your servos and ESCs to the outputs and your done, 8 channels in, 8 channels out in three easy steps.


Step 1 - Loop back testing
To use the loop back test in step 1, connect pins 2 to 9 (the channel inputs) to pins 10 to 13 and A0 to A3 (the channel outputs).

Each channel is set to a fixed output from 1100 to 1800, if the code works on your board, your should see these values being output in the serial monitor.

Step 2 - RC Channel reading

Once you have this up and running, you can start swapping the connections to an RC receiver one by one. You should now see the channel values for the receiver channels updating in your serial monitor. If the values are within a range of around 1000 to 2000, your ready to move to step 3.

Step 3 - Full Control
To start outputting the values that you have read in, remove the comments from the servoName.writeMicroseconds functions to have full control of upto 8 servos/escs using 8 RC Receiver channels.

If it does not work, let me know which board you have, the code is easily adapted to work on any of the 8-bit Arduino boards. If you have a 32 bit Arduino Due, there is a dedicated post here -

http://rcarduino.blogspot.ae/2013/04/reading-rc-channels-with-arduino-due.html

For more performance and smoother output on the 8-bit Arduinos an RCArduinoFastLib versions will be added in separate in the coming days.

// RCArduino MultiChannel Loop back and servo ESC control for upto 8 RC channels
//
// 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 CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN A0
#define CHANNEL6_OUT_PIN A1
#define CHANNEL7_OUT_PIN A2
#define CHANNEL8_OUT_PIN A3

// 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 servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// 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 unChannel1InShared;
volatile uint16_t unChannel2InShared;
volatile uint16_t unChannel3InShared;
volatile uint16_t unChannel4InShared;
volatile uint16_t unChannel5InShared;
volatile uint16_t unChannel6InShared;
volatile uint16_t unChannel7InShared;
volatile uint16_t unChannel8InShared;

void setup()
{
  Serial.begin(115200);

  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
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // using the PinChangeInt library, attach the interrupts
  // used to read the channels
  PCintPort::attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  PCintPort::attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  PCintPort::attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  PCintPort::attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  PCintPort::attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  PCintPort::attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  PCintPort::attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  PCintPort::attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

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 unChannel1In;
  static uint16_t unChannel2In;
  static uint16_t unChannel3In;
  static uint16_t unChannel4In;
  static uint16_t unChannel5In;
  static uint16_t unChannel6In;
  static uint16_t unChannel7In;
  static uint16_t unChannel8In;

  uint8_t bUpdateFlags = 0;
  // check shared update flags to see if any channels have a new signal
  // for nicely formatted serial output use this
  if(bUpdateFlagsShared == 0xFF)
  // for more responsive projects update any channels whenever a new signal is available using this
  // 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 & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }

    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
  
    // 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
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println("");
      Serial.print(bUpdateFlags);
      Serial.print(",");
      Serial.print(unChannel1In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL4_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel4.writeMicroseconds(unChannel4In);
      Serial.print(unChannel4In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel5.writeMicroseconds(unChannel5In);
      Serial.print(unChannel5In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel6.writeMicroseconds(unChannel6In);
      Serial.print(unChannel6In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel7.writeMicroseconds(unChannel7In);
      Serial.print(unChannel7In);
      Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
      // when you are ready to add a servo or esc, remove the // from the following line to use the channel input to control it
      // servoChannel8.writeMicroseconds(unChannel8In);
      Serial.print(unChannel8In);
      Serial.print(",");
  }
}


void calcChannel1()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState) // this is equivalent to digitalRead(CHANNEL1_IN_PIN) but about 10 times faster
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(PCintPort::pinState)
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint16_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}
Duane B


Sunday, November 11, 2012

How To Read RC Receiver PPM Stream

Many RC Transmitters and Receivers provide access to the PPM Stream, this is a single stream of pulses which includes the information for all of the receiver channels in a single connection. 

This stream can often be accessed by removing the receiver case and adding a single wire to an existing connection within the receiver.

If we can gain access to this stream we can write smaller, faster code and only need a single interrupt pin to read all of the receiver channels.

Using a single built in interrupt to read multiple channels is much faster than using pin change interrupts which leads to a visibly smoother output signal for your servos and ESCs.

This post concludes with a library which can be used to read a PPM stream using a single interrupt. The same library is also able to output upto 18 servo signals from a single Arduino UNO with no additional components. This is an increase of 6 servos over the standard library - its also faster leading to fewer glitches.

Scroll down for a video of the library and an RC Receiver hack in action -

What does the PPM Stream Look Like ?




The stream is made up of a series of short pulses, the first pulse is the start marker. The time between the start marker and the start of the next pulse defines the pulse length for channel one. The time to the next pulse defines the pulse length for channel 2 and so on. The end of the pulse stream is marked by a gap know as the frame space. This gap indicates that there are no more channels to receive and the next pulse will be the start of a new frame. Each frame contains the pulse widths for all of your receiver channels.

Note - unlike servo signals, in a PPM Stream it is the gaps between pulses that defines the pulse width for a channel, not the duration of the pulse itself which can be very short.


How do we find the PPM Stream ?
If your equipment provides direct access to the PPM Stream, skip over this part, if it does not, read on.

The PPM Stream is transmitted between your transmitter and receiver as a single data stream. Inside the receiver this signal is de-multiplexed to produce the individual channels signals as seen in the diagram.


Fortunately for us, the de-multiplexer is most often just a simple shift register clocked by the PPM Stream.


The shift register is clearly visible inside this Hitec HFS-03MM Receiver - its the IC in the center.

The PPM Stream is routed to the clock pin (clock A) of the shift register, the PWM Streams for the individual channels are taken from the shift register outputs (Q1a,Q2a,Q3a).

Usually we would try to read these outputs using three separate interrupt pins, however as they are directly derived from the clock signal we can access the same information by reading the (PPM) clock signal directly. This saves us  two interrupt pins and  a lot of code and memory.

The best bit - read on and theres a ready made library at the end of the post.

Example Datasheet for the 4015 Shift Register used to demultiplex the PPM Signal in the pictured receiver
http://docs-europe.electrocomponents.com/webdocs/05f9/0900766b805f9f8d.pdf

Breaking out the PPM Signal
As above, if your receiver already provides access to the PPM Stream, skip ahead, if not, here are two receivers I have hacked. Its as simple as follows -

To access the PPM Stream from Arduino we need to solder an additional wire to the clock pin of the 4015 shift register.

Example PPM Hack - 1

Hitec 27Mhz FM 3 Channel HFS03MM Receiver - tapping the 4015 shift register to access PPM signal.






Example PPM Hack - 2

Different Receiver, Different Manufacturer, Different Technology - Futaba R152JE 27Mhz AM

The Same 4015 Shift Register inside tapped for PPM Output using thin white wire soldered to the clock pin.


A male jumper wire attached to the hacked Hitec receiver ready for connection to Arduino -


VIDEO - The RC Arduino Library and Receiver Hack in action 


Schematic showing connections in the video above


Multiplexing Servos From Arduino Using PPM Style Signals

In a recent RCArduino Post we showed how a 4017 Decade counter IC could be used to control 10 servos from a single Adruino pin. Each time the counter is clocked by the Arduino it sets one servo output low and sets the next one high. We control the clock pulses to control the pulse widths for the individual servo outputs. Demulitplexing a PPM signal is essentially the same process, the RC Receiver applies a clock pulse to a shift register which shifts the pulse from one output to the next, the longer between clock pulses, the longer the servo pulse.

RC Arduino Serial Servos

Introduction and 10 Servos from 2 Pins
http://rcarduino.blogspot.com/2012/08/arduino-serial-servos.html

20 Servos From 4 Pins
http://rcarduino.blogspot.com/2012/10/arduino-serial-servos-20-servos-4-pins.html

How do we read this RC Receiver PPM Pulse stream with a micro controller ?

Now that we have access to the PPM Stream, how do we read it with our Arduino ?

First of all we need to synchronise with the pulse stream, we do this by waiting for the long pause (the frame space) which indicates the end of one frame and the start of the next.


1) Once we have found this space, we can set our channel counter to 0 and record the current time.

2 ) The next pulse that arrives will indicate the end of the channel 1 pulse. We calculate the channel one pulse width by subtract the time recorded in 1) above from the current time. We also store the current time as the starting point for the channel 2 pulse width

3) We repeat the above process - subtract the last pulse time from the current time to get the pulse width for each of the remaining channels

4) When we have received all of the channels we expect, we start again at 1.

At each stage of the process 1-4 we know whether we are expecting a channel signal or a frame space and so we can use this information to confirm synchronization with the PPM Stream.

Sample Arduino Code For Reading RC Receiver PPM Signal

The following code is taken from the RCArduinoFastLib -

// we could save a few micros by writting this directly in the signal handler rather than using attach interrupt
void CRCArduinoPPMChannels::INT0ISR()
{
  // only ever called for rising edges, so no need to check the pin state
 
  // calculate the interval between this pulse and the last one we received which is recorded in m_unChannelRiseTime
  uint16_t ulInterval = TCNT1 - m_unChannelRiseTime;
 
  // if all of the channels have been received we should be expecting the frame space next, lets check it
  if(m_sCurrentInputChannel == RC_CHANNEL_IN_COUNT)
  {
    // we have received all the channels we wanted, this should be the frame space
    if(ulInterval < MINIMUM_FRAME_SPACE)
    {
     // it was not so we need to resynch
     forceResynch();
    }
    else
    {
      // it was the frame space, next interval will be channel 0
      m_sCurrentInputChannel = 0;
    }
  }
  else
  {
    // if we were expecting a channel, but found a space instead, we need to resynch
    if(ulInterval > MAXIMUM_PULSE_SPACE)
    {
      forceResynch();
    }
    else
    {
     // its a good signal, lets record it and move onto the next channel
     m_unChannelSignalIn[m_sCurrentInputChannel++] = ulInterval;
    }
  }
  // record the current time
  m_unChannelRiseTime = TCNT1; 



Reading the PPM stream with the RCArduinoFastLib

One of the main features of the RCArduinoFastLib is a servo library, why do we need another servo library when the existing servo library is well know, widely used and reliable ?

The standard Arduino Servo library has one major flaw - it resets timer1 at the start of every frame. This means that timer1 cannot be used for timing as easily as you might want. A common approach to overcoming this is to use the micros() function for timing, but this is many times slower than accessing TCNT1 directly.

As our channel input timing and servo output timing is interrupt driven, we really care about speed, every little bit of inefficiency leads to more and bigger interrupt clashes which introduce the ticks and jitter often seen in Arduino based RC projects.

By using the RCArduinoFastServos library we avoid resetting timer1, gain an additional six servos and also the added benefit of being able to user timer1 for very fast timing of our input signals. These lead to a measurable increase in output signal quality with fewer and smaller clashes in short - glitch free projects.

You can find the RCArduinoFastServos library in this post -
http://rcarduino.blogspot.com/2012/11/how-to-read-rc-channels-rcarduinofastlib.html

Sample Sketch - Read PPM Input and Output To Mulitple Servos

A sample sketch you can use to read three channels or PPM is presented below. The sketch can easily be modified to read upto 10 channels.

If you need help or want to ask a question - ask away.

#include <RCArduinoFastLib.h>

 // 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
//

// Assign your channel out pins
#define THROTTLE_OUT_PIN 8
#define STEERING_OUT_PIN 9
#define AUX_OUT_PIN 10
#define OTHER_OUT_PIN 11

// Assign servo indexes
#define SERVO_THROTTLE 0
#define SERVO_STEERING 1
#define SERVO_AUX 2
#define SERVO_OTHER 3
#define SERVO_FRAME_SPACE 4

volatile uint32_t ulCounter = 0;

void setup()
{
  Serial.begin(115200);

  // 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

  CRCArduinoFastServos::attach(SERVO_THROTTLE,THROTTLE_OUT_PIN);
  CRCArduinoFastServos::attach(SERVO_STEERING,STEERING_OUT_PIN);
  CRCArduinoFastServos::attach(SERVO_AUX,AUX_OUT_PIN);
  CRCArduinoFastServos::attach(SERVO_OTHER,OTHER_OUT_PIN);
 
  // lets set a standard rate of 50 Hz by setting a frame space of 10 * 2000 = 3 Servos + 7 times 2000
  CRCArduinoFastServos::setFrameSpaceA(SERVO_FRAME_SPACE,6*2000);

  CRCArduinoFastServos::begin();
  CRCArduinoPPMChannels::begin();
}

void loop()
{
  // Pass the signals straight through - 

 
  uint16_t unThrottleIn =  CRCArduinoPPMChannels::getChannel(SERVO_THROTTLE);
  if(unThrottleIn)
  {
    CRCArduinoFastServos::writeMicroseconds(SERVO_THROTTLE,unThrottleIn);
  }

  uint16_t unSteeringIn =  CRCArduinoPPMChannels::getChannel(SERVO_STEERING);
  if(unSteeringIn)
  {
    CRCArduinoFastServos::writeMicroseconds(SERVO_STEERING,unSteeringIn);
  }

  uint16_t unAuxIn =  CRCArduinoPPMChannels::getChannel(SERVO_AUX);
  if(unAuxIn)
  {
   CRCArduinoFastServos::writeMicroseconds(SERVO_AUX,unAuxIn);
  }
}



/* DB 04/02/2012 REMOVED The interrupt service routine definition here, it clashes with the attachInterrupt in the cpp file */
/* REMOVE BEGIN 
ISR(INT0_vect) {
 CRCArduinoPPMChannels::INT0ISR();
}

REMOVE END */

Duane B