RC plane elevon and V-tail mixer on an Arduino Uno
An Arduino flight mixer: two joysticks stand in for the sticks, and two servos move as a flying wing’s elevons or a V-tail, picked by a switch. The whole build, an Arduino Uno and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 25 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $38. On Mokxi they're free.
What you'll learn
- Mixing two control inputs into two servo outputs
- Converting an analog stick reading to a percent
- Using a dead band so a stick rests at zero
- RC servo pulse widths from 1000 to 2000 microseconds
- Powering servos separately with a shared ground
- Difficulty: Intermediate
- Time: About 25 minutes
- Board: Arduino Uno R3
- Parts: 10
How it works
A flying wing has no tail, so two surfaces on the back edge do both pitch and roll. Each servo gets a sum of two stick values: left is elevator plus the other input, right is elevator minus it. The slide switch on pin 4 picks the other input: the aileron stick in elevon mode, the rudder stick in V-tail mode.
Every stick is turned into a percent from -100 to +100, and each sum is held to that range. Then each percent becomes a servo pulse between 1000 and 2000 microseconds, with 1500 as center. The Serial Monitor prints a line whenever something changes.
Parts list
10 parts, plus the jumper wires. They retail for about $38; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 2 × Hobby servoIn Halloween Prop Kit, Christmas Lights Kit, Starter Projects Kit.
- 1 × Half breadboard
- 1 × Slide switchIn Logic Lab Kit, Spaceship Panel Kit.
- 2 × Thumb stickIn Claw Machine Kit, Spaceship Panel Kit.
- 3 × Power, 5 V
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Arduino Uno R3 pin GND | Ground |
| Hobby servo (2) pin PWM | Arduino Uno R3 pin 10 |
| Hobby servo (1) pin PWM | Arduino Uno R3 pin 9 |
| Slide switch pin 2 | Arduino Uno R3 pin 4 |
| Thumb stick (1) pin VCC | Arduino Uno R3 pin 5V |
| Thumb stick (1) pin VRX | Arduino Uno R3 pin A0 |
| Thumb stick (2) pin VRX | Arduino Uno R3 pin A1 |
| Thumb stick (2) pin VRY | Arduino Uno R3 pin A2 |
| Hobby servo (1) pin GND | Ground |
| Hobby servo (1) pin VCC | 5 V |
| Hobby servo (2) pin GND | Ground |
| Hobby servo (2) pin VCC | 5 V |
| Slide switch pin 1 | Ground |
| Thumb stick (1) pin GND | Ground |
| Thumb stick (2) pin GND | Ground |
| Thumb stick (2) pin VCC | 5 V |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// RC plane servo mixer: elevon or V-tail mixing on an Arduino, from two
// joysticks standing in for a transmitter's sticks.
//
// left stick VRX (rudder) on A0
// right stick VRX (aileron) on A1, VRY (elevator) on A2
// switch MODE on pin 4: elevon one way, V-tail the other
// servos left surface on pin 9, right surface on pin 10,
// from a separate 5 V supply with the grounds joined
//
// WHY MIX. A flying wing has no tail: two surfaces on the back edge of the
// wing, called elevons, do the work of the elevator and the ailerons. For
// pitch both move the same way; for roll they move opposite ways. So each
// servo gets a sum:
//
// left = elevator + aileron
// right = elevator - aileron
//
// A V-tail is the same idea with the rudder in place of the ailerons: two
// tail surfaces at a V make pitch moving together and yaw moving apart.
//
// left = elevator + rudder
// right = elevator - rudder
//
// Every value here is a percent of full stick, -100 to +100. The sums can
// reach 200, so each one is held to +/-100: at full up and full roll
// together, one surface sits at its end and the other in the middle.
//
// RC SERVO PULSES. Model servos and receivers speak in pulse widths: 1500
// microseconds is center and 1000 to 2000 is full travel, 50 times a second.
// The servos are attached with that window, and writeMicroseconds() sends
// the width outright.
#include <Servo.h>
Servo leftServo, rightServo;
const int MODE_PIN = 4;
// If a surface moves the wrong way on your model, set its entry to true.
const bool REVERSE_LEFT = false;
const bool REVERSE_RIGHT = false;
// A small dead band, so a stick that does not center perfectly reads 0.
const int DEAD_BAND = 3; // percent
// A stick's reading, 0 to 1023, as -100 to +100 percent.
int stickPercent(int pin) {
int percent = map(analogRead(pin), 0, 1023, -100, 100);
if (abs(percent) <= DEAD_BAND) percent = 0;
return percent;
}
// A percent, -100 to +100, as a pulse width, 1000 to 2000 microseconds.
int toPulse(int percent, bool reverse) {
if (reverse) percent = -percent;
return 1500 + percent * 5;
}
int lastLeft = 0, lastRight = 0;
bool lastElevon = false;
void setup() {
pinMode(MODE_PIN, INPUT_PULLUP);
leftServo.attach(9, 1000, 2000);
rightServo.attach(10, 1000, 2000);
Serial.begin(115200);
Serial.println("servo mixer: move the sticks, flip MODE for elevon or V-tail");
}
void loop() {
bool elevonMode = digitalRead(MODE_PIN) == HIGH;
int elevator = stickPercent(A2);
// In elevon mode the right stick's sideways axis rolls; in V-tail mode
// the left stick's sideways axis yaws.
int other = elevonMode ? stickPercent(A1) : stickPercent(A0);
int left = constrain(elevator + other, -100, 100);
int right = constrain(elevator - other, -100, 100);
int leftPulse = toPulse(left, REVERSE_LEFT);
int rightPulse = toPulse(right, REVERSE_RIGHT);
leftServo.writeMicroseconds(leftPulse);
rightServo.writeMicroseconds(rightPulse);
// Report a change once.
if (leftPulse != lastLeft || rightPulse != lastRight || elevonMode != lastElevon) {
lastLeft = leftPulse;
lastRight = rightPulse;
lastElevon = elevonMode;
Serial.print(elevonMode ? "elevon elevator " : "V-tail elevator ");
Serial.print(elevator);
Serial.print(elevonMode ? "% aileron " : "% rudder ");
Serial.print(other);
Serial.print("% -> left ");
Serial.print(leftPulse);
Serial.print(" us right ");
Serial.print(rightPulse);
Serial.println(" us");
}
delay(20);
}
Code walkthrough
The key functions of the sketch, and what each one does.
stickPercent sketch.ino
Reads one stick axis from 0 to 1023 and maps it to -100 to +100. Anything within DEAD_BAND of zero is forced to 0, so a stick that does not center perfectly does not make the servo twitch.
// A stick's reading, 0 to 1023, as -100 to +100 percent.
int stickPercent(int pin) {
int percent = map(analogRead(pin), 0, 1023, -100, 100);
if (abs(percent) <= DEAD_BAND) percent = 0;
return percent;
}toPulse sketch.ino
Turns a percent into a pulse width: 1500 plus 5 microseconds for each percent. That gives 1000 at -100 and 2000 at +100. If the reverse flag is true the percent is flipped first, for a surface that moves the wrong way.
// A percent, -100 to +100, as a pulse width, 1000 to 2000 microseconds.
int toPulse(int percent, bool reverse) {
if (reverse) percent = -percent;
return 1500 + percent * 5;
}loop sketch.ino
Reads the mode switch and the elevator stick on A2. The other input comes from A1 in elevon mode or A0 in V-tail mode. It makes the two sums, limits them with constrain, and sends the pulses with writeMicroseconds.
void loop() {
bool elevonMode = digitalRead(MODE_PIN) == HIGH;
int elevator = stickPercent(A2);
// In elevon mode the right stick's sideways axis rolls; in V-tail mode
// the left stick's sideways axis yaws.
int other = elevonMode ? stickPercent(A1) : stickPercent(A0);
int left = constrain(elevator + other, -100, 100);
int right = constrain(elevator - other, -100, 100);
int leftPulse = toPulse(left, REVERSE_LEFT);
int rightPulse = toPulse(right, REVERSE_RIGHT);
leftServo.writeMicroseconds(leftPulse);
rightServo.writeMicroseconds(rightPulse);
// Report a change once.
if (leftPulse != lastLeft || rightPulse != lastRight || elevonMode != lastElevon) {
lastLeft = leftPulse;
lastRight = rightPulse;
lastElevon = elevonMode;
Serial.print(elevonMode ? "elevon elevator " : "V-tail elevator ");
Serial.print(elevator);
Serial.print(elevonMode ? "% aileron " : "% rudder ");
Serial.print(other);
Serial.print("% -> left ");
Serial.print(leftPulse);
Serial.print(" us right ");
Serial.print(rightPulse);
// ... 4 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- A control surface moves the wrong way This project
SetREVERSE_LEFTorREVERSE_RIGHTto true for that servo. - Servos jitter or the board resets This project
Power the servos from a separate 5 V supply and join its ground to the Arduino ground. - The servo jitters, twitches or resets the board when it moves. Hobby servo
Power it from its own 5 V supply, not the Arduino 5 V pin, and join the grounds. A hobby servo can pull several hundred milliamps. Add a 100 uF to 470 uF capacitor across its supply. - The servo hums at the end of its travel or will not reach full angle. Hobby servo
Stay inside the pulse range, about 1000 to 2000 us for most hobby servos. Pushing past the end stop makes it strain. Calibrate the min and max pulse for your servo. - The input reads random values in one position. Slide switch
A switch only connects or disconnects. Add a pull-up or pull-down resistor, or use INPUT_PULLUP, so the pin has a defined level when the switch is open. - The load never turns on when the switch is flipped. Slide switch
Use the center (common) leg and one outer leg. The unused outer leg goes nowhere. Check that the switch is in series with the load. - The axis reads about half scale at rest instead of exactly zero, and the values drift a little, so the cursor or motor creeps. Thumb stick
Center is about half of the ADC range (about 512 on a 10 bit Arduino). Read it at startup and subtract that, then add a dead zone of a few counts. - The push button reads random values or always looks pressed. The switch pin is a plain contact to ground and has no pull-up on the stick. Thumb stick
Set the switch pin to INPUT_PULLUP and treat LOW as pressed. Debounce it in code. Power the stick from the same voltage as the ADC reference.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.