Elevon / V-Tail mixer with Arduino Pro Mini 5V
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; } }