logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[AVR] [AVR][C] - Jak efektywnie tworzyć rozbudowane menu wielopoziomowe?

tehaceole 03 Paź 2012 07:31 20094 16
  • #1 11371916
    tehaceole

    Poziom 28  
    Witam Kolegów
    Ostatnio zachciało mi się zmienić podejście do tworzenia menu. Do tej pory realizowałem je na konstrukcjach switch-case. Jednak ten sposób jest dość uciążliwy, jeżeli menu ma być mocno rozbudowane, ma posiadać submenu itp.
    Postanowiłem dokonać przesiadki na bardziej przyjazny sposób tworzenia menu. Odpowiedź była jasna: tablice, struktury i wskaźniki...
    Poszukałem, pogmerałem i znalazłem kilka bardzo ciekawych "modeli" menu. Były to m.in. menu Butterfly, tinymenu. Niestety jakoś nie mogłem się w nich odnaleźć. Czegoś mi w nich brakowało. Wreszcie w przepastnych czeluściach brzuszyska wujka Gugla trafiłem na projekt Niemca Tobiasa Schlegela. OLD_LCDmenu jest do obejrzenia tutaj a paczka z kodami jest do pobrania tutaj.
    Dodanie menu do własnego projektu nie sprawiło żadnych problemów. Brak warningów itp. tym bardziej napawał mnie optymizmem.

    Niestety za wcześnie się ucieszyłem. Po wgraniu aplikacji, która w pętli głównej nie robi nic więcej poza obsługą (nawigacja, wyświetlanie) menu przyszło rozczarowanie. Na głównym poziomie menu na wyświetlaczu znajdują się same "krzaczki". Dopiero po przejściu do submenu lub subsubmenu wszystko jest wyświetlane i obsługiwane "cacy". Wygląda mi to tak, jakby brakowało czegoś w inicjalizacji wskaźnika na główne menu. Poniżej kody:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Kod: text
    Zaloguj się, aby zobaczyć kod

    No i główna pętla programu:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Koledzy spójrzcie na ten kod i podpowiedzcie co może być nie tak, że wywołanie jako aktualnego menu *ui_TopMenu wywala same krzaki na wyświetlaczu, natomiast nawigacja po niższych poziomach menu przebiega bez najmniejszych problemów.
    Dodam tylko, że autor ma też dostępną nowszą wersję menu tutaj.
  • Pomocny post
    #2 11372215
    excray
    Poziom 41  
    Korzystam z tego:
    http://caladan.jogger.pl/2010/11/06/implement...prostego-menu-w-c-takze-dla-mikrokontrolerow/
    menu. Działa i to działa świetnie. Przerobiłem tylko na wersję z tablicą we flash bo szkoda RAMu.


    Raport:
    Można by umieścić przekierowanie do tego tematu: https://www.elektroda.pl/rtvforum/topic3275560.html#16083176 tutaj są umieszczone źródła do tego menu.
  • #3 11372289
    tehaceole

    Poziom 28  
    Przyznam Ci Kolego, że to było drugie menu jakie brałem pod uwagę. Mógłbyś zamieścić jakąś fotkę lub filmik jak ono się sprawuje w boju? Jak wygląda możliwość budowy submenu dla danego menu?

    Do czego zmierzam? Szukam przyjaznego mechanizmu GENEROWANIA menu. Czemu generowania? Bo wiadomo - dla każdego urządzenia menu będzie inne. Jednak przy wspólnym mechanizmie generowania (nawigacja, wyświetlanie) menu całość sprowadza się do podmiany pozycji menu i zdarzeń do obsługi.

    W podanym przez Ciebie przykładzie poszczególne pozycje menu definiuje się w ten sposób:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Tak gapię się na ten obrazek z rozpiską stanów automatu i gapię i... Kurka to proste:) Tylko trzeba to sobie najpierw rozrysować na kartce a poźniej uważać żeby się nie zamotać przy wprowadzaniu do tabeli next_state.
    Nooo powiem Ci, że jest to zarąbiste i bardzo proste podejście, do tego wygląda niezbyt zasobożernie i przede wszystkim jest nie blokujące.
    Rewelka Kolego.
    Byłbyś tak uprzejmy i podrzucił tą swoją przerobioną wersję?

    Tak na marginesie podam przyczynę dla której szukam jakiegoś łatwego w implementacji "sposobu" na menu:[AVR] [AVR][C] - Jak efektywnie tworzyć rozbudowane menu wielopoziomowe?Dodawanie kolejnych pozycji do tego i tak już rozrośniętego menu będzie po prostu nieporozumieniem... Menu pochodzi z tego urządzenia.
  • Pomocny post
    #4 11372391
    excray
    Poziom 41  
    Niestety nie mam żadnego filmiku. Sprawuje się REWELACYJNIE. Za to podrzucę Ci gotowca bo ten na stronie wymaga sporo poprawek i domysłów co gdzie jest. Jest to wersja pierwotna która przechowuje w tablicy RAM. Funkcje których tutaj nie ma robią:
    LCDINIT - inicjalizacja wyświetlacza
    LCD_POS - ustawia pozycję: wiersz, kolumna
    LCD_CLS - czyści ekran
    LCD_PSTR - wyświetla ciąg znaków z FLASH
    LCD_DTA - wyświetla 1 znak.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    A jak chcesz przerzucić tą tabelę do pamięci FLASH to poszukaj w moich tematach z [162] w tytule tam jest to opisane co i jak.
  • #5 11372435
    tadzik85
    Poziom 38  
    Do opisu stanów warto użyć enuma.

    Kod stanie się czytelniejszy.
  • #6 11372456
    tehaceole

    Poziom 28  
    tadzik85 napisał:
    Do opisu stanów warto użyć enuma.

    Kod stanie się czytelniejszy.
    To w 100% popieram. Po południa jak dorwę się do swojego urządzenia to przetestuję to rozwiązanie.
  • #7 11372685
    tmf
    VIP Zasłużony dla elektroda
    Ponieważ wysłałeś mi info na PW, więc odpowiadam. Sprawę menu wyjaśniłem w swojej książce, a przykłady są dostępne za darmo dla wszystkich na ftp Helionu, tam też jest kompletny kod menu. Jeśli chcesz to go użyj, jeśli masz wątpliwości to chętnie pomogę i je wyjaśnię. Jak widzisz generalnie filozofii w tym wielkiej nie ma, po prostu struktura w formie listy zazwyczaj jednokierunkowej, z funkcjami obsługi poszczególnych pozycji w formie callbacków. Tu trudno wymyślić coś nowego i lepszego. Do opisu stanu wystarczy użyć wskaźnika wskazującego na aktualną pozycję menu.
  • #8 11375474
    tehaceole

    Poziom 28  
    excray - pomęczyłem wczoraj te procedury. I jestem zachwycony!
    Plusy:
    - potrzebują mało zasobów
    Minusy:
    - trzeba uważać, żeby prawidłowo rozpisać wszystkie stany automatu

    Rozwiązanie zaproponowane w moim pierwszym poście (niestety nie padła odpowiedź czemu tak się ono zachowuje), drugie rozwiązanie tego Niemca oraz rozwiązanie zaproponowane przez Kolegę tmf są do siebie bliźniaczo podobne. W implementacji wydają się znacznie prostsze od tej maszyny stanów, jednak z drugiej strony potrzebują znacznie bardziej rozbudowanych funkcji do nawigacji po menu. Coś za coś. Na chwilę obecną zaproponowany przez Ciebie kod spełnia całkowicie moje oczekiwania. Znając życie niedługo sam dla siebie sprawdzę (dobrze opisane w książce) rozwiązanie tmf. Ale na chwilę obecną zostaję przy tym automacie. :)
    Poniżej kod tego co wczoraj spłodziłem:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Dokonałem zmian w funkcjach change_menu i display_menu. Przyjąłem koncepcję w której ekran wygląda następująco:
    [AVR] [AVR][C] - Jak efektywnie tworzyć rozbudowane menu wielopoziomowe?
    Zmiany w funkcjach miały na celu:
    - wprowadzenie migania edytowanej wartości,
    - eliminację zmiany wartości po wejsciu do menu edycji (previous_menu==current_menu).
    To tak na szybko:) Cała struktura menu już ładnie mi śmiga. Teraz linkuję odpowiednie funkcje do edycji poszczególnych wartości.
  • Pomocny post
    #9 11376572
    tymon_x
    Poziom 30  
    Jak zobaczyłem tą tablicę, to aż ciary przeszły na plecach. Bardziej elegancko by wyglądała lista, modyfikować tablicę to jak koszmar z dzielnicy wiązów. Możesz zobaczyć technikę na zasadzie buttonów:
    Nieblokująca obsługa switchy - gotowiec ;-).

    I cała zabawa polega na wywołaniu:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    W menu najlepiej zrobić jako listę dwukierunkową góra/dół i lewo/prawo. To ma taką zaletę jak masz nowy ficzer w innym osobnym module, to wystarczy wywołać taką funkcję i masz nową pozycję w menu, a nie modyfikację mega tablicy i externowanie zasobów.
  • #10 11377212
    tmf
    VIP Zasłużony dla elektroda
    tymon_x: Oczywiście rozwiązanie jakie pokazałeś jest doskonałe, ma tylko jedną wadę w świecie AVR - lista musi znaleźć się w SRAM (bo inaczej nie będzie można jej modyfikować). Jest to dosyć istotna wada, bo SRAM na AVR za wiele nie ma.
  • #11 11377301
    tymon_x
    Poziom 30  
    tmf napisał:
    tymon_x: Oczywiście rozwiązanie jakie pokazałeś jest doskonałe, ma tylko jedną wadę w świecie AVR - lista musi znaleźć się w SRAM (bo inaczej nie będzie można jej modyfikować). Jest to dosyć istotna wada, bo SRAM na AVR za wiele nie ma.

    No tak, a jakby to obejść. Będzie to taki trochę high-level, stworzyć tymczasową strukturę w SRAM, przepisać ją do wolnego miejsca w FLASH (blank) na podstawie zarezerwowanego miejsca w linkerze, żeby się nie gryzło i usunąć z SRAM. W ramach pojedynczej inicjalizacji. Mam fantazję :)
  • #12 11377447
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Sprawę można obejść prościej - wystarczy zrobić coś na kształt sekcji inicjalizacyjnych z newliba - po prostu w każdym module masz stałą umieszczoną w odpowiedniej sekcji i linker sobie już to wmontuje gdzie należy. Funkcja od menu po prostu zakłada że opis menu znajduje się w danej sekcji. Prostsze niż to co proponujesz [;

    4\/3!!
  • #13 11377591
    tymon_x
    Poziom 30  
    Tylko, że dla każdego innego obiektu (innej struktury) byś musiał mieć inną sekcję, żeby wiedzieć co przeszukiwać, a mój pomysł polegał trzymaniu różnych obiektów, i elastycznym łączeniu w listę. Bo nie widzę jak wskaźniki prev i next w strukturach mają wiedzieć o innych strukturach, jeśli wszystko będzie pakowane w sekcje ? Chyba wiesz co mi chodzi (albo i nie) :)
  • #14 11377698
    Freddie Chopin
    Specjalista - Mikrokontrolery
    Przecież Twoja funkcja Button_add(Button_create(), pin_0, button_n, NULL); też niewiele wie o innych elementach...

    Jedyne czego by się wtedy nie dało zrobić to porządkowania listy - trzeba by więc dorzucić dodatkowy parametr definiujący kolejność. Oczywiście w przypadku zabawy z sekcjami inicjalizator miałby formę tablicy a nie listy.

    Zresztą - dyskusja jest bez sensu - jak brakuje RAM to trzeba wziąć coś większego [; Zresztą "za-externowanie" wszystkich nagłówków z elementami po to żeby w jednym pliku zbudować stałą tablicę/listę nie jest aż takim wielkim problemem przecież...

    W ARMach nie ma tego problemu [;

    4\/3!!
  • #15 11389162
    tehaceole

    Poziom 28  
    Witam Kolegów
    Przepraszam, że przez kilka dni nie zabierałem głosu w temacie, ale bylo to niezależne ode mnie (awaria neta).
    A więc tak:
    - Po pierwsze chcę serdecznie podziękować Koledze excray - wdrożyłem przytoczone przez Ciebie rozwiązanie i jestem zachwycony. Oczywiście przeniosłem całe menu do pamięci programu. Stąd zmianie uległy procedury obsługi menu, które aktualnie wyglądają tak:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Zmodyfikowałem również strukturę opisująca daną pozycję menu. Dodałem pola definiujące zachowanie klawiszy (częstotliwość samopowtarzania):
    Kod: text
    Zaloguj się, aby zobaczyć kod

    I teraz dla przykładu menu o następującej strukturze:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Po rozpisaniu stanów autoamtu jako enumy:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Ma następującą postać po zakodowaniu w tablicy:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Teraz w pętli głównej wywołuję sobie wygodnie:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Gdzie funkcje generujące eventy z klawiszy wyglądają tak:
    Kod: text
    Zaloguj się, aby zobaczyć kod


    Koncepcja działania menu jest taka jak na rysunku z postu #8:
    - jezeli dana pozycja menu ma submenu to klikniecie ok powoduje wejście do submenu
    - jezeli dana pozycja posiada wartość do edycji to klikniecie ok na tej pozycji powoduje zaprzestanie wyświetlania w drugiej linii kolejnej pozycji menu, zamiast tego wyświetlana jest wartość edytowana.

    Ponieważ trochu tych wartości do zmiany miałem, musiałem napisać funkcje które umożliwią łatwą zmianę 1,2,3,4 wartości na jednym ekranie. Założenie było takie:
    - aktualnie edytowana wartość otoczona jest z obu stron zdefiniowanymi znakami (u mnie są to ">" i "<").
    - aktualnie edytowana wartość miga
    - pomiędzy wartości mogę wstawiać zdefiniowany znak (np ":" przy edycji czasu)
    - edytowana wartość moze byc wyswietlana z zadaną ilością miejsc po przecinku
    Na chwilę obecną wygląda to tak:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    i teraz wywoływanie tych potworków w konkretnych menu:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Jak widać wywołując te bloczki można od razu zdefiniować zachowanie klawisza next (u mnie down):
    - jeżeli w submenu jest tylko jedna wartość do zmiany to klawisz służy do jej dekrementacji
    - jeżeli submenu zawiera kilka wartości do zmiany to klawisz służy do przełączania się pomiędzy nimi

    Tak przy okazji to zmuszony byłem rozszerzyć swoje funkcje obsługi RTC PCF8583 o obsługę daty. Jak wiadomo zawsze w tym PCF są problemy z obsługą dnia tygodnia i roku. Ja zrealizowałem to tak:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Nie wrzucam tu kodów obsługi samego i2c ponieważ w większości pochodzą one z netu i są ogólnodostępne.
    Założenie było takie, że wartość roku (np. 12 dla 2012) przechowuję w pamięci RAM pcfa, natomiast zmianę roku (podczas normalnego odliczania czasu) dokonuję przez odczytywanie wartosci roku (0-3) z rejestru roku pcfa.
    Z kolei numer dnia tygodnia zrealizowany jest całkowicie z pominięciem pcfa - wykorzystałem tu algorytm wiecznego kalendarza Zellera.

    Od razu mówię: specjalnie co sekundę odczytuję z PCFa czas i datę - chcę eksperymentalnie stwierdzić jak będzie to wpływać na jego pracę.

    W wolnej chwili zrobię filmik prezentujący pracę tego menu.
    Jeszcze raz dziękuję Koledze excray.

    Teraz kilka słów do Kolegi tymon_x - przeanalizowałem Twoją funkcję obsługi klawiszy. Stwierdzić muszę, że jest cwanie napisana. To już bardzo wysoki poziom abstrakcji. O ile wydawało mi się, że rozumiem jej działanie o tyle wysiadłem przy zapisach:
    Kod: text
    Zaloguj się, aby zobaczyć kod

    Po jakiego czorta funkcje te zwracają liczbę pseudolosową?
  • Pomocny post
    #16 11389595
    tymon_x
    Poziom 30  
    tehaceole napisał:
    Po jakiego czorta funkcje te zwracają liczbę pseudolosową?

    Post wyjaśniający

    tymon_x napisał:
    Kod pod PC często tak testuje i przenoszę do uC. Testowanie na mikrokontrolerze jest mało efektywne, zważywszy na brak technik typu Code Coverage czy Function Coverage. Albo ciekawsze testy jak symulacja zachowania EEPROM dla algorytmu emulowania EEPROM w Flash, jak błędy podczas kasowania, zanik zasilania i inne przyjemności. Tam był przykład generowania zakłóceń w sposób prostacki {0, 1};

    Jedyną jego rolą była symulacja napierniczania klawisza, tylko że można to odpalić po stronie PC (w tym wypadku Linux) i sprawdzić czy poprawienie działa debounce i zmieniają się stany przycisku ( low -> rising -> high -> falling -> low -> ... ) i co wypluwa do konsoli. I zwraca losowo 1 albo 0 :)

    To i tak niski poziom abstrakcji, lepiej to by wyglądało w C++ :)
  • #17 11389685
    tehaceole

    Poziom 28  
    tymon_x napisał:
    I zwraca losowo 1 albo 0
    Tu akurat moje przejęzyczenie z tą liczbą psęudolosową. Dzięki za wyczerpujące wyjaśnienie. W sumie to ten sposób testowania nawet do głowy mi nie przyszedł... Ale cóż - język C nie jest w żaden sposób zależny od platformy. Wystarczy podmiana funkcji niskopoziomowych i już jakąś tam opracowaną przez siebie bibliotekę odpalasz na innym sprzęcie.
REKLAMA