Tutorial: how to control an IR helicopter programmatically with Arduino, Android, Kinect, brain, and more!

For those of you who are not familiar with an infrared helicopter, please search "SYMA S107" on eBay or on YouTube. It's a little indoor coaxial helicopter around 20 USD, recharged through USB cable.




The original remote control that comes with the helicopter is pretty good, at least a lot easier to use than a touchscreen. 



However, our passion doesn't end here with mere manual control. Inspired by all those robodance projects, we need more joy of robotic automation. 

Let's hack the SYMA S107 helicopter to give it some artificial intelligence! 

You might wonder why this particular model (Syma S107)? Well, simply because it is perhaps the most popular one in the IR helicopter market. And many brilliant hackers have already done the work for us. We don't have to reinvent the wheel to hack it again.

As usual, let's start with an Arduino, the most simple way to prototype electronic projects. Our goal here is to make an arduino based IR transmitter to programmatically control the helicopter. 

Step 1. Prepare the circuit 


The circuit is extremely simple, we just need:

1 Arduino, 1 IR LED and 1 resister of 200 to 1000 ohm.

Optional: an IR receiver (for testing).



That's all to transmit IR signal from Arduino to the helicopter. If you already have an Arduino board, it costs you almost nothing to get the rest parts.

If you are interested in IR tests, here is a detailed tutorial: http://www.ladyada.net/learn/sensors/ir.html

Otherwise, we can directly load the Arduino code and control the helicopter!

Step 2. Program the Arduino

Fortunately, there are several existing Arduino projects we can directly use and customize. 

The first working code (tested):
//Arduino code to control a helicotper.
int IRledPin = 12;
int incomingByte = 0;
String incomingString;
int pulseValues[33];
int pulseLength = 0;

void setup() {
// initialize the IR digital pin as an output:
pinMode(IRledPin, OUTPUT);
pinMode(13, OUTPUT);
Serial.begin(9600);

for (int i=0; i < 13; i++)
pulseValues[i] = 0;


}

void loop()
{
SendCode();
}


void pulseIR(long microsecs) {
cli(); // this turns off any background interrupts

while (microsecs > 0) {
// 38 kHz is about 13 microseconds high and 13 microseconds low
digitalWrite(IRledPin, HIGH); // this takes about 3 microseconds to happen
delayMicroseconds(10); // hang out for 10 microseconds
digitalWrite(IRledPin, LOW); // this also takes about 3 microseconds
delayMicroseconds(10); // hang out for 10 microseconds

// so 26 microseconds altogether
microsecs -= 26;

}

sei(); // this turns them back on
}

void Zero()
{
pulseIR(300);
delayMicroseconds(300);
pulseLength += 600;
}

void One()
{
pulseIR(300);
delayMicroseconds(600);
pulseLength += 900;
}

void sendPulseValue(int pulseValue)
{
if (pulseValue == 1)
One();
else
Zero();
}

void checkPulseChanges()
{
if (Serial.available() > 0)
{
incomingByte = Serial.read();

//Pulse 1
if (incomingByte == 'a')
pulseValues[0] = 0;
if (incomingByte == 'A')
pulseValues[0] = 1;

//Pulse 2
if (incomingByte == 'b')
pulseValues[1] = 0;
if (incomingByte =='B')
pulseValues[1] = 1;

//Pulse 3
if (incomingByte == 'c')
pulseValues[2] = 0;
if (incomingByte == 'C')
pulseValues[2] = 1;

//Pulse 4
if (incomingByte == 'd')
pulseValues[3] = 0;
if (incomingByte == 'D')
pulseValues[3] = 1;

//Pulse 5
if (incomingByte == 'e')
pulseValues[4] = 0;
if (incomingByte == 'E')
pulseValues[4] = 1;

//Pulse 6
if (incomingByte == 'f')
pulseValues[5] = 0;
if (incomingByte == 'F')
pulseValues[5] = 1;

//Pulse 7
if (incomingByte == 'g')
pulseValues[6] = 0;
if (incomingByte == 'G')
pulseValues[6] = 1;

//Pulse 8
if (incomingByte == 'h')
pulseValues[7] = 0;
if (incomingByte == 'H')
pulseValues[7] = 1;

//Pulse 9
if (incomingByte == 'i')
pulseValues[8] = 0;
if (incomingByte == 'I')
pulseValues[8] = 1;

//Pulse 10
if (incomingByte == 'j')
pulseValues[9] = 0;
if (incomingByte == 'J')
pulseValues[9] = 1;

//Pulse 11
if (incomingByte == 'k')
pulseValues[10] = 0;
if (incomingByte == 'K')
pulseValues[10] = 1;

//Pulse 12
if (incomingByte == 'l')
pulseValues[11] = 0;
if (incomingByte == 'L')
pulseValues[11] = 1;

//Pulse 13
if (incomingByte == 'm')
pulseValues[12] = 0;
if (incomingByte == 'M')
pulseValues[12] = 1;

//Pulse 14
if (incomingByte == 'o')
pulseValues[13] = 0;
if (incomingByte == 'O')
pulseValues[13] = 1;

//Pulse 15
if (incomingByte == 'p')
pulseValues[14] = 0;
if (incomingByte == 'P')
pulseValues[14] = 1;

//Pulse 16
if (incomingByte == 'q')
pulseValues[15] = 0;
if (incomingByte == 'Q')
pulseValues[15] = 1;

//Pulse 17
if (incomingByte == 'r')
pulseValues[16] = 0;
if (incomingByte == 'R')
pulseValues[16] = 1;

//Pulse 18
if (incomingByte == 's')
pulseValues[17] = 0;
if (incomingByte == 'S')
pulseValues[17] = 1;

//Pulse 19
if (incomingByte == 't')
pulseValues[18] = 0;
if (incomingByte == 'T')
pulseValues[18] = 1;

//Pulse 20
if (incomingByte == 'u')
pulseValues[19] = 0;
if (incomingByte == 'U')
pulseValues[19] = 1;

//Pulse 21
if (incomingByte == 'v')
pulseValues[20] = 0;
if (incomingByte == 'V')
pulseValues[20] = 1;

//Pulse 22
if (incomingByte == 'w')
pulseValues[21] = 0;
if (incomingByte == 'W')
pulseValues[21] = 1;

//Pulse 23
if (incomingByte == 'x')
pulseValues[22] = 0;
if (incomingByte == 'X')
pulseValues[22] = 1;

//Pulse 24
if (incomingByte == 'y')
pulseValues[23] = 0;
if (incomingByte == 'Y')
pulseValues[23] = 1;

//Pulse 25
if (incomingByte == 'z')
pulseValues[24] = 0;
if (incomingByte == 'Z')
pulseValues[24] = 1;

//Pulse 26
if (incomingByte == '1')
pulseValues[25] = 0;
if (incomingByte == '2')
pulseValues[25] = 1;

//Pulse 27
if (incomingByte == '3')
pulseValues[26] = 0;
if (incomingByte == '4')
pulseValues[26] = 1;

//Pulse 28
if (incomingByte == '5')
pulseValues[27] = 0;
if (incomingByte == '6')
pulseValues[27] = 1;

//Pulse 29
if (incomingByte == '7')
pulseValues[28] = 0;
if (incomingByte == '8')
pulseValues[28] = 1;

//Pulse 30
if (incomingByte == '9')
pulseValues[29] = 0;
if (incomingByte == '!')
pulseValues[29] = 1;

//Pulse 31
if (incomingByte == '@')
pulseValues[30] = 0;
if (incomingByte == '#')
pulseValues[30] = 1;

//Pulse 32
if (incomingByte == '$')
pulseValues[31] = 0;
if (incomingByte == '%')
pulseValues[31] = 1;

//Pulse 33
if (incomingByte == '^')
pulseValues[32] = 0;
if (incomingByte == '&')
pulseValues[32] = 1;
}

}

void SendCode() {

while (true)
{
checkPulseChanges();

pulseIR(4000);
delayMicroseconds(2000);
pulseLength=6000;

sendPulseValue(pulseValues[0]);
sendPulseValue(pulseValues[1]);
sendPulseValue(pulseValues[2]);
sendPulseValue(pulseValues[3]);
sendPulseValue(pulseValues[4]);
sendPulseValue(pulseValues[5]);
sendPulseValue(pulseValues[6]);
sendPulseValue(pulseValues[7]);
sendPulseValue(pulseValues[8]);
sendPulseValue(pulseValues[9]);
sendPulseValue(pulseValues[10]);
sendPulseValue(pulseValues[11]);
sendPulseValue(pulseValues[12]);
sendPulseValue(pulseValues[13]);
sendPulseValue(pulseValues[14]);
sendPulseValue(pulseValues[15]);
sendPulseValue(pulseValues[16]);
sendPulseValue(pulseValues[17]);
sendPulseValue(pulseValues[18]);
sendPulseValue(pulseValues[19]);
sendPulseValue(pulseValues[20]);
sendPulseValue(pulseValues[21]);
sendPulseValue(pulseValues[22]);
sendPulseValue(pulseValues[23]);
sendPulseValue(pulseValues[24]);
sendPulseValue(pulseValues[25]);
sendPulseValue(pulseValues[26]);
sendPulseValue(pulseValues[27]);
sendPulseValue(pulseValues[28]);
sendPulseValue(pulseValues[29]);
sendPulseValue(pulseValues[30]);
sendPulseValue(pulseValues[31]);

//Footer
pulseIR(360);
delayMicroseconds( (28600 - pulseLength) );
}
}

This is the processing code to control the helicopter using the PC's camera and mouse wheel:
 
import processing.serial.*;
import controlP5.*;
import JMyron.*;

JMyron m;

ControlP5 controlP5;
CheckBox checkbox;
Button b;

float boxX;
float boxY;
int boxSize = 20;
boolean mouseOverBox = false;
byte[] previousFlags = new byte[32];
byte[] flagsToSend = new byte[32];
Serial port;
String outString;

int helicopterUpSpeed = 0;
int helicopterPitch = 63;
int helicopterYaw = 68;

void setup() {
m = new JMyron();
m.start(640,480);
size(640, 480);

controlP5 = new ControlP5(this);
checkbox = controlP5.addCheckBox("checkBox", 20, 20);
// make adjustments to the layout of a checkbox.
checkbox.setColorForeground(color(120));
checkbox.setColorActive(color(255));
checkbox.setColorLabel(color(128));
checkbox.setItemsPerRow(8);
checkbox.setSpacingColumn(30);
checkbox.setSpacingRow(10);
// add items to a checkbox.
checkbox.addItem("1", 0);
checkbox.addItem("2", 0);
checkbox.addItem("3", 0);
checkbox.addItem("4", 0);
checkbox.addItem("5", 0);
checkbox.addItem("6", 0);
checkbox.addItem("7", 0);
checkbox.addItem("8", 0);
checkbox.addItem("9", 0);
checkbox.addItem("10", 0);
checkbox.addItem("11", 0);
checkbox.addItem("12", 0);
checkbox.addItem("13", 0);
checkbox.addItem("14", 0);
checkbox.addItem("15", 0);
checkbox.addItem("16", 0);
checkbox.addItem("17", 0);
checkbox.addItem("18", 0);
checkbox.addItem("19", 0);
checkbox.addItem("20", 0);
checkbox.addItem("21", 0);
checkbox.addItem("22", 0);
checkbox.addItem("23", 0);
checkbox.addItem("24", 0);
checkbox.addItem("25", 0);
checkbox.addItem("26", 0);
checkbox.addItem("27", 0);
checkbox.addItem("28", 0);
checkbox.addItem("29", 0);
checkbox.addItem("30", 0);
checkbox.addItem("31", 0);
checkbox.addItem("32", 0);

checkbox.deactivateAll();


controlP5.addButton("Up", 0, 120, 120, 35, 20);
controlP5.addButton("Down", 0, 120, 160, 35, 20);

controlP5.addButton("Forward", 0, 180, 120, 45, 20);
controlP5.addButton("Backward", 0, 180, 160, 45, 20);

controlP5.addButton("TurnLeft", 0, 60, 120, 40, 20);
controlP5.addButton("TurnRight", 0, 60, 160, 40, 20);


port = new Serial(this, Serial.list()[0], 9600);

for (int i=0;i<32;i++)
{
flagsToSend[i] = 0;
previousFlags[i] = 0;
}

addMouseWheelListener(new java.awt.event.MouseWheelListener() {
public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
mouseWheel(evt.getWheelRotation());
}
}
);

startSetUp();
}

String addForwardZeroesTT(String inputString, int totalLength)
{

String outString = "";
for (int i = 0; i < (totalLength - inputString.length()); i++)
outString += "0";

outString = outString + inputString;

return outString;
}


//Incremental like bits
//0000, 0001, 0010, 0011, 0100, etc
void Up()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

if(helicopterUpSpeed <= 125)
helicopterUpSpeed += 1;

String newSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

setNewSpeed(currentSpeed, newSpeed);
}

void Down()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);
if (helicopterUpSpeed > 0)
helicopterUpSpeed -= 1;
String newSpeed = addForwardZeroesTT(binary(helicopterUpSpeed), 7);

setNewSpeed(currentSpeed, newSpeed);
}


void Backward()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

helicopterPitch += 1;

String newSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

setNewPitch(currentSpeed, newSpeed);
}

void Forward()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

helicopterPitch -= 1;

String newSpeed = addForwardZeroesTT(binary(helicopterPitch), 7);

setNewPitch(currentSpeed, newSpeed);
}

void TurnLeft()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

helicopterYaw -= 1;

String newSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

setNewYaw(currentSpeed, newSpeed);
}

void TurnRight()
{
String currentSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

helicopterYaw += 1;

String newSpeed = addForwardZeroesTT(binary(helicopterYaw), 7);

setNewYaw(currentSpeed, newSpeed);
}


void setNewSpeed(String currentSpeed, String newSpeed)
{

//Compare each bit and see if it needs changing.
if (newSpeed.charAt(6) != currentSpeed.charAt(6) )
checkbox.toggle(23);

if (newSpeed.charAt(5) != currentSpeed.charAt(5) )
checkbox.toggle(22);

if (newSpeed.charAt(4) != currentSpeed.charAt(4) )
checkbox.toggle(21);

if (newSpeed.charAt(3) != currentSpeed.charAt(3) )
checkbox.toggle(20);

if (newSpeed.charAt(2) != currentSpeed.charAt(2) )
checkbox.toggle(19);

if (newSpeed.charAt(1) != currentSpeed.charAt(1) )
checkbox.toggle(18);

if (newSpeed.charAt(0) != currentSpeed.charAt(0) )
checkbox.toggle(17);
}

void setNewPitch(String currentSpeed, String newSpeed)
{
if (newSpeed.charAt(6) != currentSpeed.charAt(6) )
checkbox.toggle(15);

if (newSpeed.charAt(5) != currentSpeed.charAt(5) )
checkbox.toggle(14);

if (newSpeed.charAt(4) != currentSpeed.charAt(4) )
checkbox.toggle(13);

if (newSpeed.charAt(3) != currentSpeed.charAt(3) )
checkbox.toggle(12);

if (newSpeed.charAt(2) != currentSpeed.charAt(2) )
checkbox.toggle(11);

if (newSpeed.charAt(1) != currentSpeed.charAt(1) )
checkbox.toggle(10);

if (newSpeed.charAt(0) != currentSpeed.charAt(0) )
checkbox.toggle(9);
}

void setNewYaw(String currentSpeed, String newSpeed)
{
if (newSpeed.charAt(6) != currentSpeed.charAt(6) )
checkbox.toggle(7);

if (newSpeed.charAt(5) != currentSpeed.charAt(5) )
checkbox.toggle(6);

if (newSpeed.charAt(4) != currentSpeed.charAt(4) )
checkbox.toggle(5);

if (newSpeed.charAt(3) != currentSpeed.charAt(3) )
checkbox.toggle(4);

if (newSpeed.charAt(2) != currentSpeed.charAt(2) )
checkbox.toggle(3);

if (newSpeed.charAt(1) != currentSpeed.charAt(1) )
checkbox.toggle(2);

if (newSpeed.charAt(0) != currentSpeed.charAt(0) )
checkbox.toggle(1);
}

void startSetUp()
{
//First clear the arduino.
port.write('a');
port.write('b');
port.write('c');
port.write('d');
port.write('e');
port.write('f');
port.write('g');
port.write('h');
port.write('i');
port.write('j');
port.write('k');
port.write('l');
port.write('m');
port.write('o');
port.write('p');
port.write('q');
port.write('r');
port.write('s');
port.write('t');
port.write('u');
port.write('v');
port.write('w');
port.write('x');
port.write('y');
port.write('z');
port.write('1');
port.write('3');
port.write('5');
port.write('7');
port.write('9');
port.write('@');
port.write('$');
port.write('^');


//Set the pulse to the basic configuration.
checkbox.toggle(1);
checkbox.toggle(6);


checkbox.toggle(10);
checkbox.toggle(11);

checkbox.toggle(12);
checkbox.toggle(13);
checkbox.toggle(14);
checkbox.toggle(15);
checkbox.toggle(16);

checkbox.toggle(25);

checkbox.toggle(28);
checkbox.toggle(29);
checkbox.toggle(30);
}


void draw()
{
background(200);

m.update();
int[] img = m.image();

//first draw the camera view onto the screen
loadPixels();

for(int i=0;i<640*480;i++){
pixels[i] = img[i];
}
updatePixels();
noFill();
int[][] a;

CheckHelicopterPosition();


text(" Current Speed: " + helicopterUpSpeed, 230, 135);
text(" Pitch: " + helicopterPitch, 230, 165);
text(" Yaw: " + helicopterYaw, 230, 195);
}


void CheckHelicopterPosition()
{

noFill();
int[][] a;

m.trackColor(255,255,0,255);
//draw bounding boxes of globs
a = m.globBoxes();
stroke(255,0,0);

int averageY = 0;

for(int i=0;i<a.length;i++){
int[] b = a[i];
rect(b[0], b[1], b[2], b[3]);

averageY += b[1];

}

if (a.length > 0)
{
averageY = averageY / a.length;
line(0,averageY,640,averageY);

text(" Average Y: " + averageY, 230, 215);

if (averageY > 240)
{
text(" Action: up ", 350, 20);
delay(150);
//Up();
}
else
{
text(" Action down ", 350,20);
//delay(250);
//Down();
}
}


}



void controlEvent(ControlEvent theEvent) {
if (theEvent.isGroup()) {

for (int i=0;i<theEvent.group().arrayValue().length;i++)
{
byte n = (byte)theEvent.group().arrayValue()[i];
flagsToSend[i] = n;
//there was a change in the flags, send the update.
if (previousFlags[i] != flagsToSend[i])
{
println(i);

if (i==0) {
if (n == 0) {
port.write('a');
}
else {
port.write('A');
}
}
if (i==1) {
if (n == 0) {
port.write('b');
}
else {
port.write('B');
}
}
if (i==2) {
if (n == 0) {
port.write('c');
}
else {
port.write('C');
}
}
if (i==3) {
if (n == 0) {
port.write('d');
}
else {
port.write('D');
}
}
if (i==4) {
if (n == 0) {
port.write('e');
}
else {
port.write('E');
}
}
if (i==5) {
if (n == 0) {
port.write('f');
}
else {
port.write('F');
}
}
if (i==6) {
if (n == 0) {
port.write('g');
}
else {
port.write('G');
}
}
if (i==7) {
if (n == 0) {
port.write('h');
}
else {
port.write('H');
}
}
if (i==8) {
if (n == 0) {
port.write('i');
}
else {
port.write('I');
}
}
if (i==9) {
if (n == 0) {
port.write('j');
}
else {
port.write('J');
}
}
if (i==10) {
if (n == 0) {
port.write('k');
}
else {
port.write('K');
}
}
if (i==11) {
if (n == 0) {
port.write('l');
}
else {
port.write('L');
}
}
if (i==12) {
if (n == 0) {
port.write('m');
}
else {
port.write('M');
}
}
if (i==13) {
if (n == 0) {
port.write('o');
}
else {
port.write('O');
}
}
if (i==14) {
if (n == 0) {
port.write('p');
}
else {
port.write('P');
}
}
if (i==15) {
if (n == 0) {
port.write('q');
}
else {
port.write('Q');
}
}
if (i==16) {
if (n == 0) {
port.write('r');
}
else {
port.write('R');
}
}
if (i==17) {
if (n == 0) {
port.write('s');
}
else {
port.write('S');
}
}
if (i==18) {
if (n == 0) {
port.write('t');
}
else {
port.write('T');
}
}
if (i==19) {
if (n == 0) {
port.write('u');
}
else {
port.write('U');
}
}
if (i==20) {
if (n == 0) {
port.write('v');
}
else {
port.write('V');
}
}
if (i==21) {
if (n == 0) {
port.write('w');
}
else {
port.write('W');
}
}
if (i==22) {
if (n == 0) {
port.write('x');
}
else {
port.write('X');
}
}
if (i==23) {
if (n == 0) {
port.write('y');
}
else {
port.write('Y');
}
}
if (i==24) {
if (n == 0) {
port.write('z');
}
else {
port.write('Z');
}
}
if (i==25) {
if (n == 0) {
port.write('1');
}
else {
port.write('2');
}
}
if (i==26) {
if (n == 0) {
port.write('3');
}
else {
port.write('4');
}
}
if (i==27) {
if (n == 0) {
port.write('5');
}
else {
port.write('6');
}
}
if (i==28) {
if (n == 0) {
port.write('7');
}
else {
port.write('8');
}
}
if (i==29) {
if (n == 0) {
port.write('9');
}
else {
port.write('!');
}
}
if (i==30) {
if (n == 0) {
port.write('@');
}
else {
port.write('#');
}
}
if (i==31) {
if (n == 0) {
port.write('$');
}
else {
port.write('%');
}
}
if (i==32) {
if (n == 0) {
port.write('^');
}
else {
port.write('&');
}
}
}

previousFlags[i]=n;
}
}
}


void mouseWheel(int delta) {
if (delta == 1)
Down();
else
Up();
}
We have tested it. It worked like a charm. Exactly as the project creator says:

"Utilizing the input from the webcam, it adjusts the speed until the helicopter is in the middle of the screen.
If it goes too high, it lowers the speed. If it gets too low or is stopped, it slowly increases the upwards speed."



For more details on this project, please have a glance here: http://www.avergottini.com/2011/05/arduino-helicopter-infrared-controller.html


The second working code (not yet tested by DIY Phone Gadgets):


Of course, your SYMA S107 helicopter might not always be exactly the same as others'. The first code might not work for your helicopter. Don't worry, it is possible that you have a 3-channel (30-bit) version, which uses a different protocol. Just load the following Arduino code:
/* S107 3-channel with checksum helicopter control code
* Copyright (C) 2012, Andrew Barry, Dan Barry
*
* Uses an Arduino to control a S107 helicopter
*
*
* Instructions:
* Connect an IR LED array to pin 8 (using a FET to amplify the signal)
* and use the serial monitor to send commands to the system
*
*/

#define LED 8

#define STATUS 13

//#define TAKEOFF_THROTTLE 240
//#define HOLDING_THROTTLE 130

byte yawCmd, pitchCmd, throttleCmd, trimCmd;

// Set this value for the default channel
// A = 0
// B = 1
// C = 2
byte channel = 0;

/*
* Setup function that initializes the serial port and
* sets some default values for the control variables.
* Also sets up the pins we'll be using.
*/
void setup()
{
Serial.begin(9600);
pinMode(STATUS,OUTPUT);
digitalWrite(STATUS,LOW);

pinMode(LED,OUTPUT);
digitalWrite(LED,LOW);

yawCmd = 8;
pitchCmd = 8;
trimCmd = 0;
throttleCmd = 0;

Serial.println("throttle = 0, standing by for commands.");
}

/*
* Function that does the actual work of converting commands into
* IR LED pulses and changes the pins in the appropriate manner.
*/
byte sendPacket(byte yaw, byte pitch, byte throttle, byte trim)
{

int packetData[100];
int pulseNum;

digitalWrite(STATUS,HIGH);

float channelDelayValue = 136500;

// channel A B or C
// A is 10 with 136500us packet delay
// B is 01 with 105200us packet delay
// C is 11 with 168700us packet delay
if (channel == 0)
{
packetData[0] = 1;
packetData[1] = 0;
channelDelayValue = 136500;

} else if (channel == 1)
{
packetData[0] = 0;
packetData[1] = 1;
channelDelayValue = 105200;

} else {
packetData[0] = 1;
packetData[1] = 1;
channelDelayValue = 168700;

}
packetData[2] = 0;
packetData[3] = 0;

// pitch

packetData[7] = (pitch & 0b1000) >> 3; // direction bit

if (pitch < 8) { pitch = 8 - pitch; } packetData[6] = (pitch & 0b0100) >> 2; // others are speed bits, note that they are reversed
packetData[5] = (pitch & 0b0010) >> 1;
packetData[4] = (pitch & 0b0001);

// throttle
// bits are reversed in the throttle command
packetData[15] = (throttle & 0b10000000) >> 7;
packetData[14] = (throttle & 0b01000000) >> 6;
packetData[13] = (throttle & 0b00100000) >> 5;
packetData[12] = (throttle & 0b00010000) >> 4;

packetData[11] = (throttle & 0b00001000) >> 3;
packetData[10] = (throttle & 0b00000100) >> 2;
packetData[9] = (throttle & 0b00000010) >> 1;
packetData[8] = (throttle & 0b00000001);

// yaw
packetData[19] = (yaw & 0b1000) >> 3; // direction bit
if (yaw < 8) { yaw = 8 - yaw; } packetData[18] = (yaw & 0b0100) >> 2;
packetData[17] = (yaw & 0b0010) >> 1;
packetData[16] = (yaw & 0b0001);

// these 4 bits are the checksum, so make sure they
// are 0s so they don't change the XOR later on
packetData[20] = 0;
packetData[21] = 0;
packetData[22] = 0;
packetData[23] = 0;

// yaw trim / yaw adjust (the little dial on the controller)
// 6 bits
packetData[24] = 0;
packetData[25] = 0;
packetData[26] = 0;
packetData[27] = 0;

packetData[28] = 0;
packetData[29] = 0;

// these bits are never sent but we do the checksum
// computation in 4-bit chunks, with the trailing two
// bits set to zero, so we set them to zero here to make
// the checksum a bit easier to compute
packetData[30] = 0;
packetData[31] = 0;

int i;

int checksum[10];
checksum[0] = 0;
checksum[1] = 0;
checksum[2] = 0;
checksum[3] = 0;

// compute checksum -- bitwise XOR of 4-bit chunks
// with two zeros padding the *end* of the last two bits
for (i=0; i 0)
{
if (Serial.available() == true)
{
Serial.println("HOLD ABORTED");
break;
}

packetDelay = sendPacket(yawIn, pitchIn, throttleIn, trimCmd);
delayTime = delayTime - packetDelay;

delay(packetDelay);

delay(delayAmount);
delayTime = delayTime - delayAmount;
}
Serial.println("Done holding.");
}

void Land()
{
static int i;
Serial.println("Landing");
for(i=throttleCmd;i>0;i--){
HoldCommand(8,8,throttleCmd,50);
}
throttleCmd = 0;
}

/*
* Function that manages receiving data from the serial port.
* Mostly changes the global variables that are passed to the
* control functions.
*/
void serialEvent()
{
char cmd = Serial.read();
Serial.println();
Serial.print("command received is ");
Serial.println(cmd);

switch (cmd)
{
// Take off with 't'
case 't':
Serial.println("Taking Off");

// Yaw: 1-15
// 8 = no turn
// 1 = max right turn
// 15 = max left turn
//
// Pitch: 1-15
// 8 = no pitch
// 15 = max forward
// 1 = max backwards
//
// Throttle: 0-255
// 0 = off
// ~130 = steady flight
// ~240 = fast climb

// First, go up with lots of throttle for 650ms
// yaw: 8 --> no yaw
// pitch: 8 --> no pitch
// throttle: 240 --> fast climb
// delay: 650ms --> enough time to climb, not too long so won't hit ceiling

// HoldCommand: a function that sends the same data for a given amount of time
// HoldCommand(yaw, pitch, throttle, time-to-hold-in-ms);
HoldCommand(8, 8, 240, 650);

// set the *global* throttle to steady flight throttle
throttleCmd = 130;
break;

// land with 'x' or 'q'
case 'x':
case 'q':
Land();
break;

// throttle commands
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
throttleCmd = atoi(&cmd) * 25; //single character, so we can go from 0 to 255 by inputting 0 to 9 in the serial monitor
break;

// turn left
case 'a':
if (yawCmd < 15) { yawCmd ++; } Serial.print("Yaw is "); Serial.println(yawCmd); break; // turn right case 'd': if (yawCmd > 1)
{
yawCmd --;
}
Serial.print("Yaw is ");
Serial.println(yawCmd);
break;

// move forwards
case 'w':
if (pitchCmd < 15){ pitchCmd ++; // moves forward } Serial.print("Pitch is "); Serial.println(pitchCmd); break; // move backwards case 's': if (pitchCmd > 1)
{
pitchCmd --; // moves backward
}
Serial.print("Pitch is ");
Serial.println(pitchCmd);
break;

// increase throttle
case 'u':
if (throttleCmd < 255 - 6) { throttleCmd += 6; } Serial.print("Throttle is "); Serial.println(throttleCmd); break; // decrease throttle case 'j': if (throttleCmd > 6)
{
throttleCmd -= 6;
}
Serial.print("Trottle is ");
Serial.println(throttleCmd);
break;

// change channel
case 'c':
Serial.println("Changing channel");
if (channel >= 2)
{
channel = 0;
} else
{
channel ++;
}
Serial.print("Channel is: ");
Serial.println(channel);
break;

// reset yaw and pitch
case 'r':
Serial.println("resetting yaw and pitch");
yawCmd = 8;
pitchCmd = 8;
break;

default:
Serial.println("Unknown command");
}
Serial.print("Throttle is at ");
Serial.println(throttleCmd);
}

/*
* Loops continuously sending and delaying for the transmission
*/
void loop()

{
// Note that serialEvent() gets called on each path of the loop
// and runs if there is data at the serial port

// we call delay here on the return value of sendPacket because that will
// cause us to put the right amount of time between packets. The delay is
// not constant, but is instead based on how long the packet was
// that we sent
delay(sendPacket(yawCmd, pitchCmd, throttleCmd, trimCmd));
}


To use the code, open the Serial Monitor (Tools > Serial Monitor) and use the following commands:

0-9: throttle
w: forward
a: left
s: backwards
d: right
t: take off
u: increase throttle
j: decrease throttle
r: reset pitch and yaw

For more details on this project, please go to:
http://www.abarry.org/likelytobeforgotten/?p=55

What's next?


Now that we can successfully send programmatical commands from Arduino, we can take advantage of the solutions we have learned here  in DIY Phone Gadgets to control the IR helicopter using PCs, game consoles, Kinect, tablets, smartphones or whatever electronic gadgets.

Here is a project using Kinect:


Here is a project using brain (your mind) to control a helicopter:


Or a Nunchuk-Wiimote-controlled helicopter, if you need more accuracy:



How to decode IR signal (very useful if you don't have a SYMA S107):

Of course, you can always decode the IR signal from scratch, if your helicopter is not SYMA S107. Here is a great video tutorial:


Here is the project owner's original blog: http://technologyonmymind.blogspot.fr/2012/03/helicopter-auto-pilot-introduction.html

DIY Phone-controlled helicopters with Arduino:


Needless to say, using Arduino as a bridge, we can easily control helicopters. Here is "Yan's helicopter Controller" from DIY Phone Gadgets.

Here is how it works:

1. The Android phone is controlling the Arduino using bluetooth.
2. Arduino is controlling the original helicopter transmitter.
3. The transmitter is programmatically controlling the helicopter.











DIY Phone-controlled helicopters with audio dongle:


There are already some fantastic existing tools in the toy market that are really helpful. Like these audio jack dongles:




These dongles capture the audio signal in the 3.5 audio jack and translate the audio signal into wireless signal.

For normal users, these dongles are just wireless transmitters, compatible with Android or iPhone apps. They can use it to control the helicopter with their smartphones.

For DIYers, the dongles should be capable of transmitting IR or other wireless signals from any smartphone or tablet (Android, iPhone, Blackberry or Windows Phone). You simply produce programmatically some audio sound from the phone. To make a touchscreen controller is so boring because the physical joysticks are way better and more precise. A phone should be a mobile command station to interact with the helicopter with a lot more intelligence. The helicopter should be able to dance with your own written program!

The easiest way to get these dongles is searching "iPhone Android Helicopter" on eBay. Then write your iOS or Android code to produce some audio signal, control your TV, fly your helicopter! Don't forget to share your exciting discoveries with the DIY Phone Gadgets community.

Imagine that you are playing your favorite music while watching a bunch of helicopters dancing in the sky, following the melody and rhythm. Yes, that's so geeky. But you are so happy. What's more beautiful than a creative mind? 
Share on Google Plus

0 comments: