Pages: [1] 2 3 ... 12   Go Down
Author Topic: Decoding Radio Control signal pulses  (Read 30153 times)
0 Members and 1 Guest are viewing this topic.
London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I posted a library for decoding signals from a radio control transmitter (with a 'trainer port') or from an RC receiver that provides access to the pulse stream. The original code and discussion was in a huge thread titled '3 axis auto stabilized platform' http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1204020386

I've heard that people have trouble compiling the ServoDecode library under 0012. I have created a new thread here to make it easier to find. The following change is needed in ServoDecode.h file to get it to compile under Arduino release 0012.

Remove the line:
Code:
#include <wiring.h>
and replace it with:
Code:
typedef uint8_t byte;

This fixes a conflict with a pesky macro in wiring.h that I hope will be removed in future versions.

But in the mean time, you can make the above change to the previously posted ServoDecode file or use the code  in the following posts. Note that there is no change needed to the ServoDecode.cpp file
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

ServoDecode.h
Code:
// ServoDecodeClass.h
//This library decodes typical RC composite servo inputs into individual channel pulse widths

#ifndef ServoDecode_H
#define ServoDecode_H

#include <inttypes.h>
//#include <wiring.h> //removed for 0012
typedef uint8_t byte;

#define icpPin            8         // this interrupt handler must use pin 8
#define TICKS_PER_uS      2          // number of timer ticks per microsecond


typedef enum {
    NULL_state=-1, NOT_SYNCHED_state, ACQUIRING_state, READY_state, FAILSAFE_state    
 } decodeState_t;

#define MAX_CHANNELS    8         // maximum number of channels we can store, don't increase this above 8
#define MIN_IN_PULSE_WIDTH (750 * TICKS_PER_uS) //a valid pulse must be at least 750us (note clock counts in 0.5 us ticks)
#define MAX_IN_PULSE_WIDTH (2250 * TICKS_PER_uS) //a valid pulse must be less than  2250us
#define SYNC_GAP_LEN      (3000 * TICKS_PER_uS) // we assume a space at least 3000us is sync (note clock counts in 0.5 us ticks)
#define FAILSAFE_PIN   13  // if defined, this will set the given pin high when invalid data is received


class ServoDecodeClass
{
  public:
      ServoDecodeClass(); //Constructor
      void begin();
      decodeState_t getState(); //State Function
      int GetChannelPulseWidth(byte chan);  // this is the access function for channel data
      void setFailsafe(byte chan, int value); // pulse width to use if invalid data, value of 0 uses last valid data
      void setFailsafe();// setFailsafe with no arguments sets failsafe for all channels to their current values
                       // useful to capture current tx settings as failsafe values
      byte getChanCount();
 private:

};

extern ServoDecodeClass ServoDecode;  // make an instance for the user

#endif
ServoDecode.cpp
Code:
//ServoDecode.cpp
#include "ServoDecode.h"
#include <wiring.h>
#include <avr/interrupt.h>

#define PULSE_START_ON_RISING_EDGE  0
#define PULSE_START_ON_FALLING_EDGE (1<<ICES1)
#define ACQUISITION_COUNT  8  // must have this many consecutive valid frames to transition to the ready state.
volatile byte pulseEnd = PULSE_START_ON_RISING_EDGE ; // default value
static volatile unsigned int Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds
static volatile unsigned int Failsafe[MAX_CHANNELS + 1]; // array holding channel fail safe values
static volatile byte Channel;      // number of channels detected so far in the frame (first channel is 1)
static volatile byte NbrChannels; // the total number of channels detected in a complete frame
static volatile decodeState_t State;         // this will be one of the following states:
static volatile byte stateCount;         // counts the number of times this state has been repeated

static void processSync(){
// Sync was detected so reset the channel to 1 and update the system state      
   Pulses[0] = ICR1 / TICKS_PER_uS;  // save the sync pulse duration for debugging
   if(State == READY_state) {  
         if( Channel != NbrChannels){  // if the number of channels is unstable, go into failsafe
               State = FAILSAFE_state;
         }
   }
   else{
     if(State == NOT_SYNCHED_state){
             State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array    
             stateCount = 0;
       }
       else if( State == ACQUIRING_state)      {
          if(++stateCount > ACQUISITION_COUNT) {
            State = READY_state;           // this is the second sync and all channel data is ok so flag that channel data is valid    
                NbrChannels = Channel; // save the number of channels detected
            }
     }
       else if( State == FAILSAFE_state)      {  
             if(Channel == NbrChannels){  // did we get good pulses on all channels
                  State = READY_state;
            }
       }
   }
   Channel = 0;       // reset the channel counter
}

ISR(TIMER1_OVF_vect){
  if(State == READY_state){
    State = FAILSAFE_state;  // use fail safe values if signal lost  
    Channel = 0; // reset the channel count
  }
}

ISR(TIMER1_CAPT_vect)
{
  // we want to measure the time to the end of the pulse
  if( (_SFR_BYTE(TCCR1B) & (1<<ICES1)) == pulseEnd ){         
    TCNT1 = 0;       // reset the counter
    if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
      processSync();
    }    
    else if(Channel < MAX_CHANNELS) {  // check if its a valid channel pulse and save it
      if( (ICR1 >= MIN_IN_PULSE_WIDTH)  && (ICR1 <= MAX_IN_PULSE_WIDTH) ){ // check for valid channel data                  
        Pulses[++Channel] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
      }
      else if(State == READY_state){
        State = FAILSAFE_state;  // use fail safe values if input data invalid  
        Channel = 0; // reset the channel count
      }
    }      
  }
}

ServoDecodeClass::ServoDecodeClass(){
}

void ServoDecodeClass::begin(){
  pinMode(icpPin,INPUT);
  Channel = 0;            
  State = NOT_SYNCHED_state;
  TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled
  TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
  TIMSK1 = _BV(ICIE1)|_BV (TOIE1);   // enable input capture and overflow interrupts for timer 1
  for(byte chan = 1; chan <=  MAX_CHANNELS; chan++)
        Failsafe[chan] = Pulses[chan]= 1500; // set midpoint as default values for pulse and failsafe
}

decodeState_t ServoDecodeClass::getState(){
  return State;
}

byte ServoDecodeClass::getChanCount(){
  return NbrChannels;
}

void  ServoDecodeClass::setFailsafe(byte chan, int value){
// pulse width to use if invalid data, value of 0 uses last valid data
  if( (chan > 0) && (chan <=  MAX_CHANNELS)  ) {
       Failsafe[chan] = value;
  }
}
void  ServoDecodeClass::setFailsafe(){
// setFailsafe with no arguments sets failsafe for all channels to their current values
// usefull to capture current tx settings as failsafe values
  if(State == READY_state)
    for(byte chan = 1; chan <=  MAX_CHANNELS; chan++) {
        Failsafe[chan] = Pulses[chan];
    }
}

int ServoDecodeClass::GetChannelPulseWidth( uint8_t channel)
{
  // this is the access function for channel data
  int result = 0; // default value
  if( channel <=  MAX_CHANNELS)  {
     if( (State == FAILSAFE_state)&& (Failsafe[channel] > 0 ) )
         result = Failsafe[channel]; // return the channels failsafe value if set and State is Failsafe
       else if( (State == READY_state) || (State == FAILSAFE_state) ){
         cli();       //disable interrupts
         result =  Pulses[channel] ;  // return the last valid pulse width for this channel
         sei(); // enable interrupts
       }
  }
  return result;
}
// make one instance for the user
ServoDecodeClass ServoDecode = ServoDecodeClass() ;

ServoDecode.pde Test Sketch
Code:
// ServoDecodeTest

#include <ServoDecode.h>

char * stateStrings[] = {
  "NOT_SYNCHED", "ACQUIRING", "READY", "in Failsafe"};

void setup()                    // run once, when the sketch starts
{
  Serial.begin(38400);  
  pinMode(12,OUTPUT);
  pinMode(13,OUTPUT);  
  ServoDecode.begin();
  ServoDecode.setFailsafe(3,1234); // set channel 3 failsafe pulse  width
}

void loop()                     // run over and over again
{
  int pulsewidth;

  // print the decoder state
  if( ServoDecode.getState()!= READY_state) {
    Serial.print("The decoder is ");
    Serial.println(stateStrings[ServoDecode.getState()]);
    for ( int i =0; i <=MAX_CHANNELS; i++ ){ // print the status of the first four channels
      Serial.print("Cx"); // if you see this, the decoder does not have a valid signal
      Serial.print(i);
      Serial.print("= ");
      pulsewidth = ServoDecode.GetChannelPulseWidth(i);
      Serial.print(pulsewidth);
      Serial.print("  ");
    }
    Serial.println("");
  }
  else {
    // decoder is ready, print the channel pulse widths
    for ( int i =1; i <=MAX_CHANNELS; i++ ){ // print the status of the first four channels
      Serial.print("Ch");
      Serial.print(i);
      Serial.print("= ");
      pulsewidth = ServoDecode.GetChannelPulseWidth(i);
      Serial.print(pulsewidth);
      Serial.print("  ");
    }
    Serial.println("");
    digitalWrite(12,LOW);
    digitalWrite(13,LOW);
  }
  delay(500); // update 2 times a second        
}
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 2
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Thank you again for posting the servo decoder.  I uploaded it and it worked like a charm!  My only problem now is that when i try to use the analogWrite function whle running the decoder, it doesn't seem to work.  Has anyone else run into this problem?
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi dave, this code uses hardware support from timer1 so analogWrite() for digital pins 9 and 10 will not work. Try it on the other pins and report back here if it works.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm really new to arduino..... How do I use these 3 files.  I have them each open in a tab and when I compile it says:

25: error: ServoDecode.h: No such file or directory In function 'void setup()':
 In function 'void loop()':
« Last Edit: December 30, 2008, 09:16:50 am by ubuntucat » Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This code is intended to be used as an Arduino library. Create a new directory called ServoDecode in the directory with the other libraries ( … hardware\libraries\ServoDecode)

Copy ServoDecode.h and ServoDecode.cpp into this directory.

The ServoDecode.pde code should be pasted into the IDE to create a new sketch. This example sketch includes <ServoDecode.h> but if you create your own sketch, you can use the 'Sketch/Import Library' function from the IDE menu.

There is some information in the playground on Arduino Libraries that goes into more detail.

Have fun!    
« Last Edit: December 30, 2008, 09:36:28 am by mem » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

what all needs to be changed in order for this to work with the 6-channel Vex
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I don't think anything needs to be changed in the library, you may want to contact jamiemcshan about connecting it up -  see this post:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1204020386/176#176

When you get it going, please come back here and post details of how you did it.

Have fun!
« Last Edit: January 07, 2009, 05:41:28 pm by mem » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 27
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It's every time in failsafe.

How I do the connections?

Where I connect the PWM input?
« Last Edit: February 19, 2009, 06:58:59 pm by aerodolphin » Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

You connect the PPM stream to arduino pin 8.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello mem,

thanks for the great libraries. New life for the old receiver - I used your code with GWS R-4PII/H receiver with only 3 channels working - accidentally burned one channel long time ago. The beauty is I get all 6 channels on arduino. Now I'm wondering if the library could be used to decode Futaba PCM signal? Have you heard if anyone tried that? I found arduino helicopter autopilot thread (another great project) mentions PCM in several places, but looking at the code samples in the thread it seems its just PPM.

« Last Edit: February 22, 2009, 11:04:45 pm by arcore » Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The technique used in this library is completely different from that needed to decode a signal encoded using the Futaba PCM protocol. That protocol uses proprietary serial encoding and would required much more processing than the PPM decoding used here.

In my opinion it would not be worth the effort to do this – of course you are welcome to take it on if you are up for the challenge. But I would think in most situations there would be no benefit and for those cases with strong interference you would be better of using 2.4ghz technology.

What was the reason you were interested in PCM?
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I just thought it would be fun to get PCM out or PPM receiver. But PPM works great and I don't want to spend time working on PCM if will eat up too much arduino processing power, though your interrupt method could probably be adopted to read the bits of PCM transmission too?

By the way the library works with inverted signals too. For some reason my GWS receiver had it inverted and the only problem i noticed first channel had very high numbers (around 10,000 or so) but the rest of the channels were fine.
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

In my opinion, PCM would be a lot of work for no benefit. There are many more productive and fun things to do with your time.

BTW, if you want to use the library with a receiver with an inverted output, in ServorDecode.cpp change:
volatile byte pulseEnd = PULSE_START_ON_RISING_EDGE ; // default value
  to
volatile byte pulseEnd = PULSE_START_ON_FALLING_EDGE; // inverted pulses

then recompile the library (by deleting ServoDecode.o and rebuilding your sketch)
« Last Edit: February 24, 2009, 09:57:41 am by mem » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 14
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Bummer, should have asked earlier about the inverted. I already soldered a simple amplifier/inverter right on to GWS R4PII/H smiley-sad
Well, it needed amplifier though - only had 2.5V impulses without it and was getting very noisy feedback from arduino controller back into the receiver. But with this code change i could have gotten away with just one transistor...
Thanks for the info!  smiley-wink
Logged

Pages: [1] 2 3 ... 12   Go Up
Jump to: