<
>
back to all cubes

Vibrating Cube

vibrating
            cube
Vibrate upon touch

This cube gave us hard times. It took 8 design iterations, including building it completely from scratch twice, and it still doesn't work as we would like it to.

The cube is based on Sparkfun's Capacitive Sensor Board, which is a relative capacitive sensor that (theoretically, at least) allows for measuring the changes of capacitance caused by bringing one's hands closer to "antennas" connected to it.

Basically, the measurement works, but something happens over time and the readings dramatically change, even when the cube is left alone. All of our attempts to solve this electrically and by software auto-calibration were in vain, and the cube sadly didn't get the privilege of being placed on the table for thousands of hands to play with...

On the bright side of things, it taught us a lot about wire-tracing and cube design in general, and it's very aesthetic...

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

  Vibrating Cube by Shachar Geiger
*/

#include <Wire.h>
#define AD7746_ADR 0x48

/*
Conversion times (in milliseconds):
 
 CONVT CAPF2 CAPF1 CAPF0 time
 0     0     0     0     11.0
 1     0     0     1     11.9
 2     0     1     0     20.0
 3     0     1     1     38.0
 4     1     0     0     62.0
 5     1     0     1     77.0
 6     1     1     0     92.0
 7     1     1     1     109.6
 */

#define CONVT 5          // conversion time

#define OFFSETA 115       // offset capacitance for sensor A
#define OFFSETB 114       // offset capacitance for sensor B

#define CAL_TIME 5000    // auto-calibration time - 5 seconds
#define CAL_THRESHOLD 20000 // going outside threshold (min to max) resets calibration time measurement
#define FILTER_RATIO 20 // ratio betwee memory and new reading in filter variable

#define TOUCH_THRESHOLD_A 50000 // delta capacitance for detecting touch on sensor A
#define TOUCH_THRESHOLD_B 50000 // delta capacitance for detecting touch on sensor B

#define MIN_TOUCH_TIME   1000
#define MIN_RELEASE_TIME 70
#define TOUCHED_TIMEOUT  60000 // timeout for calibrating sensors when one or both get stuck in "touched" input
#define FADE_IN_TIME 500
#define FADE_OUT_TIME 3500

#define MOTOR_PIN 3

// setting delay between measurements in milliseconds according to selected conversion time:
#if CONVT < 2
#define DELAY 12
#elif CONVT == 2
#define DELAY 21
#elif CONVT == 3
#define DELAY 39
#elif CONVT == 4
#define DELAY 63
#elif CONVT == 5
#define DELAY 78
#elif CONVT == 6
#define DELAY 93
#else
#define DELAY 110
#endif



// commands, registers and bit names for AD4476:
#define RESET_CMD 0xBF

#define STATUS_REG 0x00
#define RDY 2                  // 0 indicates a new unread data is available

#define CAP_DATA_REG 0x01

#define CAP_SETUP_REG 0x07
#define CAPEN 7            // enables capacitive reading
#define CIN2 6               // switches to second sensor
#define CAPDIFF 5      // enables differential mode on the selected sensor - set to 0.
#define CAPCHOP 0      // doubles the conversion time and slightly improves single to noise ratio

#define EXC_SETUP_REG 0x09  // capacitive excitation register
#define CLKCTRL 7           // decrease excitation signal frequency by factor of 2
#define EXCB 5              // enables EXCB pin as the excitation output
#define IEXCB 4             // enables EXCB pin as the inverted excitation output
#define EXCA 3              // enables EXCB pin as the excitation output
#define IEXCA 2             // enables EXCB pin as the inverted excitation output
#define EXCLVL1 1
#define EXCLVL0 0           // excitation voltage

#define CONFIGURATION_REG 0x0A
#define CAPF2 5
#define CAPF1 4
#define CAPF0 3         // capacitive channel digital filter setup
#define MD2 2
#define MD1 1
#define MD0 0              // converter mode: 0 - idle; 1 - continious; 2 - single; 3 - power-down 5 - offset calibration; 6 - gain calibration

#define CAP_DAC_A_REG 0x0B
#define DACAENA 7      // connect DAC A to positive input

#define CAP_DAC_B_REG 0x0B
#define DACBENB 7      // connect DAC B to negative input


boolean nextSensorA = true; // variable for altering the measurements between sensors
long lastMeasurementTime;
long sensorA, sensorB;

long nextCalibrationTime;
long nextTouchedCalibration;
long displayTime=0;
long sensorAMin, sensorAMax;
long sensorBMin, sensorBMax;
long sensorAZero, sensorBZero;
long sensorAFiltered, sensorBFiltered;
long motorPower;

boolean sensorAOn, sensorBOn;


// vasirables and constants for interaction statemachine:
#define IDLE            0
#define TOUCH_STARTED   1
#define PLAY_MOTOR   2
#define RELEASE_STARTED 3
#define FADE_OUT           4

long touchTime;     // time of user touching the cube
long releaseTime;   // time of user leaving the cube
long motorPlayTime; // time started to run the motor
int SMState = IDLE; // state machine current state

void generalSensorsSetup() {
  Wire.beginTransmission(AD7746_ADR);
  Wire.write(RESET_CMD);
  Wire.endTransmission();
  delayMicroseconds (200);

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_DAC_B_REG);
  Wire.write(0);  // set negative offset for both sensors to 0
  Wire.endTransmission();

  // getting sensor A ready for reading:

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(EXC_SETUP_REG);
  //Wire.send((1<<EXCA));  //  start excitation on sensor A
  Wire.write((1<<EXCA) | (1<<EXCLVL1) | (1<<EXCLVL0));  //  start excitation on sensor A and set voltage level to high
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_DAC_A_REG);
  Wire.write((1<<DACAENA) | OFFSETA);  // set offset for sensor A
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CONFIGURATION_REG);
  Wire.write((1<<MD0) | CONVT<<3);  // set measurement to continues. set measurerment time
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_SETUP_REG);
  Wire.write(1<<CAPEN); // start measurement on capacitors
  Wire.endTransmission();
}

unsigned long readSensorA() {

  Wire.beginTransmission(AD7746_ADR); // begin read cycle
  Wire.write(STATUS_REG); //pointer to first cap data register
  Wire.endTransmission(); // end cycle

  Wire.requestFrom(AD7746_ADR,4); // reads 3 bytes
  char data[4];
  int i = 0;
  while(Wire.available()) {
    data[i] = Wire.read();
    i++;
  }

  // getting sensor B ready for reading:

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(EXC_SETUP_REG);
  //Wire.send((1<<EXCB));  //  start excitation on sensor A
  Wire.write((1<<EXCB) | (1<<EXCLVL1) | (1<<EXCLVL0));  //  start excitation on sensor A and set voltage level to high
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_DAC_A_REG);
  Wire.write((1<<DACAENA) | OFFSETB);  // set offset for sensor B
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CONFIGURATION_REG);
  Wire.write((1<<MD0) | CONVT<<3);  // set measurement to continues. set measurerment time
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_SETUP_REG);
  Wire.write((1<<CIN2) | (1<<CAPEN)); // start measurement on capacitors
  Wire.endTransmission();

  return (((((long)data[1]<<16)+((long)data[2]<<8)+(long)data[3])-0x800000));
}


long readSensorB() {

  Wire.beginTransmission(AD7746_ADR); // begin read cycle
  Wire.write(STATUS_REG); //pointer to first cap data register
  Wire.endTransmission(); // end cycle

  Wire.requestFrom(AD7746_ADR,4); // reads 3 bytes
  char data[4];
  int i = 0;
  while(Wire.available()) {
    data[i] = Wire.read();
    i++;
  }

  // getting sensor A ready for reading:

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(EXC_SETUP_REG);
  //Wire.send((1<<EXCA));  //  start excitation on sensor A
  Wire.write((1<<EXCA) | (1<<EXCLVL1) | (1<<EXCLVL0));  //  start excitation on sensor A and set voltage level to high
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_DAC_A_REG);
  Wire.write((1<<DACAENA) | OFFSETA);  // set offset for sensor A
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CONFIGURATION_REG);
  Wire.write((1<<MD0) | CONVT<<3);  // set measurement to continues. set speed to moderate - 38ms (possible to change later to 11ms)
  Wire.endTransmission();

  Wire.beginTransmission(AD7746_ADR);
  Wire.write(CAP_SETUP_REG);
  Wire.write(1<<CAPEN); // start measurement on capacitors
  Wire.endTransmission();

  return (((((long)data[1]<<16)+((long)data[2]<<8)+(long)data[3])-0x800000));
}

void calibrateSensors() {
  Serial.println ("Calibration!!!");
  sensorAZero = sensorAFiltered;
  sensorAMin = sensorAFiltered;
  sensorAMax = sensorAFiltered;
  sensorBZero = sensorBFiltered;
  sensorBMin = sensorBFiltered;
  sensorBMax = sensorBFiltered;
  sensorAFiltered = sensorA;
  sensorBFiltered = sensorB;
  nextCalibrationTime = millis() + CAL_TIME;
  nextTouchedCalibration = millis() + TOUCHED_TIMEOUT;
}

void resetCalibration() {
  //  Serial.println ("---------------------------------------------");
  sensorAMin = sensorA;
  sensorAMax = sensorA;
  sensorBMin = sensorB;
  sensorBMax = sensorB;
  sensorAFiltered = sensorA;
  sensorBFiltered = sensorB;
  nextCalibrationTime = millis() + CAL_TIME;
  nextTouchedCalibration = millis() + TOUCHED_TIMEOUT;
}


void setup() {
  Wire.begin();
  generalSensorsSetup();
  delay(DELAY);
  sensorA = readSensorA();
  delay(DELAY);
  sensorB = readSensorB();
  sensorAZero = sensorA;
  sensorAMin = sensorA;
  sensorAMax = sensorA;
  sensorAFiltered = sensorA;
  sensorBZero = sensorB;
  sensorBMin = sensorB;
  sensorBMax = sensorB;
  sensorBFiltered = sensorB;
  nextCalibrationTime = millis() + CAL_TIME/5;
  lastMeasurementTime = millis();
  pinMode (MOTOR_PIN, OUTPUT);
  pinMode (13, OUTPUT);
  Serial.begin (115200);
}

void loop() {
  // take measurments according to timing:
  if (millis() - lastMeasurementTime > DELAY) {
    if (nextSensorA) {
      sensorA = readSensorA();
      sensorAFiltered = (sensorAFiltered * (FILTER_RATIO - 1) + sensorA) / FILTER_RATIO;
      sensorAMin = min (sensorAMin, sensorA);
      sensorAMax = max (sensorAMax, sensorA);
      if (sensorAMax - sensorAMin > CAL_THRESHOLD)
        resetCalibration();
    } 
    else {
      sensorB = readSensorB();
      sensorBFiltered = (sensorBFiltered * (FILTER_RATIO - 1) + sensorB) / FILTER_RATIO;
      sensorBMin = min (sensorBMin, sensorB);
      sensorBMax = max (sensorBMax, sensorB);
      if (sensorBMax - sensorBMin > CAL_THRESHOLD)
        resetCalibration();
    }
    nextSensorA = !nextSensorA;
    lastMeasurementTime = millis();
    if (sensorAZero-sensorA> TOUCH_THRESHOLD_A) sensorAOn = true;
    else sensorAOn = false;
    if (sensorBZero-sensorB> TOUCH_THRESHOLD_B) sensorBOn = true;
    else sensorBOn = false;
  }

  // calibrate sensors according to timing:
  if (millis() > nextCalibrationTime) {
    if (!sensorAOn && !sensorBOn) calibrateSensors();
    else if (millis() > nextTouchedCalibration) calibrateSensors;
    else resetCalibration();
  }

  // display sensors data every 100msecs:
  if (millis() - displayTime > 100) {
    displayTime = millis();
    Serial.print (sensorA);
    Serial.print ('\t');
    Serial.print (sensorA - sensorAZero);
    Serial.print ('\t');
    Serial.print (sensorB);
    Serial.print ('\t');
    Serial.println (sensorB - sensorBZero);
  }

  // state machine for user input and vibrator output:
  switch (SMState) {
  case IDLE:
    if (sensorAOn && sensorBOn) {
      touchTime = millis();
      SMState = TOUCH_STARTED;
      Serial.println ("Touch Started");
    }
    break;

  case TOUCH_STARTED:
    if (!sensorAOn || !sensorBOn) {
      Serial.println ("IDLE");
      SMState = IDLE;
    }
    else if (millis() - touchTime > MIN_TOUCH_TIME) {
      SMState = PLAY_MOTOR;
      Serial.println ("Playing");
      touchTime = millis();
    }
    break;

  case PLAY_MOTOR:
    motorPower = (millis() - touchTime)*256/FADE_IN_TIME;
    if (motorPower > 255) digitalWrite (MOTOR_PIN, HIGH);
    else analogWrite (MOTOR_PIN, motorPower);
    if (!sensorAOn || !sensorBOn) {
      releaseTime = millis();
      SMState = RELEASE_STARTED;
      Serial.println ("Released");
    }
    break;

  case RELEASE_STARTED:
    if (sensorAOn && sensorBOn) {
      SMState =PLAY_MOTOR;
      Serial.println ("Playing");
    }
    else if (millis() - releaseTime > MIN_RELEASE_TIME) {
      Serial.println ("Fading");
      SMState = FADE_OUT;
    }
    break;

  case FADE_OUT:
    motorPower = ((FADE_OUT_TIME - (millis() - releaseTime))*256)/FADE_OUT_TIME;
    if (motorPower <= 2 || motorPower > 255) {
      digitalWrite (MOTOR_PIN, LOW);
      SMState = IDLE;
    } 
    else {
      analogWrite (MOTOR_PIN, motorPower);
    }
    if (sensorAOn && sensorBOn) {
      touchTime = millis();
      SMState = PLAY_MOTOR;
      Serial.println ("Resuming Play");
    }
    break;
  }
}

Parts:

(1x) Arduino Mini Pro 3.3V

(1x) Capacitive Sensing Module

(1x) DC Vibration Motor Take from Massage Chair

(1x) TIP112 - Darlington Transistor

(1x) 1N914 Fast Switching Diode

(1x) 10.8V Li-Po Battery

(1x) Audio Jack Socket