Video explanation and demonstration:
During one of my programming camps I showed a student how to send a letter character through the Arduino IDE serial monitor to control lights and then motors. This was based off of the "physical pixel" example in the Arduino IDE examples tab in "file"--> "examples" --> "communication" path. Student's liked this example however, one student wanted to control the robot with just the arrow keys; specifically by a key press and the one way I know how to do this is by establishing communication between Processing and the Arduino programming languages.
This is the Processing GUI I created to control my robot along with tabs for signaling the beginning of each autonomous function:
The Processing code for this is below. Lines of code 255-257 deal with button presses and how this corresponds with a letter being sent over the serial monitor to an Arduino connected to "COM7" on my machine. This action is indicated by line 34 of the code below:
import processing.serial.*; Serial myPort; // Create object from Serial class String val;//to hold value passed through keypress int value = 0; int value2= 0; int value3= 0; int value4=0; int value5=0; int value6=0; int value7=0; int value8=0; PFont font; void setup(){ size(500,550); background(207,0,0); font= loadFont("Consolas-17.vlw"); textFont(font,20); text("autopilot",100,50); text("wiskas",100,125); text("light",100,200); text("wizka-light",100,275); text("day-seeker",100,350); text("night-seeker",100,425); text("cliff hanger",100,500); text("ultimate",395,500); String portName = "COM7"; //change the 0 to a 1 or 2 etc. to match your port myPort = new Serial(this, portName, 9600); right(); up(); down(); left(); } void draw(){ fill(value); rect(25, 25, 50, 50); fill(value2); rect(25,100,50,50); fill(value3); rect(25,175,50,50); fill(value4); rect(25,250,50,50); fill(value5); rect(25,325,50,50); fill(value6); rect(25,400,50,50); fill(value7); rect(25,475,50,50); fill(value8); rect(325,475,50,50); if (value == 0 && value2==0 && value3==0 && value4 ==0 && value5==0 && value6==0 && value7==0 && value8 ==0){ right(); up(); down(); left(); } else if(value==255){ println("o"); myPort.write("o"); if(value2==255 || value3==255 || value4==255 || value5==225 || value6==255 || value7==255 || value8==255){ value=0; value2=0; value3=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value2==255){ println("c"); myPort.write("c"); if(value==255 || value3==255 || value4==255 || value5==255||value6==255 || value7==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5 =0; value6 =0; value8=0; } } else if(value3==255){ println("l"); myPort.write("l"); if(value==255 || value2==255 || value4==255 || value5==255 || value6==255|| value7==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value4==255){ println("e"); myPort.write("e"); if(value==255 || value2==255 ||value3==255 || value5==255 || value6==255|| value7==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value5==255){ println("h"); myPort.write("h"); if(value==255 || value2==255 ||value3==255 || value4==255|| value6==255|| value7==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value6==255){ println("n"); myPort.write("n"); if(value==255 || value2==255 ||value3==255 || value4==255 || value5==255 || value7==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value7==255){ println("j"); myPort.write("j"); if(value==255 || value2==255 ||value3==255 || value4==255 || value5==255 || value6==255|| value8==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } else if(value8==255){ println("u"); myPort.write("u"); if(value==255 || value2==255 ||value3==255 || value4==255 || value5==255 || value6==255|| value7==255){ value3=0; value2=0; value=0; value4=0; value5=0; value6=0; value7=0; value8=0; } } } void left(){ if (keyPressed && (key == CODED)) { if (keyCode==LEFT){ fill(127,0,205); rect(250,200,100,100); println("a"); myPort.write("a"); } } else{ fill(255,255,255); rect(250,200,100,100); println("v"); myPort.write("v"); } } void right(){ if (keyPressed && (key == CODED)) { if (keyCode==RIGHT){ fill(255,255,0); rect(350,200,100,100); println("d"); myPort.write("d"); } } else{ fill(255,255,255); rect(350,200,100,100); println("v"); myPort.write("v"); } } void up(){ if (keyPressed && (key == CODED)) { if (keyCode==UP){ fill(0,255,0); rect(300,100,100,100); println("w"); myPort.write("w"); } } else{ fill(255,255,255); rect(300,100,100,100); println("v"); myPort.write("v"); } } void down(){ if (keyPressed && (key == CODED)) { if (keyCode==DOWN){ fill(51,153,255); rect(300,300,100,100); println("s"); myPort.write("s"); } } else{ fill(255,255,255); rect(300,300,100,100); println("v"); myPort.write("v"); } } void AUTO(){ if (keyPressed && (key == CODED)) { if (keyCode==DOWN){ fill(0,0,255); rect(200,300,100,100); println("s"); myPort.write("s"); } } else{ fill(255,255,255); rect(200,300,100,100); println("v"); myPort.write("v"); } } void mouseClicked() { if ((mouseX>25 && mouseX<75) && (mouseY>25&& mouseY<75)){ if (value == 0) { value = 255; } else { value = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>100&& mouseY<150)){ if (value2 == 0) { value2 = 255; } else { value2 = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>175&& mouseY<225)){ if (value3 == 0) { value3 = 255; } else { value3 = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>250&& mouseY<300)){ if (value4 == 0) { value4 = 255; } else { value4 = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>325&& mouseY<375)){ if (value5 == 0) { value5 = 255; } else { value5 = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>400&& mouseY<450)){ if (value6 == 0) { value6 = 255; } else { value6 = 0; } } if ((mouseX>25 && mouseX<75) && (mouseY>475&& mouseY<525)){ if (value7 == 0) { value7 = 255; } else { value7 = 0; } } if ((mouseX>325 && mouseX<375) && (mouseY>475&& mouseY<525)){ if (value8 == 0) { value8 = 255; } else { value8 = 0; } } }
The picture below shows an Arduino connected to "COM7" (circled in yellow).
This Arduino is collecting the serial monitor letter characters being transmitted by an event signaled in Processing.For example, mouse clicks in particular "button region" in Processing corresponds with a value change in the Processing code line 386-455. This is then translated and sent by the "myPort.write(" ") in lines 78-241.
The letter character sent to Arduino (circled in yellow) must then be communicated remotely to the Blambo bot delux robot and to do this it has the following code uploaded onto it to make use of the nRF24LO1 wireless module. The code loaded onto the Arduino is here:
#include <SPI.h> #include "RF24.h" RF24 radio(9, 10); const uint64_t pipes[2] = { 0xF0F0F0F000LL, 0xF0F0F0F0FFLL}; void setup(){ Serial.begin(9600); radio.begin(); radio.setDataRate(RF24_250KBPS); radio.setChannel(100); radio.setRetries(15,15); radio.openWritingPipe(pipes[1]); radio.openReadingPipe(1, pipes[0]); radio.startListening(); } void loop(){ if(Serial.available()){ char data[32] = ""; byte i = 0; while(Serial.available()){ data[i] = Serial.read(); i++; delay(2); } data[i] = 0; radio.stopListening(); radio.write(&data, 32); radio.startListening(); } if(radio.available()){ char data[32] = ""; radio.read(&data, 32); Serial.println(data); } }
The letter character, transmitted by the Arduino attached by the computer is then recieved by another nRF24L0 which is programmed to be a receiver. This is loaded up onto an Arduino attached to the Parallax robot:
#include <SPI.h> #include "RF24.h"//libraries for the wireless function #include<Servo.h> ///these are notes for the london bridge song int c = 1047; int d = 1147; int e = 1319; int f = 1397; int g = 1568; int a = 1760; int b = 1976; int cc = 2093; int londonbridge[] = {g,a,g,f,e,f,g,d,e,f,e,f,g,g,a,g,f,e,f,g,d,g,e,c};//london bridge song array class Ultrasonic //defining ultrasonic sensor object { public: Ultrasonic(int pin); void DistanceMeasure(void); long microsecondsToCentimeters(void); long microsecondsToInches(void); private: int _pin;//pin number of Arduino that is connected with SIG pin of Ultrasonic Ranger. long duration;// the Pulse time received; }; Ultrasonic::Ultrasonic(int pin) { _pin = pin; } /*Begin the detection and get the pulse back signal*/ void Ultrasonic::DistanceMeasure(void) { pinMode(_pin, OUTPUT); digitalWrite(_pin, LOW); delayMicroseconds(2); digitalWrite(_pin, HIGH); delayMicroseconds(5); digitalWrite(_pin,LOW); pinMode(_pin,INPUT); duration = pulseIn(_pin,HIGH); } /*The measured distance from the range 0 to 400 Centimeters*/ long Ultrasonic::microsecondsToCentimeters(void) { return duration/29/2; } /*The measured distance from the range 0 to 157 Inches*/ long Ultrasonic::microsecondsToInches(void) { return duration/74/2; } Ultrasonic ultrasonic(6); RF24 radio(9, 10);//wireless reciever object Servo servoLeft; Servo servoRight; int incomingbyte; int ledstatus=2; bool check; const int collisionThresh = 20; //threshold for obstacles (in inch) obstacle avoidance int leftDistance; int rightDistance; int centerDistance;//distances on either side- for measuring distance and making decisions const uint64_t pipes[2] = { 0xF0F0F0F000LL, 0xF0F0F0F0FFLL};//communication pathway for wifi byte wLeftOld; // Previous loop whisker values byte wRightOld; byte counter; // For counting alternate corners for wiska void setup() { //////start up tunes tone(3, londonbridge[0], 500); delay(200); tone(3, londonbridge[1], 500); delay(200); tone(3, londonbridge[2], 500); delay(200); tone(3, londonbridge[3], 500); delay(200); tone(3, londonbridge[4], 500); delay(200); tone(3, londonbridge[5], 500); delay(200); tone(3, londonbridge[6], 500); ///////////end of start up tune pinMode(ledstatus,OUTPUT); radio.begin(); radio.setDataRate(RF24_250KBPS); radio.setChannel(100); radio.setRetries(15,15); radio.openWritingPipe(pipes[0]); radio.openReadingPipe(1, pipes[1]); radio.startListening(); servoLeft.attach(8); servoRight.attach(7); pinMode(4, INPUT); // Set right whisker pin to input pinMode(5, INPUT); // Set left whisker pin to input wLeftOld = 0; // Init. previous whisker states wRightOld = 1; counter = 0; // Initialize counter to 0 pinMode(A3, INPUT); pinMode(A2, OUTPUT); // Left IR LED & Receiver pinMode(A1, INPUT); pinMode(A0, OUTPUT); // Right IR LED & Receiver } void loop() { if(radio.available()){ digitalWrite(ledstatus,HIGH);//status light for wifi signal check=true; //these are the byte characters which singnal which function to follow char data[32] = ""; radio.read(&data, 32); if (data[0] == 'w') forward(); if (data[0] == 's') backward(); if (data[0] == 'a') left(); if (data[0] == 'd') right(); if (data[0] == 'v') stop(); if (data[0] == 'o') OBSTICALAVOID(); if (data[0] == 'c') WISKAWISKA(); if (data[0] == 'l')LIGHTSEEKER(); if (data[0] == 'e')WISKALIGHT(); if (data[0] == 'h')DAYSEEKER(); if (data[0] == 'n')NIGHTSEEKER(); if (data[0] == 'j')EDGEDETECT(); if (data[0] == 'u')ULTIMATE(); radio.stopListening(); radio.write(&data, 32); radio.startListening(); } else{ digitalWrite(ledstatus,LOW); } } ///////////////////////////////////////////////////////////// ///begining of the function definitions for the robot follow below: //functions for the arrow key movements among other basic movement commands void backward(){ servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1300); } void left(){ servoLeft.writeMicroseconds(1300); servoRight.writeMicroseconds(1300); } void right(){ servoLeft.writeMicroseconds(1700); servoRight.writeMicroseconds(1700); } void forward(){ servoLeft.writeMicroseconds(1300); servoRight.writeMicroseconds(1700); } void stop(){ servoLeft.writeMicroseconds(1500); servoRight.writeMicroseconds(1500); } /////////////////////////////////////////////////////// //for obstacle avoidance test the distance left and right void LeftDistance(){ long RangeInInches; ultrasonic.DistanceMeasure();// get the current signal time; RangeInInches = ultrasonic.microsecondsToInches();//convert the time to inches; leftDistance = RangeInInches; } void RightDistance(){ long RangeInInches; ultrasonic.DistanceMeasure();// get the current signal time; RangeInInches = ultrasonic.microsecondsToInches();//convert the time to inches; rightDistance = RangeInInches; } //sequence of obstacle avoidance actions void OBSTICALAVOID()//main function to be called early on by a letter character { long RangeInInches; ultrasonic.DistanceMeasure();// get the current signal time; RangeInInches = ultrasonic.microsecondsToInches();//convert the time to inches; if (RangeInInches > collisionThresh) { forward(); } else { PathBlock(); } } //sequence for measuring distance left and right in case of a blocked object void PathBlock(){ stop(); right(); delay(500); RightDistance(); delay(500); left(); delay(1000); LeftDistance(); delay(500); right(); delay(500); compareDistance(); } //////////////////////////////////////compare the distance abfter the path block measurements void compareDistance() { if (leftDistance > rightDistance) //if left is less obstructed { left(); delay(500); } else if (rightDistance > leftDistance) //if right is less obstructed { right(); delay(500); } else //if they are equally obstructed { right(); delay(1800); } } //movement to return to the center before making a decision as to which direction to go void returntocenter(){ right(); delay(800); stop(); delay(100); } void WISKAWISKA(){//this function is the same as Wiska() but has music in it. Cannot be used in conjunction with other functions due to the delays involved with the music // Corner Escape byte wLeft = digitalRead(4); // Copy right result to wLeft byte wRight = digitalRead(5); // Copy left result to wRight if(wLeft != wRight) // One whisker pressed? { // Alternate from last time? if ((wLeft != wLeftOld) && (wRight != wRightOld)) { counter++; // Increase count by one wLeftOld = wLeft; // Record current for next rep wRightOld = wRight; if(counter == 4) // Stuck in a corner? { wLeft = 0; // Set up for U-turn wRight = 0; counter = 0; // Clear alternate corner count } } else // Not alternate from last time { counter = 0; // Clear alternate corner count } } // Whisker Navigation if((wLeft == 0) && (wRight == 0)) // If both whiskers contact { wbackward(1000); // Back up 1 second turnLeft(800); tone(3, londonbridge[13], 700); //music delay(200); tone(3, londonbridge[14], 500); delay(200); tone(3, londonbridge[15], 500); delay(200); // Turn left about 120 degrees } else if(wLeft == 0) // If only left whisker contact { wbackward(1000); // Back up 1 second turnRight(400); tone(3, londonbridge[7], 700); //music delay(200); tone(3, londonbridge[8], 500); delay(200); tone(3, londonbridge[9], 500); delay(200); // Turn right about 60 degrees } else if(wRight == 0) // If only right whisker contact { wbackward(1000); // Back up 1 second turnLeft(400); tone(3, londonbridge[10], 500); //music delay(200); tone(3, londonbridge[11], 500); delay(200); tone(3, londonbridge[12], 500); delay(200); // Turn left about 60 degrees } else // Otherwise, no whisker contact { wforward(20); // Forward 1/50 of a second } } void WISKA(){ // Corner Escape byte wLeft = digitalRead(4); // Copy right result to wLeft byte wRight = digitalRead(5); // Copy left result to wRight if(wLeft != wRight) // One whisker pressed? { // Alternate from last time? if ((wLeft != wLeftOld) && (wRight != wRightOld)) { counter++; // Increase count by one wLeftOld = wLeft; // Record current for next rep wRightOld = wRight; if(counter == 4) // Stuck in a corner? { wLeft = 0; // Set up for U-turn wRight = 0; counter = 0; // Clear alternate corner count } } else // Not alternate from last time { counter = 0; // Clear alternate corner count } } // Whisker Navigation all movements have a "w" in front to indicate that they belong with the wiska movements done because of the inclusion of (time) within the function declaration if((wLeft == 0) && (wRight == 0)) // If both whiskers contact { wbackward(1000); // Back up 1 second turnLeft(800); // Turn left about 120 degrees } else if(wLeft == 0) // If only left whisker contact { wbackward(1000); // Back up 1 second turnRight(400); // Turn right about 60 degrees } else if(wRight == 0) // If only right whisker contact { wbackward(1000); // Back up 1 second turnLeft(400); // Turn left about 60 degrees } else // Otherwise, no whisker contact { wforward(20); // Forward 1/50 of a second } } void wbackward(int time) // Forward function { servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(time); // Maneuver for time ms } void turnLeft(int time) // Left turn function { servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1300); // Right wheel clockwise delay(time); // Maneuver for time ms } void turnRight(int time) // Right turn function { servoLeft.writeMicroseconds(1700); // Left wheel counterclockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(time); // Maneuver for time ms } void wforward(int time) // Backward function { servoLeft.writeMicroseconds(1300); // Left wheel clockwise servoRight.writeMicroseconds(1700); // Right wheel counterclockwise delay(time); // Maneuver for time ms } /////////////////////////////////////////////////// //light seeking test to calcuate the rate of light change at a given moment long rcTime(int pin) // rcTime measures decay at pin { pinMode(pin, OUTPUT); // Charge capacitor digitalWrite(pin, HIGH); // ..by setting pin ouput-high delay(5); // ..for 5 ms pinMode(pin, INPUT); // Set pin to input digitalWrite(pin, LOW); // ..with no pullup long time = micros(); // Mark the time while(digitalRead(pin)); // Wait for voltage < threshold time = micros() - time; // Calculate decay time return time; // Returns decay time } // maneuver function for light seeking void maneuver(int speedLeft, int speedRight, int msTime) { servoLeft.writeMicroseconds(1500 - speedLeft); // Set Left servo speed servoRight.writeMicroseconds(1500 + speedRight); // Set right servo speed if(msTime==-1) // if msTime = -1 { servoLeft.detach(); // Stop servo signals servoRight.detach(); } delay(msTime); // Delay for msTime } void LIGHTSEEKER(){//looks for the most light float tLeft = float(rcTime(A5)); // Get left light & make float float tRight = float(rcTime(A4)); // Get right light & make float float ndShade; // Normalized differential shade ndShade = tRight / (tLeft+tRight) - 0.5; // Calculate it and subtract 0.5 int speedLeft, speedRight; // Declare speed variables if (ndShade > 0.0) // Shade on right? { // Slow down left wheel speedLeft = int(200.0 - (ndShade * 1000.0)); speedLeft = constrain(speedLeft, -200, 200); speedRight = 200; // Full speed right wheel } else // Shade on Left? { // Slow down right wheel speedRight = int(200.0 + (ndShade * 1000.0)); speedRight = constrain(speedRight, -200, 200); speedLeft = 200; // Full speed left wheel } maneuver(speedLeft, speedRight, 20); // Set wheel speeds } void DARKSEEKER(){//opposite of LIGHTSEEKER(), and goes towards darkness float tLeft = float(rcTime(A5)); // Get left light & make float float tRight = float(rcTime(A4)); // Get right light & make float float ndShade; // Normalized differential shade ndShade = tRight / (tLeft+tRight) - 0.5; // Calculate it and subtract 0.5 int speedLeft, speedRight; // Declare speed variables ndShade = -ndShade; if (ndShade > 0.0) // Shade on right? { // Slow down left wheel speedLeft = int(200.0 - (ndShade * 1000.0)); speedLeft = constrain(speedLeft, -200, 200); speedRight = 200; // Full speed right wheel } else // Shade on Left? { // Slow down right wheel speedRight = int(200.0 + (ndShade * 1000.0)); speedRight = constrain(speedRight, -200, 200); speedLeft = 200; // Full speed left wheel } maneuver(speedLeft, speedRight, 20); // Set wheel speeds } void WISKALIGHT(){//wiskas and light combined WISKAWISKA(); LIGHTSEEKER(); } void DAYSEEKER(){ //wiskas, light and obstacle avoidance combined WISKA(); LIGHTSEEKER(); OBSTICALAVOID(); } void NIGHTSEEKER(){//wiskas, dark seeking and obstacle avoidance combined DARKSEEKER(); OBSTICALAVOID(); WISKA(); } void EDGEDETECT(){//edge detection to stay on a table, detect vertical drop offs using IR sensors int irLeft = irDetect(A2, A3, 38000); // Check for object on left int irRight = irDetect(A0, A1, 38000); // Check for object on right if((irLeft == 0) && (irRight == 0)) // Both sides see table surface { tablemaneuver(200, 200, 20); // Forward 20 milliseconds } else if(irLeft == 0) // Left OK, drop-off on right { tablemaneuver(-200, -200, 300); tablemaneuver(-200, 200, 400); // Left for 400 ms } else if(irRight == 0) // Right OK, drop-off on left { tablemaneuver(-200, -200, 300); tablemaneuver(200, -200, 400); // Right for 400 ms } else // Drop-off straight ahead { tablemaneuver(-200, -200, 250); // Backward 250 ms before retry } } int irDetect(int irLedPin, int irReceiverPin, long frequency) { tone(irLedPin, frequency, 8); // IRLED 38 kHz for at least 1 ms delay(1); // Wait 1 ms int ir = digitalRead(irReceiverPin); // IR receiver -> ir variable delay(1); // Down time before recheck return ir; // Return 1 no detect, 0 detect } void tablemaneuver(int speedLeft, int speedRight, int msTime) { // speedLeft, speedRight ranges: Backward Linear Stop Linear Forward // -200 -100......0......100 200 servoLeft.writeMicroseconds(1500 - speedLeft); // Set Left servo speed servoRight.writeMicroseconds(1500 + speedRight); // Set right servo speed if(msTime==-1) // if msTime = -1 { servoLeft.detach(); // Stop servo signals servoRight.detach(); } delay(msTime); // Delay for msTime } void ULTIMATE(){//combination of all the functions, wiskas, light seeking, obstacle avoidance and edge detection WISKA(); LIGHTSEEKER(); OBSTICALAVOID(); EDGEDETECT(); }
Lines 176-190 in the void loop() show how each letter sent to the robot corresponds with a movement function. The entirety of these functions can be seen at the bottom of the void loop(). Many of the sketches which make up these autonomous functions were adapted from the Parallax online code documentation. However, the obstacle avoidance code is something I developed and used during past projects.
The video below is a short, "fun", abbreviated summary of the "Ultimate" function for the Tara Bot Blambo Bot Delux. Bon appetite!:
Top Comments