<
>
back to all cubes

Homesick Cube

Turn it anywhere - it turns back to point to the Interaction Lab where it was built

In creating the cubes, we sought inspiration from interesting projects that had been created by other designers. One of these was the 'El Sajjadah' prayer carpet by Soner Ozenc, which points the worshiper in the direction of Mecca. At first, Eran planned the cube so that it would indicate North no matter in which direction it was aimed, but after further thought it was decided that it should always point towards the Interaction Lab in Israel.

The cube behaves like a compass and will always indicate the direction of the lab, no matter which way you turn it. Whenever the cube travels to different places in the world, it needs a new code to direct it to the appropriate azimuth.

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

  Home sick cube by Eran Gal-Or
*/

/*
  Calibration:
  Flip the cube up-side down (crossing horizon) 3 times within 3 seconds. A brief flashing will occure.
    Then put the cube flat and slowly rotate it fully (get 360 degrees) a few times. After 30 seconds another brief flasshing will notify end of calibnration.

  See if you can figure out (and activate!) the easter egg...
*/

//motor control pins:
#define out_STBY 13
#define out_B_PWM 11
#define out_B_IN1 12
#define out_B_IN2 10

// Reference the HMC5883L Compass Library
#include "src/Adafruit_Unified_Sensor/Adafruit_Sensor.h"
#include "src/Adafruit_HMC5883_Unified/Adafruit_HMC5883_U.h"

// EEPROM memory is used for storing calibration data
#include <EEPROM.h>
// calibration address beginning. If calibration problems occure, it might be because of bad memory - change the address.
#define CAL_ADDR 0

float headTo = 320; // where to point to (degrees) - direction from Design Museum Holon to Interaction Lab (R.I.P) in Holon Institute of Technology

// declare and initialize the compass
Adafruit_HMC5883_Unified compass = Adafruit_HMC5883_Unified();

// previous read of sensor event - for processing only new events (default at 15Hz)
unsigned long prevRead = 0;

// giving names to different states possible inside the loop
enum State {
  idle,
  cube_flipped,
  calibration,
  e_a_pending_1,
  e_a_pending_2,
  e_a_pending_3,
  e_a_pending_4,
  easter_egg
};

// this variable holds current state
State state = idle;

// for entering calibration mode, the user needs to flip (up-side down) the cube 3 times in 3 seconds.
// these variables are used for discovering these flips
float prevZ = -1; // assume negative field in z direction on startup
int flips = 0;
unsigned long flipStartTime;

float headingDegrees;

// calibration data
unsigned long calibrationStartTime;
float minX = -100;
float maxX = 100;
float minY = -100;
float maxY = 100;

//easter egg variables:
unsigned long easterEggTimer;

void setup()
{
  //Motors pins
  pinMode(out_STBY, OUTPUT);
  pinMode(out_B_PWM, OUTPUT);
  pinMode(out_B_IN1, OUTPUT);
  pinMode(out_B_IN2, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  // put motor in standby mode (this happens anyway, but just to be clear...)
  digitalWrite(out_STBY, LOW);

  // initialize the serial port
  Serial.begin(115200);

  // initialize the compass
  Serial.println("Starting HMC5883L");
  if (!compass.begin()) // Construct a new HMC5883 compass.
  {
    Serial.println("Ooops, no HMC5883 detected ... Check your wiring!");
    while (1);
  }

  readCalibrationFromEEPROM();
}

// Our main program loop.
void loop()
{
  // Get a new sensor event
  sensors_event_t event;
  compass.getEvent(&event);
  bool newEvent = (millis() - prevRead > 66);

  if (newEvent) {
    prevRead = millis();
    // Display the results (magnetic vector values are in micro-Tesla (uT))
    Serial.print("X: "); Serial.print(event.magnetic.x); Serial.print("\t");
    Serial.print("Y: "); Serial.print(event.magnetic.y); Serial.print("\t");
    Serial.print("Z: "); Serial.print(event.magnetic.z); Serial.print("\t");
  }

  switch (state) {
    case idle:
      seekHome(event, newEvent);

      // the cube was flipped. check possible entry to calibration mode
      if (sign(event.magnetic.z) != sign(prevZ)) {
        flipStartTime = millis();
        flips++;
        state = cube_flipped;
        digitalWrite(out_STBY, LOW);
      }

      // check if easter egg sequence is starting
      if (abs(headingDegrees - int(headTo + 90)%360) < 10) {
        if (millis() - easterEggTimer > 1000) {
          digitalWrite(LED_BUILTIN, LOW);
          delay(150);
          digitalWrite(LED_BUILTIN, HIGH);
          state = e_a_pending_1;
          easterEggTimer = millis();
        }
      }
      else {
        easterEggTimer = millis();
      }
      break;

    case cube_flipped:
      if (sign(event.magnetic.z) != sign(prevZ)) {
        flips++;
      }

      // 6 flips. give notification to user and go to calibration
      if (flips > 5) {
        for (int i = 0; i < 30; i++) {
          analogWrite(out_B_PWM, 0);
          digitalWrite(LED_BUILTIN, HIGH);
          delay(50);
          digitalWrite(LED_BUILTIN, LOW);
          delay(50);
        }
        state = calibration;
        calibrationStartTime = millis();
        minX = 100;
        maxX = -100;
        minY = 100;
        maxY = -100;
        Serial.println("Calibrating");
      }
      // 3 seconds elapsed without 6 flips. go back to idle
      else if (millis() - flipStartTime > 3000) {
        flips = 0;
        state = idle;
      }
      break;

    case calibration:
      // blink slowly while calibrating
      if (millis() / 1000 % 2) digitalWrite(LED_BUILTIN, HIGH);
      else digitalWrite(LED_BUILTIN, LOW);

      if (newEvent) {
        if (event.magnetic.x > maxX) maxX = event.magnetic.x;
        if (event.magnetic.x < minX) minX = event.magnetic.x;
        if (event.magnetic.y > maxY) maxY = event.magnetic.y;
        if (event.magnetic.y < minY) minY = event.magnetic.y;
        Serial.print(minX); Serial.print("\t");
        Serial.print(maxX); Serial.print("\t");
        Serial.print(minY); Serial.print("\t");
        Serial.println(maxY);
      }

      // end calibration after 30 seconds
      if (millis() - calibrationStartTime > 30000) {
        Serial.println("Calibrating finished");
        writeCalibrationToEEPROM();
        for (int i = 0; i < 10; i++) {
          digitalWrite(LED_BUILTIN, HIGH);
          delay(50);
          digitalWrite(LED_BUILTIN, LOW);
          delay(50);
        }
        state = idle;
      }
      break;

    case e_a_pending_1:
      seekHome(event, newEvent);

      if (millis() - easterEggTimer > 2000) {
        easterEggTimer = millis();
        state = e_a_pending_2;
      }
      break;

    case e_a_pending_2:
      seekHome(event, newEvent);

      if (abs(headingDegrees - int(headTo + 180)%360) < 30) {
        if (millis() - easterEggTimer > 1000) {
          digitalWrite(LED_BUILTIN, LOW);
          delay(150);
          digitalWrite(LED_BUILTIN, HIGH);
          state = e_a_pending_3;
          easterEggTimer = millis();
        }
      }
      else {
        for (int i = 0; i < 3; i++) {
          digitalWrite(LED_BUILTIN, HIGH);
          delay(50);
          digitalWrite(LED_BUILTIN, LOW);
          delay(50);
        }
        state = idle;
      }
      break;

    case e_a_pending_3:
      seekHome(event, newEvent);

      if (millis() - easterEggTimer > 2000) {
        easterEggTimer = millis();
        state = e_a_pending_4;
      }
      break;

    case e_a_pending_4:
      seekHome(event, newEvent);

      if (abs(headingDegrees - int(headTo + 270)%360) < 30) {
        if (millis() - easterEggTimer > 1000) {
          state = easter_egg;
        }
      }
      else {
        for (int i = 0; i < 3; i++) {
          digitalWrite(LED_BUILTIN, HIGH);
          delay(50);
          digitalWrite(LED_BUILTIN, LOW);
          delay(50);
        }
        state = idle;
      }
      break;

    case easter_egg:
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, 255);
      digitalWrite(out_B_IN1, HIGH);
      digitalWrite(out_B_IN2, LOW);
      delay(3000);
      digitalWrite (out_STBY, LOW);
      delay(1000);
      digitalWrite(out_B_IN1, LOW);
      digitalWrite(out_B_IN2, HIGH);
      digitalWrite (out_STBY, HIGH);
      delay(3000);
      state = idle;
      break;
  }
  prevZ = event.magnetic.z;
}

void seekHome(sensors_event_t event, bool newEvent) {
  if (newEvent) {
    // Calculate heading when the magnetometer is level, then correct for signs of axis.
    float calX = (event.magnetic.x - (maxX + minX) / 2) / (maxX - minX);
    float calY = (event.magnetic.y - (maxY + minY) / 2) / (maxY - minY);
    float heading = atan2(calY, calX);

    // Once you have your heading, you must then add your 'Declination Angle', which is the 'Error' of the magnetic field in your location
    // Find yours here: http://www.magnetic-declination.com/
    // Mine is: 2� 37' W, which is 2.617 Degrees, or (which we need) 0.0456752665 radians, I will use 0.0457
    // If you cannot find your Declination, comment out these two lines, your compass will be slightly off.
    float declinationAngle = 0.085;
    heading += declinationAngle;

    // Correct for when signs are reversed.
    if (heading < 0)
      heading += 2 * PI;

    // Check for wrap due to addition of declination.
    if (heading > 2 * PI)
      heading -= 2 * PI;

    // Convert radians to degrees for readability.
    headingDegrees = heading * 180 / M_PI;
    //    Serial.println(headingDegrees);

    // rotate cube towards home
    Rotate(headingDegrees);
  }
}


void Rotate(float headingDegrees)
{
  float headTo = 320; // where to point to (degrees) - direction from Design Museum Holon to Interaction Lab (R.I.P) in Holon Institute of Technology
  int tolerance = 5; // of heading degrees to each direction
  int tmax = 200; //waiting time
  int tmin = 10;
  int pause = 250; //time between spining
  Serial.print(headingDegrees); Serial.print("\t");

  // on the spot. NOTICE: This need to be changed if headTo is closer to 0 or 360.
  if (abs(headingDegrees - headTo) < tolerance) {
    digitalWrite(out_STBY, LOW);
    Serial.println("ON");
  }

  else {
    float deviation = headingDegrees - headTo;
    if (deviation > 180) deviation -= 360;
    if (deviation < -180) deviation += 360;
    Serial.println(deviation);

    /////////////////////////////////////////////CCLW////////////////////////////////////////////
    if (sign(deviation) == 1) {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, int(deviation * 0.4 + 20));
      digitalWrite(out_B_IN1, HIGH);
      digitalWrite(out_B_IN2, LOW);
    }

    /////////////////////////////////////////////CLW///////////////////////////////////////////
    else {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, int(-deviation * 0.4 + 20));
      digitalWrite(out_B_IN1, LOW);
      digitalWrite(out_B_IN2, HIGH);
    }
  }
}
/*
  if (headingDegrees > HeadTo) {
    if (headingDegrees < (HeadTo + (tollerance))) {
      digitalWrite(out_STBY, LOW);
    }
  }
  /////////////////////////////////////////////CLW////////////////////////////////////////////
  if (millis() - time > tmax) {
    delay(pause);
    if (headingDegrees < HeadTo) {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, (abs(headingDegrees - HeadTo)) * 0.6 + 25);
      digitalWrite(out_B_IN1, HIGH);
      digitalWrite(out_B_IN2, LOW);
      time = millis();
    }
  }

  if (millis() - time > tmin) {
    if (headingDegrees < HeadTo) {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, (abs(headingDegrees - HeadTo)) * 0.6 + 25);
      digitalWrite(out_B_IN1, HIGH);
      digitalWrite(out_B_IN2, LOW);
      time = millis();
    }
  }
  //////////////////////////////////////////CLW/////////////////////////////////////////


  if (millis() - time > tmax) {
    delay(pause);
    if (headingDegrees > (HeadTo + tollerance)) {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, (abs(headingDegrees - HeadTo)) * 0.6 + 25);
      digitalWrite(out_B_IN1, LOW);
      digitalWrite(out_B_IN2, HIGH);
      time = millis();
    }
  }

  if (millis() - time > tmin) {
    if (headingDegrees > (HeadTo + tollerance)) {
      digitalWrite (out_STBY, HIGH);
      analogWrite(out_B_PWM, (abs(headingDegrees - HeadTo)) * 0.6 + 25);
      digitalWrite(out_B_IN1, LOW);
      digitalWrite(out_B_IN2, HIGH);
      time = millis();
    }
  }


  //////////////////////////////////////////Easter Egg//////////////////////////////////////////////////////
  if (headingDegrees > 50) {
    if (headingDegrees < 90) {
      easteregg1 = millis();
      if (flag3 = 1) {
        flag1 = 0;
        flag2 = 0;
        flag3 = 0;
      }
      flag1 = 1;
    }
  }

  if (headingDegrees > -20) {
    if (headingDegrees < 10) {
      easteregg2 = millis();
      if (flag3 = 1) {
        flag1 = 0;
        flag2 = 0;
        flag3 = 0;
      }
      flag2 = 1;
    }
  }

  if (headingDegrees < 270) {
    if (headingDegrees > 230) {
      easteregg3 = millis();
      if (flag1 = 0) {
        flag1 = 0;
        flag2 = 0;
        flag3 = 0;
      }
      flag3 = 1;
    }
  }

  if ((easteregg3 - easteregg2 > 2000) && (easteregg2 - easteregg1 > 2000)) {
    easteregg = 1;
  }

  if ((easteregg == 1) && (flag1 = 1) && (flag2 = 1) && (flag3 = 1)) {
    digitalWrite (out_STBY, HIGH);

    analogWrite(out_B_PWM, 255);
    digitalWrite(out_B_IN1, HIGH);
    digitalWrite(out_B_IN2, LOW);
    delay(4000);
    digitalWrite (out_STBY, LOW);
    delay(1000);
    digitalWrite (out_STBY, HIGH);
    analogWrite(out_B_PWM, 255);
    digitalWrite(out_B_IN1, LOW);
    digitalWrite(out_B_IN2, HIGH);
    delay(4000);
    digitalWrite (out_STBY, LOW);
    easteregg = 0;
    easteregg1 = 0;
    easteregg2 = 0;
    easteregg3 = 0;

  }


  /*
  Serial.print(headingDegrees);
  Serial.print(" Degrees");
  Serial.print("\tEaster egg1:");
  Serial.print(easteregg1);
  Serial.print("\tEaster egg2:");
  Serial.print(easteregg2);
  Serial.print("\tEaster egg3:");
  Serial.print(easteregg3);
  Serial.print("\tEaster dance!!:");
  Serial.println(easteregg);

  }
*/
int sign(float a) {
  return (a > 0) - (a < 0);
}

void readCalibrationFromEEPROM() {
  int eeAddress = CAL_ADDR;
  EEPROM.get(eeAddress, minX);
  eeAddress += sizeof(float);
  EEPROM.get(eeAddress, maxX);
  eeAddress += sizeof(float);
  EEPROM.get(eeAddress, minY);
  eeAddress += sizeof(float);
  EEPROM.get(eeAddress, maxY);
  Serial.println("--------------------------------------------------------------------------------");
  Serial.print(minX); Serial.print("\t");
  Serial.print(maxX); Serial.print("\t");
  Serial.print(minY); Serial.print("\t");
  Serial.println(maxY);
  Serial.println("--------------------------------------------------------------------------------");
}

void writeCalibrationToEEPROM() {
  int eeAddress = CAL_ADDR;
  EEPROM.put(eeAddress, minX);
  eeAddress += sizeof(float);
  EEPROM.put(eeAddress, maxX);
  eeAddress += sizeof(float);
  EEPROM.put(eeAddress, minY);
  eeAddress += sizeof(float);
  EEPROM.put(eeAddress, maxY);
}

Arduino code - download here

Parts:

(1x) Arduino Mini Pro 3.3V

(1x) HMC5883L Compass Module

(1x) DC Gearmotor

(1x) TB6612FNG - Motor Driver 1A Dual

(1x) 7.2V Li-Po Battery

(1x) 5.5/2.1mm Jack Socket