« Midterm Observation: Mastermind | Main | Midterm Prototype Notes »

Physical Mouse Using Photocells

Assignment: What applications can you think of that could use a better physical interface for a mouse? Create a prototype in Arduino and Processing, or whatever programming environment you choose. Come up with a physical interface that makes it clear what actions map to what movements and actions.

For this assignment, I built a simple console with two photocells wired to the base of two posts. The input of each photocell—which is manipulated by the user sliding a knob up and down the post—is detected through a serial USB connection to the Arduino board and fed to Processing. There, the values are passed into x and y values for "mouse" control over an applet of a dancing bear (see it in it's full glory here).

I didn't notice the grim news report in the background until later, by the way. That, combined with my friend's sound effects, make it all a bit surreal...


Some minor problems I encountered with this project were:

  • Initially, I wasn't referencing the correct serial port in my Processing code, which resulted in the following error: "RXTX Warning: Removing stale lock file. /var/lock/LK.001.009.000". To remedy this, I referenced the list of ports that were printed out in Processing, thanks to Tom's code.
  • It took a while of fiddling with the math to get the serial input from the photocells to match the output to Processing.
  • The Processing applet is occasionally glitchy (bear jumps). Still not sure what's causing it, but it's not a significant problem. Might be the result of my math (as I'm dividing the input by 6 in the Arduino before sending it to Processing).

Breadboard Setup

Arduino Code

int firstSensor = 0;    // first photocell sensor
int secondSensor = 0;   // second photocell sensor
int placeHolder = 0;   // null placeholder to make a full byte
int inByte = 0;         // incoming serial byte

void setup() {
  // start serial port at 9600 bps:
  Serial.begin(9600);
}

void loop() {
  // if we get a valid byte, read analog ins:
  if (Serial.available() > 0) {
    // get incoming byte:
    inByte = Serial.read();
    // read first analog input and do some math to make the range 0-165 (due to processing requirement):
    firstSensor = constrain(analogRead(0)/6,0,165);
    // delay 10ms to let the ADC recover:
    delay(10);
    // read second analog input and do some math to make the range 0-107:
    secondSensor = constrain(analogRead(1)/6,0,107);
    // read placeholder value (always 0)

    // send sensor values:
    Serial.print(firstSensor, BYTE);
    Serial.print(secondSensor, BYTE);
    Serial.print(placeHolder, BYTE);               
  }
}

Processing Code

import processing.serial.*;

int rVar = 146; //set initial r value
int gVar = 195; //set initial g value
int bVar = 222; //set initial b value
int xpos = 0; //takes the place of xpos
int ypos = 0; //takes the place of ypos
Serial port;                         // The serial port
int[] serialInArray = new int[3];    // Where we'll put what we receive
int serialCount = 0;                 // A count of how many bytes we receive
boolean firstContact = false;        // Whether we've heard from the microcontroller

void setup()  {
  size(200,200);
  smooth();
  frameRate(30);
  
  // Print a list of the serial ports, for debugging purposes:
  println(Serial.list());

  // Open serial port (using number based on results of list above)
  port = new Serial(this, Serial.list()[2], 9600);
  port.write(65);    // Send a capital A to start the microcontroller sending
}

void draw()  {
  // If no serial data has beeen received, send again until we get some.
  // (in case you tend to start Processing before you start your external device):
  if (firstContact == false) {
    delay(300);
    port.write(65);
  }
  
// BEAR  
  background(rVar,gVar,bVar);
  // the following statements limit the action to the window to avoid weirdness
  if (ypos >= 107)  {
    ypos = 107;
  }
  if (ypos <= 20)  {
    ypos = 20;
  }
  if (xpos >= 165)  {
    xpos = 165;
  }
  if (xpos <= 34)  {
    xpos = 34;
  }
  noStroke();
  //circles in bg
  fill(255,35);
  ellipse(70,50,ypos+20,ypos+20);
  ellipse(165,120,ypos+20,ypos+20);
  ellipse(55,165,ypos+20,ypos+20);
  fill(40,80,0); //set head color
  ellipse(xpos,ypos,50,40); //head
  ellipse(xpos+25,ypos-13,20,13); //right ear
  ellipse(xpos-25,ypos-13,20,13); //left ear
  rectMode(CENTER);
  fill(0);
  quad(xpos-5,ypos+18,xpos+5,ypos+18,xpos+13,ypos+90,xpos-12,ypos+90); //trunk
  rect(xpos,ypos-5,49,3); //shades band
  quad(xpos-17,ypos-5,xpos-3,ypos-5,xpos-5,ypos+5,xpos-15,ypos+5); //left shade
  quad(xpos+4,ypos-5,xpos+18,ypos-5,xpos+16,ypos+5,xpos+6,ypos+5); //right shade
  rect(xpos,ypos+31,32,10); //shoulders
  strokeWeight(10); //set arm width
  stroke(0);  //set arm and leg color
  line(xpos-15,ypos+31,xpos/5+50,ypos/4+100); //left arm
  line(xpos+15,ypos+31,xpos/5+110,ypos/4+100); //right arm
  strokeWeight(17);  //set leg width
  stroke(xpos/4*6+25,ypos/2*3-20,xpos*2-200); //set pant color
  rect(xpos,ypos+80,15,3); //hips
  line(xpos-6,ypos+82,xpos/5+60,ypos/4+165);  //left leg
  line(xpos+6,ypos+82,xpos/3+83,ypos/4+165); //right leg
  noStroke();
  //note that there is no line break in these two quads--added for formatting on this web page
  quad(xpos/5+52,ypos/4+165,xpos/5+69,ypos/4+165,xpos/5+71,ypos/4+178,
  xpos/5+46,ypos/4+178); //left foot
  quad(xpos/3+75,ypos/4+165,xpos/3+92,ypos/4+165,xpos/3+98,ypos/4+178,
  xpos/3+73,ypos/4+178); //right foot
  // if someone clicks or holds the mouse, change the bg
  if (mousePressed == true)  {
    rVar = rVar + 20;
    gVar = gVar + 5;
    bVar = bVar + 40;
  }
// if the colors go off the map, reset them to a lower value
  if (rVar >= 255)  {
    rVar = 0;
  }
  if (gVar >= 255)  {
    gVar = 10;
  }
  if (bVar >= 255)  {
    bVar = 30;
  }
}

void serialEvent(Serial port) {
  // if this is the first byte received, 
  // take note of that fact:
  if (firstContact == false) {
    firstContact = true; 
  }
  // Add the latest byte from the serial port to array:
  serialInArray[serialCount] = port.read();
  serialCount++;

  // If we have 3 bytes:
  if (serialCount > 2 ) {
    xpos = serialInArray[0];
    ypos = serialInArray[1];

    // print the values (for debugging purposes only):
    println(xpos + "\t" + ypos);

    // Send a capital A to request new sensor readings:
    port.write(65);
    // Reset serialCount:
    serialCount = 0;
  }
}

TrackBack

TrackBack URL for this entry:
http://itp.nyu.edu/~km63/cgi-bin/mt/mt-tb.cgi/8

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)