Menu na wyświetlaczu 16×2
Bardzo często robiąc jakiś projekt zachodzi konieczność zaimplementowania interfejsu do komunikacji człowiek-urządzenie. W prostych konstrukcjach z reguły wystarcza kilka pirzycisków i świecących diod, jednak gdy robimy bardziej zaawansowane urządzenie warto byłoby zastanowić się, czy nie wygodnie byłoy dodać wyświetlacz LCD i zrobić na nim menu w którym pomieścimy tyle ustawień na ile nam pamięć kontrolera pozwoli.
Potrzebne części
- Arduino, Teensy lub inny mikrokontroler, którym obsłużymy wyświetlacz LCD
- Wyświetlacz LCD (w projekcie użyłem takiego z konwerterem na interfejs I2C)
- 4 przyciski
Tak to już wszystko! Aby zrobić takie menu poza „mózgiem” w postaci mikrokontrolera będziemy potrzebowali tylko wyświetlacza i czterech przycisków :)
Opis podłączenia
Do wszystkich czterech przycisków podłączamy z jednej strony masę, a z drugiej strony wybrany przez nas pin cyfrowy. Nie musimy się martwić o rezystor podciągający, gdyż ustawimy go programowo.
Aby podłączyć wyświetlacz z konwererem należy podpiąć do niego zasilanie oraz magistralę I2C (piny oznaczone jako SDA i SCL). Dokładniejszy opis podłączenia i sterowania takim wyświetlaczem możnap rzeczytać tutaj.
Założenia programu
Interfejs będzie składał się z linii nagłówka oraz linii opcji. Na pierwszym poziomie menu w nagłówku będzie wyświetlany napis „Menu glowne”, natomiat na drugim poziomie będzie tam widniała nazwa aktualnie ustawianej opcji.
4 przyciski będą nam służyły do nawigowania po menu oraz zatwierdzania opcji. Będą to przyciski W LEWO, W PRAWO, OK oraz COFNIJ. Przycisk OK będzie wybierał daną opcję i wchodził w drugi poziom menu oraz zapisywał ustawienia. Przycisk COFNIJ będzie tylko wracał do menu głównego (bez zapisywania).
Nasze menu powinno być uniwersalne, to jest powinniśmy móc swobodnie po nim nawigować, ustawiać wartości graniczne, wyświetlać listę możliwych ustawień dla danej opcji, ustawić daną opcję jako akcję (po wciśnięciu przycisku OK zamiast wchodzenia poziom niżej wykona się jakaś funkcja).
Piszemy kod
Gotowy kod do pobrania tutaj.
Jak widać po zadeklarowanych stałych
#define BTN_BACK 8 #define BTN_NEXT 3 #define BTN_PREV 7 #define BTN_OK 5
przycisk COFNIJ należy podłączyć do pinu 8, W PRAWO do 3, W LEWO do 7 oraz OK do 5. Możemy oczywiście zmienić te piny na takie, jakie są dla nas dogodne :)
W naszym kodzie deklarujemy też jedną strukturę oraz typ wyliczeniowy:
typedef struct { String label; int minVal; int maxVal; int currentVal; void (*handler)(); } STRUCT_MENUPOS; typedef enum { BACK, NEXT, PREV, OK, NONE } ENUM_BUTTON;
Każdy element STRUCT_MENUPOS będzie reprezentacją pojedynczej opcji w menu. Zastanawiać może tutaj ostatnie pole, czyli wskaźnik do funkcji nie przyjmującej oraz nie zwracającej żadnych parametrów. Będzie ona nam służyła na dwojaki sposób. Jeżeli minVal będzie większe bądź równe maxVal (czyli sytuacja zdawałoby się bezsensowna) funkcja ta będzie akcją, którą chcemy wykonać (bez wchodzenia na drugi poziom menu). Drugi sposób wykorzystania tej funkcji to nadpisanie domyślnego zachowania programu (czyli wyświetlania użytkownikowi bezpośrednio wartości currentVal – będziemy mogli wstawić tam własne stringi). Trzecia możliwość to wpisanie tam wartości NULL – wtedy program zachowa się domyślnie, czyli wyświetli currentVal bez żadnego formatowania.
LiquidCrystal_PCF8574 lcd(LCD_ADDR); STRUCT_MENUPOS menu[5]; int currentMenuPos = 0; int menuSize; bool isInLowerLevel = false; int tempVal;
Teraz musimy zadeklarować kilka przydacnych później zmiennych. Tablica menu będzie zawierała pozycje naszego menu. currentMenuPos będzie informowało porgram która opcja jest aktualnie wybrana. menuSize to rozmiar menu (jest obliczany automatycznie na podstawie deklaracji). isInLowerLevel informuje nas, czy jesteśmy w menu główny, czy poziom niżej, a tempVal to tymczasowa wartośc dla currentVal ze struktury STRUCT_MENUPOS.
Przyjrzyjmy się teraz funkcji setup:
void setup() { Serial.begin(9600); lcd.begin(16, 2); lcd.setBacklight(255); pinMode(BTN_NEXT, INPUT_PULLUP); pinMode(BTN_PREV, INPUT_PULLUP); pinMode(BTN_BACK, INPUT_PULLUP); pinMode(BTN_OK, INPUT_PULLUP); menu[0] = {"Cyfry", 0, 9, 5, NULL}; menu[1] = {"Liczby", 10, 1000, 15, NULL}; menu[2] = {"Napisy", 0, 2, 0, formatNapisy}; menu[3] = {"Ulamki", 0, 30, 15, formatUlamki}; menu[4] = {"Port szer.", 0, 0, 0, actionPortSzeregowy}; menuSize = sizeof(menu)/sizeof(STRUCT_MENUPOS); }
Poza oczywistymi oczywistościami jak ustawianie trybu działania pinów cyfrowych i inicjalizacji wyświetlacza ustawiamy tutaj wszystkie pozycje naszego menu i tak np. menu z indeksem 2 będzie miało etykietkę „Napisy”, wartości w granicach od 0 do 2, a rysowanie tego podmenu będzie realizowała funkcja o sygnaturze void formatNapisy(). Menu z indeksem 4 wykona funkcję zadeklarowaną jako void actionPortSzeregowy() i nie wejdzie do niższego poziomu.
Czas zobaczyć co dzieje się w sercu programu, czyli w funkcji drawMenu:
Pozwolę sobie zacząć omawiać tę funkcję od końca, tak chyba będzie prościej zrozumieć w jaki sposób ona działa.
lcd.home(); lcd.clear(); if(isInLowerLevel) { lcd.print(menu[currentMenuPos].label); lcd.setCursor(0, 1); lcd.print(F("> ")); if(menu[currentMenuPos].handler != NULL) { (*(menu[currentMenuPos].handler))(); } else { lcd.print(tempVal); } } else { lcd.print(F("Menu glowne")); lcd.setCursor(0, 1); lcd.print(F("> ")); lcd.print(menu[currentMenuPos].label); }
Powyższy fragment odpowiada za wyświetlanie danych na ekranie. Na początku czyścimy wyświetlacz oraz ustawiamy kursor na pozycji początkowej (lcd.home()). Następnie sprawdzamy, czy jesteśmy na drugim poziomie. Jeżeli tak to w nagłówku wyświetlamy etykietkę opcji w któej jesteśmy, wypisujemy znak zachęty i jeżeli nie ma żadnej funkcji, która obsłuży nam wyświetlanie dostępnych możliwości to wypisujemy na ekran zmienną tymczasową. W przeciwnym wypadku wywołujemy funkcję z pola handler, która powinna zająć się wyświetleniem wartości na swój sposób.
Przyjrzyjmy się teraz obsłudze przycisków:
ENUM_BUTTON pressedButton = getButton();
[…]
switch(pressedButton) { case NEXT: handleNext(); break; case PREV: handlePrev(); break; case BACK: handleBack(); break; case OK: handleOk(); break; }
Odczytaniem przycisku zajmuje się funkcja getButton(). Nie będę się o niej rozpisywał, gdyż jedyne co robi to sprawdza stany na pinach za pomocą funkcji digitalRead i zwraca wciśnięty przycisk. Następnie wywołujemy odpowiednią funkcję w zależności od przycisku.
void handleNext() { if(isInLowerLevel) { tempVal++; if(tempVal > menu[currentMenuPos].maxVal) tempVal = menu[currentMenuPos].maxVal; } else { currentMenuPos = (currentMenuPos + 1) % menuSize; } }
Obsługa przycisku W PRAWO jest prosta. Jeżeli jesteśmy na drugim poziome menu to zwiększamy wartość tymczasową i sprawdzamy, czy nie wyszła poza zakres. Jeżeli jesteśmy w menu głównym to zwiększamy currentMenuPos i w razie przekroczenia zakresu zaczynamy od 0 (operacja modulo). Podobnie działa przycisk W LEWO z tym, że w drugą stornę :)
void handleBack() { if(isInLowerLevel) { isInLowerLevel = false; } }
Przycisk WSTECZ jedyne co robi to cofa się o poziom wyżej (do menu głównego).
void handleOk() { if(menu[currentMenuPos].handler != NULL && menu[currentMenuPos].maxVal <= menu[currentMenuPos].minVal) { (*(menu[currentMenuPos].handler))(); return; } if(isInLowerLevel) { menu[currentMenuPos].currentVal = tempVal; isInLowerLevel = false; } else { tempVal = menu[currentMenuPos].currentVal; isInLowerLevel = true; } }
Zobaczmy co kryje pod sobą funkcja obsługująca przycisk OK. Jeżeli pole handler nie jest NULLem oraz maxVal jest mniejszy bądź równy minVal to wykonujemy funkcję zdefiniowaną jako handler i wychodzimy z handleOk(). W przeciwnym wypadku jeżeli jesteśmy na niższym poziomie to zapisujemy tempVal do pola currentVal i przechodzimy poziom wyżej. Jeżeli jesteśmy w menu głównym to robimy dokładnie odwrotnie: zapisujemy currentVal do tempVal oraz wchodizmy na niższy poziom menu.
Teraz ostatnia (a właściwie pierwsza) część funkcji drawMenu:
static unsigned long lastRead = 0; static ENUM_BUTTON lastPressedButton = OK; static unsigned int isPressedSince = 0; int autoSwitchTime = 500; ENUM_BUTTON pressedButton = getButton(); if(pressedButton == NONE && lastRead != 0) { isPressedSince = 0; return; } if(pressedButton != lastPressedButton) { isPressedSince = 0; } if(isPressedSince > 3) autoSwitchTime = 70; if(lastRead != 0 && millis() - lastRead < autoSwitchTime && pressedButton == lastPressedButton) return; isPressedSince++; lastRead = millis(); lastPressedButton = pressedButton;
W tym fragmencie kodu dbamy o to, aby menu nie odświeżało się za każdym razem a tylko wtedy, keidy wciśniemy jakiś przycisk. Jest też funkcjonalność, która przy przytrzymaniu przycisku przez 3 zmiany pozycji menu zacznie robić to szybciej (widać na filmiku na końcu artykułu). Są to głównie zależności czasowe na podstawie funkcji millis().
Zerknijmy jeszcze tylko szybko na funkcje-uchwyty:
/* Funkcje-uchwyty użytkownika */ void actionPortSzeregowy() { Serial.println("Wywolano akcje: Port szeregowy"); } void formatNapisy() { String dictonary[] = {"Napis 1", "Napis 2", "Napis 3 :)"}; lcd.print(dictonary[tempVal]); } void formatUlamki() { lcd.print(tempVal / 10.0); }
Tak przygotowany program możemy wgrać na Arduino i sprawdzic go w praktyce.
Buduję właśnie slider do aparatu, chciałem wykorzystać powyższy kod do oprogramowania całości.
Jak „łapać” ustawione parametry w celu przekazania ich do środka funkcji, np do actionPortSzeregowy().
W dalszej części ta funkcja będzie miała inną zawartość i inną nazwę. Ale to nie istone ;)
Aktualną wartość ustawianego parametru masz w tempVal (tymczasowa, która jest wyświetlana aktualnie na ekranie), a jak robisz akcję, czyli np. port szeregowy i chciałbyś wypisać na nim to co jest aktualnie ustawione w ustawieniu „Cyfry” to robisz w handlerze Serial.println(menu[0].currentVal);
Sterownik do kotła chcę uzupełnić o menu Gdzie można a będzie zmieniać nastawy kilku danych. Moja wiedza o programowa i jest bardzo mierna. Wymyśliłem że inicjując zmienną globalną bez konkretnej wartości wprowadzę ją w funkcji pod konkretną pozycją w menu. Jednak po restarcie wartość zadana kasuje się do wartości domyślnej w menu. Zapisywać to w eprom? Jeszcze dwa pytania. Czy to normalne, że po wywołaniu funkcji actionPortSzeregowy i późniejszym włączeniu okna w Arduino IDE arduino się resetuje i powraca do menu głównego. Drugie pytanie:
Odnosi się do postu z 29.08.2017.
„… robisz w handlerze Serial.println(menu[0].currentVal);”.
Nie kumam o co chodzi. Czy dane (menu[0].currentVal) to wartość dla void (*handler) () ;, czyli (*handler) (menu[0].currentVal)?
1. Zmienne wracają do domyślnych dlatego, że są w RAMie. Zapisanie ich do EEPROMu to dobry pomysł – wtedy na pewno po przywróceniu zasilania będą miały dobre wartości (musisz tylko pamiętać, żeby wczytać te dane z EEPROM do odpowiednich zmiennych w funkcji setup).
2. Każde otworzenie monitora portu szeregowego wysyła sygnał reset do Arduino, także to normalne :) Wystarczy, że otworzysz go raz i później nie będziesz tego okna zamykał dopóki arduino pracuje i powinno działać (ewentualnie skorzystaj z innego programu np. putty do obsługi portu szeregowego).
3. W handlerze, czyli wewnątrz funkcji na którą wskazuje parametr oznaczony jako void (*handler)() ze struktury STRUCT_MENUPOS. Wystarczy, że gdzieś w kodzie zdefiniujesz funkcję typu void, która nie przyjmuje żadnych parametrów i podasz jej nazwę jako parametr w tej strukturze. przykładowo dla void testowaFunkcja() { … } tym parametrem będzie testowaFunkcja.
Dzięki wielkie za oświecenie. Będę działał na razie nie mam czasu – ochoty myśleć nad ukończeniem dzieła co chwila odkrywając się od tego.
dziękować :)
Menu.ino:14:1: error: 'ENUM_BUTTON’ does not name a type
Dlaczego nie chce mi się skompilować? :(
A zadeklarowałes typ ENUM_BUTTON przez typedef? :)
Czy można jaśniej przepraszam ale nie wszyscy wiedzą jak zadeklarować ENUM_BUTTON PRZEZ typedef
Jasne, chodzi dokładnie o ten krótki fragment kodu podany w artykule:
typedef enum {
BACK, NEXT, PREV, OK, NONE
} ENUM_BUTTON;
Mam głupie pytanie:
jak zmienić aby pod napisem menu kryła się na przykład zmienna odpowiadająca za temperaturę.
z góry dzięki za odpowiedź.
W funkcji drawMenu() masz linijkę „lcd.print(F(„Menu glowne”));”. Wypisz tam po prostu to co chcesz zamiast Menu glowne
Dzięki,
a wie pan może jak dodać kolejne okno (pulpit) w menu??
chodzi o to:
menu[0] = {„Cyfry”, 0, 9, 5, NULL};
menu[1] = {„Liczby”, 10, 1000, 15, NULL};
menu[2] = {„Napisy”, 0, 2, 0, formatNapisy};
menu[3] = {„Ulamki”, 0, 30, 15, formatUlamki};
menu[4] = {„Port szer.”, 0, 0, 0, actionPortSzeregowy};
Zależy co chcesz wyświetlić, jeżeli będzie to menu z wartościami liczbowymi to
menu[x] = {„Nazwa menu”, start_zakresu, koniec_zakresu, wartosc_domyslna, NULL};
W przypadku jakbyś chciał wyświetlić opcje tego menu we własnym formacie musisz napisać funkcję formatującą i jej adres przezazać jako ostatni parametr (zamiast NULL).
chodziło mi o pomnożenie np. menu[1] = {„Liczby”, 10, 1000, 15, NULL};
Nie bardzo rozumiem co masz na myśli przez pomnożenie :D
Nie bardzo rozumiem co masz na myśli przez pomnożenie :D
chodzi o dwie pozycje w menu. są od numerowane od 0 do 4 a chciał bym aby było od o do 6.
No to dopisz menu[5] = {…} i menu[6] = {…} tylko pamiętaj żeby wcześniej odpowiednio zwiększyć rozmiar tablicy menu
sory za głupie pytania.
A jak zwiększyć rozmiar tablicy :D
Gdzieś w kodzie masz pewnie fragment „STRUCT_MENUPOS menu[5];”. Tam zmieniasz 5 na inną liczbę np. jak masz menu od 0 do 7 to jest razem 8 elementów więc wpisujesz tam 8.
Witam,
mam pewien problem odnośnie czujnika temperatury. Gdy dodaję „void” odpowiedzialny za temperaturę i chcę umieścić to w menu to po wybraniu danej opcji na wyświetlaczu wyświetla się „-127 stopni” (czyli jakby czujnik nie był podpięty) lecz gdy wgram szkic odpowiedzialny tylko za wyświetlenie temperatury to już jest dobrze.
nie wiem z czego to wynika…
z góry dziękuje za pomoc
Hej, wrzuć kod na pastebina to coś pomyślimy ;)
tylko jest to na wyświetlaczu oled
(ale myślę że to nie problem)
zaraz wrzucę
https://pastebin.com/A3ijPsXc
Używanie funkcji delay() to bardzo zły pomysł w przypadku takich aplikacji. Druga sprawa w funkcji temperatura() masz sensors.getTempCByIndex(0), pdoczas gdy wcześniej przypisałeś pobraną temperaturę do zmiennej temp (w funkcji loop). Zrób sobie globalną zmienną na temperaturę, zrób pomiar w oddzielnym „wątku” i w funkcji temperatura() użyj tej globalnej zmiennej ;)
a mógł byś (jeśli się da) zmienić ten program tak aby działał
Jeżeli chcesz mogę to zrobić odpłatnie. Napisz na kontakt (at) krupson.eu
nie trzeba,
bawię sie dla przyjemność.
a co oznacza słowo „wątku w tym przypadku”
i jeczcze chciałem dodać że w programie który polega tylko na tym aby pokazać temperaturę na wyświetlaczu oled wszystko działa
Pod pojęciem „wątek” mam na myśli, abyś użył funkcji takich jak millis() i co określony czas robił odczyt temperatury , zapisywał go do jakiejśc zmiennej globalnej. Drugi wątek odpowiedzialny za wyświetlanie obrazu na ekranie pobierałby z tej zmiennej dane i je prezentował odpowiednio.
Tutaj pokazałem sposób jak możesz coś takiego zaimplementować: https://pastebin.com/DYLvQbfW
Oczywiście to tylko udawana wielowątkowość. Jakbyś chciał prawdziwą to możesz użyć FreeRTOS lub FemtoOS, ale ostrzegam że to już trochę wyższa szkoła jazdy :)
ok dzięki spróbuję to zrobić
i wieczcorem napisze jak mi wyszło
gdzie w szkicu mam umieścić
” float temp;
sensors.requestTemperatures();
temp = sensors.getTempCByIndex(0);
„
Witam. Zmodyfikowałem menu do 3 pozycji i wszystko śmiga dobrze, ale teraz chciałbym użyć tego kodu i tu pojawia się problem. Jak odwołać się do tych trzech pozycji??? Generalnie to kod ma być wykorzystany w termostacie. Te 3 pozycje to ZAŁĄCZENIE POMPY HISTEREZA i PRACA RĘCZNA.
Jeżeli chcesz żeby np. wciśnięcie przycisku OK na funkcji ZAŁĄCZENIE POMPY wykonało jakąś akcję, to ustaw dany element w ten sposób: {„ZAŁ. POMPY”, 0, 0, 0, actionZalaczeniePompy}; a następnie napisz funkcję void actionZalaczeniePompy() {…} w której wykonasz czynności niezbędne do załączenia tejże pompy :) W przypadku HISTEREZY możesz odczytać ustawioną wartość przez menu[x].currentVal
Witam od kilku miesięcy tworze projekt, który poiada menu. jedną z funkcji jest zegarek. problemem jest to że w momencie wejścia w nią godzina jest poprawna ale kiedy godzina się zmieni (np. o sekundę) godzina na wyświetlaczu się nie zmienia. Wie ktoś co mogę zrobić aby zadziałało.
z góry dzięki za odpowiedź
Musisz zadbać o to, żeby funkcja drawMenu wywoływała się co sekundę kiedy jesteś w menu, w któym pokazana jest godzina. W kodzie z przykładu drawMenu wykonuje się tylko jeżeli była jakaś akcja ze storny użytkownika (zakładamy, że nie wyświetlamy wartości dynamicznych, a to użytkownik zmienia.
dobrze,
ale jak mam to zrobić próbowałem już kilka razy ale nie wyszło.
nie za bardzo wiem w którym momencie szkicu i jak to zrobić.
z góry dzięki za odpowiedź
dołączam się do pytania ;-)
Słuchajcie no, nie jestem jasnowidzem i nie wiem jaki macie kod :P Całej aplikacji za Was też pisać nie mogę bo nie na tym polega idea uczenia się. Skoro bierzecie się za takie projekty to chyba powinniście mniej więcej wiedzieć co dzieje się w Waszym kodzie :)
osobiście rozumie, że nie tak wygląda nauka ale jak by si dało naprowadzić mnie i kolegę jak uzupełnić kod.:)
mój kod nie wiele się różni od twojego.
tylko zamiast ułamków jest zegarek.
W głównej pętli programu możecie dać sobie warunek (instrukcja if), że jeżeli jesteście aktualnie w menu wyświetlającym sekundę to co sekundę niech wywołuje drawMenu(). Można to osiągnąć zapamiętując ostatni czas wywołania w zmiennej globalnej np. unsigned long lastLcdRefreshTime, do której za każdym razem kiedy wywołacie drawMenu() zostanie orzypisana wartość zwracana przez millis(). Potem piszesz warunek if(millis() – lastLcdRefreshTime >= 1000) { drawMenu(); }
dzięki sprawdzę czy działa Ci powiem
a jak zrobić aby na każdej pozycji menu odświeżało się co sekundę.
a jak zrobić aby na każdej pozycji menu odświeżało się co sekundę.
Poczytaj moje komantarze wyżej, pisałem już jak to zrobić ;)
Witam,
tutorial jest super mam tylko pytanie jak wprowadzić opcję aby np: na wyświetlaczu wyświetlały się mierzone dane a do menu wchodziło się po wciśnięciu przycisku
W funkcji loop() wywołuj drawMenu() tylko wtedy, kiedy chcesz rysować menu, nic prostszego :D
Niestety nie działa mi to prawidłowo.
if (digitalRead(11) == HIGH)
{
drawMenu();
}
else
{
lcd.clear();
lcd.print(„Wilgotnosc:”);
}
po odpalaeniu arduino ( z stanem LOW na pinie 11) wyświetla się menu, następnie znika ( lcd.clear()) pojawia się napis wilgotnoć ale jest on „zanikający” jakby cos innego chciało się nadpisywać w tym miejscu lcd
To przez ciągłe nadpisywanie ekranu nowym napisem „Wilgotnosc:”. Spróbuj aktualizować go rzadziej, te ekrany tak mają że nie można ich za syzbko odświeżać.
Witam,
jak wywołać menu np kliknięciem klawisza?
Odpowiedz jeden post wyzej… na oczy mi juz pada :) przepraszam
Kłaniam się ponownie!
Drogi Kamilu bardziej ie już mi to wszystko chce działać, ale no właśnie ale. Podsunął byś pomysły jak aktualizować wyświetlanie pomiaru temp. Aktualnie odbywa się to przez naciskaie klawisza cofnij lub plus minus. Jest to naturalne bez dodatkowych zabiegów. Drugi ważniejszy problem jaki mnie dręczy to nastawy pozycji serva min, max. Dodałem dwie funkcje, które w zależności od zmiany tempVal kręcą sercem, a następnie zapisują do eeprom położenia. Wykonują to posłusznie, ale tylko jak w loop nie ma funkcji, która ma sterować servem. Dlatego kombinuję jak by tu zrobić, żeby gdy działam z ustawieniem min, max wyjść z funkcji servo. Jeżeli nie wetnie Ci zadłużo czasu to mam oczywistą prośbę.
Pozdrawiam.
Witam mój imienniku :)
Bardzo spodobało mi się Twoje menu i postanowiłem się z nim trochę pobawić. Chcę je wywoływać oddzielnym przyciskiem BTN_MENU (leda dodałem, żeby widzieć w jakim stanie mam program ;) ) i przy takim kodzie:
void loop() {
lcdDisplay();
}
void lcdDisplay() {
int MENU = digitalRead(BTN_MENU);
if (MENU != LAST_BTN_STATE) {lastDebTime = millis();}
if ((millis() – lastDebTime) > debTime) {if (MENU != BTN_STATE) {BTN_STATE = MENU;
if (BTN_STATE == HIGH) {MENU_STATE = !MENU_STATE;}}}
if(MENU_STATE == LOW){drawMenu(); digitalWrite(LED, HIGH);}
if(MENU_STATE == HIGH){noMenu();digitalWrite(LED, LOW);}
LAST_BTN_STATE = MENU;
}
void noMenu() {
static unsigned long lastRead = 0;
int autoSwitchTime = 500;
if(millis() – lastRead < autoSwitchTime) return;
lcd.clear();
lcd.print("Brak menu");
lastRead = millis();
}
led działa prawidłowo, czyli jedno naciśnięcie i świeci, drugie naciśnięcie i nie świeci, lecz z menu jest mały problem.
Kiedy mam funkcję noMenu() i nacisnę BTN_MENU to stan programu zmienia się na drawMenu() tylko, że do momentu jak nie nacisnę któregoś z przycisków obsługujących menu, nie wyświetla mi się nic… potem działa już wszystko dobrze
W drugą stronę działa to już lepiej, bo gdy mam wywołane drawMenu() i nacisnę BTN_MENU to od razu wyświetla mi się to co zapisane w funkcji noMenu()
myślę, że trzeba coś przekombinować w drawMenu() lub dodać jakiś warunek w lcdDisplay(), ale męczę się z tym już dłuższy czas i nie wychodzi :D
gdyby był potrzebny to cały kod tutaj:
https://pastebin.com/Ukjw9QRW
Cześć, to pewnie problem z tym, że drawMenu samo kończy swoje działanie jeżeli nie ma nic nowego do narysowania na ekranie. Trzeba spróbować wymusić odświeżenie menu przy pierwszym wywołaniu po pojawieniu się menu. Spróbuj tego kodu: https://pastebin.com/v9nKr8yh
Dodatkowo zmieniłęm enuma OK na SET, bo w moim Arduino IDE 1.8.5 nie chciało się skompilować twierdząc, że OK redefiniuje inny byt :P Widocznie twórcy dodali coś do biblioteki odkąd opublikowałęm artykuł.
Sprawdziłem działanie kodu i niestety nie pomogło ;) ale zobaczyłem, że po zmianie w
drawMenu(){
drawMenu(false)
}
z false na true, zaczyna „trochę” działać
po wywołaniu drawMenu przyciskiem, menu się pojawia od razu, tylko bardzo często się odświeża :D
także pomysł jest dobry, muszę tylko poszukać jak to udoskonalić… chyba, że masz jeszcze jakiś pomysł na szybko
Ustawienie tam true zamiast false jest złym pomysłem właśnie ze względu na to odświeżanie. TRzeba znaleźć problem gdzie indziej
Wróciłem do tematu menu, gdyż chcę go zacząć używać :D
Tymczasowo „wyrzuciłem” return z:
if(pressedButton == NONE && lastRead != 0) {
isPressedSince = 0;
//return;
}
i ustawiłem:
int autoSwitchTime = 800;
na tą chwilę mi wystarcza takie działanie menu (to co robię nie jest profesjonalnym projektem :D)
Mam jednak inny problem. Chcę aby zawsze po wywołaniu menu zaczynało od pozycji menu[0]. Na tą chwilę po wywołaniu menu zaczyna od ostatniej wyświetlanej pozycji, co mi się nie podoba i nie mogę sobie z tym poradzić, a sądzę że nie może to być aż takie trudne.
Masz
Coś mi ucięło komentarz :D
Miało być na końcu:
Masz może jakąś sugestię co mogę zrobić?
W momencie wywoływania menu ustaw zmienne isInLowerLevel na false i currentMenuPos na 0, powinno pomóc :)
Zrobiłem coś takiego:
(używam ENUM_BUTTON tak samo jak w drawMenu, MENU_STATE przyjmuje LOW lub HIGH i program startuje z HIGH, żeby nie zaczynać od włączonego menu)
if(pressedButton == MENU) MENU_STATE = !MENU_STATE;
if(MENU_STATE == HIGH){
noMenu();
}
if(MENU_STATE == LOW){
isInLowerLevel = false;
currentMenuPos = 0;
drawMenu();
}
Przy takim kodzie po wejściu do menu cały czas cofa mnie do pierwszej pozycji menu (co właściwie jest logiczne, bo w/w kod mam w loopie, także cały czas ustawia mi pozycję menu na 0) i nie wiem jak go przekonać, żeby tak robił tylko raz po naciśnięciu przycisku MENU ;)
Kurde ale ja głupi :D
wystarczy to przerzucić do if(MENU_STATE == HIGH) i zadziała, bo zawsze po wyjściu z menu ustawi sobie te dwie wartości na false i 0 :)
Dzięki za radę
Czołem!
Jakiś czas temu zapytałem kilka razy o to menu. U mnie się sprawdza i sprawdzza sie coraz lepiej.
Ostatnio wymyśliłem sposób na wyświetlanie innego ekranu niż ekranu menu. Może ktoś na to wpadł, może ktoś uzna,że to mało fajny sposób, że są jakieś błędy (narazie nie sprawdzałem większym projekcie), może komóś przyda się za wystarczające.
unsigned long klepsydra=0;
void loop() {
//while (millis()-klepsydra<50000);
do
{
wyswietlacz();
}
while ((!digitalRead(BTN_BACK))||(!digitalRead(BTN_NEXT))||(!digitalRead(BTN_OK))||(!digitalRead(BTN_PREV)));
do
{
drawMenu();
}
while (millis()-klepsydra<25000);
//while ((!digitalRead(BTN_BACK))&&(!digitalRead(BTN_OK)));
klepsydra=millis();
}
Co jest?
Działało, działało i przestało. Zacząłem uleprzać dobre, aż zepsułem na dobre. Podpowiecie co mogło się stać ? Powót do tego stanu, który tu zamieściłem wali błędami, że dziw bierze.