22 Apr 2013

V-Tail mixer with low pass filter

/**
PPM V-Tail/elevon mixer

Input pins:
 2: aileron/rudder
 3: elevator

Output pins:
 9: servo left
10: servo right
*/


#include <math.h>
#include <Servo.h>

//#define DEBUG
#define START_OUTPUT_PIN 9
#define LOW_PASS_FILTER 50 // difference must be greater than X at 


int Chan1Interrupt = 0; // pin 2, aileron
int Chan2Interrupt = 1; // pin 3, elevator

unsigned long Chan1_startPulse, Chan2_startPulse;
volatile double Chan1_val, Chan2_val = 1500;
volatile double Chan1_val_last, Chan2_val_last = 1500;
long StartMillis=0;
long FrameCounter=0;

Servo ServoArray[2];

double reverse(double val) {
 return (val-1500) * -1 + 1500;
}

double add(double val1, double val2) {
 double out = (val1 -1500 + val2 -1500) + 1500;
 out = (out < 1000) ? 1000 : out;
 out = (out > 2000) ? 2000 : out;
 return out;
}

void serviceServos(long pFrameCounter) {
 
 // do the v-tail mixing 
 double left  = add(Chan2_val, Chan1_val);
 double right = add(reverse(Chan2_val), Chan1_val);
 
 ServoArray[0].writeMicroseconds(left);
 ServoArray[1].writeMicroseconds(right);
}

void setup() {
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
 
 ServoArray[0].attach(START_OUTPUT_PIN+0, 900, 2100); // aileron
 ServoArray[1].attach(START_OUTPUT_PIN+1, 900, 2100); // elevator

 StartMillis = millis();
#ifdef DEBUG
 Serial.begin(115200);
#endif
}

void loop() {
 long LocalMillis;
 long LocalFrameCounter;
 LocalMillis = millis();
 LocalFrameCounter = (LocalMillis - StartMillis) / 20;

 if (LocalFrameCounter > FrameCounter) {
  FrameCounter = LocalFrameCounter;
  serviceServos(FrameCounter);
 }
}

void Chan1_begin() {
 Chan1_startPulse = micros();
 detachInterrupt(Chan1Interrupt);
 attachInterrupt(Chan1Interrupt, Chan1_end, FALLING);
}

void Chan1_end() {
 Chan1_val = micros() - Chan1_startPulse;
 detachInterrupt(Chan1Interrupt);
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 if (Chan1_val < 1000 || Chan1_val > 2000) {
  Chan1_val = Chan1_val_last;
 } else {
  if ((Chan1_val + LOW_PASS_FILTER) > Chan1_val_last || 
      (Chan1_val - LOW_PASS_FILTER) < Chan1_val_last)
   Chan1_val_last = Chan1_val;
  else
   Chan1_val = Chan1_val_last;
 }
}

void Chan2_begin() {
 Chan2_startPulse = micros();
 detachInterrupt(Chan2Interrupt);
 attachInterrupt(Chan2Interrupt, Chan2_end, FALLING);
}

void Chan2_end() {
 Chan2_val = micros() - Chan2_startPulse;
 detachInterrupt(Chan2Interrupt);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
 if (Chan2_val < 1000 || Chan2_val > 2000) { 
  Chan2_val = Chan2_val_last;
 } else {
  if ((Chan2_val + LOW_PASS_FILTER) > Chan2_val_last || 
      (Chan2_val - LOW_PASS_FILTER) < Chan2_val_last)
   Chan2_val_last = Chan2_val;
  else
   Chan2_val = Chan2_val_last;
 }
}

21 Apr 2013

Elevon / V-Tail mixer with Arduino Pro Mini 5V



I got this new and and really handy r/c transmitter from multiplex: the SMART SX.



It's really a minimal approach. I have been a fan of multiplex for a long time because they have always been pioneers in their domain. This one is the first quality r/c transmitter with a gamepad layout (there are many technical features other will copy sooner or later but I bought it because of the layout primarily).

However, this being an entry level transmitter it is really limited in functionality. It has all the features a modern transmitter needs to have, configurable through 1 (yes ONE) button!

However, Elevon/Y-tail mixing isn't a feature baked in. I needed an elevon mixer for my new flying wing and couldn't get one quickly. Fortunately I have some unused micro controller boards laying around which just waited for this hack.


A 15$ Arduino Pro mini 5V seemed to be suitable (although overkill, I just needed something in a short time). Using 2 input interrupts  1 timer for the Servo.H library and 2 PWM pins as output, there is still a lot of unused hardware.

Code

/**
PPM V-Tail/elevon mixer

Input pins:
 2: aileron/rudder
 3: elevator

Output pins:
 9: servo left
10: servo right
*/


#include <math.h>
#include <Servo.h>

//#define DEBUG
#define START_OUTPUT_PIN 9

int Chan1Interrupt = 0; // pin 2, aileron
int Chan2Interrupt = 1; // pin 3, elevator

unsigned long Chan1_startPulse, Chan2_startPulse;
volatile double Chan1_val, Chan2_val = 1500;
volatile double Chan1_val_last, Chan2_val_last = 1500;
long StartMillis=0;
long FrameCounter=0;

Servo ServoArray[2];

double reverse(double val) {
 return (val-1500) * -1 + 1500;
}

double add(double val1, double val2) {
 double out = (val1 -1500 + val2 -1500) + 1500;
 out = (out < 1000) ? 1000 : out;
 out = (out > 2000) ? 2000 : out;
 return out;
}

void serviceServos(long pFrameCounter) {
 
 // do the v-tail mixing 
 double left  = add(Chan2_val, Chan1_val);
 double right = add(reverse(Chan2_val), Chan1_val);
 
 ServoArray[0].writeMicroseconds(left);
 ServoArray[1].writeMicroseconds(right);
}

void setup() {
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
 
 ServoArray[0].attach(START_OUTPUT_PIN+0, 900, 2100); // aileron
 ServoArray[1].attach(START_OUTPUT_PIN+1, 900, 2100); // elevator

 StartMillis = millis();
#ifdef DEBUG
 Serial.begin(115200);
#endif
}

void loop() {
 long LocalMillis;
 long LocalFrameCounter;
 LocalMillis = millis();
 LocalFrameCounter = (LocalMillis - StartMillis) / 20;

 if (LocalFrameCounter > FrameCounter) {
  FrameCounter = LocalFrameCounter;
  serviceServos(FrameCounter);
 }
}

void Chan1_begin() {
 Chan1_startPulse = micros();
 detachInterrupt(Chan1Interrupt);
 attachInterrupt(Chan1Interrupt, Chan1_end, FALLING);
}

void Chan1_end() {
 Chan1_val = micros() - Chan1_startPulse;
 detachInterrupt(Chan1Interrupt);
 attachInterrupt(Chan1Interrupt, Chan1_begin, RISING);
 if (Chan1_val < 1000 || Chan1_val > 2000) {
  Chan1_val = Chan1_val_last;
 } else {
  Chan1_val_last = Chan1_val;
 }
}

void Chan2_begin() {
 Chan2_startPulse = micros();
 detachInterrupt(Chan2Interrupt);
 attachInterrupt(Chan2Interrupt, Chan2_end, FALLING);
}

void Chan2_end() {
 Chan2_val = micros() - Chan2_startPulse;
 detachInterrupt(Chan2Interrupt);
 attachInterrupt(Chan2Interrupt, Chan2_begin, RISING);
 if (Chan2_val < 1000 || Chan2_val > 2000) { 
  Chan2_val = Chan2_val_last;
 } else {
  Chan2_val_last = Chan2_val;
 }
}