środa, 6 kwietnia 2011

Zaawansowana obsługa klawiatury ARM, AVR, PIC i inne

Autor: Deucalion
Redakcja: Dondu

Artykuł jest częścią cyklu: Kurs ARM: Spis treści


Tym razem artykuł prezentujący kod sterownika w języku C, służący do zaawansowanej obsługi klawiatury na praktycznie dowolny mikrokontroler wraz z przykładem dla LPC1114.

Oprócz zamieszczenia kodów źródłowych, szczegółowo zostały opisane funkcje oraz przedstawione zostały przykłady jak z tych funkcji korzystać.

Możliwości sterownika:
  • debouncing czyli eliminacja drgań styków,
  • dynamiczna autorepetycja z możliwością blokady i zmiany międzyczasów,
  • dowolny typ klawiatury – pojedyncze klawisze, klawiatura matrycowa,
  • obsługa naciśnięć kombinacji klawiszy,
  • pomiar czasu wciśnięcia klawisza lub kombinacji,
  • implementacja niezależna od typu procesora. Można więc dostosować niniejszą bibliotekę także do innych mikrokontrolerów np. AVR, PIC, itp.


Wszelkie pytania dot. tej biblioteki możesz zadawać na forum w temacie:
MegaSuperDebounce - Biblioteka zaawansowanej obsługi przycisków AVR, ARM PIC ...



Biblioteka

Poniżej pliki keyb.h i keyb.c z programem sterownika klawiatury (znajdziesz je także w spakowanym pliku na końcu artykułu).

MegaSuperDebounce ™


keyb.h


keyb.c



Definiowanie klawiszy

Do przedstawionego kodu należy dopisać własną wersję funkcji unsigned int GetKeybSample( void ). Funkcja ma zwracać stan klawiszy, np. jeśli klawiatura posiada 4 klawisze, to ich stan ma znajdować się w zwracanej wartości i wciśnięte klawisze mają reprezentować ustawione bity. Nie ma znaczenia, który bit będzie reprezentował dany klawisz.

Inną czynnością jest przyporządkowanie określonym bitom odpowiednich klawiszy w pliku keyb.h. Dla przykładu przypuśćmy, że klawisze znajdują się na pinach PD1, PD3, PD6, PD7 i tym klawiszom przypisane są następujące funkcje PD1 - KEY_UP, PD3 – KEY_ENTER, PD6 – KEY_DOWN, PD7 – KEY_ESC. Nasze deklaracje powinny wyglądać w ten sposób:

Tych deklaracji będziemy używać w naszym programie. W tym przypadku funkcja GetKeybSample( ) może wyglądać w ten sposób:


Kolejną czynnością jaką musimy zrobić to wywoływać gdzieś w programie funkcję KeybProc() dokładnie co 10ms. Można to zrobić z przerwania lub gdzieś w głównej pętli programu – pełna dowolność. Jeśli zdecydujemy się na wywoływanie z przerwania należy wziąć pod uwagę dwa fragmenty kody z tej biblioteki (opis w komentarzach), które muszą być wykonywane atomowo, czyli nieprzerywalnie oraz funkcja GetKeybSample() powinna wykonywać się możliwie jak najszybciej. To wszystko co trzeba na ten moment zrobić, aby móc korzystać w programie z tej biblioteki. Teraz kilka przykładów jak jej używać.



1. Reakcja na naciśnięcie dowolnego klawisza



Funkcja GetKeys() zwraca stan klawiatury, ale tylko co czas określony w tabeli czasów autorepetycji, czyli standardowo po naciśnięciu przycisku po 30ms, 1030ms, 1530ms, 2030ms, 2530ms, 3030ms, 3130ms, 3230ms, 3330ms itd. co kolejne 100ms, aż do jakiejkolwiek zmiany stanu klawiatury.



2. Reakcja na naciśnięcie wybranego klawisza



3. Reakcja na naciśnięcie dowolnego z dwóch klawiszy lub ich kombinacji



4. Reakcja w zależności od wciśniętego przycisku lub ich kombinacji


Przy braku możliwości wyłączenia autorepetycji i dłuższym przytrzymaniu klawisza mogłoby dojść do sytuacji gdzie stracilibyśmy kontrole nad urządzeniem.
Co zrobić, jeśli nie chcemy aby w danym momencie działa autorepetycja, np. kolejne naciśnięcia klawisza KEY_ENTER powodują wejście i wyjście z menu lub kolejne wybieranie podmenu? W takim przypadku należy skorzystać z funkcji ClrKeyb().

Funkcja jako parametr przyjmuje dwie zdeklarowane wartości KBD_LOCK i KBD_NOLOCK. KBD_LOCK powoduje zablokowanie klawiatury, aż do momentu zwolnienia wszystkich klawiszy i analogicznie KBD_NOLOCK nie powoduje takiego zablokowania, wszystko zaczyna się od nowa, tak jak od naciśnięcia klawisza.

Funkcja ClrKeyb() w ogólnej postaci służy do resetowania klawiatury. Może się tak zdarzyć, że program będzie wykonywał czasochłonną operację trwająca kilka lub więcej sekund. Trzymanie w tym czasie wciśniętego klawisza powoduje naliczanie czasów autorepetycji. Gdy program wznowi pobieranie stanu klawiatury może dojść do takiego zachowania jakby został wielokrotnie naciśnięty przycisk. Można temu zapobiec poprzez zresetowanie klawiatury po wykonaniu takiej czasochłonnej operacji. Użycie funkcji ClrKeyb( KBD_LOCK) w takiej postaci powoduje również zatrzymanie autorepetycji.



5. Reakcja w zależności od wciśniętego przycisku lub ich kombinacji z zatrzymaniem autorepetycji na wybranych klawiszach.



6. Sprawdzenie czy naciśnięty dowolny klawisz niezależnie od czasów autorepetycji, ale z uwzględnieniem eliminacji drgań styków.


Funkcja unsigned int IsKey( unsigned int mask ) zwraca zamaskowany stan klawiszy. Jako parametr przyjmuje maskę, która przysłania stany nieistotnych klawiszy. Jeśli wywołamy funkcję w postaci IsKey( KEY_ENTER ) to funkcja zwróci tyko stan klawisza KEY_ENTER i jeśli klawisz ten będzie wciśnięty zostanie również zwrócona wartość KEY_ENTER. Jeśli chcemy sprawdzić stan kilku klawiszy wywołujemy tą funkcję podając ich nazwy: IsKey( KEY_ENTER | KEY_ESC )



7. Sprawdzenie czy naciśnięty którykolwiek ze wskazanych klawiszy niezależnie od czasów autorepetycji, ale z uwzględnieniem eliminacji drgań styków.


Funkcja unsigned int IsKeyPressed( unsigned int mask ) działa podobnie do funkcji IsKey() z tą tylko różnicą, że nie jest uwzględniony czas drgań styków tzn. funkcja zwraca stan klawiszy zgodny z podaną maską, jeśli w tym czasie będą drgania styków to funkcja również je przeniesie do programu.



8. Reakcja programu w zależności od czasu naciskania klawisza.


Wersja druga:


Funkcja unsigned int KeysTime( void ) zwraca czas jaki upłynął od ostatniej zmiany na klawiaturze, jeśli żaden klawisz nie jest wciśnięty funkcja zwraca 0. Jednostką czasu jest 10ms czyli jeśli chcemy sprawdzić czy klawisz jest wciśnięty dłużej niż 1s zapisujemy to w taki oto sposób:


Autorepetycja jest to funkcjonalność, która symuluje wciskanie klawisza co określony czas jeśli klawisz jest cały czas wciśnięty. Dzięki temu mamy możliwość „wciśnięcia” klawisza kilka, kilkanaście a nawet kilkadziesiąt razy w ciągu sekundy. Opcja ta przydaje się gdy mamy np. do przestawienia jakąś wartość o kilkadziesiąt lub kilkaset jednostek za pomocą tylko dwóch klawiszy. Odstępy czasów mogą być stałe lub zmieniać się w czasie.

W przypadku tej biblioteki do obsługi klawiatury mamy sporą dowolność. Sterownik posiada domyślne odstępy czasów, ale można również w trakcie programu je zmieniać. Po naciśnięciu klawisza, domyślnie za pomocą funkcji GetKeys() kody klawiszy udostępniane są po 30ms, 1030ms, 1530ms, 2030ms, 2530ms, 3030ms, 3130ms, 3230ms, 3330ms itd. co kolejne 100ms, aż do jakiejkolwiek zmiany stanu klawiatury. Za pomocą funkcji void KeybSetAutoRepeatTimes( unsigned short * AutoRepeatTab ) można sterownikowi dostarczyć tablicę opisującą inną sekwencję odstępów czasów.

Przykładowa tablica:

i przekazanie jej do sterownika:

Efektem w/w przykładu będzie symulacja naciśnięć klawisza w czasach 30ms, 530ms, 560ms, 590ms itd. co 30ms, co da nam około 33 pseudo naciśnięcia na sekundę. Tablica może mieć dowolną ilość odstępów czasów nie mniejszą niż 2. Pierwsza, przedostatnia i ostatnia pozycja mają specyficzne znaczenie. Pierwsza nie powinna być mniejsza niż 3 ( czyli 30ms), jest to czas na eliminację drgań styków. Ostatnia musi mieć wartość 0 i sygnalizuje koniec tablicy czasów. Natomiast wartość przedostatniej jest czasem autorepetycji nieskończonej. Należy pamiętać, że jednostką czasu w tablicy jest 10ms.


Podsumowanie

Aby móc zastosować tą bibliotekę w swoim projekcie należy:
  • skopiować i dołączyć do drzewa projektu pliki keyb.h i keyb.c,
  • utworzyć odpowiednie deklaracje w pliku keyb.h lub dołączyć własny plik z takimi deklaracjami,
  • napisać własną wersję funkcji GetKeybSample( ), która jest pomostem między sterownikiem a sprzętem,
  • umieścić w programie wywołanie funkcji KeybProc(). Funkcja musi być wywoływana co 10ms.


Wszelkie pytania dot. tej biblioteki możesz zadawać na forum w temacie:
MegaSuperDebounce - Biblioteka zaawansowanej obsługi przycisków AVR, ARM PIC ...





Przykład implementacji dla LPC1114.

Poniżej przykładowy film prezentujący działanie tego sterownika klawiatury oraz cały projekt dla procesora LPC1114 utworzony na bazie kursu o ARM.

Plik: Keyb.zip









Podziękowania

Ode mnie podziękowania za podzielenie się biblioteką z początkującymi. Dzięki takim osobom jak Deucalion mają oni znacznie łatwiej.  Mam nadzieję, że korzystający z tej biblioteki mega superdebounce przyłączą się i miłym słowem skomentują dobry uczynek kolegi :D :D

21 komentarzy:

  1. No faktycznie wygląda nieźle. Chyba dzisiejszy wieczór spędzę na testowaniu na jakimś AVR. Dzięki!

    OdpowiedzUsuń
  2. Witam. Przyznam że do tej pory pisałem bardziej skomplikowany program by osiągnąć podobne efekty. W twojej bibliotece podoba mi się ta uniwersalność przez co łatwo przerobić na inny mikrokontroler tej samej rodziny - ja pracuję tylko na AVR. Fajne, naprawdę fajne. Pozdrawiam

    OdpowiedzUsuń
  3. Witam. Ta wersja funkcjonuje już od około pół roku, wcześniej też używałem bardziej złożonej wersji, ale działającej na tej samej zasadzie. Z takiego podejścia do obsługi klawiatury korzystam już od kilku lat na różnych procesorach (ARM, AVR, Zilog) i dzięki temu w każdym kolejnym projekcie nie muszę się zastanawiać nad wszystkim co jest związane z obsługą klawiatury. Zazwyczaj implementacja polega na Ctrl+V Ctrl+C plus ta cała wymagana otoczka opisana w podsumowaniu.
    Zapraszam więc do testowania i dzielenia się uwagami. Pozdrawiam

    OdpowiedzUsuń
  4. Witam, jestem początkujący w temacie i możliwość skorzystania z takiej biblioteki to nieoceniona pomoc.
    Zrobiłem swój pierwszy projekt w którym obsługę jednego klawisza realizuje dedykowana funkcja.W drugiej wersji chciałem wprowadzić modyfikację polegającą na użyciu twojej biblioteki, ale brakuje funkcji realizującej obsługę jednego klawisza z rozróżnieniem: krótkie naciśnięcie i naciśnięcie długie z auto repetycją. Ładnie to działa jeżeli mamy dwa przyciski do dyspozycji, ale jak to zrobić na jednym ?.
    Przykład:
    if( IsKey( KEY))
    {
    // Kod wykonywany za każdym razem gdy wciśnięty jest klawisz z uwzględnieniem drgań styków co jest w moim projekcie wadą, aby to usunąć trzeba wywołać funkcję ClrKeyb( KBD_LOCK );, ale wówczas nigdy nie zostanie spełniony 2 warunek
    }
    if( IsKey( KEY ) && KeysTime( ) >= 100 )
    {
    // Tutaj kod reakcji na naciśniecie klawisza KEY
    Kod wykona się tylko wtedy gdy czas trzymania klawisza przekroczy 1s
    }
    Czy jest sposób aby taką obsługę zrobić z tą biblioteką ?
    Pozdrawiam,

    OdpowiedzUsuń
  5. O coś takiego chodziło?

    staic char _key,
    static unsigned int _key_time;

    if( IsKey( KEY ))
    {
    _key = 1;
    _key_time = KeysTime();
    }
    else if ( _key )
    {
    _key = 0;
    if( _key_time < 10 )
    {
    // Tutaj kod obsługi krótkiego naciśnięcia (t < 100ms)
    ....
    }
    }


    if( IsKey( KEY ) && KeysTime( ) >= 100 )
    {
    // Tutaj kod reakcji na naciśniecie klawisza KEY
    // Kod wykona się tylko wtedy gdy czas trzymania klawisza przekroczy 1s
    }

    OdpowiedzUsuń
  6. Bardzo fajny kurs. Kiedy następny odcinek.
    P.S. Nie mogę się doczekać kontynacji !

    OdpowiedzUsuń
  7. Dondu, popraw listing keyb.h bo jest niekompletny. Potem czytelnicy bezmyślnie kopiują i nie wiedzą co jest grane.

    OdpowiedzUsuń
    Odpowiedzi
    1. Porozmawiam z autorem, a na razie zapraszam na forum, gdzie jak sądzę pomogłeś pytającemu: [ATmega8][WinAVR] - biblioteka Zaawansowana obsługa klawiatury jak uruchomic.

      Dzięki Twoim uwagom pytający zrozumiał jak proste jest używanie tej biblioteki:
      cyt.: "działa! dzieki wielkie! w sumie to prosta sprawa :) "

      Dziękuję za pomoc,
      :-)

      Usuń
  8. Od kiedy GPL pozwala na limitowanie do zastosowań niekomercyjnych?

    OdpowiedzUsuń
  9. Czy komuś się udało wykorzystać tą bibliotekę do obsługi chińskiej matrycy 4x4 z 8 pinami?

    OdpowiedzUsuń
    Odpowiedzi
    1. A masz jakieś konkretny problem, czy tak tylko pytasz?
      Można ją nawet wykorzystać do obsługi klawiatury znajdującej się po drugiej stronie globu, kwestia tylko cyklicznego dostarczenia fizycznego stanu klawiszy. Wykorzystywalem ją do obsługi klawiatury pojemnosciowej wykonanej w oparciu o układ MPR121, więc na pewno da radę z jakąś tam chińska klawiaturą.

      Usuń
    2. Pytam bo nie mam pomysłu jak to zrobić, biblioteka z domysłu oczekuje odpowiedniego stanu na odpowiednim ustalonym pinie, a te klawiaturki matrycowe trzeba skanować zmieniając porty z wejść na wyjścia itp.

      Usuń
    3. Odczyt stanu klawiszy realizuj w jakimś przerwaniu i zapisuj do jednej zmiennej która będzie odczytywać ta biblioteka. Ustaw jedna kolumnę i wyjdź z przerwania. Podzczas następnej obsługi tego przerwania odczytaj wiersze i ustaw następna kolumnę itd. Wszystkie wiersze powinny być odczytane przed odczytem zmiennej przez bibliotekę.

      Usuń
  10. Niestety u mnie nie działa.

    dostaje komunikaty
    undefined reference to `GetKeys'
    i
    ld returned 1 exit status

    Jak mogę naprawić ten błąd.

    OdpowiedzUsuń
  11. Hej nie mogę sprawdzać stanu kilku klawiszy w funkcji.
    Jakby GetKeys() się blokowało lub działało tylko w funkcji main

    void submenu()
    {

    lcd_locate(0,0);
    lcd_str("tekst");
    while( 1 )
    {
    if(GetKeys() == KEY_UP)
    {
    lcd_str("key_up");
    }
    if(GetKeys() == KEY_ENTER)
    {
    lcd_str("key_enter");
    }
    }

    }

    OdpowiedzUsuń
  12. Hi, mam pytanie czy i w jaki sposób można zaadoptować tą bibliotekę do obsługi klawiszy które są podpięte pod różne porty tj np: trzy klawisze podpięte do portu B PB1, PB3, PB4 a czwarty klawisz do portu D - PD7 ?? Próbowałem na różne sposoby zmusić program do pracy ale nic mi nie wyszło (jeśli klawisze są na jednym porcie jest ok) ale ja potrzebuje mieć klawisze na różnych portach.
    Z góry dzięki za pomoc.

    OdpowiedzUsuń
  13. Niemam już nerwów do tego. Może mi ktoś powie co robie źle:

    #include
    #include "keyb.h"
    #define F_CPU 1000000 //ustawienie oscylatora na 1MHz
    #include //dołączenie biblioteki z przerwaniami

    int main(void)
    {
    //ustawienie wejśc/wyjść
    DDRC=0; //Piny na porcie C jako wejścia
    PORTC |= (1<<PC0) | (1<<PC1); //PC0 i PC1 jako wejscia do sterowania prawo-lewo
    DDRB = 0xFF; //PORTB jako wyjścia
    PORTB = 0xFF;

    //Ustawianie przerwań co 10ms
    TCCR0 |= (1<<CS02) | (1<<CS00); //ustawienie preskalera na 1024, źródło CLK, czyli standardowo przerwanie bedzie co 256ms (jeżeli nie ustawimy TCNT0)
    TIMSK |= 1<<TOIE0; //włączenie przerwania od przepełnienia licznika
    TCNT0 = 246; //ustawienie wartości początkowej
    sei(); //zezwolenie na przerwania

    while(1)
    {

    switch( GetKeys( ))
    {
    case KEY_UP:
    PORTB=~PORTB;
    break;
    case KEY_DOWN:
    PORTB=~PORTB;
    break;
    }


    }
    }


    ISR(TIMER0_OVF_vect) //początek funkcji obsługi przerwania
    {
    KeybProc();
    TCNT0 = 246; //ustawienie wartości początkowej
    }
    Dodam, że mam dodane do projektu keyb.c i keyb.h zgodnie z zaleceniami z powyższego linku z elektrody.

    OdpowiedzUsuń
    Odpowiedzi
    1. sory, że tak się rozciągnąłem, ale lubie przejrzystość w kodzie :)
      Dodam, że uC to ATmega8 a program piszę Atmel Studio 6.

      Usuń
    2. Nie napisałeś co Ci nie działa i co chciałeś osiągnąć.
      Załóż wątek na elektrodzie , dołącz cały kod i wklej tutaj link do tego wątku.

      Usuń
    3. http://www.elektroda.pl/rtvforum/viewtopic.php?p=14156636#14156636

      Usuń
  14. Szanowni blogowicze,
    Czy idzie w tej bibliotece zrobić tak żeby po wciśnięciu dwóch klawiszy jednoczesnie wykonała się instrukcja? Próbowałem tak:
    if( IsKey( KEY_ENTER & KEY_ESC ))

    ale nie działa.

    Pozdrawiam Kamil

    OdpowiedzUsuń