Go Down

Topic: reading 6-channel RC receiver PWM signal (Read 19556 times) previous topic - next topic

eurobug

Hello all,

In case anyone is interested, the following code can be used to read the PWM channels of a hobby RC receiver. The code has been tested on an arduino nano, and is interrupt based, and hence avoids using any blocking code. To use, attach the RC PWM channels (the data lines of the 3-pin servo plugs on your RC receiver) to pins 8-13 of the arduino. If you require less channels, just start from pin 8 and work your way up, the unused channels at the end will simply never change from their initial value.

The reason I wrote this is that my RC receiver contains only SMD components, and hence "hacking" the PPM signal was too difficult for me. Also, I haven't found any simple non-blocking code for reading the PWM channels. Most seem to use the PulseIn command, which I would like to avoid.

Quote

#include "avr/interrupt.h"

unsigned int rc[6];
float timer_period;
unsigned int T, T_reset, T_prev, T_offset, T_min, T_max;
unsigned short T_pointer=0;
unsigned int tics_before_reset;
 
void setup()

  Serial.begin(115200);
  
  for (int i=8; i<14; i++) {
    pinMode(i,INPUT);
  }
  
  // Timer1 setup
  TCCR1A = 0b00000000;          
  TCCR1B = 0b00000011;        // Start timer1 with prescaler 8
  timer_period = 0.000004;    // One tick takes 4 us
  PORTB|=0b00111111;          // Turn on pullup resistors
  PCMSK0=0b00111111;          // Pin change interrupt 0 bit mask: we interrupt on change on pins PCINT0-5 (arduino nano pins 8 - 13)
  PCICR=0b00000001;           // Enable pin change interrupt 0
  sei();                      // Global interrupt enable 
  TCNT1 = 0;                  // Reset timer1

  T_min = (unsigned int) (0.0005 / timer_period); // shorter than 0.5ms pwm signal is ignored
  T_max = (unsigned int) (0.003 / timer_period); // longer than 3ms pwm signal indicates frame end
  rc[0]=375;                  // Init rc with sane values
  rc[1]=375;
  rc[2]=375;
  rc[3]=375;
  rc[4]=375;
  rc[5]=375;

  tics_before_reset = (unsigned int) (0.10/timer_period); // 0.1 sec control loop (25000 tics)
  
  Serial.println("Read RC receiver test");
  delay(1000);
}

void loop() //Main Loop
{
  while (TCNT1 < tics_before_reset) ; // Wait until start of new frame
 
  cli(); // No interrupts allowed when modifying timer1
  T_reset = TCNT1;
  TCNT1 = 0;
  T_offset = T_reset - T_prev;
  T_prev=0;
  sei(); // Reactivate interrupts.
  
  // Other stuff goes here: Calculations, sensor readings, generating PWM on other pins, ...
  printdata();
}


ISR(PCINT0_vect) {
  T = TCNT1 - T_prev + T_offset;
  if (T > T_min) {
    T_prev = TCNT1;
    T_offset=0;
    if (T > T_max) {
      T_pointer = 0;
    } 
    else {
      rc[T_pointer] = T;
      T_pointer++;
      if (T_pointer > 5) T_pointer--; // overflow protection (glitches can happen...)
    }
  }
}
 

void printdata(void) {
  for (int i=0; i<6; i++) {
    Serial.print(rc);
    Serial.print(" ");
  }
  Serial.println();   
}




DuaneB

#1
Feb 14, 2012, 01:51 pm Last Edit: Feb 14, 2012, 02:07 pm by DuaneB Reason: 1
Hi,
  I would suggest an alternative approach, timer1 is used by the servo library and most projects that read RC Channels will interface to one or more servos or electronic speed controllers. Your current approach prevents the use of this library.

  My own approach uses interrupts and the micros function, it also does not block whereas your current code blocks here - while (TCNT1 < tics_before_reset) ; // Wait until start of new frame

  You have done an excellent job understanding and using the timer and pin change registers.

  You might want to look up the 'volatile' keyword - http://arduino.cc/en/Reference/Volatile

   Blog post with my code - http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with.html

EDIT: The post above reads only a single channel however through the PinChangeInt library this can be extended to six channels. An advantage of using PinChangeInt is that the library provides information on which pin has changed, this becomes very important outside the lab where glitches can be expected, I would strongly suggest investigating this approach for a real world application rather than treating every rising or falling edge as the beginning or end of a valid pulse.

   Duane B

rcarduino.blogspot.com












Read this
http://rcarduino.blogspot.com/2012/04/servo-problems-with-arduino-part-1.html
then watch this
http://rcarduino.blogspot.com/2012/04/servo-problems-part-2-demonstration.html

Rcarduino.blogspot.com

eurobug

Thank you for the information. From what I understand from the volatile discussion: Every global variable that is modified inside an interrupt service routine, and used elsewhere, should be declared volatile. Hence in the code I posted, variables rc, T_prev, T_offset and T_pointer should all be volatile.

The code as it stands will indeed require timer1 for itself, and hence use of other libraries that need this timer will no longer function. However, for my purpose I will not need the servo library, since I will generate the servo pulses for my ESC's and servo's myself. Hence the line " while (TCNT1 < tics_before_reset) ; // Wait until start of new frame". The frame I am talking about here is not the frame of the RC receiver, but the frame of the servo signal I will create in the "other code here" part. For the moment, these frames are 0.1 sec, but I will reduce them to 20 msec so that they behave like normal RC PWM frames.

The entire control loop (this will become a quadcopter) will be something along the lines of

- wait until the new frame starts
- Reset timers, write down period of previous frame for the integrations
- Read IMU, calculate Euler angles.
- Use the desired Euler angles, yaw speed and thrust in a PID controller to calculate the four servo signals for the ESC's.
- Output the servo signals on 4 pins simultaneously using timer1 and a simple while and if-loop (this will be a blocking loop of maximum 2000 usec).

The reading of the RC input needed to be completely detached from this control loop, which it is now.

I prefer not to use PinChangeInt because the changes come very quickly: if channel 1 goes down, channel 2 will go up only 1-2 microseconds later. This is too fast for complex interrupt handlers. Now it doesn't matter if two interrupts succeed each other very quickly, or if one of them was missed, the code will output the correct timings.

DuaneB

Hi,
   I would still experiment with the interrupt library, when I tested it by connecting a single PWM output to 5 or 6 inputs it still captured each pin change, it would make your input capture more resilient.

   I am interested in how you plan to use timer1 to generate the PWM output, will you handle it in two cycles ? i.e. read the inputs, then generate the outputs and repeat ?

   Duane B

rcarduino.blogspot.com

Read this
http://rcarduino.blogspot.com/2012/04/servo-problems-with-arduino-part-1.html
then watch this
http://rcarduino.blogspot.com/2012/04/servo-problems-part-2-demonstration.html

Rcarduino.blogspot.com

eurobug

Well, to handle the generation of the RC signal I though of using something along (this is typed in quickly, so probably contains some mistakes):

Code: [Select]

unsigned int timing[4];

char done=0b00001111;
for (int pin=14; pin<18; pin++) digitalWrite(pin, high);
T_start = TCNT1;
while (!done) {
  T_run = TCNT1 - T_start;
  if (T_run > timing[0]) {
    digitalWrite(14,low);
    done&=0b00001110;
  }
  if (T_run > timing[1]) {
    digitalWrite(15,low);
    done&=0b00001101;
  }
  if (T_run > timing[2]) {
    digitalWrite(16,low);
    done&=0b00001011;
  }
  if (T_run > timing[3]) {
    digitalWrite(17,low);
    done&=0b00000111;
  }
}

Since I have a 4 usec resolution in my timer1, which should be enough, this while loop should not take longer than 4 usec. Some tweaking, or assembler, might be needed to reach this speed. On the other hand, I do not think that real-life applications will suffer much from a possible 1-tick error, especially not within a PID control loop.

Reading the RC inputs is interrupt based, with a fast ISR, and hence does not interfere (significantly) with this loop. This loop is blocking however, for at most 2000 usec (longest possible RC PWM signal).

DuaneB

#5
Feb 14, 2012, 05:05 pm Last Edit: Feb 14, 2012, 05:24 pm by DuaneB Reason: 1
Hi,
  When I measured RC Signals I found at least 40us of noise in real world environments so a 4us margin in code is nothing to worry about.

http://rcarduino.blogspot.com/2012/01/how-to-read-rc-receiver-with_20.html

Your approach to generating the signal looks viable. One thing I like about the servo library is that it essentially uses hardware to do the work, running 12 servos takes around 1% of processor time.

http://rcarduino.blogspot.com/2012/02/can-i-control-more-than-x-servos-with_03.html

I don't know how complex your calculations will be but if performance becomes an issue you can get a lot more done in loop using the pin change interrupt library and servo library.

I will get out of your way now and follow with interest how your code develops

Duane B

rcarduino.blogspot.com



Read this
http://rcarduino.blogspot.com/2012/04/servo-problems-with-arduino-part-1.html
then watch this
http://rcarduino.blogspot.com/2012/04/servo-problems-part-2-demonstration.html

Rcarduino.blogspot.com

eurobug

Thanks for the input. I will keep you posted here as things develop.

Since the control loop is 20 msec, and the blocking part to generate the RC signals takes at most 2 msec, I still have 90% of the time available for reading sensors and calculations. This is more than enough. If I run into trouble with the RC reading interrupts, I will look at your proposal, but for now I will use the "don't fix what's not broken" approach ;)

Best regards,

helscream

If some one is fowlling this thread, its worth to mention this http://hobbylogs.me.pn/?p=110 blog. He wrote a library using interrupts for reading the rc input signals. But the library is for arduino mega as uno have only two interrupts and its not enought for most of rc applications.

Jantje

I wrote a library for RC that can also work with pinchange int https://github.com/jantje/libraries/tree/master/RCLib
based on the work of duane.

Do not PM me a question unless you are prepared to pay for consultancy.
Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -

Go Up