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


20 comments:

  1. Hi Duane! Maybe you can help me out!
    I'm able to compile and run your code. Generating the PPM-Signal seems to work fine (i cheked it with a logic analyser). But i think the input interrupts don't work. In the Serial Monitor i only see "multiChannels". I'm working on an Arduino Mega 1280.

    ReplyDelete
  2. Hi,
    This has an easy solution, the Arduino UNO, Leonardo, Mega 1280 and Mega 2560 are all based on different chips. Each chip has the interrupts attached to different pins. Before I suggest a solution for you, how many channels do you need ?

    Duane B

    ReplyDelete
  3. I would need 8 Channels. But i could use a PPM Sum on only one Pin, right!?

    ReplyDelete
  4. Sure - http://rcarduino.blogspot.ae/2012/11/how-to-read-rc-receiver-ppm-stream.html

    Duane B

    ReplyDelete
  5. Duane, I've wired the loop-back on a Arduino UNO. This is (part of) the serial monitor result:
    255,1112,1204,1304,1404,1504,1620,1708,1796,
    255,1108,1204,1304,1404,1504,1620,1708,1796,
    255,1108,1204,1300,1404,1504,1624,1708,1796,
    255,1112,1204,1300,1404,1500,1624,1712,1800,
    255,1112,1204,1304,1404,1504,1620,1712,1800,
    255,1112,1204,1304,1400,1504,1620,1708,1796,
    255,1108,1204,1304,1404,1504,1624,1708,1796,
    255,1108,1204,1300,1404,1504,1624,1708,1796,
    255,1112,1204,1304,1404,1504,1624,1712,1800,
    My Pro Mini is soldered into a project, can't easily do the loop-back wiring. I've ordered another Pro Mini, with which I will do just that. Not sure if results are expected to be different for UNO and Pro Mini.

    My initial reason for commenting had to do with using PWM out, which doesnt work (Pro Mini) on pin 9 and 12, while it does on 3,5,6. For my project I figured I can do without PWM and just emulate by quickly switching via digitalWrite (not tested yet).

    Does this info give you any clue, as you requested in my previous comment?

    Thanks!! JH

    ReplyDelete
  6. Duane, full test runs fine (I was initially confused by the channel reading 255, which is the flag, not a channel). Receiving works on all channels, sending also. Now starting to add PWM on the Uno...
    Regards, JH

    ReplyDelete
  7. Duane, me again. In the loopback test script, everything seems to stop if I comment out the line
    PCintPort::attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);
    I don't know why, can u clarify? I'm working towards 3 RC in channels and 1 servo out channel.
    Other tests with PWM give very unclear results, mostly increased servo jitter.
    Hope to trigger a clue by commenting, while I keep searching on myself... Thanks, JH

    ReplyDelete
    Replies
    1. I suggest using the original code posted above for now and interface your RC Receiver one channel at a time without making any changes to the code.

      Once you have the channels up and running I can advise the changes you need for your 3 channel, 1 Servo and PWM Project, but for now, lets just get the channels working.

      If you following this procedure and seeing unexpected results it is almost certainly due to the wiring, do you have a common ground between the receiver and the Arduino ? How are you powering the Arduino ?

      Duane B

      Delete
    2. "it is almost certainly due to the wiring", that most likely has been it. My Mini project is working fine now, though I'm not fully sure what did it in the end. It's not your library, which now works great! Thanks for that JH

      Delete
  8. Hey Duane,

    I'm having an issue with the loopback test on my Pro Micro, which uses the ATmega32U4.
    I tried the initial sketch and wasn't getting any serial output back.

    I tried commenting everything in the setup and void loops out, then adding a simple Serial.println("test"); to the loop to see if the board was responding. This test worked, and I got my response back.

    Then I tried uncommenting the setup loop, and the problem came back. I uncommented it line by line and it appears that the issue is in the PinChangeInt library. The program just locks up when it gets to that point. Any ideas?

    ReplyDelete
  9. There is a big difference between the ATMega328 used in UNOs and the 32u4 in the Leonardo/micro. The UNOs support pin change interrupts on any pin whereas the Micro only has 6 of them.

    Let me know how many channels you need to read and I can let you know - 1) if its going to be possible with the mico and 2) the changes you will need to make

    Duane B

    ReplyDelete
  10. Hey Duane,

    I only need to read 2 channels, but also retain 2 interrupts for my wheel speed sensors (4 total). It's possible with the stock interrupt routine, just inconvenient (I'd have to detach my wheel speed sensors to program).

    Thanks for the help,
    Victor

    ReplyDelete
  11. I have set up the sketch just as describe above, using a Uno. The loop back seems to work just fine. However, I comment out channel one and attach a servo, (yes, it has a separate power rail and yes, everything has a common ground. However, the serial monitor shows "multichannels", and then nothing. The servo does not respond. This is a simplified explanation, I have quadruple checked libraries, the sketch, and all hardware. The system seems to just hang.
    Rick Harms
    rick@inharmsway.org.
    PS The Arduino forum seems to be non-operational.

    ReplyDelete
  12. Hi,
    No need to comment out anything, just swap the wire that connects the output to the input so that the input is connected to your receiver. You should see each channel that you swap over is now being controlled by your transmitter/receiver there is no need to change any of the code to do this.

    Once you have your channels up and running this way - i.e. updating in the serial monitor - I will advise you how to start controlling the servos.

    Let me know how you get on

    Duane B

    ReplyDelete
  13. Thank you
    OK, I did as you advised, it works fine. I have one channel of the receiver connected and can indeed see the channel change in the serial monitor.
    A clarification from my previous post. My next step, I thought, would be to comment out this: servoChannel1.writeMicroseconds(1100); and uncomment this: // servoChannel1.writeMicroseconds(unChannel1In);
    I await your correction. :-) Thank you
    PS I am also having compile problems with RCArduinoFastLib, but, for now, one step at a time

    ReplyDelete
  14. Hi,
    In order to get nicely formatted output, the loopback code waits to get a signal from all of the channels before it prints anything. My guess is that in you earlier test you disconnected a channel in hardware or software. Now that you have things working as expected, your right about the change you need to make - just uncomment the line that writes a fixed value and write out the value that is read in instead.

    Let me know how it goes

    Duane B

    ReplyDelete
  15. OK, got it working. Thank you.
    Now, I went ahead and set up channel two, (in and out) and got it working.
    My goal is to build a "super" mixer. So, I decided to use the input channel one to drive the servo channels one and two. I need to go through the code, get a better handle on how it works. I see that even if i am not using, for example, the channel two input, it needs to have input or everything comes to a halt.
    Next step, I want to drive 16 servos. The Uno, by itself, cannot do that.
    Options
    1. Use the Mega?
    2. Use your fast lib and the 4017 decoder.
    3. use the I2C 10 bit servo driver board from Adafruit (16 channels)
    4. Use the 8 bit serial connected servo driver board from Pololu (up to 24 channels)

    There are pros and cons for each of the above. This is my idea of fun. I think you understand :-)
    Rick Harms

    ReplyDelete
  16. I would normally react to a channel whenever I get a signal on that channel, regardless of whether I have signals on any other channel. This works great in real projects, however for the loop back where you initially want to see nicely formatted result I wait for a signal on all channels before printing the inputs.

    To change this to the more responsive behaviour, see the comments in the code here -

    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)

    Your servos will work just fine, but your serial output will look a mess - now that you are starting to add servos you can start removing the serial output anyway.

    As for lots of servos, it depends on how complex the logic to drive them will be, you can drive them using two 30 cent components and this library http://rcarduino.blogspot.ae/2012/10/arduino-serial-servos-20-servos-4-pins.html with your UNO, but you might need the extra memory of a Mega for the driving algorithms.

    Duane B

    ReplyDelete
  17. Hey,
    I'm keen on RC-Cars and got into programming recently.
    I found your interesting code and uploaded it to a Nano V3.0 board.
    Step 1 (loop back testing) worked well, the serial monitor showed values between 1100 and 1800 all the time.
    In Step 2 I disconnected output of channel 1 and connected the input of channel 1 to the signalport of my receiver. The serial monitor showed values above 4000!
    What to do now?

    Benni

    ReplyDelete
    Replies
    1. What is the signal output port, is it a ppm output or an individual channel output ? Also do you have a connection from the rc receiver ground to the arduino ground ?

      Duane

      Delete