Wednesday, August 26, 2015

Flying a Quadcopter with the Wii Remote and Arduino (4): Using Arduino as a Receiver

⚠️ Warning

I do not recommend flying a drone built with the method described here anywhere near people or buildings.

The Wii Remote's sensors were never designed for RC use. The craft can become uncontrollable, veer off course, or crash unexpectedly.

Take all necessary safety precautions.

…or better yet, don't actually fly it.




In the previous post I got everything roughly assembled, but MultiWii and Arduino were both empty — so let's fill them in.

How to use MultiWii as a flight controller is well covered on other sites, so I'll start with the Arduino side first.



What the Arduino does in this build


MultiWii is used as the flight controller, handling the quadcopter's attitude (orientation) control.

Normally, a radio transmitter (TX) sends signals that the receiver (RX) picks up, and those control signals feed into MultiWii.



…but in this build we're using a Wii Remote instead of a transmitter, so there's no standard receiver that can talk directly to MultiWii.

The solution: run the Arduino UNO as a Wii Remote receiver.



MultiWii input channels




MultiWii accepts 7 channels, of which the first 4 are mandatory. These are typically driven with analog (PWM) input.

  • Roll

    Rotation around the forward axis — controls left/right banking for turning


  • Pitch

    Rotation around the lateral axis (perpendicular to forward, parallel to ground) — controls nose up/down


  • Yaw

    Rotation around the vertical axis — turns the craft without banking


  • Throttle

    Thrust — climbs or descends when Roll and Pitch are at center


  • AUX1, AUX2, AUX3

    Auxiliary control signals — e.g. toggling altitude-hold auto-control on/off, or firing a camera shutter

The Arduino UNO has 6 PWM output pins, which sounds like enough — but it isn't.



The problem: to communicate with the Wii Remote we need a USB Bluetooth dongle, which requires a USB Host Shield. The USB Host Shield consumes 3 of the 6 PWM pins, leaving too few to connect to MultiWii.

(See the RC car post using Arduino + Wii Remote for more detail on the Bluetooth dongle and USB Host Shield setup.)



Sending all signals on one wire with PPM


Since we're short on pins, we use PPM (Pulse Position Modulation) to send all channels over just 3 wires: signal, power, and GND.



These pages explain PPM clearly:

"Decoding RC Signals with a Single-Chip Microcontroller"

"Mahou no Ookama (Magic Cauldron blog)"

The Magic Cauldron post is especially interesting — it controls MultiWii with a PS3 controller.



In short, PPM encodes each channel value as a pulse width and transmits them sequentially on a single signal line.



Generating the PPM signal on Arduino


Using Arduino's Timer with interrupts makes it straightforward to generate a PPM signal.

Here's the relevant code:

#include <TimerOne.h>

#define CHANNELS 6

#define ROLL     0
#define PITCH    1
#define THROTTLE 2
#define YAW      3
#define AUX1     4
#define AUX2     5

#define MIN_PULSE_TIME  1000 // 1000us
#define MAX_PULSE_TIME  2000 // 2000us

#define SYNC_PULSE_TIME  3050 // 3000us

#define PIN_PPM 5

unsigned int pw[CHANNELS];

void setPulse(unsigned int r, unsigned int p, unsigned int t, unsigned int y, unsigned int a1, unsigned int a2){
pw[ROLL] = r;
pw[PITCH] = p;
pw[THROTTLE] = t;
pw[YAW] = y;
pw[AUX1] = a1;
pw[AUX2] = a2;
}

void setup() {
pinMode(PIN_PPM, OUTPUT);
pinMode(PIN_CAMERA, OUTPUT);
digitalWrite( PIN_CAMERA, LOW );

Serial.begin(38400);

activate = 0;

// Start timer with sync pulse
Timer1.initialize(SYNC_PULSE_TIME);
Timer1.attachInterrupt(isr_sendPulses);
isr_sendPulses();
}

// Sync pulse first
volatile int currentChannel = 0;

void isr_sendPulses() {
digitalWrite(PIN_PPM, LOW);
if( ! activate ){ return; }

if (currentChannel == CHANNELS) {
// After last channel
Timer1.setPeriod(SYNC_PULSE_TIME);
currentChannel = 0; // Will be 0 on next interrupt
} else {
if(pw[currentChannel] < MIN_PULSE_TIME){ pw[currentChannel] = MIN_PULSE_TIME; }
if(pw[currentChannel] > MAX_PULSE_TIME){ pw[currentChannel] = MAX_PULSE_TIME; }
Timer1.setPeriod(pw[currentChannel]);
currentChannel++;
}

digitalWrite(PIN_PPM, HIGH);
}

Call setPulse with the Roll, Pitch, Yaw, Throttle, etc. values to store them in a buffer.

The code above omits it for brevity, but the loop function reads input from the Wii Remote and calls setPulse to update the buffer continuously.



Timer1 is initialized in setup separately from setPulse. After SYNC_PULSE_TIME, a timer interrupt fires and calls isr_sendPulses.



Inside isr_sendPulses, the Timer1 period is overwritten based on the buffered values from setPulse, which controls the pulse width for each channel.



Strictly speaking, the LOW period for PIN_PPM should match the PPM spec's exact timing window — but MultiWii reads the values correctly with this code as-is, so I left it.



Wiring Arduino UNO to MultiWii


MultiWii's PPM input pin defaults to D2, so the connections are:

  • Arduino PIN_PPM → MultiWii D2

  • Arduino GND → MultiWii GND

  • Arduino Vin → MultiWii 5V



You might expect it to be Arduino 5V → MultiWii 5V, but here's why Vin is used instead:

When the battery is connected to MultiWii, it supplies 5V out. If you also connect USB to the Arduino at that point, you'd have 5V coming in from two directions — one would back-feed into the other, which seems like a bad idea. (Probably.)



By connecting Arduino Vin to MultiWii 5V, the Arduino is powered directly from the battery via MultiWii, and no USB cable is needed. This also means the Arduino doesn't need its own battery.



Honestly, I'm not 100% confident this is the textbook-correct way to wire it — but it works.



Bonus: Camera control


Check on Amazon MultiWii has a built-in feature to send a record-start pulse to a camera — but since we have an Arduino on board, let's control the camera directly from there instead.



A GoPro would be the obvious choice, but it's pricey. A compact camera is too heavy and destabilizes the flight.



Instead, I'm using a Walkera action camera designed for RC use. It's Chinese-made and a bit dubious in operation, but it's incredibly cheap and weighs only around 10 grams.



The connector is Walkera-proprietary, but cut the cable and wire the 3 signal lines to Arduino digital pins, 5V, and GND and you're good.


Controlling the camera from Arduino


This camera accepts a ~1.5-second pulse as its trigger signal.

In photo mode: one pulse = one shot.
In video mode: each pulse toggles recording on/off.



(This isn't from official documentation — I figured it out by experimenting, so it may not be perfectly accurate.)



We already have one timer occupied by the PPM signal, so using an interrupt for this would be overkill. Instead, here's the loop-based approach:

#define PIN_CAMERA 2

unsigned long camtrig = 0;
#define CAMERA_PULSE 1500

void setup() {
pinMode(PIN_CAMERA, OUTPUT);
}

void loop() {
if( 0 < camtrig && camtrig < millis() ){
camtrig = 0;
digitalWrite( PIN_CAMERA, LOW );
}
if( camtrig == 0 && Wii.getButtonPress(TWO) ){
camtrig = millis() + CAMERA_PULSE;
digitalWrite( PIN_CAMERA, HIGH );
}
}

Pressing the Wii Remote's 2 button sends an approximately 1.5-second pulse.

Timing this in the loop is imprecise, but the camera accepts it just fine.



Note: a delay(1500) might look simpler, but delay internally uses a timer that tends to interfere with the PPM interrupt — avoid it here.



Here's what the footage looks like. The image quality is pretty rough — this isn't exactly an action camera.



This was filmed long before multirotors became a regulatory issue — flying in a park like this might not be okay these days.



This post is getting long, so I'll stop here.

Next time: mapping Wii Remote buttons to control channels and configuring MultiWii.

No comments:

Post a Comment