MenuBackend jak się w nim odnaleźć ?

Zbiór tutoriali związanych z Arduino.
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 10 gru 2013, 22:23

Witam. Ostatnimi czasy otrzymuję na forum od kolegów sporo pytań jak tworzyć proste menu we własnych projektach w oparciu o bibliotekę MenuBackend. Postanowiłem przyjrzeć się nieco bliżej bibliotece autorstwa Alexandra Brevig-a i napisać ten poradnik. Może będzie komuś pomocny, bo wcześniej czy później rozbudowując swoje projekty będziesz takiego menu potrzebował :-)
Najpierw krótki wstęp:
Zasadniczo każda porządna biblioteka jest po prostu zbiorem kilku plików, gdzie zdefiniowano tzw. klasę, w której zamknięto jak w czarnej skrzynce wszystkie zmienne i funkcje obsługujące ją właśnie. Sama klasa jest niejako szablonem do tworzenia obiektów posiadających właściwości i metody owej klasy.
Biblioteka MenuBackend składa się z 2 klas: MenuBackend i MenuItem , ta druga jest klasą zaprzyjaźnioną z pierwszą czyli w dużym uproszczeniu może korzystać ze zmiennych i funkcji klasy MenuBackend.
Obie klasy służą do zaprojektowania MENU, określenia wszystkich opcji MENU, poziomów zagnieżdżenia oraz co najważniejsze do obsługi samego MENU, czyli do przechodzenia przez wszystkie opcje i reakcji na wybranie danej opcji MENU. W prostych żołnierskich słowach masz jakieś własne menu, masz różne opcje a program musi wiedzieć jaka opcja jest aktywna i co wcisnąłeś aby zareagować na Twój wybór. Program musi także wiedzieć gdzie w drzewie menu aktualnie się znajdujesz, jaka opcja była poprzednio, co jest za nią, powyżej , poniżej, z lewej, z prawej. Pisanie takiego menu za pomocą funkcji wyboru if czy switch w głównej pętli programu jest mało wydajne i mało estetyczne. Biblioteka MenuBackend może być nam przy tym bardzo pomocna.
Aby korzystać z dobrodziejstwa biblioteki warto zaznajomić się z jej zawartością. Kopalnią wiedzy jest katalog examples gdzie w każdej porządnej bibliotece znajdziemy chociaż jeden przykład użycia biblioteki. Polecam także lekturę pliczku keywords.txt Pliczek ten składa się z opisu wszystkich nazw funkcji (metod) danej biblioteki. Słowa KEYWORD1 i KEYWORD2 są potrzebne IDE Arduino, gdyż dzięki nim masz właściwie kolorowaną składnię w edytorze. Teraz funkcja klasy jest zaznaczona tym samym kolorem jakby była funkcją wbudowaną w język C++.
Dla nas ważne są te dane:
MenuBackend KEYWORD1 - nazwa konstruktora klasy MenuBackEnd
MenuItem KEYWORD1 - nazwa konstruktora klasy MenuItem
MenuChangeEvent KEYWORD1 - funkcja obsługująca zmianę akt .pozycji w MENU
MenuUseEvent KEYWORD1 - funkcja obsługująca wybranie akt. pozycji (np. klawiszem OK)
oraz funkcje :
getRoot KEYWORD2 - pobiera nazwę korzenia drzewa ( jak w Awatarze drzewa rosną korzeniem do góry :-)
getCurrent KEYWORD2 - pobiera aktualną pozycję menu
use KEYWORD2 - przejmuje kontrolę nad akt. pozycją menu - czyli obsługuje Twój wybór
getName KEYWORD2 - pobiera nazwę pozycji menu
hasShortkey KEYWORD2 - sprawdza czy dana pozycja ma tzw. shortkey - o tym potem
getShortkey KEYWORD2 - pobiera wartość owego shortkeya
moveBack KEYWORD2 - przenosi "kursor" menu do tyły
moveUp KEYWORD2 - do góry
moveDown KEYWORD2 - w dół
moveLeft KEYWORD2 - w lewo
moveRight KEYWORD2 - w prawo
jak również funkcje służące do tworzenia "mapy" Menu:
add KEYWORD2 - dodaje pozycję w pionie
addBefore KEYWORD2 - dodaje przed
addRight KEYWORD2 - dodaje z prawej
addAfter KEYWORD2 - dodaje po
addLeft KEYWORD2 - z lewej
from KEYWORD2 - określa skąd skoczył kursor
to KEYWORD2 - określa gdzie skoczył kursor
item KEYWORD2 - określa bieżącą pozycje
Jak widać już sama nazwa funkcji podpowiada jej działanie :-)

Do dzieła - czas na nasze przykładowe Menu:
Zaprojektowałem układ Menu nieco podobny do menu jakie jest wykorzystywane w IDE Arduino:

Kod: Zaznacz cały

      PLIK
         Nowy
         Otworz
         Szkicownik
         Przyklady
         Zapisz
         Zapisz jako
         Zaladuj
         Exit
      EDYCJA
         Wytnij
         Kopiuj
         Wklej
         Zaznacz
         Znajdz
      SZKIC
         Weryfikuj
         Kompiluj
         Importuj
             EEPROM
             GSM
             SD
             MenuBackend
      NARZEDZIA
          Plytka
             Arduino Uno
             Leonardo
             Decimila
             Nano
             LilyPad
          Odczyt
             Temperatura
             COM 2 
             COM 15 
          Programator      
             USBasp
             AVR ISP
             AVR ISP MK II
      POMOC 
          Jak zaczac
          Srodowisko
          Dokumentacja
          O Arduino
Zatem nasze menu jest nieco skomplikowane i ma 3 poziomy:
Poziom 1: PLIK, EDYCJA, SZKIC, NARZEDZIA, POMOC
Każdy z poziomów (rodzic) ma swoje dzieci (podpoziomy)
Dzieci :Importuj w poziomie SZKIC, Plytka, Odczyt i Programator w poziomie NARZEDZIA mają własne dzieci, czyli 3 poziom zagnieżdżenia menu.
Tak zbudowane menu musimy zdefiniować w programie. To oznacza iż musimy utworzyć obiekt klasy MenuBackend:

Kod: Zaznacz cały

MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent); // konstruktor 
Za przykładem mistrza gatunku (Jerzego Grębosza) powyższą linię tłumaczymy następująco:
Utworzyliśmy obiekt klasy MenuBackend o nazwie menu i przekazaliśmy sterowanie nim do dwóch funkcji menuUseEvent i menuChangeEvent. Pierwsza obsługuje wybór opcji z menu a druga nawigację po nim.

Następnie powołujemy do życia wszystkie opcje:(czyli obiekty klasy MenuItem).
Tutaj definiujemy każdy element menu. Możemy robić to na 2 sposoby:
- bez używania tzw. shortkey, czyli np. MenuItem P1 = MenuItem("PLIK");
- lub z shortkey, czyli MenuItem P1 = MenuItem("PLIK",1);
Shortkey to dodatkowy parametr i dzieki niemu będziemy mogli decydować co ma się
wyświetlać na wyświetlaczu. W tym przykładzie parametr shortkey identyfikuje
nam poziom zagnieżdżenia menu. Potem w dość prosty sposób za pomocą strzałek
bedziemy pomagać użytkownikowi w nawigacji po menu.

Kod: Zaznacz cały

MenuItem P1 =  MenuItem("     PLIK",1);
      MenuItem P11 = MenuItem("     Nowy",2);
      MenuItem P12 = MenuItem("    Otworz",2);
      MenuItem P13 = MenuItem("  Szkicownik",2);
      MenuItem P14 = MenuItem("  Przyklady",2);
      MenuItem P15 = MenuItem("    Zapisz",2);
      MenuItem P16 = MenuItem(" Zapisz jako..",2);
      MenuItem P17 = MenuItem("   Zaladuj",2);
      MenuItem P18 = MenuItem("     Exit",2);
      
   MenuItem P2 = MenuItem("    EDYCJA",1);
      MenuItem P21 = MenuItem("    Wytnij",2);
      MenuItem P22 = MenuItem("    Kopiuj",2);
      MenuItem P23 = MenuItem("     Wklej",2);
      MenuItem P24 = MenuItem("    Zaznacz",2);
      MenuItem P25 = MenuItem("    Znajdz",2);
   
   MenuItem P3 = MenuItem("     SZKIC",1);
      MenuItem P31 = MenuItem("   Weryfikuj",2);
      MenuItem P32 = MenuItem("   Kompiluj",2);
      MenuItem P33 = MenuItem("   Importuj",3);
          MenuItem P331 = MenuItem(" Menu Backend",4);
          MenuItem P332 = MenuItem("    EEPROM",4);
          MenuItem P333 = MenuItem("   KeyBoard",4);
          MenuItem P334 = MenuItem("      GSM",4);
   
   MenuItem P4 = MenuItem("  NARZEDZIA",1);
      MenuItem P41 = MenuItem("    Plytka",3);
          MenuItem P411 = MenuItem("  Arduino Uno",4);
          MenuItem P412 = MenuItem("   Leonardo",4);
          MenuItem P413 = MenuItem("   Decimila",4);
          MenuItem P414 = MenuItem("   LilyPad",4);
          MenuItem P415 = MenuItem("     Nano",4);
      MenuItem P42 = MenuItem("    Odczyt",3);  
          MenuItem P421 = MenuItem(" Temperatura",4);
          MenuItem P422 = MenuItem("     COM 2",4);
          MenuItem P423 = MenuItem("    COM 13",4);
      MenuItem P43 = MenuItem("  Programator",3);  
          MenuItem P431 = MenuItem("    USBasp",4);
          MenuItem P432 = MenuItem("    AVR ISP",4);
          MenuItem P433 = MenuItem(" AVR ISP MK II",4);    
      
      MenuItem P5 = MenuItem("     POMOC",1);
          MenuItem P51 = MenuItem("  Jak zaczac",2);
          MenuItem P52 = MenuItem("  Srodowisko",2);
          MenuItem P53 = MenuItem(" Dokumentacja",2);
          MenuItem P54 = MenuItem("  O Arduino",2);
Tajemnicze numerki oznaczają poziomy zagnieżdżenia dla każdej opcji. Numerek 3 oznacza tutaj,że ta opcja ma swoje własne podmenu. Oczywiście można wcale nie używać parametru shortkey, jednak dalej pokaże do czego przyda się nam to w trakcie pracy programu. Spacje przed nazwą opcji służą do wyśrodkowania jej na wyświetlaczu LCD. Można także użyć funkcji operujących na stringach aby automatycznie obliczać długość napisu i potem odpowiednio pozycjonować. Ja wolę oszczędzać bajty :-)
Skoro mamy już zdefiniowane wszystkie opcje czas "nauczyć" program ich położeń na tzw mapie MENU.
Korzystamy z funkcji add addLeft i addRight. Add - dodaje w pionie, addRight - dodaje w poziomie z prawej , addLeft dodaje z lewej :
Aby zachować jakiś porządek robimy to w funkcji void menuSetup():

Kod: Zaznacz cały

void menuSetup()                       // funkcja klasy MenuBackend 
{
      menu.getRoot().add(P1);          // ustawiamy korzeń Menu, czyli pierwszą opcję
      P1.add(P11);                     // rodzic PLIK ma dziecko Nowy więc dodaje je w pionie 
        P11.add(P12);P11.addLeft(P1);  // poniżej Nowy jest Otworz więc także w pionie 
                                       // a addLeft(P1) pozwoli nam wrócić klawiszem w lewo do PLIK  
        P12.add(P13);P12.addLeft(P1);  // analogicznie robimy ze wszystkimi podopcjami dla PLIK
        P13.add(P14);P13.addLeft(P1);
        P14.add(P15);P14.addLeft(P1);
        P15.add(P16);P15.addLeft(P1);
        P16.add(P17);P16.addLeft(P1);
        P17.add(P18);P17.addLeft(P1);
        P18.addLeft(P1);P18.add(P11);  // tutaj zamykamy pętlę i wracamy do pierwszej podopcji
                                       // dzieki temu nie musimy wracać na górę przez uciążliwe 
                                       // klikanie klawisza Up 
                                  
      P1.addRight(P2);                 // po prawej dla PLIK jest EDYCJA
      P2.add(P21);                     // rodzic EDYCJA ma dziecko Wytnij
        P21.add(P22);P21.addLeft(P2);  // poniżej Wytnij jest Kopiuj
        P22.add(P23);P22.addLeft(P2);  // analogicznie dla wszystkich podopcji
        P23.add(P24);P23.addLeft(P2);
        P24.add(P25);P24.addLeft(P2);
        P25.addLeft(P2);P25.add(P21);  // i zamknięcie pętli oraz ew. powrót do pierwszej opcji
      P2.addRight(P3);                 // na prawo od EDYCJA jest SZKIC 
      P3.add(P31);                     // rodzic SZKIC ma dziecko Weryfikuj 
        P31.add(P32);P31.addLeft(P3);  // poniżej Weryfikuj jest Kompiluj 
        P32.add(P33);P32.addLeft(P3);  // poniżej kompiluj jest Importuj 
        P33.addRight(P331);            // a tu dziecko Importuj ma już własne dziecko MenuBackend
                                       // dodajemy z prawej, ponieważ gdybyśmy dali poniżej to zrobilibyśmy
                                       // kolejne dziecko dla SZKIC, a w projekcie jest inaczej
          P331.add(P332);P331.addLeft(P33);  // poniżej MenuBackend jest EEPROM
          P332.add(P333);P332.addLeft(P33);  // postepujemy analogicznie
          P333.add(P334);P333.addLeft(P33);
          P334.addLeft(P33);P334.add(P331);
        P33.addLeft(P3);P33.add(P31);   // zamknięcie pętli i ew. powrót do pierwszej opcji  
      P3.addRight(P4);                  // dalej podobnie .... 
      P4.add(P41);
        P41.addRight(P411);             // kolejne dziecko, ktore ma dziecko :-)
          P411.add(P412);P411.addLeft(P41);
          P412.add(P413);P412.addLeft(P41);
          P413.add(P414);P413.addLeft(P41);
          P414.add(P415);P414.addLeft(P41);
          P415.addLeft(P41);P415.add(P411); // zamknięcie pętli itd...
        P41.addLeft(P4);
        P41.add(P42);
        P42.addRight(P421);
          P421.add(P422);P421.addLeft(P42);
          P422.add(P423);P422.addLeft(P42);
          P423.addLeft(P42);P423.add(P421); // zamkniecie pętli itd...
        P42.addLeft(P4);
        P42.add(P43);
        P43.addRight(P431);
          P431.add(P432);P431.addLeft(P43);
          P432.add(P433);P432.addLeft(P43);
          P433.addLeft(P43);P433.add(P431); // zamkniecie pętli itd...
        P43.addLeft(P4);P43.add(P41);
        P4.addRight(P5);
      P5.add(P51);
        P51.add(P52);P51.addLeft(P5);
        P52.add(P53);P52.addLeft(P5);
        P53.add(P54);P53.addLeft(P5);
        P54.addLeft(P5);P54.add(P51);     // zamkniecie pętli
      P5.addRight(P1);                    // zamkniecie pętli głównej, czyli poziomej - po POMOC jest PLIK
}
Opatrzenie kodu komentarzami chyba wyczerpuje temat, prawda? :-)

Następnie potrzebujemy jeszcze tylko 3 rzeczy:
1) Funkcji obsługi zmiany położenia "kursora" w Menu. (tutaj funkcja wskazana w konstruktorze -menuChangeEvent)
2) Funkcji obsługi wyboru danej opcji (de facto wciśnięcia klawisza OK - funkcja menuUseEvent)
3) Samej klawiatury i funkcji jej obsługi. (tutaj funkcja czytaj_1, lub czytaj_2 lub czytaj_3 )
Ponieważ w "rozmowach" w kolegami na forum spotykam się zasadniczo z 3 rodzajami używanych "klawiatur" postanowiłem przygotować 3 różne wersje funkcji czytaj, w zależności od tego który typ klawiatury używasz w swoim projekcie. I tak:
1) Funkcja int czytaj_1(int analog) obsługuje popularną klawiaturę wykorzystującą jedno wejście analogowe. Budowa takiej klawiatury oparta jest na drabince rezystorowej. Wejście analogowe odczytuje napięcie z dzielnika z i zależności od tego który klawisz wciśnięto wystawia wartość z innego przedziału. Wystarczy teraz odpowiednio zinterpretować ten stan i przełożyć na określoną wartość zwracaną przez funkcję.Parametrem funkcji jest numer wejścia analogowego.
2) Funkcja int czytaj_2(int analog1, int analog2, int pinD) obsługuje popularny moduł joysticka, gdzie pion obsługuje jedno wejście analogowe, poziom - drugie wejście analogowe a klawisz OK pin cyfrowy). funkcja zwraca wartość odpowiadającą danej akcji joysticka.
3) Funkcja int czytaj_3(int p1,int p2,int p3,int p4, int p5) wykorzystuje po prostu 5 przycisków przypiętych do wejść cyfrowych. Jeśli zamierzasz korzystać z takiej klawiaturki właśnie to pamiętaj o odpowiednim skonfigurowaniu w setupie pinMode(pin,INPUT) i digitalWrite(pin,HIGH).
Uważny czytelnik w mig zauważy, że można było przecież użyć jednej nazwy funkcji dla wszystkich trzech, dzięki mechanizmowi przeciążania funkcji. Tak byłoby lepiej i czytelniej. Cóż każdy może potem sobie to zrobić, służę pomocą w razie co :-)

Kod: Zaznacz cały

// --- wersja dla klawiatury 5-cio przyciskowej DFRobot --------------------------------------
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 < 50)   return 0;  // w prawo  
   if (stan_Analog < 150)  return 1;  // do gĂłry 
   if (stan_Analog < 300)  return 2;  // w dół 
   if (stan_Analog < 500)  return 3;  // w lewo  
   if (stan_Analog < 700)  return 4;  // OK 
   return -1;                         // nic nie wcisnieto
}
// --- wersja dla joysticka (2 wejscia analogowe + pin cyfrowy -------------------------------
int czytaj_2(int poziom, int pion, int pinD)
{
// poziom - nr wejścia analogowego do którego podłączona jest manetka joysticka dla ruchu lewo-prawo
// pion   - nr wejścia analogowego do którego podłączona jest manetka joysticka dla ruchu góra-dół
// pinD   - nr pinu cyfrowego do którego podłączony jest przycisk OK w joysticku
int stan1= analogRead(pion); {delay(60);if(stan1>0)stan1=(stan1+50)/1024+1;}  
   int stan2= analogRead(poziom); {delay(60);if(stan2>0)stan2=(stan2+50)/1024+1;} 
   int stanD=digitalRead(pinD);
   if(stanD==LOW) return 4;           // OK 
   if(stan1==0) return 2;             // w dół
   if(stan1==2) return 1;             // do gĂłry
   if(stan2==0) return 3;             // w lewo
   if(stan2==2) return 0;             // w prawo
   return -1;                         // nic nie wcisnieto
}
// --- wersja dla 5-ciu przycisków cyfrowych --------------------------------------------------
// dla przykładu jeśli wykorzystujesz piny: 1,2,3,11 i 12 to wołasz : czytaj_2(1,2,3,11,12)
int czytaj_3(int gora, int lewo, int ok, int prawo,int dol)
// gora   - nr pinu cyfrowego do którego podłączony jest przyciski góra
// lewo   - nr pinu cyfrowego do którego podłączony jest przyciski lewo
// ok     - nr pinu cyfrowego do którego podłączony jest przyciski OK
// prawo  - nr pinu cyfrowego do którego podłączony jest przyciski prawo
// dol    - nr pinu cyfrowego do którego podłączony jest przyciski dół
{
if(digitalRead(gora)==LOW) return 1;
if(digitalRead(lewo)==LOW) return 3;
if(digitalRead(ok)==LOW) return 4;
if(digitalRead(prawo)==LOW) return 0;
if(digitalRead(dol)==LOW) return 2;
return -1;
}
Także i tutaj stałem się o komentarze :-)
Dobra, zostały jeszcze 2 funkcje:
Pierwsza obsługuje nawigację po menu. Dla przykładu wykorzystano wyświetlacz LCD 16x2. Klawisze strzałek informują użytkownika o możliwych ruchach kursora.

Kod: Zaznacz cały

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, lub4)
  lcd.clear();                                            // bez komentarza 
  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ół 
    }
}
I nadszedł czas na najważniejsze - jak przekazać i potem obsłużyć wybranie opcji klawiszem OK, jak potem wrócić do pętli loop() ?. Przykładowe rozwiązanie:

Kod: Zaznacz cały

void menuUseEvent(MenuUseEvent used)      // funkcja klasy MenuBackend - reakcja na wciśnięcie OK
                                          // tutaj właśnie oddajemy menu na rzecz akcji obsługi klawisza OK
{
   Serial.print("wybrano:  "); Serial.println(used.item.getName()); // do testów, potem niepotrzebne
   // --- ponizej kilka przykładów obsługi  opcji -----------
   // przykładowa reakcja na wcisnięcie klawisza OK w opcji Otworz :
   if (used.item.getName() == "    Otworz")   // Uwaga - dokładnie taki sam ciąg "    Otworz" jak w menu !!!
                                              // bo przecież getName() pobiera nazwę
      {
        lcd.setCursor(1,0);lcd.print("Otwieram drzwi"); // info 
        digitalWrite(0,HIGH);delay(2000);digitalWrite(0,LOW); // na 2 sekundy pin 0 otrzymał stan wysoki
                                                              // czyli np. otworzyły się drzwi
        lcd.setCursor(1,0);lcd.print("              ");lcd.setCursor(1,0);lcd.print(linia1); //poprzedni stan LCD
      }
    // A teraz coś ambitniejszego :-), bo przekazujemy sterowanie klawiaturką do innej procedury,
    // w tym przykładzie programik czeka aż ustawisz jakąś temperaturę i po wciśnięciu OK wraca do pętli głównej
      if (used.item.getName() == " Temperatura")   // dokładnie taki sam ciąg " Temperatura"
      {
        int temp=21;                         // przykładowo 21 st. C
        lcd.setCursor(0,0);lcd.write(7);     // wyswietlamy nasz symbol strzałki góra-dół
        lcd.print("              ");lcd.setCursor(1,0);lcd.print("Ust.temp. = "); // tekst dla użytkownika
        lcd.setCursor(13,0);lcd.print(temp); // wyświetlamy akt. temperaturę
        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(0);//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) {temp++;if(temp>99)temp=99;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono temperaturę
               // ustawiono max próg i wyświetlono obecną temperaturę
             if(akcja==2)  {temp--;if(temp<10)temp=10;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=2 (czyli wciśnięto klawisz w dół to mniejszono temperaturę
               // ustawiono min próg i wyświetlono obecną temperaturę
             if(akcja==4) // jeśli wciśnieto OK 
               {
                 lcd.setCursor(0,0);lcd.print("*Temperatura OK");delay(2000); // pokazujemy 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
               }
             } 
         } 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()
      }
// a tutaj obsługa pozostałych opcji :-)  
// ...
// ...
}
I to by było na tyle... pozostaje jeszcze dodać setup i główną pętle programu:

Kod: Zaznacz cały

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 Seriala, głównie do testów 
  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);
  /* tu przykładowe piny cyfrowe dla 3 wersji funkcji czytaj_3(1,2,3,11,12)
   pinMode(1,INPUT);digitalWrite(1,HIGH);
   pinMode(2,INPUT);digitalWrite(2,HIGH);
   pinMode(3,INPUT);digitalWrite(3,HIGH); 
   pinMode(11,INPUT);digitalWrite(11,HIGH); 
   pinMode(12,INPUT);digitalWrite(12,HIGH); 
  */ 
  pinMode(0,OUTPUT);digitalWrite(0,LOW); // do testów 
  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 :-)
}
// --- I nadszedł czas na neverending story :-) -------------------------------------------- 
void loop()    
{
  x=czytaj_1(0);delay(30);             // odczytujemy stan klawiatury:
  /*
  Ja używam funkcji czytaj_1() bo mam akurat klawiaturkę podpiętą pod A0
  Jeśli masz inna klawiaturkę to użyj funkcji czytaj_2 lub czytaj_3 - patrz opis
  Ponadto musisz pamiętać że w funkcji obsługa klawisza OK - menuUseEvent(MenuUseEvent used)
  także musisz użyć odpowiedniej wersji funkcji czytaj !!!
  */
  if(zm!=x)                               // jeśli była zmiana stanu to :
    {
      switch(x)                           // sprawdzamy co naciśnięto 
      {
      case 0: menu.moveRight();break;     // jeśli 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 obsługi zdarzenia.
      }
    } zm=x;                               // przypisanie zmiennej zm wartości x po to, aby dłuższe wciskanie tego
                                          // samego klawisza nie powodowało ponownej generacji zdarzenia. 
                                          // program reaguje na zmianę stanu klawiatury. 
}
// === KONIEC ===========================================================
Trochę dużo tego wyszło. Mam nadzieję że potrzebnie. Na koniec w załączniku przesyłam oryginalną bibliotekę MenuBackend autorstwa Alexandra Brevig-a, gdzie w examples dołączyłem opisany tutaj mój przykład wojtekizk.ino
Pozdrawiam
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Co miesiąc do wygrania nagrody o wartości ponad 1600 zł!


esylwek
Młodszy majsterkowicz
Posty: 1
Rejestracja: 23 sty 2014, 13:01

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: esylwek » 14 lut 2014, 12:31

Wielkie brawa Wojtku za ten tutorial, niejeden z nas, włącznie ze mną, mógł dzięki Tobie, poprzez modyfikację, utworzyć swoje wymarzone menu. Jednak nadal mam wiele pytań (brak wiedzy), i jeśli zadane przeze mnie pytania są głupie, to bardzo proszę o wybaczenie (uczenie się programowania na starość jest lekiem na alzheimera :-D):

1. Czy główne okno programu gdzie wyświetlane (odświeżane) są dane pomiarowe, stany wyjść itd. to "menuRoot", czy musi to być osobny element MenuItem ?
2. Jeśli wg Twojego przykładu, w elemencie MenuItem zmieniam temperaturę zadaną, oraz inne parametry w pozostałych elementach menu, to gdzie ma być zapis programu dotyczący regulacji temperatury (zmiany stanu wyjść, itd.)czy to ma być w "loop"? Jeśli tak, to gdzie i jak się to wszystko wyświetli na LCD?
3. Jeśli określiłem co ma wyświetlać się na poszczególnych ekranach wg shortkey (C1,C2) to jak do tych ekranów dołożyć zmienne w odniesieniu do elementów MenuItem które chcemy modyfikować? Przykład:
Element menu MenuItem "Set Temp" wyświetla mi wszystkie informację, strzałki itd. wg. tego co określiłem dla C2 w menuChangeEvent i chcę dołożyć do tego ekranu zmienną (dla każdego elementu MenuItem inną) temperatury zadanej "Temp" którą modyfikuję klawiszami góra lub dół, a naciskając OK zapisałbym ją do pamięci przechodząc jednocześnie do kolejnego elementu MenuItem. Chciałbym aby naciśnięcie klawisza OK spowodowało wykonanie dwóch akcji: Zapis modyfikowanej zmiennej do pamięci oraz wykonanie instrukcji z menuUseEvent) czyli przejście do kolejnego elementu MenuItem. Czy tak w ogóle można?

Mam w tej chwili jeden program którym jest menu (dzięki Tobie), poruszam się po nim sprytnie klawiszami (góra, dół i OK) oraz drugi program w którym są realizowane funkcje. Oba działają świetnie, tyle że osobno. I tutaj wielka prośba: Czy można by rozbudować lub napisać część 2 tutoriala, jak takie dwa programy ze sobą połączyć, czyli: co gdzie ma być aby to wszystko działało i nie gryzło się ze sobą? Aby wchodząc w menu, nadal były realizowane funkcje programu, który odniósł by się do nowych wartości po wyjściu z menu lub bezpośrednio po zapisaniu zmiennej do pamięci.

Wiem, że główny brak mojej wiedzy w tym momencie to przerwania i pętla "while" i dlatego chciałbym prosić o wytłumaczenie takiemu laikowi jak ja na przykładzie np. zegara z termostatem i timerem(DS1307+DS18B20) gdzie programowany byłby RTC, timer oraz temperatury poprzez elementy Menubackend. Jestem przekonany, że pomógłbyś nie tylko mnie, ale wielu osobom rozpoczynającym swoją przygodę z Arduino i mikrokontrolerami w ogóle, za co z góry z całego serca dziękuję.
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 14 lut 2014, 22:26

Witam... w zasadzie odpowiem tu koledze esylwek ale może i innym się przyda.
Zacznę od końca postu Sylwka - co do przerwań i wiedzy o nich - to odsyłam kolegów do działu "jak to zrobić" i do postu "Zmiana programu kliknięciem", gdzie ostatnio produkowałem się w sprawie przerwań, zamieszczając tam coś co może być wstępem do tutoriala :-)
Natomiast pętla while należy do tzw. sztandarowych instrukcji warunkowych i jak każda taka instrukcja zawiera w sobie warunek oraz klamry {} gdzie jest opisane w języku C co trzeba robić jeśli ów warunek jest lub nie jest spełniony.
W sumie nic strasznego... trzeba sie tylko z nią "opatrzeć" :-)
Dobra teraz meritum tego o co pyta Sylwek:

Mamy jakieś tam menu, które de facto jest obiektem (egzemplarzem) klasy MenuBackend, co z kolei oznacza że ów obiekt posiada wszystkie dostępne właściwości (zmienne) i metody (funkcje) dostępne w tej klasie.
Jak napisano w tutorialu obiekt klasy MenuBackend komunikuje się ze światem zewnętrznym za pomocą 2 funkcji:
1) menuChangeEvent - reakcja na wciśnięcie klawisza nawigacyjnego ( kierunku)
2) menuUseEvent - reakcja na wciśnięcie klawisza wyboru, nazwijmy go OK
Pierwsza funkcja obsługuję nawigację po samym menu, natomiast druga przekazuje sterowanie do czegoś co właśnie możemy nazwać podprogramem. To właśnie w niej sprawdzamy:
- co wybrano, czyli de facto jak nazywa się nasza wybrana opcja menu: used.item.getName()
- potem wykonujemy nasz "podprogram" w zależności od tego właśnie, co wybrano.
Tu zatrzymam się na nieco dłużej.
Jeśli dany podprogram polega na wyświetleniu jakieś zmiennej, wyświetleniu wyniku pomiaru, wykonaniu jakieś czynności itp.... czyli na zmianie stanu naszego LCD to najpierw zapamiętujemy jak wyglądał nasz wyświetlacz, potem wyświetlamy tam nasz wynik działania i wracamy do poprzedniego stanu LCD. Przykład:
if (used.item.getName() == " Otworz") // Uwaga - dokładnie taki sam ciąg " Otworz" jak w menu !!!
                                              // bo przecież getName() pobiera nazwę
      {
        lcd.setCursor(1,0);lcd.print("Otwieram drzwi"); // info
        digitalWrite(0,HIGH);delay(2000);digitalWrite(0,LOW); // na 2 sekundy pin 0 otrzymał stan wysoki
                                                              // czyli np. otworzyły się drzwi
        lcd.setCursor(1,0);lcd.print(" ");lcd.setCursor(1,0);lcd.print(linia1); //poprzedni stan LCD
      }
A jeśli natomiast nasza wybrana opcja wymaga dalszych działań, na przykład pokazania temperatury i jej zmiany to musimy do roboty zagonić pętle while(), w której przekazujemy działanie do podprogramu i siedzimy w nim, dokonując prezentacji odczytanej temperatury, zmianę jej nastaw itp. ... tak długo aż nie zatwierdzimy naszych działań klawiszem OK !. Nic się nie dzieje w głównej pętli programu loop(). Cała obsługa odbywa się tutaj!
if (used.item.getName() == " Temperatura") // dokładnie taki sam ciąg " Temperatura"
      {
        int temp=21; // tu zamiast tego odczytujesz temperaturę np z Dallasa i pokazujesz poniżej
        lcd.setCursor(0,0);lcd.write(7); // wyswietlamy nasz symbol strzałki góra-dół
        lcd.print(" ");lcd.setCursor(1,0);lcd.print("Ust.temp. = "); // tekst dla użytkownika
        lcd.setCursor(13,0);lcd.print(temp); // wyświetlamy akt. temperaturę
        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(0);//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) {temp++;if(temp>99)temp=99;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono temperaturę
               // ustawiono max próg i wyświetlono obecną temperaturę
             if(akcja==2) {temp--;if(temp<10)temp=10;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=2 (czyli wciśnięto klawisz w dół to mniejszono temperaturę
               // ustawiono min próg i wyświetlono obecną temperaturę
             if(akcja==4) // jeśli wciśnieto OK
               {
                 lcd.setCursor(0,0);lcd.print("*Temperatura OK");delay(2000); // pokazujemy 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
                // ... i UWAGA --- tutaj zapsiujemy np. temperaturę do pamięci EEPROM, wykonujemy jakieś
                // inne działanie, czyli de facto nasz podprogram :-)
               }
             } 
         } 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()
      }
Zauważ uważny czytelniku :-), że to właśnie w tej pętli while(akcja!=4) korzystam z funkcji czytaj_1(0)... czyli z jednej z funkcji odczytu klawiatury opisanej w tutorialu. Jeśli wciśnięto klawisz np. strzałki w prawo to zwiększam temperaturę, jeśli w lewo to zmniejszam... czyli de facto wykonuję jakiś swój własny podprogram, prawda?
Robię to tak długo aż w końcu wcisnę OK i co wtedy? Ano to, że kończy się nasza pętla while(akcja!=4) i wracamy do naszego głównego programu :-)
Ale wcześniej w tej właśnie pętli pytamy:
if(akcja==4) // jeśli wciśnieto OK
               {
                 lcd.setCursor(0,0);lcd.print("*Temperatura OK");delay(2000); // pokazujemy 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
                // i zapisujemy np. naszą nowo ustawioną temperaturę np w EEPROM, lub zapisujemy cokolwiek
               }
Oto cała tajemnica. Dla przypomnienia zamieszczam tu całą funkcję z tutoriala:
void menuUseEvent(MenuUseEvent used) // funkcja klasy MenuBackend - reakcja na wciśnięcie OK
                                          // tutaj właśnie oddajemy menu na rzecz akcji obsługi klawisza OK
{
   Serial.print("wybrano: "); Serial.println(used.item.getName()); // do testów, potem niepotrzebne
   // --- ponizej kilka przykładów obsługi opcji -----------
   // przykładowa reakcja na wcisnięcie klawisza OK w opcji Otworz :
   if (used.item.getName() == " Otworz") // Uwaga - dokładnie taki sam ciąg " Otworz" jak w menu !!!
                                              // bo przecież getName() pobiera nazwę
      {
        lcd.setCursor(1,0);lcd.print("Otwieram drzwi"); // info
        digitalWrite(0,HIGH);delay(2000);digitalWrite(0,LOW); // na 2 sekundy pin 0 otrzymał stan wysoki
                                                              // czyli np. otworzyły się drzwi
        lcd.setCursor(1,0);lcd.print(" ");lcd.setCursor(1,0);lcd.print(linia1); //poprzedni stan LCD
      }
    // A teraz coś ambitniejszego :-), bo przekazujemy sterowanie klawiaturką do innej procedury,
    // w tym przykładzie programik czeka aż ustawisz jakąś temperaturę i po wciśnięciu OK wraca do pętli głównej
      if (used.item.getName() == " Temperatura") // dokładnie taki sam ciąg " Temperatura"
      {
        int temp=21; // przykładowo 21 st. C
        lcd.setCursor(0,0);lcd.write(7); // wyswietlamy nasz symbol strzałki góra-dół
        lcd.print(" ");lcd.setCursor(1,0);lcd.print("Ust.temp. = "); // tekst dla użytkownika
        lcd.setCursor(13,0);lcd.print(temp); // wyświetlamy akt. temperaturę
        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(0);//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) {temp++;if(temp>99)temp=99;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono temperaturę
               // ustawiono max próg i wyświetlono obecną temperaturę
             if(akcja==2) {temp--;if(temp<10)temp=10;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
               // jesli akcja=2 (czyli wciśnięto klawisz w dół to mniejszono temperaturę
               // ustawiono min próg i wyświetlono obecną temperaturę
             if(akcja==4) // jeśli wciśnieto OK
               {
                 lcd.setCursor(0,0);lcd.print("*Temperatura OK");delay(2000); // pokazujemy 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
               }
             } 
         } 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()
      }
// a tutaj obsługa pozostałych opcji :-)  
// ... if (used.item.getName() == " Leonardo") {  tutaj podprogram z kolejna pętlą while }
 
// ... if (used.item.getName() == "  Lilypad")  { tutaj podprogram z kloejną pętlą while }
// ...itd ... czyli wszystkie Wasze opcje, czy podprogramy
}
Jeszcze raz przypomnę - wszystko dzieje się w pętli while(akcja!=4), tam umieszczamy nasze "zewnętrzne" programy. W głównej pętli loop czytamy tylko stan klawiatury:
void loop()
{
  x=czytaj_1(0);delay(30); // odczytujemy stan klawiatury:
  /*
  Ja używam funkcji czytaj_1() bo mam akurat klawiaturkę podpiętą pod A0
  Jeśli masz inna klawiaturkę to użyj funkcji czytaj_2 lub czytaj_3 - patrz opis
  Ponadto musisz pamiętać że w funkcji obsługa klawisza OK - menuUseEvent(MenuUseEvent used)
  także musisz użyć odpowiedniej wersji funkcji czytaj !!!
  */
  if(zm!=x) // jeśli była zmiana stanu to :
    {
      switch(x) // sprawdzamy co naciśnięto
      {
      case 0: menu.moveRight();break; // jeśli 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 obsługi zdarzenia.
      }
    } zm=x;                               // przypisanie zmiennej zm wartości x po to, aby dłuższe wciskanie tego
                                          // samego klawisza nie powodowało ponownej generacji zdarzenia.
                                          // program reaguje na zmianę stanu klawiatury.
}
Mam nadzieję, że teraz nieco przybliżyłem kolegów do tematu ... i okrzyku pewnego średniowiecznego mędrca:
EUREKA ! :-)
Pozdrawiam
Ostatnio zmieniony 17 lut 2014, 10:05 przez wojtekizk, łącznie zmieniany 3 razy.
siwy2411
Młodszy majsterkowicz
Posty: 29
Rejestracja: 4 cze 2013, 14:56

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: siwy2411 » 14 lut 2014, 22:48

popełniłeś kolego kupę roboty - może warto by to umieścić na głównej jako pełnoprawny artykuł? na pewno nie jednej osobie by się przydało, a w czeluściach forum może czasami zaginąć, a byłoby szkoda.
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 27 mar 2014, 23:36

Witam,
tak sobie testuję tą bibliotekę ... Narzędzia poniżej Odczyt, strzałka w prawo, do dołu i jesteśmy w Temperatura. Klikam OK, w górnej linijce wyświetla się "Ust.temp. - 21". Naciskam Enter, pojawia się Temperatura OK i ... wyświetlane jest w górnej linijce NARZEDZIA a w dolnej Temperatura. Chyba powinno być u góry "Odczyt" a na dole Temperatura. Czy nie?
Pozdrawiam serdecznie
Mariusz
PS. Logikę chcę uchwycić, nie o wytykanie błędów czy inne takie chodzi ...
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 28 mar 2014, 07:55

Witam
Jakiej klawiaturki używasz, bo już jeden kolega miał taki problem. Jeśli jest to klawiaturka oparta na Analogu to trzeba indywidualnie dobrać zakresy w funkcji czytaj_1(int pin), bo to zależy od rezystorów w drabince.
Testuj np. tym:
Serial.println(analogRead(pin));
... i porównuj jakie wartości Ci wyświetla.
Jesli inna klawiaturka to powinno wszystko działać jak trzeba.
Pozdrawiam
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 28 mar 2014, 09:54

Witam,
:=) o poprawnym przypisaniu generowanych przez moja klawiaturkę wartości oczywiście pamiętałem. Mam analogową. Wszystkie operacje na menu, czyli w lewo, w prawo, w dół, w górę i enter działają poprawnie. Powrót z edycji wartości temperatury do poprzedniego miejsca w menu wydaje mi się po prostu niepoprawny. Z edycji Narzędzia->Odczyt-> Temperatura powinno moim zdaniem powrócić do Odczyt -> Temperatura a nie Narzędzia->Temperatura.
Edycja wartości temperatury UP/DOWN działa ok i naciśnięcie klawisza OK skutkuje komunikatem Temperatura OK i powrotem tam gdzie napisałem Narzędzia->Temperatura, czyli niepoprawnie. Czyli z tej pętli:

Kod: Zaznacz cały

while (akcja != 4)                  // ta pętla trwa tak długo aż wciśniesz klawisz OK
wychodzę Enterem. Przyglądałem się:

Kod: Zaznacz cały

MenuItem P4 = MenuItem("  NARZEDZIA", 1);
   MenuItem P41 = MenuItem("    Plytka", 3);
      MenuItem P411 = MenuItem("  Arduino Uno", 4);
      MenuItem P412 = MenuItem("   Leonardo", 4);
      MenuItem P413 = MenuItem("   Decimila", 4);
      MenuItem P414 = MenuItem("   LilyPad", 4);
      MenuItem P415 = MenuItem("     Nano", 4);
   MenuItem P42 = MenuItem("    Odczyt", 3);
      MenuItem P421 = MenuItem(" Temperatura", 4);
... ale nie widzę jakoś błędu.
Inną ciekawostką jest to że zdefiniowana struktura menu jest:

Kod: Zaznacz cały

MenuItem P4 = MenuItem("  NARZEDZIA", 1);
...
   MenuItem P42 = MenuItem("    Odczyt", 3);
      MenuItem P421 = MenuItem(" Temperatura", 4);
      MenuItem P422 = MenuItem("     COM 2", 4);
      MenuItem P423 = MenuItem("    COM 13", 4);
a podczas nawigowania z Narzedzia->Odczyt(P42) wchodzi się do COM13 (P423) a nie do Temperatura (P421), pomimo że jest P42.addRight(P421);. Jakim więc prawem z Odczyt(P42) przechodzę najpierw do COM13 (P423) jeśli w kodzie jest chwilę dalej: P422.add(P423);.
Zgłupiałem przyznaję. Jeśli ma ktoś jakiś pomysł to bardzo proszę o zaprezentowanie.
Z góry dziękuję
... i pozdrawiam wszystkich serdecznie
Mariusz
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 28 mar 2014, 10:08

.. kontynuując. To samo jest ze Szkic->Importuj (P33). Po wybraniu opcji w prawo przechodzi się do GSM (P334), a nie do Menu Backend (P331). Hmmm. Poziom drugi menu działa ok, Poziom trzeci nie. W kodzie jest: P33.addRight(P331);. Jest co prawda P334.addLeft(P33); ale to do powrotu z podmenu GSM(P334) do Importuj (P33).
Ok, w trakcie pisania posta znalazłem regułę. Pierwszy poziom działa ok, zgodnie z projektem i kodem. Drugi poziom też. Natomiast na trzecim poziomie skok z drugiego poziomu jest niezgodnie z kodem do ostatniej pozycji w podmenu. Czyli z Narzędzia->Płytka-> skok do Nano a nie do Arduino Uno, Narzędzia->Odczyt-> skok do COM13 a nie do Temperatura etc.
Jeśli któryś z szanownych forumowiczów mógłby pomóc, byłbym wdzięczny, bo to coś z biblioteką chyba.
Jeszcze raz pozdrawiam i już nie marudzę :=).
Mariusz
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 28 mar 2014, 10:19

Skłamałem, jeszcze raz się pojawiam, a miałem już nie marudzić. :=)
Tak sobie myślę, że gdyby oprócz mnie jeszcze ktoś się pojawił chętny do stworzenia swojego systemu menu, to byłoby super. Co innego grzebać w cudzym czego się nie rozumie, a co innego stworzyć własne i jeszcze się czegoś przy tym nauczyć. Moglibyśmy wspólnie przedyskutować założenia i powoli wspólnie napisać wymieniając doświadczenia.
Pozdrawiam
Mariusz
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 28 mar 2014, 19:31

Witam
Jestem na tak... zwłaszcza, że sama biblioteka MenuBackend nie jest przecież mojego autorstwa. Ja tylko starałem się nieco przybliżyć ją innym. Być może sam jeszcze niewiele o niej wiem. Jestem w trakcie pisania menu opartego na nieco innej idei... to już chyba z 3 wersja... pierwsza jest w projektach : "biblioteka MENU + klawiaturka 5-cio przyciskowa". Niestety tamta obsługuje tylko 2 poziomy zagnieżdżeń. Spoko... niedługo wrzucę moje wypociny jako gotowiec 5 :-) Trochę mało czasu teraz... wiesz wiosna i działeczka wzywa :-)
Pozdrawiam
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 28 mar 2014, 22:19

Witam,
jeden błąd znalazłem. W procedurze "void menuUseEvent(MenuUseEvent used) ", w liniach:

Kod: Zaznacz cały

if (akcja == 4) // jeśli wciśnieto OK
        {
          lcd.setCursor(0, 0); lcd.print("*Temperatura OK"); delay(2000); // pokazujemy 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
ostatnia z nich, czyli:

Kod: Zaznacz cały

lcd.setCursor(1, 0); lcd.print(linia1);         // odtwarzamy poprzedni stan na LCD
, powinna mieć zamiast w lcd.print(linia 1), lcd.print(linia2). Czyli

Kod: Zaznacz cały

lcd.setCursor(1, 0); lcd.print(linia2);         // odtwarzamy poprzedni stan na LCD
.
To spowoduje że po wybraniu Narzędzia->Odczyt->Temperatura i po naciśnięciu OK i ustawieniu temperatury i naciśnięciu OK, na wyświetlaczu pojawi się: Odczyt w górnej linii i Temperatura w dolnej, a nie Narzędzie w górnej i Temperatura w dolnej.
Czyli będzie tak jak powinno. :=).
Pozdrawiam
Mariusz
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 28 mar 2014, 22:31

Witam,
nikt nie pomoże? :=)
.. kontynuując. To samo jest ze Szkic->Importuj (P33). Po wybraniu opcji w prawo przechodzi się do GSM (P334), a nie do Menu Backend (P331). Hmmm. Poziom drugi menu działa ok, Poziom trzeci nie. W kodzie jest: P33.addRight(P331);. Jest co prawda P334.addLeft(P33); ale to do powrotu z podmenu GSM(P334) do Importuj (P33).
Ok, w trakcie pisania posta znalazłem regułę. Pierwszy poziom działa ok, zgodnie z projektem i kodem. Drugi poziom też. Natomiast na trzecim poziomie skok z drugiego poziomu jest niezgodnie z kodem do ostatniej pozycji w podmenu. Czyli z Narzędzia->Płytka-> skok do Nano a nie do Arduino Uno, Narzędzia->Odczyt-> skok do COM13 a nie do Temperatura etc.
Jeśli któryś z szanownych forumowiczów mógłby pomóc, byłbym wdzięczny, bo to coś z biblioteką chyba.
Jeszcze raz pozdrawiam i już nie marudzę :=).
Mariusz
C++ nie jest moją najsilniejszą stroną.
Pozdrawiam
Mariusz
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 29 mar 2014, 22:09

Witam
Do kol. Mariusza:

Przesyłam Ci do oblukania i przetestowania menu nad którym zacząłem pracować. Docelowo ma być to biblioteka, w sumie jeszcze nie wiem. Miało być coś prostego, a wyszło jak zawsze :-)

Kod: Zaznacz cały

/* 
MOJE Przykładowe MENU: (w sumie 19 opcji w trzech poziomach - rodzic, dziecko, wnuk)

       PLIK            EDYCJA          SZKIC           NARZEDZIA            POMOC
          Nowy            Kopiuj          Kompiluj         Temperatura
          Zapisz          Wklej           Importuj            Odczyt
          Exit            Zaznacz            EEPROM           Ustaw
                                             GSM           Wilgotnosc

rodzice (poziom 0) : PLIK, EDYCJA, SZKIC, NARZEDZIA, POMOC
dzieci  (poziom 1) : Nowy, Zapisz, Exit, Kopiuj, Wklej, Zaznacz, 
                     Kompiluj, Importuj, Temperatura, Wilgotnosc
wnuki   (poziom 2) : EEPROM i GSM dla Importuj oraz Odczyt i Ustaw dla Temperatura 
*/                                             
#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7); // konfiguracja pinów dla LCD
uint8_t arrowUpDown[8] = {0x4,0xe,0x15,0x4,0x15,0xe,0x4}; // strzałka góra-dół
const int ILE=20;      // ilość opcji +1 
int poz=1;             // nr aktualnej pozycji menu
int popm=1;            // nr poprzedniej pozycji menu
int pop=-1;            // poprzedni odczyt stanu klawiatury
int akt=-1;            // aktualny odczyt stanu klawiatury
char opcje[ILE][16]={  // tablica nazw opcji
  "              ",    // jedna pusta linia, dla czyszczenia LCD
  "     PLIK     ",    // 1   // numer opcji
  "     Nowy     ",    // 2
  "    Zapisz    ",    // 3
  "     Exit     ",    // 4
  "    EDYCJA    ",    // 5
  "    Kopiuj    ",    // 6
  "     Wklej    ",    // 7
  "    Zaznacz   ",    // 8
  "     SZKIC    ",    // 9
  "   Kompiluj   ",    // 10
  "   Importuj   ",    // 11
  "    EEPROM    ",    // 12
  "      GSM     ",    // 13
  "   NARZEDZIA  ",    // 14
  "  Temperatura ",    // 15
  "    Odczyt    ",    // 16
  "    Ustaw     ",    // 17
  "  Wilgotnosc  ",    // 18 
  "     POMOC    ",    // 19
};
/* Poniżej specyficzna tablica do nawigacji po menu.
Znaczenie poszczególnych składników:
index 0 - nr pozycji powyżej - UP (w stosunku do obecnej)
index 1 - numer pozycji dla RIGHT ( na prawo)
index 2 - numer pozycji dla DOWN (poniżej)
index 3 - numer pozycji dla LEFT ( na lewo)
index 4 - numer pozycji dla OK ( dla -1 skok do funkcji akcjaOK )
index 5 - poziom menu 0 - rodzic( poziom 0), 1 - dziecko (poziom 1) ,2 - wnuk (poziom2)
 przykłady:
 Zapisz (opcja nr 3) ma : UP-2, RIGHT-0, DOWN-4, LEFT-0, OK (-1) , 1 - bo to poziom 1 menu
 Temperatura (opcja 15) ma: UP-14, RIGHT-0, DOWN-18, LEFT-0, OK-16, 1 bo to poziom 1 menu
 Ustaw (opcja 17) ma: UP-16, RIGHT-0, DOWN-0, LEFT-0, OK (-1), 2, bo to poziom 2 menu
*/
int navi[ILE][6]={       // tablica opcji nawigacji po menu
{0,0,0,0,0,0},           // dla pustej linii
{0,5,0,19,2,0},          // PLIK
{1,0,3,0,-1,1},          // Nowy
{2,0,4,0,-1,1},          // Zapisz
{3,0,0,0,-1,1},          // Exit
{0,9,0,1,6,0},           // EDYCJA
{5,0,7,0,-1,1},          // Kopiuj
{6,0,8,0,-1,1},          // Wklej
{7,0,0,0,-1,1},          // Zaznacz
{0,14,0,5,10,0},         // SZKIC
{9,0,11,0,-1,1},         // Kompiluj
{10,0,0,0,12,1},         // Importuj 
{11,0,13,0,-1,2},        // EEPROM
{12,0,0,0,-1,2},         // GSM
{0,19,0,9,15,0},         // NARZEDZIA
{14,0,18,0,16,1},        // Temperatura
{15,0,17,0,-1,2},        // Odczyt
{16,0,0,0,-1,2},         // Ustaw
{15,0,0,0,-1,1},         // Wilgotnosc
{0,1,0,14,-1,0}          // POMOC
};
//--- odczyt stanu klawiatury ---------------------------------------
volatile int klawisz(int analog)
{
   int stan_Analog = analogRead(analog);delay(30);
   if (stan_Analog > 1000) return -1; // dla wartosci poza zakresem
   if (stan_Analog < 50)   return 0;  // w prawo  
   if (stan_Analog < 150)  return 1;  // do gĂłry 
   if (stan_Analog < 350)  return 2;  // w dół 
   if (stan_Analog < 550)  return 3;  // w lewo  
   if (stan_Analog < 750)  return 4;  // OK 
   return -1;                         // nic nie wcisnieto
}
// --- akcja po wcisnięciu klawisza OK ------------------------------
void akcjaOK()
{
  Serial.println("  - WYKONANO -  ");
  lcd.setCursor(0,1);lcd.print("  - WYKONANO -  ");
  switch(poz)
    {
    case 2:       // obsługa opcji Nowy (funkcja obsługi do napisania)
          break;
    case 3:       // obsługa opcji Zapisz (wszędzie poniżej funkcje obsługi)        
          break;
    case 4:       // obsługa opcji Exit 
          break;
    case 6:       // obsługa opcji Kopiuj
          break;
    case 7:       // obsługa opcji Wklej 
          break;
    case 8:       // obsługa opcji Zzanacz
          break;
    case 10:      // obsługa opcji Kompiluj      
          break;
    case 12:      // obsługa opcji Importuj -> EEPROM 
          break;
    case 13:      // obsługa opcji Importuj -> GSM
          break;
    case 16:      // obsługa opcji Temperatura -> Odczyt        
          break;
    case 17:      // obsługa opcji Temperatura -> Ustaw 
          break;
    case 18:      // obsługa opcji Wilgotność
          break;
    case 19:      // obsługa opcji POMOC        
          break;    
    }
  delay(1000);    // opcjonalne aby zobaczyć komunikat"  - WYKONANO - " 
  ekran();        // powrót do ostatniej opcji
}
// --- funkcja ekran ------------------------------------------------
void ekran()
{
  if(navi[poz][5]==0) // jeśli rodzic to wyświetlamy w pierwszej linii LCD
    {
    lcd.clear();
    lcd.setCursor(0,0);lcd.print("<");lcd.print(opcje[poz]);lcd.print(">");
    }
  else               // jeśli dziecko lub wnuk to w drugiej linii
    {
      lcd.setCursor(0,0);lcd.print(" ");lcd.setCursor(15,0);lcd.print(" ");
      lcd.setCursor(0,1);lcd.print("                ");
      lcd.setCursor(0,1);lcd.print((char)0);lcd.print(opcje[poz]);lcd.print((char)0); 
    }
  Serial.println(opcje[poz]);  // opcjonalnie także na serialu 
}
// --- funkcja menu -------------------------------------------------
void menu()
{
  akt=klawisz(0); // odczyt stanu klawiatury analogowej podpiętej do Analog 0
  if(akt!=pop)    // jeśli zmiana (naciśnięto coś)
    {
      switch(akt)
      {
      case 0: if(navi[poz][1]!=0)poz=navi[poz][1];break;  // klawisz w prawo
      case 1: if(navi[poz][0]!=0)poz=navi[poz][0];break;  // w górę
      case 2: if(navi[poz][2]!=0)poz=navi[poz][2];break;  // w dół
      case 3: if(navi[poz][3]!=0)poz=navi[poz][3];break;  // w lewo
      case 4: if(navi[poz][4]!=-1)poz=navi[poz][4];else akcjaOK();break;  // OK
      }
    if(akt!=-1 && poz!=popm){ekran();popm=poz;} // przy zmianie pozycji menu 
                                                // aktualizujemy LCD
    }
  pop=akt; // przypisanie aby nie generować autopowtarzania
}
// --- funkcja setup ------------------------------------------------
void setup()
{
  Serial.begin(9600);
  lcd.begin(16,2);               // inicjacja LCD
  lcd.createChar(0,arrowUpDown); // tworzymy nietypowy znak w pamięci LCD
  ekran();                       // wyświetlamy na LCD
}
// --- neverending story --------------------------------------------
void loop()
{
  menu(); // wywołujemy menu
}
// === KONIEC ========================================================
  
Wszystkie uwagi, propozycje i olśnienia mile widziane :-)
Pozdrawiam
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 1 kwie 2014, 23:12

Witam,
do kol. wojtekizk:
bardzo fajne menu. :=) i co najważniejsze działa poprawnie. Jest jednym z kilku którym się przyglądam.
Klikam intensywnie w ostatnich dniach w Arduino, w szczególności w propozycje menu. Realizuję projekt x i potrzebuję menu, a skoro już się za nie wziąłem, to chciałbym mieć docelowe. Takie do wszystkiego. Dobrze, tak myślę, mieć jednego "wroga" :=) i go dobrze poznać. Tak sobie myślę, że w każdym projekcie powinna być możliwość "widzenia" pewnych wartości po włączeniu, nie startup screen, tylko taki pokazujący statusy/wartości podczas normalnej pracy urządzenia. Po kliknięciu dowolnego klawisza przejście do menu i tu konfiguracja systemu. Myślę że przydałoby się lewo/prawo, góra /dół, esc i może klawisz menu (przejście do najwyższego poziomu). Wybór dowolnych klawiatur.
Najbardziej obiecującą biblioteką jest na dziś Menwiz. Dużo czasu poświęciłem na Menubackend. Rozważam napisanie "własnej/z kimś" / "od początku" biblioteki bo rozpoznawanie cudzych zajmuje mi za dużo czasu :=). Na pewno musi być nieblokująca (żadnych delay), pożądane callbacki i uniwersalność (różne wyświetlacze hd44780) - graficzne na razie odpuszczam.
Mariusz
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 2 kwie 2014, 08:43

Witam
co do żadnych delay to polecam tutorial " gotowiec 4" do oblukania. Jest tam dość ciekawa koncepcja :-)
Pozdrawiam
przepro
Młodszy majsterkowicz
Posty: 12
Rejestracja: 24 mar 2014, 23:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: przepro » 4 kwie 2014, 21:59

Hi,
znalazłem najnowszą wersję MenuBackend z 2013 r. która po podmianie na wspomnianą powyżej powoduje generowanie błędów. Jest też sporo zmian w liczbie dostępnych metod.
Czy ktoś mógłby się odnieść do zawartości tej wersji biblioteki? Przydałby się komentarz dostępnych metod osoby programującej obiektowo :=). Link do posta i pliku: http://forum.wiring.co/index.php/topic,253.0.html
II wersja tutoriala? :=)
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 5 kwie 2014, 11:23

Witam
Od jakiegoś tygodnia wgryzam się w tą nową wersje właśnie, jest sporo zmian, głównie kosmetyka i dodatkowa opcja dla klawisza skrótu. Coz wiosna i za bardzo nie mam teraz czasu, bo działeczka wzywa i zona coraz częściej wygania z piwnicy. Jak znajdę trochę czasu to napiszę jakiś poradnik :-).
Pozdrawiam
glowas
Młodszy majsterkowicz
Posty: 3
Rejestracja: 17 kwie 2014, 09:19

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: glowas » 21 kwie 2014, 00:46

Witam
Na wstępie życzę wszystkim Wesołych Świąt ;) .....
Od jakiegoś czasu próbuje zaprojektować małe urządzonko do testowania pewnej elektroniki w pracy...i próbuje złożyć teraz to w całość opierając sie właśnie na MenuBackend. Jako nowicjusz ,jesli chodzi o programowanie utknąłem w pewnym miejscu i nie mogę sam już tego ogarnąć, mianowicie mam problem w funkcji MenuUseEvent dla poziomu menu
(used.item.getName() == " Vref = 2.5V") tzn. po naciśnięciu klawisza ok ( opcja klawiatury czytaj_3 dla 5 przycisków) i jesli parametr akcja=czytaj_3(5,9,7,8,6); wstawię nad pętlą while to program "wskakuje" to tej pętli i działa pętla if (akcja==0) ale nie działa if (akcja==4). Natomiast jeśli parametr akcja=czytaj_3(5,9,7,8,6); wstawię do pętli while (jak jest podane w powyższym przykładzie gdzie ustawia sie temperaturę ) to pętla if (akcja==0) nie działa , ale pętla if (akcja==4) działa. Nie rozumiem dlaczego skoro sa to warunki równoległe mogące działać w tym samym czasie w końcu pętla while, jedynie co mi przychodzi do głowy co może powodować kolizję to ten delay(500); ,ale nie jestem pewny co z tym zrobić.
Poniżej załączam kod który posiadam na obecną chwilę:

Kod: Zaznacz cały

#include <MenuBackend.h>                 // dołączenie biblioteki
#include <ShiftLCD.h>

// --- definiujemy dla LCD własne znaki 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};

ShiftLCD lcd(2, 4, 3);

volatile int zm =-1;               // to dla kontroli zmiany stanu klawiatury
volatile int x=-1;                 // zmienna pomocnicza

char *linia1;                      // pierwsza linia wyświetlanego tekstu na LCD
char *linia2;                      // druga linia wyświetlanego tekstu na LCD

int analogInput1 = 1;              // A1 for GU Power
int analogInput2 = 2;              // A2 for CBoot
int analogInput0 = 0;              // A0 for Battery Level
int ledOutput = A5;                // LED for Battery Level

//TEST  dla Vref=2.5V//
int HOLD_DELAY = 100;    // Sets the hold delay of switch for Vref state change
int Vref_1 = 10;


unsigned long start_hold;
boolean allow = false;
int sw_RIGHT_state;
int sw_laststate = HIGH;
int Vref_1_state = LOW;
//................//

// for GUPower, CBoot, Power Level 
float vout = 0.0;
float vin = 0.0;
float R1 = 1000.0;    // !! resistance of R1 !!
float R2 = 1000.0;     // !! resistance of R2 !!
float R4 = 12000.0;    // !! resistance of R4 !!
float R6 = 4000.0;     // !! resistance of R6 !!

int value = 0;         // variable to store the value 

// --- wersja dla 5-ciu przycisków cyfrowych --------------------------------------------------
// dla przykładu jeśli wykorzystujesz piny: 1,2,3,11 i 12 to wołasz : czytaj_2(1,2,3,11,12)
int czytaj_3(int gora, int lewo, int ok, int prawo,int dol)
//int czytaj_3(int gora, int lewo, int ok, int prawo,int dol)
// gora   - nr pinu cyfrowego do którego podłączony jest przyciski góra
// lewo   - nr pinu cyfrowego do którego podłączony jest przyciski lewo
// ok     - nr pinu cyfrowego do którego podłączony jest przyciski OK
// prawo  - nr pinu cyfrowego do którego podłączony jest przyciski prawo
// dol    - nr pinu cyfrowego do którego podłączony jest przyciski dół
{
if(digitalRead(5)==LOW) return 1;
if(digitalRead(9)==LOW) return 3;
if(digitalRead(7)==LOW) return 4;
if(digitalRead(8)==LOW) return 0;
if(digitalRead(6)==LOW) return 2;
return -1;
}


// --- tworzymy wszystkie opcje Menu: ---------------------------------------
// de facto tworzymy obiekty klasy MenuItem, które dziedziczą po klasie MenuBackend
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent); // konstruktor 
                       
   MenuItem P1 = MenuItem(" GU Power test",1);
            
   MenuItem P2 = MenuItem("  CBoot test",1);
       
   MenuItem P3 = MenuItem(" TX/RX Data",1);
       
   MenuItem P4 = MenuItem(" Ref. Voltage",1);
      MenuItem P41 = MenuItem(" Vref = 2.5V",2);
      MenuItem P42 = MenuItem(" Vref = 1.25V",2);  
      MenuItem P43 = MenuItem(" Vref = -2.5V",2);  
      MenuItem P44 = MenuItem(" Vref = -1.25V",2);    
      
   MenuItem P5 = MenuItem("Battery Level",1);


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 :-)
  
  lcd.begin(16, 2);     // inicjacja LCD
  lcd.setCursor(16,0);
  lcd.print("HELLO   ");
  delay(100);
   int var = 0; 
	while(var < 25){
	  lcd.scrollDisplayLeft();
          var++;    //  ta opcja powoduje ze napis przesunie sie tylko raz i wyonuje nastepne zadanie
	  delay(30);
	}  
  lcd.clear();
  delay(100);
  
  // tworzymy w pamięci LCD 5 własnych znaków dla strzałek
  lcd.createChar(3,arrowLeft);    
  lcd.createChar(4,arrowRight);
  lcd.createChar(5,arrowDown);
  lcd.createChar(6,arrowBack);
  lcd.createChar(7,arrowUpDown);
    
    // tu przykładowe piny cyfrowe dla 3 wersji funkcji czytaj_3(1,2,3,11,12)
  pinMode(5,INPUT);digitalWrite(5,HIGH);
  pinMode(6,INPUT);digitalWrite(6,HIGH);
  pinMode(7,INPUT);digitalWrite(7,HIGH); 
  pinMode(8,INPUT);digitalWrite(8,HIGH); 
  pinMode(9,INPUT);digitalWrite(9,HIGH); 

    // declaration of pin modes GU Power monitor
  pinMode(analogInput1, INPUT);
    // declaration of pin modes CBoot monitor
  pinMode(analogInput2, INPUT);
    // declaration of pin modes Battery Level monitor
  pinMode(analogInput0, INPUT);
  pinMode(ledOutput, OUTPUT);
  
    // declaration of pin mode Vref_+2.5V
  pinMode(Vref_1, OUTPUT);
  
  
    //print on screen "Test Menu"
  lcd.setCursor(1,0);lcd.write(5); 
  lcd.print("  TEST MENU");
  
  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_3(5,9,7,8,6);
  delay(30);             // odczytujemy stan klawiatury:
  /*
  Ja używam funkcji czytaj_1() bo mam akurat klawiaturkę podpiętą pod A0
  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. 
}




          
/* --- Teraz pozycjonujemy  menu ( zgodnie z ustawieniem podanym 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);          // ustawiamy korzeń Menu, czyli pierwszą opcję
                                        
      P1.addRight(P2);                 // po prawej dla GU Power test jest CBoot test
      
      P2.addRight(P3);                 // na prawo od CBoot test jest TX/RX Data test
      
      P3.addRight(P4);                  // Ref Voltage
      P4.add(P41);
        P41.add(P42);P41.addLeft(P4);           
        P42.add(P43);P42.addLeft(P4);
        P43.add(P44);P43.addLeft(P4);
        P44.addLeft(P4);P44.add(P41);    // zamknięcie pętli itd...
        
      P4.addRight(P5);

      
      P5.addRight(P1);                    // zamkniecie pętli głównej, czyli poziomej - po Battery Level jest GU Power test
}

void menuUseEvent(MenuUseEvent used)      // funkcja klasy MenuBackend - reakcja na wciśnięcie OK
                                          // tutaj właśnie oddajemy menu na rzecz akcji obsługi klawisza OK
{
 
  if (used.item.getName() == " GU Power test") // Uwaga - dokładnie taki sam ciąg " GU Power test" jak w menu !!!
                                              // bo przecież getName() pobiera nazwę
      {
        // read the value on analog input
        value = analogRead(analogInput1);
        vout = (value * 5.0) / 1023.0;
        vin = vout / (R6/(R4+R6));  
        lcd.setCursor(1,1);      //set the cursor to the end of line 2
        lcd.print(vin);         //print the voltage
        lcd.setCursor(5,1);
        lcd.print("V");
         delay(200);                 //this delay makes the display readout more stable and not flash
 
      if (vin <= 14.0) {
      lcd.setCursor(8,1);
      lcd.print("FAILED");
         }
           else if (vin >= 16.0) {
               lcd.setCursor(8,1);
               lcd.print("FAILED");
         }
          else {
               lcd.setCursor(8,1);
               lcd.print("PASSED");
       }
      delay(300);
   // lcd.setCursor(1,0);lcd.print(" ");lcd.setCursor(1,0);lcd.print(linia1); //poprzedni stan LCD
    }
  if (used.item.getName() == "  CBoot test")
     {
      // read the value on analog input
       value = analogRead(analogInput2);
       vout = (value * 5.0) / 1023.0;      
       lcd.setCursor(1,1);      //set the cursor to the end of line 2
       lcd.print(vout);         //print the voltage
       lcd.setCursor(5,1);
       lcd.print("V");
       delay(200);                 //this delay makes the display readout more stable and not flash

       if (vout <= 3.0) {
      lcd.setCursor(8,1);
      lcd.print("FAILED");
      }
        else if (vout >= 3.6) {
           lcd.setCursor(8,1);
           lcd.print("FAILED");
      }
         else {
           lcd.setCursor(8,1);
           lcd.print("PASSED");
      }
      delay(300);
     }
     
  if (used.item.getName() == "Battery Level")
    {
      value = analogRead(analogInput0);
      vout = (value * 5.0) / 1024.0;
      vin = vout / (R2/(R1+R2));
      
      lcd.setCursor(10,1);      //set the cursor to the end of line 2
      lcd.print(vin);         //print the voltage
      lcd.setCursor(15,1);
      lcd.print("V");
      lcd.setCursor(1,1);     //set the cursor to the end of line 1
      lcd.print("Battery");        //print a label for the voltage
      delay(100);  
     
     if (vin < 4){
       digitalWrite(ledOutput, HIGH); 
          }
          else { digitalWrite(ledOutput, LOW); }
        delay(500);      
    }
   
   if (used.item.getName() == " Vref = 2.5V")
     {     
     
     int  akcja=-1;
     delay(500);
    akcja=czytaj_3(5,9,7,8,6);          
     while(akcja!=4)                   // ta pętla trwa tak długo aż wciśniesz klawisz OK       
         {
           zm=-1;
          //akcja=czytaj_3(5,9,7,8,6);           
          if(zm!=akcja)                    // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
            {                                            
              if (akcja==0)                //ON/OFF Vref_1=2.5V -> "RIGHT BUTTON"
               {
                sw_RIGHT_state = digitalRead(8);             // read input "RIGHT BUTTON"                       
                
                if (sw_RIGHT_state == LOW && sw_laststate == HIGH)
                {                                            // for button pressing
                 start_hold = millis();                       // mark the time
                 allow = true;                                // allow Vref_1 state changes
                 }              
                if (allow == true && sw_RIGHT_state == LOW && sw_laststate == LOW)
                {                                               // if button remains pressed
                    if ((millis() - start_hold) >= HOLD_DELAY)
                     {                                            // for longer than x/1000 sec(s)
                      Vref_1_state = !Vref_1_state;                              // change state of Vref_1
                      allow = false;                                            // prevent multiple state changes
                     }
                 }  
                 sw_laststate = sw_RIGHT_state;  
                digitalWrite(Vref_1, Vref_1_state);               
               }
               
            if (akcja==4)             
                 {     }             
            }
         }zm=akcja;
   
      }
}

// --- 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, lub4)
  lcd.clear();                                         
  lcd.setCursor(0,0); 
  
  MenuItem newMenuItem=changed.to;                       //get the destination Test Menu
  if(newMenuItem.getName()==menu.getRoot())
     {
      lcd.setCursor(1,0);lcd.write(5);
      lcd.print("  TEST MENU");
       }
    
  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 - (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 
    lcd.setCursor(15,1);lcd.write(7);                     // strzałka góra-dół
    }
    
}
Będę bardzo wdzięczny za wskazówki ,może w końcu ruszę dalej bo mi się wszystkie pomysły wyczerpały jak to rozgryźć.
Pozdrawiam wszystkich
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 21 kwie 2014, 08:38

Witam
Kilka spraw:
Jeśli przed pętlą while dasz to:

Kod: Zaznacz cały

akcja=czytaj_3(5,9,7,8,6);  
... a w samej pętli masz "zaremowane"

Kod: Zaznacz cały

//akcja=czytaj_3(5,9,7,8,6);  
... to przecież nigdy nie wykona się warunek if(akcja==4). Cały dowcip polega na tym właśnie, że w samej pętli trzeba wywoływać funkcję czytaj_3. Wtedy jest szansa, że wykonają się oba warunki.
Natomiast przed while wywołanie funkcji czytaj_3 tak naprawdę nie jest potrzebne. Robię to tylko po to aby mieć jakąś wartość dla warunku while. Równie dobrze mógłbym napisać zamiast akcja=czytaj_3 to: akcja =-1
Napisałem Ci krótki programik do testów, sprawdź jak to Ci działa
 int zm=-1;
int czytaj_3(int gora, int lewo, int ok, int prawo,int dol)
{
  if(digitalRead(5)==LOW) return 1;
  if(digitalRead(9)==LOW) return 3;
  if(digitalRead(7)==LOW) return 4;
  if(digitalRead(8)==LOW) return 0;
  if(digitalRead(6)==LOW) return 2;
  return -1;
}
//--------------------------------------------------------------------------------------------------------------
void test()
{
  int akcja=-1;
  while(akcja!=4) // ta pętla trwa tak długo aż wciśniesz klawisz OK
    {
      zm=akcja;
      akcja=czytaj_3(5,9,7,8,6);           
      if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
        {                                            
         if (akcja==0) //ON/OFF Vref_1=2.5V -> "RIGHT BUTTON"
           {
            Serial.println("akcja = 0");
           }
         if (akcja==4)
           {
            Serial.println("akcja = 4");
           }             
        }
   } 
}
// ------------------------------------------------------
void setup()
{
Serial.begin(9600);
}
// ------------------------------------------------------
void loop()
{
test();delay(1000);Serial.println("koniec testu");
}
Pozdrawiam i dzięki za życzenia. Odwzajemniam się tym samym :-)
glowas
Młodszy majsterkowicz
Posty: 3
Rejestracja: 17 kwie 2014, 09:19

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: glowas » 22 kwie 2014, 00:20

Witam serdecznie i szczególne podziękowania dla wojtekizk ;)
Wracając do kod, ta część 'zaremowana' to tylko dlatego że raz testowałem ją w pętli a raz nad pętlą. to akurat opisałem w poście.
Przetestowałem twój kod , sam działa ale jak wkleiłem go dokładne w miejsce (used.item.getName() == " Vref = 2.5V") to występowało coś dziwnego jak dla mnie bo dla:

if (akcja==0) -- w serial monitorze wyświetlało mi tak jakbym wcisnął około 12-stu razy klawisz a dla
if (akcja==4) -- działało bez problemu.

Dlaczego coś takiego wystepuje,nie mam pojęcia ???? ...ale dodając funkcje delay() w pętli if(zm!=akcja) { działa normalnie. Metodą prób i błędów w końcu zadziałało ale jeśli miałbym wytłumaczyć dlaczego tak jest nie mam pojęcia, bo dla mnie powinno to działać bez tego dodatkowego delay()

Także dziękuje za pomoc i może ktoś mi te ciekawe zjawisko wyjaśni ;) ... teraz dalej trzeba walczyć.

Poniżej część kodu ktora teraz działa;
if (used.item.getName() == " Vref = 2.5V")
{

int akcja=-1;
delay(500);
while(akcja!=4) // ta pętla trwa tak długo aż wciśniesz klawisz OK
{
zm=-1;
akcja=czytaj_3(5,9,7,8,6);
if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
{
delay(50); // to własnie te miejsce
if (akcja==0) //ON/OFF Vref_1=2.5V -> "RIGHT BUTTON"
{
sw_RIGHT_state = digitalRead(8); // read input "RIGHT BUTTON"

if (sw_RIGHT_state == LOW && sw_laststate == HIGH)
{ // for button pressing
start_hold = millis(); // mark the time
allow = true; // allow Vref_1 state changes
}
if (allow == true && sw_RIGHT_state == LOW && sw_laststate == LOW)
{ // if button remains pressed
if ((millis() - start_hold) >= HOLD_DELAY)
{ // for longer than x/1000 sec(s)
Vref_1_state = !Vref_1_state; // change state of Vref_1
allow = false; // prevent multiple state changes
}

}
sw_laststate = sw_RIGHT_state;
digitalWrite(Vref_1, Vref_1_state);
}

if (akcja==4)
{ }
}
}zm=akcja;

}
}


Pozdrawiam
Awatar użytkownika
wojtekizk
Starszy majsterkowicz
Posty: 311
Rejestracja: 19 lis 2013, 10:54
Lokalizacja: Bydgoszcz

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: wojtekizk » 23 kwie 2014, 21:24

Witam
Kurcze bardzo nieczytelny kod, bo nie stosujesz wcięć... użyj znaczników code
Co do dziwnego zachowania to problem lezy w tym:

Kod: Zaznacz cały

zm=akcja;

musisz to dać o jeden nawias zamykający wyżej... taka konstrukcja zapobiega wielokrotnemu powielaniu kodu przy wciśniętym na dłużej przycisku. Jeśli nawet bardzo krótko starasz się naciskać to procek i tak zinterpretuje to jako kilka wciśnięć, bo przecież wszystko dzieje się w niekończącej się pętli loop prawda? :-)
Zmienna zm służy właśnie temu by zapobiec sytuacjom dłuższego wciśnięcia klawisza. Ty dałeś delay(50) i od biedy jakoś działa. Gdy pokombinujesz ze zmienną zm=akcja nieco wyżej to problem ustąpi :-) i delay(50) będzie zbędne.
Pozdrawiam
glowas
Młodszy majsterkowicz
Posty: 3
Rejestracja: 17 kwie 2014, 09:19

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: glowas » 24 kwie 2014, 23:15

Witam ponownie ;)
Sorki za złe wklejenie kodu, chciałem zaznaczyć kolorem gdzie dodałem delay(). Co do części kodu:

Kod: Zaznacz cały

zm=akcja;
zgadza się działa tak jak piszesz, widać do w części twojego kodu obsługi klawiatury i nawigacji po menu gdzie spełnia swoją funkcje. Niestety w tym kodzie nie działa ;( ..... po prostu jest tak jakby tego nie było..więc żeby nie było że sobie utrudniam robotę zrezygnowałem z obsługi jednym przyciskiem i rozbiłem to na dwa, niby łatwiej ;) dodając podgląd na serialu :

Kod: Zaznacz cały

if (used.item.getName() == " Vref = 2.5V")
     {     
     lcd.setCursor(2,0);                
     lcd.print("             ");    
     delay(200);        
     volatile int akcja=-1;
          
     while(akcja!=3)                   // ta pętla trwa tak długo aż wciśniesz klawisz 'LEFT'     
         {
          zm=-1;
          akcja=czytaj_3(5,9,7,8,6); 
          delay(50);          
          if(zm!=akcja)                    // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
            {                                                        
             if (akcja==1)
               {  
               digitalWrite(Vref_1, HIGH);    // przycisk 'UP'
               lcd.setCursor(2,0);                
               lcd.print("");
               lcd.print("  Activated"); 
               Serial.println("akcja = 1");                  //for test
               }
             
             if (akcja==2)
               {  
               digitalWrite(Vref_1, LOW);    // przycisk  'DOWN'
               lcd.setCursor(2,0);                
               lcd.print("");
               lcd.print("Deactivated"); 
               Serial.println("akcja = 2");   //for test
               }
                              
            if (akcja==3)                    // przycisk   'LEFT'
               {  
                 Serial.println("akcja = 3");   //for test   
               }          
            }zm=akcja;
         }//zm=akcja;   
      }
I tu widać co się dzieję i znowu bez delay(50) nie da rady, tzn. przy naciśnięciu klawisza do góry bądź na dół pokazuje jakby był wciskany kilkakrotnie, na szczęście nie powoduje to zmiany stanu na Vref_1. Znowu wracając do zm=akcja; w powyższym kodzie , czy to bedzię za pętlą while czy w środku jest to samo....nie kumam. Czy jest możliwe ze jest jakaś kolizja z pętlą switch case do obsługi klawiatury ?????
Chciałbym zaznaczyć ze obecnie cały kod testuję w symulacji w środowisku Proteus , chyba to nie jest powód złej interpretacji kodu. Niestety płytki Arduino jeszcze nie posiadam ;) ...już nie długo.

Pozdrawiam
Bartek
Młodszy majsterkowicz
Posty: 2
Rejestracja: 22 cze 2014, 12:08

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: Bartek » 4 lip 2014, 15:42

Witam,
Poniżej wrzucam programik. Niestety nie mogę dojść dlaczego:
1. Nie odświeża ekranu w pierwszej linii na ostatnim znaku - jeżeli ustawiam wartość poniżej 100 zostaje na końcu 0 czyli np 50 wyświetla się jako 500
2.Po dorzuceniu fragmentu w void loop do sterowania wyjściami PWM ( analogWrite), menu praktycznie nie działa - długa reakcja na naciśnięcie przycisku, bez tego fragmentu menu działa bardzo dobrze.
3.Nie mam jak tego przetestować z napędami - czekam na shieldy. Czy tak zadeklarowane wartości ustawień (temp, silnik, went) dla PWM będą tym co ustawię w menu - tzn. czy zmieniając wartość z pozycji menu silnik będzie zwalniał/przyśpieszał?
Do programu chcę jeszcze dorzucić odczyt impulsów z fotokomórki - w programie chcę mieć dwie opcje - praca ciągła i odliczanie wstecz zadeklarowanej wartości. Impulsy z fotki mają też kontrolować urządzenie - gdy np przez 30sek t nie ma impulsu a pracuje, to ma się wyłączyć.
Trzeba jeszcze zrobić start/stop z dwóch oddzielnych przycisków, oraz zmianę trybu pracy - ciągła/odliczanie. Dodatkowo deklarowanie ile to jest 1 imp z fotki - 1 szt, 2 szt czy np3 szt. Na chwilę obecną utknąłem. Czy mogę liczyć na podpowiedź w temcie. Pozdrawiam.

#include <MenuBackend.h> // dołączenie biblioteki
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // obsługa wyświetlacza LCD

//definicja strzałek
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,16,2); // definicja pinów dla LCD (sprawdź piny w swoim LCD)
volatile int zm =-1; // to dla kontroli zmiany stanu klawiatury
volatile int x=-1; // zmienna pomocnicza
volatile int stan_Analog;
char *linia1; // pierwsza linia wyświetlanego tekstu na LCD
char *linia2; // druga linia wyświetlanego tekstu na LCD
int grzalka=9;
int naped=10;
int powietrze=11;
int zezwolenie=12;
int startm=5;
int stopm=6;
int czujnik=7;

int temp=110;
int silnik=120;
int went=130;

MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent); // konstruktor
// (" ")
MenuItem P1 = MenuItem(" NAPED PASOW",1);
MenuItem P11 = MenuItem("USTAW PREDKOSC",2);

MenuItem P2 = MenuItem(" POWIETRZE",1);
MenuItem P21 = MenuItem("USTAW NADMUCH",2);

MenuItem P3 = MenuItem(" TEMPERATURA",1);
MenuItem P31 = MenuItem(" USTAW TEMP.",2);

MenuItem P4 = MenuItem(" DLUGOSC TOREBKI",1);
MenuItem P41 = MenuItem("USTAW DLUGOSC",2);

MenuItem P5 = MenuItem(" TRYB PRACY",1);
MenuItem P51 = MenuItem(" TRYB CIAGLY",2);
MenuItem P52 = MenuItem(" ODLICZANIE",2);

/*
add - dodaje w pionie, addRight - dodaje w poziomie z prawej , addLeft dodaje z lewej
*/
void menuSetup() // funkcja klasy MenuBackend
{
menu.getRoot().add(P1); // korzeń Menu, czyli pierwsza opcja
P1.add(P11);
P11.addLeft(P1);

P1.addRight(P2);
P2.add(P21);
P21.addLeft(P2);

P2.addRight(P3);
P3.add(P31);
P31.addLeft(P3);

P3.addRight(P4);
P4.add(P41);
P41.addLeft(P4);

P4.addRight(P5);
P5.add(P51);
P51.add(P52);P51.addLeft(P5);
P5.addRight(P1);
}
// ----------- uff... nareszcie :-) -----------------------------------------------------------------------
void menuUseEvent(MenuUseEvent used) // funkcja klasy MenuBackend - reakcja na wciśnięcie OK
{

if (used.item.getName() == " ODLICZANIE")
{
lcd.setCursor(1,0);lcd.print("ODLICZANIE-->"); // info
digitalWrite(8,HIGH);
delay(2000);
lcd.setCursor(1,0);lcd.print(" ");lcd.setCursor(1,0);lcd.print(linia1); //poprzedni stan LCD
}

if (used.item.getName() == "USTAW PREDKOSC")
{
// przykładowa wartosc startowa - do zmiany po testach
lcd.setCursor(0,0);lcd.write(7); // wyswietlamy nasz symbol strzałki góra-dół
lcd.print(" ");lcd.setCursor(1,0);lcd.print("Ust.pred.= "); // tekst dla użytkownika
lcd.setCursor(13,0);lcd.print(silnik); // wyświetlamy akt. temperaturę
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_3(0,1,2,3,4); //delay(300);

if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
{
if (akcja==1) {silnik++;if(silnik>255)silnik=255;lcd.setCursor(13,0);lcd.print(silnik);delay(300);
}
// jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono PWM silnika napedowego
// ustawiono max próg i wyświetlono obecną temperaturę
if(akcja==2) {silnik--;if(silnik<50)silnik=50;lcd.setCursor(13,0);lcd.print(silnik);delay(300);}
// jesli akcja=2 (czyli wciśnieto klawisz w dół to mniejszono PWM silnika napedowego
// ustawiono min próg i wyświetlono obecny PWM silnika napedowego
if(akcja==4) // jeśli wciśnieto OK
{
lcd.setCursor(0,0);lcd.print("*USTAWIONO*");delay(2000); // pokazujemy 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
}
}
}

zm=akcja; // aktualizacja zmiennej zm, po to aby reagować tylko na zmiany stanu klawiatury

}
if (used.item.getName() == "USTAW NADMUCH")
{
// przykładowa wartosc startowa - do zmiany po testach
lcd.setCursor(0,0);lcd.write(7); // wyswietlamy nasz symbol strzałki góra-dół
lcd.print(" ");lcd.setCursor(1,0);lcd.print("Ust.went.= "); // tekst dla użytkownika
lcd.setCursor(13,0);lcd.print(went); // wyświetlamy akt. temperaturę
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_3(0,1,2,3,4); //delay(300);

if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
{
if (akcja==1) {went++;if(went>255)went=255;lcd.setCursor(13,0);lcd.print(went);delay(300);}
// jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono PWM silnika napedowego
// ustawiono max próg i wyświetlono obecną temperaturę
if(akcja==2) {went--;if(went<50)went=50;lcd.setCursor(13,0);lcd.print(went);delay(300);}
// jesli akcja=2 (czyli wciśnieto klawisz w dół to mniejszono PWM silnika napedowego
// ustawiono min próg i wyświetlono obecny PWM silnika napedowego
if(akcja==4) // jeśli wciśnieto OK
{
lcd.setCursor(0,0);lcd.print("*USTAWIONO*");delay(2000); // pokazujemy 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
}
}
}

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()
}
if (used.item.getName() == "USTAW TEMP.")
{
// przykładowa wartosc startowa - do zmiany po testach
lcd.setCursor(0,0);lcd.write(7); // wyswietlamy nasz symbol strzałki góra-dół
lcd.print(" ");lcd.setCursor(1,0);lcd.print("Ust.temp.= "); // tekst dla użytkownika
lcd.setCursor(13,0);lcd.print(temp); // wyświetlamy akt. temperaturę
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_3(0,1,2,3,4); //delay(300);

if(zm!=akcja) // ruszamy do pracy tylko wtedy gdy zmienił sie stan klawiatury
{
if (akcja==1) {temp++;if(temp>255)temp=255;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
// jesli akcja=1 (czyli wciśnieto klawisz w górę to zwiększono PWM silnika napedowego
// ustawiono max próg i wyświetlono obecną temperaturę
if(akcja==2) {temp--;if(temp<50)temp=50;lcd.setCursor(13,0);lcd.print(temp);delay(300);}
// jesli akcja=2 (czyli wciśnieto klawisz w dół to mniejszono PWM silnika napedowego
// ustawiono min próg i wyświetlono obecny PWM silnika napedowego
if(akcja==4) // jeśli wciśnieto OK
{
lcd.setCursor(0,0);lcd.print("*USTAWIONO*");delay(2000); // pokazujemy 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
}
}
}

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()
// ...
// ...
}
}
// --- Reakcja na wciśnięcie klawisza -----------------------------------------------------------------
void menuChangeEvent(MenuChangeEvent changed) // funkcja klasy MenuBackend
{

int c=changed.to.getShortkey(); // pobieramy shortkey (1,2,3, lub4)
lcd.clear(); // bez komentarza
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ół
}
}

int czytaj_3(int gora, int lewo, int ok, int prawo,int dol)
// gora - nr pinu cyfrowego do którego podłączony jest przyciski góra
// lewo - nr pinu cyfrowego do którego podłączony jest przyciski lewo
// ok - nr pinu cyfrowego do którego podłączony jest przyciski OK
// prawo - nr pinu cyfrowego do którego podłączony jest przyciski prawo
// dol - nr pinu cyfrowego do którego podłączony jest przyciski dół
{
if(digitalRead(0)==LOW) return 1;
if(digitalRead(1)==LOW) return 3;
if(digitalRead(2)==LOW) return 4;
if(digitalRead(3)==LOW) return 0;
if(digitalRead(4)==LOW) return 2;
return -1;
}
// ============================================================================================
//
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 tego nie zrobić
// 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 !!!

lcd.init();
lcd.backlight();
lcd.begin(16, 2); // inicjacja LCD
lcd.createChar(3,arrowLeft); // tworzy 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);

pinMode(0,INPUT_PULLUP);digitalWrite(0,HIGH);
pinMode(1,INPUT_PULLUP);digitalWrite(1,HIGH);
pinMode(2,INPUT_PULLUP);digitalWrite(2,HIGH);
pinMode(3,INPUT_PULLUP);digitalWrite(3,HIGH);
pinMode(4,INPUT_PULLUP);digitalWrite(4,HIGH);
pinMode(startm,INPUT_PULLUP);
pinMode(stopm,INPUT_PULLUP);
pinMode(czujnik,INPUT_PULLUP);

pinMode(8,OUTPUT);digitalWrite(8,LOW);
pinMode(grzalka, OUTPUT);
pinMode(naped, OUTPUT);
pinMode(powietrze, OUTPUT);
pinMode(zezwolenie, OUTPUT);

menuSetup(); // funkcja klasy MenuBackend - tu tak naprawdę tworzymy nasze menu
menu.moveDown();
}
// --- I nadszedł czas na neverending story :-) --------------------------------------------
void loop()
{
x=czytaj_3(0,1,2,3,4);delay(30); // odczytujemy stan klawiatury:

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.
{
digitalWrite(zezwolenie, HIGH);
delay(2000);
analogWrite(grzalka,temp);
delay(2000);
analogWrite(naped,silnik);
delay(2000);
analogWrite(powietrze,went);
}
}
// === KONIEC ===========================================================
Merki
Młodszy majsterkowicz
Posty: 10
Rejestracja: 10 lut 2013, 10:32

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: Merki » 31 sty 2015, 21:43

Witam serdecznie,

nie chciałbym zaczynać nowego wątku dotyczącego MenuBackEnd, ale mam pytanie co do działania tej biblioteki (dopiero zaczynam pisanie w Arduino).

Wzoruję się na przykładzie zarówno orginalnym jak i wojtekkizk'a. Chodzi mi mianowicie o problem ciągłego odświeżania np. wartości temperatury. Czy jeśli w kodzie programu przejdę instrukcją

Kod: Zaznacz cały

menu.moveDown();
oraz następnie

Kod: Zaznacz cały

menu.use();
do określonego menu, to to menu będzie się wywoływać ciągle? Zamieszczę może wycinek swojego kodu:

Kod: Zaznacz cały

if (used.item.getName() == "AKTUALNE")
  {
    
    odczyt();
    lcd.setCursor(0, 0); lcd.print("TEMP.AKT.");
    lcd.setCursor(10, 0); lcd.print(temp); lcd.write(byte(223)); lcd.print("C");
  
    lcd.setCursor(0,1); lcd.print(dt.day);lcd.print("-");
    lcd.setCursor(4,1);lcd.print(dt.month);lcd.print("-");
    lcd.setCursor(6,1);lcd.print(dt.year);
  
    lcd.setCursor(11,1);lcd.print(dt.hour);lcd.print(":");
    lcd.setCursor(14,1);lcd.print(dt.minute);
  }
oraz w pętli loop:

Kod: Zaznacz cały

delay(10);
  if((digitalRead(menu_key)==LOW) && (zm==0))                               
    {
      menu.moveDown();
      menu.use();
      zm=1;
    }    
    
     
  if (digitalRead(menu_key)==HIGH)
    {
      zm=0;
    }
Chodzi mi o to czy jeśli przejdę do jakiegoś menu i nie zmienia mi się stan na wejściu klawisza, to czy to dane menu pracuje ciągle (u mnie tego nie obserwuje)?

Rozwiązanie w pętli loop planuję nieco uprościć, tym niemniej póki co spełnia ono swoje zadanie dobrze (przechodzę między menu i reaguje tylko na zbocze narastające z przycisku menu).

W projekcie istnieją jedynie 3 przyciski: menu (do przechodzenia między menu), dodaj i odejmij (zmiana wartości zadanych). Brak klawisza OK. W tej chwili w funkcji odczyt robię odczyt RTC i temperatury, ale stan panela nie odświeża się na bieżąco, lecz dopiero gdy "przewinę" całe menu i wrócę do niego ponownie. Szukałem odpowiedzi na tym forum i innym, ktoś już pytał o podobny problem, ale z kodu programu nie potrafię wyczytać tego rozwiązania...

Jak można w prosty sposób rozwiązać ciągłe odświeżanie aktualnie wybranego menu?
marciu11
Młodszy majsterkowicz
Posty: 1
Rejestracja: 2 lut 2015, 18:40

Re: MenuBackend jak się w nim odnaleźć ?

Post autor: marciu11 » 2 lut 2015, 19:21

Jestem to nowy więc pozwolę sobie powiedzieć przysłowiowe Cześć :)
Wielkie dziękuję dla wojtekizk'a za włożony wysiłek i chęć przybliżenia po ludzku jak tej biblioteki używać.

Mam pytanie. Czy można jakoś wpływać na to, o ile manu pozycji ma się przesunąć przy nawigowaniu po menu?

Standardowo jest tak: Strzałka w prawo następna pozycja MENU... Strzałka w lewo poprzednia pozycja MENU...
A jak potrzebuję mieć menu dynamiczne. Na początku wybieram "ilość kroków"(od 1 do 5) a następnie od tego ustawienia muszę mieć uzależnioną ilość opcji w menu.

"Ile kroków" (3):
1 -> "Czas"
1 -> "temperatura"

2 -> "Czas"
2 -> "temperatura"

3 -> "Czas"
3 -> "temperatura"

-------------- pomiń te ...
4 -> "Czas"
4 -> "temperatura"

5 -> "Czas"
5 -> "temperatura"

6... dalsze menu...

Jakiś pomysł jak zdynamizować menu, aby nawigacja była uzależniona od początkowych ustawień?
Może jest jakaś opcja aktywacji/dezaktywacji pozycji w menu?
ODPOWIEDZ

Strony partnerskie: