• Witaj na Forum Arduino Polska! Zapraszamy do rejestracji!
  • Znajdziesz tutaj wiele informacji na temat hardware / software.
Witaj! Logowanie Rejestracja


Ocena wątku:
  • 0 głosów - średnia: 0
  • 1
  • 2
  • 3
  • 4
  • 5
Zmiana wartości warunków za pomocą przycisków
#1
Cześć,
Mam pewnie dość standardowy problem. Jest mnóstwo przykładów wykonania tego, ale żaden który próbowałem nie działa w moim przypadku. Na pewno robię coś źle. Potrzebuje pomocy/wzoru jak napisać zadanie dodające lub odejmujące wartość od zmiennej.

Generalnie, mam napisany główny program, który załącza odpowiednie przekaźniki w zależności od odczytów z sensorów, które porównuje z ustalonymi przeze mnie warunkami, i to działa.
Ale chciałbym móc zmieniać wartość graniczną warunku, chcę móc zmienić wartość temperatury, którą sterownik będzie starał się utrzymać za pomocą przycisków, a nie podłączania do komputera. Wartości, które chcę zmieniać mam zapisane np. jako int 25 - to wartość temperatury. Chciałbym teraz za pomocą przycisków móc dodać lub odjąć np. 1. Pomijam wartości histerezy itd. bo to mam ustawione na "sztywno" w głównym programie i nie będzie potrzeby tego zmieniać.

Mam takie proste menu, gdzie obsługa przycisków i wyświetlanie danych z czujników działa, nie wychodzi mi pisanie akcji zmiany wybranej wartości int.
Kod:
/*
  Code for LCD shield by Saptashisb Das
  Menu Driven Program
  Tutorial 1
*/

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C  lcd(0x27,2,1,0,4,5,6,7); 
//zmienne początkowe
int temp = 25;
int hum = 50;
int hzal = 1;
int hwyl = 2;
int minzal = 15;
int minwyl = 15;

int keypad_pin = A0;
int keypad_value = 0;
int keypad_value_old = 0;

char btn_push;

byte mainMenuPage = 1;
byte mainMenuPageOld = 1;
byte mainMenuTotal = 4;

void setup()
{
    lcd.begin(16,2);  //Initialize a 2x16 type LCD

    MainMenuDisplay();
    delay(1000);
}
void loop()
{
    btn_push = ReadKeypad();

    MainMenuBtn();

    if(btn_push == 'S')//enter selected menu
    {
        WaitBtnRelease();
        switch (mainMenuPage)
        {
            case 1:
              MenuA();
              break;
            case 2:
              MenuB();
              break;
            case 3:
              MenuC();
              break;
            case 4:
              MenuD();
              break;
        }

          MainMenuDisplay();
          WaitBtnRelease();
    }



    delay(10);

}//--------------- End of loop() loop ---------------------

void MainMenuDisplay()
{
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Select your menu");
    lcd.setCursor(1,1);
    switch (mainMenuPage)
    {
        case 1:
          lcd.print("1. Temperatura");
          break;
        case 2:
          lcd.print("2. Menu B");
          break;
        case 3:
          lcd.print("3. Menu C");
          break;
        case 4:
          lcd.print("4. Menu D");
          break;
    }
}

// This function is called whenever a button press is evaluated. The LCD shield works by observing a voltage drop across the buttons all hooked up to A0.
int evaluateButton(int x) {
  int result = 0;
  if (x < 50) {
    result = 1; // right
  } else if (x < 195) {
    result = 2; // up
  } else if (x < 380) {
    result = 3; // down
  } else if (x < 790) {
    result = 4; // left
  }
  return result;
}

void MenuA()

    lcd.clear();
     lcd.setCursor(0, 0);
    lcd.print("Temp:");
    lcd.print(temp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.print("(");
    lcd.print(temp);
    lcd.print((char)223);
    lcd.print("C");
    lcd.print(")");
   
   
    while(ReadKeypad()!= 'L');
    {
        //Insert Task for Menu A here
if int x ==2 temp = temp++;
lcd.clear();
lcd.setCursor(0,1);
lcd.print(temp);
}

}

void MenuB()

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Inside Menu B");

    while(ReadKeypad()!= 'L')
    {
        //Insert Task for Menu B here

    }
}
void MenuC()

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Inside Menu C");

    while(ReadKeypad()!= 'L')
    {
        //Insert Task for Menu C here

    }
}
void MenuD()

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Inside Menu D");

    while(ReadKeypad()!= 'L')
    {
        //Insert Task for Menu D here

    }
}



void MainMenuBtn()
{
    WaitBtnRelease();
    if(btn_push == 'U')
    {
        mainMenuPage++;
        if(mainMenuPage > mainMenuTotal)
          mainMenuPage = 1;
    }
    else if(btn_push == 'D')
    {
        mainMenuPage--;
        if(mainMenuPage == 0)
          mainMenuPage = mainMenuTotal;   
    }

    if(mainMenuPage != mainMenuPageOld) //only update display when page change
    {
        MainMenuDisplay();
        mainMenuPageOld = mainMenuPage;
    }
}

char ReadKeypad()
{
  /* Keypad button analog Value
  no button pressed 1023
  select  741
  left    503
  up      326
  down    142
  right   0
  */
  keypad_value = analogRead(keypad_pin);

  if(keypad_value < 100)
    return 'R';
  else if(keypad_value < 200)
    return 'D';
  else if(keypad_value < 400)
    return 'U';
  else if(keypad_value < 600)
    return 'L';
  else if(keypad_value < 800)
    return 'S';
  else
    return 'N';

}

void WaitBtnRelease()
{
    while( analogRead(keypad_pin) < 800){}
}
 
Odpowiedź
#2
Na razie masz półprodukt w każdym aspekcie i takie półprodukty starasz się połączyć w w funkcjonalny program.
Naucz się używać millis(), obsługiwać przyciski z obsługą drgań i bez blokowania z while, menu zrób chociaż na tablicy, tu jest niezły przykład bez blokowania loop: https://starter-kit.nettigo.pl/2017/04/m...aczu-16x2/ (o ile dobrze pamiętam). Ale to też przykład, demo. Nie ma sensu rysowac po ekranie częściej niż jest to niezbędne.
Razem z menu odpalam sobie led'a migającego co 50ms z loop, jeśli to miganie jest zakłócane przez menu, to szkoda tracić czas na taką konstrukcję.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#3
(20-04-2022, 05:28)kaczakat napisał(a): Na razie masz półprodukt w każdym aspekcie i takie półprodukty starasz się połączyć w w funkcjonalny program.
Naucz się używać millis(), obsługiwać przyciski z obsługą drgań i bez blokowania z while, menu zrób chociaż na tablicy, tu jest niezły przykład bez blokowania loop: https://starter-kit.nettigo.pl/2017/04/m...aczu-16x2/ (o ile dobrze pamiętam). Ale to też przykład, demo. Nie ma sensu rysowac po ekranie częściej  niż jest to niezbędne.
Razem z menu odpalam sobie led'a migającego co 50ms z loop, jeśli to miganie jest zakłócane przez menu, to szkoda tracić czas na taką konstrukcję.

No to zadam może trochę głupie pytanie, ale dla mojego rozumowania i dalszego działania istotne. 
Mam kod programu który steruje przekaźnikami i załącza odpowiednie urządzenia w zależności od warunków zewnętrznych, które mierzy sensorami. Ja chcę tylko ustawiać zadaną wartość którą program będzie starał się utrzymać po przez odpowiednie urządzenia (grzałki, wentylatory, światło itd.).  Na przykład mam wartość int temp=25 - wartość temperatury , w menu chciałbym zmienić 25 na inną liczbę. I pytanie dotyczy tego, czy mogę zrobić to po przez zmianę tego w menu i pętla pobierze sobie nową wartość, tak jakbym ją wpisał ręcznie w IDE czy muszę zrobić to na zasadzie: ustawiam wartość w menu -> jeśli wartość > od pobranej przez sensor to taka akcja, jeśli < od pobranej to inna akcja. Jednym słowem wszystko spójnie w menu, czy może to być "rozdzielone" - menu ustawienie parametru -> głowna pętla pobiera to co ustawione i tyle?
 
Odpowiedź
#4
<Napisałem się i zamknąłem okno Chrome, ale jest treść po przywróceniu, więc poczytasz Big Grin.>
Ale to wszystko jest to samo. Nie ma takiego miejsca gdzie program wchodzi i czeka co tam zrobisz z przyciskiem, ani takiego gdzie wchodzi i czeka co tam sobie zmienisz w menu. Tak to tylko wygląda na ekranie dla użytkownika. Bo jak będzie jakieś zakłócenie mocy Jedi i wejdzie do menu to kurczaki staną w płomieniach. Nawet jak wejdziesz do menu, co ma być pewnym odczytem, wielokrotnie potwierdzonym dla każdego kliknięcia, choć w czasie mrugnięcia okiem i z milionem innych rzeczy zrobionych między tymi potwierdzeniami, i zmienisz jakąś wartość, co powinno być zweryfikowane, że nastawa mieści się między spodziewanym min/max, a na koniec ewentualnie zapisana/wyjście bez zapisu po umownym czasie, można też dodatkowo zapisać do EEPROM by to mieć od następnego resetu, powrotu zasilania, albo jak np. zadziała WDT. Program ma przelatywać przez wszystkie funkcje w loop, wykonywać je lub nie, w zależności od wielu czynników, np. czy minęło odpowiednio dużo czasu od poprzedniego wykonania. Wyświetlenie czegoś nowego na LCD, albo kolejny odczyt temperatury, takich rzeczy nie warto robić w każdym obiegu loop. Można sprawdzać stan pinów jeśli są to jakieś krańcówki maszyny, i jest to ważne w każdym loop, nawet przycisków nie warto sprawdzać częściej niż co 10ms. Ale każda funkcja powinna mieć możliwość wykonać się w obiegu loop.
Jak wszystko będziesz chciał robić tak samo często ile w loop się da, to nic nie będzie ważne, a program nie będzie dobrze działał. A to że na ekranie pokazuje menu, to nie znaczy, że program ma stać w jakieś funkcji menu, on ma normalnie działać, miganie led ustawione w loop ma migać led, jak wchodzisz w menu i to staje to funkcja menu jest do śmieci. Funkcje mogą mieć zmienne globalne, albo lokalne statyczne, które pozwalają wykonywać funkcje z miejsca ostatniego wywołania, albo pomijać całe wykonanie wnętrza funkcji (wejść i wyjść, podjąć decyzję o wyjściu musi, to trwa kilka us), przydaje się tu np. konstrukcja switch case by przeskoczyć do etapu gdzie czekasz na jakąś odpowiedź z przycisku do menu. W funkcji menu powinno być zapisane, że tu wszedłeś, że jest wyświetlane, że jesteś przy opcji nr 5, jak wciskasz strzałki góra/dół to zmieniasz aktywną opcję, jak wciskasz lewo/prawo to zmieniasz wartości opcji 5, o krok dla wartości 5, może z różnymi skokami w zależności od długości wciśnięcia przycisku, w zakresie min/max opcji 5. Jak przycisków jest mniej to te same zmieniają pozycję i wartości, a to co się dzieje zależy czy jest się w aktywnej opcji, którą przełącza 3 przycisk, albo znowu jeden z tych dwóch ale długim wciśnięciem. Można odmierzać czas od wciśnięcia przycisku, takie coś wywołuje menu, ty myślisz a program liczy czas, coś wciśniesz to się coś zadzieje na ekranie, czas się resetuje, w tle wszystko działa w loop bez zmian, tylko zawartość ekranu mówi o grzebaniu w menu. Minie za dużo czasu to wszystko jest anulowane odnośnie grzebania w menu, ta funkcja znowu zaczyna być po wywołaniu od razu kończona, a za wyświetlanie do LCD bierze się np. ta od pokazywania temperatur. Tu jest taki przykład z eliminacją drgań styków https://www.youtube.com/watch?v=v8KXa5uRavg&t=1s , akurat do przycisków, analogowych również są gotowe biblioteki i po prostu można ich użyć. Ale to pokazuje jak tworzyć takie funkcje, ich może być koło siebie dziesiątki w loop (a nawet setki jeśli nie będziesz ich wywoływał częściej niż to absolutnie niezbędne) i każda sobie ogarnie trochę przestrzeni do działania bez zamulania reszty.
Ale jak robisz jakąś makietę, zabawkę do pokazania, demo pojedynczych funkcji urządzenia, gdzie nic złego się nie wydarzy choćby nie wiem co to takie rzeczy nie mają znaczenia, możesz sobie kuśtykać z delay i blokowaniem programu, byle coś tam migało na ekranie.
Do jednej wartości nie potrzebujesz w ogóle menu, dotykasz przycisku góra zmieniasz zmienną w górę, w dół to w dół, dotykałeś a nie dotykasz przez 10s to zapisz do EEPROM (no sprawdź czy jest w zakresie np. 20-30), a wartość bierze do pracy na bieżąco.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#5
(21-04-2022, 11:36)kaczakat napisał(a): <Napisałem się i zamknąłem okno Chrome, ale jest treść po przywróceniu, więc poczytasz Big Grin.>
Ale to wszystko jest to samo. Nie ma takiego miejsca gdzie program wchodzi i czeka co tam zrobisz z przyciskiem, ani takiego gdzie wchodzi i czeka co tam sobie zmienisz w menu. Tak to tylko wygląda na ekranie dla użytkownika. Bo jak będzie jakieś zakłócenie mocy Jedi i wejdzie do menu to kurczaki staną w płomieniach. Nawet jak wejdziesz do menu, co ma być pewnym odczytem, wielokrotnie potwierdzonym dla każdego kliknięcia, choć w czasie mrugnięcia okiem i z milionem innych rzeczy zrobionych między tymi potwierdzeniami,  i zmienisz jakąś wartość, co powinno być zweryfikowane, że nastawa mieści się między spodziewanym min/max, a na koniec ewentualnie zapisana/wyjście bez zapisu po umownym czasie, można też dodatkowo zapisać do EEPROM by to mieć od następnego resetu, powrotu zasilania, albo jak np. zadziała WDT. Program ma przelatywać przez wszystkie funkcje w loop, wykonywać je lub nie, w zależności od wielu czynników, np. czy minęło odpowiednio dużo czasu od poprzedniego wykonania. Wyświetlenie czegoś nowego na LCD, albo kolejny odczyt temperatury, takich rzeczy nie warto robić w każdym obiegu loop. Można sprawdzać stan pinów jeśli są to jakieś krańcówki maszyny, i  jest to ważne w każdym loop, nawet przycisków nie warto sprawdzać częściej niż co 10ms. Ale każda funkcja powinna mieć możliwość wykonać się w obiegu loop.
Jak wszystko będziesz chciał robić tak samo często ile w loop się da, to nic nie będzie ważne, a program nie będzie dobrze działał. A to że na ekranie pokazuje menu, to nie znaczy, że program ma stać w jakieś funkcji menu, on ma normalnie działać, miganie led ustawione w loop ma migać led, jak wchodzisz w menu i to staje to funkcja menu jest do śmieci. Funkcje mogą mieć zmienne globalne, albo lokalne statyczne, które pozwalają wykonywać funkcje z miejsca ostatniego wywołania, albo pomijać całe wykonanie wnętrza funkcji (wejść i wyjść, podjąć decyzję o wyjściu musi, to trwa kilka us), przydaje się tu np. konstrukcja switch case by przeskoczyć do etapu gdzie czekasz na jakąś odpowiedź z przycisku do menu. W funkcji menu powinno być zapisane, że tu wszedłeś, że jest wyświetlane, że jesteś przy opcji nr 5, jak wciskasz strzałki góra/dół to zmieniasz aktywną opcję, jak wciskasz lewo/prawo to zmieniasz wartości opcji 5, o krok dla wartości 5, może z różnymi skokami w zależności od długości wciśnięcia przycisku, w zakresie min/max opcji 5. Jak przycisków jest mniej to te same zmieniają pozycję i wartości, a to co się dzieje zależy czy jest się w aktywnej opcji, którą przełącza 3 przycisk, albo znowu jeden z tych dwóch ale długim wciśnięciem. Można odmierzać czas od wciśnięcia przycisku, takie coś wywołuje menu, ty myślisz a program liczy czas, coś wciśniesz to się coś zadzieje na ekranie, czas się resetuje, w tle wszystko działa w loop bez zmian, tylko zawartość ekranu mówi o grzebaniu w menu. Minie za dużo czasu to wszystko jest anulowane odnośnie grzebania w menu, ta funkcja znowu zaczyna być  po wywołaniu od razu kończona, a za wyświetlanie do LCD bierze się np. ta od pokazywania temperatur. Tu jest taki przykład z eliminacją drgań styków https://www.youtube.com/watch?v=v8KXa5uRavg&t=1s , akurat do przycisków, analogowych również są gotowe biblioteki i po prostu można ich użyć. Ale to pokazuje jak tworzyć takie funkcje, ich może być koło siebie dziesiątki w loop (a nawet setki jeśli nie będziesz ich wywoływał częściej niż to absolutnie niezbędne) i każda sobie ogarnie trochę przestrzeni do działania bez zamulania reszty.
Ale jak robisz jakąś makietę, zabawkę do pokazania, demo pojedynczych funkcji urządzenia, gdzie nic złego się nie wydarzy choćby nie wiem co to takie rzeczy nie mają znaczenia, możesz sobie kuśtykać z delay i blokowaniem programu, byle coś tam migało na ekranie.
Do jednej wartości nie potrzebujesz w ogóle menu, dotykasz przycisku góra zmieniasz zmienną w górę, w dół to w dół, dotykałeś a nie dotykasz przez 10s to zapisz do EEPROM (no sprawdź czy jest w zakresie np. 20-30), a wartość bierze do pracy na bieżąco.
Nie spodziewałem się, aż tak wyczerpującej odpowiedzi Smile  Nie miałem ostatnio czasu, ale znowu siadam i próbuję pisać. Próbuję przerobić menu z linku, który podesłałeś pod mój ekran z przyciskami, na razie nie chcą mi działać wszystkie przyciski ale to raczej ogarnę.
Myslę też jak rozwiązać akcję zmiany wartości liczbowych int.
 
Odpowiedź
#6
Hej, 
siadłem do klejenia ze sobą pętli termostatu i menu w którym działała mi zmiana wartości. Tylko tym razem przestały działać mi przyciski. Chyba coś namieszałem. Mógłby ktoś zerknąć?

Kod:
Obsługę wszystkich opcji  void menuUseEvent(MenuUseEvent used)
  Tam zapuszczasz pętle while lub do while i czytasz w niej stan klawiaturki.
  W tej pętli modyfikujesz zachowanie programiku w zależności od tego, co wciśniesz.
  Jeśli wciśniesz OK to wychodzisz z pętli while i de facto wracasz do loop-a.
Wszystkie ważniejsze kwestie opatrzono dość czytelnym komentarzem.
*/
// ============= MenuBackend tutorial ===============================================
#include <MenuBackend.h>                // dołączenie biblioteki
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <DS3231.h>
#include <DHT.h>                          // DHT sensor library
#include <OneWire.h>
#include <DallasTemperature.h>
#include <DFRobot_VEML6075.h>
#include "Sodaq_DS3231.h"
LiquidCrystal_I2C  lcd(0x27,2,1,0,4,5,6,7); // 0x27 is the I2C bus address for an unmodified backpack
//Constants
#define SENSOR_IN 2                        // Terrarium sensor pin
#define SENSOR_IN_Type DHT22              // DHT22
//#define SENSOR_OUT 3                      // Room sensor pin
//#define SENSOR_OUT_Type DHT22              // DHT22
#define RELAY_Heat 4                          // Pin no.4 - Heat relay
#define RELAY_Fan 5                          // Pin no.5 - Fan relay
#define RELAY_Light 6                      // Pin no.6 - Light relay
#define ONE_WIRE_BUS_1 A2                  // Pin A1 - termometr DS18B20 - wyspa
#define VEML6075_ADDR  0x10
DHT dht_in(SENSOR_IN, SENSOR_IN_Type);    // Initialize DHT sensor for 16mhz Arduino
//DHT dht_out(SENSOR_OUT, SENSOR_OUT_Type);  // Initialize DHT sensor for 16mhz Arduino
OneWire oneWire_in(ONE_WIRE_BUS_1);        // Informujemy bibliotekę o urządzeniu działającym na protokole 1-Wire
DallasTemperature sensor_inhouse(&oneWire_in);  // Łączymy funkcje biblioteki DallasTemperature z naszym urządzeniem 1-Wire (DS18B20)

Time t;
DFRobot_VEML6075_IIC VEML6075(&Wire, VEML6075_ADDR);  // sensor uv

float temperature_min = 28;            // przykładowa temperatura zalączenia grzałki
float temperature_max = 26;          // przykładowa temperatura wyłączenia grzałki
int OnHour = 19;              // przykładowa godzina załączenia oświetlenia
int OnMin = 25;          // przykładowa godzina wyłączenia oświetlenia
int OffHour = 19;            // przykładowa minuta załączenia oświetlenia
int OffMin = 26;          // przykładowa minuta wyłączenia oświetlenia

//Variables
float set_delay = 5000;      //Set delay for loop (30s)
int hum_min = 60;            //Set MINimum humidity level
int hum_max = 90;            //Set MAXimum humidity level

boolean light_value;
boolean heat_value;
boolean fan_value;
boolean x;
float temperature;  // Zmierz temperaturę DS18B20
float hum;                    //Stores humidity value
float temp;                  //Stores temperature value
//float hum_room;              //Stores room humidity value
//float temp_room;              //Stores room temperature value
float lightOn = (60 * OnHour) + OnMin;
float lightOff = (60 * OffHour) + OffMin;
float currentTime;
char weekDay[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };


// --- 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};
uint8_t degree[8] = {0x06,0x09,0x09,0x06,0x00,0x00,0x00,}; // Custom char degres c

    // definicja pinów dla LCD (sprawdź piny w swoim LCD)
volatile int zm =-1;              // to dla kontroli zmiany stanu klawiatury
volatile int y=-1;                // zmienna pomocnicza
volatile int stan_Analog;          // wartość na wejściu Analogowym dla klawiaturki analogowej
char *linia1;                      // pierwsza linia wyświetlanego tekstu na LCD
char *linia2;                      // druga linia wyświetlanego tekstu na LCD
/* Przykładowe Menu (podobieństwo do IDE Arduino): --------------------------------------------
// mamy 5 głównych opcji PLIK, EDYCJA, SZKIC, NARZEDZIA, POMOC
// w opcji SZKIC i NARZEDZIA mamy rozbudowane opcje dla 2 i 3 poziomu Menu
// --------------------------------------------------------------------------------------------
      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
--- Poniżej definicja opcji menu: ------------------------------------------
de facto to definicja konstruktora dla obiektu klasy MenuBackend o nazwie menu.
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 szortkey, czyli MenuItem P1 = MenuItem("PLIK",1);
Mamy tu 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
pomagamy użytkownikowi w wyborze.
*/
// --- tworzymy wszystkie opcje Menu: ---------------------------------------
// de facto tworzymy obiekty klasy MenuItem, które dziedziczą po klasie MenuBackend
extern void menuUseEvent(MenuUseEvent used);
extern void menuChangeEvent(MenuChangeEvent changed);
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent); // konstruktor
  //                        ("                ")
MenuItem P1 =  MenuItem("DaneKlimatyczne",1);
      MenuItem P11 = MenuItem("Temp.",2);
      MenuItem P12 = MenuItem("Temp.wyl.",2);
    // MenuItem P13 = MenuItem("Reiksmiu atst",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("Temperatura",1);
      MenuItem P21 = MenuItem("Temp.zal.",2);
      MenuItem P22 = MenuItem("Temp.wyl.",2);
    // MenuItem P13 = MenuItem("Reiksmiu atst",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 P3 = MenuItem("Oswietlenie",1);
      MenuItem P31 = MenuItem("Czas zal.",2);
      MenuItem P32 = MenuItem("Czas wyl.",2);
    // MenuItem P23 = MenuItem("Rankinis vald.",2);
//      MenuItem P24 = MenuItem("    Zaznacz",2);
//      MenuItem P25 = MenuItem("    Znajdz",2);

  MenuItem P4 = MenuItem("Timer",1);
      MenuItem P41 = MenuItem("Timer ON",2);
      MenuItem P42 = MenuItem("Timer OFF",2);
      //MenuItem P33 = MenuItem("Apsauga nuo salc",3);
    //  MenuItem P34 = MenuItem("Nuorinimas",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("    IRANKIAI",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("    PAGALBA",1);
          MenuItem P51 = MenuItem("  Jak zaczac",2);
          MenuItem P52 = MenuItem("  Srodowisko",2);
          MenuItem P53 = MenuItem(" Dokumentacja",2);
          MenuItem P54 = MenuItem("  O Arduino",2);
          */
/* --- 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.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(P11);P12.addLeft(P1);  // analogicznie robimy ze wszystkimi podopcjami dla PLIK
    //  P13.addLeft(P1);P13.add(P11);
//        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(P21);P22.addLeft(P2);  // analogicznie dla wszystkich podopcji
      // P23.addLeft(P2);P23.add(P21);
//        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 SZseKIC ma dziecko Weryfikuj
        P31.add(P32);P31.addLeft(P3);  // poniżej Weryfikuj jest Kompiluj
        P32.add(P31);P32.addLeft(P3);
      // P33.add(P34);P33.addLeft(P3);
      // P34.addLeft(P3);P34.add(P31);    // poniżej kompiluj jest Importuj
      P3.addRight(P4);

      P4.add(P41);                    // rodzic SZKIC ma dziecko Weryfikuj
        P41.add(P42);P41.addLeft(P4);  // poniżej Weryfikuj jest Kompiluj
        P42.add(P42);P42.addLeft(P4);
      // P33.add(P34);P33.addLeft(P3);
      // P34.addLeft(P3);P34.add(P31);    // poniżej kompiluj jest Importuj
      P4.addRight(P1);
/*        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
      */
}
// ----------- uff... nareszcie :-) -----------------------------------------------------------------------
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
//{
  //lcd.clear();
// lcd.setCursor(0,0);lcd.print("temperatura");
  // 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() == "Temp.zal.")  // dokładnie taki sam ciąg " Temperatura"
      {
        float temperature_min;
        float skok=0.2;// 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("Temp.=");lcd.setCursor(11,0);lcd.write(8);lcd.print("C"); // 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) {temperature_min+=skok;if(temperature_min>99)temperature_min=10;lcd.setCursor(7,0);lcd.print(temperature_min,1);delay(100);}
              // 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)  {temperature_min-=skok;if(temperature_min<10)temperature_min=10;lcd.setCursor(7,0);lcd.print(temperature_min,1);delay(100);}
              // jesli akcja=2 (czyli wciśnieto klawisz w dół to mniejszono temperaturę
              // ustawiono min próg i wyświetlono obecną temperaturę
            if(akcja==4) // jeśli wciśnieto OK
              {
              lcd.clear();lcd.setCursor(0,0);lcd.print("*Ustawiono:");lcd.setCursor(11,0);lcd.print(temperature_min);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() == "Temp.wyl.")  // dokładnie taki sam ciąg " Temperatura"
      {
       
        float temperature_max;
        float skok=0.2;// 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("Temp.=");lcd.setCursor(11,0);lcd.write(8);lcd.print("C"); // 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) {temperature_max+=skok;if(temperature_max>99)temperature_max=10;lcd.setCursor(7,0);lcd.print(temperature_max,1);delay(100);}
              // 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)  {temperature_max-=skok;if(temperature_max<10)temperature_max=10;lcd.setCursor(7,0);lcd.print(temperature_max,1);delay(100);}
              // jesli akcja=2 (czyli wciśnieto 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("*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()
      }
        /////////////////////////////////////////////////////////Czas////////////////////////////////////////
            if (used.item.getName() == "Czas zal.")  // dokładnie taki sam ciąg " Czas"
      {
        int skok;
        int tt;
        int OnHour=tt/60;
        int OnMin=tt%60;
     
        lcd.setCursor(0,0);lcd.write(7);    // wyswietlamy nasz symbol strzałki góra-dół
        lcd.print("              ");lcd.setCursor(1,0);lcd.print("Czas:"); // 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) {tt++;if(tt>23*60+50)tt=0;lcd.setCursor(7,0);lcd.print(OnHour);lcd.print(":");lcd.print(OnMin);delay(100);}
              // 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) {tt--;if(tt<0)tt=23*60+50;lcd.setCursor(7,0);lcd.print(OnHour);lcd.print(":");lcd.print(OnMin);delay(100);}
              // jesli akcja=2 (czyli wciśnieto klawisz w dół to mniejszono temperaturę
              // ustawiono min próg i wyświetlono obecną temperaturę
             
                OnHour=tt/60;
        OnMin=tt%60;
       
            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()
      }
// a tutaj obsługa pozostałych opcji :-)
// ...
// ...
}
// --- 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();                                            // 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(19,0);lcd.write(4);                    // strzałka w prawo
    lcd.setCursor(0,1);lcd.write(5);                      // strzałka w dół
    lcd.setCursor(19,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(19,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(19,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(19,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(19,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(19,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(19,1);lcd.write(7);                    // strzałka góra-dół
    }
}
/* --- ponżej funkcja odczytująca stan klawiatury -------------------------------------------
przygotowałem 3 różne wersje:
1) dla klawiaturki analogowej z shielda LCDanalogKey firmy DFRobot
2) dla joysticka ( 2 wejscia Analogowe i 1 pin cyfrowy )
  Uwaga dla tej wersji opcji musisz dopisać w funkcji setup konfigurację dla pin, np. tak:
  pinMode(1,INPUT);digitalWrite(1,HIGH);
3) dla 5-ciu pojedynczych przycisków ( potrzeba 5 pinów cyfrowych)
  Uwaga dla tej wersji opcji musisz dopisać w funkcji setup konfigurację dla pinów, np. tak:
  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);
*/
// --- wersja dla klawiatury 5-cio przyciskowej DFRobot --------------------------------------
volatile int czytaj_1(int analog)
{
  int pinAnalog=0;
  int stan_Analog = analogRead(pinAnalog);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 < 176)  return 1;  // do gĂłry
  if (stan_Analog < 332)  return 2;  // w dół
  if (stan_Analog < 525)  return 3;  // w lewo
  if (stan_Analog < 750)  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;
}*/
// ============================================================================================
//
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
  Wire.begin();
  rtc.begin();
  lcd.createChar(3,arrowLeft);    // tworzymy w pamięci LCD 5 własnych znaków dla strzałek
  lcd.createChar(4,arrowRight);
  lcd.createChar(5,arrowDown);
  lcd.createChar(6,arrowBack);
  lcd.createChar(7,arrowUpDown);
  lcd.createChar(8,degree);
  /* 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(RELAY_Light, OUTPUT);
  //digitalWrite(RELAY_Light, LOW);
  pinMode(RELAY_Heat, OUTPUT);
  pinMode(RELAY_Fan, OUTPUT);
  dht_in.begin();
  //dht_out.begin();
  sensor_inhouse.begin();

                                                      //Sensor uv
//Serial.begin(115200);
  delay(2000);
  while(!Serial);

  Serial.println();
  while(VEML6075.begin() != true)
    Serial.println("VEML6075 begin faild");
    delay(2000);

  Serial.println("VEML6075 begin successed");
  light_value = digitalRead(RELAY_Light);        //Read pin values (1 = ON, 0 = OFF)
  heat_value = digitalRead(RELAY_Heat);
  fan_value = digitalRead(RELAY_Fan);

{

  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 :-) --------------------------------------------
uint32_t old_ts;
void loop()
{
DateTime now = rtc.now();                    //get the current date-time
  uint32_t ts = now.getEpoch();
  currentTime = (60 * int(now.hour())) + int(now.minute());
                                                //Timer światła UV
{
if (lightOn <= currentTime < OffHour)
  {
    if (light_value == 1)
    {
      digitalWrite(RELAY_Light, 0);
      light_value = 0;
    }
  }
  if (lightOn > currentTime || lightOff <= currentTime)
  {
    if (light_value == 0)
    {
      digitalWrite(RELAY_Light, 1);
      light_value = 1;
    }
  }
}
{                                      // Wyświetlanie aktualnego czasu
    if (old_ts == 0 || old_ts != ts);{
old_ts = ts;
  Serial.print(now.year(), DEC);
  Serial.print('/');
  Serial.print(now.month(), DEC);
  Serial.print('/');
  Serial.print(now.date(), DEC);
  Serial.print(' ');
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.print(' ');
  Serial.print(weekDay[now.dayOfWeek()]);
  Serial.println();
    }
    delay(1000);
}                                                //sensor temp DS18B20
    {
    sensor_inhouse.requestTemperatures();
    Serial.print("Wyspa: ");
    Serial.print(sensor_inhouse.getTempCByIndex(0),1);
      Serial.println("*C");
                                                // sensor DHT22
hum = dht_in.readHumidity();                  //Read hum and temp data
temp = dht_in.readTemperature();
  temperature = sensor_inhouse.getTempCByIndex(0);
  //hum_room = dht_out.readHumidity();            //Read hum_room and temp_room data
  //temp_room= dht_out.readTemperature();

  for (int i = 0; i < 25; i++) {                // Print line for separating single loops
  Serial.print("_ ");
  delay(20);
  }

  Serial.print("Temp.: ");                  //Print out temperature and humidity values
Serial.print(temp);
  Serial.print("*C, Wilg.: ");
Serial.print(hum);
  Serial.println("%");

  //Serial.print("Room temperature: ");
// Serial.print(temp_room);
// Serial.print(" Celsius, Room humidity: ");
  //Serial.print(hum_room);
// Serial.println("%");
    }
      x = light_value;                            //Sterowanie temperaturą                                                           
switch (x){
  case 0:
    if (temperature < temperature_min && heat_value == 1) {      //check MIN_temp and if the heat is ON
    Serial.println("Temperatura: za niska, Grzałka: ON");
  }
              else if (temperature < temperature_min && heat_value == 0) { //check MIN_temp and if the heat is OFF
                digitalWrite(RELAY_Heat, 1);                  //turn heat ON when temperature is below temp_min and the heating is OFF
                Serial.println("Grzałka: ON.");
              }
                    else if (temperature > temperature_max && heat_value == 1) { //check MAX_temp and if the heat is ON
                      digitalWrite(RELAY_Heat, 0);                  //turn heat OFF when temperature is above temp_max and the heating is ON
                      digitalWrite(RELAY_Fan, 1);
                      Serial.println("Grzałka: OFF. Wentylator: ON");
                    }
                          else if (temperature > temperature_max && heat_value == 0) { //check MAX_temp and if the heat is OFF
                            digitalWrite(RELAY_Fan, 1);                  //turn fan ON when temperature is above temp_max and the heating is OFF
                            Serial.println("Grzałka: OFF. Wentylator: ON.");
                          }
                                else if (temperature > temperature_min && temperature < temperature_max && fan_value == 1) {  //check if temperature is between borderd and if the fan is ON
                                  digitalWrite(RELAY_Fan, 0);                                    //turn the fan OFF
                                  Serial.println("Temperatura: OK. Wentylator: OFF");
                                }
                                      else {
                                          Serial.println("Temperatura: OK.");
                                          break;
                                          case 1:
                                          digitalWrite(RELAY_Fan, 0);
                                          digitalWrite(RELAY_Heat, 0);
                                          break;
                                      }};
                                     
   
                                              //Sprawdzenie wilgotności
  if (hum < hum_min) {          //check hum_min
    Serial.println("Wilgotność: za niska");
  }
        else if (hum > hum_max) {  //check hum_min
          Serial.println("Wilgotność: za wysoka");
        }
              else {
                Serial.println("Wilgotność: OK.");
              }
Serial.println("");
  delay(set_delay);
{
  {                              //Sprawdzenie UV
  uint16_t    UvaRaw = VEML6075.readUvaRaw();        // read UVA raw
  uint16_t    UvbRaw = VEML6075.readUvbRaw();        // read UVB raw
  //uint16_t    comp1Raw = VEML6075.readUvComp1Raw();  // read COMP1 raw
// uint16_t    comp2Raw = VEML6075.readUvComp2Raw();  // read COMP2 raw

  float      Uva = VEML6075.getUva();                // get UVA
  float      Uvb = VEML6075.getUvb();                // get UVB
  float      Uvi = VEML6075.getUvi(Uva, Uvb);        // get UV index

  Serial.println();
  /*Serial.println("======== start print ========");
  Serial.print("UVA raw:    ");
  Serial.println(UvaRaw);
  Serial.print("UVB raw:    ");
  Serial.println(UvbRaw);
  Serial.print("COMP1 raw:  ");
  Serial.println(comp1Raw);
  Serial.print("COMP2 raw:  ");
  Serial.println(comp2Raw);*/
  Serial.print("UVA:        ");
  Serial.println(Uva, 2);
  Serial.print("UVB:        ");
  Serial.println(Uvb, 2);
  Serial.print("UVIndex:    ");
  Serial.print(Uvi, 2);
  if(Uvi < UVI_LOW)
    Serial.println("  UVI low");
  else if(Uvi < UVI_MODERATE)
    Serial.println("  UVI moderate");
  else if(Uvi < UVI_HIGH)
    Serial.println("  UVI high");
  else if(Uvi < UVI_VERY_HIGH)
    Serial.println("  UVI very high");
  else
    Serial.println("  UVI extreme");
  Serial.print("mw/cm^2:    ");
  Serial.println(Uvi2mwpcm2(Uvi), 2);
  //Serial.println("======== end print ========");
  delay(1000);
}
}
  if(zm!=y)                              // 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.
{
  y=czytaj_1(15);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 !!!
  */

      }
    } zm=y;                              // 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.
}
}
// === KONIEC ===========================================================
 
Odpowiedź
#7
A czy program wchodzi w funkcję czytaj_1()?
A jeśli tak, to co owa funkcja zwraca?
Jeśli masz problem z kodem lub sprzętem, zadaj pytanie na forum. Nie odpowiadam na PW, jeśli nie dotyczą one spraw forum lub innych tematów prywatnych.

[Obrazek: SsIndaG.jpg]
 
Odpowiedź
#8
To jest tak złe, na tak wielu poziomach, że... Albo rób proste programy i zostań z tym delay, albo cofnij się do robienia prostych programów i zacznij robić je bez delay, buduj swój warsztat programisty na nowo, w innym kierunku.
- stosujesz delay i zmienne delay
- floaty dla całkowitych temperatur
- inty dla godzin i minut, doba ma 24x60=1440 minut, w kuchence wystarczy 1 przycisk by ustawić godzinę, timery, czas załaczenia, długość pieczenia.
- własne znaki dla hd44780, sprawdziłeś jakie znaki są dostępne i czy nie dublujesz istniejących?
- czas w float? tak jak dla doby 1140 tak dla >100 lat wystarczy zmienna uint32_t by określić czas z dokładnością co do 1s, tak działa czas UNIX, licznik sekund wystartował w 1970, zmienna wystarczy do 2100r.
- linię tekstu LCD możesz po prostu zrobić statyczną tablicą na 16+1 znak NULL
- DS czytaj asynchronicznie bo tez blokuje na 750ms (maks);
- jak przestaniesz korzystać z delay to bzdurą będzie również czytanie czasu w każdym loop z zegara I2C
- nie startujesz zegara, ale czekasz na while(!Serial);, to jest tylko dla płytek jak Leonardo z UART na USB wbudowanym
- ten Twój loop trwa co najmniej 9s (KRZYK!), wciśnięcia przycisku z 50ms, ile musisz mieć wciśnięty przycisk by ktoś to zauważył?
- volatile sugeruje, że ktoś myślał o wrzuceniu odczytu przycisków w przerwanie, np. od timera.
Przykłady znajdziesz na tym forum, tu taki prosty z mierzeniem czasu:
Kod:
#define led 13
uint32_t czasTeraz,czasPoprzedni,tik=100; //tik musi byc mniejszy niz 1000 i dzilic 1000ms na rowne czesci
uint8_t nTik,sekundy,minuty,godziny,dni; //liczniki tikow, sekund, itd.
bool fnTik,fsekundy,fminuty=1,fgodziny,fdni; //flagi zdarzen nowy tik, nowa sekunda,minuta, godzina, dzien
char napis[10];

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
pinMode(led,OUTPUT);


}
//oczywiscie serialprinty i ledy sa do wyrzucenia
void loop() {
  // put your main code here, to run repeatedly:
  czas(); 



//gdy nowa sekunda zmien stan led
if (fsekundy)
{
  digitalWrite(led, ! digitalRead (led));
}

//gdy nowa sekunda i co dwie sekundy
if (fsekundy && sekundy%2==0)
{
  sprintf(napis,"%03d:%02d:%02d",godziny,minuty,sekundy);
Serial.println(napis);
}

if (fminuty && minuty%30==0) //gdy nowa minuta i co 30min
{
Serial.println("Minelo 30 minut");
}

 


}


void czas()
{
  czasTeraz=millis();
fnTik=fsekundy=fminuty=fgodziny=fdni=0;
if((uint32_t)(czasTeraz-czasPoprzedni)>=tik) //tak napisany warunek jest odporny na "klątwe 50 dni millis()"
{
  czasPoprzedni=czasTeraz;
  fnTik=1;
  nTik++;
  if(nTik>=(1000/tik))
  {
    nTik=0;
    sekundy++;
    fsekundy=1;
     if (sekundy>=60)
    {
      sekundy=0;
      minuty++;
      fminuty=1;
      if (minuty>=60)
      {
        minuty=0;
        godziny++;
        fgodziny=1;
        if (godziny>=24)
        {
          godziny=0;
          fdni=1;
          dni++;
   
        }
      }
    }
  }
}
}
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#9
Poniżej przykład z biblioteki do analogowej klawiaturki  AnalogKeypad by Makuna z 5 przyciskami, używam 2. Jest używana 1 zmienna przechowująca czas z dokładnością 1 minuty (0-1399). W int16 można zmieścić jeszcze manewrowanie sekundami dodając kolejne przyspieszenie by to nie trwało za długo, ale nie można by się rozdrobnić na pojedyncze bo braknie bitów, co 15 czy nawet 10s się zmieści. Bazą jest funkcja czas(), zamiast zatrzymywać program sprawdzam ile czasu minęło. 
Tak by pokazać, że zmienne pokazane na ekranie to nie te zmienne z pamięci komputera i jak łatwo wtedy je modyfikować.
Ustawienie czasu z pozycji 00:00 do 12:00 trwa 20s, w wersji z sekundami 30s. Kręcić licznikiem można w obie strony przez północ. A pozycja startowa też nie musi być 0:00, moja mikrofalówka ma start od 12:00, choć akurat osobno się w niej ustawia godz i minuty.
Kod:
#include <AnalogKeypad.h>

// the following table is for this Banggood Analog Keypad
// https://www.banggood.com/AD-Analog-Keyboard-Module-Electronic-Building-Blocks-5-Keys-For-Arduino-DIY-p-1374279.html
// see the AnalogKeypad_CreateConfig.ino sketch example on how to create this table by Makuna
const int KeypadMap[] = {0, 50, 100, 200, 400};

const uint16_t KeypadHoldTimeMs = 1000;
const uint8_t KeypadAnalogPin = A0;

int16_t czasDobowy, czasWduszenia; //int by dalo sie krecic licznikiem przez 0
uint8_t lastbutton;
bool wduszony,update1;
//dla funkcji czas()

uint32_t czasTeraz,czasPoprzedni,tik=10; //tik musi byc mniejszy niz 1000 i dzilic 1000ms na rowne czesci
uint8_t nTik,sekundy,minuty,godziny,dni; //liczniki tikow, sekund, itd.
bool fnTik,fsekundy,fminuty,fgodziny,fdni; //flagi zdarzen nowy tik, nowa sekunda,minuta, godzina, dzien

char napis16[17];

AnalogKeypad keypad(KeypadAnalogPin, KeypadMap, countof(KeypadMap), KeypadHoldTimeMs);

// the button event callback
// this will be called when buttons are pressed and released
void ButtonHandler(const ButtonParam& param)
{

//  Serial.print(param.button);
//  Serial.print(" ");
// 
lastbutton=param.button+1;
  switch (param.state)
  {
    case ButtonState_Up:
//    Serial.print("Up");
wduszony=0;
    break;
   
    case ButtonState_Down:
//    Serial.print("Down");
    break;
   
    case ButtonState_Click:
//    Serial.print("Click");
if(lastbutton==2) czasDobowy++;
else if (lastbutton==3) czasDobowy--;
update1=1;

    break;
   
    case ButtonState_DoubleClick:
//    Serial.print("Double Click");
    break;
   
    case ButtonState_Hold:
//    Serial.print("Hold");
wduszony=1;
    break;
  }
 
//  Serial.println();
}

void setup() {
 
    Serial.begin(115200);
//    while (!Serial); // wait for serial attach

//    Serial.println();
    Serial.println("Initialized");
}

void loop() {
  czas();
if(fnTik) //zamiast dely 10 to mozna sprawdzac stan przyciskow co 10ms
{
  keypad.loop(ButtonHandler); //
  keypadAction();
}

  if(fsekundy or update1)
  {
   update1=0;
//tylko minuty   
   sprintf(napis16,"%02d:%02d",czasDobowy/60,czasDobowy%60); // jak zaminiec liczbe 0-1440 na godziny i minuty
   Serial.println(napis16);
//sekundy w 10
//   sprintf(napis16,"%02d:%02d:%02d",czasDobowy/(60*6),(czasDobowy%(60*6))/6,10*(czasDobowy%(6)) ); // jak zaminiec liczbe   na godziny  minuty sekundy w piatkach
//   Serial.println(napis16);
  }
  // simulate other work happening
  // it also should avoid long delays so loop above can be called
  // at least every 10-20ms
//  delay(10);
}


//wersja dla minut
void keypadAction()
{
//funkcja jest wywolywana co 10ms, ale chcę coś zrobic  co 100ms
if(nTik%10==0) {
if (wduszony)
{     update1=1;
      czasWduszenia++;
 

       switch (lastbutton)
        {
        case 2:
          if(czasWduszenia<30)czasDobowy++;
          else if (czasWduszenia<120)czasDobowy+=10;
          else  czasDobowy+=60;
        break;

        case 3:
          if(czasWduszenia<30)czasDobowy--;
          else if (czasWduszenia<120)czasDobowy-=10;
          else  czasDobowy-=60;
        break;
        }
} else czasWduszenia=0;     
              }


if (czasDobowy>1440-1) czasDobowy=0;
if (czasDobowy<0) czasDobowy=1440-1; 

//if (czasDobowy>(1440*6)-1) czasDobowy=0;
//if (czasDobowy<0) czasDobowy=((1440*6)-1); 
}



void czas()
{
  czasTeraz=millis();
fnTik=fsekundy=fminuty=fgodziny=fdni=0;
if((uint32_t)(czasTeraz-czasPoprzedni)>=tik) //tan napisany warunek jest odporny na "klątwe 50 dni millis()"
{
  czasPoprzedni=czasTeraz;
  fnTik=1;
  nTik++;
  if(nTik>=(1000/tik))
  {
    nTik=0;
    sekundy++;
    fsekundy=1;
     if (sekundy>=60)
    {
      sekundy=0;
      minuty++;
      fminuty=1;
      if (minuty>=60)
      {
        minuty=0;
        godziny++;
        fgodziny=1;
        if (godziny>=24)
        {
          godziny=0;
          fdni=1;
          dni++;
   
        }
      }
    }
  }
}
}
Tym można ustawić czas i timery on/off dla całej doby, w zwykłych int16. 
Ten sam int można wykorzystać jak wyżej do ustawienia temperatur z dokładnością do 0.01oC, zamiast trzymać w pamięci 27.70 oC będzie to po prostu 2770 c oC.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
  


Skocz do:


Przeglądający: 1 gości