ping stick

the ping stick demonstrates some ideas implicit in embodied cognition, the philosophical idea that, in essence, what we call 'mind' is deeply and irretrievably embedded in, a part of, the body. it's a really interesting subject and a useful way to think about thinking and the body and interface (computer or otherwise) . the wikipedia page on embodied cognition is a decent introduction.

the ping stick couples your sense of touch to your mind's "visual" comprehension of space. put simply, you can feel the physical environment around you with your fingertip, and walk around a room or other space with your eyes closed. with essentially zero training, just holding the thing in one hand and gently waving it in front of you as you walk, you feel/sense/see walls, doorways, furniture, etc around you in a way that immediately makes sense. practice brings much subtlety to its use.

the ping torch is directly inspired by Adam Spiers' haptic torch (University of Reading, UK, 2004), encountered in Professor Simon Penny's reader for his Embodied Cognition class, University of California, Irvine, 2009. mine appears to be simpler, and much smaller (the inexorable stampede of technology).

usage is very simple; you hold it in one hand and point it like a flashlight in the direction you are walking, more or less gently swinging it back and forth in front of you, your finger resting on a small round button on the bottom. the sole purpose of the ping stick is to vibrate the small touch-button with an intensity proportional to distance to an object such as a wall or a chair, with things closer vibrating more intensely.

the vibrating button is the sole output; no pixels, LEDs or sounds. there is a power switch (which i would like to eliminate) and a sonar sensor in front, and no adjustments of any kind. software within the device translates sonar range (distance) data from the sonar device, into vibration intensity in the tiny motor. technical details are below.

the result is a fairly immediate mapping of what would seem to be utterly unrelated cognitive events; arbitrary sensation in a fingertip, and visualization of space. this is apparently due to the spectacular redundancy and innate ability of the body/mind to analogize sensory data, analogous (to me) as a form of useful synaesthesia. (it is very much like the instantaneous bodily orientation one gets by suddenly touching a wall after wandering around in a pitch-dark room; one swipe of a hand across the wall tells you much about the space that persists for some time after you walk away from the wall).

here's a brief (8 second) movie of it swept past a wall edge. this awkward video was taken with a remote microphone held onto the vibrating button so that it can be heard in the video as a mild buzzing. Note the edge-emphasis as it sweeps past the stepladder leaning on the building.

technical details

the ping torch consists of a Sparkfun Arduino Mini Pro, an EZ-1 sonar sensor (see note below), a vibrating motor, power supply and support glue, and housed in a thin wall aluminum tube. the software is almost trivially simple: sonar range is taken continuously (as the power button is held down), at the EZ1's maximum rate of about 20 measurements/second, and that range (in inches) is translated to an arbitrary "intensity" value. some heuristics are applied: the mapping is logarithmic, not linear; things closer are more "interesting" (collision avoidance, etc). objects beyond 80 inches are intentionally ignored; "feeling" things beyond that became confusing (sounds quite arbitrary here in type, but experienced quite obvious). to somewhat help overcome response time limitations inherent in the sensor, edge emphasis is done when there is an abrupt change in distance, eg. sweeping the sensor past a doorway, or an object projecting from the wall. emphasis is done by differentiating the range and adding this into the steadystate intensity. edges feel more edgy.

here's the code.


 Ping Torch
 Tom Jennings 
 27 nov 2012   stripped out IR code (forked a copy with IR left in) as IR reflectivity
               is too unreliable. Put reaction code into ISR so that it's run as soon as
               the sonar PW edge falls, for best-case response time. Tweaked emphasis.
 15 nov 2012   since IR reflectivity is spotty, added a "hold" feature ("retriggerable
               one shot") to hold output for a time so that brief reflections
               get extended. juggled logic. sonar to IR crossover not that great
               around USEIR, but seems OK. decreased timers all around.
 13 nov 2012   changed EZ-1 over to interrupt-driven (from pulseIn()). Tweaked
               distance threshholds, added timer so that it updates intensity
               (and looks at ir, when appropriate) every 10mS.
 08 nov 2012   tweaked up and working.
 07 nov 2012   switched from unusable Pro Micro to the non-USB version; couldn't
               get the driver to work. added code for Sharp sensor.
 04 nov 2012   Moved to Sparkfun Arduino Pro Micro, added Sharp IR sensor.
 18 Oct 12     Removed integration; i assumed (wrongly) it was needed and all it
               did was of course slow response, and fast response is more
               important than noise. 
               Restored edge detection.
 17 Oct 2012   Disabled edge amplification.
 16 Oct 2012   New.
 inspired by the Haptic Torch project of the Interactive Systems Group  of the 
 University of Reading,, by Adam Spiers
 This one uses a vibratory motor as effector, arranged to be under the tip of
 your pointing finger. The power switch is under the thumb. There are no other controls.
 The entire thing is housed in a section of tubing with the sensor looking out the front.
 Uses a MaxSonar EZ-1 ultrasonic range finder. The EZ-1 takes a measurement every 
 ~49mS by default. Vibration intensity is inversely proportional to distance; close
 objects vibrate most intensely. Objects beyond MAXDIST inches generate no
 Additionally, abrupt changes in distance (eg. the edges of objects) are emphasized by
 momentarily increasing intensity when seeing an edge. 

// need log() from this.

// Tunable parameters.
const int DEBUGTIME = 333;    // how often we print distance gunk
const int EDGEEMPH = 4;       // exaggerates the effect of distance step changes (1==none)
const int EDGETHRESH = 4;     // how abrupt a change we notice
const int MINDIST = 30;       // anything below this is "close" (maximum intensity)
const int MAXDIST = 80;       // anything beyond this (in inches) is un-seen

// These are ad hoc characteristics of this vibrating motor; below Min it's undetectable,
// and not too much difference between Max and actual maximum (AWFS) which we'll reserve 
// for edge detection emphasis.
const int BUZZMIN = 50;
const int BUZZMAX = 180;

const int AWFS = 256;         // analogWrite() full scale value (0..255)

// Arduino hardware pin assignments.
const int EZPW = 3;           // EZ-1 PW output (interrupt 1)
const int EZPWR = 9;          // lol, ran out of physical pins for 5V; EZ-1 draws 3mA!
const int BUZZER = 6;         // vibrating motor driver pin

struct _diff {
  int *a;                      // history (of differences)
  unsigned z;                  // size
  unsigned i;
  long int sum;
  unsigned recent;             // most-recent
} D;

// EZ-1 sonar pinger stuff.
volatile unsigned long ezT;    // uS timer
volatile int distance;         // most recent measurement
volatile int intensity;        // most recent vibration intensity

void setup (void) {

  analogWrite (BUZZER, 0);     // shut up!
  pinMode (EZPWR, OUTPUT);     // (ran out of holes on the board)
  digitalWrite (EZPWR, 1);     // EZ-1 draws only 3mA!

  Serial.begin (9600);
  newDiff (&D, 2);             // set up the differentiator

  attachInterrupt (1, EZ1PW, CHANGE);

/* The EZ-1 "free runs", taking a measurement every 50mS or so, and generates a
pulse whose width is proportional to distance; 147uS/inch. The ISR (EZ1PW()) 
handles that. */

void loop (void) { 
  static unsigned long T;       // loop response timer
  static unsigned long dbT;     // debug output timer

// Just print crap out to the F232 adapter, 3 times/sec.

  if (millis() > dbT && intensity) {
   dbT= millis() + DEBUGTIME;
   Serial.print (distance, DEC); 
   Serial.print ("\", buzz ");
   Serial.print (intensity, DEC);

/* The EZ-1 generates a positive-going pulse whose width is 147uS/inch. This ISR
is invoked on each edge of the pulse. Start the timer on the rising edge, 
stop it and calculate integer distance (inches) on the falling edge. */

void EZ1PW () {
int edge;                     // exaggerates response at the edge of objects

  if (digitalRead (EZPW)) {                              // rising edge
    ezT= micros();                                       // start the timer
  } else {
    intensity= 0;
    distance= (micros() - ezT + (147/2)) / 147;          // falling edge
// this better take less than 47 mS!
    if (distance < MAXDIST) {

// Map linear distance onto a logarithmic vibration-intensity scale; things 
// closer are more intense.
//                invert range            logarithmic scaling (0..1)          vibration range
      intensity= BUZZMAX - (log10 ((float)distance * (10.0/(float)MAXDIST)) * (BUZZMAX - BUZZMIN));

// Add emphasis at abrupt edges.
      edge= diff (&D, distance);                          // this is non-zero at edges,
      if (abs (edge) > EDGETHRESH) intensity *= EDGEEMPH; // emphasize edges,
      intensity= min (BUZZMAX, max (intensity, BUZZMIN)); // bound it,

    analogWrite (BUZZER, intensity);                      // update buzzer intensity

 * Compute the sum of differences in a stream of input values.
 * d= diff (v);

void newDiff (struct _diff *d, int z) {
  d-> z= z;
  d-> sum= 0L;
  d-> i= d-> recent= 0;
  d-> a= (int *) malloc (z * sizeof(int));
  for (int i= 0; i < z; i++) d->a[i]= 0;

int diff (struct _diff *d, int v) {

  int n= v - d-> recent;           // new diff is current - previous
  d-> recent= v;                   // current becomes next previous

  d-> sum -= d-> a[d-> i];         // subtract oldest (diff) from sum
  d-> a[d-> i]= n;                 // replace oldest difference with newest (shift register)

  ++d-> i %= d-> z;                // advance index

  return(d-> sum += n);            // add newest diff to sum, return it.