joystick to arduino to RC transmitter

Hello all,

I recently thought of a cool way to control your RC model; by hooking up a USB joystick, throttle and rudder pedals to the arduino, having the arduino inturprut said code and put it out to a RC transmitter via the trainer port.

I have read the topic "Arduino-Controlled RC Transmitter"
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1193927390/0

As a total noob i cant understand very much. :frowning: I do understand that the PPM signal goes in the Signal In, but how would I program it so the potentiometers and buttons to tell the PPM signal how to change? does that make sense?

I want to control all six channels so how would i do that? I want the arduino to send the PPM pulses to a Futaba 6EX. It has a square trainer port on the bottom and i know what pins are used.

Also does anyone know, to connect the joystick, throttle and pedals, would i have to rip them open and connect the arduino to the potentiometers and buttons individualy? Or could this be done through the USB cables?

thanks for any answers,
The noob with no clue

I know im asking alot and this is proably way over my head but if someone can give me any help that would be great.

Here is some code that I did a while ago that creates a PPM pulse stream from the values of the 6 analog pins. It uses library files that needs to be copied into a directory called RCEncode.

This is a test sketch for the encoder library:

/* sketch to test RCEncoder  */
// Sends a pulse stream on pin 2 proportional to the values of pots connected to the analog pins 

#include <RCEncoder.h>

#define OUTPUT_PIN 2

void setup ()
{
  encoderBegin(OUTPUT_PIN);
}

void loop ()
{
  for(int i=0; i < 6; i++)
  {
    int value = analogRead(i);
    int pulseWidth = map(value, 0,1023, 1000, 2000); 
    encoderWrite(i, pulseWidth);
  }  
  delay(20); 
}

here is the header file for the encoder library

// RCEncoder.H

#ifndef RCEncoder_h
#define RCEncoder_h

#include "WProgram.h" 

#ifdef __cplusplus
extern "C"{
#endif


#define ledTest1 8
#define ledTest2 9

#define NBR_OF_CHANNELS  6
#define MIN_CHANNEL_PULSE 1000  // 1ms 
#define MID_CHANNEL_PULSE 1500  // 1.5ms
#define MAX_CHANNEL_PULSE 2000  // 2 ms
#define INTER_CHAN_DELAY  200   // 200 microseconds 
#define FRAME_RATE        20000 // 20 ms 
#define SYNC_PULSE_WIDTH (FRAME_RATE - (NBR_OF_CHANNELS * (MID_CHANNEL_PULSE + INTER_CHAN_DELAY))) 
#define MS_TO_TICKS(_ms)  ((_ms) * 2)  // todo, use macro here

void encoderBegin(byte pin);
void encoderWrite(byte channel, int microseconds);

#ifdef __cplusplus
} // extern "C"
#endif

#endif

this is the RCEncoder.cpp file

// RCencoder.cpp
//

#include "RCEncoder.h"

/* variables for Encoder */
volatile  byte Channel = 0;  // the channel being pulsed
static byte OutputPin;       // the digital pin to use
/* processing states */
enum pulseStates {stateDISABLED, stateHIGH, stateLOW}; 
static byte pulseState = stateDISABLED;  

typedef struct {
  unsigned int ticks;  // we use 16 bit timer here so just store value to be compared as int
} Channel_t;

Channel_t Channels[NBR_OF_CHANNELS + 1];  // last entry is for sync pulse delay
 
ISR(TIMER1_COMPA_vect) {

   if( pulseState == stateLOW ) {
        digitalWrite(OutputPin, LOW);     
        OCR1A = Channels[Channel].ticks;
        pulseState = stateHIGH; 
   }
   
   else if(pulseState == stateHIGH)
   {
         OCR1A = MS_TO_TICKS(INTER_CHAN_DELAY);
             if( Channel < NBR_OF_CHANNELS)
            digitalWrite(OutputPin, HIGH);
         pulseState = stateLOW;
         if(++Channel > NBR_OF_CHANNELS) {// note that NBR_OF_CHANNELS+1 is the sync pulse
           Channel = 0;                 
         }       
   }    
}

// private functions
// -----------------------------------------------------------------------------
// convert microseconds to timer cycles + ticks, and store in the Channels array
static void ChannelStorePulseWidth(byte Channel, int microseconds) {
  cli();  
  Channels[Channel].ticks = MS_TO_TICKS(microseconds) ; 
  digitalWrite(ledTest1,HIGH);
  sei();             // enable interrupts 
#ifdef DEBUG  
   Serial.print(Channel,DEC);
   Serial.print("=\t");
   Serial.print(Channels[Channel].ticks,DEC);
   Serial.print("\r\n");
#endif
}

// user api
// -----------------------------------------------------------------------------
// turns on a Channels pulse of the specified length, on the specified pin
void encoderWrite(byte channel, int microseconds) {

    if ( microseconds > MAX_CHANNEL_PULSE ) {
         microseconds = MAX_CHANNEL_PULSE;
    } 
    else if ( microseconds < MIN_CHANNEL_PULSE ) {
         microseconds = MIN_CHANNEL_PULSE;
    }
    ChannelStorePulseWidth(channel, microseconds);
  
}

// start the encoder with output on the given pin
void encoderBegin(byte pin) {
  byte i; 
  OutputPin = pin;
  pinMode(OutputPin,OUTPUT);
   
  // initialize pulse width data in Channel array. 
  for (i=0; i < NBR_OF_CHANNELS; ++i) 
     ChannelStorePulseWidth(i, 1500);
     
  ChannelStorePulseWidth(NBR_OF_CHANNELS, SYNC_PULSE_WIDTH);  // store sync pulse width

  TIMSK1 |= (1<<OCIE1A); //Enable compare interrupt
  TCCR1A = _BV(WGM10) | _BV(WGM11);   //Timer 1 is Phase-correct 10-bit PWM. 
  TCCR1A |= _BV(COM1A1);              //Clear OC1A on compare match when up-counting, set OC1A on compare match when down-counting. 

   TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); /* div 8 clock prescaler */
     
  Channel = 0;
  pulseState = stateLOW; // flag ISR we are ready to pulse the channels
}

Hi,

I have used your code and it works great. However in my project I want to be able to read a PPM signal on one pin, change some of the channels and then output the new PPM on another pin.

Your code is great for generating PPM but I have problems reading the PPM. Using pusein works but the values jitter - i guess due to pulsein not being accurate. I have also tried using an interrupt to listen to the input pin. But every once in a while I get a false reading and also some jitter.

Here is my code:

rxInt uses the interrupt
read_ppm uses pulsein

#include <RCEncoder.h>

#define OUTPUT_PIN 9 // PPM Signal to JETI
#define INPUT_PIN 5 // PPM Signal from MX12

#define MXCHANNUMBER 6 // Number of channels read from MX12

int value[MXCHANNUMBER];
int chan[MXCHANNUMBER];
long rxlast;
int ccount = 0;

void setup()                    // run once, when the sketch starts
{

  Serial.begin(9600); //Serial Begin
  attachInterrupt(1, rxInt, RISING);
  encoderBegin(OUTPUT_PIN);
}

void loop()      // run over and over again
{
  read_ppm();  // reading data from ppm and checking for errors
  write_ppm(); // send data as PPM24 to jeti
} 

void rxInt() {
  long now = micros();
  boolean complete = false;
  if(rxlast>0) {
    unsigned long diff = now - rxlast;
    if (diff > 5000){
      // sync found
      ccount = 0;
    }
    else{
      // read the channel data
      if(ccount<=MXCHANNUMBER-1){
        if(diff>1050 && diff<2000){ // valid signal!!
          value[ccount]=diff;
        }
        ccount ++;
      }
      else{
        ccount = 0;
      }
    }
  }
  rxlast = now;
}


void read_ppm()
{
  // input 
  int pulsewidth;
  while(pulseIn(INPUT_PIN, LOW) < 5000){
  } //Wait for the beginning of the frame
  for(int x=0; x<=MXCHANNUMBER-1; x++)//Loop to store all the channel position
  {
    value[x]=pulseIn(INPUT_PIN, LOW,100000);
  }
  //  }

  for(int x=0; x<=MXCHANNUMBER-1; x++)//Loop to print and clear all the channel readings
  { 
    Serial.print(value[x]); //Print the value
    Serial.print(" ");
    chan[x] =value[x];
    value[x]=0; //Clear the value after is printed
  }
  Serial.println(""); //Start a new line 
}


void write_ppm()
{
  //encoderWrite(2,map(chan[2], 1100, 1900, 860 ,1700));  // for using read_ppm()
    encoderWrite(2,map(value[2], 1100, 1900, 860 ,1700)); // for using interrupt()
}

Any ideas / solutions to this?
Thanks
oves

oves,
I'm curious if you have made any progress on this project. I am also interested in decoding/manipulating/encoding the ppm signal. At first glance, it appears that mem's decoding and encoding libraries both use timer1, which I believe is causing your issue. I'm not convinced, however, that changing one of the libraries to another interrupt timer will resolve the issue. One idea I'm trying to wrap my mind around is to read every other ppm 'packet', and write the manipulated 'packet' in between reads. The downside of this approach would be missing out on every other packet transmitted to the arduino, as well as reduced holding power of the servos caused by only receiving a position command every 40ms versus every 20ms. Please update with any progress, and perhaps mem will chime in with some insight...

As Tweaked has noticed, you can't user timer1 to both decode and encode the PPM stream. The easiest solution is to use a board with more than one 16 bit timer, such as the teensy (Teensy USB Development Board) or the Mega and modify the encode or decode library to use the second 16 bit timer.

Another approach could be to modify the encode logic to use an 8 bit timer based on the servoTimer2 code: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1230479947
But that not easy thing to implement and unfortunately I don't currently have the bandwidth to help.

Mem,

given the same choice I decided to go the other way and decoded the PWM signal with a combination of micros() using Timer2 for added precision and left Timer1 to the Servo library. With a little software smoothing to prevent the worst errors due to interrupts.

I chose this way, because a stable Servo on the output was more important to me and timing errors there a lot harder to correct than on the PWM input.

All this runs quite reasonably on a Duemilanove or Pro mini with 8 or 16 MHz.

Korman

Korman, good to hear you have it working.
Perhaps you could post your code for others seeking this functionality

mem,
Thanks for the insight on using a second 16-bit timer. I was concerned the two interrupts would interfere with the timing of the other, but will definitely give it a try. I have both a teensy and Mega in hand, and will post my findings here.

Thanks again!

mem,
I have modified the RCencoder library to use Timer0 so it can be used alongside the Decode library, and wanted your thoughts on disabling the interrupts independently versus using the global cli()/sei() disabler. Do you think this is justified, or overkill?

Thanks in advance,
Billy

a better way to handle the interrupts is to save and restore the interurpt state:

  uint8_t oldSREG = SREG; // save the interrupt register
  cli();  
  Channels[Channel].ticks = MS_TO_TICKS(microseconds) ;
  digitalWrite(ledTest1,HIGH);
  SREG = oldSREG; // restore the interrupt register

I would be interested to see the timer0 code, are you changing the time prescale? (I wonder if a prescale of 64 gives good enough resolution?)

Oops, I meant timer3! Basic modifications:

  TIMSK3 |= (1<<OCIE3A); //Enable compare interrupt
  TCCR3A = _BV(WGM30) | _BV(WGM31);   //Timer 3 is Phase-correct 10-bit PWM.
  TCCR3A |= _BV(COM3A1);              //Clear OC3A on compare match when up-counting, set OC3A on compare match when 

down-counting.

   TCCR3B = _BV(WGM33) | _BV(WGM32) | _BV(CS31); /* div 8 clock prescaler */

Does this look accurate?

Also, since the decode library gives the option for rising/falling edge detection, would it be worthwhile to include a TCCRxA |= _BV(COMxA0) option in the encode library for those working with inverted signals?

As always, thanks for paving the way in ppm signal handling!

Kind Regards,
Billy

Billy, that Looks good.

Have fun!