Arduino Multitasking - Teil drei

Zeichnung von David Earl

Zeichnung von David Earl

Digitale RGB-LEDs wie Neopixel eignen sich hervorragend zum Erstellen atemberaubender Displays und Lichteffekte. Aber sie in einem Projekt zu kombinieren ist nicht so einfach. Der Arduino ist eine kleine CPU, die es vorzieht, nur eine Sache gleichzeitig zu tun. Wie bringt man es also dazu, auf die Eingaben von außen zu achten und gleichzeitig all diese erstaunlichen Pixelmuster zu erzeugen?

Einige der häufigsten Fragen zu Neopixel in den Arduino -Foren sind:

  • Wie kann ich dafür sorgen, dass mein Neopixel-Projekt zuverlässig auf Tastendrücke reagiert?
  • Wie führe ich zwei (oder mehr) verschiedene Neopixel-Muster gleichzeitig aus?
  • Wie bringe ich mein Arduino dazu, andere Dinge zu tun, während das Neopixel-Muster läuft?

In diesem Leitfaden untersuchen wir Möglichkeiten, wie Sie Ihren Neopixel-Code so gestalten können, dass er reaktionsschnell und auf Multitasking reagiert.

Problem? Schleifen und Verzögerungen

In der Praxis bestehen alle Codebeispiele aus Schleifen, die die verschiedenen Phasen der Pixelanimation durchlaufen. Der Code ist so damit beschäftigt, Pixel zu aktualisieren, dass die Hauptschleife nie die Chance bekommt, Schalter zu überprüfen.

Aber ist er wirklich beschäftigt? Tatsächlich verbringt Code die meiste Zeit damit, nichts zu tun! Dies liegt daran, dass die Verzögerung dank der Funktion delay () funktioniert. Wie wir in Teil Eins gesehen haben, ist die Verzögerung buchstäblich Zeitverschwendung. Daher werden wir es los.

Aber die Schleifen bleiben immer noch. Wenn Sie versuchen, eine Schleife zu schreiben, die zwei Animationsschemata gleichzeitig zeigt, stürzt der Code schnell ab. Aus diesem Grund müssen wir auch Schleifen vermeiden.

Loop-Dekonstruktion

Geben Sie nicht auf

Eine häufig vorgeschlagene Lösung zur Behebung des Antwortproblems besteht darin, die Schalter in der Schleife zu überprüfen und sie zu beenden, wenn die Taste gedrückt wird. Die Schalterprüfung kann sorgfältig auf die Verzögerung bezogen werden. Und die Schleife kann so umgeschrieben werden, dass sie vorzeitig aufhört zu arbeiten.

Dadurch wird Ihr Programm reaktionsschneller. Allerdings gibt es bei dieser Methode mehrere Probleme:

  • Sie verwenden immer noch die Funktion delay (), sodass Sie immer noch nicht zwei (oder mehr) Muster gleichzeitig ausführen.
  • Was ist, wenn Sie die Schleife nicht wirklich überspringen möchten? Wir müssen einen Weg finden, weiter zu reagieren – ohne das System zu stören.

Helfen Interrupts?

Wir haben in Teil 2 gelernt, wie man Interrupts verwendet. Aber sie sind nicht die Antwort auf alle diese Probleme.

Interrupts helfen, die Verpflichtung zur Überprüfung von Schaltern zu beseitigen. Dadurch wird der Code etwas sauberer. Dies bedeutet jedoch nicht, dass es den Rest der oben genannten Probleme löst.

Außerhalb des Kreises denken:

All diese Schwierigkeiten können beseitigt werden. Wir müssen jedoch sowohl die Verzögerung beseitigen als auch die Schleife aufgeben. Die gute Nachricht ist, dass der endgültige Code überraschend einfach sein wird.

Das Schleifenproblem und die Verzögerung im Neopixel sind dem Servo-Sweep-Beispiel in Teil 1 sehr ähnlich. In Neopixel-Schaltplänen können wir dieselbe Methode mit einer Zustandsmaschine verwenden.

Die grundlegende Lösung besteht darin, die Formeln in der Klasse C++ zusammenzufassen. Erfassen Sie alle Zustandsvariablen als Mitgliedsvariablen und verschieben Sie den Kern der Schleifenlogik in eine Update()-Funktion, die die millis()-Funktion verwendet, um mit der Verzögerung umzugehen.

Die Aktualisierungsfunktion kann von der Funktion loop() oder vom Zeitinterrupt aufgerufen werden. Sie können bei jedem Durchlauf mehrere Schemas gleichzeitig aktualisieren und die Interaktion zwischen Benutzern gleichzeitig überwachen.

Gemeinsamer Code

In diesem Absatz werden wir einige beliebte Pixelschemata neu gestalten, darunter:

  • RainbowCycle
  • ColorWipe
  • TheaterChase
  • Scanner
  • Fader

Wir werden diese Formeln auf eine einzelne "NeoPattern" -Klasse eingrenzen, die von der Adafruit_NeoPixel -Klasse erhalten wird. So können Sie die Muster auf dem gleichen Streifen freiwillig ändern.

Und da NeoPattern von Adafruit_NeoPixel abgeleitet ist, können Sie auch alle Standardfunktionen aus der NeoPixel-Bibliothek verwenden!

Bevor wir den Mustercode implementieren, sehen wir uns an, was diese Schemas gemeinsam haben und was wir über sie wissen müssen, um sie zu verwalten. Dann können wir damit beginnen, eine Klassenstruktur zu erstellen, die sich um alle Formeln kümmert.

Mitgliedsvariablen:

Die folgende Klassendefinition listet die Mitgliedsvariablen auf, die das Muster verwenden sollen, die Richtung, in die es geht, und den aktuellen Zustand der Schemamaschine.

Tipp: Die Richtungsoption ist hilfreich, wenn Sie mit Neopixel-Discs arbeiten, die eine Pixelreihenfolge im Uhrzeigersinn und im Uhrzeigersinn haben.

Diese Variablen stellen einen Kompromiss zwischen Geschwindigkeit und Größe dar. Der Overhead pro Pixel ist derselbe wie bei der Basisklasse Adafruit_Neopixel. Die meisten Musterzustandsvariablen sind den meisten Mustern gemeinsam.

Wenn auf dem Arduino begrenzter SRAM verfügbar ist, werden einige Berechnungen "on the fly" in den Update () -Funktionen durchgeführt und sparen zusätzlichen Platz für mehr Pixel!

Rückrufe im Abschluss:

Die Callback-Funktion "OnComplete ()" wird, falls vorhanden, am Ende jeder Version des Musters aufgerufen. Sie können den Rückruf angeben, um eine beliebige Aktion auszuführen – einschließlich der Änderung des Musters oder Status der Aktion. Wir zeigen Ihnen später, wie es funktioniert.

 #include 

// Unterstützte Mustertypen:
Aufzählungsmuster {NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE};
// Unterstützte Musterrichtungen:
enum Richtung {FORWARD, REVERSE};

// NeoPattern-Klasse - abgeleitet von der Adafruit_NeoPixel-Klasse
Klasse NeoPatterns: öffentlich Adafruit_NeoPixel
{
    öffentlich:

    // Mitgliedsvariablen:  
    Muster ActivePattern; // welches Muster läuft
    Richtung Richtung; // Richtung zum Ausführen des Musters
    
    unsigned long Intervall; // Millisekunden zwischen Aktualisierungen
    unsigned long lastUpdate; // Letzte Aktualisierung der Position
    
    uint32_t Farbe1, Farbe2; // Welche Farben verwendet werden
    uint16_t Gesamtschritte; // Gesamtzahl der Schritte im Muster
    uint16_t-Index; // aktueller Schritt innerhalb des Musters
    
    void (* OnComplete) (); // Rückruf nach Beendigung des Musters

Konstrukteur:

Der Konstruktor initialisiert die Basisklasse und (optional) einen Zeiger auf die Callback-Funktion.

#include 

    // Konstruktor - ruft den Konstruktor der Basisklasse auf, um Strip zu initialisieren
    NeoPatterns (uint16_t Pixel, uint8_t Pin, uint8_t Typ, void (* Callback) ())
    : Adafruit_NeoPixel (Pixel, Pin, Typ)
    {
        OnComplete = Rückruf;
    }

Updater:

Die Update()- Funktion ist der Update()-Funktion in Teil 1 sehr ähnlich. Es prüft die seit der letzten Aktualisierung verstrichene Zeit und kehrt sofort zurück, wenn es noch nichts tun muss.

Wenn es feststellt, dass es an der Zeit ist, das Schema zu aktualisieren, prüft es die Member-Variable ActivePattern , um zu entscheiden, welche musterspezifische Aktualisierungsfunktion aufgerufen werden soll.

Wir müssen für jedes Schema in der Liste eine Initialisierungsfunktion und eine Update()-Funktion schreiben.

 // Aktualisiere das Muster
    void Aktualisieren ()
    {
        if ((millis () - lastUpdate)> Interval) // Zeit zum Aktualisieren
        {
            lastUpdate = millis ();
            Schalter (ActivePattern)
            {
                Fall RAINBOW_CYCLE:
                    RainbowCycleUpdate ();
                    brechen;
                Fall THEATER_CHASE:
                    TheaterChaseUpdate ();
                    brechen;
                Fall COLOR_WIPE:
                    ColorWipeUpdate ();
                    brechen;
                Fall SCANNER:
                    ScannerUpdate ();
                    brechen;
                Fall FADE:
                    FadeUpdate ();
                    brechen;
                Ursprünglich:
                    brechen;
            }
        }
    }

Musterinkrement

Die Funktion Increment () betrachtet den aktuellen Status der Richtung und erhöht oder verringert den Index entsprechend. Wenn es das Ende des Musters erreicht, wird es umgekehrt, um das Muster neu zu starten. Die Callback-Funktion OnComplete() wird aufgerufen, wenn sie nicht null ist.

 // Inkrementiere den Index und setze am Ende zurück
    void Inkrement ()
    {
        if (Richtung == VORWÄRTS)
        {
           Index ++;
           if (Index> = Gesamtschritte)
            {
                Index = 0;
                if (OnComplete! = NULL)
                {
                    OnComplete (); // Rufen Sie den Completion-Callback auf
                }
            }
        }
        sonst // Richtung == RÜCKWÄRTS
        {
            --Index;
            wenn (Index <= 0)
            {
                Index = Gesamtschritte-1;
                if (OnComplete! = NULL)
                {
                    OnComplete (); // Rufen Sie den Completion-Callback auf
                }
            }
        }
    }

RainbowCycle

Rainbow Cycle verwendet einen farbigen Kreis, um einen Regenbogeneffekt zu erzeugen, der die Länge des Streifens umkreist. Dies ist eine einfache Strukturänderung des RainbowCycle-Musters im Strand Test Sketch Library Sample.

Initialisierung:

Die RainbowCycle()- Funktion initialisiert die NeoPatterns-Klasse, um das Rainbow Cycle-Muster auszuführen.

ActivePattern ist auf RAINBOW_CYCLE eingestellt.

Der Parameter "Intervall" gibt die Anzahl der Millisekunden zwischen Aktualisierungen an, die die Geschwindigkeit des Musters bestimmen.

Der Parameter "dir" ist zusätzlich. Es ist standardmäßig auf die Operation FORWARD eingestellt, aber Sie können es jederzeit auf REVERSE ändern.

TotalSteps ist auf 255 eingestellt, was die Anzahl der Farben im Farbkreis angibt.

Der Index wird auf 0 gesetzt, also beginnen wir mit der ersten Farbe im Kreis.

 // Für einen RainbowCycle initialisieren
    void RainbowCycle (uint8_t Intervall, Richtung dir = FORWARD)
    {
        ActivePattern = RAINBOW_CYCLE;
        Intervall = Intervall;
        Gesamtschritte = 255;
        Index = 0;
        Richtung = dir;
    }

Aktualisieren:

Die Funktion RainbowCycleUpdate () setzt jedes Pixel im Streifen auf die Farbe des farbigen Kreises. Der Startpunkt auf dem Kreis wird durch den Index hervorgehoben.

Die Funktion Increment() wird aufgerufen, um den Index zu aktualisieren, nachdem in den Balken geschrieben wurde, der die Funktion show() aufruft.

 // Aktualisieren Sie das Rainbow Cycle-Muster
    void RainbowCycleUpdate ()
    {
        for (int i = 0; i 

ColorWipe

Das ColorWipe-Muster erzeugt einen Maleffekt entlang des Streifens.

Initialisierung:

Die ColorWipe()- Funktion initialisiert das NeoPattern-Objekt, um ein ColorWipe-Muster zu erstellen.

Der Farbparameter gibt die Farbe an, die entlang der Leiste angezeigt werden soll.

Der Intervallparameter gibt die Anzahl von Millisekunden zwischen erfolgreichen Pixelanzeigen an.

Der Richtungsparameter ist optional und bestimmt die Anzeigerichtung. Pixel werden standardmäßig VORWÄRTS angezeigt.

TotalSteps wird auf die Anzahl der Pixel im Streifen gesetzt.

 // Für ein ColorWipe initialisieren
    void ColorWipe (uint32_t color, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = COLOR_WIPE;
        Intervall = Intervall;
        TotalSteps = numPixels ();
        Farbe1 = Farbe;
        Index = 0;
        Richtung = dir;
    }

Aktualisieren:

Die Funktion ColorWipeUpdate () setzt das nächste Pixel im Streifen, ruft dann show () auf, um in den Streifen zu schreiben, und Increment () , um den Index zu aktualisieren.

 // Aktualisiere das Color-Wipe-Muster
    void ColorWipeUpdate ()
    {
        setPixelColor (Index, Farbe1);
        Show();
        Zuwachs ();
    }

TheaterChase

Das TheaterChase-Muster imitiert das klassische Muster der Theaterplakate der 1950er Jahre. Bei dieser Variante können Sie die Front- und Rücklichtfarbe bestimmen.

Initialisierung:

Die TheaterChase()- Funktion initialisiert das NeoPattern-Objekt, um das TheaterChase-Schema auszuführen.

Die Parameter color1 und color2 geben die Vorder- und Hintergrundfarbe des Musters an.

Der Inkrementparameter gibt die Anzahl von Millisekunden zwischen Aktualisierungen des Musters an und steuert die Geschwindigkeit des "Chase"-Effekts.

Der Richtungsparameter ist optional und ermöglicht es Ihnen, das FORWARD- (Standard) oder REVERSE-Schema zu starten.
TotalSteps legt die Anzahl der Pixel im Streifen fest.
 // Für eine Theaterjagd initialisieren
    void TheaterChase (uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Intervall = Intervall;
        TotalSteps = numPixels ();
        Farbe1 = Farbe1;
        Farbe2 = Farbe2;
        Index = 0;
        Richtung = dir;
   }

Aktualisieren:

Die Funktion TheaterChaseUpdate () erweitert das Muster um ein Pixel. Jedes dritte Pixel bekommt die Frontfarbe ( color1 ). Der Rest erhält Farbe2.

Die Funktion Increment () wird aufgerufen, um den Index zu aktualisieren, nachdem die Pixelfarben mit der Funktion show () zum Balken hinzugefügt wurden.

 // Aktualisiere das Theatre Chase Pattern
    void TheaterVerfolgungUpdate ()
    {
        for (int i = 0; i 

Scanner

Das Scanner-Schema besteht aus einer einzelnen LED, die sich hin und her bewegt und eine Spur verblassender LEDs hinterlässt.

Initialisierung:

Die Scanner()- Funktion initialisiert das NeoPattern-Objekt, um ein Scanner-Muster auszuführen.

Der Farbparameter   ist die Farbe eines sich bewegenden Pixels.

Der Intervallparameter gibt die Anzahl der Millisekunden zwischen Aktualisierungen an und steuert die Geschwindigkeit des „Scannens“.

Ein Scan ist die gesamte Hin- und Rückfahrt von einem Ende des Streifens zum anderen und wieder zurück, ohne zusätzliche Wartezeiten an den Enden der Pixel. Total Steps wird also auf die doppelte Anzahl Pixel minus 2 gesetzt.

 // Für einen SCANNER initialisieren
    void Scanner (uint32_t color1, uint8_t interval)
    {
        ActivePattern = SCANNER;
        Intervall = Intervall;
        TotalSteps = (Strip-> numPixels () - 1) * 2;
        Farbe1 = Farbe1;
        Index = 0;
    }

Aktualisieren:

Die Funktion ScannerUpdate() wiederholt sich immer wieder zwischen den Pixeln im Streifen . Wenn das Pixel mit dem aktuellen Indexwert oder dem TotalSteps - Index-Wert übereinstimmt, setzen wir das Pixel auf color1.

Andernfalls "dunkeln" wir das Pixel ab, wodurch ein verblassender Schmiereffekt entsteht.

Wie alle anderen Muster ruft ScannerUpdate die Funktion show() auf, um sie an den Balken anzuhängen, und die Funktion Increment() , um den Automaten zu erweitern.

 // Aktualisieren Sie das Scannermuster
    void ScannerUpdate ()
    { 
        for (int i = 0; i 

Fader

Das Fader-Muster ist einer der am häufigsten nachgefragten Neopixel-Effekte in Foren. Dieses Muster erzeugt einen sanften linearen Übergang von einer Farbe zur anderen.

Initialisierung:

Die Fade()- Funktion initialisiert das NeoPattern-Objekt, um das Unschärfemuster auszuführen.

Der Parameter color1 gibt die Startfarbe an.

color2 gibt die Endfarbe an.

Schritte gibt die Anzahl der Schritte von Farbe1 bis Farbe2 an.

Das Intervall gibt die Anzahl der Millisekunden zwischen den Schritten an und steuert die Geschwindigkeit der Unschärfe.

Richtung ermöglicht es Ihnen, die Unschärfe umzukehren, sodass sie von Farbe2 zu Farbe1 geht.

 // Für einen Fade initialisieren
    void Fade (uint32_t color1, uint32_t color2, uint16_t Schritte, uint8_t Intervall, Richtung dir = FORWARD)
    {
        Aktives Muster = FADE;
        Intervall = Intervall;
        TotalSteps = Schritte;
        Farbe1 = Farbe1;
        Farbe2 = Farbe2;
        Index = 0;
        Richtung = dir;
    }

Aktualisieren:

Die Funktion FadeUpdate() setzt alle Pixel im Streifen auf die Farbe, die dem nächsten Schritt entspricht.

Farbe wird als lineare Interpolation zwischen den roten, grünen und blauen Elementen von Farbe1 und Farbe2 berechnet. Zur Beschleunigung verwenden wir ganze Zahlen. Um Fehler zu minimieren, haben wir die Division zum Schluss gelassen.

Wie alle anderen Muster schreibt ScannerUpdate mit der Funktion show() in den Balken, die Funktion Increment() bewegt die Zustandsmaschine.

 // Aktualisiere das Fade-Muster
    void FadeUpdate ()
    {
        uint8_t red = ((Red (Color1) * (TotalSteps - Index)) + (Red (Color2) * Index)) / TotalSteps;
        uint8_t green = ((Green (Color1) * (TotalSteps - Index)) + (Green (Color2) * Index)) / TotalSteps;
        uint8_t blue = ((Blue (Color1) * (TotalSteps - Index)) + (Blue (Color2) * Index)) / TotalSteps;
        ColorSet (Farbe (rot, grün, blau));
        Show();
        Zuwachs ();
    }

Gemeinsame Hilfsfunktionen

Wir werden nun einige allgemeine Hilfsfunktionen hinzufügen, die von jedem Muster verwendet werden können:

Zuerst haben wir die Funktionen Red (), Blue () und Green (). Sie sind die Umkehrung der Funktion Neopixel Color (). Sie ermöglichen es Ihnen, rote, blaue und grüne Elemente aus einer Pixelfarbe zu extrahieren.

 // Gibt die Rot-Komponente einer 32-Bit-Farbe zurück
    uint8_t Rot (uint32_t Farbe)
    {
        return (Farbe >> 16) & 0xFF;
    }

    // Gibt die grüne Komponente einer 32-Bit-Farbe zurück
    uint8_t Grün (uint32_t Farbe)
    {
        return (Farbe >> 8) & 0xFF;
    }

    // Gibt die blaue Komponente einer 32-Bit-Farbe zurück
    uint8_t Blau (uint32_t Farbe)
    {
        Farbe zurückgeben & 0xFF;
    }

Die Funktion DimColor () verwendet die Funktionen Red (), Green () und Blue () , um die Farbe zu trennen und in ihre gedimmte Version umzuwandeln. Es verschiebt die roten, grünen und blauen Elemente einer Pixelfarbe um ein Bit nach rechts - und teilt sie effektiv in 2. Rot, Grün und Blau "verfallen" nach dem achten Aufruf, da es sich um 8-Bit-Werte handelt.

Diese Funktion wird verwendet, um einen verblassenden Wischeffekt im Scanner-Muster zu erzeugen.

 // Farbe zurückgeben, um 75 % gedimmt (vom Scanner verwendet)
    uint32_t DimColor (uint32_t-Farbe)
    {
        uint32_t dimColor = Color (Rot (Farbe) >> 1, Grün (Farbe) >> 1, Blau (Farbe) >> 1);
        dimColor zurückgeben;
    }

Die nächste Funktion ist Wheel() , die im RainbowCycle-Muster verwendet wird. Es stammt aus dem StrandTest-Beispiel.

 // Geben Sie einen Wert von 0 bis 255 ein, um einen Farbwert zu erhalten.
    // Die Farben sind ein Übergang r - g - b - zurück zu r.
    uint32_t Rad (Byte WheelPos)
    {
        WheelPos = 255 - RadPos;
        wenn (WheelPos <85)
        {
            Farbe zurückgeben (255 - WheelPos * 3, 0, WheelPos * 3);
        }
        Sonst wenn (WheelPos <170)
        {
            WheelPos - = 85;
            Farbe zurückgeben (0, WheelPos * 3, 255 - WheelPos * 3);
        }
        anders
        {
            WheelPos - = 170;
            Farbe zurückgeben (WheelPos * 3, 255 - WheelPos * 3, 0);
        }
    }

Reverse () kehrt die Richtung des Musters um.

 // Richtung des Musters umkehren
    Nichtig Rückwärts ()
    {
        if (Richtung == VORWÄRTS)
        {
            Richtung = RÜCKWÄRTS;
            Index = Gesamtschritte-1;
        }
        anders
        {
            Richtung = VORWÄRTS;
            Index = 0;
        }
    }

Schließlich haben wir noch eine ColorSet()- Funktion, die alle Pixel synchron auf die gleiche Farbe setzt. Es wird vom Fader-Pattern verwendet.

 // Alle Pixel auf eine Farbe setzen (synchron)
    void ColorSet (uint32_t-Farbe)
    {
        for (int i = 0; i 

Verbindung

Der Beispielcode in diesem Handbuch wurde unter Verwendung von 16- und 24-Pixel-Reifen und 16-Pixel-Streifen (eigentlich ein Paar Neopixel-Streifen) entwickelt, die mit 3 separaten Arduino-Pins verbunden sind, wie im folgenden Diagramm gezeigt.

Die Tasten sind mit den Pins 8 und 9 verbunden, um die Reaktionsfähigkeit auf Benutzereingaben zu demonstrieren.

Schaltplan

Verwenden von NeoPatterns

Deklarieren Sie NeoPatterns

Bevor wir beginnen, müssen wir einige NeoPattern-Objekte deklarieren, um unsere Pixel zu steuern.

Unter Verwendung der Schaltpläne aus dem vorherigen Absatz initialisieren wir einen 16-Pixel- und einen 24-Pixel-Rahmen und einen 16-Pixel-Streifen, der aus zwei 8-Pixel- Streifen besteht.

 // Definiere einige NeoPatterns für die beiden Ringe und den Stick
// und übergeben Sie die Adresse der zugehörigen Abschlussroutinen
NeoPatterns Ring1 (24, 5, NEO_GRB + NEO_KHZ800 und Ring1Complete);
NeoPatterns Ring2 (16, 6, NEO_GRB + NEO_KHZ800 und Ring2Complete);
NeoPatterns-Stick (16, 7, NEO_GRB + NEO_KHZ800 und StickComplete);

Lass uns anfangen

Wie bei den NeoPixel-Streifen müssen wir die Funktion begin () aufrufen, um alles zu initialisieren, bevor wir die Streifen verwenden. Wir werden auch ein Paar Eingangsstifte für unsere Schaltflächen einrichten und ein Anfangsmuster für jeden Streifen starten.

 // Alles initialisieren und startbereit machen
Void-Setup ()
{
    // Alle PixelStrips initialisieren
    Ring1.begin ();
    Ring2.begin ();
    Stick.begin ();
    
    // Interne Pullups an den Schaltereingängen aktivieren
    PinMode (8, INPUT_PULLUP);
    PinMode (9, INPUT_PULLUP);
    
    // Starten Sie ein Muster
    Ring1.TheaterVerfolgung (Ring1.Color (255.255,0), Ring1.Color (0.0.50), 100);
    Ring2.RainbowCycle (3);
    Ring2.Farbe1 = Ring1.Farbe1;
    Stick.Scanner (Ring1.Color (255.0.0), 55);
}

Aktualisieren von Mustern

Damit die Dinge nach Ihren Wünschen laufen, müssen Sie regelmäßig die Funktion Update () für jedes NeoPattern aufrufen. Dies kann in der Funktion loop() erfolgen, wie wir es gezeigt haben, oder im Timer-Interrupt-Handler, wie wir es in Teil 2 vorgestellt haben. Dies ist möglich, weil wir die internen Schleifen und Verzögerungen aus den Mustern entfernt haben.

Der einfachste Weg wäre, einfach ein Feature-Update für jedes von Ihnen angegebene NeoPatterns aufzurufen.

 // Hauptschleife
leere Schleife ()
{
    // Aktualisiere die Ringe.
    Ring1.Update ();
    Ring2.Update ();    
    Stick.Update ();  
}

Benutzerinteraktionen

Ohne innere Schleifen reagiert Ihr Code weiterhin auf Benutzerinteraktionen oder externe Ereignisse. Sie können Muster und Farben basierend auf diesen Ereignissen auch sofort ändern.
Um es interessanter zu machen, werden wir diese Fähigkeit nutzen und unsere Schleife erweitern, um zu reagieren, wenn eine Taste neben beiden Tasten gedrückt wird:

Drücken der Taste # 1 (Pin 8):

  • ändert das Ring1-Muster von THEATER_CHASE auf FADE
  • ändert die RainbowCycle-Geschwindigkeit auf Ring2
  • färbt den Balken rot

Drücken der Taste # 2 (Pin 9):

  • ändert das Ring1-Muster von THEATER_CHASE zu COLOR_WIPE

  • ändert das Ring2-Muster von THEATER_CHASE zu COLOR_WIPE

Wenn keine Taste gedrückt wird, werden alle Muster standardmäßig fortgesetzt.

 // Hauptschleife
leere Schleife ()
{
    // Aktualisiere die Ringe.
    Ring1.Update ();
    Ring2.Update ();    
    
    // Muster auf Knopfdruck wechseln:
    if (digitalRead (8) == LOW) // Taste # 1 gedrückt
    {
        // Schalte Ring1 auf FASE-Muster
        Ring1.ActivePattern = FADE;
        Ring1.Intervall = 20;
        // Beschleunige den Regenbogen auf Ring2
        Ring2.Intervall = 0;
        // Stick auf ganz rot stellen
        Stick.ColorSet (Stick.Color (255, 0, 0)); } Else if (digitalRead (9) == LOW) // Taste Nr. 2 gedrückt {// Wechseln Sie zu abwechselnden Farbwischern auf Ring1 und 2 Ring1.ActivePattern = COLOR_WIPE; Ring2.ActivePattern = COLOR_WIPE; Ring2.TotalSteps = Ring2.numPixels (); // Und den Stick aktualisieren Stick.Update (); } else // Zurück zum Normalbetrieb {// Alle Musterparameter auf normale Werte zurücksetzen Ring1.ActivePattern = THEATER_CHASE; Ring1.Intervall = 100; Ring2.ActivePattern = RAINBOW_CYCLE; Ring2.TotalSteps = 255; Ring2.Interval = min (10, Ring2.Interval); // Und den Stick aktualisieren Stick.Update (); }}

Rückrufe abschließen

Jedes Muster, sich selbst überlassen, wird sich immer und immer wieder wiederholen. Die optionale Callback-Beendigung gibt Ihnen die Möglichkeit, einige Aktionen am Ende jedes Musterzyklus auszuführen.
Die beim Callback-Abschluss durchgeführte Aktion kann einige Aspekte des Musters ändern oder einige externe Ereignisse auslösen.

Ring1-Rückrufe abgeschlossen

Der Abschluss von Ring1-Rückrufen wirkt sich auf den Betrieb von Ring1 und Ring2 aus. Es hebt Schaltfläche Nr. 2 hervor. Wenn es gedrückt wird, dann:

  • es wird Ring2 beschleunigen, indem es sein Intervall verringert. Dies wird sie effektiv "aufwecken".

  • es wird Ring1 verlangsamen und sein Intervall erhöhen. Dies wird sie effektiv einschlafen lassen.

  • Es wird eine zufällige Farbe aus dem Kreis für die nächste Version des Ring1-Musters auswählen.


Wenn keine Taste gedrückt wird, ändert sich nur die Richtung des Standardmusters.

 // Ring1 Abschluss-Callback
nichtig Ring1Complete ()
{
    if (digitalRead (9) == LOW) // Button # 2 gedrückt
    {
        // Alternative Farbwischmuster mit Ring2
        Ring2.Intervall = 40;
        Ring1.Color1 = Ring1.Wheel (zufällig (255));
        Ring1.Intervall = 20000;
    }
    sonst // Zum Normalzustand zurückkehren
    {
      Ring1.Reverse ();
    }
}

Ring2-Rückrufe abgeschlossen

Das Abschließen der Rückrufe von Ring2 schließt das Abschließen der Rückrufe von Ring1 ab.
Wenn Taste Nr. 2 gedrückt wird, dann:

  • es wird Ring1 beschleunigen, indem es sein Intervall verringert. Dies wird sie effektiv "aufwecken".

  • es wird Ring2 verlangsamen und sein Intervall erhöhen. Dies wird sie effektiv einschlafen lassen.

  • Es wird eine zufällige Farbe aus dem Kreis für die nächste Version des Ring2-Musters auswählen.


Wenn die Taste nicht gedrückt wird, wird einfach eine zufällige Geschwindigkeit für die nächste Version des standardmäßigen RainbowCycle Ring2-Musters ausgewählt.

 // Ring 2 Abschluss Callback
nichtig Ring2Complete ()
{
    if (digitalRead (9) == LOW) // Button # 2 gedrückt
    {
        // Alternative Farbwischmuster mit Ring1
        Ring1.Intervall = 20;
        Ring2.Color1 = Ring2.Wheel (zufällig (255));
        Ring2.Intervall = 20000;
    }
    sonst // Zum Normalzustand zurückkehren
    {
        Ring2.RainbowCycle (zufällig (0,10));
    }
}

Bar-Callbacks abschließen

Durch das Abschließen von Bar-Callbacks wird eine zufällige Farbe aus dem Kreis für den nächsten Scanmusterdurchlauf ausgewählt.

 // Stick-Vervollständigungs-Callback
void StickComplete ()
{
    // Zufällige Farbänderung für den nächsten Scan
    Stick.Color1 = Stick.Wheel (zufällig (255));
}

Jetzt alles zusammenfügen

Der folgende Code enthält die vollständige NeoPattern-Klasse zusammen mit einigen Beispielcodes in Form eines Arduino-Sketch. Kopieren Sie diesen Code und fügen Sie ihn in die IDE ein und verbinden Sie Ihre Pixel wie im Verbindungsdiagramm gezeigt.

 #include 

// Unterstützte Mustertypen:
Aufzählungsmuster {NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE};
// Unterstützte Musterrichtungen:
enum Richtung {FORWARD, REVERSE};

// NeoPattern-Klasse - abgeleitet von der Adafruit_NeoPixel-Klasse
Klasse NeoPatterns: öffentlich Adafruit_NeoPixel
{
    öffentlich:

    // Mitgliedsvariablen:  
    Muster ActivePattern; // welches Muster läuft
    Richtung Richtung; // Richtung zum Ausführen des Musters
    
    unsigned long Intervall; // Millisekunden zwischen Aktualisierungen
    unsigned long lastUpdate; // Letzte Aktualisierung der Position uint32_t Color1, Color2; // Welche Farben werden verwendet uint16_t TotalSteps; // Gesamtzahl der Schritte im Muster uint16_t Index; // aktueller Schritt innerhalb des Musters void (* OnComplete) (); // Rückruf bei Abschluss des Musters // Konstruktor – ruft den Konstruktor der Basisklasse auf, um NeoPatterns (uint16_t Pixel, uint8_t Pin, uint8_t Typ, void (* Callback) ()) zu initialisieren: Adafruit_NeoPixel (Pixel, Pin, Typ) {OnComplete = Rückrufen; } // Aktualisiere das Muster void Update () {if ((millis () - lastUpdate)> Interval) // Zeit zum Aktualisieren {lastUpdate = millis (); Schalter (ActivePattern) {Fall RAINBOW_CYCLE: RainbowCycleUpdate (); brechen; case THEATER_CHASE: TheaterVerfolgungUpdate (); brechen; Fall COLOR_WIPE: ColorWipeUpdate (); brechen; case SCANNER: ScannerUpdate (); brechen; case FADE: FadeUpdate (); brechen; Standard: Unterbrechung; }}} // Index erhöhen und am Ende zurücksetzen void Increment () {if (Direction == FORWARD) {Index ++; if (Index> = TotalSteps) {Index = 0; if (OnComplete! = NULL) {OnComplete (); // Callback der Completion aufrufen}}} else // Direction == REVERSE {--Index; if (Index <= 0) {Index = TotalSteps-1; if (OnComplete! = NULL) {OnComplete (); // Callback der Completion aufrufen}}}} // Musterrichtung umkehren void Reverse () {if (Direction == FORWARD) {Direction = REVERSE; Index = Gesamtschritte-1; } Else {Richtung = VORWÄRTS; Index = 0; }} // Initialisieren für einen RainbowCycle Void RainbowCycle (uint8_t Intervall, Richtung Dir = FORWARD) {ActivePattern = RAINBOW_CYCLE; Intervall = Intervall; Gesamtschritte = 255; Index = 0; Richtung = dir; } // Aktualisiere das Rainbow Cycle Pattern void RainbowCycleUpdate () {for (int i = 0; i > 1, Grün (Farbe) >> 1, Blau (Farbe) >> 1); dimColor zurückgeben; } // Setze alle Pixel auf eine Farbe (synchron) void ColorSet (uint32_t color) {for (int i = 0; i > 16) & 0xFF; } // Gibt die grüne Komponente einer 32-Bit-Farbe zurück uint8_t Green (uint32_t color) {return (color >> 8) & 0xFF; } // Gibt die blaue Komponente einer 32-Bit-Farbe zurück uint8_t Blue (uint32_t color) {return color & 0xFF; } // Geben Sie einen Wert von 0 bis 255 ein, um einen Farbwert zu erhalten. // Die Farben sind ein Übergang r - g - b - zurück zu r. Uint32_t Wheel (Byte WheelPos) {WheelPos = 255 - WheelPos; if (WheelPos <85) {return Color (255 - WheelPos * 3, 0, WheelPos * 3); } Else if (WheelPos <170) {WheelPos - = 85; Farbe zurückgeben (0, WheelPos * 3, 255 - WheelPos * 3); } Sonst {WheelPos - = 170; Farbe zurückgeben (WheelPos * 3, 255 - WheelPos * 3, 0); }}}; void Ring1Complete (); void Ring2Complete (); void StickComplete (); // Definiere einige NeoPatterns für die zwei Ringe und den Stick // sowie einige Vervollständigungsroutinen NeoPatterns Ring1 (24, 5, NEO_GRB + NEO_KHZ800, & Ring1Complete); NeoPatterns Ring2 (16, 6, NEO_GRB + NEO_KHZ800 und Ring2Complete); NeoPatterns-Stick (16, 7, NEO_GRB + NEO_KHZ800 und StickComplete); // Alles initialisieren und den Start vorbereiten void setup () {Serial.begin (115200); PinMode (8, INPUT_PULLUP); PinMode (9, INPUT_PULLUP); // Alle pixelStrips initialisieren Ring1.begin (); Ring2.begin (); Stick.begin (); // Starten Sie ein Muster Ring1.TheaterVerfolgung (Ring1.Color (255,255,0), Ring1.Color (0,0,50), 100); Ring2.RainbowCycle (3); Ring2.Farbe1 = Ring1.Farbe1; Stick.Scanner (Ring1.Color (255.0.0), 55); } // Hauptschleife void loop () {// Aktualisiere die Ringe. Ring1.Update (); Ring2.Update (); // Muster auf Knopfdruck umschalten: if (digitalRead (8) == LOW) // Taste Nr. 1 gedrückt {// Ring1 auf FADE-Muster umschalten Ring1.ActivePattern = FADE; Ring1.Intervall = 20; // Beschleunigung des Regenbogens auf Ring2 Ring2.Interval = 0; // Stick auf alles Rot setzen Stick.ColorSet (Stick.Color (255, 0, 0)); } Else if (digitalRead (9) == LOW) // Taste Nr. 2 gedrückt {// Wechseln Sie zu abwechselnden Farbwischern auf Ring1 und 2 Ring1.ActivePattern = COLOR_WIPE; Ring2.ActivePattern = COLOR_WIPE; Ring2.TotalSteps = Ring2.numPixels (); // Und den Stick aktualisieren Stick.Update (); } else // Zurück zum Normalbetrieb {// Alle Musterparameter auf normale Werte zurücksetzen Ring1.ActivePattern = THEATER_CHASE; Ring1.Intervall = 100; Ring2.ActivePattern = RAINBOW_CYCLE; Ring2.TotalSteps = 255; Ring2.Interval = min (10, Ring2.Intervall); // Und den Stick aktualisieren Stick.Update (); }} // ---------------------------------------------- -------------- // Vervollständigungsroutinen - werden bei Vervollständigung eines Musters aufgerufen // ---------------------- -------------------------------------- // Ring1 Completion Callback void Ring1Complete () {if ( digitalRead (9) == LOW) // Taste # 2 gedrückt {// Farbwischmuster mit Ring2 abwechseln Ring2.Interval = 40; Ring1.Color1 = Ring1.Wheel (zufällig (255)); Ring1.Intervall = 20000; } Else // Zurück zum Normalen {Ring1.Reverse (); }} // Ring 2 Completion Callback void Ring2Complete () {if (digitalRead (9) == LOW) // Taste # 2 gedrückt {// Farbwischmuster mit Ring1 abwechseln Ring1.Interval = 20; Ring2.Color1 = Ring2.Wheel (zufällig (255)); Ring2.Intervall = 20000; } Else // Zurück zum Normalen {Ring2.RainbowCycle (random (0,10)); }} // Stick Completion Callback void StickComplete () {// Zufälliger Farbwechsel für den nächsten Scan Stick.Color1 = Stick.Wheel (random (255)); } 

<- Teil zwei

Quelle: https://learn.adafruit.com/multi-tasking-the-arduino-part-3

Wir freuen uns auf die Zusammenarbeit mit Ihnen!