[Anfängerfrage] Bestehende Lösung für "Interrupt" per WLAN für Neopixels? Oder zwei D1 Minis koppeln für Interrupt?

Hey ihr Lieben :slight_smile:

Ich bin ein Arduino-Anfänger, der über eine offene Maker-Werkstatt gerade etwas in Programmierung/Elektronik reinschnuppert und stehe gerade vor einem für mich kompliziertem Problem:

  • Mein Aufbau: Ich habe mir mehrere Sensoren (DHT22, Bodenfeuchtigkeitssensoren, Bewegungsmelder, etc.) an Wemos D1 Minis gelötet und die übertragen ihre Daten per WLAN an eine MariaDB auf dem Raspberry Pi
  • Der Raspbery Pi verarbeitetet die Daten und gibt in die Datenbank in eine Tabelle "Aufträge"
  • Die Aktoren-D1 Minis (z.B. Steckdosen, Pumpe, Magnetventile, etc.) holen sich die Aufträge und agieren entsprechend

Jetzt will ich in dieses System auch Lichttechnik (WS2812B mit Adafruit Neopixel) integrieren, aber:

  • Wenn ich z.B. die "Rainbow" (Funktion aus Neopixel strandtest) laufen lasse, dann dauert es etwa 20 Sekunden, bis die Loop erneut beginnt und die Datenbankabfrage stattfindet, d.h. ich kann zwar Lichtänderungs-Aufträge raushauen, aber sie werden nicht sofort (<100ms) bearbeitet.

Aktuell habe ich zwei Ideen, um das Problem zu lösen:
1.) Zwei D1 Minis koppeln, so dass der eine nur dafür zuständig ist, um neue Aufträge zu checken und einen Interrupt an den zweiten schickt, um die Loop neuzubeginnen, d.h. dass der "Abspiel-D1 Mini" sich den neuen Auftrag holen kann

2.) Dass ich in jede länger andauernde Funktion (z.B. Rainbow) selbst SQL-Abfragen raushaue und mit millis() und delay es so versuche hinzubiegen, dass die Lichter trotzdem harmonisch aussehen. (Edit: Eine SQL-Abfrage dauert bei mir durchschnittlich [100 Abfragen gemittelt] 12ms)

Aaaaber... ich habe mir gedacht, dass es doch da eine Lösung bereits geben muss, oder? :sweat_smile: Ich finde nur nichts dazu...

Habt ihr eine schönere Lösung für mein Problem oder Gedanken zu den zwei Ideen?

LG LG Wolf :blush:

du musst deinen "Rainbow" Effekt "nicht blockierend" programmieren.
Also keine Delays, keine tiefen For / Do / While schleifen

Dann können deine SQL Anfragen - zwar nicht parallel - aber so schnell hintereinander geschehen, dass es für dich fast wie parallel aussieht.

Dein Code in Code Tags inklusive anklickbarer Links zu all deinen verwendeten Libraries helfen uns dir besser zu helfen.

Aber vermutlich findest das auch selber: mit einer Suchmaschine

edit:
hab da in meinem Fundus was gefunden:

// Simple demonstration on using an input device to trigger changes on your
// NeoPixels. Wire a momentary push button to connect from ground to a
// digital IO pin. When the button is pressed it will change to a new pixel
// animation. Initial state has all pixels off -- press the button once to
// start the first animation. As written, the button does not interrupt an
// animation in-progress, it works only when idle.

// by noiasca

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

// Digital IO pin connected to the button. This will be driven with a
// pull-up resistor so the switch pulls the pin to ground momentarily.
// On a high -> low transition the button press logic will execute.
constexpr byte buttonPin = A0; // was 2
constexpr byte pixelPin = 8;  // Digital IO pin connected to the NeoPixels.
constexpr byte pixelCount = 16;  // Number of NeoPixels (was 16)

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(pixelCount, pixelPin, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)

boolean oldState = HIGH;
byte mode = 0;    // Currently-active animation mode
bool active = true;
constexpr byte modeLast = 8;

void setup() {
  Serial.begin(115200);
  pinMode(buttonPin, INPUT_PULLUP);
  strip.begin(); // Initialize NeoPixel strip object (REQUIRED)
  strip.show();  // Initialize all pixels to 'off'
}

void loop() {
  // Get current button state.
  boolean newState = digitalRead(buttonPin);

  // Check if state changed from high to low (button press).
  if ((newState == LOW) && (oldState == HIGH)) {
    // Short delay to debounce button.
    delay(20);
    // Check if button is still low after debounce.
    newState = digitalRead(buttonPin);
    if (newState == LOW) {       // Yes, still low
      if (++mode > modeLast) mode = 0;  // Advance to next mode, wrap around after #8
      Serial.print(F("mode=")); Serial.println(mode);
      active = true; // activate the effect
    }
  }

  // switch is now on top level, as we have to call the function over and over again
  switch (mode) {            // Start the new animation...
    case 0:
      colorWipe(strip.Color(  0,   0,   0), 50);    // Black/off
      break;
    case 1:
      colorWipe(strip.Color(255,   0,   0), 50);    // Red
      break;
    case 2:
      colorWipe(strip.Color(  0, 255,   0), 50);    // Green
      break;
    case 3:
      colorWipe(strip.Color(  0,   0, 255), 50);    // Blue
      break;
    case 4:
      theaterChase(strip.Color(127, 127, 127), 50); // White
      break;
    case 5:
      theaterChase(strip.Color(127,   0,   0), 50); // Red
      break;
    case 6:
      theaterChase(strip.Color(  0,   0, 127), 50); // Blue
      break;
    case 7:
      rainbow(10);
      break;
    case 8:
      theaterChaseRainbow(50);
      break;
  }

  // Set the last-read button state to the old state.
  oldState = newState;
}

// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, uint16_t wait) {
  static uint32_t previousMillis = 0;      // previous timestamp of change
  static int i = 0;                        // former i from for loop
  if (active && millis() - previousMillis > wait)
  {
    previousMillis = millis();
    strip.setPixelColor(i, color);         // Set pixel's color (in RAM)
    strip.show();                          // Update strip to match
    if (i < strip.numPixels() - 1)
    {
      i++;
    }
    else
    {
      i = 0;
      active = false;        // this effect stopps
    }
  }
}

// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
  static uint32_t previousMillis = 0;
  static int a = 0;
  static int b = 0;
  if (active && millis() - previousMillis > wait)
  {
    previousMillis = millis();
    strip.clear();                   //   Set all pixels in RAM to 0 (off)
    // 'c' counts up from 'b' to end of strip in steps of 3...
    for (int c = b; c < strip.numPixels(); c += 3) {
      strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
    }
    strip.show();                    // Update strip with new contents
    if (++a >= 10) {
      a = 0;
      // active = false;             // this effect runs endless
    }
    if (++b >= 3)
    {
      b = 0;
    }
  }
}

// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(int wait) {
  static uint32_t previousMillis = 0;
  // Hue of first pixel runs 3 complete loops through the color wheel.
  // Color wheel has a range of 65536 but it's OK if we roll over, so
  // just count from 0 to 3*65536. Adding 256 to firstPixelHue each time
  // means we'll make 3*65536/256 = 768 passes through this outer loop:
  static long firstPixelHue = 0;
  if (active && millis() - previousMillis > wait) {
    for (int i = 0; i < strip.numPixels(); i++) { // For each pixel in strip...
      // Offset pixel hue by an amount to make one full revolution of the
      // color wheel (range of 65536) along the length of the strip
      // (strip.numPixels() steps):
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
      // optionally add saturation and value (brightness) (each 0 to 255).
      // Here we're using just the single-argument hue variant. The result
      // is passed through strip.gamma32() to provide 'truer' colors
      // before assigning to each pixel:
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show(); // Update strip with new contents
    if (firstPixelHue < 3 * 65536)
    {
      firstPixelHue += 256;
    }
    else
    {
      firstPixelHue = 0;
      //active = false;  // this effect runs endless
    }
  }
}

// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
  static uint32_t previousMillis = 0;
  static int a = 0;                 // Repeat 30 times...
  static int b = 0;                 //  'b' counts from 0 to 2...
  static int firstPixelHue = 0;     // First pixel starts at red (hue 0)
  strip.clear();                    //   Set all pixels in RAM to 0 (off)
  if (active && millis() - previousMillis > wait)
  {
    previousMillis = millis();
    // 'c' counts up from 'b' to end of strip in increments of 3...
    for (int c = b; c < strip.numPixels(); c += 3) {
      // hue of pixel 'c' is offset by an amount to make one full
      // revolution of the color wheel (range 65536) along the length
      // of the strip (strip.numPixels() steps):
      int      hue   = firstPixelHue + c * 65536L / strip.numPixels();
      uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
      strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
    }
    strip.show();                // Update strip with new contents
    firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
    if (a < 30)
    {
      a++;
    }
    else
    { a = 0;
      //active = false; // this effect runs endless
    }
    if (b < 3)
    {
      b++;
    }
    else
    {
      b = 0;
    }
  }
}

Die Antwort steht in #2, das wiederhole ich daher nicht :slightly_smiling_face:

Wie viele Pixel willst Du animieren?

Jedes strip.show(); schreibt die Daten ohne Unterbrechung an alle Pixel. Daraus ergibt sich eine Pause für alle anderen Aktivitäten, die länger ist, je mehr Pixel Du animierst.

@noiasca , vielen Danke für deine Antwort und auch auch dir @agmue :slight_smile:

Das war natürlich mein erster Versuch und ich habe damit auch ca. 2 Stunden verbracht, d.h. Eigeninitiative ist schon vorhanden :smiley: ich frage, weil mir googeln leider nicht geholfen hat...

Das verstehe ich auch nicht so ganz :see_no_evil: und leider auch nicht inwiefern der Code die Antwort auf meine Frage ist :confused: sorry :sweat_smile:

  if (active && millis() - previousMillis > wait) {

Diese if-Schleife verursacht doch, dass jeder Frame eine bestimmte Zeit dauert (in meinem Fall 100ms) und

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

diese Schleife, dass sie 300x (ich habe 300 LEDs @agmue :blush: ) durchläuft, d.h. die komplette rainbow-Funktion wird mindestens 30 Sekunden dauern.

Oder stehe ich komplett auf der Leitung? :sweat_smile:

Edit:

* *// As written, the button does not interrupt an* *// animation in-progress, it works only when idle.* *
Ok... Genau das versuche ich zu realisieren... eine animation in-progress zu verändern und zwar über eine Datenbankabfrage (z.B. in Helligkeit) :upside_down_face: da war meine Ursprungsfrage wohl etwas missverständlich...

nein. Das bewirkt lediglich dass alle 100ms etwas durchgeführt wird, die restlichen 99ms macht dein Controller was anderes (vereinfacht gesprochen).

Hast du den Sketch mach ausprobiert? In den Animationen kannst du jederzeit den Button betätigen und der nächste Effekt kommt.

zweiter if

nein. wird es nicht. Es werden nur die 300 Werte gesetzt, einmal upgedated und dann bist wieder im loop. Das dauert keine 30 Sekunden.

Verstehe ich nicht :joy: aber auch wenn ich es nicht verstehe, radel ich jetzt mal in den Makerspace (da habe ich meine Buttons zum Ausprobieren :blush: )

Vielleicht muss ich es sehen, um es zu verstehen ^^ danke schön :blush:

Noch mal für ganz blöde: Das heißt, dass ich statt der Button analogRead()-Funktion dort einfach meine db.checkAction()-Funktion reinhauen kann und für die Loop einen anderen switch-case wählen kann?

ja du musst nur den Modus (mode) weiterschalten.

if (++mode > modeLast) mode = 0;  // Advance to next mode, wrap around after #8

oder halt auf das setzen was du willst.

1 Like

Das sind dann 300 Pixel * 34 µs/Pixel = 10,2 ms für ein strip.show();.

Wenn Du zwischen zwei strip.show(); der restlichen Logik etwas Luft läßt, sollte das funktionieren.

Die Bibliotheksbeispiele strandtest_nodelay.ino und StrandtestBLE_nodelay.ino sollten Dich in die richtige Richtung führen. delay() und blockierende Schleifen mußt Du vermeiden.

Basierend auf strandtest_nodelay.ino habe ich eine einfache Animation erstellt, mit der die Zeit für ein Pixel-Update gemessen werden kann. Getestet mit ESP32:

#include <Adafruit_NeoPixel.h>
#define LED_PIN    16
#define LED_COUNT 300

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("\nStart ...");
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  uint32_t startzeit = micros();
  strip.show();            // Turn OFF all pixels ASAP
  Serial.printf("Zeit für ein Pixel-Update %ld µs\n", micros() - startzeit);
  strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255)
}

void loop() {
  rainbow(10); // Flowing rainbow cycle along the whole strip
}

void rainbow(uint8_t wait) {
  uint32_t currentMillis = millis();                     //  Update current time
  uint32_t pixelInterval = wait;
  static uint32_t pixelPrevious = 0;
  static uint16_t pixelCycle = 0;

  if (currentMillis - pixelPrevious >= pixelInterval) {       //  Check for expired time
    pixelPrevious = currentMillis;
    for (uint16_t i = 0; i < LED_COUNT; i++) {
      uint16_t farbe = i * 218 + pixelCycle;
      strip.setPixelColor(i, strip.ColorHSV(farbe));
    }
    strip.show();                               //  Update strip to match
    pixelCycle += 22;                           //  Advance current cycle
  }
}
1 Like

Vielen lieben Dank @noiasca und @agmue !

Mittlerweile habe ich eure Lösung verstanden :hugs: und es funktioniert!

Leider habe ich kein Array als return hinbekommen, aber mittlerweile einfach eine Query-Funktion, die ich halt mehrfach mit unterschiedlichen Argumenten laufen lasse. Dabei dauern 7 Abfragen zwischen 40 und 45ms, die ich jedoch von dem wait-Delay abziehen kann.

Ich bin begeistert :smiley: so habe ich mir das vorgestellt... also vielen Dank :blush:

Ein Beispielprogramm und eine Frage wären möglich :wink:

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.