product packet v2
DESCRIPTION
ISM Product PacketTRANSCRIPT
E L E C T R I C A L E N G I N E E R I N G
2 0 1 1 ~ 2 0 1 2
THE
QUADCOPTER
R O G E R TA N G
D I C K S WA N
PRODUCT COMPLETION PRODUCT COMPLETION PRODUCT COMPLETION PRODUCT COMPLETION
SUMMARYSUMMARYSUMMARYSUMMARY
The overall objective is to build a small robotic quadcopter (i.e. a helicopter with four separate
blades/motors). The quadcopter has an embedded controller programmed using available open source
control software. The project will add functionality by building a custom designed remote control
communicating over standard Wi-Fi with new innovative user interface capabilities. The original plan for
the quadcopter was to have it flying autonomously. This means that the drone would be able navigate
around a room without human interaction. The flight would be stable and a simple command would
have made the quadcopter move to a position precisely.
The final product didn’t end up anywhere near what we planned. There were delays in shipping,
inaccuracies with hardware, and problems with code. As a result, there were constant reassessments on
what we could get done.
During the shipping time, I worked with the ArduIMU (hardware that contains a gyroscope, an
accelerometer, and a magnetometer). We planned to use the three sensors on the board to aid the
control process. For instance, we can theoretically control how far it flies, how many degrees it will turn,
and how fast it will go. Unfortunately, what can be done on paper does not translate perfectly to reality.
We found out that the method that calculates the values that I stated previously gets less and less
accurate as time approaches infinity. If one plotted a percent error versus time graph, the function on
the graph would be exponential. As a result, many of the autonomous functions had to be thrown out
the window.
The compass also posed some very interesting difficulties. The direction (after converting into
data that humans can understand) was never constant. Sometimes, rarely, the compass would be
accurate. Most of the time, the degrees between north and south was 135 (should be 180). Mr. Swan
tried to modify the code to get it to work. It posed to be too time consuming with little output. The
compass is apparently not used among the hobbyist community. Why? The standard quadcopter is
meant for outdoor flying. Therefore, GPS is a better method to figure out where the quadcopter should
go. For us, we planned to fly the robot indoors. The GPS solution therefore is not feasible. If the
quadcopter is off by a hundred feet outdoors, the error is not very noticeable. In an indoor environment,
just a few feet could spell disaster. And there goes another part of our original plan.
So now, we had to totally revisit the drawing board and figure out what we could get done.
Note, this occurred two weeks prior to when the final project was actually due. We used a lot of time to
figure out that some aspects of the project would not work. What we decided that could get finished
was the remote control. While the compass navigation was out of the question, the standard control
worked perfectly. We first used the Boe-Bot to get the initial structure of the program. The first version I
wrote from scratch with no outside help worked a little. It was a brute force way with only maximums
and minimums. When I showed this to Mr. Swan, we conducted a huge makeover. We wanted to have
speeds for the max and min and everything else in between. After all, you don’t want to fly an aircraft
with either max throttle or none at all. It doesn’t work well.
After creating a massive program (for me at least), we found out it didn’t work as intended. We
later found out that the motors range for variable speed is extremely small. So we put in a quick
algorithm to translate the range to a smaller scale. The variable speed worked! With that finished, I put
it on the robot to test it out. To the amusement of my class, the remote control worked great. There
only needed to be a slight modification to allow the quadcopter to be controlled by our own remote.
Meanwhile, testing the quadcopter itself proved to be tedious. No matter how many
modifications we placed in the quadcopter, it would not achieve a stable flight. The broken propellers
are a testament to that. We only connected the quadcopter to our remote without the propellers on. If
flying with a professional control proved to be difficult, our own remote control would only fare worse.
It was only after the due date for the project did we get advice on how to stabilize level flight. We have
tested it though.
The end product then was a pile of incomplete work (in my opinion). I feel we did not get to
discover all the potential that the quadcopter held. We had the parts to make a whole but we lacked the
nuts and bolts. I learned that engineering is not one for accurate deadlines. Problems arise and it takes
time to smooth things out. What looks good on paper does not necessarily mean that it will work
flawlessly. In this line of work, debugging is a commonplace. There is faulty programming, unknown
variables, and other mishaps that prevent a flawless product, especially when starting from scratch.
However, for all the bumps along the way, I was a good experience. I enjoyed making breakthroughs and
learning how to code. I hope to take these experiences to college where I plan to engage in research.
REPRESENTATIONREPRESENTATIONREPRESENTATIONREPRESENTATION
Here is the best I can do with representing my product. This is the property of my mentor so this is
probably the best shot of seeing it.
DRAFTS, PICTURES, AND DRAFTS, PICTURES, AND DRAFTS, PICTURES, AND DRAFTS, PICTURES, AND
RELATED ITEMSRELATED ITEMSRELATED ITEMSRELATED ITEMS
LineFollowing Code #include <Servo.h>
#include <PololuQTRSensors.h>
PololuQTRSensorsAnalog qtra((unsigned char[]) {1, 2, 3, 4}, 4);
int speed = 100, rotate = 8;
int threshhold = 100;
int left = speed, right = speed;
Servo leftServo;
Servo rightServo;
void setup()
{
leftServo.attach(11);
rightServo.attach(10);
Serial.begin(9600);
}
void loop()
{
unsigned int sensors[4];
qtra.read(sensors);
left = speed;
right = speed;
if (sensors[1] > 100 && sensors[2] < 100 && sensors[3] < 100)
{
left = speed + rotate + 5;
right = speed - rotate - 5;
}
else if (sensors[2] < 100 && sensors[3] > 100 && sensors[4] > 100)
{
left = speed - rotate - 5;
right = speed + rotate + 5;
}
else if (sensors[1] > 100 && sensors[3] < 100)
{
left = speed + rotate;
right = speed - rotate;
}
else if (sensors[2] < 100 && sensors[4] > 100)
{
left = speed - rotate;
right = speed + rotate;
}
else
{
left = speed;
right = speed;
}
leftServo.write(left);
rightServo.write(180 - right);
delay(5);
}
IRSensorReadings #include <PololuQTRSensors.h>
#define NUM_SENSORS 5
// number of sensors used
#define NUM_SAMPLES_PER_SENSOR 4
// average 4 analog samples per sensor reading
//sensors 0 through 5 are connected to analog inputs 0 through 5
PololuQTRSensorsAnalog qtra((unsigned char[]) {0, 1, 2, 3, 4},
NUM_SENSORS, NUM_SAMPLES_PER_SENSOR, QTR_NO_EMITTER_PIN);
unsigned int sensorValues[NUM_SENSORS];
void setup(){
Serial.begin(9600);
}
void loop()
{
qtra.read(sensorValues);
unsigned char i;
for (i = 0; i < NUM_SENSORS; i++)
{
Serial.print(sensorValues[i]);
Serial.print(' ');
}
Serial.println(" ");
delay(250);
}
SOSBlinking void setup() {
pinMode(13, OUTPUT);
}
void loop() {
for(int i = 0; i < 3; i++) {
digitalWrite(13,HIGH);
delay(500);
digitalWrite(13,LOW);
delay(500);
}
for(int i = 0; i < 3; i++) {
digitalWrite(13, HIGH);
delay(1500);
digitalWrite(13, LOW);
delay(500);
}
for(int i = 0; i < 3; i++) {
digitalWrite(13,HIGH);
delay(500);
digitalWrite(13,LOW);
delay(500);
}
}
ServoSpeedControl #include <Servo.h>
int speed = 0;
//speed = 0, to make right servo go full speed forward
//speed = 180, to make left servo go full speed forward
//speed = 90, to make both servos stop (need to trim to get perfect stop)
Servo myservo;
void setup()
{
myservo.attach(10);
}
void loop()
{
speed++; // increment speed by one each loop
if (speed > 180) { //set limit to be 180
speed = 0; // return to 0 if limit reached
}
myservo.write(speed);
delay(25);
}
TwinkleTwinkleSongPlayback #include "pitches.h"
// notes in the melody:
int melody[] = {
NOTE_C5,NOTE_C5, NOTE_G5,NOTE_G5, NOTE_A5,NOTE_A5, NOTE_G5,
NOTE_F5,NOTE_F5, NOTE_E5,NOTE_E5, NOTE_D5,NOTE_D5, NOTE_C5,
NOTE_G5,NOTE_G5, NOTE_F5,NOTE_F5, NOTE_E5,NOTE_E5, NOTE_D5,
NOTE_G5,NOTE_G5, NOTE_F5,NOTE_F5, NOTE_E5,NOTE_E5, NOTE_D5,
NOTE_C5,NOTE_C5, NOTE_G5,NOTE_G5, NOTE_A5,NOTE_A5, NOTE_G5,
NOTE_F5,NOTE_F5, NOTE_E5,NOTE_E5, NOTE_D5,NOTE_D5, NOTE_C5,};
// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
4,4,4,4,4,4,2,4,4,4,4,4,4,2,
4,4,4,4,4,4,2,4,4,4,4,4,4,2,
4,4,4,4,4,4,2,4,4,4,4,4,4,2};
void setup() {
// iterate over the notes of the melody:
for (int thisNote = 0; thisNote < 42; thisNote++) {
// to calculate the note duration, take one second
// divided by the note type.
//e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
int noteDuration = 1000/noteDurations[thisNote];
tone(8, melody[thisNote],noteDuration);
// to distinguish the notes, set a minimum time between them.
// the note's duration + 30% seems to work well:
int pauseBetweenNotes = noteDuration * 1.30;
delay(pauseBetweenNotes);
// stop the tone playing:
noTone(8);
}
}
void loop() {
// no need to repeat the melody.
}
Remote Control #include <Servo.h>
char inByte = 0;
int outputPin = 13;
int nMessageCount;
int nJoystickHorizontal = 512;
int nJoystickVertical = 512;
int nHorizontal = nJoystickHorizontal;
int nVertical = nJoystickVertical;
int leftSpeed;
int rightSpeed;
int leftSpeedRaw;
int rightSpeedRaw;
Servo leftServo;
Servo rightServo;
void setup(){
pinMode(outputPin, OUTPUT);
Serial1.begin(9600);
Serial.begin(115200);
leftServo.attach(11);
rightServo.attach(10);
}
void dumpResults()
{
Serial.print(nMessageCount, DEC);
Serial.print(" Raw H/V ");
Serial.print(nJoystickHorizontal, DEC);
Serial.print(" ");
Serial.print(nJoystickVertical, DEC);
Serial.print(" speed L/R ");
Serial.print(leftSpeed, DEC);
Serial.print(" ");
Serial.print(rightSpeed, DEC);
Serial.print(" speed L/R Raw ");
Serial.print(leftSpeedRaw, DEC);
Serial.print(" ");
Serial.print(rightSpeedRaw, DEC);
Serial.println();
}
void blinkLEDForEveryMessage()
{
typedef enum
{
nStateReadyToBlink,
nStateLEDOn,
nStateLEDOff,
} TBlinkStates;
static TBlinkStates nState;
static unsigned long nBlinkTimer;
static int nLastMessageBlinked;
if (nLastMessageBlinked == nMessageCount)
return;
switch (nState)
{
case nStateReadyToBlink:
if (nLastMessageBlinked == nMessageCount)
return;
nState = nStateLEDOn;
digitalWrite(outputPin, HIGH);
nBlinkTimer = millis() + 100;
break;
case nStateLEDOn:
if (millis() < nBlinkTimer)
return;
digitalWrite(outputPin, LOW);
nBlinkTimer = millis() + 400;
nState = nStateLEDOff;
break;
case nStateLEDOff:
if (millis() < nBlinkTimer)
return;
nLastMessageBlinked = nMessageCount;
nState = nStateReadyToBlink;
break;
}
}
void stateMachineLoop()
{
typedef enum
{
stateLookForMessageStart,
stateLookForD,
stateLookForR,
stateLookForCycleCount,
stateLookForJoystickHorizontal,
stateLookForJoystickVertical,
} TStates;
static TStates nState;
static int nTemp;
static bool bNegativeValue;
blinkLEDForEveryMessage();
if (Serial1.available() <= 0)
return;
inByte = Serial1.read();
/*Debug
Serial.print("'");
Serial.write(inByte);
Serial.print("' ");0
Serial.print((int) inByte, DEC);
Serial.print(" ");
Serial.print(nState, DEC);
Serial.println();
*/
switch (nState)
{
default:
case stateLookForMessageStart:
if (inByte == 'H')
nState = stateLookForD;
else
nState = stateLookForMessageStart;
break;
case stateLookForD:
if (inByte == 'D')
nState = stateLookForR;
else
nState = stateLookForMessageStart;
break;
case stateLookForR:
if (inByte == 'R')
{
nTemp = 0;
bNegativeValue = false;
nState = stateLookForCycleCount;
}
else
nState = stateLookForMessageStart;
break;
case stateLookForCycleCount:
if (inByte == '-')
{
bNegativeValue = true;
break;
}
if ((inByte >= '0') && (inByte <= '9'))
{
nTemp *= 10;
nTemp += (inByte - '0');
break;
}
if ((inByte == ',') || (inByte == '\r') || (inByte == '\n'))
{
if (bNegativeValue)
nMessageCount = - nTemp;
else
nMessageCount = nTemp;
nTemp = 0;
bNegativeValue = false;
nState = stateLookForJoystickHorizontal;
}
else
nState = stateLookForMessageStart;
break;
case stateLookForJoystickHorizontal:
if (inByte == '-')
{
bNegativeValue = true;
break;
}
if ((inByte >= '0') && (inByte <= '9'))
{
nTemp *= 10;
nTemp += (inByte - '0');
break;
}
if ((inByte == ',') || (inByte == '\r') || (inByte == '\n'))
{
if (bNegativeValue)
nJoystickHorizontal = - nTemp;
else
nJoystickHorizontal = nTemp;
nTemp = 0;
bNegativeValue = false;
nState = stateLookForJoystickVertical;
}
else
nState = stateLookForMessageStart;
break;
case stateLookForJoystickVertical:
if (inByte == '-')
{
bNegativeValue = true;
break;
}
if ((inByte >= '0') && (inByte <= '9'))
{
nTemp *= 10;
nTemp += (inByte - '0');
break;
}
if ((inByte == ',') || (inByte == '\r') || (inByte == '\n'))
{
if (bNegativeValue)
nJoystickVertical = - nTemp;
else
nJoystickVertical = nTemp;
nTemp = 0;
bNegativeValue = false;
nState = stateLookForMessageStart;
dumpResults();
}
else
nState = stateLookForMessageStart;
break;
}
}
#define kMaxValue 10
#define kMaxValueMinusOne (kMaxValue - 1)
int setMotorSpeed(int speed, bool bReversed)
{
//speed range is from -512 to 512
speed *= (float) kMaxValue/ (float) 512; //change range from -512..512 to -kMaxValue..kMaxValue
if ((speed >= -1) && (speed <= 1)) //deadzone is -1..1
speed = 0;
if (speed >= kMaxValueMinusOne)
speed = 45;
else if (speed <= -kMaxValueMinusOne)
speed = -45;
speed += 90; //servo uses 90 for stop
if(bReversed)
speed = 180 - speed; //left motor is reversed
return speed;
}
void motorControlLoop()
{
//normalize values
nHorizontal = nJoystickHorizontal;
nVertical = nJoystickVertical;
if (nHorizontal > 0)
{
rightSpeed = nVertical;
leftSpeed = rightSpeed * (1 - (nHorizontal / (float)256));
}
else
{
nHorizontal = -nHorizontal;
leftSpeed = nVertical;
rightSpeed = leftSpeed * (1 - (nHorizontal / (float)256));
}
leftSpeedRaw = setMotorSpeed(leftSpeed, false);
leftServo.write(leftSpeedRaw);
rightSpeedRaw = setMotorSpeed(rightSpeed, true);
rightServo.write(rightSpeedRaw);
return;
}
void compassHandlerLoop()
{
return;
}
void loop()
{
stateMachineLoop();
compassHandlerLoop();
motorControlLoop();
}
Remote Control Cont. int outputPin = 2;
char Str1[] = "HDR";
int counter;
int joystickVertical = 58;
int joystickHorizontal = 24589;
unsigned long nextMessageTime;
#define kTimeBetweenMessages 50
#define sensorVerticalPin A0
#define sensorHorizontalPin A1
void setup(){
pinMode(outputPin, OUTPUT);
Serial.begin(9600);
}
void loop(){
unsigned long currentTime;
currentTime = millis();
if(currentTime < nextMessageTime)
return;
nextMessageTime = currentTime + kTimeBetweenMessages;
++counter;
joystickVertical = analogRead(sensorVerticalPin) - 512;
joystickHorizontal = analogRead(sensorHorizontalPin) - 512;
Serial.write(Str1);
Serial.print(counter, DEC);
Serial.write(',');
Serial.print(joystickVertical, DEC);
Serial.write(',');
Serial.print(joystickHorizontal, DEC);
Serial.write('\r');
Serial.write('\n');
Serial.write('\r');
}