[PROJEKT] Obsługa enkodera – KAmduino UNO, enkoder oraz WS2812
W naszym pierwszym programie będziemy wykrywać obrót pokrętła enkodera za pomocą przerwań. Kręcenie pokrętłem enkodera zgodnie z wskazówkami zegara będzie powodowało zwiększenie licznika. Natomiast obracając pokrętło w kierunku przeciwnym do ruchu wskazówek zegara spowodujemy zmniejszanie wartości licznika. Wartość tą będzie można odczytać na monitorze portu szeregowego. Kod realizujący to zadanie znajduje się na listingu 1 (kod programu dostępny jest również w zakładce do pobrania).
#define PinA 2 #define PinB 3 unsigned long time = 0; long ilosc_impulsow = 0; long licznik = 0; void setup() { Serial.begin(9600); pinMode(PinA,INPUT); pinMode(PinB,INPUT); attachInterrupt(0, blinkA, LOW); attachInterrupt(1, blinkB, LOW); time = millis(); } void loop() { while (licznik != ilosc_impulsow) { licznik = ilosc_impulsow; Serial.println(licznik); } } void blinkA() { if ((millis() - time) > 3) ilosc_impulsow--; time = millis(); } void blinkB() { if ((millis() - time) > 3) ilosc_impulsow++ ; time = millis(); }
Listing 1. Kod programu to testowania enkodera
Pierwszym krokiem będzie wskazanie, do których pinów KAmduino UNO są dołączone wyjścia kanałów A i B enkodera. Piny 2 oraz 3 zostały wybrane, ponieważ jako jedyne posiadają obsługę przerwań (o tym w dalszej części artykułu) dlatego definiujemy takie piny w programie:
#define PinA 2 #define PinB 3
Listing 2. Deklaracja pinów wejściowych dla sygnałów enkodera
Następnie deklarujemy niezbędne do działania programu zmienne:
unsigned long time = 0; long ilosc_impulsow = 0; long licznik = 0;
Listing 3. Deklaracja zmiennych niezbędnych do działania programu
Kolejnym krokiem jest konfiguracja: prędkości portu szeregowego (9600), ustawienie pinów 2 oraz 3 jako wejścia, włączenie obsługi przezwań na pinach A i B oraz przypisanie do zmiennej czas funkcji milis(). Funkcja milis() zwraca czas w milisekundach, który upłynął od momentu włączenia zasilania Arduino. Zatrzymajmy się na moment przy przerwaniach. W KAmduino UNO (oraz innych płytkach zgodnych z Arduino UNO) pinami, na których może wystąpić przerwanie są piny 2 oraz 3, przypisane zostały im numery identyfikujące 0 oraz 1. Do obsługi przerwań jest używana funkcja attachInterrupt(). Jej pierwszym parametrem jest własnie numer pinu, na którym może występować przerwanie (0 oraz 1). Następnym parametrem funkcji attachInterrupt() jest funkcja, która zostanie wywołana, gdy pojawi się stan niski. Ostatnim parametrem jest określenie, jakim stanem wyjścia ma być aktywowana funkcja przerwania – w naszym przypadku jest to stan niski.
void setup() { Serial.begin(9600); pinMode(PinA,INPUT); pinMode(PinB,INPUT); attachInterrupt(0, blinkA, LOW); attachInterrupt(1, blinkB, LOW); time = millis(); }
Listing 4. Ustawienia m.in szybkości transmisji na porcie szeregowym, ustawienia przerwań itp.
Następnie przechodzimy do pętli głównej programu (listing 5).
void loop() { while (licznik != ilosc_impulsow) { licznik = ilosc_impulsow; Serial.println(licznik); } }
Listing 5. Pętla główna programu
W tej pętli jest wyświetlana (poprzez port szeregowy) wartość licznika, wtedy gdy ulegnie ona zmianie (tzn. gdy zmienna licznik jest różna od ilosc_impulsow).
Na końcu programu znajdują się dwie funkcje obsługi przerwań: void blinkA() oraz void blinkB().
void blinkA() { if ((millis() - time) > 3) ilosc_impulsow--; time = millis(); } void blinkB() { if ((millis() - time) > 3) ilosc_impulsow++ ; time = millis(); }
Listing 6. Funkcje obsługi przerwań
Powyższe funkcje one wywołane, gdy zostanie wykryte przerwanie od stanu niskiego odpowiednio na pinie 2 lub 3. Powodują one zwiększenie albo zmniejszenie zmiennej ilosc_impulsow (w zależności od tego, na którym pinie wystąpiło przerwanie).
Wgrywamy program na KAmdruino UNO, następnie otwieramy monitor portu szeregowego oraz ustawiamy prędkość na 9600 (jeżeli jest ustawiona inna). Teraz kręcąc pokrętłem enkodera możemy zmniejszać lub zwiększać wartość licznika. Przykładowy widok monitora protu szeregowego pokazano na rysunku 5 – w czerwonym prostokącie widzimy, że wartość licznika zwiększa się, natomiast w zielonym prostokącie wartość licznika zmniejsza się.
Rysunek 5. Monitor portu szeregowego podczas działania programu
Teraz przejdziemy do drugiego programu. Będziemy w nim sterować diodami adresowanymi WS2812 za pomocą enkodera. Obracanie pokrętła enkodera w prawo (zgodnie z kierunkiem ruchu wskazówek zegara) będzie powodować zapalanie się kolejnych diod WS2812 w pierścieniu. Natomiast kręcąc pokrętłem w przeciwnym kierunku (przeciwnie do wskazówek zegara ) będziemy zmniejszać ilość święcących się diod. Dodatkowo krótkie przyciśnięcie enkodera będzie powodowało zmianę koloru świecenia diod na następny kolor, w kolejności: czerwony, zielony oraz niebieski.
Kod programu realizujący powyższe założenia znajduje się poniżej (kod programu dostępny jest również w zakładce do pobrania):
#define PinA 2 #define PinB 3 #define Przycisk 4 #include <Adafruit_NeoPixel.h> unsigned long time = 0; long ilosc_impulsow = 0; long licznik = 0; int r=255; int g=0; int b=0; const uint8_t LICZBA_DIOD_RGB = 16; const uint8_t PIN_GPIO = 6; Adafruit_NeoPixel strip = Adafruit_NeoPixel(LICZBA_DIOD_RGB, PIN_GPIO, NEO_GRB + NEO_KHZ800); void zapal_diody() { for(int i=0;i <licznik ;i++) { strip.setPixelColor(i, r, g, b); strip.show(); } for(int j=16;j>licznik ;j--) { strip.setPixelColor(j, 0, 0, 0); strip.show(); } } void setup() { Serial.begin(9600); pinMode(PinA,INPUT); pinMode(PinB,INPUT); pinMode(Przycisk,INPUT); attachInterrupt(0, blinkA, LOW); attachInterrupt(1, blinkB, LOW); time = millis(); strip.begin(); strip.show(); } void loop() { while (licznik != ilosc_impulsow) { licznik = ilosc_impulsow; Serial.println(licznik); } zapal_diody(); if (digitalRead(Przycisk)==LOW) { delay(70); if (digitalRead(Przycisk)==LOW) { Serial.println("Przycisk!"); if (r==255) { r=0; g=255; } else if(g==255) { g=0; b=255; } else if (b==255) { b=0; r=255; } } } } void blinkA() { if ((millis() - time) > 3) { ilosc_impulsow--; } time = millis(); } void blinkB() { if ((millis() - time) > 3) { ilosc_impulsow++; } time = millis(); }
Listing 7. Kod programu do sterowania diodami przy pomocy enkodera
Do deklaracji musimy dodać pin, do którego podłączymy przycisk enkodera (pin 4):
#define PinA 2 #define PinB 3 #define Przycisk 4
Listing 8. Deklaracja pinów wejściowych dla sygnałów enkodera
Do sterowania diodami WS2812 wykorzystamy bibliotekę Adafruit_NeoPixel.h musimy ją dołączyć do programu:
#include <Adafruit_NeoPixel.h>
Listing 9. Dodanie biblioteki Adafruit_NeoPixel.h do programu
Dokładny opis użycia sterowania diodami WS2812 przy użyciu tej biblioteki został opisany przy okazji wpisu [PROJEKT] KAmodWS2812 + KAmduinoUNO.
Następnie deklarujemy zmienne przechowujące nasycenie poszczególnych kolorów, na który będą świecić się diody WS2812 oraz zmienne zawierające liczbę diod WS2812 oraz numer pinu, do którego zostały podłączone:
int r=255; int g=0; int b=0; const uint8_t LICZBA_DIOD_RGB = 16; const uint8_t PIN_GPIO = 6;
Listing 10. Dodatkowe zmienne potrzebne do obsługi diod WS2812
Dodatkowo należy dokonać inicjalizacji wymaganej przez bibliotekę, w której m.in. należy podać liczbę używanych diod oraz wskazać pin, do którego są one do:
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LICZBA_DIOD_RGB, PIN_GPIO, NEO_GRB + NEO_KHZ800);
Listing 11. Inicjalizacja biblioteki Adafruit_NeoPixel
Na końcu funkcji setup() należy wywołać dwie funkcje, które odpowiadają za uruchomienie biblioteki:
void setup() { Serial.begin(9600); pinMode(PinA,INPUT); pinMode(PinB,INPUT); pinMode(Przycisk,INPUT); attachInterrupt(0, blinkA, LOW); attachInterrupt(1, blinkB, LOW); time = millis(); strip.begin(); strip.show(); }
Listing 12. Ustawienia m.in szybkości transmisji na porcie szeregowym, ustawienia przerwań itp.
Do zapalania odpowiedniej liczby diod napiszemy funkcję zapal_diody():
void zapal_diody() { for(int i=0;i <licznik ;i++) { strip.setPixelColor(i, r, g, b); strip.show(); } for(int j=16;j>licznik ;j--) { strip.setPixelColor(j, 0, 0, 0); strip.show(); } }
Listing 13. Funkcja void zapal_diody(): do sterowania liczbą zapalonych diod
W zależności od wartości licznika zostaje zapalona odpowiednia liczba diod WS2812. Funkcja będzie wywoływana w pętli głównej programu:
void loop() { while (num != count) { num = count; Serial.println(num); } zapal_diody(); [...]
Listing 14. Pętla główna programu
W pętli głównej programu będzie znajdować się również obsługa przycisku enkodera. Krótkie wciśnięcie przycisku powoduje wystąpienie stanu niskiego, dlatego w warunku IF sprawdzamy czy na pinie 4 pojawił się stan niski. Z uwagi na drgania styków trzeba zastosować podwójny warunek IF. Część programu znajdująca się w funkcji warunkowej IF będzie wykonana tylko wtedy, gdy przycisk będzie wciśnięty dłużej niż 70 milisekund od pierwszego wciśnięcia. Jeżeli to nastąpiło, w konsoli zostanie wyświetlony napis „Przycisk!” informujący o wciśniętym przycisku. Następnie są sprawdzane zmienne r,g,b. W zależności od tego, która zmienna ma wartość 255, po przyciśnięciu przycisku nastąpi zmiana koloru świecenia diod na pierścieniu. Kolory będą zmienione w kolejności czerwony > zielony > niebieski. Po uruchomieniu programu, diody będą świecić się na czerwono.
[...] if (digitalRead(Przycisk)==0) { delay(70); if (digitalRead(Przycisk)==0) { Serial.println("Przycisk!"); if (r==255) { r=0; g=255; } else if(g==255) { g=0; b=255; } else if (b==255) { b=0; r=255; } } }
Listing 15. Pętla główna programu – ciąg dalszy
Działanie programu do sterowania diodami WS2812 z wykorzystaniem enkodera zostało przedstawione na materiale wideo: