Decoding PPM Signal from VEX Transmitter

Hello,

I am looking for some assistance to get the ServoDecode library from this thread to work with a VEX Transmitter/Receiver and my Arduino Mega. The original library files and example sketch are in the first page, but the OP posted a .cpp version for the Mega in page 5 (uses Timer 4 instead of Timer 1).

I found the pinout for the transmitter online and have all wires connected properly to the Mega (5V, GND, PIN 49). I also have a 100k potentiometer connected between PIN49 and 5V, adjusted to 10k. This is a pullup resistor which I read would be needed to properly read the signal.

I know that both the transmitter and the receiver are in working order. I found the sample sketch below to test them and can see all six channels as well as the sync pulse (~8900). All channels change up/down with inputs from the remote. Range (duration) per channel is [~500,~1500] with the center position at ~1000.

int pin = 49;  // set this to the pin connected to your receiver
unsigned long duration;

void setup()
{
  Serial.begin(38400);
  pinMode(pin, INPUT);
}

void loop()
{
  duration = pulseIn(pin, LOW);
  Serial.println(duration);
  delay(500);
}

I have a few specific questions about the library code. Trying to understand why the signal keeps going into failsafe mode all the time.

  • With the remote off the state shows NOT_SYNCED which is correct.
  • Once I turn the remote on the signal starts to show up but it keeps going into FAILSAFE state.

ServoDecode.cpp

/*
* ServoDecode.cpp
* this version for Mega only
*/

#include "ServoDecode.h"
#include <wiring_private.h>
#include <avr/interrupt.h>

#define PULSE_START_ON_RISING_EDGE  0
#define PULSE_START_ON_FALLING_EDGE (1<<ICES4) 
#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: NOT_SYNCHED_state, ACQUIRING_state, READY_state, FAILSAFE_state
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] = ICR4 / 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(TIMER4_OVF_vect){
 if(State == READY_state){
   State = FAILSAFE_state;  // use fail safe values if signal lost  
   Channel = 0; // reset the channel count
 }
}

ISR(TIMER4_CAPT_vect)
{
 // we want to measure the time to the end of the pulse 
 if( (_SFR_BYTE(TCCR4B) & (1<<ICES4)) == pulseEnd ){         
   TCNT1 = 0;       // reset the counter
   if(ICR4 >= 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( (ICR4 >= MIN_IN_PULSE_WIDTH)  && (ICR4 <= MAX_IN_PULSE_WIDTH) ){ // check for valid channel data                  
       Pulses[++Channel] = ICR4 / 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;
 TCCR4A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC from Timer/Counter  -- PWM11=0,PWM10=0 => PWM Operation disabled 
 TCCR4B = 0x02;         // 16MHz clock with prescaler means TCNT increments every .5 uS (cs11 bit set
 TIMSK4 = _BV(ICIE4)|_BV (TOIE4);   // enable input capture and overflow interrupts for timer 4
 for(byte chan = 1; chan <=  MAX_CHANNELS; chan++) 
       Failsafe[chan] = Pulses[chan]= 1000; // 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() ;

Questions for .cpp file:

  • Are the channel counters correct? Some start at 0 which would give me an array of 7 elements with a MAX_CHANNEL = 6.
  • Line 12 - the code should be based on a pulse starting on the rising edge. From line 9 this variable is defined as 0. However, the if argument on line 58 has (1<<ICES4) which is the same value assigned to the falling_edge variable in line 10. Is this correct?

ServoDecode.h
Changes:
Line 19 - changed from 8 to 6 channels
Line 20 - changed from 750 to 500, minimum pulse read from the test code
Line 21 - changed from 2250 to 1500, maximum code read from test code
Line 22 - changed from 3000 to 8500, based on sync pulse from test 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_private.h> //removed for 0012
typedef uint8_t byte;

#define icpPin            49         // 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    6         // maximum number of channels we can store, don't increase this above 8
#define MIN_IN_PULSE_WIDTH (500 * TICKS_PER_uS) //a valid pulse must be at least 750us (note clock counts in 0.5 us ticks)
#define MAX_IN_PULSE_WIDTH (1500 * TICKS_PER_uS) //a valid pulse must be less than  2250us 
#define SYNC_GAP_LEN      (7500 * 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

Sample Test Sketch:

// 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
 digitalWrite(icpPin, HIGH);
}

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