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(); }