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;
  }
}


30 comments:

  1. Duane! I really must apologize if this is the 3rd posting you see from me, but each time I've submitted it has signalled an error. So here goes again! (each time I make it shorter and sweeter!) ;-)

    I'm looking for an Arduino guy in Dubai -- to exchange thoughts with, and to possibly collaborate. It's a project featuring Arduino...and ice-cream. Yes, ice cream. Please reach out at wissamharoun at hotmail dot com with your contact details and I'll contact you asap. super thanks... and for the third time, I love your Tamiya Sand Scorcher :-)

    ReplyDelete
  2. Hi,
    You can contact me through the arduino forum arduino.cc

    My username is DuaneB

    Duane B

    ReplyDelete
  3. Duane,
    Thanks for your great library. Could you help: I'm using the Arduino Pro Mini. My setup needs 4 PWM out channels, 3 RC in channels and 1 Servo out. Which pins do you advise to use for what? Now I have PWM on 3,5,6,11 (9 doesn't work as noted in your blog). Servo out is on A0 (rest not build yet), RC in is on 12. I just can't get the RC in to work. I've used your test script from this article, no succes. It could be my Mini died (though the ChangeInt test seems to work), or am I messing up the pins? Thanks anyway... Regards, JH

    ReplyDelete
  4. Hi,
    This code is designed for the Arduino Due, it will not work correctly on the 8-bit Arduinos - Mini, Micro, Leonardo, UNO or Mega.

    For the 8-bit boards you should start with the code here, also post you comments there as well, you might need to change some of the pins, but its simple enough -

    try this and let me know in the comments how you get on -

    http://rcarduino.blogspot.ae/2013/04/problem-reading-rc-channels-rcarduino.html

    Duane B

    ReplyDelete
  5. Hmmm The loop back works just great. However, things go south when I substitute even one channel input with an RC receiver. The readings in that channel (using channel one in this case) go crazy, varying from 10 to 80000. I am using an 8 channel level shifter from Adafruit. (of course, just a single channel at this time) I have a cheap USB Oscilliscope. It shows an unstable pulse and very small spikes in the base line.
    I am thinking that the level shifter is adding noise. Now, to figure out how to test this and/or find a different way to level shift. Any insight would be welcome.
    Rick Harms
    rick@inharmsway.org

    ReplyDelete
  6. OK, not the level shifter. I removed the level shifter. I ran the receiver straight to the Due, but applied only 3.3 volts to the receiver. I first verified its operation by attaching a servo to the receiver, it worked fine, but slow. Unattached the servo and connected to the DUE. Same problem. So, is my FR-SKY/Turnigy 9x the problem? I substituted my Futaba trans and rec. Same problem.
    Frustrating. Now I am at a total lose.
    Rick Harms

    ReplyDelete
  7. Rick, everything you are describing sound as if you do not have a common ground between the Arduino and receiver, try checking the connection with a multimeter

    Duane.

    ReplyDelete
  8. Duane, you are freak'in awesome. :-)
    I had gone over and over the mess of wires and could not see a problem. Today, I found it with your insight. What a bonehead mistake I made. Not only was the DUE not grounded, but I "grounded" it to the +3.3 rail. It is a wonder it survived. The level shifter also worked like a charm. Now, this is only one servo, now to attach 7 more, in increments. I am ecstatic. Thank you.
    Rick Harms

    ReplyDelete
  9. Hi Duane! I have used your code for reading 8 Channel RC Remote and the code works absolutely fine as long as I am not using serial communication. When I read data from a sensor at the same 50Hz. Then I get ambiguous values on different channels, (specifically one channel at a time). I think the problem is that there are no interrupt priorities in arduino and the channel's interrupt is colliding with that of serial. So can you kindly suggest a solution to my problem?

    ReplyDelete
  10. Thank you very much for posting this example code! I am using to to read signals from an RC radio for a custom quadcopter. It works perfectly.

    ReplyDelete
  11. Hi Duane
    Long time ....

    2 quick questions:
    1 - do you consider this stable enough to send 2-3000 USD 200 feet up in the air with a DUE controlling ?
    2 - how will the lib work without disabling interrupt. i don't need RX pulses - will use SerialRX (SRXL),
    but need to measure RPM pulses instead - on a twin-rotor heli

    ReplyDelete
    Replies
    1. Good question

      You will be sending at least 4 things up 1) Parts of this code 2) Your own code 3) Your own circuit 4) Your own hardware. You really need to test all four together and for every type of failure you can think of - turn the transmitter off, pull a wire out, low Arduino battery, three or four times the range you planned in case there is interference from buildings, transmitters etc.

      The two most common problems I have found with remote controlled projects are solder joints failing due to vibration effecting wires and unexpected interference from underground cables, transmitters, buildings etc.

      So in summary test everything you can think of on the bench, build in fail safe mechanisms for software, hardware and mechanical failure and then bench test in the operational environment to eliminate environmental factors. After that, go for it.

      Duane.

      Delete
  12. Well - I did use your earlier code in a earlier "toss it in the air and see if it will work" - project

    https://www.youtube.com/watch?v=w4VAapGF3ls

    ReplyDelete
  13. Duane B
    could you help me with this code, pleased.
    I used your example but nothing is happening.
    I am using this on a teensy 3.2, not sure if it will work but it compiles. I am testing it out on one ESC/brushed motor first, channel 3.
    I can write to the servo and get the motor to spin on channel 3. The loop works before I edit out the servo to write write. I can see the values change in the serial monitor.
    But as soon as I edit out servoChannel3.writeMicroseconds(unChannel3In); nothing.
    It is like the value will not write to the servo for unChannel3In, but I can see the value on the monitor changing. Any ideas.
    thanks
    webzter30

    ReplyDelete
  14. Water Filter UAE
    Hello there! I just want to offer you a big thumbs up for your great info you have right here on this post. I'll be coming back to your web site for more soon.

    ReplyDelete
  15. Your Posts Very Interesting. In fact after reading, I had to go show it to my friend and he ejoyed it as well! I have also some good Stuf check it.
    Water Purifier Dubai

    ReplyDelete
  16. Wow Great i am in searching of this Post . You Explain the Topic Very Well . Thanks And you can also know about curtains by Visit!this site
    Pakistan Cargo Sharjah

    ReplyDelete
  17. It’s an awesome piece of writing in favor of all the web users; they will get benefit from it I am sure.
    Water Filter Dubai

    ReplyDelete
  18. Just wanted to say I love reading through your blog and look forward to all your posts! Keep up the great work!
    Pakistan Cargo

    ReplyDelete
  19. Why not buy Madden NFL 22 Coins on GameMS? Both affordable and safe!

    Attached link: https://www.gamems.com/madden-22-coins

    ReplyDelete
  20. Purchase Soundcloud plays and get a long typical risk on your moved substance. Soundcloud is the best music streaming and sharing site that awards you to raise your music limit. By getting more fans, you will get appreciation. As your general fan network makes, you become an apparent name.buy soundcloud plays and downloads

    ReplyDelete
  21. Downloadable Nintendo Switch skin cut templets for vinyl cutting on different shaper. most silly breaker gave, buttons are truly open. The Cut guarantees Smooth and principal skin application, particularly at changed corners.mobile skin template

    ReplyDelete
  22. After genuinely depicting the title, and before we end the articles, we're happy to enlighten you that your web-based redirection business is gone to accomplish two or three clear advances. Get Facebook Page Likes Australia to get the electronic effect.pay for followers on instagram

    ReplyDelete
  23. Before we get into the substance Before you read the article, you should see the worth in that you truly need to gather how much people who read your story to reply! Moving past this is what's going on, buy australian instagram followers is the best choice for you. Produce a fair satisfying extra through them.

    ReplyDelete
  24. The result of online amusement is sensible to all and in every way that really matters, reasonable for them. To transform into an Instagram influential person.Tips to Become an Influencer and Create a Buzz on Instagram

    ReplyDelete
  25. Following is nothing to joke about it can happen to anyone at whatever point. Anyone can be an overcomer of following, whether they know it. Following happens when an individual perseveringly follows and watches someone without their consent. It could show up, evidently, to be a harmless loosening up advancement to express people.buy australian instagram followers

    ReplyDelete
  26. You've gone through hours valuing your ideal Insta posts and those 10-second records. You genuinely need to guarantee your social occasion benefits from each and every post. That is where Instagram Stories go out to be critical. It grants you to post brief records that pivotal continue onward for 24 hours.buy Instagram Videos Story veiws

    ReplyDelete
  27. Gaining a huge number of Instagram followers, on the other hand, is not something that can be done quickly or easily. It takes a long time and a lot of effort to complete.place to buy instagram followers

    ReplyDelete
  28. I am very much impressed by your content of your article. It gives me so much information. Please keep me updated in this article. Now its time to avail best town car service Houston for more information.

    ReplyDelete
  29. Such a great well written article. I really love it. You done nice work in this article post for us. I hope in future you share more good updates. Now it's time to avail Baby Liquid Soap for more information.

    ReplyDelete