<
>
back to all cubes

Knock Cube

Knock on this cube - it will knock back the same pattern

Technically based on "Secret-Knock-Detecting-Door-Lock"[http://www.instructables.com/id/Secret-Knock-Detecting-Door-Lock/], the knock cubes looks much too innocent to justify the trouble it caused. From the nights spent by Jonathan (and through Skype - Gil) searching for the right sensors and specific parameters to fit the mechanism into a plastic cube, to the amazingly individual behavior each cube had (in spite of replacing and cross-checking every electronic component). The final two cubes have different parameters programmed into each of them to compensate for their different behaviors, and even so, one of them is still more "jerky" and the other more "sleepy".

While working on other cubes, we used to let thos 2 "talk" to each other for hours, waiting to see what they'll talk would sound like in the end (the imperfections and limitations of the responses brought the output knocks to be a bit different from the input knocks each loop).

For spicing things up a bit, we introduced a small "easter egg" into the code: knocking the first five bits of "Shave and a haircut, two bits" will produce the lust 2 bits accordingly. Same goes for another 11-dut tune whose name we don't know.

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

  Knock cube by Yonatan Ward and Gil Adam
*/

//pin definitions
const int piezo = A1;         // Piezo sensor, ANALOG pin
const int solenoid = 2;      // Solenoid tapper, DIGITAL pin

//behaviour definitions
const int sensor_threshold = 490;    // minimum input from sensor considered as a tap
// knocker 1 - 600.     knocker2 - 1000
const int time_fade = 35;         // time for knock to fade before listening for next knock (ms)
const int time_end = 1000;            // time of silence after which the cube moves to playback phase (ms)
const int maximum_knocks = 256;     // maximum length of knock sequence
const int solenoid_delay = 38;     // time between pull and push of solenoid, like we did last time. not sure we need this, depends on the solenoid and how we want it to tap
const int solenoid_relax = 10;   // minimum time for delay betwin taps
const int filter_ratio = 2;       // low-pass filter ratio (1:x new sample to history)

const int tatta1[] = {
  315, 137, 171, 329};  // sequences of tatta
const int tatta2[] = {
  393, 381, 185, 204, 379, 184, 192, 186};
const int tattaSpeedTolMax = 200;          // faster or slower by these factors (%) acceptable
const int tattaSpeedTolMin = 30;
const int tattaPercentTol = 25;              // percent of mistake acceptable
const int tattaDelay1 = 3;
const int tattaDelay2 = 2;

//variables
int sensorBase;

int sequence[maximum_knocks];    // array for the knock sequence, as long as the maximum
int mode=0;                                          // mode of the cube, 0=wait, 1=listen, 2=play
int reading;
long filteredReading=0;
int knock_count;                 // Incrementer for the array and saves number of knocks

long tattaSpeedFactor1;  // percentage speed relative to sequence
long tattaSpeedFactor2;
byte TATTA;        // bitwise flags for possible tatta

void setup(){
  pinMode(solenoid, OUTPUT);     // define solenoid pin as output 
  Serial.begin (115200);
//  getSensorBase();
}

void loop(){
  wait();                        // wait for user to tap, ecourage him by tapping every now and then. ends when the user taps
  listen();                    // record the tapping sequence, ends when there is silence longer then defined in "time_end"
  play();                        // play back the tapping sequence
}

void getReading() {
  reading = analogRead(piezo);
//  filteredReading *= filter_ratio;
//  filteredReading += reading;
  filteredReading = reading; ///= (filter_ratio+1);
  delay(1);
}
/*
void getSensorBase() {
  delay(3000);
  long r=0;
  for (int i=0; i<1000; i++) {
    r+=analogRead(piezo);
  }
  sensorBase = r / 1000;
  Serial.print("sensorBase: ");
  Serial.println(sensorBase);
}*/

void wait(){
  while (filteredReading < sensor_threshold)   // while input from piezo is smaller then threshold do nothing
    getReading();
  Serial.print("!");
  filteredReading = 0;
  mode=1;                           // once there is a tap and the while loop is broken move to listen mode
}


void listen(){
  long time_start = millis();            // Reference for when this knock started
  long tattaCalc;
  TATTA = 3;
  for (knock_count=0;knock_counttime_end){                                                                // but if there is no tap in the time defined in "time_end" then move to playback mode
          mode=2;
      }
    }
      filteredReading = 0;
    //    if (mode==1){
    sequence[knock_count] = millis()-time_start;                                     // write the time between knocks to the array
    if (TATTA) {
      if (knock_count == 0) {
        tattaSpeedFactor1 = (long)sequence[0]*100/tatta1[0];
        if (tattaSpeedFactor1 > tattaSpeedTolMax || tattaSpeedFactor1 < tattaSpeedTolMin)
          TATTA &= 2;
        tattaSpeedFactor2 = (long)sequence[0]*100/tatta2[0];
        if (tattaSpeedFactor2 > tattaSpeedTolMax || tattaSpeedFactor2 < tattaSpeedTolMin)
          TATTA &= 1;
      } 
      else if ((knock_count < 4) && (TATTA & 1)) {
        tattaCalc = (long)tatta1[knock_count]*tattaSpeedFactor1/sequence[knock_count];
        if (abs(100 - tattaCalc) > tattaPercentTol)
          TATTA &= 2;
      } 
      else if (mode != 2) {
        TATTA &= 2;
      }
      if ((knock_count < 8) && (TATTA & 2)) {
        tattaCalc = (long)tatta2[knock_count]*tattaSpeedFactor2/sequence[knock_count];
        if (abs(100 - tattaCalc) > tattaPercentTol)
          TATTA &= 1;
      } 
      else if (mode != 2) {
        TATTA &= 1;
      }
    }
    time_start=millis();
    //   }                                                        // reset int for next knock  
  }
  if (knock_count < 3)
    TATTA = false;
}

void play(){
  Serial.print("playing: ");
  if (TATTA & 1) {
    Serial.println("TATTA 1! ");
    solenoid_tap();
    delay (tattaDelay1 * tattaSpeedFactor1);
    solenoid_tap();
  } 
  else if (TATTA & 2) {
    Serial.println("TATTA 2! ");
    solenoid_tap();
    delay (tattaDelay2 * tattaSpeedFactor2);
    solenoid_tap();
  } 
  else {
    for (int c=0; c < knock_count-1; c++){
      Serial.print(sequence[c]);
      Serial.print(", ");
    }
    Serial.println(".");

    solenoid_tap();
    for (int c=0; c < knock_count-1; c++){    // run through the array
      delay(max(solenoid_relax, sequence[c]- solenoid_delay));
      solenoid_tap();
    }
  }
  //  for (int c=0; c < maximum_knocks; c++){    // reset the array
  //    sequence[c]=0;
  //  }

  delay(100);
}


void solenoid_tap(){
  //the code that activates the solenoid, still don't know. i guess its best to use a LED for the start
  digitalWrite(solenoid, HIGH);
  delay(solenoid_delay);
  digitalWrite(solenoid, LOW);
  // !! we need to do this part with the least possible amount of delay so to not ruin the rythem
}


// how does the solenoid knock? is it always pulled and released to knock? is there an obnoxious pre-knock from the solenoid?
// how do we calibarate piezo threshold so it would ignore lifting, shaking or anything which isnt a tap?

Parts:

(1x) Arduino Mini Pro 5V

(1x) TIP112 - Darlington Transistor

(1x) Breakout Board for Electret Microphone

(1x) 12V Solenoid

(1x) 1N914 Fast Switching Diode

(1x) 10.8V Li-Po Battery

(1x) Audio Jack Socket