6 Channels to Arduino Mega?

Hey All,

I am working on a project and trying to get the 6 channels out put from a normal hobby receiver into an Arduino Mega. I found this tutorial on youtube ---> https://www.youtube.com/watch?v=u0Ft8SB3pkw&t=11s

This worked for me at first, but doesn't seem to work pass reading two channels. From my research it appears that pulseIn() is a pretty terrible function for accomplishing this. I was hoping to find a nice clean way to get TX input into the arduino.

Concerns/Questions

From what I have read it is better to use external interrupts. I still need to research more on how those work, but my main concern now lies with whether I have enough interrupt pins on my arduino mega. Only 6 pins are capable of this from the documentation I have found.

Also, Do I need 6 separate pins to read all 6 channels? Could I accomplish it with one?

And finally, is there another way to do this that is potentially cleaner? I don't want to end up doing things that hard way when there are better solutions available.

Many thanks for any help.

I think I have found a vid doing exactly what I need. → YMFC-3D part 2 - Connect RC transmitter and receiver - Arduino quadcopter. - YouTube

Any other suggestions or resources you guys know of are of course more than welcome!

Do you really need to use a hobby transmitter and receiver?

If you can use a pair of nRF24L01+ transceivers you have much more flexibility - potentially 32 channels.

...R Simple nRF24L01+ Tutorial

I have worked with those, and I have had nothing but difficulties with them. We used them as our initial approach to building our robots. We even used another arduino as the transmitter. I wasn't responsible for the programming but it was a pain to get working. On top of that we got some bad components that made it difficult to know what was broken.

Now I don't want to be close minded here. I don't know of the potential capabilities of those tx/rx.

Could they interface with a normal hobby grade controller? (I want to avoid creating more work here)

Would it be worth the investment of time to get familiar with how to work with these transmitters?(I have a fair amount of programming experience, I would need to become familiar with their libraries)

I'm simply looking for an easier way to pipe controller input into an arduino. I currently see pre-built transmitter with reliable signal transmission, and a good user interface as being an excellent solution so long as I can get user input into the program reliably.

Many thanks for your response.

I don’t have any experience using an Arduino to decode the output of a regular RC receiver. My concern is that it is “hard work” for the Arduino. However others have done it.

I have never had a problem with using the nRF24s. I did have one faulty device in about 15 to 20 units.

AFAIK the regular 2.4GHz RC systems use nRF24s (or their Cypress equivalents) under the hood. However I have no idea how you could interface an nRF24 directly to an RC transmitter that was using an nRF24. The guy in this link seems to have done it. (Cypress and Nordic transceivers are incompatible).

The decision is up to you.

…R

All right well thanks for your input. Much appreciated.

When I said "others have done it" I should have added that it is a fairly common subject on this Forum.

...R

I hope its worth reporting that I had success with getting all 6 channels to be read off on an arduino mega. I am actually using the 'pulseIn()' function to do this. I have omitted the timeout input for the function and it now reads it all off correctly.

I don't think that I am out of the woods yet though. My understanding is still limited. Theres a chance that these functions may be taking to much time. This may cause difficulties when trying to control servos and other motors.

I'll try to hook up some components and see how well it works.

Also I would like to time how long it takes for the loop to run through on average as well. I know there are some timer functions out there. If you have any suggestions I am all ears. Many thanks.

const int THRO_CHA = 43;
const int AILE_CHA = 45;
const int ELEV_CHA = 47;
const int RUDD_CHA = 49;
const int GEAR_CHA = 51;
const int AUX1_CHA = 53;


const int deadzone = 20;  // Anything between -20 and 20 is stop

void setup() {
  Serial.begin(9600);
}

void loop() {

  //int aux1 = pulseIn(AUX1_CHA, HIGH, 25000);
  //int gear = pulseIn(GEAR_CHA, HIGH, 25000);
  //int rudd = pulseIn(RUDD_CHA, HIGH, 25000);
  //int aile = pulseIn(AILE_CHA, HIGH, 25000);
  //int elev = pulseIn(ELEV_CHA, HIGH, 25000);
  //int thro = pulseIn(THRO_CHA, HIGH, 25000);
  int thro = pulseIn(THRO_CHA, HIGH);
  int rudd = pulseIn(RUDD_CHA, HIGH);
  int aile = pulseIn(AILE_CHA, HIGH);
  int elev = pulseIn(ELEV_CHA, HIGH);
  int gear = pulseIn(GEAR_CHA, HIGH);
  int aux1 = pulseIn(AUX1_CHA, HIGH);


  thro = pulseToPWM(thro);
  aile = pulseToPWM(aile);
  elev = pulseToPWM(elev);
  rudd = pulseToPWM(rudd);
  gear = pulseToPWM(gear);
  aux1 = pulseToPWM(aux1);

  Serial.print("thro ");
  Serial.print(thro);
  Serial.print(" rudd ");
  Serial.print(rudd);
  Serial.print(" aile ");
  Serial.print(aile);
  Serial.print(" elev ");
  Serial.print(elev);
  Serial.print(" gear ");
  Serial.print(gear);
  Serial.print(" aux1 ");
  Serial.println(aux1);

  
}

At the start of the loop function make a note of the millis() timer in a variable. Then before you print anything else out print the diferance between this variable and the current value of the millis() reading.

Well thanks for your input. I went ahead and measured it and it takes 65ms for it to complete the loop. This confirms my suspicions that pulseIn() takes to long.

I am working on doing something similar to what this youtube video did but for the mega.

In short I have only been able to read in 4 channels with the mega on PCINT2_vect uses pins A8-A15. Even though I initialize A8-A12 pins A12 fails to operate. Not to sure why this is the case. I haven’t been able to find any bugs in my code that are preventing this interrupt from working.

I had something similar happen when I was practicing with the Uno. I was able to get 5 channels to read with the 6 one on digital pin 13 failing to read despite having it set as an interrupt pin.

Thus far I am pleased with what I have been able to figure out with pin change interrupts. The documentation is hard to understand, but thanks to the video I have been able to figure out a lot.

Do you guys have any ideas as to why those last pins aren’t working?

Also, I am starting feel as if I am pushing the limits of micro-controllers, are there easier ways to put a micro-controller between an Hobby tx and the components.

Many thanks for you input guys.

volatile int receiver_input_channel_1, receiver_input_channel_2, receiver_input_channel_3, receiver_input_channel_4, receiver_input_channel_5;
unsigned long timer_1, timer_2, timer_3, timer_4, timer_5, current_time;
byte last_channel_1, last_channel_2, last_channel_3, last_channel_4, last_channel_5;
int receiver_input[6];

void setup() {
  PCICR |= (1 << PCIE2);            //Set PCIE2 to enable PCMSK2 scan.
  PCMSK2 |= (1 << PCINT16);         //Set PCINT16 (digital input 8) to trigger an interrupt on state change.
  PCMSK2 |= (1 << PCINT17);         //Set PCINT17 (digital input 9)to trigger an interrupt on state change.
  PCMSK2 |= (1 << PCINT18);         //Set PCINT18 (digital input 10)to trigger an interrupt on state change.
  PCMSK2 |= (1 << PCINT19); 
  PCMSK2 != (1 << PCINT20);
  //PCMSK0 != (1 << PCINT5);
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(250);
  print();
}

ISR(PCINT2_vect) {
  current_time = micros();
  //Channel 1=========================================
  if (PINK & B00000001) {                              //Is input 8 high?
    if (last_channel_1 == 0) {                         //Input 8 changed from 0 to 1.
      last_channel_1 = 1;                              //Remember current input state.
      timer_1 = current_time;                          //Set timer_1 to current_time.
    }
  }
  else if (last_channel_1 == 1) {                      //Input 8 is not high and changed from 1 to 0.
    last_channel_1 = 0;                                //Remember current input state.
    receiver_input[1] = current_time - timer_1;        //Channel 1 is current_time - timer_1.
  }

  //Channel 2=========================================
  if (PINK & B00000010 ) {                                                 
    if (last_channel_2 == 0) {                                              
      last_channel_2 = 1;                                                   
      timer_2 = current_time;                                               
    }
  }
  else if (last_channel_2 == 1) {                                          
    last_channel_2 = 0;                                                     
    receiver_input[2] = current_time - timer_2;                             
  }
  //Channel 3=========================================
  if (PINK & B00000100 ) {                                                  
    if (last_channel_3 == 0) {                                              
      last_channel_3 = 1;                                                   
      timer_3 = current_time;                                               .
    }
  }
  else if (last_channel_3 == 1) {                                           
    last_channel_3 = 0;                                                     
    receiver_input[3] = current_time - timer_3;                             

  }
  //Channel 4=========================================
  if (PINK & B00001000 ) {                                                 
    if (last_channel_4 == 0) {                                              
      last_channel_4 = 1;                                                   
      timer_4 = current_time;                                               
    }
  }
  else if (last_channel_4 == 1) {                                           
    last_channel_4 = 0;                                                     
    receiver_input[4] = current_time - timer_4;                             
  }
  //Channel 5=========================================
  if (PINK & B00010000 ) {                                                  
    if (last_channel_5 == 0) {                                              
      last_channel_5 = 1;                                                 
      timer_5 = current_time;                                               
    }
  }
  else if (last_channel_5 == 1) {                                           
    last_channel_5 = 0;                                                     
    receiver_input[5] = current_time - timer_5;                             
  }
  

}

void print() {
  Serial.println("----------------");
  Serial.print(receiver_input[1]);
  Serial.println(" ");
  Serial.print(receiver_input[2]);
  Serial.println(" ");
  Serial.print(receiver_input[3]);
  Serial.println(" ");
  Serial.print(receiver_input[4]);
  Serial.println();
  Serial.print(receiver_input[5]);
  Serial.println();
}

Sample of my failing output

1180 
1496 
1508 
1496
-13576
----------------
1180 
1496 
1508 
1496
-13576
----------------

I'm not familiar with exactly what's going on in the code you last posted, but if I were you, I'd set a timer interrupt for 5, 10, or 20ms and on every interrupt read the pin status using a simple digital read for each pin (digitalReadFast maybe?) and calculate duty cycle from there.

This confirms my suspicions that pulseIn() takes to long.

It doesn’t take too long, it takes the time of the pulse you are trying to measure. It is blocking code just like the delay function.

Your print function is just crying out for a “for loop”.
When using if statement there is no need for the else to check the inverse of the origional if.

if (last_channel_1 == 0) {                         //Input 8 changed from 0 to 1.
      last_channel_1 = 1;                              //Remember current input state.
      timer_1 = current_time;                          //Set timer_1 to current_time.
    }
  }
  else [s]if (last_channel_1 == 1)[/s] {                      //Input 8 is not high and changed from 1 to 0.
    last_channel_1 = 0;                                //Remember current input state.
    receiver_input[1] = current_time - timer_1;        //Channel 1 is current_time - timer_1.
  }

While this might not stop it working it is something you need to look at.

I haven’t used that many interrupt pins but I don’t think you can have that many on one vector. I know the documentation is hard to read but keep doing it. The interrupt structure is different between Uno and Mega.

I'm not familiar with exactly what's going on in the code you last posted, but if I were you, I'd set a timer interrupt for 5, 10, or 20ms and on every interrupt read the pin status using a simple digital read for each pin (digitalReadFast maybe?) and calculate duty cycle from there.

I don't think that will work unfortunately with trying to read PWM. 6 pulses are sent and they need their rising and falling edges timed. A timer interrupt may work with some careful programming. Like waiting for the first channel to rise, and then read the rest of the channels sequentially as they are expected to come. I guess this may work. Based on my research however, I haven't seen any one do this. There may be reasons that I am unaware of.

Thanks for your input. I'll likely look into it.

Your print function is just crying out for a "for loop".

Yes your right!

When using if statement there is no need for the else to check the inverse of the origional if.

There are some subtleties to why this is. I think it is explained in the video. I elected to not modify that portion of the code until I was able to read the channels and ensure that I had the functionality.

I know the documentation is hard to read but keep doing it. The interrupt structure is different between Uno and Mega.

The documentation suggests that those pins can be used, I haven't found anything that states otherwise. But then again what I am trying to accomplish something I feel may the similar to trying make a fish climb a tree. I have had some success, but I would really like to be able to read the 6 signals, and use the micro controller to increase the functionality. Everything from integrating steppers, to controlling servos. I have looked outside of the arduino eco system including other robotics systems, but they are quite pricey or really don't offer a whole lot of flexibility.

I am currently considering purchasing the 'arduino due'. Which from what I can tell has the same I/O as the mega. Most importantly all of the digital pins can be set as interrupts. I don't think it would give 6 interrupt vectors, but maybe it could simplify the interrupt pin issue and give me the ability to have 6 on the pins that I would like to select.

Is selecting better hardware like the 'due' a good option?

There are some subtleties to why this is

No there is not. If the value of the variable can change between the first and second part of the if statement you should just capture the input into a variable and make your decision on that variable, not test again.

the ‘arduino due’. Which from what I can tell has the same I/O as the mega.

No, it is just the surface software that makes it look similar. You are down at the processor level, they are very different.

Is selecting better hardware like the ‘due’ a good option?

It is not all good, you have other things to contend with as well, like the limited output current of the pins and the 3V3 operating voltage are big factors against.

What RC receiver are you using?
If it supports any of the serial protocols, you can read all of the channels using one pin.

Link talking about various protocols.

Chazz7555: I don't think that will work unfortunately with trying to read PWM. 6 pulses are sent and they need their rising and falling edges timed. A timer interrupt may work with some careful programming.

Honestly, all it really depends on is the refresh rate of the timer - even if the ISR fires every 20 microseconds you should still have plenty enough resolution to catch rising and falling edges accurately enough

I bet it's easy to write a quick and simple test program to see if it'll work for you

Honestly, all it really depends on is the refresh rate of the timer - even if the ISR fires every 20 microseconds you should still have plenty enough resolution to catch rising and falling edges accurately enough

Well thank you for pointing that out. Its hard to know whats possible when you are still learning! I will be looking more into timer interrupts. When I have a better understanding I'll give it a try!

Thanks for your input!

What RC receiver are you using? If it supports any of the serial protocols, you can read all of the channels using one pin.

I was aware of this this but decided to not look into initially; which was a bad choice. Turns out, at least for the CPPM protocol might work nicely for what I am doing. Its similar to PWM but instead has all the signals on one wire with a different way of relaying positions of the sticks. This is similar to what I am all ready doing just with having to initialize 6 pins as interrupts that really act as one.

This vid outlines it pretty well-> https://www.youtube.com/watch?v=63JmO4Mc8NM

So in short I am going to get my hands on a receiver that outputs PPM for my DX6i. They have a few on Amazon. I think this might be the best option right now.

Many thanks for your input.

It is not all good, you have other things to contend with as well, like the limited output current of the pins and the 3V3 operating voltage are big factors against.

Thanks for mentioning that. I overlooked that it was 3.3 volts. Also thanks for mentioning how the processor is different. There's a lot to these micro controllers! You'd think there so simple compared to a modern computer, but my they are a feat of engineering.

Thanks for your insights Grumpy_Mike.