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

[Atmega16][C] Menu - obsługa przycisków

GOWOMB 23 Lis 2008 16:07 11354 13
REKLAMA
  • #1 5774109
    GOWOMB
    Poziom 13  
    Witam,

    od kilku dni próbuje zrobić menu na wyświetlaczu LCD bez większego powodzenia, chciał bym zrobić kilka tekstów i po każdym nacisnięciu przycisku żeby się zmieniały. chciałem to zrobić za pomocą funkcji switch ale ogólnie pierwszy problem to:
    * nie do końca wiem jak w programie ustawić porty żeby działały jak wejścia oraz podciągnąć je do logicznej jedynki
    drugu problem:
    to to że coś pewnie pokręciłem w zamieszczonym niżej kodzie bo mniej więcej - bardziej mniej program działa ale działa tek że na wyświetlaczu pojawia się tekst tylko jak trzymam switch-a i nie zmienia się po koleji na
    1.xxx
    2.xxx
    3.xxx
    tylko losowo jak mu się chce

    od kilku dni szukam na forum i narazie udało mi sie to doprowadzić do takiej postaci

    #include <avr/io.h>
    #include <inttypes.h>
    #include <bity.h>
    #include <util/delay.h>
    #include <hd44780.c>
    
    
    #define KEY_PIN 0
    #define KEY (1<<KEY_PIN)           
    
    
    int main(void)
    {
    
    DDRC &= ~KEY;  
    PORTC |= KEY;
    
    int M=0;
    LCD_Initalize();
    LCD_Clear();
    LCD_WriteText("tutaj bedzie");
    LCD_GoTo(0,1);
    LCD_WriteText("jakis tekst");
    _delay_ms(300);
    
    while (1)
    {
    if ( !(PINA&KEY) )
    	{
    	M++;
    	if(M==3) M=0;
    	
    	switch(M)
    		{
    		case 0:            
    			LCD_Clear();					  	
    			LCD_WriteText("1.xxx");
    			break;
    				
    		case 1:
    			LCD_Clear();					  	
    			LCD_WriteText("2.Pomiar");
    			break;
    
    		case 2:            
    			LCD_Clear();					  	
    			LCD_WriteText("3.Pomiar");
    			break;
    		}		
    	}	
    }
    return 0;
    }
  • REKLAMA
  • Pomocny post
    #2 5774161
    BoskiDialer
    Poziom 34  
    co do wejścia z pullup: kasujesz odpowiedni bit w DDR (ustawiasz jako wejście), a odpowiedni bit w PORT ustawiasz.
    co do kodu: losowo? może to być spowodowane przez 2 rzeczy: po pierwsze nie masz żadnego eliminowania drgania styków (jeśli takowe mogło by się pojawić), po drugie sprawdzasz nie przejście 1->0 tylko sprawdzasz, czy przycisk jest naciśnięty - ponieważ cały czas jest, cały czas przechodzisz po menu. Najprostsze rozwiązanie:
    while(1) 
    { 
      if(!( PINA&KEY ))
      {
        // opóźnienie
        _delay_ms(20);
        // ponowne sprawdzenie stanu przycisku - jeśli nadal jest naciśnięty, wykonaj kod
        if(!( PINA&KEY ))
        {
    // [...]
          // oczekiwanie na zwolnienie przycisku
          while(!( PINA&KEY ))
            ;
        }
      }   
    }
  • REKLAMA
  • #4 5774269
    GOWOMB
    Poziom 13  
    BoskiDialer - dzięki poprawiłem i problem losowości znikną.

    zumek - tak jak mówiłem nie bardzo rozumiem ten kawałek z deklarowanie portów itd i znalazłem to gotowe na elce w którymś z postów no i prawie wszystko zmieniał na dobre oprócz nieszczęsnego PINA

    teraz wszystko ogólnie ładnie działa dzięki za pomoc

    Pozdrawiam
  • #5 5775536
    Dave_Masters
    Poziom 14  
    Pozwolę sobie dorzucić swoje 3 grosze ;)
    W przypadku menu wielopoziomowego przydałoby się znać możliwe przejścia miedzy poszczególnymi elementami menu. Można to zrobić jawnie jako osobną strukturę danych (tablicę/drzewo/listę) lub niejawnie zaszywając przejścia w numerze pozycji menu.

    
    
    //wyswietlanie menu
    switch (Menu_state)
    	{
    		case MAIN_SCREEN:
    		{
    			//wyswietl co trzeba			
    			break;
    		}
    		case MAIN_MENU:
    		{
                            //kolejne elementy...
    			break;
    		}
                    case MENU2:
    		{
                            //kolejne elementy...
    			break;
    		}
    


    Teraz w celu zdefiniowania dozwolonych przejść - MAIN_SCREEN<->MAIN_MENU<->MENU2
    można zdefiniować
    
    #define MAIN_SCREEN 0x00  
    #define MAIN_MENU 0x01
    #define MENU2	0x11
    #define MENU3	0x111
    #define MENU4   0x12
    


    Numery te mają taką właściwość, że menu podrzędne ma numer menu nadrzędnego * 0x10 + numer_pozycji_w_menu
    Czyli MAIN_SCREEN ma tylko 1 pozycję - MAIN_MENU, tam zaś są 2 pozycje MENU2 i MENU4 ( numerach odpowiednio 1 i 2).
    Powiedzmy, że mamy 2 przyciski - OK i ESC. OK wchodzi do menu, ESC - poziom wyżej.
    Teraz jeżeli chcemy wejść do menu to:
    
    switch(klawisz)
    { 
      case OK:
      { 
         Menu_state = <aktualny_numer_ekranu> * 0x10 + <numer_zaznaczonej_pozycji_menu>;
        break;
       }
      case ESC:
      {
         Menu_state = <aktualny_numer_ekranu> / 0x10;
         break;
      }
    }
    


    Do tego oczywiście zabezpieczenia przed wejściem zbyt głęboko w menu. Wyjście "ponad" menu nie jest możliwe jeżeli główny ekran ma nr 0x00. Bo 0x00 / 0x10 wciąż daje zero ;)

    Zalety takiego rozwiązania:
    -Uproszczenie rozbudowy menu
    -Oszczędność pamięci - brak jeszcze jednej struktury danych
    -Przejrzystość

    Wady:
    -Ograniczona głębokość menu - dla typu uint16_t jest to 5 poziomów -
    ekran główny + 4 poziomy menu.
    -Max 15 pozycji menu
  • REKLAMA
  • #6 6849249
    Myrek1
    Poziom 23  
    Witam.
    A ja zastanawiam się nad menu z parametrami działającym w czasie pracy uC. Załóżmy, że mamy sterownik na Atmedze 16 i steruje sobie tam czymś, robi pomiary itp - ot normalna praca. Teraz chcemy wejść sobie w menu i przestawić parametry, ale podczas przebywania w menu sterownik dalej ma normalnie działać. Menu ma być na LCD, na którym w czasie rzeczywistym wyświetlany jest stan pracy (pomiary).
    Jaki macie pomysł na to w uC? Wątki odpadają.

    Mam taki pomysł, nad którym myślałem całe 2 minuty.
    Mamy pętle główną ze wszystkimi funkcjami np funkcje pomiaru, ustawień , sondowania wciśniętych przycisków itp i tak w kółko. W pewnym momencie wciskamy przycisk, jakaś tam flaga zostaje ustawiona. W pętli jest funkcja, która sprawdza klawisze.

    Ja myślę, by zrobić to tak, że w funkcji odświeżającej LCD zmieścić w instrukcji switch() poszczególne ekrany menu czy pomiarów. W jakiejś zmiennej globalnej ustawiać (za pomocą klawiszy) daną, która reprezentuje poszczególne ekrany menu.
    Ten pomysł jest trochę dziwny, macie może lepszy?
    Bo wszystko ok, jest wyświetlone menu, ale co ze zmianą parametrów? Trzeba by wrzucić do main() następną funkcję, która w zależności od zawartości flagi pozwala na poruszanie się po menu w jakiś tam sposób.
    Będzie działać, ale wydaje mi się to trochę mało "uniwersalne".

    Może macie lepsze pomysły?
  • #7 6849489
    m.bartczak
    Poziom 16  
    Pewnie że mamy ;).

    Użyj przerwań.

    Najprościej jest zrobić całość 'głównej' części pracy na przerwaniach w następujący sposób:

    a) Pomiary na timerze 0, wywoływane cyklicznie
    b) Obsługa przycisków na przerwaniu int0, wystarczy że podepniesz odpowiednio przyciski do pinu int0 i AtMega automagicznie wywoła przerwanie gdy tylko wykryta zostanie zmiana stanu pinu. Ważne - trzeba ustawić bity ISC01 i ISC00 na wartości odpowiednio 0 i 1 - AtMega wywoła wtedy przerwanie INT0 w wypadku zmiany wartości sygnału logicznego na pinie int0. Podepnij wszystkie przycisko do tego pinu *oraz* do pinów sprawdzających - wtedy po wywołaniu przerwania sprawdzasz co było naciśnięte.
    c) Obsługa LCD np. w głównym wątku, albo za pomocą innego przerwania cyklicznego - do wyboru.

    Nie potrzebujesz wątków - AtMega ma mnóstwo fajnych przerwań :) - ale w sumie też nie ma powodu się ich bać - jeśli jest taka potrzeba to jest mnóstwo mikrokerneli dla AVR'ów, które pozwalają na wykorzystanie wątków bez bólu - przykładem może być np. Femto OS

    PS: sporo rzeczy możesz wykorzytstać (oraz nauczyć się przeglądając kod) używając biblioteki Procyon AVRlib
  • REKLAMA
  • #8 6849742
    Myrek1
    Poziom 23  
    Oczywiście, wiem, że tak można zrobić. Ale to wg mnie tonie dobre. Przerwania powinno się obsługiwać tylko do ustawienia flag, czy zasygnalizowania pewnego działania. Pomyśl, co wtedy, jeśli wysyłamy coś do LCD, gdzie liczy się czas a w przerwaniu coś dostaniemy i zaczniemy to obsługiwać. Skończymy, wyskoczymy z przerwania, a LCD przestanie działać bo upłynął czas odpowiedzi. Przykładem jest obłucga np LCD graficznego oraz 1-wire czy wysyłanie w przerwaniu danej przez UART.

    Podsumowując, wg mnie w przerwaniach warto ustawiać zmienne, czy dać znać, że trzeba np wysłać znak na UART, a samo wysyłanie zrobić osobno (zajmuje to wtedy minimalną ilość cykli). Wtedy minimalizuje się wpływ dziwnych zachowań i krytycznych czasów na pracę układu.
  • #9 6850180
    m.bartczak
    Poziom 16  
    Osobiście używam instrukcji CLI i SEI w głównej pętli programu gdy potrzebuję mieć coś 'ekskluzywnie' i to wystarcza. Trzeba tylko pamiętać że podczas wyłączonch przerwań nowe zdarzenia 'nie przychodzą' dopóki ich nie włączymy, ale jeśli jakieś się 'przydarzy w międzyczasie' to zostanie obsłużone zaraz po włączeniu przerwań.

    Ważna informacja - podczas wystąpienia przerwania automatycznie wykonywana jest instrukcja CLI. Dopóki nie umożliwimy 'zagnieżdżania' przerwań, nie da się ich normalnie przerwać w trakcie wykonywania. Dzięki temu jeżeli przerwanie trwa dłużej niż jego częstotliwość wywołania to cała aplikacja się nie wykłada.

    Cytując dokumentację:

    "When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled.
    The user software can write logic one to the I-bit to enable nested interrupts. All enabled
    interrupts can then interrupt the current interrupt routine. The I-bit is automatically set when a
    Return from Interrupt instruction – RETI – is executed."

    Tłumaczenie:

    "Gdy następuje przerwanie, bit Globalne Przerwania Włączone (I) jest ustawiany na 0, i wszystkie przerwania są wyłączane.
    Użytkownik może zapisać 1 do tego bitu aby włączyć 'zagnieżdżanie' przerwań. Wszystkie włączone przerwania mogą wtedy przerwać wykonywanie aktualnej procedury przerwania. Bit I jest ustawiany automatycznie podczas wykonywania instrukcji RETI"
  • #10 6850251
    BoskiDialer
    Poziom 34  
    Bez przerwań, z aktualizacją w czasie rzeczywistym opisów pozycji menu, dowolny poziom komplikacji menu można rozwiązać za pomocą dwóch prostych rzeczy: wskaźnika i wskaźnika na funkcję.
    Każda pozycja menu powinna zostać zapisana jako jedna struktura. Struktura taka posiadała by 3 wskaźniki na inne struktury tego samego typu (pozycja nadrzędna (tj powrót do poprzedniego menu), poprzednia pozycja (do góry), następna pozycja (w dół)). Dodatkowo struktura posiadała by 2 wskaźniki na funkcje: pierwsza funkcja służyła by do wpisywania do tablicy znaków przekazanej przez argument aktualnego opisu pozycji w menu [możliwość aktualizowania opisu na bieżąco], druga funkcja była by wywoływana podczas wybrania danej pozycji w menu (funkcja mogła by przechodzić do podmenu, robić jakąś akcję etc - opcjonalnie dodać czwarty wskaźnik na strukturę aby odciążyć funkcję od wchodzenia do podmenu). Odpowiednio łącząc wszystko wskaźnikami oraz posiadając globalne dwa wskaźniki: na początkową pozycję całego menu oraz na aktualną pozycję można już bez dodatkowych zmiennych stanu przechodzić po menu bez blokowania reszty kodu. Co np 20ms odbierać stan przycisków, pobierać opisu pozycji menu wyświetlać i powrót do głównego zadania.

    Kiedyś myślałem nad zaimplementowaniem takiego menu ale projekt porzuciłem, więc nie mam przykładowych kodów.

    Jeśli menu i kod mają się nie blokować, muszą przechowywać cały stan w zmiennych, nie jest dopuszczalnym siedzenie w pętli oczekiwania (tutaj wskaźnik programu staje się częścią stanu - wskazując na instrukcje pętli informuje, że należy czekać) etc...
  • #11 6850743
    m.bartczak
    Poziom 16  
    Pozwalam sobie zamieścić 'żyjący' przykład korzystający z przerwań:

    Zakładam że zegar w mojej atmedze ma 16MHz :)

    Program na przerwaniach timera0 robi 'pracę' programu, konkretnie konwersję A/D z portu A0, i robi to około 60 razy na sekundę

    Na przerwaniach timer1 odświeżany jest ekran LCD (przerwanie wywoływane 30 razy na sekundę)

    Na porcie A znajduje się też wyświetlacz LCD zgodny z HD44780: bit 2 = RS, bit 3 = E, bity 4-7 = dane.

    Na porcie B, bit 0 znajduje się przycisk (stan 'wciśnięty' = zwarcie do masy)

    W głównej pętli programu znajduje się obsługa samego menu.

    Program odpalałem na module uruchomieniowym AtMEGA16 GoTronik. Działa :)
    Załączniki:
  • #12 6850863
    BoskiDialer
    Poziom 34  
    Zacząłem pisać kod do menu w oparciu o listy/wskaźniki, kod się nieco rozbudował względem pierwotnego opisu. Jest to tylko wersja demonstracyjna - nie jest zoptymalizowany, zawiera wywołania ciężkich funkcji (printf, sprintf). Dzięki dodaniu funkcji przechwytującej zdarzenia - do każdej pozycji menu może być inna - kod zyskał na elastyczności co widać po przykładach. Wszystko pracuje w jednym wątku, bez przerwań a mimo to menu i zadania w tle (tutaj zwiększanie zmiennej) nie blokują się wzajemnie. Ze względu na moje lenistwo, kod demonstracyjny działa na konsoli, 'wyświetlacz' jest symulowany przez wypisywanie zawartości na konsolę, sterowanie 'wsad'.

    Kod w C, kompiluje się pod gcc.
    Załączniki:
  • #13 6851116
    m.bartczak
    Poziom 16  
    GOWOMB: Jak widać, ilu programistów tyle sposobów rozwiązania.

    Zarówno moja propozycja jak i Boskiego Dialera ma wady: proces 'robiący coś' nie ma gwarancji wykonania z odpowiednią częstotliwością.

    Jeżeli potrzebujesz systemu czasu rzeczywistego przerwania Cię raczej nie ominą, ale nie będzie Ci wolno zrobić nic 'nieistotnego' poza nimi - nieistotne w tym wypadku jest np. menu i obsługa LCD.
  • #14 6851183
    BoskiDialer
    Poziom 34  
    m.bartczak: W moim przykładzie obsługa samego menu jest pomijalnie krótka - Pi(1) (nie licząc kopiowania ciągów znaków) - jeśli jednak mają być wykonywane dwie czynności z czego jedna ma reagować natychmiastowo (lub z małym marginesem czasowym) na jakieś zdarzenie, to i tak nie obejdzie się bez przerwań (chyba że testowanie warunków zdarzenia po każdej instrukcji komuś odpowiada).
REKLAMA