Idee
Für ein Hochzeitsgeschenk, dass zeitig fertig werden musste, brauchte ich eine kleinere, überschaubarere Variante des Rundbuntplasma. Inspiriert von der Idee des Projektes RGB-Pipe wurden die LED-Streifen dazu nicht gewickelt, sondern auch der Länge nach auf ein Rundholz passenden Durchmessers geklebt. Das ganze sollte "Mini" werden, statt einem Raspberry Pi musste ein Arduino ausreichen, statt einem Webinterface gibt es drei Potis, um alles einzustellen. Die Auflösung ist auch "nur" 8x8 Pixel, wobei ein Band mit einem engeren Pixelabstand (60 Pixel pro Meter) verwendet wurde. Trotzdem braucht das Teil bereits ein 3A Netzteil, um die LEDs voll auszuleuchten. Das Projekt ist tatsächlich zeitig fertig geworden, an zwei Nachmittagen wurde die Hardware gebaut, etwas länger dauerte die Software-Entwicklung, die nun 4 verschiedene Programme/Animationen enthält, die über das erste Poti ausgewählt werden, dass auch gleichzeitig zur Einstellung der Gesammt-Helligkeit dient. Mit den anderen beiden Potis können die Effekte und Farben beeinflußt werden. Das ganze wurde eingebaut in eine sehr preiswerte Tischlampe eines großen Möbelhauses, deren mattierter Glaszylinder für sehr sanfte Farbübergänge sorgt und in deren Fuß noch genug Platz für einen Arduino Mini sammt Potis war.
Aufbau
Auf einem Stück Rundholz von passendem Durchmesser wurden 8 Abschnitte mit jeweils 8 LEDs von einem schwarzen Neopixel-Band mit WS2812B LEDs aufgeklebt (per doppelseitigem Klebeband, fixiert mit zwei dünnen schwarzen Kabelbindern). Die Streifen wurden dazu von ihrer Silikonhülle befreit und Zick-Zack-förmig angeordnet, damit sich die Datenleitung einfacher verdrahten ließ. Mit jeweils 3 Wicklungen etwas dickerem Silberdraht wurde eine niederohmige ringförmige Stromversorgung zu allen Streifen am unteren Ende derselben hergestellt. Die beiden etwas dickeren Versorgungsleitungen sowie die dünnere Datenleitung wurden durch ein kleines Loch außen im Rundholz in einen zuvor gebohrten Kanal im Inneren des Rundholzes gelegt, von wo sie in den Sockel der Lampe gelangten. Die Datenleitung wurde an Pin 6 des Arduinos angeschlossen und die drei Potis, beschaltet als Spannungsteiler zwischen 5V und GND, an die Analogeingänge A0-A2. Über einen Hohlstecker im Lampenfuß wird das Netzteil, ein Steckernetzteil 5V/3A fertig gekauft bei Reichelt, angeschlossen. Der Lampenfuß wurde nach der Programmierung des Arduinos wieder mit Moosgummi verschlossen.
Software
Arduino Projekt Download Datei:RundbuntMini V1.zip
Die Software verwendet die Bibliotheken Flash und FastLED (statt Adafruits Neopixel Bibliothek, da diese keine so schönen Farbraumkonversationsroutinen besitzt). Eine Art Renderpipeline bringt die als HSV-Array[0..63] vorgerechneten Daten auf die Lampe, dabei werden die HSV-Werte in RGB-Werte umgerechnet, eine Gamma-Korrektur von 2.4 per Tabelle darauf geworfen und dann diese Daten in die richtige Reihenfolge für die LEDs gebracht (Zick-Zack und Offset-Korrektur). Der Einfachheit legt sich die Pixelfläche wie ein rechteckiges Blatt von vorne auf den Zylinder, hinten links unten ist Pixel 0, Pixel 7 ist hinten links oben, dann geht es bei Pixel 8 in der nächsten Spalte wieder etwas weniger hinten links unten weiter. Die Spalten laufen einmal von Mitte-Links-Hinten über Vorne-Mitte nach Mitte-Rechts-Hinten. Dadurch ist der Rand der 8x8 Pixelfläche auf der Rückseite der Lampe, die typischerweise mit den Potis nach vorne aufgestellt wird.
Die Potis werden etwas "entrauschst" und das erste Poti bestimmt die Animation: 1. Lampe wird mit einer konstanten Farbe "angemalt", Farbe (Hue) und Sättigung (Saturation) können per Poti eingestellt werden. 2. Linear wird ein laufender "Hue"-Wert (Regenbogen) von Pixel 0 bis Pixel 63 ausgegeben. Das eine Poti bestimmt die Schrittweite, das andere den Start-Wert der Farbe. Werden die Potis über die Hälfte gedreht, so werden diese Werte dynamisch verändert, Potiausschlag bestimmt die Änderungsgeschwindigkeit. Es ergeben sich sehr schöne Farbübergänge, die mal horizontal, mal vertikal, mal schräg, mal wild über die Lampe wabern. 3. Kaleidoskop: Der Quadrant links unten (4x4 Pixel) wird mit zufälligen Farben "bepixelt", wobei sich immer nur ein Pixel langsam in der Farbe verändert. Die untere Ecke wird an die drei verbleibenen Quadranten gespiegelt. Es ergibt sich ein symetrischer Farbeffekt. Über die Potis kann die Änderungsgeschwindigkeit und die Farbwahl eingestellt werden. Farbwahl: Weit nach links: viele dunkle Pixel, mittig: keine dunklen, keine weißen Pixel, weiter nach rechts: mehr Pixel, die auch weiß sind. 4. Plasmaeffekt: Der Klassiker, der Ausgangswert der Plasmasummenformel wird über ein Poti-Wert einstellbar verschieden stark in den Hue-Wert skaliert. Über das andere Poti kann die Änderungsgeschwindigkeit des Plasmaeffektes eingestellt werden.
Bilder
Hurra, pünktlich fertig geworden! Leider verschenkt, muss also noch mal nachgebaut werden.
Video: http://youtu.be/VHgYGVzX3lM
Materialliste
Anzahl | Teil | Preis | Kaufen bei |
---|---|---|---|
Tischleuchte BOXXX Ø 10cm, H: 20 cm | 5,00 | XXL | |
Holzkern + Halbzeug | 2,00 | Baumarkt | |
Neopixelstreifen mit 64 LEDs, 60 LEDs/m | 19,00 | Ebay | |
3x Poti 10k (522-0439) + 3x Potiknopf (Alu Design) (465-9814) | 13,00 | RS-Online | |
Netzeil 5V, 2.5A (SNT 2500 5V) + Hohlbuchse 2.1mm (HEBL 21) | 13,00 | Reichelt | |
Arduino Pro Mini | 3,00 | EBay | |
Kursgebühr für Nicht-Mitglieder | 20,00 | Hackerspace FFM |
Summe Teile ohne Kursgebühr: 55,00 EUR
Nachbau
Lampe zerlegen
Überblick über die gewonnenen Teile:
Lampensockel bohren
- Das Loch für das ehemalige 230V Kabel aufbohren auf 8mm
- Gegenüberliegend 3 Löcher 6mm für die Potis, die beiden äußeren 45° versetzt
Matrix auf Stab erstellen und löten
Potis vorbereiten
Alles zusammen bauen
Firmware
Es wird die zusätzlich die Library FastLED benötigt.
/* The MIT License (MIT) Copyright (c) 2014, Lutz Lisseck (lutz. lisseck AT gmx. de) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // #define FORCE_SOFTWARE_SPI // #define FORCE_SOFTWARE_PINS #include "FastLED.h" // How many leds are in the strip? #define NUM_LEDS 64 // Data pin that led data will be written out over #define DATA_PIN 6 // 5 lines with 8 pix shift in this situation #define PIXSHIFT 40 #define GAMMA 2.4 const PROGMEM uint8_t gamma_table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,10, 10,10,11,11,11,12,12,13,13,14,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,22,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33, 34,35,35,36,37,38,39,39,40,41,42,43,43,44,45,46,47,48,49,50,51,52,53,53,54,55,56,57,58,59,60,62,63,64,65,66,67,68,69,70,71,73,74,75, 76,77,78,80,81,82,83,85,86,87,88,90,91,92,94,95,96,98,99,100,102,103,105,106,108,109,111,112,114,115,117,118,120,121,123,124,126,127, 129,131,132,134,136,137,139,141,142,144,146,148,149,151,153,155,156,158,160,162,164,166,167,169,171,173,175,177,179,181,183,185,187, 189,191,193,195,197,199,201,203,205,207,210,212,214,216,218,220,223,225,227,229,232,234,236,239,241,243,246,248,250,253,255 }; // This is an array of leds. One item for each led in your strip. CRGB leds[NUM_LEDS]; CHSV hsvbuf1[NUM_LEDS]; //CHSV hsvbuf2[NUM_LEDS]; CRGB rgbbuf1[NUM_LEDS]; // parameters from potis int para[3]; // Auto animation here later... void getPara(void) { static int paraInt[3]; paraInt[0] = analogRead(A0); paraInt[1] = analogRead(A1); paraInt[2] = analogRead(A2); if(abs(paraInt[0] - para[0]) > 2) para[0] = paraInt[0]; if(abs(paraInt[1] - para[1]) > 0) para[1] = paraInt[1]; if(abs(paraInt[2] - para[2]) > 0) para[2] = paraInt[2]; } // This function sets up the ledsand tells the controller about them void setup() { Serial.begin(9600); FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS); Serial.print("Mem: "); Serial.println(availableMemory()); randomSeed(analogRead(A7)+analogRead(A1)); getPara(); for(uint8_t i=0; i<NUM_LEDS; i++) { leds[i] = CRGB::Black; hsvbuf1[i] = CHSV(0,0,0); } FastLED.show(); delay(1000); } // Copy HSV to RGB, rearranged and show void showHSV(const struct CHSV * phsv) { CRGB *pLed; pLed = &leds[PIXSHIFT]; for(uint8_t i=0; i<NUM_LEDS; i++) { if((i+PIXSHIFT)==NUM_LEDS) pLed = &leds[0]; if(i & 8) { hsv2rgb_rainbow(phsv[i], *pLed++); } else { hsv2rgb_rainbow(phsv[i^7], *pLed++); } } FastLED.show(); } // Copy HSV to RGB, rearranged, gamma-corrected and show void showHSVg(const struct CHSV * phsv) { CRGB *pLed; pLed = &leds[PIXSHIFT]; for(uint8_t i=0; i<NUM_LEDS; i++) { if((i+PIXSHIFT)==NUM_LEDS) pLed = &leds[0]; if(i & 8) { hsv2rgb_rainbow(phsv[i], *pLed); } else { hsv2rgb_rainbow(phsv[i^7], *pLed); } pLed->r = pgm_read_byte_near(gamma_table + pLed->r); pLed->g = pgm_read_byte_near(gamma_table + pLed->g); pLed->b = pgm_read_byte_near(gamma_table + pLed->b); pLed++; } FastLED.show(); } // Copy RGB to RGB, rearranged and show void showRGB(const struct CRGB * prgb) { CRGB *pLed; pLed = &leds[PIXSHIFT]; for(uint8_t i=0; i<NUM_LEDS; i++) { if((i+PIXSHIFT)==NUM_LEDS) pLed = &leds[0]; if(i & 8) { *pLed++ = prgb[i]; } else { *pLed++ = prgb[i^7]; } } FastLED.show(); } void rearrangeLeds(void) { CRGB C, *pCa, *pCb; for(uint8_t i=8; i < NUM_LEDS; i+=16) { pCa = &leds[i]; pCb = &leds[i+7]; for(uint8_t j=0; j <= 3; j++) { C = *pCa; *pCa++ = *pCb; *pCb-- = C; //C = leds[i+j]; //leds[i+j] = leds[i+(7-j)]; //leds[i+(7-j)] = C; } } } void loop() { #define AnimCount 4 int para0remain, para0main; getPara(); para0main = (para[0]*AnimCount)/1024; para0remain = map(para[0]%(1024/AnimCount),0,(1024/AnimCount)-1,0,255); switch(para0main) { case 0: if(para0remain < 4) { // turn all off for(uint8_t i=0; i<NUM_LEDS; i++) { leds[i] = CRGB::Black; hsvbuf1[i] = CHSV(0,0,0); } FastLED.show(); // optional: Power down stuff here... } else { // lightpainter to one color CHSV hsv_col(para[2]/4,para[1]/4,min(255,para0remain+32)); for(uint8_t i=31; i>0; i--) { hsvbuf1[i] = hsvbuf1[i-1]; } hsvbuf1[0] = hsvbuf1[NUM_LEDS-1]; for(uint8_t i=NUM_LEDS-1; i>32; i--) { hsvbuf1[i] = hsvbuf1[i-1]; } hsvbuf1[32] = hsv_col; showHSVg(hsvbuf1); delay(30); } break; case 1: { // Rainbow-Fill animation static float p2_mover = 0.0; static float hue_mover = 0.0; int para1rem = para[1]%512; p2_mover += pow(para1rem/511.0,4.0); if(p2_mover > 255.0) p2_mover = 0.0; if(para[1] >= 512) para1rem = p2_mover; int para2rem = para[2]%512; hue_mover += pow(para2rem/511.0,4.0); if(hue_mover > 255.0) hue_mover = 0.0; if(para[2] >= 512) para2rem = int(hue_mover); CHSV hsv_col(para2rem,255,min(para0remain,255)); float hue_f = para2rem; for(uint8_t i=0; i<NUM_LEDS; i++) { hsv_col.h = int(hue_f); hsvbuf1[i] = hsv_col; hue_f += para1rem/8.0; } //Serial.println(int(hue_f)); showHSVg(hsvbuf1); } break; case 2: { // ======================= // ==== kaleidoscope ===== // ======================= //for(uint8_t i=0; i<NUM_LEDS; i++) hsvbuf1[i].v = 0; // light-up pixel in lower-left corner static unsigned char pix_addr = 0; static CHSV hsv_target; static CHSV hsv_source; static float step_part = 0.0; uint8_t v_max = min(255,para0remain+32); // target color adapted or not? if(hsv_source != hsv_target) { float old_step = step_part; int do_step; step_part += para[1]/64.0; do_step = int(step_part - old_step); step_part -= do_step; if(step_part > 100.0) step_part -= 100.0; // slowly (with do_step) adjust hsv to target if(hsv_source.s < hsv_target.s) { hsv_source.s = min((int)hsv_target.s, hsv_source.s + do_step); } else { hsv_source.s = max((int)hsv_target.s, hsv_source.s - do_step); } if(hsv_source.h < hsv_target.h) { hsv_source.h = min((int)hsv_target.h, hsv_source.h + do_step); } else { hsv_source.h = max((int)hsv_target.h, hsv_source.h - do_step); } if(hsv_source.v < hsv_target.v) { hsv_source.v = min((int)hsv_target.v, hsv_source.v + do_step); } else { hsv_source.v = max((int)hsv_target.v, hsv_source.v - do_step); } hsvbuf1[pix_addr] = hsv_source; } else { // choose new pixel and new color pix_addr = random(32) & 0x1b; // limit to one quadrant hsv_target.h = random(256); hsv_target.s = (para[2]>=512)?((random(512)>(para[2]-512))?255:0):255; hsv_target.v = (para[2]>=512)?v_max:((random(512)>(para[2]))?0:v_max); hsv_source = hsvbuf1[pix_addr]; } // big pixel hack /* hsvbuf1[12] = hsvbuf1[18]; hsvbuf1[17] = hsvbuf1[18]; hsvbuf1[9] = hsvbuf1[18]; hsvbuf1[11] = hsvbuf1[19]; hsvbuf1[25] = hsvbuf1[26]; hsvbuf1[8] = hsvbuf1[16]; hsvbuf1[2] = hsvbuf1[1]; */ // clone pixels to other corners for(uint8_t i=0;i<32;i++) { if(i & 0x04) i+=4; if(i != pix_addr) { hsvbuf1[i].v = hsvbuf1[i].v?v_max:0;} hsvbuf1[(7-(i&0x03))|(i&0x18)] = hsvbuf1[i]; hsvbuf1[(56-(i&0x18))|(i&0x03)] = hsvbuf1[i]; hsvbuf1[(56-(i&0x18))|(7-(i&0x03))] = hsvbuf1[i]; } showHSVg(hsvbuf1); } break; case 3: { float v,cx,cy,t; static int para1, para2; if(abs(para1 - para[1]) > 4) para1 = para[1]; if(abs(para2 - para[2]) > 4) para2 = para[2]; t = millis()/(10000.0/pow(para1/511.0,4.0)); for(uint8_t x=0; x<8; x++) { for(uint8_t y=0; y<8; y++) { v = sin(x*0.2+t); v += sin(0.2*(x*sin(t/2)+y*cos(t/3))+t); cx = x - 3.5 + 5.0*sin(t/3.0); cy = y - 3.5 + 5.0*cos(t/3.0); v += sin(sqrt(0.2*(cx*cx+cy*cy)+0.0)+t); hsvbuf1[x*8+y].h = v*para2; hsvbuf1[x*8+y].s = 255; hsvbuf1[x*8+y].v = min(255,para0remain+32); } } showHSVg(hsvbuf1); } break; case 4: { static float edge; edge = para[1]/100.0; for(uint8_t i=NUM_LEDS-1; i>7; i--) { hsvbuf1[i] = hsvbuf1[i-8]; } for(uint8_t i=0;i<=7;i++) { hsvbuf1[i].s = (i>int(edge))?0:255; if(int(edge) == i) hsvbuf1[i].s = int((edge-(i))*255.0); hsvbuf1[i].h = 140; hsvbuf1[i].v = min(255,para0remain+32); } showHSVg(hsvbuf1); delay(100); } break; default: showHSV(hsvbuf1); break; } } int availableMemory() { int size = 1024; byte *buf; while ((buf = (byte *) malloc(--size)) == NULL); free(buf); return size; }