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.
As a total noob i cant understand very much. 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);
}
// 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
}
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()
}
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.
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.
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.
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?
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!