Arduino-Bastelei: LED-Spielereien

Es ist schon gut ein Jahr her, dass ich damit angefangen hatte, mit LED-Strips in Kombination mit dem Arduino zu spielen. Und zwar mit solchen LED-Strips, bei denen man jede einzelne LED extra ansprechen und somit nette Animationen erzeugen kann.

Hintergrund: Der Jüngere der Söhne schläft oben in einem Stockbett, dessen unteren Teil er als eine Art Sofa oder was auch immer nutzt. Anlässlich seinen Geburtstages wünschte er sich eine Beleuchtung dieses unteren Stockbett-Teils, und das kam mir gerade recht. Ich hatte einige nette Abende und habe ihm aus einem Arduino Uno, einem 5-Meter-LED-Strip, ein paar Potis zum Einstellen der Farben (einen für die Helligkeit und jeweils einen für die RGB-Farbanteile), einem Taster zum Umschalten zwischen verschiedenen Betriebsmodi, ein paar Sensoren (Temperatur, Lautstärke) und einem leistungsstarken Netzteil etwas zusammengeschustert.

Zum Geburtstag gab es dann eine Box mit den Elektronik-Bauteilen und diesem Schaltplan:

Folgende Betriebsmodi gibt es:

  • Manuelle Farbsteuerung: Mit den vier Potis kann die Helligkeit des Strips und die Farbzusammensetzung (RGB = rot, grün, blau) eingestellt werden. Änderungen betreffen nicht sofort den ganzen Strip, sondern wandern vom Anfang her über den Strip.
  • Temperatur-Steuerung: Abhängig von der Zimmertemperatur ändert der Strip seine Farbe von blau (kalt) hin zu rot (warm).
  • Lautstärke-Steuerung: Der Strip fungiert als Lautstärkeanzeige für Musik: Je lauter die Musik, desto mehr LEDs entlang des Strips leuchten. Außerdem ändert sich mit der Lautstärke auch die Farbe.
  • Rainbow: Farbverläufe (wie sie eigentlich keiner sehen will) mäandern über den Strip.
  • DiscoDsicoPartyParty: Zum Bekloppt-Werden: Zufälliges Lichter-Zucken auf allen Kanälen. Die Farbenanteile und die Helligkeit können eingestellt werden.
  • Volle Helligkeit: Alle LEDs auf weiß und volle Helligkeit. Nichts zu steuern.
  • LEDs aus: Wie der Name schon sagt, einfach alles aus.

Mit etwas Unterstützung (an einem Geburtstag soll ja kein Frust aufkommen) war das Zeug schnell zusammengebaut und auch lauffähig. Noch bei der Feier haben wir ein bisschen was modifiziert und ergänzt und das Ding irgendwann am Bett montiert. Wurde Zeit, das hier mal zu dokumentieren und euch mit einem kleinen Video einen Einblick zu geben, was man mit doch recht einfachen Mitteln nettes basteln kann. Das Video wurde vom Geburtstagskind selbst geschnitten, bitte zum Ende ansehen, es gibt ein kleines Easeregg:



Falls dich der Code interessiert gibt es den unten. Neu war für mich die Verwendung von Interrupts. War in diesem Fall wichtig, denn diverse Licht-Animationen dauern so ihre Zeit, und man will mit dem Tastendruck zum Umschalten in einen anderen Modus nicht genau den Moment abpassen, in dem der Microcontroller auf den Tastendruck lauscht. Der Interrupt kann den Code (deswegen heißt er ja so) zu jedem Zeitpunkt unterbrechen und dann etwas anderes auslösen. Ohne wäre das Ding nicht wirklich bedienbar.


// Includes für den LED-Strip:
#include "Adafruit_WS2801.h"
#include "SPI.h"
//#ifdef __AVR_ATtiny85__
//  #include <avr/power.h>
//#endif

// Variablen für den LED-Strip:
uint8_t dataPin  = 13;    // BLAUES Kabel am LED-Strip
uint8_t clockPin = 12;    // ROTES Kabel am LED-Strip
int numberOfLEDs = 160;   // Anzahl an LEDs auf dem Strip
Adafruit_WS2801 strip = Adafruit_WS2801(numberOfLEDs, dataPin, clockPin);




// Variablen für den Button (als Interrupt, siehe https://www.arduino.cc/en/Reference/AttachInterrupt):
const byte interruptPin = 2;
int modus = 0;  // kann 1, 2, 3, 4, 5, 6, 7 sein und legt den Modus der LEDs fest




// Variablen für die Dreh-Potis:
int potBrightPin  = 2;
int potRedPin     = 3;
int potGreenPin   = 4;
int potBluePin    = 5;

int potBright = 0;
int potRed = 0;
int potGreen = 0;
int potBlue = 0;




// Includes für Temp-Sensor:
#include "cactus_io_DHT22.h"

// Data-Pin des Temp-Sensors:
#define DHT22_PIN 4

// Sensor initialisieren:
DHT22 dht(DHT22_PIN);
// Note: If you are using a board with a faster processor than 16MHz then you need
// to declare an instance of the DHT22 using 
// DHT22 dht(DHT22_DATA_PIN, 30);
// The additional parameter, in this case here is 30 is used to increase the number of
// cycles transitioning between bits on the data and clock lines. For the
// Arduino boards that run at 84MHz the value of 30 should be about right.


// Include für das Display:
#include <TM1637Display.h>

// PINs des Displays:
const int CLK = 6; //Set the CLK pin connection to the display
const int DIO = 7; //Set the DIO pin connection to the display

// Display initialisieren:
TM1637Display display(CLK, DIO);  //set up the 4-Digit Display.
// Noch zwei Zustände definieren:
uint8_t all_on[]  = { 0xff, 0xff, 0xff, 0xff };
uint8_t all_off[] = { 0x00, 0x00, 0x00, 0x00 };


// PIN des Lautstärke-Sensors:
int pinVolume = 0;





void setup() {

  // Setup für Interrupt:
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), changeModus, FALLING);

  // Setup für LED-Strip:
//  #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L)
//    clock_prescale_set(clock_div_1); // Enable 16 MHz on Trinket
//  #endif
  strip.begin();
  strip.show();   // Update LED contents, to start they are all 'off'
  

  // Setup für Temperatur-Sensor:
  dht.begin();

  // Setup des Displays:
  display.setBrightness(0xff);  //set the diplay to maximum brightness
  // Testweise dreimal alle Segmente kurz anzeigen:
  display.setSegments(all_on);
  delay(100);
  display.setSegments(all_off);
  delay(100);
  display.setSegments(all_on);
  delay(100);
  display.setSegments(all_off);
  delay(100);
  display.setSegments(all_on);
  delay(100);
  display.setSegments(all_off);
  
  // Ausgabe auf seriellem Monitor:
  Serial.begin(9600);
  Serial.println("Bright\tRed\tGreen\tBlue");

  
}






void loop() {

  
  switch (modus) {
    // Modus 1: Manuelle Farbsteuerung
    case 1:
      ColorModusManual(modus);
    break;
    // Modus 2: Temperatur-Steuerung
    case 2:
      ColorModusTemperature(modus);
    break;
    // Modus 3: Lautstärke-Steuerung
    case 3:
      ColorModusVolume(modus);
    break;
    // Modus 4: Rainbow
    case 4:
      ColorModusRainbow(modus, 0);
    break;
    // Modus 5: DiscoDsicoPartyParty
    case 5:
      ColorModusDiscoDiscoPartyParty(modus, 0);
    break;
    // Modus 6: Volle Helligkeit
    case 6: 
      ColorModusAllOn(modus, 0);
    break;
    // Modus 7: LEDs aus
    case 7: 
      ColorModusAllOff(modus, 0);
    break;
  }



}









void changeModus() {

  // Debouncing (see http://forum.arduino.cc/index.php?topic=45000.msg325952#msg325952):
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 300ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 300) {
    //modus = !modus;
    modus = (modus % 7) + 1; // "modus" kann dann 1, 2, 3, 4, 5, 6, 7 sein
    Serial.print("- - - Modus changed: ");
    Serial.print(modus);
    Serial.print(" - - - ");
    switch (modus) {
      // Modus 1: Manuelle Farbsteuerung
      case 1:
        Serial.print("Manuelle Steuerung");
        display.setSegments(all_off);
      break;
      // Modus 2: Temperatur-Steuerung
      case 2:
        Serial.print("Temperatur-Steuerung");
      break;
      // Modus 3: Lautstärke-Steuerung
      case 3:
        Serial.print("Lautstaerke-Steuerung");
        display.setSegments(all_off);
      break;
      // Modus 4: Rainbow
      case 4:
        Serial.print("Rainbow");
        display.setSegments(all_off);
      break;
      // Modus 5: DiscoDiscoPartyParty
      case 5:
        Serial.print("DiscoDiscoPartyParty");
        display.setSegments(all_off);
      break;
      // Modus 6: Volle Helligkeit
      case 6: 
        Serial.print("Komplett AN");
        display.setSegments(all_off);
      break;
      // Modus 7: LEDs aus
      case 7: 
        Serial.print("Komplett AUS");
        display.setSegments(all_off);
      break;
    }
    Serial.println(" - - - ");
  }
  last_interrupt_time = interrupt_time;
}






// LED-Strip-Funktionen:

void ColorModusManual(int modusOK) {

  potBright = analogRead(potBrightPin) / 4;
  potRed    = analogRead(potRedPin) / 4;
  potGreen  = analogRead(potGreenPin) / 4;
  potBlue   = analogRead(potBluePin) / 4;
  Serial.print(potBright);  Serial.print("\t");
  Serial.print(potRed);     Serial.print("\t");
  Serial.print(potGreen);   Serial.print("\t");
  Serial.print(potBlue);    Serial.println("\t");

  int i;
  for (i=0; i < strip.numPixels(); i++) {

      // INTERRUPT EINBAUEN:
      if ((i%100) == 0) Serial.print("");
      if (modus != modusOK) return;
    
      potBright = analogRead(potBrightPin) / 4;
      potRed    = analogRead(potRedPin) / 4;
      potGreen  = analogRead(potGreenPin) / 4;
      potBlue   = analogRead(potBluePin) / 4;
    
      strip.setPixelColor(i, Color(potBright * potRed / 255, potBright * potGreen / 255, potBright * potBlue / 255));
      strip.show();
  }
}


void ColorModusTemperature(int modusOK) {

  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  dht.readHumidity();
  dht.readTemperature();

  // Gemessene Werte auf seriellen Monitor ausgeben:
  Serial.print("Feucht: "); Serial.print(dht.humidity); Serial.print(" %\t\t");
  Serial.print("Temp: "); Serial.print(dht.temperature_C); Serial.print(" *C\t");
  Serial.print("gef. Temp: "); Serial.print(dht.computeHeatIndex_C()); Serial.println(" *C\t");    // Heat index: https://de.wikipedia.org/wiki/Hitzeindex

  // GEFÜHLTE Temperatur auf Display ausgeben:
  //display.showNumberDec(dht.computeHeatIndex_C()*100, false);
  // ECHTE Temperatur auf Display ausgeben:
  display.showNumberDec(dht.temperature_C*100, false);
  // Darstellung mit Doppelpunkt (statt Komma), besser als nichts:  
  uint8_t segto;
  //segto = 0x80 | display.encodeDigit((int)(dht.computeHeatIndex_C())%10);
  segto = 0x80 | display.encodeDigit((int)(dht.temperature_C)%10);
  display.setSegments(&segto, 1, 1);
  
  

  float T     = dht.computeHeatIndex_C();
  float Tmin  = 10;
  float Tmax  = 35;

  float r = 0;
  float g = 0;
  float b = 0;
  
  // Linearer Verlauf:
  // - Bei Tmin ist die Farbe nur blau
  // - Bei Tmax ist die Farbe nur rot
  // - Unter Tmin ist die Farbe weiß
  // - Über Tmax ist die Farbe gelb
  
  if (T < Tmin) { r = 1; g = 1; b = 1; } else if (T > Tmax) {
    r = 1;
    g = .5;
    b = 0;
  } else {
    r = (T - Tmin) / (Tmax - Tmin);
    g = 0;
    b = (T - Tmax) / (Tmin - Tmax);
  }
//  Serial.print("R = "); Serial.print(r); Serial.print("\t");
//  Serial.print("G = "); Serial.print(g); Serial.print("\t");
//  Serial.print("B = "); Serial.print(b); Serial.print("\t\n"); 
  
  int i;
  for (i=0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i,  Color(r * analogRead(potBrightPin) / 4, g * analogRead(potBrightPin) / 4, b * analogRead(potBrightPin) / 4));

    // INTERRUPT EINBAUEN:
    if ((i%100) == 0) Serial.print("");
    if (modus != modusOK) return;
    
  }
  strip.show();   // write all the pixels out

  //delay(2000);
}


void ColorModusVolume(int modusOK) {

  float maxvolume = 757; // Erkenntnis durch Ausprobieren...
  float volume = analogRead(pinVolume) / maxvolume; // Wert zwischen 0 und 1

//  // Mit Mittelungen als Glättung:
//  volume = 0;
//  int mittelungen = 10;
//  int m;
//  for (m = 1; m <= mittelungen; m++) {
//    volume = volume + (analogRead(pinVolume) / maxvolume);
//    //delay(10);
//  }
//  volume = volume / mittelungen;

  // Die unteren z.B. 20% abschneiden:
  float volcut = 0.2;
  if (volume < volcut) { volume = 0; }   // jetzt gehen die Werte von 0.2 - 1
  volume = volume - volcut;              // jetzt gehen die Werte von 0 - .8
  volume = volume * (1 / (1 - volcut));        // jetzt gehen die Werte von 0 - 1
  
  
  Serial.print("Volume:\t");
  Serial.println(volume);


  float potBright = analogRead(potBrightPin) * 1.0 / 1023;

  
  int i;
  for (i=0; i < strip.numPixels(); i++) {

    if (i < strip.numPixels() * volume) {
      strip.setPixelColor(i,  Color(255 * volume * potBright, 255 * (1-volume) * potBright, 0));
    }
    else {
      strip.setPixelColor(i,  Color(0, 0, 0));   
    }


    // INTERRUPT EINBAUEN:
    if ((i%100) == 0) Serial.print("");
    if (modus != modusOK) return;
    
  }
  strip.show();   // write all the pixels out

}



void ColorModusRainbow(int modusOK, uint8_t wait) {
  int i, j;
  
  for (j=0; j < 256 * 5; j++) {     // 5 cycles of all 25 colors in the wheel
    for (i=0; i < strip.numPixels(); i++) {
      // tricky math! we use each pixel as a fraction of the full 96-color wheel
      // (thats the i / strip.numPixels() part)
      // Then add in j which makes the colors go around per pixel
      // the % 96 is to make the wheel cycle around
      
      //strip.setPixelColor(i, Wheel( ((i * 256 / strip.numPixels()) + j) % 256, analogRead(potBrightPin) / 4) );
      // Mal so abändern, dass über den ganzen Strip nicht EINMAL die Farben sind, sondern ACHTMAL:
      strip.setPixelColor(i, Wheel( ((i * 256 * 8 / strip.numPixels()) + j) % 256, analogRead(potBrightPin) / 4) );

      // INTERRUPT EINBAUEN:
      if ((i%100) == 0) Serial.print("");
      if (modus != modusOK) return;
      
    }
    strip.show();   // write all the pixels out
    delay(wait);
  }
}




void ColorModusDiscoDiscoPartyParty(int modusOK, uint8_t wait) {
  
  float potBright = analogRead(potBrightPin) * 1.0 / 1023;
  float potRed    = analogRead(potRedPin) * 1.0  / 1023;
  float potGreen  = analogRead(potGreenPin) * 1.0  / 1023;
  float potBlue   = analogRead(potBluePin) * 1.0  / 1023;
  
  int i;
  for (i=0; i < strip.numPixels(); i++) {
      //strip.setPixelColor(i, Color(random(0,255) * analogRead(potBrightPin) / 1024, random(0,255) * analogRead(potBrightPin) / 1024, random(0,255) * analogRead(potBrightPin) / 1024));
      //strip.setPixelColor(i, Color(random(0,255) * analogRead(potBrightPin) / 1024 * analogRead(potRedPin) / 1024, random(0,255) * analogRead(potBrightPin) / 1024 * analogRead(potGreenPin) / 1024, random(0,255) * analogRead(potBrightPin) / 1024 * analogRead(potBluePin) / 1024));
      strip.setPixelColor(i, Color(random(0,255) * potBright * potRed, random(0,255) * potBright * potGreen, random(0,255) * potBright * potBlue));
      delay(wait);
  
      // INTERRUPT EINBAUEN:
      if ((i%100) == 0) Serial.print("");
      if (modus != modusOK) return;
    
  }
  strip.show();

}



void ColorModusAllOn(int modusOK, uint8_t wait) {
  int i;
  for (i=0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Color(255, 255, 255));
      strip.show();
      delay(wait);
  
      // INTERRUPT EINBAUEN:
      if ((i%100) == 0) Serial.print("");
      if (modus != modusOK) return;
    
  }
}


void ColorModusAllOff(int modusOK, uint8_t wait) {
  int i;
  
  for (i=0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Color(0, 0, 0));
      strip.show();
      delay(wait);
  
      // INTERRUPT EINBAUEN:
      if ((i%100) == 0) Serial.print("");
      if (modus != modusOK) return;
    
  }
}












/* Helper functions */

// Create a 24 bit color value from R,G,B
uint32_t Color(byte r, byte g, byte b)
{
  uint32_t c;
  c = r;
  c <<= 8;
  c |= g;
  c <<= 8;
  c |= b;
  return c;
}

//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, float Brightness)
{
  Brightness = Brightness / 255;
  if (WheelPos < 85) {
   return Color(Brightness * WheelPos * 3, Brightness * (255 - WheelPos * 3), 0);
  } else if (WheelPos < 170) {
   WheelPos -= 85;
   return Color(Brightness * (255 - WheelPos * 3), 0, Brightness * WheelPos * 3);
  } else {
   WheelPos -= 170; 
   return Color(0, Brightness * WheelPos * 3, Brightness * (255 - WheelPos * 3));
  }
}

13 Reaktionen auf “Arduino-Bastelei: LED-Spielereien

  1. Domenik

    Hallo,

    cooles Projekt, würde das gerne auch umsetzen. Magst du vielleicht mal alle Hardware-Komponenten auflisten, die du eingesetzt hast?

    Gerade bei der Musikerkennung (Mikrofon?) und der Temperaturerkennung bin ich mir unsicher, welche Bauteile ich kaufen soll.

    Wenn du ein paar Links hast wäre das natürlich super.

    Besten Dank.

    Antworten
      1. Max

        Hallo,
        super Beitrag!
        Ich suche schon ziemlich lange nach einer temperaturabhängigen RGB-Steuerung!

        Hätte eine Frage zu dem RGB LED Strip: – welchen hast du verwendet – (Spannung und Schema)?
        Danke dir für den tollen Beitrag!

        Antworten
        1. dasaweb Beitragsautor

          Hallo Max,

          Hinweise zum LED-Strip und zu den PINs findest du ganz oben im Code. Es war ein LED-Strip vom Typ „WS2801“, die Pins waren 13 und 12 (data und clock).

          Hab in letzter Zeit was über Farbräume gelernt und würde daher nicht mehr RGB für die temperaturabhängige Farbsteuerung verwenden, sondern sowas wie HSV oder so. Also einen Farbraum, bei dem Helligkeit und Sättigung konstant bleiben und man mit EINEM Parameter direkt den Farbwert setzen kann. Geht so auch, aber anders ist das doch eleganter.

          Antworten
          1. Max

            Hallo dasaweb,
            danke erst mal für die schnelle Antwort!
            ich denke für die ersten Versuche und mein Vorhaben, wir dieser Aufbau erst genügen.
            Möchte einfach meine Lüfter im NAS-Server nach Temperatur – (Blau/Rot) leuchten lassen.

            Wenn du das mit HSV umsetzt würde mich das natürlich total interessieren…
            …wenn man(n) Blut geleckt hat, gibt es kein zurück! 😉

            Cu
            Max

  2. Max

    Hallo dasaweb,
    sorry wenn ich nochmals zu dem Projekt ein-zwei Fragen habe!
    Habe ja erzählt, dass ich die Steuerung für eine temperaturgesteuerte RGB-Beleuchtung meines NAS einsetzen möchte.
    Hierzu benötige ich natürlich nur einen Teil der vorhandenen Modi (1.Manual, 2. Temperatur, 6. AllOn und 7. AllOff).

    Zwischenzeitlich habe ich einige veränderte Sketches abgespeichert, komme aber irgendwie nicht weiter.
    Im ColorModusTemperature habe ich das Problem, dass ich zwar Tmin und Tmax verändern kann (30 bzw. 45) aber schon bei der Display Anzeige von ca. 31:40C wird der Strip auf „Gelb“ gesetzt, obwohl dies doch erst bei 45:00 passieren sollte. Auch bei einer Anzeige von 30:60
    ist der Strip schon feuerrot. Ändert die Farbe in 0:10C -Schritten.
    Die Grenz „weiß-blau“ ist relativ stabil bei der angegebene Tmin (30:00).
    Auch wenn ich nur an dem original Code von dir die Tmin und Tmax ändere verhält sich die Steuerung so wie beschrieben. Einziger Unterschied ist, dass ich nur 1m – also 32 LED dran habe, dies aber in den Variablen geändert habe.

    Woran kann dies liegen?
    Welche Poti-Werte hattest du im Einsatz? Habe mal 25k eingesetzt.
    Welchen Zweck hat der Abschnitt „Gefühlte Temp. und Echte Temp. ? Ich bräuchte nur die eine von DHT22.

    Ich hoffe, dass du mir hier ein wenig Nachhilfe geben kannst und ich nicht zu unverschämt bin, wegen meiner Nachfrage!
    Danke dir schon mal für eventuelle Unterstützung.

    Grüße aus BW
    Max

    Antworten
    1. dasaweb Beitragsautor

      Hallo Max,

      das mit der Farbberechnung ist ohne den Code nur Kaffeesatzleserei… Lässt du dir die berechneten Werte für r,g,b ausgeben? Würde erst mal checken, ob die Berechnung sinnvoll ist. Dann würde ich sie mal nicht berechnen lassen, sondern einfach fest setzen und schauen, ob die LEDs das machen, was sie unter den gegebenen Werten tun sollen. Damit könntest du dich auf die Fehlersuche machen.

      Zur Temperatur: Die Bibliothek berechnet mit computeHeatIndex_C eine „gefühlte Temperatur“, die die Luftfeuchtigkeit mit einbezieht, siehe https://de.wikipedia.org/wiki/Hitzeindex

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert