Zuletzt geändert am 27. Oktober 2015 um 20:59

Rundbunt Mini

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.


Kern mit LED-Streifen Im Sockel, Arduino Mini unter Schrumpfschlauch

Software

Arduino Projekt Download Datei:RundBuntMini V2.zip

Die Software verwendet die Bibliothek 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.
Runbunt Mini in Betrieb.

Video: http://youtu.be/VHgYGVzX3lM

Materialliste

Anzahl Teil Preis Kaufen bei
Tischleuchte BOXXX Ø 10cm, H: 20 cm 5,00 XXL
Holzkern Ø 2,5cm + Halbzeug 2,00 Baumarkt
Neopixelstreifen mit 64 LEDs, 60 LEDs/m 19,00 Ebay
3x Poti 10k 3310Y von Bourns
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:

Ü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

So wird die Matrix gelötet.


Potis vorbereiten

RundBuntMiniPotis.png


Rundbunt Poti IMAG2697.jpg


Alles zusammen bauen

Rundbunt Mini Bau2.png


Firmware

Es wird die zusätzlich die Library FastLED benötigt.

/*
The MIT License (MIT)

Copyright (c) 2015, 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;
}


Muster entwickeln