Noch ein LED Projekt

Hallo erstmal,

ich bin schon länger in dem Forum am lesen und Informationen zusammen zu suchen für mein Projekt. Leider bin ich absoluter Anfänger in den ganzen Sachen und versuche mich direkt ein einem etwas komplexeren Projekt.

Zunächst, das Projekt soll nicht von heute auf morgen stehen, sondern mit der Zeit wachsen.

Es handelt sich hierbei um zwei getrennte WS2812B Streifen an jeweils einem Digitalen Ausgang mit einmal 117 und 30 LEDs. Für ein Netzteil mit 18 Ampere ist gesorgt, da in Zukunft noch weitere LEDs geplant sind, aber die können ruhig mit der Zeit folgen.
Angeschlossen ist es einem einem Arduino Uno.
Beide sind jeweils mit einem 470 Ohm Widerstand ausgestattet und in allen Test, die ich mit folgenden Script bisher versucht habe, lief auch alles technisch einwandfrei. Auch, wenn ich das Script auf meine Bedürfnisse hin angepasst habe.

#include "FastLED.h"

// This sketch shows one way to define a 'timed playlist'
// of animations that automatically rotate on a custom schedule.
//
// A "ResetPlaylist" method is provided so that the playlist can be
// restarted from a custom external trigger, e.g., a button or event.
// For demonstration purposes, the playlist is reset if the
// sketch receives a letter "r" on the serial port.
//
// -Mark Kriegsman, January 2015

#define DATA_PIN    6
#define DATA_PIN2   3

#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    117
#define NUM_LEDS2   30

CRGB leds[NUM_LEDS];
CRGB leds2[NUM_LEDS2];

#define BRIGHTNESS          96
#define FRAMES_PER_SECOND  120
void setup() {
  delay(3000); // 3 second delay for recovery
  FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.addLeds<LED_TYPE, DATA_PIN2, COLOR_ORDER>(leds2, NUM_LEDS2).setCorrection(TypicalLEDStrip);
  FastLED.setBrightness(BRIGHTNESS);
  RestartPlaylist();
  Serial.begin(57600);
}
// List of patterns to cycle through.  Each is defined as a separate function below.

typedef void (*SimplePattern)();
typedef SimplePattern SimplePatternList[];
typedef struct { SimplePattern mPattern;  uint16_t mTime; } PatternAndTime;
typedef PatternAndTime PatternAndTimeList[];
uint32_t previousMillis = 0, intervall = 500;

// These times are in seconds, but could be changed to milliseconds if desired;

const PatternAndTimeList gPlaylist = { 
  { black,             1 },
  { sign1_L,                10 },
  { sign,                 50 },
  { rainbowM,                    20 },
  { bpm,      5 },
  { sign,                  5 },
  { applause,               1 },
  { fadeToBlack,             3 }
};

// If you want the playlist to loop forever, set this to true.
// If you want the playlist to play once, and then stay on the final pattern 
// until the playlist is reset, set this to false.
bool gLoopPlaylist = true;

uint8_t gCurrentTrackNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; 
uint8_t farbe = 0;
uint8_t zeit = 0;
  
bool gRestartPlaylistFlag = false;

void loop()
{
  // Call the current pattern function once, updating the 'leds' array
  gPlaylist[gCurrentTrackNumber].mPattern();
  
  // send the 'leds' array out to the actual LED strip
  FastLED.show();  
  // insert a delay to keep the framerate modest
  FastLED.delay(1000/FRAMES_PER_SECOND); 

  // For demo purposes, restart the playlist any time we read
  // the letter "r" character from the serial port.
  if( Serial.read() == 'r') RestartPlaylist();

EVERY_N_MILLISECONDS( 20 ) { gHue++; } 

  // Here's where we do two things: switch patterns, and also set the
  // 'virtual timer' for how long until the NEXT pattern switch.
  //
  // Instead of EVERY_N_SECONDS(10) { nextPattern(); }, we use a special
  // variation that allows us to get at the pattern timer object itself,
  // and change the timer period every time we change the pattern.
  //
  // You could also do this with EVERY_N_MILLISECONDS_I and have the 
  // times be expressed in milliseconds instead of seconds.
  {
    EVERY_N_SECONDS_I(patternTimer,gPlaylist[gCurrentTrackNumber].mTime) { 
      nextPattern(); 
      
      
      patternTimer.setPeriod( gPlaylist[gCurrentTrackNumber].mTime);
    }

    // Here's where we handle restarting the playlist if the 'reset' flag
    // has been set. There are a few steps:  
    if( gRestartPlaylistFlag ) {
      
      // Set the 'current pattern number' back to zero
      gCurrentTrackNumber = 0;
      
      // Set the playback duration for this patter to it's correct time
      patternTimer.setPeriod( gPlaylist[gCurrentTrackNumber].mTime);
      // Reset the pattern timer so that we start marking time from right now
      patternTimer.reset();
      
      // Finally, clear the gRestartPlaylistFlag flag
      gRestartPlaylistFlag = false;
    }
  }
}

#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))

void nextPattern()
{
  // add one to the current pattern number
  gCurrentTrackNumber = gCurrentTrackNumber + 1;
  
  // If we've come to the end of the playlist, we can either 
  // automatically restart it at the beginning, or just stay at the end.
  if( gCurrentTrackNumber == ARRAY_SIZE( gPlaylist) ) {
    if( gLoopPlaylist == true) {
      // restart at beginning
      gCurrentTrackNumber = 0;
    } else {
      // stay on the last track
      gCurrentTrackNumber--;
    }
  }
}
void RestartPlaylist()
{
  gRestartPlaylistFlag = true;
}
void blackS(int zeit) {
  fadeToBlackBy( leds, NUM_LEDS, zeit);
}
void blackM( int zeit) {
  fadeToBlackBy( leds2, NUM_LEDS2, zeit);
}
void sign() {
  black();
  uint8_t BeatsPerMinute = 42;
  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
  uint8_t helligkeit = beat;

  sign1(0, 192);
  sign2(160, helligkeit);
  sign3(0, 192);
  sign4(160, helligkeit);
}
void sign1( fract8 farbe, fract8 helligkeit) {
  for ( int i = 0; i < 26; i++) { //9948
    leds[i] = CHSV(farbe, 255, helligkeit);
  }
}
void sign2( fract8 farbe, fract8 helligkeit) {
  for ( int i = 26; i < 36; i++) { //9948
    leds[i] = CHSV(farbe, 255, helligkeit);
  }
}
void sign3( fract8 farbe, fract8 helligkeit) {
  for ( int i = 36; i < 63; i++) { //9948
    leds[i] = CHSV(farbe, 255, helligkeit);
  }
}
void sign4( fract8 farbe, fract8 helligkeit) {
  for ( int i = 63; i < 73; i++) { //9948
    leds[i] = CHSV(farbe, 255, helligkeit);
  }
}

Folgendes versuche ich nun umzusetzen und frage mich, ob ich mit dem "theoretischen" auf dem richtigen Weg bin.
Ich möchte, dass der kürzere der beiden Streifen durchgehend unterschiedliche Animationen von einer Playlist abspielt, so als Default Modus.
Über C# und der seriellen Schnittstelle soll ein Array oder String gesendet werden, mit welchem der Arduino arbeiten und eine andere kurze Animationen aus einer Playlist auf beiden Stripes abspielen soll. Dabei soll er den den Default modus unterbrechen und wenn und falls möglich in die anderen Animationen faden.
Sobald die Playlist durchgelaufen ist, soll er wieder zurück zu default und auf das nächste Ereignis warten.

In Zukunft soll auch eine automatische Musiksteuerung erfolgen, die ich allerdings auch über C# umsetzten möchte. Der Arduino soll nur als Empfänger und Umsetzer dienen.

Somit kommen schon einmal einige Daten die ich an den Arduino mitteilen möchte. Sachen wie Modus, BPM, Danceability (0 - 1), Vocals (0 - 1).

Gedacht habe ich an einen Case Switch, ich weiß aber nicht ob es das Sinnvollste ist, was ich tun kann, zumal ich keine Ahnung habe, wie ich all die Werte übergeben soll. Sowohl die Werte von der Seriellen Schnittstelle als auch an die LEDs mit den Playlisten.
Immer wieder lese ich etwas von den Arrays die per Serial gelesen werden und interpretiert werden, aber alle haben da ihre eigenen Anforderungen, sodass ich nur schwer den Bespielen folgen kann, um sowas für mich um zusetzten.

Alternativ dachte ich, dass ich jede einzelne Animation in einen Case Switch setze und per C# und der seriellen Schnittstelle immer den jeweiligen Code (ala Wert 0 = aus; 1 = default ; 2 = eine farbe leuchten ; 3 = farbverlauf; ...; 66 = Im Takt wechsel) übermitteln lasse. Dazu kämen dann aber die Werte wie BPM, die nicht in jeden Case Switch hineingehören.

Also viel Text, noch mehr hab ich hier und in diversen anderen Foren gelesen und muss nochmals erwähnen, dass ich zwar vieles umgesetzt bekomme, aber es braucht Zeit und die richtigen Informationen, mit denen ich mich schwer tue.

Vielleicht hat ja jemand der Leser, hierfür eine bessere Idee und/oder Ratschläge um mein LED Projekt voran zu bringen.

Ich danke schon mal fürs lesen.
Viele Grüße

Hi

Warum zwei Stripes?
Sinniger ist's, wenn Du Beide als Einen verkabelst - da Du eh jede LED einzeln ansprechen kannst und eh beide bisherigen Stripes am Arduino angeschlossen sind, sehe ich keinen Nachteil, nur eine Instanz der WS2812B-Library anzulegen und auch nur einen Pin dafür benutzen zu müssen.

WS2812B-Animationen und 'mit schwätzen' schließt sich gegenseitig aus.
Das ist darin begründet, daß der Arduino sämtliche Interrupts deaktiviert, so lange Er Daten an den Stripe schickt - bei einer Animation wird Das ein Großteil der Zeit der Fall sein, in Der Du nicht auf eingehende Anrufe reagieren kannst.
Hier wirst Du ggf. mit einem Umweg glücklich - setze einen separaten Arduino dazwischen - Dieser dient als Puffer zwischen den beiden Seiten.
Wenn was von C# (PC?) kommt, wird Das empfangen und zwischengespeichert.
Wenn was vom Arduino kommt, dito, nur, daß Das direkt Richtung C# weiter gesendet werden kann (dort wird wohl Wer zuhören).
Der Stripe-Arduino fragt beim Puffer an, ob neue Daten vorliegen und ruft Diese dann ab - ggf. häppchenweise, um mit dem Timing nicht ins Stocken zu kommen.
Vll. kann man hier auch den Puffer-Arduino eine Art 'Daten vorhanden'-Melde-Leitung hoch ziehen lassen, daß der Stripe-Arduino nur Anfragen muß, wenn auch Was Da ist.

Du wirst Dir die Nachtwächter-Erklärung ansehen wollen - combie's Liste zum Thema State-Maschine

Mit den 18A bekommst Du schon eine ganze Menge LEDs versorgt - achte auf entsprechend dicke Kabel (absolutes Minimum 1,5mm², MEHR schadet aber ganz sicher nicht) und sei Dir bewusst, daß diese 18A getrieben werden, wenn sich zwei Drähte unbedarft berühren - egal, wie dünn Diese sind!
Das kann den Draht zum Glühen bringen und die Isolierung zum Schmelzen/Verbrennen - wenn dann noch was Brennbares in direkter Nachbarschaft rumliegt, beginnt der Spaß richtig.

Woher kommen die 'Daten'? Also BPM ect.pp.
Was soll der Arduino daraus machen?
Bedenke, daß Du nur sehr wenig Zeit hast außerhalb der Versendung der Daten der Stripes und als großartige Rechnenmaschine sind die kleinen Arduino's (AVR's) nicht verschriehen.
Auch die 0...1 Werte sehen wir float aus (also Komma-Werte) - Die brauchen recht viel Rechenzeit (und Speicher) - kannst Du Das auch als Ganzzahl senden?
(wobei man, wenn die Kommazahl als Zahlzeichen gesendet wird, Das auch in Integer umrechnen kann)

So, genug erst Mal ... Essen steht auf'm Tisch :slight_smile:

MfG

Erst einmal danke für deine ausführliche Antwort. Dem Thema State Machine bin ich schon ein paar mal begegnet, aber das Wachmann Prinzip hat es mir gut veranschaulicht.

postmaster-ino:
Warum zwei Stripes?
Sinniger ist's, wenn Du Beide als Einen verkabelst - da Du eh jede LED einzeln ansprechen kannst und eh beide bisherigen Stripes am Arduino angeschlossen sind, sehe ich keinen Nachteil, nur eine Instanz der WS2812B-Library anzulegen und auch nur einen Pin dafür benutzen zu müssen.

Ich habe zwei Inputs gewählt, wegen dem Interrupt. Ich habe mir gedacht, dass der Arduino so schneller die Informationen an die LEDS schickt, wenn er nicht so lange Wege gehen muss.
Zumal nicht immer beide Streifen an sind und so nur einer versorgt werden muss mit Informationen, was die Interrupts verkürzen sollte. Liege ich mit dem Gedanken falsch?

postmaster-ino:
WS2812B-Animationen und 'mit schwätzen' schließt sich gegenseitig aus.
Das ist darin begründet, daß der Arduino sämtliche Interrupts deaktiviert, so lange Er Daten an den Stripe schickt - bei einer Animation wird Das ein Großteil der Zeit der Fall sein, in Der Du nicht auf eingehende Anrufe reagieren kannst.

Da ich das noch nicht herausgefunden habe, in wie fern ist er stumm bei der Arbeit.
Angenommen ich schicke eine RGB Wave an einen Streifen und mit dem FastLED.show(); beginnt der Interrupt? Richtig?
In dem Fall einer RGB Wave würde er dann alle x-milisekunden interrupten? Sprich mit jedem Wechsel von LED Daten.

postmaster-ino:
Hier wirst Du ggf. mit einem Umweg glücklich - setze einen separaten Arduino dazwischen - Dieser dient als Puffer zwischen den beiden Seiten.
Wenn was von C# (PC?) kommt, wird Das empfangen und zwischengespeichert.
Wenn was vom Arduino kommt, dito, nur, daß Das direkt Richtung C# weiter gesendet werden kann (dort wird wohl Wer zuhören).
Der Stripe-Arduino fragt beim Puffer an, ob neue Daten vorliegen und ruft Diese dann ab - ggf. häppchenweise, um mit dem Timing nicht ins Stocken zu kommen.
Vll. kann man hier auch den Puffer-Arduino eine Art 'Daten vorhanden'-Melde-Leitung hoch ziehen lassen, daß der Stripe-Arduino nur Anfragen muß, wenn auch Was Da ist.

Das klingt schon mal nach einer brauchbaren Lösung.
Kann ich denn den Arduino, den ich schon habe, nicht eben diese Funktion einprogrammieren?
Also sobald er fähig ist zu empfangen, ein Signal an den Computer schickt, dass er bereit ist zum empfangen und darauf der Computer ihm mit dem Array antwortet.
Ob ich jetzt nen Puffer habe, oder den PC, der die Daten solange zurück hält, bis der Arduino bereit ist, sollte da doch egal sein, oder? Dazu kann der Arduino noch eine Antwort schicken, dass er alles empfangen hat, damit der PC auch den Puffer löscht. So würde ich die Zeitspanne, wo kein Interrupt anliegt ausnutzen. So denke ich zumindest. Aber ich bin blutiger Anfänger und hab keine Ahnung.

postmaster-ino:
Mit den 18A bekommst Du schon eine ganze Menge LEDs versorgt - achte auf entsprechend dicke Kabel (absolutes Minimum 1,5mm², MEHR schadet aber ganz sicher nicht) und sei Dir bewusst, daß diese 18A getrieben werden, wenn sich zwei Drähte unbedarft berühren - egal, wie dünn Diese sind!
Das kann den Draht zum Glühen bringen und die Isolierung zum Schmelzen/Verbrennen - wenn dann noch was Brennbares in direkter Nachbarschaft rumliegt, beginnt der Spaß richtig.

Ich schicke die ersten 1,5m den Strom über 4,5mm² als Hauptleitung und lasse dann in den ersten Streifen, mit den 117 LEDs, 3x den Strom an unterschiedlichen Punkten ein. Der zweite Streifen mit seinen 30 LEDs bekommt 1x den Strom an Anfang eingespeißt.
Die 18 Ampere kann ich ja nur dort anliegen haben, wo die dicke Leitung ist und wo sich der Strom noch nicht aufgeteilt hat. Vorausgesetzt, ich komme überhaupt auf die 18 Ampere. Hab es extra so gewählt, da ich in Zukunft auf insgesamt 300 LEDs kommen werde, aber nie die LEDs auf vollster Helligkeit betreiben werde. Hab also noch ordentlich Luft nach oben.

postmaster-ino:
Woher kommen die 'Daten'? Also BPM ect.pp.
Was soll der Arduino daraus machen?
Bedenke, daß Du nur sehr wenig Zeit hast außerhalb der Versendung der Daten der Stripes und als großartige Rechnenmaschine sind die kleinen Arduino's (AVR's) nicht verschriehen.
Auch die 0...1 Werte sehen wir float aus (also Komma-Werte) - Die brauchen recht viel Rechenzeit (und Speicher) - kannst Du Das auch als Ganzzahl senden?
(wobei man, wenn die Kommazahl als Zahlzeichen gesendet wird, Das auch in Integer umrechnen kann)

Also die Daten will ich der Spotify API entnehmen, die sämtliche Analysen schon zu der Musik generiert haben. Die bekomme ich über ein Array der genannten API. Da es mit meinem spotify konto verbunden ist, weiß es immer, wann und wo ich mich im welchem Lied befinde.
Daraus soll das C# Programm interpretieren, wann er welches Lichtprogramm steuern soll. Langsame Passagen sorgen für ruhige Szenen, als auch schnelle Passagen einen Flash aufrufen können.
Ich bin davon ausgegangen, dass ich einige Werte dem Arduino mitteile, damit nicht alle x-milisekunden durch den Computer ein neuer Modus gesendet wird, sondern der Arduino durch die bpm weiß, wann er zum beispiel einen Farbwechsel vollführen soll und so die Animation länger laufen lassen kann ohne das was über die serielle Schnittstelle kommt. Das mit den Floats ist gut zu wissen, ich kann die Werte mir auch umrechnen und in Ganze Zahlen senden. Das ist kein Problem. Wäre ich nicht drauf gekommen.

postmaster-ino:
So, genug erst Mal ... Essen steht auf'm Tisch :slight_smile:

MfG

Ich hoffe es hat gemundet und nochmals danke für die Zeit und die ausführliche Antwort.

Edit: Mir fällt ein, das ich ein Programm mit dem Namen Jinx! (und der dazu gehörigen Kommunikation über die Serielle Schnittstelle und Gladiator Script) getestet habe, mit dem ununterbrochen die verschiedensten LEDs angesprochen werden. Wieso macht sich da der Interupt nicht bemerkbar?
Die schicken vermutlich alle x-milisekunden ein Array mit den LEDs Informationen an den Arduino. Ist das für ihn leichter zu handhaben?

Hi

Interrupt ist so nicht ganz korrekt - bei dem .show() werden alle Interrupts ABGESCHALTET - also kein Timer-Ereignis (millis() wird nicht hochgezählt - bei meiner Uhr dauert eine Sekunde ~700 millis()-Ticks - der Rest geht für die Ansteuerung der WS2812B drauf) und es wird auch nicht auf eingehende Daten der seriellen Schnittstelle reagiert.
Wenn der Arduino 'in Seiner Freizeit' beim PC neue Daten anfordern kann - dann geh diesen Weg.
So hast Du nach jeder Änderung des Stripe (Animation einen Schritt weiter laufen lassen oder bei einem Blitz nach 10ms die LEDs wieder abschalten - Prinzip Nachtwächter) die Möglichkeit auf neue Daten - wenn Keine kommen, wird der 'alte Kram weiter gemacht'.

Interrupt heißt Unterbrechung - der Arduino kann auf verschiedenste Ereignisse mit einem Interrupt reagieren - allerdings immer nur auf Einen gleichzeitig und wenn bereits eine ISR läuft, werden die Anderen 'nur gemerkt' - und erst ausgeführt, wenn die aktuelle ISR beendet wurde.
GANZ WICHTIG: Bei eigenen ISR's (Interrupt Service Routine) nicht selber am I-Flag rumspielen (Interrupts zulassen sti/cli) - Das kann böse nach Hinten losgehen, da dann DIREKT der nächste Interrupt zünden kann, ohne den Aktuellen beendet zu haben.

Bei dem Gladiator-Teil (habe ich auch schon irgendwo Mal was zu gelesen ...) wird Das wohl genau so laufen, wie Du Das vorhast - sobald man den Stripe beschickt hat, holt man sich neue Daten.
Da Das IMMER die gleiche Zeit braucht, hast Du keine Aussetzer - oder eben eher immer die gleichen Aussetzer :slight_smile: Die fallen aber nicht auf, da's halt 'so ist'.

MfG