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;
}
}