<
>
back to all cubes

Ruler Cube

Measures distance and shows it using a split-flap display

There is nothing like that moment you see your project coming to life! We all felt that when Michael Harari, after spending a few days (and nights) in the lab, held his hand above the sensor and the numbers on the split-flap display started to roll!

But the work didn't end there. It turned out the sensor is extremely non-linear, and we needed to find a way to compensate for it (after all - it's a ruler!). First, we tried to write an equation to convert from the sensor reading to centimeters, but that wasn't accurate enough. The solution was to accurately position an object in each distance from the sensor and write its read value, thus creating a lookup table.

Next, we needed the Arduino to know what number is currently shown on the display, so we added an optical line sensor and a white spot on the numbers wheel.

A tricky challenge was picking the best interaction with the cube. The numbers wheel can turn only in one direction, so continually reading the sensor and rotating the wheel to the current read created an almost constant rotation. We divided the output to two cases: When the hand is getting closer to the cube (numbers decrease), we created a "dynamic tolerance envelope" that limits the allowed tolerance as time elapses, thus preventing the wheel from constantly rotating, while giving the user the feel of an accurate read. When the hand is lifted up (numbers increase), we constantly update the shown number, thus allowing for a very nice and almost tactile feel of controlling the numbers wheel.

The last challenge was the cube's design: the hole in the cube top were supposed to be covered with an appropriate material (not to interfere with the IR sensor), but we couldn't find any! The hole is an interaction "disaster", being an obvious affordance for the users to insert their finger into it... We still haven't found a solution for this challenge.

Arduino code - download here

/*
  CUBES originally created by the interaction Lab, Holon Institute of Technology for the Design Museum Holon.
  http://interaction.shenkar.ac.il
  This work is licensed under a Creative Commons Attribution 3.0 Unported License

  Ruler cube by Michael Harai
*/

/////////////////////////////////////////////////////////////////////////
//
// Envelop version: Tolerance shrinks as time from last change passes
// In this version - envelop works only for smaller distance. For larger distance - always update.
//


#define STEP_PIN 6 // Stepper Motor Step - pin 6
#define CALIBRATE_PIN A1 // Line Sensor - pin 5 (Interrupt 0)for resetting wheel position
#define LED_PIN 18 // to test Calibartion
#define SENSOR_PIN A2 // IR proximty sensor pin A2
#define usDelay 140 // Delay for 57 filps
#define CALIBRATE_THRESHOLD 400 // val for CALIBRATE the line sensor
#define ENABLED_PIN 14 // 
#define THRESHOLD 2 //
#define SENSOR_MAX 395
#define SENSOR_MIN 50
#define FILTER_LENGTH 200
#define INIT_TOLERANCE 40
#define TOL_FACTOR 10
#define NO_HAND 20  // below this value - probably no hand in the area, so should display 00

// analog reading to centimeters mapping - this was manually calibrated for our specific snensor.
// distance is measured from the face of the cube
int anToCm[] = {420, 375, 321, 287, 267, 260, 244, 236, 229, 226,
                224, 220, 218, 216, 213, 206, 201, 196, 190, 186,
                178, 174, 171, 167, 163, 158, 154, 150, 146, 142,
                139, 135, 134, 131, 127, 126, 123, 119, 118, 115,
                112, 111, 107, 106, 104, 103, 100, 99, 98, 97,
                94, 93, 90, 89, 87, 86, 85, 82, 81, 30
               };

int steps = 0; //steps for stepper
int rawValue; // raw data from sensor
int filter[FILTER_LENGTH];  // for filtering, we always look on the average of last 200 readings
int i = 0; // filter index
long sensorValue = 0; // filterd value
int userDistance = 0; // value for The distance of the user
int tempUserDist;     // temporary user distance for threshold
int displayDistance = 0; // value displayed on flaps
int calibrateValue = 0; // val for calibrate line sensor
boolean calibratedFlag = false;  // don't calibrate more than once a cycle
boolean noHand = true;
long lastUpdate;  // last time (in millis) user distance was updated
int tolerance = INIT_TOLERANCE;

void setup() {
  pinMode (STEP_PIN, OUTPUT); // OUTPUT for Stepper
  pinMode (CALIBRATE_PIN, INPUT);// INPUT for Line Sensor
  pinMode (LED_PIN, OUTPUT);
  pinMode (19, OUTPUT);
  digitalWrite (19, LOW);
  Serial.begin(115200);
}


void loop() {

  // Read the IR sensor/////
  /////////////////////////////////////////////
  rawValue = analogRead(SENSOR_PIN);// Reading the Sensor(Pot)
  filter[i] = rawValue;
  ++i %= FILTER_LENGTH;
  sensorValue = 0;
  for (int j = 0; j < FILTER_LENGTH; j++)
    sensorValue += filter[j];
  sensorValue /= FILTER_LENGTH;
  //Serial.println(sensorValue);
  //delay(100);

  // Maping the IR to FLIP /////
  /////////////////////////////////////////////
  tempUserDist = myMap (sensorValue);
  //// Serial.println( tempUserDist);
  //// delay(500);

  // Rotate the Filp mutipuly numbers /////
  /////////////////////////////////////////////
  //  if ((tempUserDist == 0) && (userDistance == 1))
  //    userDistance = 0;
  //  else
  tolerance = INIT_TOLERANCE - (millis() - lastUpdate) / TOL_FACTOR;
  if (abs(tempUserDist - userDistance >= tolerance) || tempUserDist > userDistance) {
    userDistance = tempUserDist;
    lastUpdate = millis();
  }


  // if (displayDistance == 10)
  //     PORTC=0;

  // Rotate the Filp  /////
  /////////////////////////////////////////////
  if ((userDistance != displayDistance) || noHand) {
    if ((noHand && steps != 0) || (!noHand)) {
      digitalWrite(ENABLED_PIN, LOW);
      digitalWrite(STEP_PIN, HIGH);
      delayMicroseconds(usDelay);

      digitalWrite(STEP_PIN, LOW);
      delayMicroseconds(usDelay);
      steps++;
      steps %= 800;
      calibrateValue = analogRead (CALIBRATE_PIN);
      //    Serial.print (userDistance);
      //    Serial.print ('\t');
      //    Serial.println (steps);
      if (calibrateValue < CALIBRATE_THRESHOLD && !calibratedFlag) { // && //MOTOR is NOT SPeening//
        //  Serial.println ("calibrate");
        steps = 307;
        calibratedFlag = true;
      }
      if (steps == 710)
        calibratedFlag = false;
    }
    if (!noHand) displayDistance = map(steps, 0, 795, 0, 59);
  }
  else digitalWrite (ENABLED_PIN, HIGH);
}

int myMap (int input) {
  int p = 0;
  while (anToCm[p] > input && p < 59) p++;
  if (input < NO_HAND) noHand = true;
  else noHand = false;
  //  Serial.println (p);
  return p;
}


//Serial.println(userDistance);
//delay(1);
//  Serial.print('\t');
//  Serial.println(displayDistance);
//  Serial.println("displayDistance");
//  delay(1);

Parts:

(1x) Arduino Mini Pro 5v

(1x) Minutes split-flap display taken from a clock

(1x) Sharp GP2Y0A21YK - 10-80cm Infrared Proximity Sensor

(1x) Allegro A4983 Stepper Motor Driver

(1x) Small Heatsink

(1x) Analog Line Sensor

(1x) 10.8V Li-Po Battery

(1x) Audio Jack Socket