Projekt przedstawia zaawansowany bezprzewodowy układ wyzwalania migawki w aparatach fotograficznych firmy Nikon wykorzystujący komunikację za pośrednictwem portu podczerwieni (Fig. 1). Stanowi on rozwinięcie pomysłu autorstwa Bartosza Zielińskiego opublikowanego na portalu Majsterkowo.pl i przedstawionego w realizacji Arduino: wyzwalanie aparatu firmy Nikon za pomoca portu IR.
Projekt umożliwia wyzwalanie aparatu fotograficznego w trybach zdjęć:
Najważniejszą funkcją układu jest możliwość wpływania na parametry fotografowania w trybie zdjęć seryjnych. System umożliwia konfigurację odstępu z jakim będą robione zdjęcia (interwal
) oraz czasu rejestracji sekwencji (czas_sekw.
). Sterowanie odbywa się za pomocą kilkupoziomowego menu sterującego. Wielopoziomowe meniu z możliwością ustawienia różnych opcji sterujących to jedno z typowych elementów spotykanych w projektach Arduino. W przedstawianym projekcie wykorzystano bibliotekę MenuBackend autorstwa Alexandra Brevig'a. Nieocenioną pomoc oddał tutorial autorstwa wojtekizk (MenuBackend jak się w nim odnaleźć?).
Układ jest wyposażony w wyświetlacz LCD (2x16) umożliwiający nawigację po menu i ustawianie parametrów sekwencji (Fig. 2). Do obsługi menu wykorzystano pięć przycisków sterujących. Cztery z nich umożliwiają nawigację w menu: w dół, w górę, w lewo i w prawo. Ostatni przycisk służy do potwierdzania wartości ustawianych zmiennych i uruchamiania wybranych akcji. Przyciski podłączone są do jednego pinu analogowego Arduino. Zastosowano drabinkę rezystorów opartą na identycznych wartościach rezystancji (Arduino: obsługa przycisków za pomocą wejścia analogowego). Sygnał sterujący portem podczerwieni aparatu generowany jest przez program i następnie jest przekazywany do diody IR. W układzie zastosowano także dwie diody LED. LED1 (czerwona) służy do sygnalizacji wciśnięć przycisków. LED2 (zielona) informuje o wygenerowaniu sygnału sterującego IR w celu zrobienia zdjęcia. Aby obniżyć liczbę połączeń pomiędzy Arduino i wyświetlaczem LCD, zastosowano konwerter I2C.
Układ wyposażono w czteropoziomowe menu sterujące:
ZDJECIE Zrob zdjecie SEKWENCJA Parametry sekw. Zrob sekwencje Ustawienia Manualne Interwał Czas sekwencji Predefiniowane co 1s przez 5s co 1s przez 10s co 1s przez 15s co 3s przez 5s co 3s przez 10s co 3s przez 15s co 5s przez 5s co 5s przez 10s co 5s przez 15s INFO
Pierwszy poziom menu stanowią trzy opcje:
Opcje menu: ZDJĘCIE i SEKWENCJA umożliwiają dostęp do zasadniczych części funkcji urządzenia. Ustawiamy w nich niezbędne parametry i uruchamiamy wybrane opcje. Menu INFO ma za zadanie informować użytkownika o wersji zastosowanego oprogramowania. W menu ZDJĘCIE istnieje tylko jedna opcja: "Zrob zdjecie". Wejście do niej i naciśnięcie przycisku OK spowoduje wygenerowanie sygnału sterującego IR i przy widoczności emitera (diody IR) i detektora (portu IR w aparacie), spowoduje wyzwolenie migawki aparatu.
Najbardziej rozbudowane jest menu trybu zdjęć seryjnych. Pierwsza opcja "Parametry sekw." umożliwia sprawdzenie aktualnych ustawień trybu zdjęć seryjnych. Po wejściu do podmenu wyświetlane są aktualne parametry sekwencji: interwał z jakim będą wykonywane zdjęcia oraz czas wykonywania całej sekwencji (Fig. 3). Zmiana ustawień zmiennych czy to w trybie manualnym:
SEKWENCJA > Ustawienia > Manualne > Interwał/Czas sekwencji
czy też w trybie predefiniowanym:
SEKWENCJA > Ustawienia > Predefiniowane
powodują, że są one globalnie modyfikowane, a zmiana odzwierciedla się także w opisywanej opcji "Parametry sekw.".
Kolejna opcja "Zrób sekwencje" umożliwia uruchomienie mechanizmu generowania sygnału IR zgodnego z ustawieniami parametrów sekwencji (interwału i czasu trwania serii). Wysyłany jest pierwszy sygnał sterujący wyzwoleniem migawki i wykonywane jest pierwsze zdjęcie. Po jego wykonaniu następuje przerwa równa ustawionemu interwałowi. Po jej upłynięciu generowany jest kolejny sygnał sterujący aparatem i wykonywane jest kolejne zdjęcie. Sytuacja powtarza się, aż do momentu upłynięcia całego czasu sekwencji. Po uruchomieniu opcji, na wyświetlaczu pojawia się informacja o liczbie wykonanych zdjęć oraz liczbie wszystkich zdjęć w sekwencji. Zielona dioda LED wskazuje wygenerowanie sygnału IR. Po wykonaniu wszystkich zdjęć sekwencji, zielona dioda LED zostaje zapalona na 5 sekund, a na wyświetlaczu pojawia się komunikat: "Sekw. wykonana". Podczas wykonywania sekwencji zdjęć wyświetlacz LED pozostaje cały czas podświetlony.
Opcja "Ustawienia" umożliwia konfigurację dwóch parametrów właściwych dla trybu zdjęć seryjnych: interwału i czasu wykonywania sekwencji. Użytkownik ma do dyspozycji dwa opcjonalne sposoby wprowadzania zmian:
Wejście do ustawień predefiniowanych umożliwia użytkownikowi wybór jednego z przygotowanych zestawów parametrów sekwencji (Fig. 6).
Dostępne są następujące opcje:
Pierwsza liczba w każdej opcji wyraża czas interwału pomiędzy kolejnymi zdjęciami. Druga liczba definiuje całkowity czas trwania sekwencji na zmontowanym filmie. Dla przykładu: Jeżeli wybrano opcję "co 1s przez 5s", czas trwania sekwencji to 5 sekund. Oznacza to, że aby otrzymać wrażenie płynności ruchu, przy FPS ( - liczbie klatek wyświetlanych na sekundę filmu: 25), należy wykonać 5 × 25 czyli 125 zdjęć. czas rejestracji wszystkich zdjęć wyniesie więc 125 × 1s = 125s, co jest równoważne 2,08 minut. Jeżeli wybrano opcję "co 5s przez 15s", oznacza to, że przy tym samym FPS, robiąć zdjęcie co 5 sekund, będziemy rejestrować 15 × 25 czyli 375 zdjęć. Zaś czas wykonania całej sekwencji wyniesie 375 × 5s = 1875s czyli ponad 31 minut.
opcja menu | interwał | czas sekwencji | liczba klatek/s | liczba zdjęć | czas rejestracji |
---|---|---|---|---|---|
d | ts [s] | FPS | n = ts × FPS | tr = (n × d) / 60 [min] | |
co 1s przez 5s |
1 | 5 | 25 | 125 | 2,08 |
co 1s przez 10s |
1 | 10 | 25 | 250 | 4,16 |
co 1s przez 15s |
1 | 15 | 25 | 375 | 6,25 |
co 3s przez 5s |
3 | 5 | 25 | 125 | 6,25 |
co 3s przez 10s |
3 | 10 | 25 | 250 | 12,5 |
co 3s przez 15s |
3 | 15 | 25 | 375 | 18,75 |
co 5s przez 5s |
5 | 5 | 25 | 125 | 10,41 |
co 5s przez 10s |
5 | 10 | 25 | 250 | 20,83 |
co 5s przez 15s |
5 | 15 | 25 | 375 | 31,25 |
Wejście do ustawień Manualnych umożliwia ręczną modyfikację interwału (Fig. 7) i czasu rejestracji sekwencji (Fig. 8).
Wartości parametrów ustawia się za pomocą przycisów "w górę" i "w dół". Naciśnięcie przycisku "w górę" zwiększa wartość parametru, a naciśnięcie przycisku "w dół" zmniejsza jego wartość. Ustawione wartości wymagają akceptacji za pomoca przycisku "OK".
/* Przykład użycia biblioteki MenuBackend do obsługi wielopoziomowego menu: ...by wojtekizk (wk@warcaby.lh.pl) przygotowany na forum majasterkowo.pl //---------------------------------------------------------------------------------- Opis: Generalnie tworzenie menu w oparciu o bibliotekę MenuBackend jest bardzo proste, jeśli pamietać o kilku zasadach: 1) Najważniejsza jest konfiguracja Menu w funkcji menuSetup() 2) Poruszanie po menu powinno byc zgodne ze schematem menu, tzn. jeśli coś jest submenu, to szukasz tego klawiszem "w prawo", a równorzędne "dół - góra" 3) Warto dla użytkownika wydrukować taka mapkę przy klawiaturce. 4) Obsługę wszystkich opcji wrzucaj do funkcji void menuUseEvent(MenuUseEvent used) Tam zapuszczasz pętle while lub do while i czytasz w niej stan klawiaturki. W tej pętli modyfikujesz zachowanie programiku w zależności od tego, co wciśniesz. Jeśli wciśniesz OK to wychodzisz z pętli while i de facto wracasz do loop-a. Wszystkie ważniejsze kwestie opatrzono dość czytelnym komentarzem. */ // ============================================================ #include <Wire.h> // Dołączenie biblioteki Wire (I2C) #include <LiquidCrystal_I2C.h> // Dołączenie biblioteki I2C dla LCD #include <MenuBackend.h> // dołączenie biblioteki // --- definicja znaków strzałek: dół, lewo, prawo, gora-dół i powrót --- uint8_t arrowUpDown[8] = { 0x4,0xe,0x15,0x4,0x15,0xe,0x4}; uint8_t arrowDown[8] = { 0x4,0x4,0x4,04,0x15,0xe,0x4}; uint8_t arrowRight[8] = { 0x0,0x4,0x2,0x1f,0x2,0x4,0x0}; uint8_t arrowLeft[8] = { 0x0,0x4,0x8,0x1f,0x8,0x4,0x0}; uint8_t arrowBack[8] = { 0x1,0x1,0x5,0x9,0x1f,0x8,0x4}; LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Ustawienie adresu LCD na 0x27 volatile int zm =-1; // to dla kontroli zmiany stanu klawiatury volatile int x=-1; // zmienna pomocnicza volatile int stan_Analog; // wartość na wejściu analogowym dla klawiaturki analogowej char *linia1; // pierwsza linia wyświetlanego tekstu na LCD char *linia2; // druga linia wyświetlanego tekstu na LCD int a = 0; //zmienna pomocnicza ustawienie początkowe (odczyt PIN-u analogowego) unsigned long czas; // Zmienna mierząca czas po naciśnięciu przycisku const int okres_sygn_38kHz = 26; // obsługa IR: okres trwania 1 taktu sygnału o częstotliwości 38kHz [mikrosek.] (T=1/f) // DOMYŚLNE USTAWIENIA FOTOGRAFICZNE SEKWENCJI long interwal=3000; // Interwał: 3 sek. long czas_sekw=5000; // Czas sekwencji: 5 sek. // -----------------------------opcje Menu: --------------------------------------- // tworzy obiekty klasy MenuItem, które dziedziczą po klasie MenuBackend MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent); // konstruktor MenuItem P1 = MenuItem("ZDJECIE",1); // Menu trybu zdjęć pojedynczych MenuItem P11 = MenuItem("Zrob zdjecie",2); // Wykonanie zdjęcia MenuItem P2 = MenuItem("SEKWENCJA",1); // Menu trybu zdjęć seryjnych (sekwencji) MenuItem P21 = MenuItem("Param. sekw.",2); // Wyświetlanie aktualnych parametrów serii (interwału pomiedzy zdjęciami i czasu rejestracji sekwencji) MenuItem P22 = MenuItem("Zrob sekwencje",2); // Wykonanie sekwencji MenuItem P23 = MenuItem("Ustawienia",2); // Menu Ustawienia MenuItem P231 = MenuItem("Manualne",3); // Menu ustawienia manualne MenuItem P2311 = MenuItem("Interwal",4); // Ustawienia manualne: Interwał MenuItem P2312 = MenuItem("Czas sekw.",4); // Ustawienia manualne: Czas sekwencji MenuItem P232 = MenuItem("Predef.",3); // Menu ustawienia predefiniowane MenuItem P2321 = MenuItem("co 1s (5s)",4); // zdjęcie co 1 sek., sekwencja 5 sek. (125 zdjęć) MenuItem P2322 = MenuItem("co 1s (10s)",4); // zdjęcie co 1 sek., sekwencja 10 sek. (250 zdjęcia) MenuItem P2323 = MenuItem("co 1s (15s)",4); // zdjęcie co 1 sek., sekwencja 15 sek. (375 zdjęć) MenuItem P2324 = MenuItem("co 3s (5s)",4); // zdjęcie co 3 sek., sekwencja 5 sek. (125 zdjęć) MenuItem P2325 = MenuItem("co 3s (10s)",4); // zdjęcie co 3 sek., sekwencja 10 sek. (250 zdjęć) MenuItem P2326 = MenuItem("co 3s (15s)",4); // zdjęcie co 3 sek., sekwencja 15 sek. (375 zdjęć) MenuItem P2327 = MenuItem("co 5s (5s)",4); // zdjęcie co 5 sek., sekwencja 5 sek. (125 zdjęć) MenuItem P2328 = MenuItem("co 5s (10s)",4); // zdjęcie co 5 sek., sekwencja 10 sek. (250 zdjęć) MenuItem P2329 = MenuItem("co 5s (15s)",4); // zdjęcie co 5 sek., sekwencja 15 sek. (375 zdjęć) MenuItem P3 = MenuItem("INFO",1); // Menu info o aplikacji /* --- Pozycjonowanie menu ( zgodnie z ustawieniem powyżej) ------------ add - dodaje w pionie, addRight - dodaje w poziomie z prawej, addLeft dodaje z lewej */ void menuSetup() // Funkcja klasy MenuBackend { menu.getRoot().add(P1); // Ustawienie korzenia Menu P1.add(P11); // rodzic ZDJECIE ma dziecko "Zrob zdjecie" P11.addLeft(P1); P1.addRight(P2); // po prawej dla ZDJECIE jest SEKWENCJA P2.add(P21); // rodzic SEKWENCJA ma dziecko "Parametry sekw" P21.add(P22); P21.addLeft(P2); // poniżej "Parametry sekw" jest "Zrob sekwencje" P22.add(P23); P22.addLeft(P2); // poniżej "Zrob sekwencje" jest "Ustawienia" P23.addLeft(P2); P23.add(P21); // tutaj zamykamy pętlę i wracamy do pierwszej podopcji P23.addRight(P231); // a tu dziecko "Ustawienia" ma już własne dziecko "Manualne" // dodajemy z prawej, ponieważ gdybyśmy dali poniżej to zrobilibyśmy // kolejne dziecko dla SZKIC, a w projekcie jest inaczej P231.add(P232); P231.addLeft(P23); // poniżej Manualne jest Predefiniowane P232.addLeft(P23); P232.add(P231); // tutaj zamykamy pętlę i wracamy do pierwszej podopcji P231.addRight(P2311); // INTERWAŁ P2311.add(P2312); // P2311.addLeft(P231); // CZAS SEKWENCJI P2312.addLeft(P231); P2312.add(P2311); // tutaj zamykamy pętlę i wracamy do pierwszej podopcji P232.addRight(P2321); // w prawo od Ustawienie predefiniowane przechodzimy do "co 1s (5s)" P2321.add(P2322); // poniżej "co 1s (5s)" jest "co 1s (10s)" P2321.addLeft(P232); // w lewo od "co 1s (5s)" przechodzimy do Predefiniowane P2322.add(P2323); // poniżej "co 1s (10s)" jest "co 1s (15s)" P2322.addLeft(P232); // w lewo od "co 1s (10s)" przechodzimy do Predefiniowane P2323.add(P2324); // poniżej "co 1s (15s)" jest "co 3s (5s)" P2323.addLeft(P232); // w lewo od "co 1s (15s)" przechodzimy do Predefiniowane P2324.add(P2325); // poniżej "co 3s (5s)" jest "co 3s (10s)" P2324.addLeft(P232); // w lewo od "co 3s (5s)" przechodzimy do Predefiniowane P2325.add(P2326); // poniżej "co 3s (10s)" jest "co 3s (15s)" P2325.addLeft(P232); // w lewo od "co 3s (10s)" przechodzimy do Predefiniowane P2326.add(P2327); // poniżej "co 3s (15s)" jest "co 5s (5s)" P2326.addLeft(P232); // w lewo od "co 3s (15s)" przechodzimy do Predefiniowane P2327.add(P2328); // poniżej "co 5s (5s)" jest "co 5s (10s)" P2327.addLeft(P232); // w lewo od "co 5s (5s)" przechodzimy do Predefiniowane P2328.add(P2329); // poniżej "co 5s (10s)" jest "co 5s (15s)" P2328.addLeft(P232); // w lewo od "co 5s (10s)" przechodzimy do Predefiniowane P2329.add(P2321); // zamykamy pętlę i wracamy do pierwszej podopcji "co 1s (5s)" P2.addRight(P3); // na prawo od SEKWENCJA jest INFO P3.addRight(P1); // zamkniecie pętli głównej, czyli poziomej - z INFO do ZDJECIE } // ============================================================================================ // = Funkcje obsługi IR = // ============================================================================================ // Funkcja opisująca działanie pojedynczego taktu sygnału void sygnal_38kHZ(){ digitalWrite(8, HIGH); delayMicroseconds(okres_sygn_38kHz / 2); // połowa okresu => współczynnik wypełnienia = 0,5 digitalWrite(8, LOW); delayMicroseconds(okres_sygn_38kHz / 2); } // ---------------------------------------------------------------------------------- // Funkcja opisująca stan niski void stan_1(int ilosc_cykli){ for(int i = 0; i < ilosc_cykli; i++) sygnal_38kHZ(); } // ---------------------------------------------------------------------------------- // Funkcja opisująca stan wysoki void stan_0(int ilosc_cykli){ delayMicroseconds(ilosc_cykli * okres_sygn_38kHz); } // ---------------------------------------------------------------------------------- // Funkcja generująca sygnał wyzwalający aparaty Nikon void zrobZdjecie(){ digitalWrite(7, HIGH); // sygnalizacja LED wykonywania zdjęcia for (int i=0; i < 2; i++) { // sygnał wzrocowy ma być powtórzony 2x stan_1(76); // 76 cykli "1" stan_0(1064); // 1064 cykli "0" stan_1(15); // 15 cykli "1" stan_0(60); // 60 cykli "0" stan_1(15); // 15 cykli "1" stan_0(136); // 136 cykli "0" stan_1(15); // 15 cykli "1" stan_0(2423*2); // 2423 cykli "0" } delay(100); // trzeba było przedłużyć, gdyż czas wysyłania informacji do aparatu był za krótki digitalWrite(7, LOW); } // ============================================================================================ // Reakcja na wciśnięcie przycisku "OK" void menuUseEvent(MenuUseEvent used) // funkcja klasy MenuBackend { Serial.print("wybrano: "); Serial.println(used.item.getName()); // do testów, potem niepotrzebne // AKCJA: ZDJECIE > zrob zdjecie if (used.item.getName() == "Zrob zdjecie") { digitalWrite(7, HIGH); zrobZdjecie(); delay(400); // szybciej może aparat odmówić posłuszeństwa digitalWrite(7, LOW); } // AKCJA: SEKWENCJA > param. sekw. if (used.item.getName() == "Param. sekw.") { lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("co "); lcd.print(interwal/1000); lcd.print("s ("); lcd.print(czas_sekw/1000); lcd.print("s)"); //poprzedni stan LCD } // Akcja SEKWENCJA > Zrob sekwencje if (used.item.getName() == "Zrob sekwencje") { int liczba_zdjec = ((czas_sekw/1000)*25); for(int zdjecie = 0; zdjecie < liczba_zdjec; zdjecie++) { digitalWrite(9, LOW); lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("fot.: "); lcd.print(zdjecie); lcd.print("(/"); lcd.print(liczba_zdjec); lcd.print(")"); zrobZdjecie(); delay(interwal); } lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("Sekw. wykonana"); digitalWrite(7, HIGH); // Zielona dioda LED informuje o wykonaniu sekwencji delay(5000); // po 5 sek. digitalWrite(7, LOW); // dioda LED gaśnie } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 1s (5s) if (used.item.getName() == "co 1s (5s)") { interwal=1000; czas_sekw=5000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 1s (10s) if (used.item.getName() == "co 1s (10s)") { interwal=1000; czas_sekw=10000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 1s (15s) if (used.item.getName() == "co 1s (15s)") { interwal=1000; czas_sekw=15000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 3s (5s) if (used.item.getName() == "co 3s (5s)") { interwal=3000; czas_sekw=5000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 3s (10s) if (used.item.getName() == "co 3s (10s)") { interwal=3000; czas_sekw=10000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 3s (15s) if (used.item.getName() == "co 3s (15s)") { interwal=3000; czas_sekw=15000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 5s (5s) if (used.item.getName() == "co 5s (5s)") { interwal=5000; czas_sekw=5000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 5s (10s) if (used.item.getName() == "co 5s (10s)") { interwal=5000; czas_sekw=10000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > PREDEFINIOWANE > co 5s (15s) if (used.item.getName() == "co 5s (15s)") { interwal=5000; czas_sekw=15000; lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("ustawiono..."); } // AKCJA: SEKWENCJA > USTAWIENIA > MANUALNE > interwal if (used.item.getName() == "Interwal") { lcd.setCursor(0,0); lcd.write(7); // Wyswietl symbol strzałki góra-dół lcd.print(" "); lcd.setCursor(1,0); lcd.print("Interwal = "); lcd.setCursor(13,0); lcd.print(interwal/1000); // wyświetl akt. interwal long interwal_sek = interwal/1000; int akcja=-1; delay(1000); // zmienna pomocnicza, sterująca dla petli while // jesli nie puścisz klawisza OK w ciągu 1 sek. to powrót do menu while(akcja!=4) // ta pętla trwa tak długo aż wciśniesz klawisz OK { zm=-1; akcja=czytaj_1(5); //delay(300); // odczyt stanu klawiatury - funkcja czytaj_1 lub czytaj_2 lub czytaj_3 // opis poniżej przy 3 różnych definicjach funkcji czytaj if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury { if (akcja==1) { // jesli akcja=1 (wciśnieto klawisz w górę) interwal_sek++; // zwiększono interwal_sek if(interwal_sek>99) interwal_sek=99; // ustawiono max próg i wyświetlono obecną interwal_sek lcd.setCursor(13,0); lcd.print(interwal_sek); delay(300); } if(akcja==2) { // jesli akcja=2 (wciśnieto klawisz w dół) interwal_sek--; // Zmniejszono interwal_sek if(interwal_sek<1) interwal_sek=1; // ustawiono min próg lcd.setCursor(13,0); lcd.print(interwal_sek); // wyświetlono aktualny interwal_sek delay(300); } if(akcja==4) // jeśli wciśnieto OK { lcd.setCursor(0,0); lcd.print("*Interwal OK"); delay(2000); // pokazujemy "*Interwal OK" przez 2 sek. lcd.setCursor(1,0); lcd.print(" "); // czyścimy linię lcd.setCursor(1,0); lcd.print(linia1); // odtwarzamy poprzedni stan na LCD interwal=interwal_sek*1000; // Aktualizacja zmiennej interwal } } } zm=akcja; // aktualizacja zmiennej zm, po to aby reagować tylko na zmiany stanu klawiatury // tu WAŻNY MOMENT - kończy się pętla while i zwracamy sterowanie do głównej pętli loop() } // AKCJA: SEKWENCJA > USTAWIENIA > MANUALNE > Czas sekw. if (used.item.getName() == "Czas sekw.") { lcd.setCursor(0,0); lcd.write(7); // wyswietlamy symbol strzałki góra-dół lcd.print(" "); lcd.setCursor(1,0); lcd.print("Czas sekw. = "); // tekst dla użytkownika lcd.setCursor(13,0); lcd.print(czas_sekw/1000); // wyświetlamy akt. interwal long czas_sekw_sek = czas_sekw/1000; int akcja=-1; delay(1000); // zmienna pomocnicza, sterująca dla petli while // jesli nie puścisz klawisza OK w ciągu 1 sek. to powrót do menu while(akcja!=4) // ta pętla trwa tak długo aż wciśniesz klawisz OK { zm=-1; akcja=czytaj_1(5); // odczyt stanu klawiatury - funkcja czytaj_1 if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury { if (akcja==1) { // jesli akcja=1 (wciśnieto klawisz w górę) czas_sekw_sek++; // zwiększonie czas_sekw_sek if(czas_sekw_sek>99) czas_sekw_sek=99; // ustawiono max próg lcd.setCursor(13,0); lcd.print(czas_sekw_sek); // wyświetlono aktualny czas_sekw_sek delay(300); } if(akcja==2) { // jesli akcja=2 (wciśnieto klawisz w dół) czas_sekw_sek--; // zmniejszono czas_sekw_sek if(czas_sekw_sek<1) czas_sekw_sek=1; // ustawiono min próg lcd.setCursor(13,0); lcd.print(czas_sekw_sek); // wyświetlono aktualny czas_sekw_sek delay(300); } if(akcja==4) // jeśli wciśnieto OK { lcd.setCursor(0,0); lcd.print("*Interwal OK"); delay(2000); // pokazujemy "*Interwal OK" przez 2 sek. lcd.setCursor(1,0); lcd.print(" "); // czyścimy linię lcd.setCursor(1,0); lcd.print(linia1); // odtwarzamy poprzedni stan na LCD czas_sekw=czas_sekw_sek*1000; // Aktualizacja zmiennej czas_sekw [ms] } } } zm=akcja; // aktualizacja zmiennej zm, po to aby reagować tylko na zmiany stanu klawiatury // tu WAŻNY MOMENT - kończy się pętla while i zwracamy sterowanie do głównej pętli loop() } // AKCJA: INFO if (used.item.getName() == "INFO") { lcd.setCursor(1,0); lcd.print(" "); lcd.setCursor(1,0); lcd.print("Ver.1.0 (2016)"); } } // --- Reakcja na wciśnięcie klawisza ----------------------------------------------------------------- void menuChangeEvent(MenuChangeEvent changed) // funkcja klasy MenuBackend { /* tak naprawdę to tylko tutaj przydaje się ów shortkey i służy przede wszystkim do wzbogacenia menu o symbole strzałek w zależności co wybrano. Wszystko co tutaj się wyprawia jest pokazywane na LCD. */ int c=changed.to.getShortkey(); // pobieramy shortkey (1,2,3, lub 4) lcd.clear(); lcd.setCursor(0,0); if(c==1) // jeśli to menu głowne (shortkey=1) to: { lcd.write(3); // strzałka w lewo strcpy(linia1,changed.to.getName()); // tworzymy napis w pierwszej linii lcd.print(linia1); // wyświetlamy ją lcd.setCursor(15,0); lcd.write(4); // strzałka w prawo lcd.setCursor(0,1); lcd.write(5); // strzałka w dół lcd.setCursor(15,1); lcd.write(5); // strzałka w dół } if(c==2) // jeśli to podmenu dla dziecka - (shortkey=2) to: { lcd.print("*"); // rysujemy gwiazdkę strcpy(linia2,changed.to.getName()); // tworzymy napis w pierwszej linii lcd.print(linia1); // wyświetlamy ją lcd.setCursor(15,0); lcd.print("*"); // gwiazdka lcd.setCursor(0,1); lcd.write(6); // druga linia i strzałka powrotu (arrowBack) lcd.print(changed.to.getName()); // wyświetlamy nazwe "dziecka" lcd.setCursor(15,1); lcd.write(7); // strzałka góra-dół } if(c==3) // jeśli dziecko ma dziecko - (shortkey =3) to: { lcd.print("*"); // gwiazdka strcpy(linia2,changed.to.getName()); // kopiujemy akt. nazwe opcji menu do zmiennej linia2 lcd.print(linia1); // i wyświetlamy pierwszą linię lcd.setCursor(15,0); lcd.print("*"); // gwiazdka lcd.setCursor(0,1); lcd.write(6); // druga linia i strzałka arrowBack lcd.print(changed.to.getName()); // wyświetlamy wnuka w drugiej linii lcd.setCursor(15,1); lcd.write(4); // strzałka w prawo bo są wnuki } if(c==4) // jeśli to wnuk (shortkey =4) to: { lcd.print("*"); // gwaizdka lcd.print(linia2); // w pierwszej linii wyświetlamy dziecko ( czyli rodzica wnuka) lcd.setCursor(15,0); lcd.print("*"); // gwaizdka lcd.setCursor(0,1); lcd.write(6); // druga linia i strzałka arrowBack lcd.print(changed.to.getName()); // wyświetlamy wnuka lcd.setCursor(15,1); lcd.write(7); // strzałka góra-dół } } // --- Funkcja odczytująca stan klawiatury -------------------------------------------------- // --- wersja dla klawiatury 5-cio przyciskowej --------------------------------------------- volatile int czytaj_1(int analog) { int stan_Analog = analogRead(analog); delay(30); //Serial.println(stan_Analog); if (stan_Analog > 1000) return -1; // dla wartosci poza zakresem if (stan_Analog < 140) return 2; // "w dół" if (stan_Analog < 600) return 1; // "do góry" if (stan_Analog < 700) return 3; // "w lewo" if (stan_Analog < 800) return 0; // "w prawo" if (stan_Analog < 900) return 4; // "OK" return -1; // nic nie wciśnięto } void setup() { linia1=new char[16]; // zainicjowanie dynamicznego wskaźnika do tekstu linia2=new char[16]; // to BARDZO WAŻNE, bo wskażnik dynamiczny musi wskazywać na // z góry określone miejsce w pamieci. Gdybyśmy tego nie zrobili // to wcześniej czy później programik mógłby wskoczyć w nieokreślony // bliżej obszar pamięci, co może skutkować nieodwracalnymi konsekwencjami // łącznie z przestawieniem Fuse Bitów !!! // Proszę uważać na wszystkie dynamiczne wskaźniki, TAKA DOBRA RADA :-) Serial.begin(9600); // inicjacja Serial portu (testy) lcd.begin(16, 2); // inicjacja LCD lcd.createChar(3,arrowLeft); // tworzymy w pamięci LCD 5 własnych znaków dla strzałek lcd.createChar(4,arrowRight); lcd.createChar(5,arrowDown); lcd.createChar(6,arrowBack); lcd.createChar(7,arrowUpDown); lcd.noBacklight(); // Wyłączenie podświetlenia LCD pinMode(A5, INPUT_PULLUP); // Pin analogowy przycisków pinMode(9, OUTPUT); // Pin cyfrowy LED sygnalzujący naciśnięcie przycisku digitalWrite(9, LOW); // wygaszenie LED sygnalizującej naciśnięcie dowolnego przycisku pinMode(8, OUTPUT); // Pin cyfrowy diody IR sterujący wyzwalaczem pinMode(7, OUTPUT); // Pin cyfrowy LED sygnalzujący wykonanie zdjęcia menuSetup(); // funkcja klasy MenuBackend - tu tak naprawdę tworzymy nasze menu menu.moveDown(); // idziemy do pierwszej opcji - PLIK, moveDown bo pierwotnie byliśmy w root // to tak jak w Awatarze drzewa rosną korzeniami do góry :-) } void loop() { x=czytaj_1(5); delay(30); // odczytujemy stan klawiatury: /* Ja używam funkcji czytaj_1() bo mam akurat klawiaturkę podpiętą pod A5 Jeśli masz inna klawiaturkę to użyj funkcji czytaj_2 lub czytaj_3 - patrz opis Ponadto musisz pamietać że w funkcji obsługo klawisza OK - menuUseEvent(MenuUseEvent used) także musisz użyć odpowiedniej wersji funkcji czytaj !!! */ if(zm!=x) // jesli była zmiana stanu to : { switch(x) // sprawdzamy co nacisnieto { case 0: menu.moveRight(); break; // jesli naciśnięto klawisz w Prawo to przesuń menu w prawo case 1: menu.moveUp(); break; // menu do góry case 2: menu.moveDown(); break; // menu w dół case 3: menu.moveLeft(); break; // menu w lewo case 4: menu.use(); break; // wciśnięto "OK" więc skok do funkcji menuUseEvent(MenuUseEvend used) // to w tej funkcji właśnie obsługujemy nasze Menu, tu sprawdzamy // jaką opcję wybrano i tutaj tworzymy kod do obslugi zdarzenia. } } zm=x; // przypisanie zmiennej zm wartości x po to, aby dluższe wciskanie tego // samego klawisza nie powodowało ponownej generacji zdarzenia. // program reaguje na zmianę stanu klawiatury. a = analogRead(5); if (a < 900){ // Zdarzenie polegające na naciśnięciu dowolnego przycisku digitalWrite(9, HIGH); // Włączenie LED sygnalizującej naciśnięcie przycisku czas = millis(); // zaczynamy zliczać czas () } else{ digitalWrite(9, LOW); } if (millis() - czas <= 5000){ // Jeżeli nie upłynęło 5 sek: lcd.backlight(); // Podświetl ekran LCD } else { lcd.noBacklight(); // Wyłącz podświetlenie LCD } // Serial.println(analogRead(5)); // Serial.print("Interwal: "); // Serial.print(interwal/1000); // Serial.print("s, czas: "); // Serial.print(czas_sekw/1000); // Serial.println("s, "); }