Built-in project · Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
Arduino Uno R3 pin GNDGround
Hobby servo (2) pin PWMArduino Uno R3 pin 10
Hobby servo (1) pin PWMArduino Uno R3 pin 9
Slide switch pin 2Arduino Uno R3 pin 4
Thumb stick (1) pin VCCArduino Uno R3 pin 5V
Thumb stick (1) pin VRXArduino Uno R3 pin A0
Thumb stick (2) pin VRXArduino Uno R3 pin A1
Thumb stick (2) pin VRYArduino Uno R3 pin A2
Hobby servo (1) pin GNDGround
Hobby servo (1) pin VCC5 V
Hobby servo (2) pin GNDGround
Hobby servo (2) pin VCC5 V
Slide switch pin 1Ground
Thumb stick (1) pin GNDGround
Thumb stick (2) pin GNDGround
Thumb stick (2) pin VCC5 V

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// 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 editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • A control surface moves the wrong way This project
    Set REVERSE_LEFT or REVERSE_RIGHT to 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.