Wyzwalacz IR do aparatów marki Nikon sterowany za pomocą wielopoziomowego menu


Tomasz Bartuś



2016-09-23
Wyzwalacz IR do aparatów marki Nikon sterowany za pomocą wielopoziomowego menu

Wstęp

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.

Wyzwalacz IR do aparatów marki Nikon
Fig. 1. Układ wyzwalacza IR do aparatów marki Nikon

Projekt umożliwia wyzwalanie aparatu fotograficznego w trybach zdjęć:

  1. pojedynczych,
  2. seryjnych (fotografia poklatkowa).

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źć?).

Hardware

Układ jest wyposażony w wyświetlacz LCD (2 × 16) 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.

Schemat ideowy

Wyzwalacz IR do Nikon
Fig. 2. Schemat układu wyzwalacza IR do Nikon

Lista części

  1. platforma prototypowa Arduino (tu Leonardo),
  2. dioda LED czerwona (LED1),
  3. dioda LED zielona (LED2),
  4. dioda IR,
  5. przycisk montażowy (5 szt.),
  6. płytka prototypowa,
  7. rezystor 100Ω (R1-R5),
  8. rezystor 470Ω (R6-R8),
  9. wyświetlacz LCD 2 × 16,
  10. konwerter I2C do LCD,
  11. przewody/mostki.

Menu sterujące

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:

  • ZDJĘCIE - fotografowanie w trybie zdjęć pojedynczych,
  • SEKWENCJA - fotografowanie w trybie zdjęć seryjnych,
  • INFO - informacje o oprogramowaniu.

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

Opcje: parametry sekwencji
Fig. 3. Aktualne parametry sekwencji (zdjęcie robione co 5 sekund przez 30 sekund)

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:

  1. Predef. - tryb ustawień predefiniowanych (Fig. 4),
    Menu predefiniowanych ustawień parametrów sekwencji
    Fig. 4. Menu predefiniowanych ustawień parametrów sekwencji
  2. Manualne - tryb ustawień manualnych (Fig. 5).
    Menu manualnych ustawień parametrów sekwencji
    Fig. 5. Menu manualnych ustawień parametrów sekwencji

Wejście do ustawień predefiniowanych umożliwia użytkownikowi wybór jednego z przygotowanych zestawów parametrów sekwencji (Fig. 6).

Predefiniowane ustawienie parametrów sekwencji
Fig. 6. Predefiniowane ustawienie parametrów sekwencji

Dostępne są następujące opcje:

  • 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,

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

Manualne ustawienie interwału
Fig. 7. Manualne ustawienie interwału

Manualne ustawienie czasu rejestracji sekwencji
Fig. 8. Manualne ustawienie czasu rejestracji sekwencji

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

Film:

Szkic

/*
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, ");
}

 
 

Doktorat

Spis treści
Rozdzialy
Abstrakt [pl]
Abstract [eng]