<
>
back to all cubes

Dizzy Cube

Shaking the cube makes it dizzy - it totters around on the table.

Dizzy's ancestor was a rough cube trying to run away. Over time, we understood the interaction with the cubes over a table doesn't fit a "long distance runner" like that, so its movement needed to stay more constrained. The "drunk / dizzy cube" concept came thereafter and from there the distance to the "shake to make dizzy" was short. Inspired by the box tilt sensor, we used a metal ball and poles instead of using some cutting-edge sensor for input.

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

  Dizzy Cube by Eran Gal-Or
*/

// This code was never finished, because we found difficulties in the input (which was much noisier than we anticipated),
// and we put our time on solving it. Maybe one day...
// Currently - only Serpentine Drive is implemented.

//Pins configure:
#define SHAKE_DBNCE_TIME 100   // debounce time in milliseconds for ball shake input
#define SHAKE_DRIVE_DELAY 1500 // time betwin shake and drive. also determines when user stopped shaking
#define MIN_DRIVE_TIME 2.0  // minimum drive time
#define ENOUGH_SHAKES 6        // less than this - not accounted for shake
#define DRIVE_TIME_FACTOR 1 //was 2  // multipluyer from shake time to drive time
#define WOBBLE_DEVIDER 1     // higher value = less wobbly drives
#define FADE_TIME 1.0          // fade in and out time for drive

#define out_STBY 11
#define out_B_PWM 5
#define out_A_PWM 3
#define out_A_IN2 4
#define out_A_IN1 10
#define out_B_IN1 12
#define out_B_IN2 14
#define in_SHAKE_SW 2

#define motor_A 0
#define motor_B 1

#define IDLE             0
#define STARTED_SHAKING  1
#define SHAKING          2
#define SET_DIZZY_DRIVE  3
#define SERPENTINE_DRIVE 4
#define CAROUSEL_DRIVE   5
#define CAROUSEL_DRIVE1  6
#define DRUNK_DRIVE      7

int state = IDLE;
int rotation=1;
int direct=0;
int distance = 4; // max road before turn
int rhtm = 1; // rhythm of turn

// calculated factors for drive:
float driveTime;  // in seconds
float wobbleFactor;
float  t1=0, t2=0;
float m1s;
float m2s;

// shake input variables:
volatile int shakeCounter = 0;

// shake variators:
int shakeBumps;
int shakeTime;

// timers:
unsigned long shakeStartTime;
volatile unsigned long lastShakeTime;  // timer for measuring time from last shake and for debouncing input
unsigned long startDriveTime;

void setup()
{
  pinMode(out_STBY,OUTPUT);
  pinMode(out_A_PWM,OUTPUT);
  pinMode(out_A_IN1,OUTPUT);
  pinMode(out_A_IN2,OUTPUT);
  pinMode(out_B_PWM,OUTPUT);
  pinMode(out_B_IN1,OUTPUT);
  pinMode(out_B_IN2,OUTPUT);

  pinMode (in_SHAKE_SW, OUTPUT);
  digitalWrite (in_SHAKE_SW, HIGH);
  pinMode (in_SHAKE_SW, INPUT);

  motor_standby (true);

  attachInterrupt (0, shakeIt, FALLING);

  randomSeed (analogRead (2));

  Serial.begin(115200);
}

void loop()
{
  switch (state) {
  case IDLE:
    if (waitForInput()) state = STARTED_SHAKING;
    break;

  case STARTED_SHAKING:
    shakeStartTime = millis();
    state = SHAKING;
    break;

  case SHAKING:
    if (notShakenLongEnough()) state = SET_DIZZY_DRIVE;
    break;

  case SET_DIZZY_DRIVE:
    if (notShakenEnough()) restart();
    else {
      state = randomDrive();
      setDriveFactors();
      motor_standby(false);
      startDriveTime = millis();
    }
    break;

  case SERPENTINE_DRIVE:
    serpentineDrive();
    break;

  }
}

void shakeIt() {
  if (millis() - lastShakeTime > SHAKE_DBNCE_TIME) {
    shakeCounter++;
    lastShakeTime = millis();
    //Serial.println("Shaken!");
  }
}

boolean waitForInput() {
  return (shakeCounter > 0);
}

void restart() {
  state = IDLE;
  Serial.print ("shakeCounter: "); 
  Serial.println (shakeCounter);
  shakeCounter = 0;
  t1=0;
  t2=0;
  rotation=1;
   direct=0;
  motor_standby(true);
  Serial.println ("Restart");
}

boolean notShakenLongEnough() {
  return (millis() - lastShakeTime > SHAKE_DRIVE_DELAY);
}

boolean notShakenEnough() {
  return (shakeCounter < ENOUGH_SHAKES);
}

int randomDrive() {
  return  SERPENTINE_DRIVE;
  //return (random (SERPENTINE_DRIVE, DRUNK_DRIVE + 1));
}

void setDriveFactors() {
  shakeTime = lastShakeTime - shakeStartTime;
  shakeBumps = shakeCounter;
  driveTime = max(MIN_DRIVE_TIME, (float)shakeTime/1000.0 * DRIVE_TIME_FACTOR);
  wobbleFactor = (float)shakeBumps / driveTime / WOBBLE_DEVIDER;
  Serial.print("shakeTime: "); 
  Serial.println(shakeTime);
  Serial.print("shakeBumps: "); 
  Serial.println(shakeBumps);
  Serial.println("Drive factors:");
  Serial.print("driveTime: "); 
  Serial.println(driveTime);
  Serial.print("wobble:    "); 
  Serial.println(wobbleFactor);
}

void serpentineDrive() {
  float t = (float)(millis() - startDriveTime) / 1000.0;
  if (t > driveTime) restart();
  else{
    if ( ((t-t1)*(wobbleFactor)<distance))
    {
      t2=t;
      switch (direct) {
        case 0:
              m1s =(wobbleFactor/6);
              m2s =(wobbleFactor/6);
          break;
          
           case 1:
              m1s =(-1)*(wobbleFactor/6);
              m2s =(-1)*(wobbleFactor/6);
          break;      
           // Serial.println(i);
      }
          motor_speed3(motor_A,m1s);
         motor_speed3(motor_B,m2s);
         //Serial.println(m1s);
        rotation=random(1,5);
    } 
    else {
      if( (t-t2)<rhtm) {
           direct=random(0,2);
        switch(rotation) {

        case  1:
           m1s=1;
           m2s=0;
          break;

        case 2:
           m1s=-1;
           m2s=0;
          break;

        case  3:
           m1s=0;
          m2s=1;
          break;

        case 4:
           m1s=0;
          m2s=-1;
          break;
        }

        motor_speed3(motor_A,m1s);
        motor_speed3(motor_B,m2s);
       
        //Serial.println(m2s);
      }
      else {
        t1=t;
      }
    }
  }
}


void motor_speed3(boolean motor, float speed) {
  motor_speed2(motor,(char)(speed*100.0));
}

void motor_speed2(boolean motor, char speed) { //speed from -100 to 100
  byte PWMvalue=0;
  PWMvalue = map(abs(speed),0,100,40,255); //anything below 50 is very weak
  if (speed > 0)
    motor_speed(motor,0,PWMvalue);
  else if (speed < 0)
    motor_speed(motor,1,PWMvalue);
  else {
    motor_coast(motor);
  }
}
void motor_speed(boolean motor, boolean direction, byte speed) { //speed from 0 to 255
  if (motor == motor_A) {
    if (direction == 0) {
      digitalWrite(out_A_IN1,LOW);
      digitalWrite(out_A_IN2,HIGH);
    } 
    else {
      digitalWrite(out_A_IN1,HIGH);
      digitalWrite(out_A_IN2,LOW);
    }
    analogWrite(out_A_PWM,speed);
  } 
  else {
    if (direction == 0) {
      digitalWrite(out_B_IN1,HIGH);
      digitalWrite(out_B_IN2,LOW);
    } 
    else {
      digitalWrite(out_B_IN1,LOW);
      digitalWrite(out_B_IN2,HIGH);
    }
    analogWrite(out_B_PWM,speed);
  }
}
void motor_standby(boolean state) { //low power mode
  if (state == true)
    digitalWrite(out_STBY,LOW);
  else
    digitalWrite(out_STBY,HIGH);
}
void motor_brake(boolean motor) {
  if (motor == motor_A) {
    digitalWrite(out_A_IN1,HIGH);
    digitalWrite(out_A_IN2,HIGH);
  } 
  else {
    digitalWrite(out_B_IN1,HIGH);
    digitalWrite(out_B_IN2,HIGH);
  }
}
void motor_coast(boolean motor) {
  if (motor == motor_A) {
    digitalWrite(out_A_IN1,LOW);
    digitalWrite(out_A_IN2,LOW);
    digitalWrite(out_A_PWM,HIGH);
  } 
  else {
    digitalWrite(out_B_IN1,LOW);
    digitalWrite(out_B_IN2,LOW);
    digitalWrite(out_B_PWM,HIGH);
  }
} 

Parts:

(1x) Arduino Mini Pro 5V

(2x) Mini Metal Gearmotor 100:1

(1x) TB6612FNG - Motor Driver 1A Dual

(2x) Specially designed and ABS printed wheels

(1x) Ball Caster Plastic 3/8"

(1x) Metal Ball (for shake sensor)

(4x) 5mm screws + bolts (for shake sensor)

(1x) 7.2V Li-Po Battery

(1x) 5.5/2.1mm Jack Socket