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 <span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span><span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>< 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));
  }
}

4 Reaktionen auf “Arduino-Bastelei: LED-Spielereien

Schreibe einen Kommentar

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