[032] Animacje diody WS2812

[032] Animacje diody WS2812

Skoro już przeszliśmy przez trudy współpracy diod WS2812 z Arduino z powodzeniem, pobawmy się teraz paroma użytecznymi funkcjami.


#include <Adafruit_NeoPixel.h>                                             // Dołącz bibliotekę sterującą diodami WS2812
Adafruit_NeoPixel dioda = Adafruit_NeoPixel(1, 12, NEO_GRB + NEO_KHZ800);  // Ustaw parametry zestawu: jedna dioda na dwunastym porcie.
void setup() {
  dioda.begin();  // Przygotuj sterowniki znajdujące się w diodach do inicjalizacji.
  dioda.show();   // Wykonaj powyższe polecenie.
}
void loop() {
  dioda.setPixelColor(0, dioda.Color(255, 0, 0));  // Ustaw kolor czerwony diodzie zerowej
  dioda.show();
  delay(1000);
  dioda.setBrightness(127);  // Zmniejsz jasność wszystkich diod do 50%
  dioda.show();
  delay(1000);
  dioda.setBrightness(255);  // Rozjaśnij z powrotem wszystkie diody.
  dioda.show();
  delay(1000);
  dioda.clear();  // Zgaś wszystkie diody.
  dioda.show();
  delay(1000);
}

Początek jest identyczny jak w szkicu z poprzedniego artykułu, więc tam odsyłam po komentarz. Zapalenie diody przeniesiemy do pętli głównej i zatrzymamy się na sekundę. Zaraz potem użyjemy funkcji dioda.setBrightness(127), która określa jasność świecenia. Oczywiście można ją zastąpić znaną już funkcją włączenia konkretnego koloru, ale tak jest prościej, a poza tym ta funkcja reguluje jasność całego łańcucha. Więc jeśli mielibyśmy tu sto diod, musielibyśmy wysłać sto komunikatów korygujących jasności wszystkich diod po kolei, a tak możemy to zrobić hurtem.

Po sekundzie ustawimy z powrotem jasność stuprocentową: dioda.setBrightness(255) Ta wartość, jak większość, także jest ośmiobitowa, czyli najjaśniej to 255.

Kolejna funkcja: dioda.clear() gasi wszystkie diody. W zasadzie to jest to samo, co wysłanie jasności zerowej, ale tak jest czytelniej z punktu widzenia programisty.

Zajmijmy się teraz modelem barw w standardzie HSV. O modelu RGB chyba każdy słyszał. Jest on intuicyjny: składa się z trzech składowych trzech barw podstawowych i im większa konkretna wartość, tym jaśniej świeci się konkretny kolor. Z mieszaniny tych barw można zrobić w zasadzie dowolny kolor, który sobie wymyślimy. Istnieje kilka innych modeli, będących przekształceniem tego. HSV z początku wydaje się być dziwny, lecz po chwili widać, że jest bardziej naturalny. Pierwsza wartość to pozycja na kole barw.

Zaczynamy od czerwieni, wpadając w żółć, zieleń, niebieskość, fiolet i po zatoczeniu koła znowu lądujemy w czerwieni. Druga zmienna oznacza nasycenie. Im więcej, tym większe. Wyzerowanie da szarości albo biele. Trzecia zmienna to jasność.

#include <Adafruit_NeoPixel.h>                                             // Dołącz bibliotekę sterującą diodami WS2812
Adafruit_NeoPixel dioda = Adafruit_NeoPixel(1, 12, NEO_GRB + NEO_KHZ800);  // Ustaw parametry zestawu: jedna dioda na dwunastym porcie.
unsigned int kolorHSV = 0;                                                 // Zmienna określająca kolor w modelu HSV
void setup() {
  dioda.begin();  // Przygotuj sterowniki znajdujące się w diodach do inicjalizacji.
  dioda.show();   // Wykonaj powyższe polecenie.
}
void loop() {
  dioda.setPixelColor(0, dioda.ColorHSV(kolorHSV, 255, 255));  // Ustaw kolor zgodny z modelem HSV.
  dioda.show();                                                // Wykonaj powyższe polecenie.
  kolorHSV++;                                                  // Zwiększ wartość koloru w modelu HSV
}

Ważne jest tutaj to, że w modelu HSV odcień jest traktowany dokładniej, już nie ośmioma, a szesnastoma bitami. Dlatego też w programie wprowadzę szesnastobitową zmienną kolorHSV, która będzie się zwiększać bez końca w pętli. Na szczęście przekroczenie jej wartości nie spowoduje awarii, bo zmienne są „sklejone końcami”, co znaczy, że przyjęcie wartości 65536 sprawi, że wartość stanie się zerem i proces jej zwiększania zacznie się od początku. Taką samą sztuczkę możemy robić także z wartościami ośmiobitowymi, tylko tutaj przepełnienie nastąpi szybciej, bo przy wartości 256.

Po kompilacji będziemy mogli zauważyć, że dioda zacznie powoli zmieniać barwę, podróżując przez tęczę, po czym na końcu miesza się z barwami z jej początku i… tak w nieskończoność. Ale troszkę to wszystko będzie wyglądać nieprecyzyjne. Dane wyliczone matematycznie niekoniecznie współpracują z czułością naszego oka. By temu zaradzić, biblioteka została zaopatrzona w kolejną funkcję: gamma32, która argumenty ujęte w nawiasach dodatkowo przelicza tak, by osiągnąć efekty zgodnie z czułością ludzkiego oka. Wymieńmy teraz odpowiednią linię na tę:

dioda.setPixelColor(0, dioda.gamma32(dioda.ColorHSV(kolorHSV, 255, 255)));  // Ustaw kolor zgodny z modelem HSV, stosując korekcję gammy.

Po wysłaniu zmodyfikowanego programu efekt zmian barw poprawi się, choć zmiana ta będzie w praktyce subtelna tylko.

Na koniec zróbmy sobie coś użytecznego. Funkcje losowe i prosta matematyka pozwala na tworzenie bardzo atrakcyjnych animacji świateł. Spróbujmy stworzyć emulację żaru z ogniska. Wykorzystamy w tym celu model HSV, ponieważ jest łatwiejszy w realizacji z perspektywy naszego problemu. Najpierw założenia. Nasz żar będzie się oczywiście żarzył, czyli zmieniał jasność w czasie, niestabilnie – jak prawdziwe, dogasające ognisko. Gdy przyjrzymy się temu zjawisku, odkryjemy, że zmienia się także barwa, czasem pojawia się jakiś płomień i czerwień staje się pomarańczowa i żółta. Do tego jeszcze pojawiają się jasne rozbłyski w mało nasyconym kolorze. Wszystko to jest subtelne i wymaga wielu prób, bo obliczenia można poczynić jedynie orientacyjnie.

#include <Adafruit_NeoPixel.h>                                             // Dołącz bibliotekę sterującą diodami WS2812
Adafruit_NeoPixel dioda = Adafruit_NeoPixel(1, 12, NEO_GRB + NEO_KHZ800);  // Ustaw parametry zestawu: jedna dioda na dwunastym porcie.
unsigned int ognisko;                                                      // Zmienna animacji ogniska.
void setup() {
  dioda.begin();  // Przygotuj sterowniki znajdujące się w diodach do inicjalizacji.
  dioda.show();   // Wykonaj powyższe polecenie.
}
void loop() {
  ognisko = random(224, 255);                                // Losuj zmienną.
  dioda.setPixelColor(0, dioda.gamma32(dioda.ColorHSV(       // Ustaw kolor zgodny z modelem HSV:
                           (63488) + (ognisko - 223) * 160,  // odcień,
                           479 - ognisko,                    // nasycenie,
                           ognisko)));                       // jasność.
  dioda.show();
  delay(100);
}

Zadeklarujemy sobie zmienną: ognisko. Będzie ona wpływać na wszystkie trzy parametry barwy modelu HSV. W każdym przejściu przez pętlę wylosujemy jej wartość z wąskiego przedziału, między 224, a 255: ognisko = random(224, 255)

Następnie, korzystając z niej, ustawimy barwę. Troszkę to zrobię naokoło, ale dzięki temu łatwiej będzie zrozumieć, skąd biorą się takie dziwne wartości. Najbardziej „czerwoną” barwą będzie karmin. Jego reprezentacja w modelu HSL to 63488. Barwę tę można dodać na oko, korzystając z poprzedniego szkicu albo ustawić np. w Photoshopie, po czym z proporcji zamienić system kątowy na ułamek szesnastobitowej liczby.

Zakładam, że maksymalne rozbłyski zmienią nam barwę na odcień pomarańczowo żółty. Jeśli podstawimy za zmienną „ognisko” wartość 255, całość da nam 63488 + (255 – 223) * 160 = 3072. Możemy sobie oczywiście zobaczyć tę barwę, podstawiając ową wartość w poprzednim szkicu.

Jeśli chodzi o nasycenie, tutaj trzeba działać delikatnie. Dla najniższej losowej, równej 224 otrzymamy: 479 – 224 = 255, czyli maksymalne wysycenie. Natomiast gdy losowa przyjmie wartość największą, czyli 255, otrzymamy: 479 – 255 = 224, co da nieco osłabione wysycenie, czyli pojawi się więcej bieli.

No i w końcu ostatnia wartość: jasność. Tym razem nie będzie żadnych obliczeń, użyjemy wprost wylosowanej wartości. Jasność będzie się losowo zmieniać między liczbą 224, a maksymalną, czyli 255.

Po wysłaniu tak mozolnie dobranych wartości należy poczekać chwilę – sto milisekund akurat będzie dobrze udawać wesoły żar. Pozostało nam uzbierać drwa i ułożyć je na płytce. Kilka zapałek będzie akurat w sam raz :)

Zachęcam do eksperymentowania z liczbami, można otrzymywać subtelne efekty jak i bardziej nerwowe rezultaty.

Na dłuższą metę jednak ta emulacja ogniska jest trochę męcząca, a po zwolnieniu, to znaczy zwiększeniu opóźnienia, robi się „poklatkowa”. Spróbujmy nieco przerobić nasz program, tchnąc w ognień trochę łagodności.

#include <Adafruit_NeoPixel.h>                                             // Dołącz bibliotekę sterującą diodami WS2812
Adafruit_NeoPixel dioda = Adafruit_NeoPixel(1, 12, NEO_GRB + NEO_KHZ800);  // Ustaw parametry zestawu: jedna dioda na dwunastym porcie.
unsigned int ognisko;                                                      // Zmienna animacji ogniska.
byte ogniskoMax;                                                           // Najjaśniejszy poziom ogniska.
void setup() {
  dioda.begin();  // Przygotuj sterowniki znajdujące się w diodach do inicjalizacji.
  dioda.show();   // Wykonaj powyższe polecenie.
}
void loop() {
  ogniskoMax = random(225, 255);                           // Losuj maksymalny poziom animacji rozjaśniania i wyciemniania.
  for (ognisko = 224; ognisko <= ogniskoMax; ognisko++) {  // Rozświetlaj ogień - od minimum do wylosowanego maksimum.
    plomien();                                             // Ustal kolory i wyślij dane do diody.
    delay(15);                                             // Opóźnienie rozjaśniania.
  }
  for (ognisko = ogniskoMax; ognisko >= 224; ognisko--) {  // Wygaszaj ogień - od wylosowanego maksimum do minimum.
    plomien();
    delay(30);  // Opóźnienie wyciemniania.
  }
}
void plomien() {
  dioda.setPixelColor(0, dioda.gamma32(dioda.ColorHSV(       // Ustaw kolor zgodny z modelem HSV:
                           (63488) + (ognisko - 223) * 160,  // odcień,
                           479 - ognisko,                    // nasycenie,
                           ognisko)));                       // jasność.
  dioda.show();
}

Powołam do życia jeszcze jedną zmienną: ogniskoMax. Tym razem to ona będzie losowana, a poprzedni algorytm, obliczający barwę, nasycenie i jasność, będzie krążył w pętli, najpierw zmierzającej z poziomu wyjściowego, to jest wysyconego karminu o jasności 224 ku wartości losowej z przedziału od 224 do 255 o jeden. Zatem rozjaśnianie będzie łagodne. Po osiągnięciu maksimum nastąpi proces odwrotny i kolejne losowanie.

Znaną już zawiłą procedurę przeliczania danych wrzucę do podprogramu plomien, ale bez pauz. Te, oczywiście skrócone względem poprzedniego przykładu, będą niezależne dla rozświetlania i wygaszania ogniska, jak to jest w prawdziwym ogniu – rozświetlanie szybciej, a wygaszanie wolniej. Regulując zmienne możemy osiągnąć zupełnie nowe wyniki, a używając funkcji wykładniczych czy trygonometrycznych – inną dynamikę zmian w czasie. Ale o tym już przy innej okazji.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2024 arduino.pl