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.

 

  1. #include
  2. // Pattern types supported:
  3. enum pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
  4. // Patern directions supported:
  5. enum direction { FORWARD, REVERSE };
  6. // NeoPattern Class - derived from the Adafruit_NeoPixel class
  7. class NeoPatterns : public Adafruit_NeoPixel
  8. {
  9. public:
  10. // Member Variables:
  11. pattern ActivePattern; // which pattern is running
  12. direction Direction; // direction to run the pattern
  13. unsigned long Interval; // milliseconds between updates
  14. unsigned long lastUpdate; // last update of position
  15. uint32_t Color1, Color2; // What colors are in use
  16. uint16_t TotalSteps; // total number of steps in the pattern
  17. uint16_t Index; // current step within the pattern
  18. void (*OnComplete)(); // Callback on completion of pattern

  

Konstruktor:

 

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

 

  1. #include
  2. // Constructor - calls base-class constructor to initialize strip
  3. NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
  4. :Adafruit_NeoPixel(pixels, pin, type)
  5. {
  6. OnComplete = callback;
  7. }

 

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. 

 

  1. // Update the pattern
  2. void Update()
  3. {
  4. if((millis() - lastUpdate) > Interval) // time to update
  5. {
  6. lastUpdate = millis();
  7. switch(ActivePattern)
  8. {
  9. case RAINBOW_CYCLE:
  10. RainbowCycleUpdate();
  11. break;
  12. case THEATER_CHASE:
  13. TheaterChaseUpdate();
  14. break;
  15. case COLOR_WIPE:
  16. ColorWipeUpdate();
  17. break;
  18. case SCANNER:
  19. ScannerUpdate();
  20. break;
  21. case FADE:
  22. FadeUpdate();
  23. break;
  24. default:
  25. break;
  26. }
  27. }
  28. }

 

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. 

 

  1. // Increment the Index and reset at the end
  2. void Increment()
  3. {
  4. if (Direction == FORWARD)
  5. {
  6. Index++;
  7. if (Index >= TotalSteps)
  8. {
  9. Index = 0;
  10. if (OnComplete != NULL)
  11. {
  12. OnComplete(); // call the comlpetion callback
  13. }
  14. }
  15. }
  16. else // Direction == REVERSE
  17. {
  18. --Index;
  19. if (Index <= 0)
  20. {
  21. Index = TotalSteps-1;
  22. if (OnComplete != NULL)
  23. {
  24. OnComplete(); // call the comlpetion callback
  25. }
  26. }
  27. }
  28. }

 

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.

 

  1. // Initialize for a RainbowCycle
  2. void RainbowCycle(uint8_t interval, direction dir = FORWARD)
  3. {
  4. ActivePattern = RAINBOW_CYCLE;
  5. Interval = interval;
  6. TotalSteps = 255;
  7. Index = 0;
  8. Direction = dir;
  9. }

 

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().

 

  1. // Update the Rainbow Cycle Pattern
  2. void RainbowCycleUpdate()
  3. {
  4. for(int i=0; i< numPixels(); i++)
  5. {
  6. setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
  7. }
  8. show();
  9. Increment();
  10. }

 

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. 

 

  1. // Initialize for a ColorWipe
  2. void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
  3. {
  4. ActivePattern = COLOR_WIPE;
  5. Interval = interval;
  6. TotalSteps = numPixels();
  7. Color1 = color;
  8. Index = 0;
  9. Direction = dir;
  10. }

 

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.

 

  1. // Update the Color Wipe Pattern
  2. void ColorWipeUpdate()
  3. {
  4. setPixelColor(Index, Color1);
  5. show();
  6. Increment();
  7. }

 

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.
  1. // Initialize for a Theater Chase
  2. void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
  3. {
  4. ActivePattern = THEATER_CHASE;
  5. Interval = interval;
  6. TotalSteps = numPixels();
  7. Color1 = color1;
  8. Color2 = color2;
  9. Index = 0;
  10. Direction = dir;
  11. }

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().

 

  1. // Update the Theater Chase Pattern
  2. void TheaterChaseUpdate()
  3. {
  4. for(int i=0; i< numPixels(); i++)
  5. {
  6. if ((i + Index) % 3 == 0)
  7. {
  8. setPixelColor(i, Color1);
  9. }
  10. else
  11. {
  12. setPixelColor(i, Color2);
  13. }
  14. }
  15. show();
  16. Increment();
  17. }

 

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.

  

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

 

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. 

 

  1. // Update the Scanner Pattern
  2. void ScannerUpdate()
  3. {
  4. for (int i = 0; i < numPixels(); i++)
  5. {
  6. if (i == Index) // first half of the scan
  7. {
  8. Serial.print(i);
  9. setPixelColor(i, Color1);
  10. }
  11. else if (i == TotalSteps - Index) // The return trip.
  12. {
  13. Serial.print(i);
  14. setPixelColor(i, Color1);
  15. }
  16. else // fade to black
  17. {
  18. setPixelColor(i, DimColor(getPixelColor(i)));
  19. }
  20. }
  21. show();
  22. Increment();
  23. }

 

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.

 

  1. // Initialize for a Fade
  2. void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
  3. {
  4. ActivePattern = FADE;
  5. Interval = interval;
  6. TotalSteps = steps;
  7. Color1 = color1;
  8. Color2 = color2;
  9. Index = 0;
  10. Direction = dir;
  11. }

  

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).

  1. // Update the Fade Pattern
  2. void FadeUpdate()
  3. {
  4. uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
  5. uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
  6. uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
  7. ColorSet(Color(red, green, blue));
  8. show();
  9. Increment();
  10. }
  11.  

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.

 

  1. // Returns the Red component of a 32-bit color
  2. uint8_t Red(uint32_t color)
  3. {
  4. return (color >> 16) & 0xFF;
  5. }
  6.  
  7. // Returns the Green component of a 32-bit color
  8. uint8_t Green(uint32_t color)
  9. {
  10. return (color >> 8) & 0xFF;
  11. }
  12.  
  13. // Returns the Blue component of a 32-bit color
  14. uint8_t Blue(uint32_t color)
  15. {
  16. return color & 0xFF;
  17. }

 

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.

 

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

 

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

 

  1. // Input a value 0 to 255 to get a color value.
  2. // The colours are a transition r - g - b - back to r.
  3. uint32_t Wheel(byte WheelPos)
  4. {
  5. WheelPos = 255 - WheelPos;
  6. if(WheelPos < 85)
  7. {
  8. return Color(255 - WheelPos * 3, 0, WheelPos * 3);
  9. }
  10. else if(WheelPos < 170)
  11. {
  12. WheelPos -= 85;
  13. return Color(0, WheelPos * 3, 255 - WheelPos * 3);
  14. }
  15. else
  16. {
  17. WheelPos -= 170;
  18. return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  19. }
  20. }

 

Funkcja Reverse() odwróci kierunek wykonania wzoru. 

 

  1. // Reverse direction of the pattern
  2. void Reverse()
  3. {
  4. if (Direction == FORWARD)
  5. {
  6. Direction = REVERSE;
  7. Index = TotalSteps-1;
  8. }
  9. else
  10. {
  11. Direction = FORWARD;
  12. Index = 0;
  13. }
  14. }

 

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

  

  1. // Set all pixels to a color (synchronously)
  2. void ColorSet(uint32_t color)
  3. {
  4. for (int i = 0; i < numPixels(); i++)
  5. {
  6. setPixelColor(i, color);
  7. }
  8. show();
  9. }

 

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.

 

  1. // Define some NeoPatterns for the two rings and the stick
  2. // and pass the adderess of the associated completion routines
  3. NeoPatterns Ring1(24, 5, NEO_GRB + NEO_KHZ800, &Ring1Complete);
  4. NeoPatterns Ring2(16, 6, NEO_GRB + NEO_KHZ800, &Ring2Complete);
  5. 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.

  

  1. // Initialize everything and prepare to start
  2. void setup()
  3. {
  4. // Initialize all the pixelStrips
  5. Ring1.begin();
  6. Ring2.begin();
  7. Stick.begin();
  8. // Enable internal pullups on the switch inputs
  9. pinMode(8, INPUT_PULLUP);
  10. pinMode(9, INPUT_PULLUP);
  11. // Kick off a pattern
  12. Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);
  13. Ring2.RainbowCycle(3);
  14. Ring2.Color1 = Ring1.Color1;
  15. Stick.Scanner(Ring1.Color(255,0,0), 55);
  16. }

  

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ś. 

 

  1. // Main loop
  2. void loop()
  3. {
  4. // Update the rings.
  5. Ring1.Update();
  6. Ring2.Update();
  7. Stick.Update();
  8. }

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.

 

  1. // Main loop
  2. void loop()
  3. {
  4. // Update the rings.
  5. Ring1.Update();
  6. Ring2.Update();
  7. // Switch patterns on a button press:
  8. if (digitalRead(8) == LOW) // Button #1 pressed
  9. {
  10. // Switch Ring1 to FASE pattern
  11. Ring1.ActivePattern = FADE;
  12. Ring1.Interval = 20;
  13. // Speed up the rainbow on Ring2
  14. Ring2.Interval = 0;
  15. // Set stick to all red
  16. Stick.ColorSet(Stick.Color(255, 0, 0));
  17. }
  18. else if (digitalRead(9) == LOW) // Button #2 pressed
  19. {
  20. // Switch to alternating color wipes on Rings1 and 2
  21. Ring1.ActivePattern = COLOR_WIPE;
  22. Ring2.ActivePattern = COLOR_WIPE;
  23. Ring2.TotalSteps = Ring2.numPixels();
  24. // And update tbe stick
  25. Stick.Update();
  26. }
  27. else // Back to normal operation
  28. {
  29. // Restore all pattern parameters to normal values
  30. Ring1.ActivePattern = THEATER_CHASE;
  31. Ring1.Interval = 100;
  32. Ring2.ActivePattern = RAINBOW_CYCLE;
  33. Ring2.TotalSteps = 255;
  34. Ring2.Interval = min(10, Ring2.Interval);
  35. // And update tbe stick
  36. Stick.Update();
  37. }
  38. }

 

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.

 

  1. // Ring1 Completion Callback
  2. void Ring1Complete()
  3. {
  4. if (digitalRead(9) == LOW) // Button #2 pressed
  5. {
  6. // Alternate color-wipe patterns with Ring2
  7. Ring2.Interval = 40;
  8. Ring1.Color1 = Ring1.Wheel(random(255));
  9. Ring1.Interval = 20000;
  10. }
  11. else // Retrn to normal
  12. {
  13. Ring1.Reverse();
  14. }
  15. }

 

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.

 

  1. // Ring 2 Completion Callback
  2. void Ring2Complete()
  3. {
  4. if (digitalRead(9) == LOW) // Button #2 pressed
  5. {
  6. // Alternate color-wipe patterns with Ring1
  7. Ring1.Interval = 20;
  8. Ring2.Color1 = Ring2.Wheel(random(255));
  9. Ring2.Interval = 20000;
  10. }
  11. else // Retrn to normal
  12. {
  13. Ring2.RainbowCycle(random(0,10));
  14. }
  15. }

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.

 

  1. // Stick Completion Callback
  2. void StickComplete()
  3. {
  4. // Random color change for next scan
  5. Stick.Color1 = Stick.Wheel(random(255));
  6. }

 

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ń. 

  1. #include
  2. // Pattern types supported:
  3. enum pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
  4. // Patern directions supported:
  5. enum direction { FORWARD, REVERSE };
  6. // NeoPattern Class - derived from the Adafruit_NeoPixel class
  7. class NeoPatterns : public Adafruit_NeoPixel
  8. {
  9. public:
  10. // Member Variables:
  11. pattern ActivePattern; // which pattern is running
  12. direction Direction; // direction to run the pattern
  13. unsigned long Interval; // milliseconds between updates
  14. unsigned long lastUpdate; // last update of position
  15. uint32_t Color1, Color2; // What colors are in use
  16. uint16_t TotalSteps; // total number of steps in the pattern
  17. uint16_t Index; // current step within the pattern
  18. void (*OnComplete)(); // Callback on completion of pattern
  19. // Constructor - calls base-class constructor to initialize strip
  20. NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
  21. :Adafruit_NeoPixel(pixels, pin, type)
  22. {
  23. OnComplete = callback;
  24. }
  25. // Update the pattern
  26. void Update()
  27. {
  28. if((millis() - lastUpdate) > Interval) // time to update
  29. {
  30. lastUpdate = millis();
  31. switch(ActivePattern)
  32. {
  33. case RAINBOW_CYCLE:
  34. RainbowCycleUpdate();
  35. break;
  36. case THEATER_CHASE:
  37. TheaterChaseUpdate();
  38. break;
  39. case COLOR_WIPE:
  40. ColorWipeUpdate();
  41. break;
  42. case SCANNER:
  43. ScannerUpdate();
  44. break;
  45. case FADE:
  46. FadeUpdate();
  47. break;
  48. default:
  49. break;
  50. }
  51. }
  52. }
  53. // Increment the Index and reset at the end
  54. void Increment()
  55. {
  56. if (Direction == FORWARD)
  57. {
  58. Index++;
  59. if (Index >= TotalSteps)
  60. {
  61. Index = 0;
  62. if (OnComplete != NULL)
  63. {
  64. OnComplete(); // call the comlpetion callback
  65. }
  66. }
  67. }
  68. else // Direction == REVERSE
  69. {
  70. --Index;
  71. if (Index <= 0)
  72. {
  73. Index = TotalSteps-1;
  74. if (OnComplete != NULL)
  75. {
  76. OnComplete(); // call the comlpetion callback
  77. }
  78. }
  79. }
  80. }
  81. // Reverse pattern direction
  82. void Reverse()
  83. {
  84. if (Direction == FORWARD)
  85. {
  86. Direction = REVERSE;
  87. Index = TotalSteps-1;
  88. }
  89. else
  90. {
  91. Direction = FORWARD;
  92. Index = 0;
  93. }
  94. }
  95. // Initialize for a RainbowCycle
  96. void RainbowCycle(uint8_t interval, direction dir = FORWARD)
  97. {
  98. ActivePattern = RAINBOW_CYCLE;
  99. Interval = interval;
  100. TotalSteps = 255;
  101. Index = 0;
  102. Direction = dir;
  103. }
  104. // Update the Rainbow Cycle Pattern
  105. void RainbowCycleUpdate()
  106. {
  107. for(int i=0; i< numPixels(); i++)
  108. {
  109. setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
  110. }
  111. show();
  112. Increment();
  113. }
  114. // Initialize for a Theater Chase
  115. void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
  116. {
  117. ActivePattern = THEATER_CHASE;
  118. Interval = interval;
  119. TotalSteps = numPixels();
  120. Color1 = color1;
  121. Color2 = color2;
  122. Index = 0;
  123. Direction = dir;
  124. }
  125. // Update the Theater Chase Pattern
  126. void TheaterChaseUpdate()
  127. {
  128. for(int i=0; i< numPixels(); i++)
  129. {
  130. if ((i + Index) % 3 == 0)
  131. {
  132. setPixelColor(i, Color1);
  133. }
  134. else
  135. {
  136. setPixelColor(i, Color2);
  137. }
  138. }
  139. show();
  140. Increment();
  141. }
  142. // Initialize for a ColorWipe
  143. void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
  144. {
  145. ActivePattern = COLOR_WIPE;
  146. Interval = interval;
  147. TotalSteps = numPixels();
  148. Color1 = color;
  149. Index = 0;
  150. Direction = dir;
  151. }
  152. // Update the Color Wipe Pattern
  153. void ColorWipeUpdate()
  154. {
  155. setPixelColor(Index, Color1);
  156. show();
  157. Increment();
  158. }
  159. // Initialize for a SCANNNER
  160. void Scanner(uint32_t color1, uint8_t interval)
  161. {
  162. ActivePattern = SCANNER;
  163. Interval = interval;
  164. TotalSteps = (numPixels() - 1) * 2;
  165. Color1 = color1;
  166. Index = 0;
  167. }
  168. // Update the Scanner Pattern
  169. void ScannerUpdate()
  170. {
  171. for (int i = 0; i < numPixels(); i++)
  172. {
  173. if (i == Index) // Scan Pixel to the right
  174. {
  175. setPixelColor(i, Color1);
  176. }
  177. else if (i == TotalSteps - Index) // Scan Pixel to the left
  178. {
  179. setPixelColor(i, Color1);
  180. }
  181. else // Fading tail
  182. {
  183. setPixelColor(i, DimColor(getPixelColor(i)));
  184. }
  185. }
  186. show();
  187. Increment();
  188. }
  189. // Initialize for a Fade
  190. void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
  191. {
  192. ActivePattern = FADE;
  193. Interval = interval;
  194. TotalSteps = steps;
  195. Color1 = color1;
  196. Color2 = color2;
  197. Index = 0;
  198. Direction = dir;
  199. }
  200. // Update the Fade Pattern
  201. void FadeUpdate()
  202. {
  203. // Calculate linear interpolation between Color1 and Color2
  204. // Optimise order of operations to minimize truncation error
  205. uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
  206. uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
  207. uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
  208. ColorSet(Color(red, green, blue));
  209. show();
  210. Increment();
  211. }
  212. // Calculate 50% dimmed version of a color (used by ScannerUpdate)
  213. uint32_t DimColor(uint32_t color)
  214. {
  215. // Shift R, G and B components one bit to the right
  216. uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
  217. return dimColor;
  218. }
  219. // Set all pixels to a color (synchronously)
  220. void ColorSet(uint32_t color)
  221. {
  222. for (int i = 0; i < numPixels(); i++)
  223. {
  224. setPixelColor(i, color);
  225. }
  226. show();
  227. }
  228. // Returns the Red component of a 32-bit color
  229. uint8_t Red(uint32_t color)
  230. {
  231. return (color >> 16) & 0xFF;
  232. }
  233. // Returns the Green component of a 32-bit color
  234. uint8_t Green(uint32_t color)
  235. {
  236. return (color >> 8) & 0xFF;
  237. }
  238. // Returns the Blue component of a 32-bit color
  239. uint8_t Blue(uint32_t color)
  240. {
  241. return color & 0xFF;
  242. }
  243. // Input a value 0 to 255 to get a color value.
  244. // The colours are a transition r - g - b - back to r.
  245. uint32_t Wheel(byte WheelPos)
  246. {
  247. WheelPos = 255 - WheelPos;
  248. if(WheelPos < 85)
  249. {
  250. return Color(255 - WheelPos * 3, 0, WheelPos * 3);
  251. }
  252. else if(WheelPos < 170)
  253. {
  254. WheelPos -= 85;
  255. return Color(0, WheelPos * 3, 255 - WheelPos * 3);
  256. }
  257. else
  258. {
  259. WheelPos -= 170;
  260. return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  261. }
  262. }
  263. };
  264. void Ring1Complete();
  265. void Ring2Complete();
  266. void StickComplete();
  267. // Define some NeoPatterns for the two rings and the stick
  268. // as well as some completion routines
  269. NeoPatterns Ring1(24, 5, NEO_GRB + NEO_KHZ800, &Ring1Complete);
  270. NeoPatterns Ring2(16, 6, NEO_GRB + NEO_KHZ800, &Ring2Complete);
  271. NeoPatterns Stick(16, 7, NEO_GRB + NEO_KHZ800, &StickComplete);
  272. // Initialize everything and prepare to start
  273. void setup()
  274. {
  275. Serial.begin(115200);
  276. pinMode(8, INPUT_PULLUP);
  277. pinMode(9, INPUT_PULLUP);
  278. // Initialize all the pixelStrips
  279. Ring1.begin();
  280. Ring2.begin();
  281. Stick.begin();
  282. // Kick off a pattern
  283. Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);
  284. Ring2.RainbowCycle(3);
  285. Ring2.Color1 = Ring1.Color1;
  286. Stick.Scanner(Ring1.Color(255,0,0), 55);
  287. }
  288. // Main loop
  289. void loop()
  290. {
  291. // Update the rings.
  292. Ring1.Update();
  293. Ring2.Update();
  294. // Switch patterns on a button press:
  295. if (digitalRead(8) == LOW) // Button #1 pressed
  296. {
  297. // Switch Ring1 to FADE pattern
  298. Ring1.ActivePattern = FADE;
  299. Ring1.Interval = 20;
  300. // Speed up the rainbow on Ring2
  301. Ring2.Interval = 0;
  302. // Set stick to all red
  303. Stick.ColorSet(Stick.Color(255, 0, 0));
  304. }
  305. else if (digitalRead(9) == LOW) // Button #2 pressed
  306. {
  307. // Switch to alternating color wipes on Rings1 and 2
  308. Ring1.ActivePattern = COLOR_WIPE;
  309. Ring2.ActivePattern = COLOR_WIPE;
  310. Ring2.TotalSteps = Ring2.numPixels();
  311. // And update tbe stick
  312. Stick.Update();
  313. }
  314. else // Back to normal operation
  315. {
  316. // Restore all pattern parameters to normal values
  317. Ring1.ActivePattern = THEATER_CHASE;
  318. Ring1.Interval = 100;
  319. Ring2.ActivePattern = RAINBOW_CYCLE;
  320. Ring2.TotalSteps = 255;
  321. Ring2.Interval = min(10, Ring2.Interval);
  322. // And update tbe stick
  323. Stick.Update();
  324. }
  325. }
  326. //------------------------------------------------------------
  327. //Completion Routines - get called on completion of a pattern
  328. //------------------------------------------------------------
  329. // Ring1 Completion Callback
  330. void Ring1Complete()
  331. {
  332. if (digitalRead(9) == LOW) // Button #2 pressed
  333. {
  334. // Alternate color-wipe patterns with Ring2
  335. Ring2.Interval = 40;
  336. Ring1.Color1 = Ring1.Wheel(random(255));
  337. Ring1.Interval = 20000;
  338. }
  339. else // Retrn to normal
  340. {
  341. Ring1.Reverse();
  342. }
  343. }
  344. // Ring 2 Completion Callback
  345. void Ring2Complete()
  346. {
  347. if (digitalRead(9) == LOW) // Button #2 pressed
  348. {
  349. // Alternate color-wipe patterns with Ring1
  350. Ring1.Interval = 20;
  351. Ring2.Color1 = Ring2.Wheel(random(255));
  352. Ring2.Interval = 20000;
  353. }
  354. else // Retrn to normal
  355. {
  356. Ring2.RainbowCycle(random(0,10));
  357. }
  358. }
  359. // Stick Completion Callback
  360. void StickComplete()
  361. {
  362. // Random color change for next scan
  363. Stick.Color1 = Stick.Wheel(random(255));
  364. }

 

 

 

<- część druga

 

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

zapraszamy do współpracy!

Open form