Omni-Directional Robot with Live-Streaming Camera

The Live-Streaming Omni-Directional Robot is a three wheeled robot controlled by a PS2 controller. Due to its unique design, it is capable of moving in any direction and rotating both directions. It has a live streaming camera that uses Raspberry Pi and a WiFi adapter to send video to a computer.

Engineer

Lily C.

Area of Interest

Mechanical Engineering

School

Homestead High School

Grade

Incoming Freshman

Reflection

Building this robot was a great experience. I learned so much about Arduino, Raspberry Pi, servos, and more. Most importantly, it taught me to persevere through my problems — even with an approaching deadline. I developed these skills as tried to research and troubleshoot my issues. I realized that with Google and a lot of time, I can learn anything. After finishing my project, I presented it at BlueStamp. My presentation can be seen to the left.

Final Project

Completed Arduino Code

#include <PS2X_lib.h> //for v1.6, see billporter.info for updates
#include <Servo.h> //copy from servo code - a maximum of eight servo objects can be created
#define PS2_DAT 13 //data 7
#define PS2_CMD 11 //command 6
#define PS2_SEL 10 //attention 5
#define PS2_CLK 12 //clock 4

#define pressures false
#define rumble false

Servo myservoA; // works on motor A - upper left placement on robot
Servo myservoB; // works on motor B - upper right placement on robot
Servo myservoC; // works on motor C - middle back placement on robot
Servo myservoD; // works on motor D - camera stand

PS2X ps2x; // create PS2 Controller Class

int error = 0;
byte type = 0;
byte vibrate = 0;

int pos = 0; // variable to store the servo position
int posD = 0;

void setup(){
Serial.begin(57600);
myservoA.attach(A2); // attaches the servo on pin A2 to the servo object
myservoB.attach(A1); // attaches the servo on pin A1 to the servo object
myservoC.attach(A0); // attaches the servo on pin A0 to the servo object
myservoD.attach(A3); // attaches the servo on pin A3 to the servo object

pinMode(A0, OUTPUT);
pinMode(A1, OUTPUT);
pinMode(A2, OUTPUT);
pinMode(A3, OUTPUT);

error = ps2x.config_gamepad(PS2_CLK, PS2_CMD, PS2_SEL, PS2_DAT, false, false); //setup pins (in this order) and settings: GamePad(clock, command, attention, data, Pressures, Rumble)

if(error == 0){ //for PS2 connection troubleshooting
Serial.println("Found Controller, configured successful");
Serial.println("HOLDING L1 or R1 will print out the ANALOG STICK values.");
Serial.println("www.billporter.info for updates and to report bugs.");
}

else if(error == 1)
Serial.println("No controller found, check wiring, see readme.txt to enable debug. visit www.billporter.info for troubleshooting tips");
else if(error == 2)
Serial.println("Controller found but not accepting commands. see readme.txt to enable debug. Visit www.billporter.info for troubleshooting tips");
else if(error == 3)
Serial.println("Controller refusing to enter Pressures mode, may not support it. ");

type = ps2x.readType();
switch(type) {
case 0:
Serial.println("Unknown Controller type");
break;
case 1:
Serial.println("DualShock Controller Found");
break;
}
}

void loop(){
/* You must Read Gamepad to get new values
Read GamePad and set vibration values
ps2x.read_gamepad(small motor on/off, larger motor strength from 0-255)
if you don't enable the rumble, use ps2x.read_gamepad(); with no values
you should call this at least once a second
*/

if(error == 1){ //skip loop if no controller found
return;
} else { //DualShock Controller Found
ps2x.read_gamepad(false, vibrate); //false unless a command written for vibrate (if "true" large motor spins at 'vibrate' speed)
vibrate = ps2x.Analog(PSAB_BLUE); //this will set the large motor vibrate speed based on how hard you press the blue (X) button
}

int rx=ps2x.Analog(PSS_RX); // reads the value of RX on the the PS2 receiver (value between 0 and 255)
int maprx=map(rx,0,255,1000,2000); //reads rx, creates a mapval integer between 1000 and 2000 that motor controller reads
int ry=ps2x.Analog(PSS_RY); // reads the value of RY on the the PS2 receiver (value between 0 and 255)
//int mapry=map(ry,0,255,0,90); //reads ry, creates a mapval integer between 0 and 180 that servo reads

int x=ps2x.Analog(PSS_LX); // reads the value of LX on the the PS2 receiver (value between 0 and 255)
int y=ps2x.Analog(PSS_LY); // reads the value of LY on the the PS2 receiver (value between 0 and 255)

if(x==128 && y==127){
myservoA.writeMicroseconds(maprx); //stationary rotation
myservoB.writeMicroseconds(maprx);
myservoC.writeMicroseconds(maprx);
delay(10);
}

if(ry==0 && posD <= 70){
for (posD = 0; posD <= 70; posD += 1) {// goes from 0 degrees to 90 degrees in steps of 1 degree
myservoD.write(posD); // tell servo to go to position in variable 'posD'
delay(15); // waits 15ms for the servo to reach the position
}
}

if(ry==255 && posD >= 0){
for (posD = 70; posD >= 0; posD -= 1) {// goes from 180 degrees to 0 degrees in steps of 1 degree
myservoD.write(posD); // tell servo to go to position in variable 'posD'
delay(15); // waits 15ms for the servo to reach the position
}
}

int mapx=map(x,0,255,100,-100);
int mapy=map(y,0,255,100,-100); //values from PS2 controller are read from 0-255, read as 100 to -100 value to represent a cartesian plane with a 0 in the center to simplify calculations.

float theta= atan2(mapx,mapy); // arc tangent of x/y

int L=sqrt(mapx*mapx+mapy*mapy); //Pythagorean theorem

float cosa=L*cos(150*M_PI/180-theta); //150 degrees minus theta
float cosb=L*cos(30*M_PI/180-theta); //first value is L (hypotenuse)
float cosc=L*cos(270*M_PI/180-theta); //second value (parentheses section) represents adjacent side along x axis

int Fa=map(cosa,-142,142,1000,2000); //-142 and 142 are limits of calculations above (should be)
int Fb=map(cosb,-142,142,1000,2000);
int Fc=map(cosc,-142,142,1000,2000);

if(rx==128 && ry==127){ //F values indicate motor rotation, robot navigation
myservoA.writeMicroseconds(Fa);
myservoB.writeMicroseconds(Fb);
myservoC.writeMicroseconds(Fc);
delay(10);
}

//Next four sets of code allow you to read stick values
if(ps2x.Button(PSB_L2)){
Serial.print(Fa,DEC);
Serial.print(",");
Serial.print(Fb,DEC);
Serial.print(",");
Serial.println(Fc,DEC);
}

if(ps2x.Button(PSB_L1)){
Serial.print(ps2x.Analog(PSS_LX), DEC);
Serial.print(",");
Serial.println(ps2x.Analog(PSS_LY), DEC);
} //same values as R1

if(ps2x.Button(PSB_R1)){
Serial.print(ps2x.Analog(PSS_RX), DEC);
Serial.print(",");
Serial.println(ps2x.Analog(PSS_RY), DEC);
}

if(ps2x.Button(PSB_R2)){
Serial.print(x,DEC);
Serial.print(",");
Serial.println(y,DEC);
} //same values as L1

delay(50);

}

Completed Raspberry Pi Code

raspivid -o - -t 0 -hf -vf -w 800 -h 400 -fps 20 |cvlc -vvv stream:///dev/stdin --sout '#standard{access=http,mux=ts,dst=:8160}' :demux=h264

Wiring Schematics

Live streaming omni directional robot schematic

For my final project here at BlueStamp, I have created this omni directional robot with a live streaming camera. The robot motion is directed through a PS2 controller, while the camera is connected and viewed through my laptop. This robot is capable of moving and rotating in any direction. To build it, I connected a wireless PS2 receiver to an Arduino board. Using C++, I wrote code allowing the Arduino to read the X and Y values of these joysticks on the PS2 remote. It then uses this information to run the motors accordingly. After assembling the frame and attaching the wheels, I began to work on my first modification: the wireless camera. This camera is controlled through a raspberry pi connected to a Wi-Fi adapter module. I used python code to instruct my raspberry pi to begin recording and broadcasting the video to the Wi-Fi router. The code I used can be seen below. Then, I can open VLC media player on any device connected to the same Wi-Fi and open a network stream. This allows me to view the footage wirelessly. However, In order to get the signal across, it is necessary to forward a port on the Wi-Fi router. To do this you need administrative access, which was not available to me at BlueStamp. However, it works perfectly with my home Wi-Fi, as you can see in the video to the left. After completing this camera, I began working on my second and final modification: allowing the camera to pan up and down. To do this, I added a fourth micro servo and connected it to my Arduino board. It is directed by the right toggle on the PS2 controller. Overall it took one week to build the robot, three weeks to make the camera, and two days to add the fourth servo. I also spent about a week troubleshooting for issues with the PS2 controller.

First Modification

Python Code for Raspberry Pi

raspivid -o - -t 0 -hf -w 800 -h 400 -fps 10 |cvlc -vvv stream:///dev/stdin --sout '#standard{access=http,mux=ts,dst=:8160}' :demux=h264

My third milestone was getting my Raspberry Pi to wirelessly stream a live video to my laptop. I was able to do this by connecting a Fisheye camera and wifi adapter module to my Raspberry Pi. I faced many issues while doing this. For instance, I did not notice that my ethernet to USB adapter required a driver, so I was unable to connect my Raspberry Pi to my laptop. After I installed it, I was able to stream video to my laptop while the Raspberry Pi was plugged in, but I continued trying to make it wireless. I realized that the signal could not reach my Raspberry Pi because the wifi I was on was blocking its port. To solve this, I logged into the wifi admin panel and forwarded the port I was using. After changing the transport protocol to HTTP, I was able to start the stream on my Raspberry Pi with the line of code shown to the left. Then I was able to open VLC on my laptop and open a network stream. After specifying the IP address and port to send it to, I could wirelessly connect to the stream and view the footage with a 2 second lag. I then attached the Raspberry Pi and camera to my omni-directional robot. For my next modification, I will add another servo to my robot. This will allow me to change the orientation of the camera by making it pan up and down.

Second Milestone

My second milestone was building the frame for my robot and attaching the wheels. I did this by cutting two 8″ regular hexagons out of wood. Then I used bent steel bars to hold them together with space in between for my electronics. I super glued the motors to the bottom of the wood and attached the wheels. However, because the wheel shafts kept falling out, I bent three steel bars into a bracket to hold the wheel in. After taping all of my electronics into the carriage of the robot, I was able to drive it around in all directions with the PS2 controller. After finishing my base project, I will work on my modifications. I plan to add a camera that streams live footage to my laptop. I will do this through Raspberry Pi and a camera.

First Milestone

My Arduino Code

#include //for v1.6, see billporter.info for updates 
#include //copy from servo code - a maximum of eight servo objects can be created 
#define PS2_DAT 6 //data 
#define PS2_CMD 8 //command 
#define PS2_SEL 7 //attention 
#define PS2_CLK 9 //clock 
#define pressures false 
#define rumble false 
Servo myservoA; // works on motor A - upper left placement on robot 
Servo myservoB; // works on motor B - upper right placement on robot 
Servo myservoC; // works on motor C - middle back placement on robot 
PS2X ps2x; // create PS2 Controller Class 
int error = 0; 
byte type = 0; 
byte vibrate = 0; 
void setup(){ 
Serial.begin(57600); 
myservoA.attach(A2); // attaches the servo on pin A2 to the servo object 
myservoB.attach(A1); // attaches the servo on pin A1 to the servo object 
myservoC.attach(A0); // attaches the servo on pin A0 to the servo object 
pinMode(A0, OUTPUT); 
pinMode(A1, OUTPUT); 
pinMode(A2, OUTPUT); 
error = ps2x.config_gamepad(9,8,7,6, false, false); //setup pins (in this order) and settings: GamePad(clock, command, attention, data, Pressures, Rumble) 
if(error == 0){ //for PS2 connection troubleshooting 
Serial.println("Found Controller, configured successful"); 
Serial.println("HOLDING L1 or R1 will print out the ANALOG STICK values."); 
Serial.println("www.billporter.info for updates and to report bugs."); 
} 
else if(error == 1) 
Serial.println("No controller found, check wiring, see readme.txt to enable debug. visit www.billporter.info for troubleshooting tips"); 
else if(error == 2) 
Serial.println("Controller found but not accepting commands. see readme.txt to enable debug. Visit www.billporter.info for troubleshooting tips"); 
else if(error == 3) 
Serial.println("Controller refusing to enter Pressures mode, may not support it. "); 
type = ps2x.readType(); 
switch(type) { 
case 0: Serial.println("Unknown Controller type"); 
break; 
case 1: Serial.println("DualShock Controller Found"); 
break; 
} 
} 
void loop(){
if(error == 1){ //skip loop if no controller found 
return; 
} else { //DualShock Controller Found 
ps2x.read_gamepad(false, vibrate); //false unless a command written for vibrate (if "true" large motor spins at 'vibrate' speed) 
vibrate = ps2x.Analog(PSAB_BLUE); //this will set the large motor vibrate speed based on how hard you press the blue (X) button 
} 
int val=ps2x.Analog(PSS_RX); // reads the value of RX on the the PS2 receiver (value between 0 and 255) 
int mapval=map(val,0,255,1000,2000); //reads val, creates a mapval integer between 1000 and 2000 that motor controller reads 
int x=ps2x.Analog(PSS_LX); // reads the value of LX on the the PS2 receiver (value between 0 and 255) 
int y=ps2x.Analog(PSS_LY); // reads the value of LY on the the PS2 receiver (value between 0 and 255) 
if(x==128 && y==127){
myservoA.writeMicroseconds(mapval); //stationary rotation 
myservoB.writeMicroseconds(mapval); 
myservoC.writeMicroseconds(mapval); 
delay(10); 
} 
int mapx=map(x,0,255,100,-100); 
int mapy=map(y,0,255,100,-100); //values from PS2 controller are read from 0-255, read as 100 to -100 value to represent a cartesian plane with a 0 in the center to simplify calculations. 
float theta= atan2(mapx,mapy); // arc tangent of x/y 
int L=sqrt(mapx*mapx+mapy*mapy); //Pythagorean theorem 
float cosa=L*cos(150*M_PI/180-theta); //150 degrees minus theta 
float cosb=L*cos(30*M_PI/180-theta); //first value is L (hypotenuse) 
float cosc=L*cos(270*M_PI/180-theta); //second value (parentheses section) represents adjacent side along x axis 
int Fa=map(cosa,-142,142,1000,2000); //-142 and 142 are limits of calculations above (should be) 
int Fb=map(cosb,-142,142,1000,2000); 
int Fc=map(cosc,-142,142,1000,2000); 
if(val==128){ //F values indicate motor rotation, robot navigation 
myservoA.writeMicroseconds(Fa); 
myservoB.writeMicroseconds(Fb); 
myservoC.writeMicroseconds(Fc); 
delay(10); 
} 
//Next four sets of code allow you to read stick values 
if(ps2x.Button(PSB_L1)){ 
Serial.print(Fa,DEC);
 Serial.print(","); 
Serial.print(Fb,DEC); 
Serial.print(","); 
Serial.println(Fc,DEC); 
} 
if(ps2x.Button(PSB_L2)){
Serial.print(ps2x.Analog(PSS_LX), DEC); 
Serial.print(","); 
Serial.println(ps2x.Analog(PSS_LY), DEC); 
} //same values as R2 
if(ps2x.Button(PSB_R1)){ 
Serial.print(ps2x.Analog(PSS_RX), DEC); 
Serial.print(","); 
Serial.println(ps2x.Analog(PSS_RY), DEC); 
} 
if(ps2x.Button(PSB_R2)){
Serial.print(x,DEC); 
Serial.print(","); 
Serial.println(y,DEC); 
} //same values as L2 
delay(50); 
}
My first milestone was writing my Arduino code and connecting it to my motors and PS2 controller. I was able to find the code used by the previous BlueStamp student to make this project and was able to use it as a template. After downloading the PS2 library required to run it, I was able to modify and upload my code to my Arduino board. One challenge I faced was importing the PS2 library to my code. After the code was uploaded to my Arduino board, I used a breadboard to connect it to the motor controllers, PS2 receiver, and battery. You can see my completed code to the left. Once the code was working, I tried connecting my PS2 controller to my Arduino board. It should have been connected, but it was not working. First, I checked the wiring and found that I had wired the PS2 receiver backwards. After fixing this, the controller was still not working. I found that my code did not define which wires from the PS2 controller were plugged into the pins. I was able to define the pins in the code after studying what each pin in a wireless PS2 receiver was responsible for. After these modifications, when the controller was moved the motors engaged. However, one of the motors was not turning on. I discovered that it was plugged into the wrong pin and fixed it. Next, I will build the frame and attach the wheels.

Starter Project

My Starter Project was the Useless Machine. When the switch on the box is flipped, an arm comes out to flick it back off. This is because it completes a circuit that allows power to travel from three AAA batteries to the motor. When the motor is activated, it raises the arm until it reaches the original switch. As the arm moves, it activates another switch that allows current to flow through two resistors before activating a LED. When the switch is finally flicked off by the moving arm, the motor is reversed and the arm is brought back in until it activates the second switch again, stopping the motor and turning off the LED. To assemble this project, I had to solder the two switches, LED, two resistors, and four wires to a circuit board. Then I attached the arm to the motor and assembled the box around the motor, circuit board, and battery carriage. While completing this project, I learned about different types of switches and what their uses are. For example, the main switch has six terminals to allow it to control three circuits. This project taught me a lot about circuits and assembling parts.
Comments
  • Kriselle T.
    Reply

    So cool! Nice to see all your hard work come together so well. Keep up the great work, Lily!

Leave a Comment

Start typing and press Enter to search