CyberTruck
We built a remotely controlled car which the user was able to control remotely using wireless transmission from P5.js. The user controls the car using arrows keys through p5js and spacebar for braking.
Arduino Components
- Arduino Uno (we are going to move it to the MEGA board)
- Infrared Range Sensor
- 4 Wheels
- 4 Motors
- 2 SparkFun Motor Drivers – Dual TB6612FNG
- 9V DC Battery
- Battery Holder
- XBee Explorer USB 06
- SparkFun – XBee Shield for Arduino 04
- XBee 1mW Trace Antenna – Series 1 01
- XBee 1mW Trace Antenna – Series 1 07
- 1 On/Off Switch
Implementation
We started by taping the motors using double sided tape and sticking it to the acrylic sheet. Then we connected the wheels to the motor and wired it to the motor driver. We manage to fit all the pins on the Arduino Uno and after couple hours of figuring out the wiring we got both the motor driver on the breadboard. Then came the challenging part of getting the pins defined correctly in the code and using the example code to build up on our car driving code.
We made the car so that it can move in all directions, using two motor drivers and four motors (4 Wheel Drive). This allows the user to control the car’s movement direction using arrow keys on P5.js through Serial Communication with the Arduino connected to the car. In this way, we have the manual driving mode fully working now.
Serial Communication
The p5.js is serially transmitting a direction flag (integer) that is indicating the driving direction and the Arduino is reading this flag and using switch statements to manipulate the motors in order to make the car move in a certain direction. These switch statements control the motors’ speeds and rotation directions.
We decided to manually control the movement of the car – without an IR remote controller – because this is the most functionally important part of this project. We spent the most of our time in the beginning trying to understanding how the motors work and what possibilities there are coding features for motors in Arduino.
The P5js sketch has an instructions page with the driving instructions and port connection instructions. After connecting with USB port the user can start driving the car using the arrow keys allowing the car to move around and detect and respond to obstacles.
Object Detection
For now, we have implemented object detection in only the forward direction using the IR Range Sensor. Any obstacle detected approximately 40 cm in front of the car causes the car to come to a halt. At this point, the user can only move the car in the backward direction to avoid the obstacle altogether. We spent most of our time working on this functionality in this project – more on this in challenges.
Wireless Transmission
We then moved on to adding wireless transmission using the XBee shield and chip we checked out as per the Professor’s suggestion. One of the XBee chips was connected with P5Js and sent out data from P5. The other chip was connected with the Arduino on the car and received the signals sent out from P5. This transmission also went both ways. Using this scheme, we were able to make our car function fully remotely.
Challenges
Initially, we were using an Ultrasonic Sensor that comes with the Sparkfun Arduino Kit for this purpose. However, there was a long delay time associated with its working and the system was not very response – especially after integrating the XBee chip for wireless transmission later on. This is why we used the more responsive IR Range Sensor and polished the obstacle detection functionality to the point that it works as originally intended.
We also struggled a lot with XCTU and the firmware issue which we couldn’t resolve and thus couldn’t get the XBEE to be configured with each other and setup their PAN IDs. This why despite the handshake functions our wireless communication would break from time to time and the packets would get lost instead of being sent to the car.
Here is the XBees and XCTU Setup Guide that we followed to get the wireless communication established.
Here is the p5js sketch we made:
let dir = 4; let connect = true; function preload(){ title = loadImage("title.png"); mov = loadImage("movement.png"); brk = loadImage("break.png"); play = loadImage("play.png"); arrow = loadImage("arroww.png"); choose = loadImage("choose.png"); } function setup() { createCanvas(640, 480); textSize(18); } function draw() { background(0); fill(255); textStyle(BOLD); imageMode(CENTER); image(title, width/2, height/8, 300, 50); image(mov, width/4, height* 3/8, 160, 25); image(arrow, width* 3/4, height * 3/8, 100, 65); image(brk, width/5, height * 4.5/8, 90, 24); fill(0); stroke(255); strokeWeight(4); rectMode(CENTER); rect(width* 3/4, height * 4.5/8, 140, 30, 5); if (connect) { if (!serialActive) { image(choose, width/2, height * 6.5/8, 400, 20); fill(0); rect(width/3 + 10, height*6.5/8, 35, 35, 9); fill(255); strokeWeight(0); textSize(28); text("A", width/3 +0.5, height*6.5/8 + 9); } else { text("Connected", 20, 50); } } else { rect(width/2, height * 6.5/8, 100, 40, 5); image(play, width/2, height * 6.5/8, 80, 23); } } function keyPressed() { if (key == "a") { // important to have in order to start the serial connection!! setUpSerial(); } else if (keyCode == UP_ARROW) { dir = 0; } else if (keyCode == LEFT_ARROW) { dir = 1; } else if (keyCode == RIGHT_ARROW) { dir = 2; } else if (keyCode == DOWN_ARROW) { dir = 3; } else if (key == " ") { dir = 4; } } function readSerial(data) { //////////////////////////////////// //READ FROM ARDUINO HERE //////////////////////////////////// if (data != null) { if (data != "0") { let sendToArduino = dir + "\n"; console.log(sendToArduino) writeSerial(sendToArduino); } else { dir = 4 let sendToArduino = dir + "\n"; console.log(sendToArduino) writeSerial(sendToArduino); } } //console.log(data); }
Here is the Arduino code:
#include <SoftwareSerial.h> SoftwareSerial XBee(2, 3); // RX, TX //Motor controls const int ain1Pin = A3; //24 3 45 const int ain2Pin = 4; //26 4 const int pwmAPin = 5; //28 5 2 const int ain1Pin_2 = 13; //37 13 const int ain2Pin_2 = 12; //36 12 const int pwmAPin_2 = 11; //35 11 5 const int bin1Pin = 8; //32 8 const int bin2Pin = 7; //31 7 const int pwmBPin = 6; //30 6 3 const int bin1Pin_2 = 9; //33 9 const int bin2Pin_2 = A2; //22 2 46 const int pwmBPin_2 = 10; //34 10 4 int turbo = 250; int med = 170; int slow = 140; int spd = 0; int dir = 0; //0 - forward, 1 - right, 2 - left, 3 - reverse, 4 - stop bool reverse = false; //ultrasonic sensor pins //const int ECHO_PIN = A0; //A8 A0 //const int TRIG_PIN = A1; //A9 A1 const int IR = A1; //the following 2 variables will be used to detect distance with ultrasonic sensor double time; double distance; bool stop = false; unsigned long timer = 0; void setup() { //setting all motor controls to output pinMode(ain1Pin, OUTPUT); pinMode(ain2Pin, OUTPUT); pinMode(pwmAPin, OUTPUT); pinMode(ain1Pin_2, OUTPUT); pinMode(ain2Pin_2, OUTPUT); pinMode(pwmAPin_2, OUTPUT); pinMode(bin1Pin, OUTPUT); pinMode(bin2Pin, OUTPUT); pinMode(pwmBPin, OUTPUT); pinMode(bin1Pin_2, OUTPUT); pinMode(bin2Pin_2, OUTPUT); pinMode(pwmBPin_2, OUTPUT); XBee.begin(9600); Serial.begin(9600); // //start the handshake while (XBee.available() <= 0) { XBee.println(); // send a starting message delay(300); Serial.println();// wait 1/3 second } } void loop() { Serial.print("xbee available: "); Serial.println(XBee.available()); distance = analogRead(IR); delay(2); dir = XBee.parseInt(); spd = 140; if (distance <= 600 && distance >= 480) { //stop the car if within range if (dir != 3) { XBee.write("0"); } } if (XBee.read() == '\n') { //&& !stop ?????? Serial.print("direction: "); Serial.println(dir); switch (dir) { case 0: reverse = false; analogWrite(pwmAPin, spd); analogWrite(pwmAPin_2, spd); analogWrite(pwmBPin, spd); analogWrite(pwmBPin_2, spd); break; case 1: analogWrite(pwmAPin, 0); analogWrite(pwmAPin_2, spd); analogWrite(pwmBPin, 0); //set this to 0 to have both wheels off analogWrite(pwmBPin_2, spd); break; case 2: analogWrite(pwmAPin, spd); //set this to 0 to have both wheels off analogWrite(pwmAPin_2, 0); analogWrite(pwmBPin, spd); analogWrite(pwmBPin_2, 0); break; case 3: reverse = true; analogWrite(pwmAPin, spd); analogWrite(pwmAPin_2, spd); analogWrite(pwmBPin, spd); analogWrite(pwmBPin_2, spd); break; case 4: //stop car analogWrite(pwmAPin, 0); analogWrite(pwmAPin_2, 0); analogWrite(pwmBPin, 0); analogWrite(pwmBPin_2, 0); break; } // } XBee.println(); } if (!reverse) { digitalWrite(ain1Pin, HIGH); digitalWrite(ain1Pin_2, HIGH); digitalWrite(ain2Pin, LOW); digitalWrite(ain2Pin_2, LOW); digitalWrite(bin1Pin, LOW); digitalWrite(bin1Pin_2, LOW); digitalWrite(bin2Pin, HIGH); digitalWrite(bin2Pin_2, HIGH); } else { digitalWrite(ain1Pin, LOW); digitalWrite(ain1Pin_2, LOW); digitalWrite(ain2Pin, HIGH); digitalWrite(ain2Pin_2, HIGH); digitalWrite(bin1Pin, HIGH); digitalWrite(bin1Pin_2, HIGH); digitalWrite(bin2Pin, LOW); digitalWrite(bin2Pin_2, LOW); } }
P5.Js Sketch
Final Demo Video