an interactive neuron

At CMK 2014, after I had just about run out of ideas of what else i can do with fabric speakers, I found out that my friend Aaron and his group were working on another e-textiles project and trying to figure out how to use Arduino to control a bunch of NeoPixels and lights. And they graciously allowed me to crash the party.

Their project idea was awesome – build an interactive representation of a neuron that will sense when someone is near by and “fire” action potentials accordingly. They had already done a lot of the soldering and sewing work, so it was time to jump into the Arduino coding – yay, my favorite! One tricky part of this project was that they couldn’t find enough NeoPixels to cover the whole neuron, so ended up using a combination of NeoPixels and regular LEDs. Each regular LED was connected to a PWM output pin of the Arduino (so that we can control brightness). All the NeoPixels were wired in series and connected to a single Arduino pin – I do love that you only need one pin to control a whole bunch of NeoPixels!

After much initial head-scratching and whining about the difficult of working with the NeoPixels Arduino library and the not-exactly-intuitive documentation, we finally pulled together code that works! When the ultrasonic sensor does not sense anyone (or any object really) nearby, the lights all along the neuron will twinkle and randomly fade on/off. When the sensor senses someone close by, it will trigger a “firing” pattern, starting from the axon terminal, propagating up the axon, and spreading through to the dendrites. If the person/object isn’t close enough, the firing pattern only goes partway up the axon. You can see all three cases in the video above.

And because we love sharing, here’s our Arduino code:

#include <Adafruit_NeoPixel.h>

#define PIN 6
#define NUMPIXELS 25
#define NUMNEO 20
#define ARRAYSIZE 10

#define echoPin 7 // Echo Pin
#define trigPin 8 // Trigger Pin


// records whether a pixel is currently on or off... 
// first batch is neopixels and then normal LEDs
int state[NUMPIXELS]; 

int LEDpins[5] = { 
  3, 5, 9, 10, 11}; // pin numbers for the non-neopixel LEDs

// during explosion sequence, this is the order the lights should light up in
int sequence[NUMPIXELS] = {
  0,1,2,3,20,21,22,23,24,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};
int vel=100;

// array to hold light sensor readings so we can keep an average
int sensorReadings[10];
long counter = 0;

// for ultrasonic sensor 
int maximumRange = 150; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long duration, distance; // Duration used to calculate distance


// create neopixel strip object
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMNEO, PIN, NEO_GRB + NEO_KHZ800);

void setup() {

  // set up the averaging array 
  counter = 0;
  for (int i=0; i< ARRAYSIZE; i++)  {
    sensorReadings[i] = 0;
  }

  strip.begin();
  clear_all();

  // set up ultrasonic pins
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  Serial.begin(9600);
}



void loop() {

  // send pulses with ultrasonic sensor to detect distance
  digitalWrite(trigPin, LOW); 
  delayMicroseconds(2); 

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10); 

  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);

  //Calculate the distance (in cm) based on the speed of sound.
  distance = duration/58.2;
  if (distance >= maximumRange || distance <= minimumRange){
   // Serial.println("out of range == TWINKLE!!");
    twinkle();
    counter = 0;
  }
  else {
    int thisIndex =  (counter % ARRAYSIZE);
    sensorReadings[thisIndex] = distance;
    counter++;

    // now calculate the average distance object is at
    int sum = 0;
    for (int i=0; i<ARRAYSIZE; i++) {
      sum += sensorReadings[i];
    }
    float average = sum/ARRAYSIZE*1.0;
      
    if (counter > 10) { // don't do anything until we've read 10 readings
     // convert average distance to number of LEDs to light
     float y = -1.0/12*distance + 32.0/3.0;
     clear_all();
     
     if (y >= 9.0) {
      explosion(24);
     } else {
       explosion((int) y);
     }
    }
  }
}


void explosion(int stop_point){
  strip.setBrightness(120);
  for (int i = 0; i<stop_point; i++){
    int lightIndex = sequence[i];
    if(lightIndex < NUMNEO){
      if(lightIndex < 4){
        strip.setPixelColor(lightIndex,255,255,255);
      }
      else{
        int r = random(255);
        int g = random(255);
        int b = random(255);
        strip.setPixelColor(lightIndex,r,g,b);
      }
      strip.show();
      delay(vel);
    }
    else {
      digitalWrite(LEDpins[lightIndex-NUMNEO], HIGH); 
      delay(vel);
    }
  }
  delay(1000);

  for (int i = stop_point; i>=0; i--){
    int lightIndex = sequence[i];
    if(lightIndex < NUMNEO){
      strip.setPixelColor(lightIndex,0,0,0);
      strip.show();
      delay(vel);
    }
    else {
      digitalWrite(LEDpins[lightIndex-NUMNEO], LOW); 
      delay(vel);
    }
  }
}

// random twinkling effect
void twinkle(){
  int pickMe = random(NUMPIXELS);
  strip.setBrightness(200); 

  if(state[pickMe]==1) { //fadeout
    fade_down(pickMe);
    state[pickMe] = 0;

  }
  else { //fadein
    fade_up(pickMe);
    state[pickMe] = 1;
  }
}

// fade the lights in
void fade_up(uint16_t u) {
  // pick a random color for the neo-pixels
  int r = random(255);
  int g = random(255);
  int b = random(255);

  for (int k=0; k<= 100; k++) { 
    if (u < NUMNEO) {
      strip.setPixelColor(u,k*r/100,k*g/100,k*b/100);
      strip.show();
      delay(5);
    } 
    else{
      analogWrite(LEDpins[u-NUMNEO], k*50/100);  
      delay(5);  
    }
  }
}

// fade the lights out
void fade_down(uint16_t u) {
  // figure out what color a neopixel is, if it's a neopixel
  uint8_t r,g,b;
  if (u< NUMNEO) {
    uint32_t c = strip.getPixelColor(u);
    r = (uint8_t)(c >> 16),
    g = (uint8_t)(c >>  8),
    b = (uint8_t)c;
  }
  for (int k=100; k>= 0; k--){ 
    if (u < NUMNEO) {
      strip.setPixelColor(u,k*r/100,k*g/100,k*b/100);
      strip.show();
      delay(5);
    } 
    else {
      analogWrite(LEDpins[u-NUMNEO], k*50/100);  
      delay(5);  
    } 
  }
}

// turn off all the lights, neo-pixels and normal LEDs
void clear_all() {
  for(int i=0; i<NUMPIXELS; i++){ //initialize strip off
    state[i]=0;
    if (i >= NUMNEO) {
      pinMode(LEDpins[i-NUMNEO], OUTPUT);
      digitalWrite(LEDpins[i-NUMNEO], LOW);

    } 
    else{ 
      strip.setPixelColor(i,0,0,0); 
    }
  }
  strip.show(); 
}
Continue Reading

a lot of soldering and a LoL shield

After much patient soldering (made worse because of OCD nature of yours truly), the LoL shield is finally up and running! These assembly instructions were superb, especially the video on how to straighten all the LEDs for maximum OCD-ness.

It doesn’t last super long on a 9V battery (unsurprisingly) but it was enough to help me advertise the Valentine Day’s edition of Bourn Fridays. On top of that, I now kind of know what “charlieplexing” means!

Continue Reading