Dream Walk
An interactive snowflake installation responds to movement and gesture with color and light.
These suspended snowflakes use sensors and a microcontroller to analyse your motion in real time to render animated pixels of light.
Event
Vanier Snow Party, February 15, 7pm at 84-88 Beechwood Avenue. This neighborhood is one of my old haunts.
Walk up to a station to make your own snowflakes from paper to take with you. Ever hear of a throwie? It’s a little light. Make a snowflake with a lanyard, add the throwie and presto! A Snowie!
Artist
I’m a multidisciplinary artist with a background in software and physics. I explore interactive and emersive art and systems highlighting features of our own social network and being.
Credits: Maryam AmiriNezhad is a computer scientist. Shirley Lee, snow flake builder. Andrew B snow flake installer. Max from FrozenElectronics. Eric Field, wiring technician. Sarah Simpkin is a librarian and advocate of the Maker Movement.
Snowflake Party
Fabrication
Here are shots from the very un-glamorous electronics fabrication.
A few shots from the snow flake production.
Related
Instructables snowflakes.
This project uses an Arduino microcontroller.
The light system is Red, Green and Blue LEDs. It uses “additive color mixing“. This project uses type 2811 LED controllers.
Code
/* Dream Walk Darcy@inventorArtist.com Quick and dirty code for one-time run. WS2811 LED Strip ================ // Parameter 1 = number of pixels in strip // Parameter 2 = Arduino pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input // and minimize distance between Arduino and first pixel. Avoid connecting // on a live circuit...if you must, connect GND first. IR Receiver SR02 Range Finder 16752735 16720095 16736415 16769055 16748655 16716015 16732335 16764975 16756815 16724175 16740495 16773135 16754775 16722135 16738455 16771095 16750695 16718055 16734375 16767015 16746615 16713975 16730295 16762935 repeat 4294967295 */ #include <Adafruit_NeoPixel.h> //Light Strip #include <IRremote.h> // Infra Red Receiver #define stripPIN 6 #include <Wire.h> // Range Finder #define Debug // Discrete Hartley transform //#define LOG_OUT 1 // use the log output function //#define FHT_N 256 // set to 256 point fht //#include <FHT.h> int motionN = 20; int motion[21]; unsigned long motionLast; unsigned int motionDelay= 400; int motionIndex=0; unsigned long calcLast; unsigned int calcDelay = 1200; unsigned int statAverage; unsigned int statTotal; unsigned int statSlope; unsigned int statMax; unsigned int statMin; //person unsigned int personFlag; unsigned int person; unsigned int lastHuman; // initialize light strip Adafruit_NeoPixel strip = Adafruit_NeoPixel(40, stripPIN, NEO_RGB + NEO_KHZ800); //Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, stripPIN, NEO_GRB + NEO_KHZ800); // range finder #define SDM_IO_TIMEOUT 10000 int TrigPin = 13; int EchoPin = 12; unsigned long ultrasoundDuration; int timeout; unsigned long tStartPing = 0; int d = 0; //range finder distance int dLast = 0; int dDir = 1; //inbound ir IRrecv irrecv(8); decode_results results; //frames unsigned long lastFrame; unsigned int FrameDelay = 900; unsigned int frame = 0; unsigned int frameN = 39; //ultrasonic range unsigned int rangeLow = 3; unsigned int rangeHi = 200; unsigned int rangeError = 999; //strip size unsigned int stripN = 38; float stripR[39]; float stripG[39]; float stripB[39]; //heart unsigned int heart = 0; unsigned int heartDir = 1; unsigned int metabolism=500; //sprite float sprite = 0; float spriteDir = .1; //random event unsigned long randomLast; unsigned int randomDelay = 10000; unsigned int effectRandom=0; void setup() { //debug Serial.begin(9600); //115200 // Discrete Hartley transform //TIMSK0 = 0; // turn off timer0 for lower jitter //ADCSRA = 0xe5; // set the adc to free running mode //ADMUX = 0x40; // use adc0 //DIDR0 = 0x01; // turn off the digital input for adc0 //light strip strip.begin(); strip.show(); // Initialize all pixels to 'off' for (int i = 0;i<=stripN;i++) { stripR[i] = 0; stripG[i] = 0; stripB[i] = 0; } rainbow(1); // for (int i = 0;i<=stripN;i++){ // strip.setPixelColor(i,strip.Color(30 ,0,100)); // strip.setPixelColor(max(i-1, 0), strip.Color(0,0,0)); // strip.show(); // delay(200); // } //theaterChaseRainbow(1); for (int i = 0;i<=stripN;i++) { strip.setPixelColor(i,strip.Color(0,0,0)); }strip.show(); //range finder Wire.begin(); //setup i2c //pinMode(TrigPin, OUTPUT); //pin is output //pinMode(EchoPin, INPUT); // pin is now input // Infrarad Reciept pinMode(7, INPUT); digitalWrite(7, HIGH); irrecv.enableIRIn(); // Start receiver //randomSeed(12); } void loop() { // range finder if (millis() >= motionLast + motionDelay) { motionLast = millis(); d = takereading(); //d = read_sdm_io_range(); //Serial.print(d); Serial.println(""); dLast = d; motion[motionIndex] = d; if (d>dLast){ dDir = 1; }else{ dDir = -1; } //fht_input[motionIndex] = d; // Discrete Hartley transform motionIndex++;if (motionIndex >=motionN) {motionIndex = 0;} //Serial.print("m=");Serial.println(motionIndex); // last human if (d < rangeHi) { lastHuman = millis(); } //for (int i=0;i<=motionN;i++){Serial.print(motion[i]);Serial.print(" ");} Serial.println(""); } // Discrete Hartley transform if (millis() >= calcLast + calcDelay) { calcLast = millis(); statTotal=0; statMax=0; statMin=99999; for (int i=0;i<=motionN;i++){ statTotal = statTotal + motion[i]; statMax = max(motion[i], statMax); statMin = min(motion[i], statMin); } statAverage = statTotal/(motionN-1); Serial.println(""); Serial.print("total "); Serial.print(statTotal); Serial.print(" average "); Serial.print(statAverage); Serial.print(" max "); Serial.print(statMax); Serial.print(" min "); Serial.println(statMin); for (int i=0;i<=motionN;i++){Serial.print(motion[i]);Serial.print(" ");} Serial.println(""); } // Discrete Hartley transform //while(1) { // reduces jitter // cli(); // UDRE interrupt slows this way down on arduino1.0 // for (int i = 0 ; i < FHT_N ; i++) { // save 256 samples // while(!(ADCSRA & 0x10)); // wait for adc to be ready // ADCSRA = 0xf5; // restart adc // byte m = ADCL; // fetch adc data // byte j = ADCH; // int k = (j << 8) | m; // form into an int // k -= 0x0200; // form into a signed int // k <<= 6; // form into a 16b signed int // fht_input[i] = k; // put real data into bins // } // fht_window(); // window the data for better frequency response // fht_reorder(); // reorder the data before doing the fht // fht_run(); // process the data in the fht // fht_mag_log(); // take the output of the fht // sei(); // Serial.write(255); // send a start byte // Serial.write(fht_log_out, FHT_N/2); // send out the data // } //random events if (millis() >= randomLast + randomDelay) { randomLast = millis(); randomDelay = random(20000); effectRandom++; if (effectRandom>=6){effectRandom=0;} Serial.print("random/////////////////////// " );Serial.println(effectRandom); switch (effectRandom) { case 0: colorWipe(strip.Color(255, 255, 0), 0); break; // case 1: colorWipe(strip.Color(0, 255, 255), 0); break; // case 2: colorWipe(strip.Color(255, 0, 255), 0); break; // case 3: rainbow(0);break; case 4: rainbowCycle(0);rainbowCycle(0);rainbowCycle(0);break; case 5: theaterChaseRainbow(5);break; //default: {renderNumber(0, 255,255,255, 255,255,255); break;} } } // Look for IR data unsigned long remoteData; if (irrecv.decode(&results)) { remoteData = results.value; Serial.print("IR in " );Serial.println(remoteData); irrecv.resume(); //we got data, cook a random function int effect = random(5); switch (effect) { case 0: theaterChaseRainbow(5); break; //R case 1: colorWipe(strip.Color(0, 255, 0), 10); break; //G case 2: colorWipe(strip.Color(0, 0, 255), 10); break; //B case 3: rainbow(0);break; case 4: rainbowCycle(0);break; case 5: theaterChaseRainbow(5);break; //default: {renderNumber(0, 255,255,255, 255,255,255); break;} } } // switch (remoteData) { // case 16748655: colorWipe(strip.Color(255, 0, 0), 10); break; //R // case 16716015: colorWipe(strip.Color(0, 255, 0), 10); break; //G // case 16732335: colorWipe(strip.Color(0, 0, 255), 10); break; //B // // //default: {renderNumber(0, 255,255,255, 255,255,255); break;} // } // advance frames if (millis() >= lastFrame + FrameDelay ) {//advance frame lastFrame = millis(); frame++; //probably not needed by this vignette if (frame > frameN) { frame = 0; } //Serial.print("frame ");Serial.println(frame); //react to range finder for (int i = 0;i<=stripN;i++){strip.setPixelColor(i,strip.Color(0,0,0));} //clear strip Serial.print(" statAverage ");Serial.print(statAverage); Serial.print(" rangeLow ");Serial.print(rangeLow); Serial.print(" rangeHi ");Serial.print(rangeHi); Serial.print(" stripN ");Serial.print(stripN); person = min(map(d, rangeLow, rangeHi, 0, stripN), stripN); //person = map(statAverage, 0, rangeHi, 0, stripN); Serial.print("d/person ");Serial.print(d);Serial.print(" ");Serial.println(person); //Boost the pixel that the person maps to stripR[person] = stripR[person] + 10; if (stripR[person] >=255) {stripR[person] = 255;} stripG[person] = stripG[person] + 0 ; if (stripG[person] >=255) {stripG[person] = 255;} stripB[person] = stripB[person] + 10; if (stripB[person] >=255) {stripB[person] = 255;} for (int j=1;j<=5;j++){ int brightness; brightness = map(j, 0, 5, 20, 0); stripR[person+j] = stripR[person+j] + brightness; if (stripR[person+j] >=255) {stripR[person+j] = 0;} stripG[person+j] = stripG[person+j] + brightness; if (stripG[person+j] >=255) {stripG[person+j] = 0;} stripB[person+j] = stripB[person+j] + brightness; if (stripB[person+j] >=255) {stripB[person+j] = 0;} stripR[person-j] = stripR[person-j] + brightness; if (stripR[person-j] >=255) {stripR[person-j] = 0;} stripG[person-j] = stripG[person-j] + brightness; if (stripG[person-j] >=255) {stripG[person-j] = 0;} stripB[person-j] = stripB[person-j] + brightness; if (stripB[person-j] >=255) {stripB[person-j] = 0;} } //fade //stripR[person] = stripR[person] - 5; if (stripR[person] <=0) {stripR[person] = 0;} //stripG[person]=stripG[person] -5; if (stripG[person] <=0) {stripG[person] = 0;} //stripB[person]=stripG[person] - 5; if (stripG[person] <=0) {stripG[person] = 0;} //strip.setPixelColor(person,strip.Color(random(255),random(255),random(255))); //if (d >= hi) { // heart=heart + heartDir; // if(heart>=stripN || heart<=0) {heartDir = -1 * heartDir;} // for (int i = 0;i<=stripN;i++){strip.setPixelColor(i,strip.Color(0,0,0));} // strip.setPixelColor(heart,strip.Color(0,0,random(8))); // //strip.show(); //} } //fade for (int i = 0;i<=stripN;i++){ stripR[i] = stripR[i] - .05; if (stripR[i] <=0) {stripR[i] = 0;} stripG[i] = stripG[i] - .05; if (stripG[i] <=0) {stripG[i] = 0;} stripB[i] = stripB[i] - .05; if (stripB[i] <=0) {stripB[i] = 0;} //strip.setPixelColor(i,strip.Color(stripR[i] ,stripG[i],stripB[i])); } float val = (exp(sin(millis()/(float)metabolism*PI)) - 0.36787944)*13; //setColor (6,1023, val); stripR[0] = val; stripG[0]= 0; stripB[0] =0; for (int i = 0;i<=stripN;i++){ strip.setPixelColor(i,strip.Color(stripR[i] ,stripG[i],stripB[i])); } strip.show(); //sprite // sprite = sprite + spriteDir; // //Serial.print(sprite); // //Serial.println(""); // if (sprite> stripN) { sprite = 0;} // if (sprite<0) {sprite = stripN;} // strip.setPixelColor(sprite,strip.Color(200,0,200)); // for (int j=1;j<=10;j++){ // int brightness; // brightness = map(j, 1, 10, 10, 1); // strip.setPixelColor(min(sprite+j, stripN),strip.Color(brightness,0,brightness)); // strip.setPixelColor(max(sprite-j, 0),strip.Color(brightness,0,brightness)); // } //strip.setPixelColor(min(sprite+1, stripN),strip.Color(20,0,20)); //strip.setPixelColor(max(sprite-1, 0),strip.Color(20,0,20)); // for (int i = 0;i<=stripN;i++){ // stripR[i] = stripR[i] - .05; if (stripR[i] <=0) {stripR[i] = 0;} // stripG[i] = stripG[i] - .05; if (stripG[i] <=0) {stripG[i] = 0;} // stripB[i] = stripB[i] - .05; if (stripB[i] <=0) {stripB[i] = 0;} // strip.setPixelColor(i,strip.Color(stripR[i] ,stripG[i],stripB[i])); // } //colorWipe(strip.Color(255, 0, 0), 10); // Green // strip.setPixelColor(5,strip.Color(255,255,255)); //strip.show(); // // theaterChase(strip.Color(127, 127, 127), 50); // White // theaterChase(strip.Color(127, 0, 0), 50); // Red // theaterChase(strip.Color( 0, 0, 127), 50); // Blue // // rainbow(20); // delay(3000); //rainbowCycle(0); // theaterChaseRainbow(5); } // Fill the dots one after the other with a color void colorWipe(uint32_t c, uint8_t wait) { for(uint16_t i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } void rainbow(uint8_t wait) { uint16_t i, j; for(j=0; j<256; j++) { for(i=0; i<strip.numPixels(); i++) { strip.setPixelColor(i, Wheel((i+j) & 255)); } strip.show(); delay(wait); } } // Slightly different, this makes the rainbow equally distributed throughout void rainbowCycle(uint8_t wait) { uint16_t i, j; for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel for(i=0; i< strip.numPixels(); i++) { strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255)); } strip.show(); delay(wait); } } //Theatre-style crawling lights. void theaterChase(uint32_t c, uint8_t wait) { for (int j=0; j<10; j++) { //do 10 cycles of chasing for (int q=0; q < 3; q++) { for (int i=0; i < strip.numPixels(); i=i+3) { strip.setPixelColor(i+q, c); //turn every third pixel on } strip.show(); delay(wait); for (int i=0; i < strip.numPixels(); i=i+3) { strip.setPixelColor(i+q, 0); //turn every third pixel off } } } } //Theatre-style crawling lights with rainbow effect void theaterChaseRainbow(uint8_t wait) { for (int j=0; j < 256; j++) { // cycle all 256 colors in the wheel for (int q=0; q < 3; q++) { for (int i=0; i < strip.numPixels(); i=i+3) { strip.setPixelColor(i+q, Wheel( (i+j) % 255)); //turn every third pixel on } strip.show(); delay(wait); for (int i=0; i < strip.numPixels(); i=i+3) { strip.setPixelColor(i+q, 0); //turn every third pixel off } } } } // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) { if(WheelPos < 85) { return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); } else if(WheelPos < 170) { WheelPos -= 85; return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); } else { WheelPos -= 170; return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); } } // range finder // Make sure you add the above line to your code -- #include <wire.h> // //The line in the setup() routine needs to be added to the setup() in your program. So add Wire.begin(); into your setup(); // Then copy and paste the whole takereading() function into your code\ // to get a reading, simply take the variable you want the reading to be assigned to, and write something like // sensorReading = takereading(); // this will make sensorReading equal to whatever the sensor returns. Then this variable can be used // in any way you want to calculate the light patterns, etc. // #include <Wire.h> int takereading() { //Serial.println("readin1"); int reading = 0; Wire.beginTransmission(112);//Serial.println("readin2"); Wire.send(0x00);//Serial.println("readin3"); Wire.send(0x50);//Serial.println("readin4"); Wire.endTransmission();//Serial.println("readin5"); //Serial.println("readin6"); delay(70); Wire.beginTransmission(112); Wire.send(0x02); Wire.endTransmission(); Wire.requestFrom(112, 2); if (2 <= Wire.available()) { reading = Wire.receive(); reading = reading << 8; reading |= Wire.receive(); } return reading; } //legacy range finder float read_sdm_io_range() { unsigned char pin = 0; unsigned int time_flag = 0; digitalWrite(TrigPin, HIGH); delayMicroseconds(2); digitalWrite(TrigPin, LOW); delayMicroseconds(50); digitalWrite(TrigPin, HIGH); tStartPing = micros(); timeout = 0; pin = digitalRead(EchoPin); while(pin) { pin = digitalRead(EchoPin); time_flag++; if(time_flag > SDM_IO_TIMEOUT) { timeout = 1; break; } } ultrasoundDuration = micros() - tStartPing; //Serial.print(ultrasoundDuration); //Serial.print(" us, "); //Serial.print(ultrasoundDuration*0.017, DEC); // result in cm //Serial.print(" cm"); //Serial.println(); if (timeout) return 999; else return ultrasoundDuration * 0.017; // result in cm }
Check out the XV-11 Lidar scanner. Its from a robot vacuum cleaner and is spectacular. It gives you a full 360 scan (360 readings) 5 times a second and works out to 6 meters. The data comes out in a 115200 baud serial stream at 3.3v and is easy to interpret. Draws around 30mA in use.
I’ve got 2 now. The latest one was $70 on eBay which is a steal compared to any other Lidar. That type of sensor typically runs into the thousands for low end versions.
http://www.theamphour.com/179-greg-charvat-returns-with-a-book-laboratory-literature-laureate/
TSOP4838 IR receiver
http://wiki.openmusiclabs.com/wiki/FHTExample
http://en.wikipedia.org/wiki/Discrete_Hartley_transform
http://dangerousprototypes.com/docs/Bus_Pirate_I/O_Pin_Descriptions
http://forums.adafruit.com/viewtopic.php?f=22&t=46064
IR an Pixel: http://learn.adafruit.com/wearable-flora-powered-tardis-costume-dogs/code
http://www.robotshop.com/ca/en/ultrasonic-range-finder-15cm-6000cm.html
http://www.hobbytronics.co.uk/srf02-ultrasonic-rangefinder
http://www.vanier-association.com/index.php/en/component/k2/item/247-apres-snow
http://apt613.ca/events/apres-snow-party/
Current bill of materials in progress (o = complete):
o-LEDs
o-Controller
o-Wire
o-Heat shrink
o-Ultrasonic Range Finder
o-Infrared Receiver
o-Remote Control
o-Paper
o-headers for range finder (female)
o-lanyard material
-extension cord
o-hanging wire
-hooks
-controller cabinet materials (hardboard, acrylic, fasteners)
Work in progress:
o-designing snowflakes
o-cutting out parts
o-Assembly of flakes
o-soldering wire harness extensions
o-wiring controller box
o-wiring sensor extensions
Snowie
o-100 batteries
o-100 LEDs
o-lanyard
Other
-extension cord
o-solder, iron
o-extra wire
o-meter
o-fishing line
-hooks
o-snippers
o-drywall screws /driver
o-tie wraps
o-tape
fft
Way awesome!
🙂
wire
http://thekavanaugh.ca/
http://paperpapier.com/