User login

Powered by Drupal, an open source content management system

Theory and Practice of Tangible User Interfaces

Lab 4 - Force sensors and photocells (Leg Beater)

Submitted by ash on Wed, 10/01/2008 - 16:28

Assignment: Sensing PART II: Force sensors and photocells

Collaborators:

Collaborators: nick


 

Description:

This lab was an attempt to create a meaningful interface for a bike-simulation video game using force sensors and photo cells.  We employed an 'egg beater' as a method of controlling left, right, and forward motion using arduino and processing.

A player can sit with the controller between their legs and 'pedal' the bike forward using the egg-beater.  Left right motion is enabled by just moving the controller to the left or to the right.

Components Used:

  • (3) 220 ohm resitors
  • (2) FSR
  • (1) Photo cell
  • (1) LED
  • Egg beater
  • Cardboard box
  • Dense foam (to diffuse force)
  • Arduino board
  • USB Cable

 

The left right functionality is controlled via force sensors which the controller presses against (intermediated by dense foam).

Forward motion is produced by the interruption of the LED light to the light sensor by the egg beater blades.

Sample code (Processing Game):

/*
Paperboy visualization
*/

// Serial setup
import processing.serial.*;
String portname = "COM5";
// String portname = "/dev/tty.usbserial-A4001nK5"; // or "COM5"
Serial port;
String buf="";
int cr = 13; // ASCII return == 13
int lf = 10; // ASCII linefeed == 10

// speed mechanics
int speedBuffer = 0;
float stepSize;
int step = 0;
int stepCount = 30;

// speed and distance
float speed = 1.0;
float distance = 0;
boolean gameOver = false;
int finalTime;

// road settings
int rmin = 10; // .5 min road width
int rmax = 190; // .5 max road width
int rh = 300; // road height
int ry = 100; // road y offset
color rColor = color(105);

// bike settings
PImage bike;
int bikeOffset = 0; // bike x offset
int bikeWidth = 150;
int bikeHeight = 60;

PImage sky, tree;

// controllers
DottedLineCtrl dlc;
ObstacleCtrl roc;

void setup()
{
size(400,400);
frameRate(60);
noStroke();
PFont myFont = createFont("Courier New", 14);
textFont(myFont);
bike = loadImage("bike.png");
sky = loadImage("sky.png");
// controllers
dlc = new DottedLineCtrl();
roc = new ObstacleCtrl();
// serial setup
port = new Serial(this, portname, 9600);
}

void draw()
{
if (!gameOver) {
// basic background
smooth();
// sky
image(sky, 0, 0, 400, 100);
// grass
fill(23, 159, 0);
rect(0, 100, 400, 300);
// road
fill(rColor);
quad (200-rmin, ry, 200+rmin, ry, 200+rmax, ry+rh, 200-rmax, ry+rh);
// line
fill(255, 255, 240);
quad(199, ry, 201, ry, 220, ry+rh, 180, ry+rh);
noSmooth();
// update distance traveled
distance = distance + speed - 1;
// controllers
dlc.run();
roc.run();
// bicycle
image(bike, 200 + bikeOffset - bikeWidth/2, 400 - bikeHeight, bikeWidth, bikeHeight);
// figure out speed
if (frameCount % 120 == 0) {
stepSize = (1.0 + (float) speedBuffer / 120 - speed) / stepCount;
step = stepCount;
println(speedBuffer);
speedBuffer = 0;
}
if (step != 0) {
speed += stepSize;
step--;
}
// print speed, distance, time
fill(255);
text("speed: " + floor((speed - 1) * 600), 10, 14);
text("distance: " + floor(distance * 6 / 10), 10, 28);
text("time: " + frameCount/60 + " sec", 10, 42);
if (floor(distance * 6 / 10) > 100) {
gameOver = true;
finalTime = frameCount/60;
}
}
else {
fill(255);
rect(100, 50, 200, 200);
fill(0, 0, 105);
text("Game Over!", 130, 120);
text("Your time: " + finalTime + " sec", 130, 170);
}
}

// listen for keyboard events
void keyPressed() {
if (key == CODED) {
if (keyCode == UP) {
speed = min(speed + 0.01, 1.1);
}
else if (keyCode == DOWN) {
speed = max(speed - 0.01, 1.0);
}
else if (keyCode == LEFT) {
bikeOffset = max(bikeOffset - 10, -100);
}
else if (keyCode == RIGHT) {
bikeOffset = min(bikeOffset + 10, 100);
}
}
}

// listen for serial events
void serialEvent(Serial p) {
int c = port.read();
if (c != lf && c != cr) {
buf += char(c);
}
if (c == lf) {
// println(buf);
// buffer holds one letter (r, l, s) + value
char cmd = buf.charAt(0);
int val = int(buf.substring(1));
switch(cmd) {
case 'o':
// interpret both r/l sensors: -500 - 500
if (val != 0) {
bikeOffset = val * (rmax - bikeWidth/2) / 500;
}
break;
case 'r':
// interpret right sensor: 0-1023 -- deprecated
if (val != 0) {
bikeOffset = val * (rmax - bikeWidth/2) / 200;
}
break;
case 'l':
// interpret left sensor: 0-1023 -- deprecated
if (val != 0) {
bikeOffset = val * (rmax - bikeWidth/2) / 500 * -1;
}
break;
case 'f':
// interpret speed sensor: 1 or 0
speedBuffer += val;
break;
}
buf = "";
}
}

class MovingObject {
float yOffset = 1;
float xOffset = 0;
float scaleFactor;
float initialWidth;
float initialHeight;

MovingObject() {
}

void update() {
// move and scale
yOffset = yOffset * speed;
scaleFactor = (yOffset * (float)(rmax-rmin)) / (float)(rh * rmin) + 1;
}

void render() {
// override in subclass
}

boolean dead() {
return yOffset > rh;
}
}

class DottedLine extends MovingObject {

DottedLine() {
initialWidth = 2;
initialHeight = 3;
}

void render() {
fill(rColor);
int w = floor(scaleFactor * initialWidth);
int h = floor(scaleFactor * initialHeight);
rect(200-w, floor(yOffset) + ry - 1, 2*w, h);
}

}

class Obstacle extends MovingObject {

color c;
color impact;

Obstacle() {
initialWidth = 6;
initialHeight = 6;
xOffset = random(2) > 1 ? -5 : 5;
c = color(0, 0, 255);
impact = color(255, 0, 0);
}

void render() {
int w = floor(scaleFactor * initialWidth);
int h = floor(scaleFactor * initialHeight);
int xO = floor(scaleFactor * xOffset);
fill(c);
// first check if we're low enough to care
if (floor(yOffset)+ry > 400-bikeHeight && c != impact) {
if (xO < 0) {
// left side collision
if ((xO + w/2) >= (bikeOffset - bikeWidth/2)) {
c = impact;
}
}
if (xO >= 0) {
// right side collision
if ((xO - w/2) <= (bikeOffset + bikeWidth/2)) {
c = impact;
}
}
}
rect(200-floor(w/2)+xO, floor(yOffset)+ry - 1 - h, w, h);
}

boolean dead() {
return (yOffset - floor(scaleFactor * initialHeight)) > rh;
}

}

class MovingObjectCtrl {
ArrayList objects;
float interval = 0.5;
float oldDistance = 0;

MovingObjectCtrl() {
objects = new ArrayList();
}

void run() {
if (checkInterval()) makeNew();
for (int i = objects.size()-1; i >= 0; i--) {
MovingObject o = (MovingObject) objects.get(i);
o.update();
if (o.dead()) {
objects.remove(i);
}
else {
o.render();
}
}
}

boolean checkInterval() {
if ((distance - oldDistance) > interval) {
oldDistance = distance;
return true;
}
else {
return false;
}
}

void makeNew() {
// override in subclass
}

}

class DottedLineCtrl extends MovingObjectCtrl {

void makeNew() {
objects.add(new DottedLine());
}

}

class ObstacleCtrl extends MovingObjectCtrl {

ObstacleCtrl() {
interval = random(3,10);
}

void makeNew() {
objects.add(new Obstacle());
}

boolean checkInterval() {
boolean c = super.checkInterval();
if (c) {
interval = random(3,10);
return true;
}
else return false;
}

}

Sample code (Arduino):

/*
* Resistive Sensor Input
* Takes the input from a resistive sensor, e.g., FSR or photocell
* Dims the LED accordingly, and sends the value (0-255) to the serial port
*/
int rPin = 1; // select the input pin for the right sensor
int lPin = 0;
int fPin = 2;
int ledPin = 11; // select the output pin for the LED

int r;
int l;
int f;
int fOld = 0;

void setup() {
Serial.begin(9600);
analogWrite(ledPin, 255);

}


void loop() {

r = analogRead(rPin); // read the value from the sensor, 0-1023
f = analogRead(fPin); // read the value from the sensor, 0-1023
l = analogRead(lPin); // read the value from the sensor, 0-1023

// print out game state as 'rX fY lZ' where X, Y, Z are values for
// right, forward, and left

/* deprecated
Serial.print('r');
Serial.println(r);

Serial.print('l');
Serial.println(l);
*/

// offset value
Serial.print('o');
Serial.println(r-l);

Serial.print('f');
// capture changes in speed
if (f - fOld > 50) {
Serial.println(1);
} else {
Serial.println(0);
}
fOld = f;

delay(50); // rest a little...
}