Wielozadaniowość Arduino - część trzecia

Rysunek - David Earl 

Rysunek wyk. David Earl

 

 

Cyfrowe diody LED RGB, tak jak Neopixel, są rewelacyjne jeśli chodzi o tworzenie zachwycających wystaw i efektów świetlnych. Ale połączenie ich w jeden projekt nie jest takie łatwe. Arduino to mały procesor, który woli robić tylko jedną rzecz jednocześnie. Więc jak sprawić, aby zwrócił uwagę na zewnętrzne wejścia podczas generowania tych wszystkich niesamowitych wzorów z pikseli?

 

Niektóre z najczęstszych pytań o Neopixel na forach Arduino to:

  • Jak sprawię, że mój projekt Neopixel odpowie niezawodnie na wciśnięcie przycisku?
  • Jak uruchomię dwa (lub więcej) różnych wzorów Neopixel w tym samym czasie?
  • Co zrobić, aby moje Arduino wykonywało inne rzeczy podczas, gdy uruchomiony jest wzór Neopixel?

 

W tym przewodniku poznamy sposoby kształtowania kodu Neopixel, aby reagował i był podatny na wielozadaniowość. 

  

Problem? Pętle i opóźnienia

W praktyce, wszystkie przykłady kodów składają się z pętli, które przechodzą przez różne fazy animacji pikseli. Kod jest tak zajęty aktualizacją pikseli, że główna pętla nigdy nie ma szansy sprawdzić przełączników.

 

Lecz czy on jest naprawdę zajęty? Tak naprawdę, kod spędza większość czasu na nic nie robieniu! Jest tak, ponieważ opóżnienie działa dzięki funkcji delay(). Jak widzieliśmy w części pierwszej, opóźnienie jest dosłownie stratą czasu. Dlatego też się go pozbędziemy. 

 

Ale pętle nadal zostają. Jeśli spróbujesz napisać jedną pętlę, która zaprezentuje dwa schematy animacji jednocześnie, kod szybko przestanie działać. Z tego powodu, musimy także zrezygnować z tworzenia pętli. 

 

Dekonstrukcja pętli

Nie poddawaj się

Jedno, często sugerowane rozwiązanie problemu reakcji, to sprawdzenie przełączników w pętli i zakończenia jej, jeśli przycisk jest wciśnięty. Sprawdzenie przełącznika może być starannie powiązane z opóźnieniem. A pętlę można znów napisać tak, aby przestała działać wcześnie. 

 

To sprawi, że Twój program będzie lepiej reagował. Jednakże, istnieje kilka problemów z tą metodą:

  • Wciąż używasz funkcji delay(), więc w dalszym ciągu nie uruchomisz dwóch (lub więcej) schematów na raz.
  • A co, jeśli tak naprawdę nie chcesz rezygnować z pętli? Musimy znaleźć sposób na to, aby nadal reagować - bez zakłócania realizacji schematu. 

 

Czy przerwania pomogą? 

Nauczyliśmy się jak używać przerwań w części 2. Lecz nie są one odpowiedzią na wszystkie takie problemy.

Przerwania pomogą wyeliminować obowiązek sprawdzania przełączników. To sprawia, że kod jest trochę bardziej klarowny. Nie oznacza to jednak, że rozwiązuje on resztę problemów wymienionych powyżej.

 

Myślenie poza pętlą:

Wszystkie te utrudnienia da się usunąć. Jednakże, będziemy musieli zarówno pozbyć się opóźnienia jak i zrezygnować z pętli. Dobra wiadomość jest taka, że ostateczny kod będzie zaskakująco prosty.

 

Problem pętli i opóźnienia w Neopixel są bardzo podobne do tego w przykładzie serwa sweep, w części 1. W schematach Neopixel możemy zastosować tą samą metodę z automatem skończonym (State Machine). 

 

Podstawowe rozwiązanie to streszczenie wzorów w klasie C++. Przechwyć wszystkie zmienne stanu jako zmienne składowe i przenieś rdzeń logiki pętli do funkcji Update(), która używa funkcji millis(), aby uporać się z opóźnieniem.

 

Funkcja Update może być przywołana z funkcji loop() lub z przerwania czasowego. Możesz zaktualizować wiele schematów jednocześnie na każdym przejściu i w tym samym czasie monitorować interakcję pomiędzy użytkownikami. 

Wspólny kod

W tym akapicie przeprojektujemy kilka popularnych schematów pikseli, w tym:

  • RainbowCycle 
  • ColorWipe
  • TheaterChase
  • Skanner
  • Fader

 

Zwęzimy te wzory do pojedynczej klasy "NeoPattern" uzyskanej z klasy Adafruit_NeoPixel. Możesz więc dobrowolnie zmieniać schematy na tym samym pasku

 

A skoro NeoPattern pochodzi od Adafruit_NeoPixel, możesz również używać wszystkich standardowych funkcji z biblioteki NeoPixel!

 

Zanim zaimplementujemy kod wzoru, zobaczmy co te schematy mają ze sobą wspólnego i co musimy o nich wiedzieć, aby nimi zarządzać. Następnie możemy zacząć tworzyć strukturę klasy, która zajmie się wszystkimi wzorami.

 

Zmienne składowe:

 

Poniższa definicja klasy zawiera zmienne składowe, które mają posiadać wzór w użyciu, kierunek, w którym on idzie i obecny stan automatu schematu.

 

Wskazówka: Opcja kierunku jest pomocna przy współpracy z krążkami neopixel, które mają kolejność pikseli niezgodną i zgodną z ruchem wskazówek zegara. 

 

Zmienne te reprezentują kompromis pomiędzy prędkością a wielkością. Narzut na piksel jest taki sam jak w klasie bazowej Adafruit_Neopixel. Większość zmiennych stanu wzoru są wspólne dla znacznej części schematów. 

 

Jeśli ograniczona pamięć SRAM będzie dostępna w Arduino, to wtedy niektóre obliczenia będą zrobione "w locie" (on-the-fly) w funkcjach Update() i zaoszczędzą dodatkowe miejsce na więcej pikseli!

 

Wywołania zwrotne w zakończeniu:

 

Funkcja wywołania zwrotnego "OnComplete()", jeśli jest zainicjowana, jest przywołana na zakończenie każdej wersji wzoru. Możesz sprecyzować wywołanie zwrotne, aby wykonało jakąkolwiek akcję - włączając w to zmianę wzoru lub stanu działania. Później pokażemy jak to działa.

 

#include 

// Pattern types supported:
enum  pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum  direction { FORWARD, REVERSE };

// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
    public:

    // Member Variables:  
    pattern  ActivePattern;  // which pattern is running
    direction Direction;     // direction to run the pattern
    
    unsigned long Interval;   // milliseconds between updates
    unsigned long lastUpdate; // last update of position
    
    uint32_t Color1, Color2;  // What colors are in use
    uint16_t TotalSteps;  // total number of steps in the pattern
    uint16_t Index;  // current step within the pattern
    
    void (*OnComplete)();  // Callback on completion of pattern

  

Konstruktor:

 

Konstruktor inicjuje klasę podstawową i (opcjonalny) wskaźnik do funkcji wywołania zwrotnego.

 

#include 

    // Constructor - calls base-class constructor to initialize strip
    NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
    :Adafruit_NeoPixel(pixels, pin, type)
    {
        OnComplete = callback;
    }

 

Aktualizator:

 

Funkcja Update() działa bardzo podobnie do funkcji Update() z części 1. Sprawdza ona czas, jaki upłynął od ostatniej aktualizacji i natychmiast wraca, jeśli nie musi jeszcze nic robić.

 

Jeśli ustali, że to już czas na aktualizację schematu, zwróci uwagę na zmienną składową ActivePattern, aby zdecydować, którą funkcję aktualizacji specyficzną dla wzoru, przywołać. 

 

Będziemy musieli napisać funkcję inicjalizacji i funkcję Update() dla każdego schematu z listy. 

 

    // Update the pattern
    void Update()
    {
        if((millis() - lastUpdate) > Interval) // time to update
        {
            lastUpdate = millis();
            switch(ActivePattern)
            {
                case RAINBOW_CYCLE:
                    RainbowCycleUpdate();
                    break;
                case THEATER_CHASE:
                    TheaterChaseUpdate();
                    break;
                case COLOR_WIPE:
                    ColorWipeUpdate();
                    break;
                case SCANNER:
                    ScannerUpdate();
                    break;
                case FADE:
                    FadeUpdate();
                    break;
                default:
                    break;
            }
        }
    }

 

Inkrementowanie wzoru

Funkcja Increment () zwraca uwagę na obecny stan kierunku i zgodnie z nim zwiększa lub pomniejsza Indeks. Kiedy dociera on do końca wzoru, jest zawracany, aby zrestartować schemat. Funkcja wywołania zwrotnego OnComplete() jest przywoływana wtedy, kiedy nie jest pusta. 

 

    // Increment the Index and reset at the end
    void Increment()
    {
        if (Direction == FORWARD)
        {
           Index++;
           if (Index >= TotalSteps)
            {
                Index = 0;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
        else // Direction == REVERSE
        {
            --Index;
            if (Index <= 0)
            {
                Index = TotalSteps-1;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
    }

 

RainbowCycle

Rainbow Cycle używa kolorowego koła do stworzenia efektu tęczy, który krąży po długości paska. Jest to prosta zmiana struktury wzoru RainbowCycle w przykładzie szkicu Strand Test z biblioteki. 

Inicjalizacja:

Funkcja RainbowCycle() inicjuje klasę NeoPatterns do uruchomienia wzoru Rainbow Cycle. 

ActivePattern jest ustawiony na RAINBOW_CYCLE.

Parametr "interval" określa ilość milisekund pomiędzy aktualizacjami, co determinuje prędkość wzoru.

 

Parametr "dir" jest dodatkowy. Domyślnie jest ustawiony na operację FORWARD, ale zawsze możesz ją zmienić na REVERSE

TotalSteps jest ustawione na 255, co wskazuje liczbę kolorów w kolorowym kole. 

Index ustawiony jest na 0, więc zaczniemy z pierwszym kolorem z koła.

 

    // Initialize for a RainbowCycle
    void RainbowCycle(uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = RAINBOW_CYCLE;
        Interval = interval;
        TotalSteps = 255;
        Index = 0;
        Direction = dir;
    }

 

Aktualizacja:

Funkcja RainbowCycleUpdate() ustawia każdy piksel na pasku, na kolor z kolorowego koła. Punkt początkowy na kółku jest wyróżniony przez Index.

 

Funkcja Increment() jest przywoływana, aby zaktualizować Indeks po zapisie na pasku z przywołaniem funkcji show().

 

   // Update the Rainbow Cycle Pattern
    void RainbowCycleUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
        }
        show();
        Increment();
    }

 

ColorWipe

Wzór ColorWipe tworzy efekt malowania na długości paska.

 

Inicjalizacja:

Funkcja ColorWipe() inicjuje obiekt NeoPattern, aby stworzyć wzór ColorWipe. 

 

Parametr color określa kolor, który ma się wyświetlać wzdłuż paska.

 

Parametr intrval określa ilość milisekund pomiędzy udanymi wyświetleniami pikseli.

 

Parametr direction jest opcjonalny i określa kierunek wyświetlania. Piksele, domyślnie, będą się wyświetlać FORWARD.

 

TotalSteps jest ustawiony na taką liczbę, ile jest pikseli w pasku. 

 

// Initialize for a ColorWipe
    void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = COLOR_WIPE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color;
        Index = 0;
        Direction = dir;
    }

 

Aktualizacja:

Funkcja ColorWipeUpdate() ustawia kolejny piksel w pasku, a następnie przywołuje funkcję show(), aby zapisać do paska, i funkcję Increment(), aby zaktualizować Indeks.

 

// Update the Color Wipe Pattern
    void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }

 

TheaterChase

Schemat TheaterChase naśladuje klasyczny wzór z afiszów teatralnych z lat 50-tych ubiegłego wieku. W tym wariancie, możesz określić przednie i tylne kolory świateł.

 

Inicjalizacja:

Funkcja TheaterChase() inicjuje obiekt NeoPattern, aby wykonywał schemat TheaterChase. 

 

Parametry color1 i color2 określają kolory pierwszego planu oraz tła wzoru.

 

Parametr increment precyzuje ilość milisekund pomiędzy aktualizacjami wzoru i kontroluje prędkość efektu "pościgu".

 

Parametr direction jest opcjonalny i pozwala uruchomić schemat FORWARD (domyślnie) lub REVERSE. 
TotalSteps ustawia liczbę pikseli w pasku.
// Initialize for a Theater Chase
    void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }

Aktualizacja:

Funkcja TheaterChaseUpdate() rozwija wzór o jeden piksel. Każdy trzeci piksel uzyskuje przedni kolor (color1). Reszta uzyskuje color2. 

 

Funkcja Increment() jest przywołana do aktualizacji Index, po dopisaniu kolorów piksela do paska funkcją show().

 

// Update the Theater Chase Pattern
    void TheaterChaseUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            if ((i + Index) % 3 == 0)
            {
                setPixelColor(i, Color1);
            }
            else
            {
                setPixelColor(i, Color2);
            }
        }
        show();
        Increment();
    }

 

Skaner

Schemat skanera składa się z pojedynczej diody LED przesuwającej się w przód i w tył, zostawiając za sobą smugę zanikających diod LED.

Inicjalizacja:

Funkcja Scanner() inicjuje obiekt NeoPattern, aby wykonać wzór Skanera. 

 

Parametr color to kolor poruszającego się piksela. 

 

Parametr interval określa ilość milisekund pomiędzy aktualizacjami i kontroluje prędkość "skanu".

 

Skan to całkowita trasa w obie strony, od jednego końca paska do drugiego i z powrotem, bez dodatkowego czasu oczekiwania na końcach pikseli. A więc Total Steps jest ustawiony na podwójną liczbę pikseli, minus 2.

  

    // Initialize for a SCANNNER
    void Scanner(uint32_t color1, uint8_t interval)
    {
        ActivePattern = SCANNER;
        Interval = interval;
        TotalSteps = (Strip->numPixels() - 1) * 2;
        Color1 = color1;
        Index = 0;
    }

 

Aktualizacja: 

Funkcja ScannerUpdate() powtarza się wielokrotnie wśród pikseli na pasku. Jeśli piksel odpowiada obecnej wartości Index lub wartości TotalSteps - Index, ustawimy piksel na color1. 

 

W przeciwnym razie, "przyciemnimy" piksel, co stworzy efekt zanikającej smugi.

 

Tak jak wszystkie inne wzory, ScannerUpdate przywołuje funkcję show(), aby dopisać ją do paska, i funkcję Increment(), aby rozwinąć automat. 

 

    // Update the Scanner Pattern
    void ScannerUpdate()
    { 
        for (int i = 0; i < numPixels(); i++)
        {
            if (i == Index) // first half of the scan
            {
                Serial.print(i);
                setPixelColor(i, Color1);
            }
            else if (i == TotalSteps - Index) // The return trip.
            {
                Serial.print(i);
                setPixelColor(i, Color1);
            }
            else  // fade to black
            {
                setPixelColor(i, DimColor(getPixelColor(i)));
            }
        }
        show();
        Increment();
    }

 

Fader

Wzór Fader jest jednym z najczęściej pożądanych efektów Neopixel na forach. Ten schemat tworzy delikatne liniowe przejście z jednego do innego koloru.

 

Inicjalizacja:

Funkcja Fade() inicjalizuje obiekt NeoPattern, aby wykonać wzór rozmycia.

 

Parametr color1 określa kolor początkowy.

 

color2 precyzuje kolor końcowy. 

 

Steps określa liczbę kroków od color1 do color2.

 

interval precyzuje ilość milisekund pomiędzy krokami i kontroluje prędkość rozmycia.

 

direction pozwala odwrócić rozmycie, aby przechodziło od color2 do color1.

 

    // Initialize for a Fade
    void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = FADE;
        Interval = interval;
        TotalSteps = steps;
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
    }

  

Aktualizacja:

Funkcja FadeUpdate() ustawia wszystkie piksele na pasku, na kolor odpowiadający kolejnemu krokowi.

 

Kolor jest obliczany jako interpolacja liniowa pomiędzy czerwonymi, zielonymi i niebieskimi elementami color1 color2.  Dla przyspieszenia, użyjemy liczb całkowitych. Aby zminimalizować błędy, dzielenie zostawiliśmy na koniec.

   

Tak jak wszystkie inne wzory, ScannerUpdate zapisuje do paska funkcją show(), funkcja Increment() przesuwa automat skończony (state machine).

// Update the Fade Pattern
    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(Color(red, green, blue));
        show();
        Increment();
    }

Wspólne funkcje użytkowe

Dodamy teraz kilka wspólnych funkcji użytkowych, które mogą być użyte przez jakikolwiek wzorzec:

 

Najpierw mamy funkcje Red(), Blue() Green(). Są one odwrotnością funkcji Neopixel Color(). Pozwalają na wydobycie czerwonych, niebieskich i zielonych elementów z koloru piksela.

 

    // Returns the Red component of a 32-bit color
    uint8_t Red(uint32_t color)
    {
        return (color >> 16) & 0xFF;
    }

    // Returns the Green component of a 32-bit color
    uint8_t Green(uint32_t color)
    {
        return (color >> 8) & 0xFF;
    }

    // Returns the Blue component of a 32-bit color
    uint8_t Blue(uint32_t color)
    {
        return color & 0xFF;
    }

 

Funkcja DimColor() używa funkcji Red(), Green() Blue(), aby rozdzielić kolor i przekształcić go w swoją przyciemnioną wersję. Przesuwa czerwone, zielone i niebieskie elementy koloru piksela o jeden bit w prawo - skutecznie dzieląc go na 2. Czerwony, zielony i niebieski "wygasną" po ósmym wywołaniu, ponieważ są one wartościami 8-bitowymi. 

 

Z tej funkcji korzysta się podczas tworzenia efektu zanikającej smugi we wzorze Skanera.

 

// Return color, dimmed by 75% (used by scanner)
    uint32_t DimColor(uint32_t color)
    {
        uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
        return dimColor;
    }

 

Następną funkcją jest Wheel() używaną we wzorze RainbowCycle. Pochodzi ona z przykładu StrandTest. 

 

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

 

Funkcja Reverse() odwróci kierunek wykonania wzoru. 

 

    // Reverse direction of the pattern
    void Reverse()
    {
        if (Direction == FORWARD)
        {
            Direction = REVERSE;
            Index = TotalSteps-1;
        }
        else
        {
            Direction = FORWARD;
            Index = 0;
        }
    }

 

Na końcu mamy funkcję ColorSet(), która synchronicznie ustawia wszystkie piksele na ten sam kolor. Jest ona używana przez wzorzec Fader.

  

    // Set all pixels to a color (synchronously)
    void ColorSet(uint32_t color)
    {
        for (int i = 0; i < numPixels(); i++)
        {
            setPixelColor(i, color);
        }
        show();
    }

 

Podłączenie

Przykładowy kod w tym przewodniku został opracowany przy użyciu 16 i 24-pikselowych obręczy oraz 16-pikselowych pasków (w rzeczywistości para pasków Neopixel) podłączonych do 3 oddzielnych pinów Arduino, jak pokazano na schemacie poniżej. 

Przyciski są połączone z pinami 8 i 9, aby demonstrować reakcję na wejścia użytkownika. 

 

Schemat podłączenia

 

Użycie NeoPatterns

Zadeklaruj NeoPatterns

Zanim zaczniemy, musimy zadeklarować kilka obiektów NeoPattern, aby kontrolować nasze piksele. 

 

Używając schematów połączeń z poprzedniego akapitu, zainicjujemy 16 i 24-pikselową obręcz oraz 16-pikselową listwę złożoną z dwóch 8-pikselowych pasków.

 

// Define some NeoPatterns for the two rings and the stick
//  and pass the adderess of the associated completion routines
NeoPatterns Ring1(24, 5, NEO_GRB + NEO_KHZ800, &Ring1Complete);
NeoPatterns Ring2(16, 6, NEO_GRB + NEO_KHZ800, &Ring2Complete);
NeoPatterns Stick(16, 7, NEO_GRB + NEO_KHZ800, &StickComplete);

 

Rozpocznijmy działanie

Tak jak w przypadku pasków NeoPixel, musimy przywołać funkcję begin(), aby wszystko zainicjalizować przed użyciem pasków. Ustawimy także parę pinów wejścia dla naszych przycisków i rozpoczniemy wstępny wzorzec dla każdego paska.

  

// Initialize everything and prepare to start
void setup()
{
    // Initialize all the pixelStrips
    Ring1.begin();
    Ring2.begin();
    Stick.begin();
    
    // Enable internal pullups on the switch inputs
    pinMode(8, INPUT_PULLUP);
    pinMode(9, INPUT_PULLUP);
    
    // Kick off a pattern
    Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);
    Ring2.RainbowCycle(3);
    Ring2.Color1 = Ring1.Color1;
    Stick.Scanner(Ring1.Color(255,0,0), 55);
}

  

Aktualizacja wzorów

Aby wszystko szło po Twojej myśli, musisz regularnie przywoływać funkcję Update() na każdy NeoPattern. Można to zrobić w funkcji loop(), tak jak pokazaliśmy, albo w obsłudze przerwań stopera, tak jak zaprezentowaliśmy w części 2. Jest to możliwe, ponieważ usunęliśmy ze wzorów wewnętrzne pętle i opóźnienia.

 

Najłatwiej byłoby po prostu przywołać aktualizację funkcji dla każdego NeoPatterns, jaki określiłeś. 

 

// Main loop
void loop()
{
    // Update the rings.
    Ring1.Update();
    Ring2.Update();    
    Stick.Update();  
}

Interakcje użytkownika

Bez wewnętrznych pętli, twój kod wciąż reaguje na jakąkolwiek interakcję użytkownika lub na zewnętrzne zdarzenia. Możesz też natychmiastowo zmieniać wzory i kolory bazując na tych zdarzeniach. 
Aby było ciekawiej, wykorzystamy tę zdolność i rozszerzymy naszą pętlę, aby reagowała na wciśnięcie przycisku przy obu przyciskach:

 

Wciśnięcie przycisku #1 (pin 8):

  • zmieni wzór Ring1 z THEATER_CHASE na FADE
  • zmieni prędkość RainbowCycle na Ring2
  • zmieni kolor paska na czerwony

 

Wciśnięcie przycisku #2 (pin 9):

  • zmieni wzór Ring1 z THEATER_CHASE na COLOR_WIPE

  • zmieni wzór Ring2 z THEATER_CHASE na COLOR_WIPE

 

Kiedy żaden przycisk nie jest wciśnięty, wszystkie wzory kontynuują standardowe działanie.

 

// Main loop
void loop()
{
    // Update the rings.
    Ring1.Update();
    Ring2.Update();    
    
    // Switch patterns on a button press:
    if (digitalRead(8) == LOW) // Button #1 pressed
    {
        // Switch Ring1 to FASE pattern
        Ring1.ActivePattern = FADE;
        Ring1.Interval = 20;
        // Speed up the rainbow on Ring2
        Ring2.Interval = 0;
        // Set stick to all red
        Stick.ColorSet(Stick.Color(255, 0, 0));
    }
    else if (digitalRead(9) == LOW) // Button #2 pressed
    {
        // Switch to alternating color wipes on Rings1 and 2
        Ring1.ActivePattern = COLOR_WIPE;
        Ring2.ActivePattern = COLOR_WIPE;
        Ring2.TotalSteps = Ring2.numPixels();
        // And update tbe stick
        Stick.Update();
    }
    else // Back to normal operation
    {
        // Restore all pattern parameters to normal values
        Ring1.ActivePattern = THEATER_CHASE;
        Ring1.Interval = 100;
        Ring2.ActivePattern = RAINBOW_CYCLE;
        Ring2.TotalSteps = 255;
        Ring2.Interval = min(10, Ring2.Interval);
        // And update tbe stick
        Stick.Update();
    }    
}

 

Ukończenie wywołań zwrotnych

Każdy wzorzec, pozostawiony sam sobie, będzie się stale powtarzał. Opcjonalne zakończenie wywołań zwrotnych da Ci zdolność wykonania niektórych działań na koniec każdego cyklu wzorca. 
Czynność wykonywana w zakończeniu wywołania zwrotnego może polegać na zmianie niektórych aspektów wzoru lub na uruchomieniu pewnych zdarzeń zewnętrznych.

 

Ukończenie wywołań zwrotnych Ring1

Ukończenie wywołań zwrotnych Ring1 wpływa na operację Ring1 i Ring2. Zwraca ono uwagę na Przycisk #2. Jeśli jest naciśnięty to:

  • przyspieszy on Ring2 zmniejszając jej Interwał. To skutecznie ją "obudzi".

  • zwolni on Ring1 zwiększając jej Interwał. To efektywnie ją uśpi.

  • wybierze on losowy kolor z koła do następnej wersji wzoru Ring1.


Jeśli żaden przycisk nie jest wciśnięty, zmienia się tylko kierunek standardowego wzorca.

 

// Ring1 Completion Callback
void Ring1Complete()
{
    if (digitalRead(9) == LOW)  // Button #2 pressed
    {
        // Alternate color-wipe patterns with Ring2
        Ring2.Interval = 40;
        Ring1.Color1 = Ring1.Wheel(random(255));
        Ring1.Interval = 20000;
    }
    else  // Retrn to normal
    {
      Ring1.Reverse();
    }
}

 

Ukończenie wywołań zwrotnych Ring2

Ukończenie wywołań zwrotnych Ring2 jest dopełnieniem ukończenia wywołań zwrotnych Ring1.
Jeśli Przycisk #2 jest wciśnięty to:

  • przyspieszy on Ring1 zmniejszając jej Interwał. To skutecznie ją "obudzi".

  • zwolni on Ring2 zwiększając jej Interwał. To efektywnie ją uśpi.

  • wybierze on losowy kolor z koła do następnej wersji wzoru Ring2.


Jeśli przycisk nie zostanie wciśnięty, po prostu wybierze losową prędkość do następnej wersji standardowego wzoru RainbowCycle Ring2.

 

// Ring 2 Completion Callback
void Ring2Complete()
{
    if (digitalRead(9) == LOW)  // Button #2 pressed
    {
        // Alternate color-wipe patterns with Ring1
        Ring1.Interval = 20;
        Ring2.Color1 = Ring2.Wheel(random(255));
        Ring2.Interval = 20000;
    }
    else  // Retrn to normal
    {
        Ring2.RainbowCycle(random(0,10));
    }
}

Ukończenie wywołań zwrotnych paska 

Ukończenie wywołań zwrotnych paska wybiera losowy kolor z koła na następne przejście wzorca skanera.

 

// Stick Completion Callback
void StickComplete()
{
    // Random color change for next scan
    Stick.Color1 = Stick.Wheel(random(255));
}

 

A teraz połącz wszystko razem

Poniższy kod zawiera kompletną klasę NeoPattern razem z niektórymi próbnymi kodami w formie szkicu Arduino. Skopiuj i wklej ten kod do IDE i podłącz swoje piksele, tak jak pokazano to na schemacie podłączeń. 

#include 

// Pattern types supported:
enum  pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum  direction { FORWARD, REVERSE };

// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
    public:

    // Member Variables:  
    pattern  ActivePattern;  // which pattern is running
    direction Direction;     // direction to run the pattern
    
    unsigned long Interval;   // milliseconds between updates
    unsigned long lastUpdate; // last update of position
    
    uint32_t Color1, Color2;  // What colors are in use
    uint16_t TotalSteps;  // total number of steps in the pattern
    uint16_t Index;  // current step within the pattern
    
    void (*OnComplete)();  // Callback on completion of pattern
    
    // Constructor - calls base-class constructor to initialize strip
    NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
    :Adafruit_NeoPixel(pixels, pin, type)
    {
        OnComplete = callback;
    }
    
    // Update the pattern
    void Update()
    {
        if((millis() - lastUpdate) > Interval) // time to update
        {
            lastUpdate = millis();
            switch(ActivePattern)
            {
                case RAINBOW_CYCLE:
                    RainbowCycleUpdate();
                    break;
                case THEATER_CHASE:
                    TheaterChaseUpdate();
                    break;
                case COLOR_WIPE:
                    ColorWipeUpdate();
                    break;
                case SCANNER:
                    ScannerUpdate();
                    break;
                case FADE:
                    FadeUpdate();
                    break;
                default:
                    break;
            }
        }
    }
  
    // Increment the Index and reset at the end
    void Increment()
    {
        if (Direction == FORWARD)
        {
           Index++;
           if (Index >= TotalSteps)
            {
                Index = 0;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
        else // Direction == REVERSE
        {
            --Index;
            if (Index <= 0)
            {
                Index = TotalSteps-1;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
    }
    
    // Reverse pattern direction
    void Reverse()
    {
        if (Direction == FORWARD)
        {
            Direction = REVERSE;
            Index = TotalSteps-1;
        }
        else
        {
            Direction = FORWARD;
            Index = 0;
        }
    }
    
    // Initialize for a RainbowCycle
    void RainbowCycle(uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = RAINBOW_CYCLE;
        Interval = interval;
        TotalSteps = 255;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Rainbow Cycle Pattern
    void RainbowCycleUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
        }
        show();
        Increment();
    }

    // Initialize for a Theater Chase
    void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }
    
    // Update the Theater Chase Pattern
    void TheaterChaseUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            if ((i + Index) % 3 == 0)
            {
                setPixelColor(i, Color1);
            }
            else
            {
                setPixelColor(i, Color2);
            }
        }
        show();
        Increment();
    }

    // Initialize for a ColorWipe
    void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = COLOR_WIPE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Color Wipe Pattern
    void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }
    
    // Initialize for a SCANNNER
    void Scanner(uint32_t color1, uint8_t interval)
    {
        ActivePattern = SCANNER;
        Interval = interval;
        TotalSteps = (numPixels() - 1) * 2;
        Color1 = color1;
        Index = 0;
    }

    // Update the Scanner Pattern
    void ScannerUpdate()
    { 
        for (int i = 0; i < numPixels(); i++)
        {
            if (i == Index)  // Scan Pixel to the right
            {
                 setPixelColor(i, Color1);
            }
            else if (i == TotalSteps - Index) // Scan Pixel to the left
            {
                 setPixelColor(i, Color1);
            }
            else // Fading tail
            {
                 setPixelColor(i, DimColor(getPixelColor(i)));
            }
        }
        show();
        Increment();
    }
    
    // Initialize for a Fade
    void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = FADE;
        Interval = interval;
        TotalSteps = steps;
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Fade Pattern
    void FadeUpdate()
    {
        // Calculate linear interpolation between Color1 and Color2
        // Optimise order of operations to minimize truncation error
        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(Color(red, green, blue));
        show();
        Increment();
    }
   
    // Calculate 50% dimmed version of a color (used by ScannerUpdate)
    uint32_t DimColor(uint32_t color)
    {
        // Shift R, G and B components one bit to the right
        uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
        return dimColor;
    }

    // Set all pixels to a color (synchronously)
    void ColorSet(uint32_t color)
    {
        for (int i = 0; i < numPixels(); i++)
        {
            setPixelColor(i, color);
        }
        show();
    }

    // Returns the Red component of a 32-bit color
    uint8_t Red(uint32_t color)
    {
        return (color >> 16) & 0xFF;
    }

    // Returns the Green component of a 32-bit color
    uint8_t Green(uint32_t color)
    {
        return (color >> 8) & 0xFF;
    }

    // Returns the Blue component of a 32-bit color
    uint8_t Blue(uint32_t color)
    {
        return color & 0xFF;
    }
    
    // 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)
    {
        WheelPos = 255 - WheelPos;
        if(WheelPos < 85)
        {
            return Color(255 - WheelPos * 3, 0, WheelPos * 3);
        }
        else if(WheelPos < 170)
        {
            WheelPos -= 85;
            return Color(0, WheelPos * 3, 255 - WheelPos * 3);
        }
        else
        {
            WheelPos -= 170;
            return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
        }
    }
};

void Ring1Complete();
void Ring2Complete();
void StickComplete();

// Define some NeoPatterns for the two rings and the stick
//  as well as some completion routines
NeoPatterns Ring1(24, 5, NEO_GRB + NEO_KHZ800, &Ring1Complete);
NeoPatterns Ring2(16, 6, NEO_GRB + NEO_KHZ800, &Ring2Complete);
NeoPatterns Stick(16, 7, NEO_GRB + NEO_KHZ800, &StickComplete);

// Initialize everything and prepare to start
void setup()
{
  Serial.begin(115200);

   pinMode(8, INPUT_PULLUP);
   pinMode(9, INPUT_PULLUP);
    
    // Initialize all the pixelStrips
    Ring1.begin();
    Ring2.begin();
    Stick.begin();
    
    // Kick off a pattern
    Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);
    Ring2.RainbowCycle(3);
    Ring2.Color1 = Ring1.Color1;
    Stick.Scanner(Ring1.Color(255,0,0), 55);
}

// Main loop
void loop()
{
    // Update the rings.
    Ring1.Update();
    Ring2.Update();    
    
    // Switch patterns on a button press:
    if (digitalRead(8) == LOW) // Button #1 pressed
    {
        // Switch Ring1 to FADE pattern
        Ring1.ActivePattern = FADE;
        Ring1.Interval = 20;
        // Speed up the rainbow on Ring2
        Ring2.Interval = 0;
        // Set stick to all red
        Stick.ColorSet(Stick.Color(255, 0, 0));
    }
    else if (digitalRead(9) == LOW) // Button #2 pressed
    {
        // Switch to alternating color wipes on Rings1 and 2
        Ring1.ActivePattern = COLOR_WIPE;
        Ring2.ActivePattern = COLOR_WIPE;
        Ring2.TotalSteps = Ring2.numPixels();
        // And update tbe stick
        Stick.Update();
    }
    else // Back to normal operation
    {
        // Restore all pattern parameters to normal values
        Ring1.ActivePattern = THEATER_CHASE;
        Ring1.Interval = 100;
        Ring2.ActivePattern = RAINBOW_CYCLE;
        Ring2.TotalSteps = 255;
        Ring2.Interval = min(10, Ring2.Interval);
        // And update tbe stick
        Stick.Update();
    }    
}

//------------------------------------------------------------
//Completion Routines - get called on completion of a pattern
//------------------------------------------------------------

// Ring1 Completion Callback
void Ring1Complete()
{
    if (digitalRead(9) == LOW)  // Button #2 pressed
    {
        // Alternate color-wipe patterns with Ring2
        Ring2.Interval = 40;
        Ring1.Color1 = Ring1.Wheel(random(255));
        Ring1.Interval = 20000;
    }
    else  // Retrn to normal
    {
      Ring1.Reverse();
    }
}

// Ring 2 Completion Callback
void Ring2Complete()
{
    if (digitalRead(9) == LOW)  // Button #2 pressed
    {
        // Alternate color-wipe patterns with Ring1
        Ring1.Interval = 20;
        Ring2.Color1 = Ring2.Wheel(random(255));
        Ring2.Interval = 20000;
    }
    else  // Retrn to normal
    {
        Ring2.RainbowCycle(random(0,10));
    }
}

// Stick Completion Callback
void StickComplete()
{
    // Random color change for next scan
    Stick.Color1 = Stick.Wheel(random(255));
}

 

 

 

<- część druga

 

Źródło: https://learn.adafruit.com/multi-tasking-the-arduino-part-3

zapraszamy do współpracy!