Obsługa prostej klawiatury

Pozostałe układy mikrokontrolerów, układy peryferyjne i inne, nie mieszczące się w powyższych kategoriach.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Obsługa prostej klawiatury

Postautor: gaweł » czwartek 18 maja 2017, 11:47

Koncepcja

kbd1_i00.jpg


Obecnie coraz więcej urządzeń elektronicznych budowanych jest w oparciu różnorodne mikrokontrolery. Są to funkcjonalnie bardzo uniwersalne elementy, na bazie których poprzez odpowiednie oprogramowanie sterujące pracą takiego mikrokontrolera możliwe jest zbudowanie dowolnego urządzenia. Jakakolwiek funkcja zostanie przyporządkowana budowanemu urządzeniu, zawsze w jego strukturze można wyodrębnić część odpowiedzialną za sterowanie jego działaniem. Przykładowym elementem pełniącym taką funkcję w klasycznych komputerach jest klawiatura. Podobnie, w każdym budowanym urządzeniu zawierającym mikrokontroler można wyodrębnić zestaw przycisków, za pomocą których istnieje możliwość wpływania na działanie urządzenia. Najprostszym rozwiązaniem sprzętowym jest przyłączenie do wyprowadzenia portu mikrokontrolera przycisku, którego naciśnięcie łączy go z potencjałem masy. W sytuacji, gdy przycisk nie jest naciśnięty na wejściu występuje stan wymuszany przez rezystor (rysunek 1).
kbd1_i01.png
Z punktu widzenia mikrokontrolera, pokazany przycisk jest zespołem cyfrowym, w którym stan naciśnięcia jest odczytywany z portu jako zero logiczne. W sytuacji, gdy przycisk nie jest naciśnięty, mikrokontroler na odpowiednim wejściu odczytuje stan logicznej jedynki. Ta prosta realizacja klawiatury ma jedną istotną wadę. Każde naciśnięcie (oraz zwolnienie) przycisku nie daje „czystego” sygnału. Z każdą zmianą stanu związane jest wygenerowanie ciągu impulsów. Ich liczba jest zależna od konstrukcji i rodzaju materiału z jakiego wykonany jest sam przycisk i właściwie należy ją uznać za losową (rysunek 2).
kbd1_i02.png
Ten występujący efekt nazywany jest dzwonieniem styków i trwa kilka milisekund. Z punktu widzenia człowieka ten czas jest niezauważalny, natomiast z punktu widzenia mikrokontrolera wygląda to odmiennie. Mając na uwadze, że przeciętny mikrokontroler wykonuje kilka milionów instrukcji na sekundę, to w czasie fazy związanej z dzwonieniem styków zostanie wykonanych kilkaset tysięcy instrukcji. Oznacza to, że każda zmiana stanu zaistniała w tej fazie przejściowej może zostać rozpoznana jako ciąg szybkich naciśnięć i zwolnień przycisku. Temu nieoczekiwanemu zjawisku można przeciwdziałać. Istnieje wiele rozwiązań pozwalających na jego eliminację. Najprostszym rozwiązaniem jest „przeczekanie” tej fazy. Sprowadza się to do próbkowania stanu generowanego przez przycisk z odpowiednią częstotliwością pozwalającą nie zauważać szybkich zmian stanu a dodatkowo traktować, że został osiągnięty stan stabilny w sytuacji, gdy w czasie kilu kolejnych próbkowań nie nastąpiła jego zmiana.
Rozpatrując tematykę dotyczącą mikrokontrolerów można zadać proste pytanie: co wspólnego ma programowanie mikrokontrolerów z elektroniką cyfrową? Okazuje się, że całkiem sporo. Należy pamiętać, że układy logiczne, cyfrowe mają znacząco dłuższą historii niż sztuka tworzenia oprogramowania dla mikrokontrolerów. Bardzo burzliwy rozwój w ostatnich czasach techniki mikroprocesorowej doprowadził do rozwoju dziedziny wiedzy dotyczącej sposobu budowania i tworzenia algorytmów. Algorytm to nic innego jako szczegółowy (nawet bardzo) przepis na postępowanie, który prowadzi do uzyskania określonej funkcjonalności lub efektu końcowego. Są one wszechobecne w naszym życiu a często wielu z nas nie zdaje sobie z tego sprawy. Wystarczy spojrzeć na przepisy kulinarne, które w rzeczywistości są zapisem algorytmu. Zawierają one informacje związane z: co, ile, w jakiej kolejności, w jaki sposób itp. prowadzące do uzyskania przykładowo pysznego deseru. To jest nic innego jak algorytm uzyskania deseru. Skoro wielu ludzi dzieli się między sobą algorytmami kulinarnymi, sądzę, że nic nie stoi na przeszkodzie do dzielenia się algorytmami trochę z bardziej inżynierskich działań.
Podglądając rozwiązania, jakich dopracowała się technika układów logicznych, można wiele z nich przenieść do świata związanym z programowaniem mikrokontrolerów. Taką inspiracją będącą dobrym wzorcem zapożyczonym z układów logicznych są automaty synchroniczne. To dość tajemnicze pojęcie spróbuję wyjaśnić na prostym przykładzie, jednak wcześniej należy wyjaśnić znaczenie przymiotnika „synchroniczny”. Oznacza on, że coś dzieje się w układzie w ściśle określonych chwilach czasu, niejako jednocześnie (czyli synchronicznie) z pewnym sygnałem sterującym, którym jest sygnał taktujący. Skoro jest to układ logiczny, to jego wyjścia będą zmieniać stan zgodnie (synchronicznie) z sygnałem zegarowym. Przykładem automatu może być dwubitowy licznik synchroniczny. Układy kombinacyjne wypracowywują nowy stan do zapisu w rejestrze. Wpis ten następuje w wyniku wystąpienia zbocza sygnału zegarowego. Całą akcję zmiany stanu na wyjściu rejestru wywołuje zdarzenie: właściwe zbocze sygnału zegarowego, po czym znowu następuje długa, błoga chwila ciszy i spokoju.
Analogią do sygnału taktującego jest realizacja instrukcji zawartych w pewnej funkcji. Tak jak w układach synchronicznych całość jest taktowana stałym sygnałem zegarowym, tak tutaj elementem pobudzającym wykonanie instrukcji zawartych w określonym algorytmie jest reakcja mikrokontrolera na przerwanie od zegara/licznika. Przy odpowiedniej konfiguracji tego zespołu można uzyskać stałe, równomierne pobudzanie. Ma to istotne znaczenie, gdyż obsługa prostej klawiatury jako zespołu przycisków jest uwarunkowana czasowo.
Przed prezentacją algorytmu ważnym elementem jest zrozumienie istotnej różnicy wynikającej z architektury mikrokontrolerów. Generalnie możliwe są dwa warianty, w oparciu o które są one budowane: architektura harwardzka (której przykładem są mikrokontrolery z rodziny ARV z modelem ATMEGA32) oraz architektura von Neumanna (której przykładem są mikrokontrolery z rodziny ARM z przykładowym modelem LPC2138). Różnica polega na stworzeniu różnych przestrzeni adresowych przewidzianych dla kodu programu oraz danych. W przypadku mikrokontrolerów AVR są dwie oddzielne przestrzenie: przestrzeń przeznaczona na kod programu (identyfikowana jako pamięć FLASH) oraz przestrzeń przeznaczoną na dane (zmienne programu, stos w programie w tym również zawierają się wszystkie rejestry sterujące i konfiguracyjne mikrokontrolera). Bezpośrednim skutkiem tego rozdzielenia przestrzeni jest oddzielny zestaw instrukcji maszynowych do pobierania danych z przestrzeni RAM (jak odczyt i zapis zawartości zmiennych) i przestrzeni FLASH (jak odczyt stałych umieszczonych w pamięci FLASH). W mikrokontrolerach opartych na architekturze von Neumanna również rozróżnia się pamięć na kod programu (pamięć FLASH) jak i pamięć zmiennych (umiejscowiona w RAM) jednak należą one do tej samej przestrzeni adresowej, czyli z punktu widzenia działania mikrokontrolera do odczytu zawartości zmiennej znajdującej się w pamięci RAM oraz do odczytu wartości stałej zapisanej w pamięci FLASH używane są dokładnie te same instrukcje maszynowe. Z kolei kompilatory języka C są „filozoficznie” przystosowane do architektury von Neumanna. Oznacza to, że kompilator nie „zna pojęcia rozdzielenia” przestrzeni adresowych i wygeneruje kod programu odpowiadający przypadkowi jakby zmienna była umieszczona w przestrzeni RAM. Z tego względu, w programie (dla procesorów o architekturze harwardzkiej → mikrokontrolery AVR) należy zastosować specjalne „triki”, które spowodują właściwą realizację kodu programu. W celu optymalizacji wykorzystania pamięci RAM w mikrokontrolerach (a pamięci RAM często jest mało), obszary stałych programu, a takim obszarem jest przykładowo obszar tablic do przekodowania stanu przycisków na generowane kody znaków, są umieszczone w przestrzeni pamięci programu (pamięci FLASH). Do „zmuszenia” kompilatora by dany obszar został umieszczony w przestrzeni programu służy kwalifikator PROGMEM (którego przykładowe użycie pokazuje poniższy fragment programu).

Kod: Zaznacz cały

static KeybEncoderRecT LongEncodeTabe [ LongEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , LongKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , LongKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , LongKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , LongKey3Code } ,
/* 04 */ { ( 1 << L0Key ) | ( 1 << L1Key ) , LongKey01Code } ,
/* 05 */ { ( 1 << L0Key ) | ( 1 << L2Key ) , LongKey02Code } ,
/* 06 */ { ( 1 << L0Key ) | ( 1 << L3Key ) , LongKey03Code } ,
/* 07 */ { ( 1 << L1Key ) | ( 1 << L2Key ) , LongKey12Code } ,
/* 08 */ { ( 1 << L1Key ) | ( 1 << L3Key ) , LongKey13Code } ,
/* 09 */ { ( 1 << L2Key ) | ( 1 << L3Key ) , LongKey23Code } ,
} ;
Implikuje to dodatkowo zastosowanie specjalizowanych funkcji przeznaczonych do sięgania do elementów tych tablic (odpowiednie funkcje są zawarte w standardowym module o nazwie pgmspace).
Tych problemów nie ma w przypadku architektury von Neumanna, gdyż to rozwiązanie jest wręcz naturalne dla kompilatora C. Oczywiście w tym przypadku również warto pokusić się o „optymalizację” zajętości pamięci RAM. Sprowadza się to do zastosowania specjalnego kwalifikatora przy deklaracji zmiennej. Oczywiście ma to również pewne implikacje przy używaniu takich zmiennych, jednak z racji jednolitej adresacji zmiennych w pamięci RAM jak i stałych obszarów w obrębie kodu programu, nie są potrzebne specjalizowane funkcje do sięganie do tych zmiennych.
By uzyskać dużą uniwersalność rozwiązania w oprogramowaniu, kod obsługi jest wyniesiony do oddzielnego pliku, który może być dołączony do programu jako „moduł biblioteczny”. Oczekiwane wymagania funkcjonalne w stosunku do oprogramowania modułu są określone w następujący sposób:
  • program rozpoznaje typowe naciśnięcie przycisku (w sensie czasu naciśnięcia),
  • program rozpoznaje „przedłużone” naciśnięcie przycisku (naciśnięcie i przytrzymanie naciśnięcia przez znacząco dłuższy czas),
  • program dopuszcza jednoczesne naciśnięcie dowolnego zestawu przycisków z typowym naciśnięciem (jednoczesne w kategorii człowieka jako użytkownika, bo z punktu widzenia elektroniki czas pomiędzy naciśnięciem pierwszego przycisku a drugiego to prawie „wieczność” oraz z praktycznego punktu widzenia warto ograniczyć się do pary przycisków, chociaż algorytm nie narzuca takiego ograniczenia),
  • program dopuszcza jednoczesne naciśnięcie dowolnej pary przycisków z przedłużonym czasem trwania naciśnięcia,
  • nie jest realizowana funkcja autorepetycji.
Właściwie rozwiązanie algorytmu jest niezależne od jakiejkolwiek platformy mikrokontrolera. Wymaga oczywiście pewnego sprzężenia z mikrokontrolerem, ale jest to uczynione maksymalnie elastycznie, co stanowi o dużym stopniu uniwersalności (realizacja operacji powiązanych ze sprzętem są wyniesione poza moduł i stanowią niejako element zewnętrzny). Modyfikując same funkcje związane z obsługą sprzętu i zmieniając wartości pewnych stałych, ten sam algorytm poprawnie został sprawdzony w środowisku mikrokontrolera AVR jak mikrokontrolera ARM (z rodziny LPC2000).
Rozwiązanie bazuje na realizacji wielostanowego automatu sterującego, takiego ekwiwalentu synchronicznych automatów cyfrowych. W fizycznych układach cyfrowych, synchroniczność automatu implikuje, że układ zmienia swoje stany z jakimś taktem zegarowym. To samo zagadnienie w ujęciu oprogramowania oznacza cykliczne wywoływanie funkcji obsługi. Jeżeli to wywołanie będzie pochodzić od przerwań generowanych przez licznik/zegar (chociaż nie jest to absolutnie konieczne, możliwe jest dowolne zorganizowanie cykliczności), to uzyskuje się rozwiązaniu w pełni autonomiczne. Uwzględniając powyższe założenia, bazując na klawiaturze 4-przyciskowej (przyciski identyfikowane jako KEY0 .. KEY3) możliwe jest rozróżnienie 20 kombinacji, są to:
  • standardowe naciśnięcie przycisku KEY0,
  • standardowe naciśnięcie przycisku KEY1,
  • standardowe naciśnięcie przycisku KEY2,
  • standardowe naciśnięcie przycisku KEY3,
  • standardowe naciśnięcie przycisku KEY0 i KEY1,
  • standardowe naciśnięcie przycisku KEY0 i KEY2,
  • standardowe naciśnięcie przycisku KEY0 i KEY3,
  • standardowe naciśnięcie przycisku KEY1 i KEY2,
  • standardowe naciśnięcie przycisku KEY1 i KEY3,
  • standardowe naciśnięcie przycisku KEY2 i KEY3,
  • przedłużone naciśnięcie przycisku KEY0,
  • przedłużone naciśnięcie przycisku KEY1,
  • przedłużone naciśnięcie przycisku KEY2,
  • przedłużone naciśnięcie przycisku KEY3,
  • przedłużone naciśnięcie przycisku KEY0 i KEY1,
  • przedłużone naciśnięcie przycisku KEY0 i KEY2,
  • przedłużone naciśnięcie przycisku KEY0 i KEY3,
  • przedłużone naciśnięcie przycisku KEY1 i KEY2,
  • przedłużone naciśnięcie przycisku KEY1 i KEY3,
  • przedłużone naciśnięcie przycisku KEY2 i KEY3.
Działanie automatu sterującego przedstawia rysunek 3, w którym wyróżnionych jest pięć stanów, są to:
Stan automatu → Znaczenie
KeybIdle → Stan jałowy automatu, automat pozostaje w tym stanie tak długo, aż pojawi się zdarzenie naciśnięcia jakiegokolwiek przycisku klawiatury. Wykrycie stanu aktywnego powoduje przejście do stanu oczekiwania na stabilny stan naciśniętego przycisku (eliminacja dzwonienia styków).
KeybWaitOnPress → Stan odczekania związany z eliminacją dzwonienia styków przy naciśnięciu przycisku. Jeżeli po upływie odpowiedniego czasu nadal będzie występował stabilny stan naciśniętego przycisku, to automat przechodzi do stanu obsługi (KeybReady) lub wraca do stanu jałowego (KeybIdle).
KeybReady → Stan gotowości związany z rzeczywistym wykryciem naciśniętego przycisku. Automat bezwarunkowo przechodzi do stanu rozróżnienia naciśnięcia standardowego lub przedłużonego (KeybActive).
KeybActive → Stan przewidziany do rozróżnienia standardowego lub przedłużonego naciśnięcia przycisku, który polega na pomiarze czasu trwania stanu naciśnięcia. Jeżeli przed upływem odpowiedniego czasu przycisk zostanie zwolniony, to przyciśnięcie jest standardowe i automat przechodzi do eliminacji dzwonienia styków związanego z puszczeniem przycisku. W przeciwnym wypadku, zdarzenie jest interpretowane jako naciśnięcie przedłużone.
KeybWaitOnFree → Stan odczekania związany ze zwolnieniem przycisku.
kbd1_i03.png
Z przedstawianym rozwiązaniem związana jest pewna kwestia „filozofii w programowaniu”: co by tu zrobić by się nie narobić. Może warto stworzyć kawałek oprogramowania o dużym stopniu niezależności od czegokolwiek. Napisać moduł oprogramowania, coś na kształt biblioteki, który będzie dołączany jako #include. Zawierałby ten moduł w sobie wszystko co jest niezbędne: zestaw funkcji i własne zmienne. Jest raczej oczywiste, że jest to nie możliwe, gdyż występują różnorodne rodziny mikrokontrolerów (pomijając wspomnianą wyżej kwestię architektury: harwardzka, von Neumanna) co implikuje odmienną obsługę portów, do których przyłączona jest klawiatura. A co, gdyby newralgiczne fragmenty wynieść poza tworzony moduł? Pozostałaby w nim jedynie części niezależne, czysty, abstrakcyjny algorytm. Nawet można w pewnym stopniu uzyskać niedostępność zmiennych lokalnych modułu, gdyż jeżeli elementy (zmienne, funkcje) zostaną opatrzone kwalifikatorem static, to nie będą widoczne na zewnątrz modułu (brak static jest równoznaczne z public). Przecież język C oferuje takie możliwości: można utworzyć zmienną będącą funkcją o określonym typie parametrowym. Pod tą zmienną można podstawić dowolną funkcję (byle był zgodny typ funkcji i lista parametrów wywołania). Dodatkowo, łatwo można zbadać, czy taka zmienna ma podstawioną wartość czy nie, co oznaczałoby, że jakaś funkcjonalność jest aktywowana lub nie. Jeżeli zmienne typu funkcyjnego będą miały zawartość NULL (w języku C odpowiada stałej 0), to znaczy, że dana funkcjonalność nie jest aktywowana. Przecież wywołanie takiej funkcji w większości przypadków mikroprocesorów spowoduje reset. Jeżeli zmienna będzie przechowywać inną wartość, to znaczy, że wskazuje na funkcję obsługi.
Ka koncepcja prowadzi do utworzenia niepublicznej zmiennej typu jakiejś struktury. Struktura ta zawierałaby wszystkie niezbędne zmienne modułu oraz zmienne będące wskazaniami do wydzielonych fragmentów obsługi. Zmienna tego typu zostanie określona jako instancja. Filozoficznie jest to zbliżone do koncepcji komponentu w językach obiektowych.
Do "manipulowania" zawartości zmiennych z prywatnej instancji modułu obsługi klawiatury zostają wyeksportowane z tego modułu odpowiednie funkcje. Przy takim podejściu, to nawet nie jest konieczne umieszczenie typu struktury w pliku nagłówkowym z wyjątkiem niezbędnych typów (głównie typów zmiennych stanowiących wskazaniami do funkcji wynikających z defragmentacji algorytmu, taki koncepcyjny odpowiednik metody z filozofii komponentów). Z punktu widzenia programu, do którego dołączony jest moduł obsługi klawiatury, jego zawartość zaczyna przypominać "czarną skrzynkę", nie jest istotne co ma w środku, istotne stają się jedynie "złączki" prowadzące do środka.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » czwartek 18 maja 2017, 12:15

Bufor cykliczny - element instancji modułu obsługi klawiatury

Instancja modułu obsługi klawiatury (KeybModuleInstanceType):

Kod: Zaznacz cały

typedef struct { uint8_t CycFr ;
                 uint8_t CycTo ;
                 uint8_t Buffer [ KBDBuffSize ] ;
               } KBDCyclicRecT ;

typedef enum { KeybIdle ,
               KeybWaitOnPress ,
               KeybReady ,
               KeybActive ,
               KeybWaitOnFree } KeybStateType ;

typedef struct {
         volatile KBDCyclicRecT                   KBDCyclic ;
                  ExtraKeybReadServiceProcType    ExtraKeybReadService ;
                  HardwareKeybReadServiceProcType HardwareKeybReadService ;
                  StoreKeyServiceProcType         StoreKeyService ;
                  uint16_t                        StandEncodeTable ;
                  uint16_t                        LongEncodeTable ;
                  KeybStateType                   KeybState ;
                  uint16_t                        KeybDelayCt ;
                  uint8_t                         LastKeybInp ;
                  uint8_t                         BitMask ;
                  uint8_t                         LongEncodeTableSize ;
                  uint8_t                         StandEncodeTableSize ;
               } KeybModuleInstanceType ;

zawiera KBDCyclicRecT jako element z kwalifikatorem volatile, gdyż przewidywane jest użycie tego elementu struktury w procedurach obsługi przerwań.
Istotnym elementem instancji jest kilkuelementowy bufor cykliczny. Jest to bardzo efektywny sposób pozwalający na kolejkowanie różnorodnej informacji (w tym przypadku kodów naciśniętych przycisków). Struktura ta zawiera oprócz obszaru przechowującego samą informację, dwa wskaźniki: zapisu i odczytu, jak pokazuje rysunek 1.
kbd2_i01.png
Wskaźnik zapisu pokazuje pierwsze wolne miejsce w tabeli, wskaźnik odczytu miejsce czoła kolejki (pierwsze do odczytu). Operacja zapisu sprowadza się w tym układzie do zapisu kolejkowanych danych do tablicy w miejsce wskazane przez wskaźnik zapisu (który należy interpretować jako indeks do tablicy), zwiększenie wskaźnika o jeden. Oczywiście po pewnym czasie ciągle zwiększając wskaźnik „wyjdzie on poza tablicę”, więc w takiej chwili należy go zawrócić na pozycję pierwszą (co w przypadku języka programowania C oznacza, że należy wskaźnik wyzerować, gdyż tablice zawsze są indeksowane od zera). Z kolei operacja odczytu wiąże się z pobraniem elementu z miejsca wskazanego przez wskaźnik odczytu (również traktowanego jako indeks do tablicy), zwiększenia wskaźnika o jeden z ewentualnym „zawróceniem” wskaźnika na początek tablicy w sytuacji, gdy wskaźnik poprzednio wskazywał element ostatni w tablicy. Operacja pobrania elementu może być dokonana pod warunkiem, że w kolejce znajduje się minimum jeden element, czyli gdy kolejka nie jest pusta. Sprawdzenie, czy kolejka jest pusta polega na porównaniu obu wskaźników (odczytu oraz zapisu). Jeżeli oba wskaźniki pokazują to samo miejsce (mają identyczna zawartość) to kolejka jest pusta. W przeciwnym wypadku, kolejka zawiera elementy. Jednocześnie z tego wynika sposób wstępnego zainicjowania całej struktury. Przed zastosowaniem w sensie operacji zapisu lub odczytu elementów do kolejki należy ustawić wskaźniki, przykładowo nadać im wartość zero, chociaż w gruncie rzeczy nie jest istotne ile. Istotne jest, by było tyle samo (oczywiście w dopuszczalnym zakresie wynikającym z liczby elementów tablicy). To, że zwyczajowo realizuje się zainicjowanie wskaźników nadając początkowo im wartość zero, jest wyłącznie kwestią przyzwyczajeń.
kbd2_i02.png
kbd2_i03.png
kbd2_i04.png
kbd2_i05.png
kbd2_i06.png
kbd2_i07.png
kbd2_i08.png
kbd2_i09.png
kbd2_i10.png
kbd2_i11.png
kbd2_i12.png
Powyższe rysunki pokazują stan kolejki dla kilku operacji zapisu oraz odczytu informacji.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » czwartek 18 maja 2017, 14:01

Dowiązanie do sprzętowej obsługi klawiatury

Zakładając, że naciśnięcie przycisku klawiatury (zwarcie określonej linii portu do masy) daje stan logicznego zera, łatwo rozpoznać sytuację, gdzie nie jest wciśnięty żaden przycisk oraz gdzie jest naciśnięty jakikolwiek przycisk. Z racji powszechnie obowiązującej struktury bajtowej jako informacji 8-bitowej, przyjęte zostało, że wczytywany stan przycisków będzie informacją 8-bitową, gdzie każdy bit będzie odzwierciedlał stan poszczególnych przycisków. Implikuje to, że obsługa klawiatury zostaje ograniczona do maksymalnie 8 przycisków. W ogromnej ilości przypadków nie jest to właściwie żadne ograniczenie. Raczej częściej zaistnieje potrzeba budowy i obsługi klawiatur o mniejszej liczbie przycisków. Z tego powodu w obsłudze klawiatury utworzona zostaje maska jako element 8-bitowy, która umożliwi „wyfiltrowanie” danych określających stan przycisków klawiatury. Jeżeli dany bit maski zawiera jedynkę, to znaczy, że ekwiwalentny bit w funkcji odczytującej sprzętowy stan klawiatury niesie w sobie informację o stanie przycisku. Jeżeli bit maski zawiera zero, to jakiekolwiek dane w ekwiwalentnym wyniku z funkcji wczytania stanu klawiatury nie mają żadnego znaczenie.
W gruncie rzeczy algorytm obsługi klawiatury nie jest zainteresowany liczbą przycisków w obsługiwanej klawiaturze. W algorytmie istotne jest jedynie to, czy coś jest naciśnięte, czy nie. Algorytm obsługi wywnioskuje o tym posiłkując się informacją zapisaną w masce. To liczba jedynek w bajcie maski oznacza liczbę przyłączonych przycisków.
Moduł obsługi klawiatury będzie potrzebował „złączki”, poprzez którą dostarczane do obsługi będą informacje o stanie klawiatury. Do odpowiedniego „gniazdka” w obsłudze klawiatury należy „podłączyć” własną funkcję. To podłączenie umożliwia funkcja:
void SoftKeybInit ( uint8_t Mask , HardwareKeybReadServiceProcType HardwKReadSrv )
gdzie (zdefiniowane w pliku nagłówkowym obsługi klawiatury)
typedef uint8_t ( * HardwareKeybReadServiceProcType ) ( void ) ;
jest bezparametrową funkcją zwracającą dane 8-bitowe.
SoftKeybInit należy jednorazowo wywołać na początku. To wywołanie spowoduje zainicjowanie niezbędnych danych i „podłączenie” do algorytmu funkcji wczytującej stan klawiatury.
kbd3_i01.PNG

Maksymalnie elastyczne, w sensie oprogramowania, zainicjowanie instancji obsługi klawiatury może wyglądać następująca:

Kod: Zaznacz cały

#define KeybPortDirection              DDRB
#define InpKeybReg                     PINB

#define L0Key                          7
#define L1Key                          6
#define L2Key                          5
#define L3Key                          4

#define KeyMask (1<<L0Key) | (1<<L1Key) | (1<<L2Key) | (1<<L3Key)

(…)
uint8_t HardwareKeybRead ( void )
{
  uint8_t KeybData ;
  /*----------------------------------------------------------------*/
  KeybData = InpKeybReg ;
  return ( KeybData ) ;
} /* HardwareKeybRead */


static void EnvirInit ( void )
{
  uint8_t Bits ;
  /*-------------------------------------------------------------------------*/
  Bits = KeybPortDirection ;
  Bits &= ~ ( 1 << L0Key ) ;
  Bits &= ~ ( 1 << L1Key ) ;
  Bits &= ~ ( 1 << L2Key ) ;
  Bits &= ~ ( 1 << L3Key ) ;
  KeybPortDirection = Bits ;
(...)
} /* EnvirInit */

(…)

  EnvirInit ( ) ;
  SoftKeybInit ( KeyMask , HardwareKeybRead ) ;

Po tym wywołaniu obsługa klawiatury będzie wiedziała, jaka funkcja (HardwareKeybRead) jest używana do wczytania stanu klawiatury oraz które bity w jej wyniku mają istotne znaczenie (KeyMask).
W najprostszym przypadku obsługi wystarczy wczytać dane z portu, do którego przyłączona jest klawiatura. Nawet można nie kłopotać się o dane, które nie odzwierciedlają informacji o przyciskach, algorytm je sobie wyfiltruje posiłkując się podaną maską.
Elastyczność rozwiązania dopuszcza również takie przypadki jak na poniższej ilustracji:
kbd3_i02.PNG
Gdzie klawiatura jest „rozproszona po kilku portach”. To jedynie oznacza bardziej złożoną funkcję odpowiedzialną za sprzętowe wczytanie klawiatury bez jakichkolwiek implikacji w algorytmie obsługi. W tym przykładzie zainicjowanie obsługi klawiatury może wyglądać następująco:

Kod: Zaznacz cały

#define TechL0Key                      6
#define TechL1Key                      4
#define TechL2Key                      5
#define TechL3Key                      4

#define LogicL0Key                     0
#define LogicL1Key                     1
#define LogicL2Key                     2
#define LogicL3Key                     3

#define KeyMask (1<<LogicL0Key)|(1<<LogicL1Key)|(1<<LogicL2Key)|(1<<LogicL3Key)

(…)
uint8_t HardwareKeybRead ( void )
{
  uint8_t KeybData ;
  uint8_t RealKeybData ;
  /*----------------------------------------------------------------*/
  RealKeybData = <wczytanie portu 1>
  KeybData = 0xFF ;
  if ( ! ( RealKeybData & ( 1 << TechL0Key ) ) )
    KeybData &= ~ ( 1 << LogicL0Key ) ;
  if ( ! ( RealKeybData & ( 1 << TechL1Key ) ) )
    KeybData &= ~ ( 1 << LogicL1Key ) ;
  RealKeybData = <wczytanie portu 2>
  if ( ! ( RealKeybData & ( 1 << TechL2Key ) ) )
    KeybData &= ~ ( 1 << LogicL2Key ) ;
  RealKeybData = <wczytanie portu 3>
  if ( ! ( RealKeybData & ( 1 << TechL3Key ) ) )
    KeybData &= ~ ( 1 << LogicL3Key ) ;
  return ( KeybData ) ;
} /* HardwareKeybRead */

Techniczne położenia przycisków zostają przemapowane do położeń logicznych.
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony sobota 20 maja 2017, 19:26 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » czwartek 18 maja 2017, 16:41

Translacja wciśniętych przycisków na kody znaków

To, że moduł obsługi klawiatury rozpozna naciśnięcie przycisku, łącznie z poprawną jego obsługą polegającą na eliminacji zjawiska dzwonienia styków, jeszcze nie oznacza zakończenia procesu związanego z obsługą klawiatury. W oprogramowaniu użyteczne jest takie rozwiązanie, że program sprawdzi, czy jest naciśnięty przycisk i ewentualnie będzie chciał „poznać” jego kod. Podobnie, przykładowo, jak w komputerach. Użytkownik naciska na pojedynczy klawisz w klawiaturze, natomiast program uzyskuje informację o naciśniętym klawiszu w postaci jego kodu. W rzeczywistości naciśnięcie klawisza na PC-towej klawiaturze nie powoduje, że klawiatura transmituje do komputera jego kod. Informacją przesłaną przez klawiaturę można utożsamiać z przekazaniem do komputera umownego numeru przycisku z klawiatury (w rzeczywistości proces ten jest bardziej złożony i raczej może być tematem oddzielnych rozważań). W komputerze informacja uzyskana z klawiatury jest przetwarzana na użytkowy kod znaku. Takie rozwiązanie przykładowo w prosty sposób umożliwia technicznie tą samą klawiaturę przełączać na różne zestawy znaków (przykładowo z polskimi znakami). Rozwiązanie stare jak świat i nie ma powodów, by poszukiwać innych. Do obsługi klawiatury zostanie domontowany mechanizm pozwalający na translację kombinacji naciśniętych przycisków na wynikowy kod znaku. Realizacja tego jest zawsze identyczna, więc zostaje zawarta w module. Jedyną informacją wymienną w tym procesie jest: co (kombinacja przycisków) należy przetworzyć na co (kod znaku). Należałoby w programie utworzyć odpowiednią tabelę i odpowiednio „podłączyć” ją do instancji obsługi klawiatury. Moduł obsługi klawiatury eksportuje (zawiera w pliku nagłówkowym) typ:

Kod: Zaznacz cały

typedef struct {
                 uint8_t       InpCode ;
                 uint8_t       OutCode ;
               } KeybEncoderRecT ;

opisujący jeden element takiej tablicy. Zawiera on informację co (InpCode) jako kombinację naciśniętych przycisków ekwiwalentną w swej koncepcji z informacją zwracaną przez funkcję sprzętowego wczytania klawiatury należy przetworzyć na co (OutCode) jako wynikowy kod znaku.
Tu warto zauważyć, że informacja wejściowa zawiera kombinację naciśniętych przycisków (nie koniecznie jednego). W założeniach moduł miał przetwarzać również naciśnięcia pary przycisków. W rzeczywistości jest w stanie przetwarzać dowolną kombinację użytych przycisków a zaproponowane ograniczenie wynika jedynie z problemów natury użytkowej, to jak przykładowo jednocześnie nacisnąć osiem przycisków. Jednocześnie to nie jest możliwe naciśnięcie nawet dwóch przycisków (nie da się uzyskać jednoczesne zwarcie styków dwóch przycisków z dokładnością do mikrosekundy). Algorytm obsługi klawiatury jest na tyle tolerancyjny, że uzna dwa różne w czasie naciśnięcia na jednoczesne jeżeli interwał czasu między tymi zdarzeniami będzie odpowiednio niewielki. Swoisty pomiar czasu naciśnięcia przycisków jest realizowany przez moduł obsługi. Na bazie tego pomiaru rozróżniane jest typowe, krótkie naciśnięcie przycisku oraz naciśnięcie z przytrzymaniem. Te rozróżnienie determinuje użycie jednego z dwóch zestawień do przekodowania.
Domontowanie tabeli przekodowań następuje w wyniku wywołania następującej funkcji modułu obsługi klawiatury:

Kod: Zaznacz cały

void SetEncodeTable ( uint16_t /* StandEncTable     */ ,
                      uint8_t  /* StandEncTableSize */ ,
                      uint16_t /* LongEncTable      */ ,
                      uint8_t  /* LongEncTableSize  */ ) ;

gdzie podawane są dowiązania do odpowiednich tabel przekodowań wraz z określeniem ich wielkości (w sensie ilości elementów w tabeli).
Przykład:

Kod: Zaznacz cały

#define StandKey0Code                  0x10
#define StandKey1Code                  0x11
#define StandKey2Code                  0x12
#define StandKey3Code                  0x13
#define StandKey01Code                 0x14
#define StandKey02Code                 0x15
#define StandKey03Code                 0x16
#define StandKey12Code                 0x17
#define StandKey13Code                 0x18
#define StandKey23Code                 0x19
#define LongKey0Code                   0x1A
#define LongKey1Code                   0x1B
#define LongKey2Code                   0x1C
#define LongKey3Code                   0x1D
#define LongKey01Code                  0x1E
#define LongKey02Code                  0x1F
#define LongKey03Code                  0x20
#define LongKey12Code                  0x21
#define LongKey13Code                  0x22
#define LongKey23Code                  0x23

#define L0Key                          7
#define L1Key                          6
#define L2Key                          5
#define L3Key                          4

#define StandEncodeTabeSize            10
#define LongEncodeTabeSize             10

static KeybEncoderRecT StandEncodeTabe [ StandEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , StandKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , StandKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , StandKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , StandKey3Code } ,
/* 04 */ { ( 1 << L0Key ) | ( 1 << L1Key ) , StandKey01Code } ,
/* 05 */ { ( 1 << L0Key ) | ( 1 << L2Key ) , StandKey02Code } ,
/* 06 */ { ( 1 << L0Key ) | ( 1 << L3Key ) , StandKey03Code } ,
/* 07 */ { ( 1 << L1Key ) | ( 1 << L2Key ) , StandKey12Code } ,
/* 08 */ { ( 1 << L1Key ) | ( 1 << L3Key ) , StandKey13Code } ,
/* 09 */ { ( 1 << L2Key ) | ( 1 << L3Key ) , StandKey23Code } ,
} ;

static KeybEncoderRecT LongEncodeTabe [ LongEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , LongKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , LongKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , LongKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , LongKey3Code } ,
/* 04 */ { ( 1 << L0Key ) | ( 1 << L1Key ) , LongKey01Code } ,
/* 05 */ { ( 1 << L0Key ) | ( 1 << L2Key ) , LongKey02Code } ,
/* 06 */ { ( 1 << L0Key ) | ( 1 << L3Key ) , LongKey03Code } ,
/* 07 */ { ( 1 << L1Key ) | ( 1 << L2Key ) , LongKey12Code } ,
/* 08 */ { ( 1 << L1Key ) | ( 1 << L3Key ) , LongKey13Code } ,
/* 09 */ { ( 1 << L2Key ) | ( 1 << L3Key ) , LongKey23Code } ,
} ;

  (...)
  SoftKeybInit ( KeyMask , HardwareKeybRead ) ;
  SetEncodeTable ( (uint16_t ) StandEncodeTabe ,
                   StandEncodeTabeSize ,
                   (uint16_t ) LongEncodeTabe ,
                   LongEncodeTabeSize ) ;

Przykładowy element tablicy: { ( 1 << L2Key ) | ( 1 << L3Key ) , StandKey23Code} oznacza, że jednoczesne naciśnięcie przycisku w linii L2Key oraz L3Key generuje kod znaku określony przez stałą StandKey23Code.
Moduł obsługi klawiatury jest tak skonstruowany, że podanie wielkości tabeli równej zero lub dowiązanie do tabeli mające wartość NULL (czyli również 0) wyłącza dany rodzaj tabeli z użycia. Tu warto zauważyć, że w trakcie działania programu dowiązania mogą być modyfikowane. Pozwala to uzyskać coś na kształt „zmiany zestawu znaków” klawiatury.
Wartości kodów znaków (stałe StandKey0Code do LongKey23Code) nie mają znaczenia, istotne jest jedynie to by były różne. Równie dobrze program będzie działać dla:

Kod: Zaznacz cały

#define StandKey0Code                  'A'
#define StandKey1Code                  'B'
#define StandKey2Code                  'C'
#define StandKey3Code                  'D'
#define StandKey01Code                 'E'
#define StandKey02Code                 'F'
#define StandKey03Code                 'G'
#define StandKey12Code                 'H'
#define StandKey13Code                 'I'
#define StandKey23Code                 'J'
#define LongKey0Code                   'K'
#define LongKey1Code                   'L'
#define LongKey2Code                   'M'
#define LongKey3Code                   'N'
#define LongKey01Code                  'O'
#define LongKey02Code                  'P'
#define LongKey03Code                  'Q'
#define LongKey12Code                  'R'
#define LongKey13Code                  'S'
#define LongKey23Code                  'T'

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » czwartek 18 maja 2017, 18:52

Dodatkowe możliwości modułu

Rozpoznane (zdekodowane) kombinacje naciśniętych przycisków są przechowywane w wspomnianej wyżej kolejce FIFO. Jest to kilkuelementowa kolejka przechowująca kody wczytanych przycisków. W typowych przypadkach, program jest w stanie reagować na naciśnięte przyciski szybciej niż użytkownik je naciskać. Rodzi się pytanie: w jakim celu jest zastosowana kolejka FIFO, przecież nie grozi, by doszło do jej przepełnienia (nie jest możliwe by człowiek „wciskał” przyciski szybciej niż program jest w stanie je obsługiwać). Zrozumiałe jest, że coś takiego może zaistnieć, przykładowo po naciśnięciu określonego przycisku program zapętli się na … 1 godzinę. Obsługa klawiatury jest zrealizowana w przerwaniach, toteż, wciskane przyciski zostaną obsłużone. Jedynie przez pewien czas nie zostaną one „skonsumowane”. Ale to nie jest prawdziwy powód zastosowania tego rozwiązania. Użycie kolejki FIFO pozwala na „wciskanie przycisków” przez program. Należy przez to rozumieć, że program mając „złącze” w module obsługi klawiatury do zapisywania rozpoznanych znaków do kolejki FIFO, może je tam dodawać. Wyobraźmy sobie, że jakieś urządzenie jest sterowane z klawiatury oraz obsługuje interfejs szeregowy UART. Jeżeli zostanie zrealizowana obsługa UART w ten sposób, że znaki odczytane z kanału UART po odpowiednim przekodowaniu mogą zostać dodawane do kolejki FIFO klawiatury. Uzyskany efekt jest taki, że program może być jednocześnie sterowany z klawiatury jak i z terminala/emulatora terminala przyłączonego poprzez UART. Można również sobie wyobrazić sytuację, gdzie naciśnięcie jednego przycisku wygeneruje kilka znaków do kolejki FIFO. Ograniczeniem jest jedynie pojemność kolejki FIFO.
Po standardowym zainicjowaniu (funkcja SoftKeybInit) instancji modułu za gromadzenie danych w kolejce FIFO jest odpowiedzialna funkcja StoreKey (jest to funkcja z jednym parametrem będącym kodem znaku, który należy dodać do kolejki FIFO). Ta funkcjonalność jest wymienna, czyli istnieje możliwość domontowania do instancji obsługi klawiatury dowolnej innej funkcji (musi mieć identyczny typ i listę parametrów jak wymieniona wyżej StoreKey). Domontowanie specyficznej funkcji odpowiedzialnej za dodawanie znaków do kolejki FIFO realizowane jest poprzez wywołanie:
void MountStoreKeyService ( StoreKeyServiceProcType StoreService )
gdzie StoreServie jest wskazaniem na funkcję, jaka ma być użyta w obsłudze klawiatury. Podanie w parametrach wskazania pustego (NULL) oznacza przełączenie na standardową funkcję. Takie rozwiązanie umożliwia różne nietypowe rozwiązania. Często jednak może zaistnieć potrzeba realizacji jakiegoś prostego działania w wyniku naciśnięcia przycisku klawiatury (dokładniej jako dodatkowa akcja wykonywana w momencie dodania znaku do kolejki). Przykładem może być krótki sygnał dźwiękowy (taki beep). Na taką okoliczność istnieje możliwość domontowania do instancji dowolnej bezparametrowej funkcji, która będzie wywołana przy każdej operacji dodania znaku do kolejki FIFO. Domontowanie to jest realizowane w wyniku wywołania funkcji:
void MountExtraKeybService ( ExtraKeybReadServiceProcType ExtraServ )
gdzie w parametrach podany jest wskaźnik do bezparametrowej funkcji, jaka ma być wywołana przy okazji dodania znaku do kolejki FIFO. Skoro jest możliwość zamontowania dodatkowej funkcji, również jest możliwość odmontowania. Realizuje się to poprzez użycie tej samej funkcji z podaniem w parametrach wskaźnika NULL. Od tej chwili dodanie znaku do kolejki FIFO nie będzie sygnalizowane w jakikolwiek dodatkowy sposób.
Moduł obsługi, oprócz wymienionych wyżej funkcji, eksportuje również:
  • funkcję KeyPressed – bezparametrową funkcją, która zwraca informację typu TAK/NIE czy w kolejce FIFO klawiatury jest coś do odczytania,
  • funkcję ReadKey – bezparametrową funkcję, która zwraca kod znaku zapisany w kolejce FIFO,
  • funkcję KeybService – bezparametrową funkcję, którą należy cyklicznie wywoływać [główny sprawca całego zamieszania].

Plik nagłówkowy oraz implementacyjny modułu obsługi klawiatury jest następujący:
kbdmodule_avr.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
dambo
Expert
Expert
Posty: 645
Rejestracja: czwartek 17 mar 2016, 17:12

Re: Obsługa prostej klawiatury

Postautor: dambo » czwartek 18 maja 2017, 19:32

świetny materiał! dodam, że to podejście z kolejką itp przypomina mi pracę układów do dekodowania klawiatur typu ADP5585
Nowy blog o tematyce embedded -> https://www.embedownik.pl/

Awatar użytkownika
j23
Expert
Expert
Posty: 506
Rejestracja: czwartek 08 paź 2015, 18:40

Re: Obsługa prostej klawiatury

Postautor: j23 » czwartek 18 maja 2017, 23:50

O ja pierniczę.. ale solidny, porządny tutorial... Wielkie dzięki! Chylę czoła z uwagi na tyle pracy włożonej w wytłumaczenie tego zagadnienia.
Internet łączy ludzi, którzy dzielą się swoimi zainteresowaniami, pomysłami i potrzebami, bez względu na geograficzne (przeciwności).
BOB TAYLOR, PARC

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » piątek 19 maja 2017, 00:14

Architektura harwardzka (AVR) – przykład użycia
kbdh_ex1-i00.jpg

Implikacją zastosowania rozwiązania modułu w środowisku mikrokontrolerów o architekturze harwardzkiej jest „specyficzny” sposób sięgania do danych znajdujących się w przestrzeni adresowej pamięci RAM oraz danych znajdujących się w przestrzeni pamięci programu. W module występuje takie miejsce: funkcja do przeszukiwania tablic określających przekodowanie z kombinacji naciśniętych przycisków na generowane kody znaków.
Jej zapis jest następujący:

Kod: Zaznacz cały

static void DecodeKey ( uint8_t KbdCode ,
                        uint8_t LongDelayMode )
{
  uint8_t Loop ;
  uint8_t TableSize ;
  uint8_t InpC ;
  uint8_t OutC ;
  uint16_t MemAddress ;
  /*----------------------------------------------------------------*/
  if ( LongDelayMode )
  {
    MemAddress = KeybModInstance . LongEncodeTable ;
    TableSize  = KeybModInstance . LongEncodeTableSize ;
  } /* if ... */
  else
  {
    MemAddress = KeybModInstance . StandEncodeTable ;
    TableSize  = KeybModInstance . StandEncodeTableSize ;
  } /* if ... else */ ;
  if ( TableSize && MemAddress )
  {
    for ( Loop = 0 ; Loop < TableSize ; Loop ++ )
    {
      InpC = pgm_read_byte_near ( MemAddress ++ ) ;
      OutC = pgm_read_byte_near ( MemAddress ++ ) ;
      if ( KbdCode == InpC )
      {
        KeybModInstance . StoreKeyService ( OutC ) ;
        return ;
      } /* if */ ;
    } /* for */ ;
  } /* if */ ;
} /* DecodeKey */

Do prezentacji działania niezbędne jest oczywiście jakieś środowisko uruchomieniowe mikrokontrolera. Rozpatrzmy następujący wariant. Jakiś system z mikrokontrolerem AVR ma w swoich zasobach 4-przyciskową klawiaturę oraz wyświetlacz umożliwiający zaprezentowanie działania obsługi. Do tego mikrokontrolera przyłączony jest moduł wyświetlacza LCD opartego o sterownik HD44780. Szczegóły wyjaśnia ilustracja 1 oraz ilustracja 2.
kbdh_ex1-i01.png
kbdh_ex1-i02.png
Takie środowisko zapewnia fizyczny układ pokazany na ilustracji 3.
kbdh_ex1-i03.jpg
kbdh_ex1-i04.jpg
Przykład użycia opisanego wyżej modułu pokazuje prosty program, którego zadaniem jest pokazanie na wyświetlaczu LCD naciśniętej kombinacji dopuszczalnej w programie (można zawęzić liczbę dostępnych kombinacji). Obsługa automatu sterującego realizowana jest w przerwaniach generowanych przez licznik/zegar 0. Parametry czasowe są skorelowane z rezonatorem kwarcowym o częstotliwości 20MHz. Przyłączenie przycisków jest widziane przez mikrokontroler w sposób pokazany na ilustracji 5.
kbdh_ex1-i05.png
Oznacza to, że przy okazji odczytu stanu przycisków nie będzie realizowane jakiekolwiek przemapowanie położenia przycisków. Ma to swoje odzwierciedlenie w masce, funkcji sprzętowego odczytu stanu klawiatury oraz w odpowiedniej konstrukcji tabel przekodowań. W tym wariancie program do prezentacji przedstawia się następująco:

Kod: Zaznacz cały

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "lcdmodule.h"
#include "keybmodule.h"



#define StandKey0Code                  0x10
#define StandKey1Code                  0x11
#define StandKey2Code                  0x12
#define StandKey3Code                  0x13
#define StandKey01Code                 0x14
#define StandKey02Code                 0x15
#define StandKey03Code                 0x16
#define StandKey12Code                 0x17
#define StandKey13Code                 0x18
#define StandKey23Code                 0x19
#define LongKey0Code                   0x1A
#define LongKey1Code                   0x1B
#define LongKey2Code                   0x1C
#define LongKey3Code                   0x1D
#define LongKey01Code                  0x1E
#define LongKey02Code                  0x1F
#define LongKey03Code                  0x20
#define LongKey12Code                  0x21
#define LongKey13Code                  0x22
#define LongKey23Code                  0x23


static uint8_t StandKey0Text [ ] PROGMEM    = "St prz 0" ;
static uint8_t StandKey1Text [ ] PROGMEM    = "St prz 1" ;
static uint8_t StandKey2Text [ ] PROGMEM    = "St prz 2" ;
static uint8_t StandKey3Text [ ] PROGMEM    = "St prz 3" ;
static uint8_t StandKey01Text [ ] PROGMEM   = "St prz 0 & prz 1" ;
static uint8_t StandKey02Text [ ] PROGMEM   = "St prz 0 & prz 2" ;
static uint8_t StandKey03Text [ ] PROGMEM   = "St prz 0 & prz 3" ;
static uint8_t StandKey12Text [ ] PROGMEM   = "St prz 1 & prz 2" ;
static uint8_t StandKey13Text [ ] PROGMEM   = "St prz 1 & prz 3" ;
static uint8_t StandKey23Text [ ] PROGMEM   = "St prz 3 & prz 4" ;
static uint8_t LongKey0Text [ ] PROGMEM     = "Lo prz 0" ;
static uint8_t LongKey1Text [ ] PROGMEM     = "Lo prz 1" ;
static uint8_t LongKey2Text [ ] PROGMEM     = "Lo prz 2" ;
static uint8_t LongKey3Text [ ] PROGMEM     = "Lo prz 3" ;
static uint8_t LongKey01Text [ ] PROGMEM    = "Lo prz 0 & prz 1" ;
static uint8_t LongKey02Text [ ] PROGMEM    = "Lo prz 0 & prz 2" ;
static uint8_t LongKey03Text [ ] PROGMEM    = "Lo prz 0 & prz 3" ;
static uint8_t LongKey12Text [ ] PROGMEM    = "Lo prz 1 & prz 2" ;
static uint8_t LongKey13Text [ ] PROGMEM    = "Lo prz 1 & prz 3" ;
static uint8_t LongKey23Text [ ] PROGMEM    = "Lo prz 2 & prz 3" ;

#define KBDPrescalerLimit              13

#define KeybPortDirection              DDRB
#define InpKeybReg                     PINB

#define L0Key                          7
#define L1Key                          6
#define L2Key                          5
#define L3Key                          4
#define KeyMask ( 1 << L0Key ) | ( 1 << L1Key ) | ( 1 << L2Key ) | ( 1 << L3Key )

#define StandEncodeTabeSize            10
#define LongEncodeTabeSize             10

static KeybEncoderRecT StandEncodeTabe [ StandEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , StandKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , StandKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , StandKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , StandKey3Code } ,
/* 04 */ { ( 1 << L0Key ) | ( 1 << L1Key ) , StandKey01Code } ,
/* 05 */ { ( 1 << L0Key ) | ( 1 << L2Key ) , StandKey02Code } ,
/* 06 */ { ( 1 << L0Key ) | ( 1 << L3Key ) , StandKey03Code } ,
/* 07 */ { ( 1 << L1Key ) | ( 1 << L2Key ) , StandKey12Code } ,
/* 08 */ { ( 1 << L1Key ) | ( 1 << L3Key ) , StandKey13Code } ,
/* 09 */ { ( 1 << L2Key ) | ( 1 << L3Key ) , StandKey23Code } ,
} ;

static KeybEncoderRecT LongEncodeTabe [ LongEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , LongKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , LongKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , LongKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , LongKey3Code } ,
/* 04 */ { ( 1 << L0Key ) | ( 1 << L1Key ) , LongKey01Code } ,
/* 05 */ { ( 1 << L0Key ) | ( 1 << L2Key ) , LongKey02Code } ,
/* 06 */ { ( 1 << L0Key ) | ( 1 << L3Key ) , LongKey03Code } ,
/* 07 */ { ( 1 << L1Key ) | ( 1 << L2Key ) , LongKey12Code } ,
/* 08 */ { ( 1 << L1Key ) | ( 1 << L3Key ) , LongKey13Code } ,
/* 09 */ { ( 1 << L2Key ) | ( 1 << L3Key ) , LongKey23Code } ,
} ;


static uint8_t HelloText [ ] PROGMEM                   = "** Klawiatura **" ;


static uint16_t KbdPrescalerCounter ;





SIGNAL ( TIMER0_OVF_vect )
{
  /*-------------------------------------------------------------------------*/
  KbdPrescalerCounter ++ ;
  if ( KbdPrescalerCounter > KBDPrescalerLimit )
  {
    KbdPrescalerCounter = 0 ;
    KeybService ( ) ;
  } /* if */ ;
} /* TIMER0_OVF_vect */


static void HardwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  TIMSK0 = ( 1 << TOIE0 ) ;
//      TIMSK0 – Timer/Counter Interrupt Mask Register
//      OCIE0B: Timer/Counter Output Compare Match B Interrupt Enable
//      OCIE0A: Timer/Counter0 Output Compare Match A Interrupt Enable
//      TOIE0: Timer/Counter0 Overflow Interrupt Enable
  TCCR0A = 0 ;
//      TCCR0A – Timer/Counter Control Register A
//      COM0A1:0: Compare Match Output A Mode
//      COM0B1:0: Compare Match Output B mode
//      WGM01:0: Waveform Generation mode
  TCCR0B = ( 1 << CS00 ) | ( 1 << CS01 ) ;
//      TCCR0B – Timer/Counter Control Register B
//      FOC0A: Force Output Compare A
//      FOC0B: Force Output Compare B
//      WGM02: Waveform Generation Mode
//      CS02,CS01,CS00: Clock Select
} /* HardwareInit */


uint8_t HardwareKeybRead ( void )
{
  uint8_t KeybData ;
  /*----------------------------------------------------------------*/
  KeybData = InpKeybReg ;
  return ( KeybData ) ;
} /* HardwareKeybRead */


static void SoftwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  KbdPrescalerCounter = 0 ;
  SoftKeybInit ( KeyMask , HardwareKeybRead ) ;
  SetEncodeTable ( (uint16_t ) StandEncodeTabe , StandEncodeTabeSize ,
                   (uint16_t ) LongEncodeTabe , LongEncodeTabeSize ) ;
} /* SoftwareInit */


static void EnvirInit ( void )
{
  uint8_t Bits ;
  /*-------------------------------------------------------------------------*/
  Bits = KeybPortDirection ;
  Bits &= ~ ( 1 << L0Key ) ;
  Bits &= ~ ( 1 << L1Key ) ;
  Bits &= ~ ( 1 << L2Key ) ;
  Bits &= ~ ( 1 << L3Key ) ;
  KeybPortDirection = Bits ;
  InitLCDEnvir ( ) ;
} /* EnvirInit */


static void OptionService ( uint8_t KeyCode )
{
  /*-------------------------------------------------------------------------*/
  ClrScrLCD ( ) ;
  switch ( KeyCode )
  {
    case StandKey0Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey0Text ) ;
      break ;
    case StandKey1Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey1Text ) ;
      break ;
    case StandKey2Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey2Text ) ;
      break ;
    case StandKey3Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey3Text ) ;
      break ;
    case StandKey01Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey01Text ) ;
      break ;
    case StandKey02Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey02Text ) ;
      break ;
    case StandKey03Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey03Text ) ;
      break ;
    case StandKey12Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey12Text ) ;
      break ;
    case StandKey13Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey13Text ) ;
      break ;
    case StandKey23Code                 :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey23Text ) ;
      break ;
    case LongKey0Code                   :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey0Text ) ;
      break ;
    case LongKey1Code                   :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey1Text ) ;
      break ;
    case LongKey2Code                   :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey2Text ) ;
      break ;
    case LongKey3Code                   :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey3Text ) ;
      break ;
    case LongKey01Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey01Text ) ;
      break ;
    case LongKey02Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey02Text ) ;
      break ;
    case LongKey03Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey03Text ) ;
      break ;
    case LongKey12Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey12Text ) ;
      break ;
    case LongKey13Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey13Text ) ;
      break ;
    case LongKey23Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) LongKey23Text ) ;
      break ;
  } /* switch */ ;
} /* OptionService */


int main ( void )
{
  /*-------------------------------------------------------------------------*/
  SoftwareInit ( ) ;
  HardwareInit ( ) ;
  EnvirInit ( ) ;
  InitialiseLCD ( ) ;
  ClrScrLCD ( ) ;
  WriteTextLCDFlash ( HelloText ) ;
  sei ( ) ;
  for ( ; ; )
  {
    if ( KeyPressed ( ) )
    {
      OptionService ( ReadKey ( ) ) ;
    } /* if */ ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */

Po umieszczeniu w pamięci FLASH mikrokontrolera AVR kodu wygenerowanego programu działanie urządzenia pokazują poniższe ilustracje.
kbdh_ex1-i06.jpg
kbdh_ex1-i07.jpg
kbdh_ex1-i08.jpg
kbdh_ex1-i09.jpg


Załącznik: projekt dla AVRSTUDIO
kbd_avr1.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 19 maja 2017, 00:27 przez gaweł, łącznie zmieniany 3 razy.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » piątek 19 maja 2017, 00:15

j23 pisze:O ja pierniczę..

Z przykładem użycia. W planach jest więcej przykładów (również w innych architekturach).
Ostatnio zmieniony piątek 19 maja 2017, 15:00 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » piątek 19 maja 2017, 11:40

Architektura harwardzka (AVR) – przykład użycia numer 2

Inny przykład użycia. Środowisko mikrokontrolera przedstawia poniższa ilustracja.
kbdh_ex2-i01.png
kbdh_ex2-i02.png
W stosunku do poprzedniego przykładu, tu odmienna (z punktu widzenia obsługi klawiatury) jest częstotliwość taktowania mikrokontrolera (8MHz) oraz dodany jest buzzer (z własnym generatorem), który jest sterowany jednym z pinów portu mikrokontrolera.
Takie środowisko zapewnia fizyczny układ pokazany na ilustracji.
kbdh_ex2-i03.jpg
Przykład użycia opisanego wyżej modułu pokazuje prosty program, którego zadaniem jest pokazanie na wyświetlaczu LCD naciśniętej kombinacji dopuszczalnej w programie (można zawęzić liczbę dostępnych kombinacji). Obsługa automatu sterującego realizowana jest w przerwaniach generowanych przez licznik/zegar 0. Parametry czasowe są skorelowane z rezonatorem kwarcowym o częstotliwości 8MHz (jest odmienne „przeskalowanie” czasowe).Każde naciśnięcie przycisku generuje krótki sygnał dźwiękowy. Jest to uzyskane poprzez domontowanie do operacji dodanie elementu do kolejki FIFO dodatkowej operacji. Każde wywołanie tej funkcji włącza buzer na określony czas (wyłączany jest po odliczeniu określonych cykli wywołania funkcji obsługi przerwań od czasu). Również obsługa klawiatury sprowadza się do najprostszego wariantu: dopuszczalne są jedynie krótkie naciśnięcia pojedynczych przycisków.

Kod: Zaznacz cały

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "lcdmodule.h"
#include "keybmodule.h"



#define BuzzerPort                     PORTD
#define BuzzerDirPort                  DDRD
#define BuzzerPin                      7

#define KeyBeepTime                    40

#define StandKey0Code                  0x10
#define StandKey1Code                  0x11
#define StandKey2Code                  0x12
#define StandKey3Code                  0x13


static uint8_t StandKey0Text [ ] PROGMEM    = "St prz 0" ;
static uint8_t StandKey1Text [ ] PROGMEM    = "St prz 1" ;
static uint8_t StandKey2Text [ ] PROGMEM    = "St prz 2" ;
static uint8_t StandKey3Text [ ] PROGMEM    = "St prz 3" ;


#define KBDPrescalerLimit              50

#define KeybPortDirection              DDRB
#define InpKeybReg                     PINB

#define L0Key                          7
#define L1Key                          6
#define L2Key                          5
#define L3Key                          4
#define KeyMask ( 1 << L0Key ) | ( 1 << L1Key ) | ( 1 << L2Key ) | ( 1 << L3Key )

#define StandEncodeTabeSize            4

static KeybEncoderRecT StandEncodeTabe [ StandEncodeTabeSize ] PROGMEM =
{
/* 00 */ { ( 1 << L0Key )                  , StandKey0Code } ,
/* 01 */ { ( 1 << L1Key )                  , StandKey1Code } ,
/* 02 */ { ( 1 << L2Key )                  , StandKey2Code } ,
/* 03 */ { ( 1 << L3Key )                  , StandKey3Code } ,
} ;



static uint8_t HelloText [ ] PROGMEM                   = "** Klawiatura **" ;


static uint16_t KbdPrescalerCounter ;
volatile static uint16_t BuzzerTimerCounter ;


static void KeybBeepOn ( void )
{
  /*----------------------------------------------------------------*/
  BuzzerTimerCounter += KeyBeepTime ;
  BuzzerPort |= 1 << BuzzerPin ;
} /* KeybBeepOn */


static void BeepOff ( void )
{
  /*----------------------------------------------------------------*/
  BuzzerTimerCounter = 0 ;
  BuzzerPort &= ~ ( 1 << BuzzerPin ) ;
} /* BeepOff */


SIGNAL ( TIMER0_OVF_vect )
{
  /*-------------------------------------------------------------------------*/
  KbdPrescalerCounter ++ ;
  if ( KbdPrescalerCounter > KBDPrescalerLimit )
  {
    KbdPrescalerCounter = 0 ;
    KeybService ( ) ;
  } /* if */ ;
  if ( BuzzerTimerCounter )
  {
    BuzzerTimerCounter -- ;
    if ( ! BuzzerTimerCounter )
    {
      BeepOff ( ) ;
    } /* if */ ;
  } /* if */ ;
} /* TIMER0_OVF_vect */


static void HardwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  TIMSK0 = ( 1 << TOIE0 ) ;
//      TIMSK0 – Timer/Counter Interrupt Mask Register
//      OCIE0B: Timer/Counter Output Compare Match B Interrupt Enable
//      OCIE0A: Timer/Counter0 Output Compare Match A Interrupt Enable
//      TOIE0: Timer/Counter0 Overflow Interrupt Enable
  TCCR0A = 0 ;
//      TCCR0A – Timer/Counter Control Register A
//      COM0A1:0: Compare Match Output A Mode
//      COM0B1:0: Compare Match Output B mode
//      WGM01:0: Waveform Generation mode
  TCCR0B = ( 1 << CS01 ) ;
//      TCCR0B – Timer/Counter Control Register B
//      FOC0A: Force Output Compare A
//      FOC0B: Force Output Compare B
//      WGM02: Waveform Generation Mode
//      CS02,CS01,CS00: Clock Select
} /* HardwareInit */


uint8_t HardwareKeybRead ( void )
{
  uint8_t KeybData ;
  /*----------------------------------------------------------------*/
  KeybData = InpKeybReg ;
  return ( KeybData ) ;
} /* HardwareKeybRead */


static void SoftwareInit ( void )
{
  /*-------------------------------------------------------------------------*/
  KbdPrescalerCounter = 0 ;
  BuzzerTimerCounter = 0 ;
  SoftKeybInit ( KeyMask , HardwareKeybRead ) ;
  SetEncodeTable ( (uint16_t ) StandEncodeTabe , StandEncodeTabeSize ,
                   (uint16_t ) 0 , 0 ) ;
  MountExtraKeybService ( KeybBeepOn ) ;
} /* SoftwareInit */


static void EnvirInit ( void )
{
  uint8_t Bits ;
  /*-------------------------------------------------------------------------*/
  BuzzerDirPort |= 1 << BuzzerPin ;
  BuzzerPort &= ~ ( 1 << BuzzerPin ) ;
  Bits = KeybPortDirection ;
  Bits &= ~ ( 1 << L0Key ) ;
  Bits &= ~ ( 1 << L1Key ) ;
  Bits &= ~ ( 1 << L2Key ) ;
  Bits &= ~ ( 1 << L3Key ) ;
  KeybPortDirection = Bits ;
  InitLCDEnvir ( ) ;
} /* EnvirInit */


static void OptionService ( uint8_t KeyCode )
{
  /*-------------------------------------------------------------------------*/
  ClrScrLCD ( ) ;
  switch ( KeyCode )
  {
    case StandKey0Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey0Text ) ;
      break ;
    case StandKey1Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey1Text ) ;
      break ;
    case StandKey2Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey2Text ) ;
      break ;
    case StandKey3Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey3Text ) ;
      break ;
  } /* switch */ ;
} /* OptionService */


int main ( void )
{
  /*-------------------------------------------------------------------------*/
  SoftwareInit ( ) ;
  HardwareInit ( ) ;
  EnvirInit ( ) ;
  InitialiseLCD ( ) ;
  ClrScrLCD ( ) ;
  WriteTextLCDFlash ( HelloText ) ;
  sei ( ) ;
  for ( ; ; )
  {
    if ( KeyPressed ( ) )
    {
      OptionService ( ReadKey ( ) ) ;
    } /* if */ ;
  } /* for */ ;
  return ( 0 ) ;
} /* main */



Po umieszczeniu w pamięci FLASH mikrokontrolera AVR kodu wygenerowanego programu urządzenie działało zgodnie z oczekiwaniami.
"Fotoplastykon"
kbdh_ex2-i04.jpg
kbdh_ex2-i05.jpg
kbdh_ex2-i06.jpg


Załącznik: projekt dla AVRSTUDIO
Kbd_avr2.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » piątek 19 maja 2017, 12:06

Architektura harwardzka (AVR) – przykład użycia numer 3

Poprzedni przykład można rozbudować o możliwość sterowania funkcjonalnością dźwiękowej sygnalizacji naciśnięcia przycisku. W tym samym środowisku co poprzedni przykład, naciśnięcie przycisku umownie nazywanego Key0 oraz Key3 dodatkowo wpływa na włączanie lub wyłączanie sygnalizacji akustycznej.
Odmienny fragment programu jest następujący:

Kod: Zaznacz cały

static void OptionService ( uint8_t KeyCode )
{
  /*-------------------------------------------------------------------------*/
  ClrScrLCD ( ) ;
  switch ( KeyCode )
  {
    case StandKey0Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey0Text ) ;
      MountExtraKeybService ( KeybBeepOn ) ;
      break ;
    case StandKey1Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey1Text ) ;
      break ;
    case StandKey2Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey2Text ) ;
      break ;
    case StandKey3Code                  :
      WriteTextLCDFlash ( ( uint8_t * ) StandKey3Text ) ;
      MountExtraKeybService ( NULL ) ;
      break ;
  } /* switch */ ;
} /* OptionService */

Załącznik: projekt dla AVRSTUDIO
Kbd_avr3.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » sobota 20 maja 2017, 00:29

Architektura von Neumanna (ARM7TDMI) – przykład użycia

kbdvn_ex1-i00.jpg


Kontynuując rozważania dotyczące obsługi prostych klawiatur przyciskowych należy wskazać na pewne istotne różnice wynikające z odmienności architektury. Pomimo, że algorytm jest dokładnie identyczny, to w tekście zapisu programu w języku C występują pewne różnice. Mikrokontrolery z rodziny AVR są budowane zgodnie z architekturą harwardzką, zaś mikrokontrolery ARM (7TDMI), którego przedstawicielem jest LPC2138 posiada architekturę określaną jako von Neumanna. Różnica polega na stworzeniu różnych przestrzeni adresowych przewidzianych dla kodu programu oraz danych. W przypadku mikrokontrolerów AVR są dwie oddzielne przestrzenie: przestrzeń przeznaczona na kod programu (identyfikowaną jako pamięć FLASH) oraz przestrzeń przeznaczoną na dane (zmienne programu, stos w programie w tym również zawierają się wszystkie rejestry sterujące i konfiguracyjne mikrokontrolera). Bezpośrednim skutkiem tego rozdzielenia przestrzeni jest oddzielny zestaw instrukcji maszynowych do pobierania danych z przestrzeni RAM (jak odczyt i zapis zawartości zmiennych) i przestrzeni FLASH (jak odczyt stałych umieszczonych w pamięci FLASH). W mikrokontrolerach opartych na architekturze von Neumanna również rozróżnia się pamięć na kod programu (pamięć FLASH) jak i pamięć zmiennych (umiejscowiona w RAM) jednak należą one do tej samej przestrzeni adresowej, czyli z punktu widzenia działania mikrokontrolera do odczytu zawartości zmiennej znajdującej się w pamięci RAM oraz do odczytu wartości stałej zapisanej w pamięci FLASH używane są dokładnie te same instrukcje maszynowe. Z kolei kompilatory języka C są „filozoficznie” przystosowane do architektury von Neumanna. Oznacza to, że kompilator nie „zna pojęcia rozdzielenia” przestrzeni adresowych i wygeneruje kod programu odpowiadający przypadkowi jakby zmienna była umieszczona w przestrzeni RAM.
Tych problemów nie ma w przypadku architektury von Neumanna, gdyż to rozwiązania jest wręcz naturalne dla kompilatora C. Oczywiście w tym przypadku również warto pokusić się o „optymalizację” zajętości pamięci RAM (bo to często jest towar deficytowy). Sprowadza się to do zastosowania specjalnego kwalifikatora przy deklaracji zmiennej (słowo kluczowe const). Oczywiście ma to również pewne implikacje przy używaniu takich zmiennych, jednak z racji jednolitej adresacji zmiennych w pamięci RAM jak i stałych obszarów w obrębie kodu programu, nie są potrzebne specjalizowane funkcje do sięganie do tych zmiennych.
Do prezentacji działania algorytmu w przypadku mikrokontrolerów ARM będzie wykorzystany zestaw ZL7ARM („Moduł DIP z mikrokontrolerem LPC213x (rdzeń ARM7TDMI-S)”) oraz ZL9ARM („Płytka bazowa dla modułów dipARM z mikrokontrolerami LPC213x/214x”). Sięgając do dokumentacji tych zestawów znajdujemy następujące informacje:
kbdvn_ex1-i01.png
kbdvn_ex1-i02.png
kbdvn_ex1-i03.png

Oznacza to, z punktu widzenia mikrokontrolera, że przyciski mają następującą lokalizację w przestrzeni zasobów mikrokontrolera:
Przycisk ↔ Położenie w przestrzeni zasobów mikrokontrolera
S1 ↔ P0.4
S2 ↔ P0.5
S3 ↔ P0.6
S4 ↔ P0.7
oraz moduł wyświetlacza LCD:
Wyprowadzenie modułu LCD ↔ Położenie w przestrzeni zasobów mikrokontrolera
RS ↔ P0.31
R/W ↔ Zwarty na stałe do masy
E ↔ P0.30
D0 ↔ P1.16
D1 ↔ P1.17
D2 ↔ P1.18
D3 ↔ P1.19
D4 ↔ P1.20
D5 ↔ P1.21
D6 ↔ P1.22
D7 ↔ P1.23
Pacjent do badań:
kbdvn_ex1-i04.jpg

Pozostaje jedynie zaeksperymentować z oprogramowaniem. No cóż … zadziałało.
kbdvn_ex1-i05.jpg
kbdvn_ex1-i06.jpg


W sensie algorytmu działanie i funkcjonalność jest identyczna jak w przypadku wyżej opisanego wariantu dla mikrokontrolera AVR. W sensie zapisu występują różnice, które dotyczą jedynie implikacji wynikających w różnic w architekturze mikrokontrolera.
W implementacji modułu obsługi klawiatury znajduje się (we fragmencie):

Kod: Zaznacz cały

typedef struct {
        volatile KBDCyclicRecT                   KBDCyclic ;
                 ExtraKeybReadServiceProcType    ExtraKeybReadService ;
                 HardwareKeybReadServiceProcType HardwareKeybReadService ;
                 StoreKeyServiceProcType         StoreKeyService ;
           const KeybEncoderRecT *               StandEncodeTable ;
           const KeybEncoderRecT *               LongEncodeTable ;
                 KeybStateType                   KeybState ;
                 USHORT                          KeybDelayCt ;
                 UCHAR                           LastKeybInp ;
                 UCHAR                           BitMask ;
                 UCHAR                           LongEncodeTableSize ;
                 UCHAR                           StandEncodeTableSize ;
               } KeybModuleInstanceType ;
Porównując z wariantem dla mikrokontrolerów AVR łatwo można dostrzec różnice. Podobnie:

Kod: Zaznacz cały

static void DecodeKey ( UCHAR KbdCode ,
                        UCHAR LongDelayMode )
{
  UCHAR Loop ;
  UCHAR TableSize ;
  const KeybEncoderRecT * MemAddress ;
  /*----------------------------------------------------------------*/
  if ( LongDelayMode )
  {
    MemAddress = KeybModInstance . LongEncodeTable ;
    TableSize  = KeybModInstance . LongEncodeTableSize ;
  } /* if ... */
  else
  {
    MemAddress = KeybModInstance . StandEncodeTable ;
    TableSize  = KeybModInstance . StandEncodeTableSize ;
  } /* if ... else */ ;
  if ( TableSize && MemAddress )
  {
    for ( Loop = 0 ; Loop < TableSize ; Loop ++ )
    {
      if ( KbdCode == MemAddress -> InpCode )
      {
        KeybModInstance . StoreKeyService ( MemAddress -> OutCode ) ;
        return ;
      } /* if */ ;
      MemAddress ++ ;
    } /* for */ ;
  } /* if */ ;
} /* DecodeKey */

Odmienny zapis nie zmienia jednak faktu, że jest w powyższej funkcji realizowana identyczna funkcjonalność.
W przykładzie prezentującym działanie algorytmu odmiennie zapisywane są stałe określające przekodowanie znaków:

Kod: Zaznacz cały

static const KeybEncoderRecT StandEncodeTabe [ StandEncodeTabeSize ] =
{
/* 00 */ { ( 1 << LogicL0Key )                       , StandKey0Code } ,
/* 01 */ { ( 1 << LogicL1Key )                       , StandKey1Code } ,
/* 02 */ { ( 1 << LogicL2Key )                       , StandKey2Code } ,
/* 03 */ { ( 1 << LogicL3Key )                       , StandKey3Code } ,
/* 04 */ { ( 1 << LogicL0Key ) | ( 1 << LogicL1Key ) , StandKey01Code } ,
/* 05 */ { ( 1 << LogicL0Key ) | ( 1 << LogicL2Key ) , StandKey02Code } ,
/* 06 */ { ( 1 << LogicL0Key ) | ( 1 << LogicL3Key ) , StandKey03Code } ,
/* 07 */ { ( 1 << LogicL1Key ) | ( 1 << LogicL2Key ) , StandKey12Code } ,
/* 08 */ { ( 1 << LogicL1Key ) | ( 1 << LogicL3Key ) , StandKey13Code } ,
/* 09 */ { ( 1 << LogicL2Key ) | ( 1 << LogicL3Key ) , StandKey23Code } ,
} ;

Inny mikrokontroler → inna architektura → inne peryferale. To również implikuje inny sposób obsługi programowej (niemniej jednak nadal występują filozoficznie identyczne działania: potrzebna jest obsługa przerwań od upływu czasu, w której obsługiwana jest klawiatura).
W tym konkretnym przypadku wystąpiła konieczność "przemapowania" pozycji bitowych odpowiadających przyciskom. Moduł, z obsługą do ośmiu przycisków, zwraca wynik jako dane 8-bitowe. Jeżeli przyłączenie przycisków wykracza poza ten zakres, zachodzi konieczność "przemieszczenia" bitów odpowiadających wczytanym przyciskom.
kbdvn_ex1-i07.png

W prezentowanym przykładzie struktura bajtu obejmuje wszystkie przyciski i jest możliwe bezpośrednie zwrócenie wczytanych danych. Jednak przykład "przekłada" przyciski.

Kod: Zaznacz cały

static UCHAR HardwareKeybRead ( void )
{
  ULONG RealKeybData ;
  UCHAR KeybData ;
  /*----------------------------------------------------------------*/
  RealKeybData = InpKeybReg ;
  KeybData = 0xFF ;
  if ( ! ( RealKeybData & TechKey0Pin ) )
    KeybData &= ~ LogicKey0Pin ;
  if ( ! ( RealKeybData & TechKey1Pin ) )
    KeybData &= ~ LogicKey1Pin ;
  if ( ! ( RealKeybData & TechKey2Pin ) )
    KeybData &= ~ LogicKey2Pin ;
  if ( ! ( RealKeybData & TechKey3Pin ) )
    KeybData &= ~ LogicKey3Pin ;
  return ( KeybData ) ;
} /* HardwareKeybRead */


Załącznik: projekt WIN ARM:
Kbd_arm.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » niedziela 21 maja 2017, 02:12

Architektura harwardzka (AVR) – przykład użycia numer 4
kbdh_ex4-i00.jpg

Inny przykład użycia. Środowisko sprzętowe zbudowane jest na bazie modułu M32 ([AVR] Moduł do eksperymentów z ATMEGA32
http://microgeek.eu/viewtopic.php?f=43&t=1065"%20target="_blank"%20target="_blank"%20target="_blank).
Z tego modułu wykorzystane są:
  • zespół diod LED przyłączonych do portu B
  • interfejs RS232 do komunikacji z komputerem PC (emulator terminala HyperTerminal).
Powyższy moduł ma dobudowaną 8-bitową klawiaturę przyłączoną do portu A (wszystkie wyprowadzenia portu zajmują przyciski klawiatury).
Zadaniem prezentowanego programu jest włączenie jednej diody LED skojarzonej z danym przyciskiem. Dodatkowo do klawiatury „domontowana” jest klawiatura z komputera PC. Wysłanie przez emulator terminala jednego znaku (cyfry od 0 do 7) jest przekształcane w oprogramowaniu mikrokontrolera AVR na odpowiedni znak i włożenie go do bufora klawiatury. Mikrokontroler AVR identycznie reaguje na naciśnięcie jednego z przycisków klawiatury jak i naciśnięcie właściwego znaku na klawiaturze komputera PC.
Układ badawczy:
kbdh_ex4-i01.jpg
kbdh_ex4-i02.jpg

Przedłużacz klawiatury na komputerze PC
kbdh_ex4-i03.png

Z istotnych fragmentów programu
  • Zaprogramowanie peryferali: timer 0 do generowania przerwań od przepełnienia licznika, UART do komunikacji z PC-tem o parametrach 9600 N81

Kod: Zaznacz cały

static void HardwareInit ( void )
{
  TIMSK = ( 1 << TOIE0 ) ;
  TCCR0 = ( 1 << CS01 ) ;
  UCSRA = 0 ;
  UCSRB = ( 1 << RXCIE ) | ( 1 << TXCIE ) | ( 1 << RXEN ) | ( 1 << TXEN ) ;
  UCSRC = ( 1 << URSEL ) | ( 1 << UCSZ1 ) | ( 1 << UCSZ0 ) ;
  UBRRH = ( uint8_t ) ( ( USART_Speed >> 8 ) & 0xFF ) ;
  UBRRL = ( uint8_t ) ( USART_Speed & 0xFF ) ;
} /* HardwareInit */

  • Sprzętowe wczytanie klawiatury → cały PORT A

Kod: Zaznacz cały

uint8_t HardwareKeybRead ( void )
{
  uint8_t KeybData ;
  /*-------------------------------------------------------------------------*/
  KeybData = KeybPortInput ;
  return ( KeybData ) ;
} /* HardwareKeybRead */

  • Zainicjowanie środowiska, port klawiatury jest skonfigurowany jako port wejściowy ze wstępnym podpolaryzowaniem do logicznych jedynek.

Kod: Zaznacz cały

static void EnvirInit ( void )
{
  /*-------------------------------------------------------------------------*/
  KeybPortDirection = 0 ;
  KeybPortConfig = 0xFF ;
  LEDPortDirection = 0xFF ;
  LEDPort = 0 ;
} /* EnvirInit */

  • Funkcja do "konsumpcji" wczytany znaków z klawiatury AVR-ra.

Kod: Zaznacz cały

static void OptionService ( uint8_t KeyCode )
{
  /*-------------------------------------------------------------------------*/
  switch ( KeyCode )
  {
    case StandKey0Code                  :
      LEDPort = ( 1 ) << L0Key ;
      break ;
    case StandKey1Code                  :
      LEDPort = ( 1 ) << L1Key ;
      break ;
    case StandKey2Code                  :
      LEDPort = ( 1 ) << L2Key ;
      break ;
    case StandKey3Code                  :
      LEDPort = ( 1 ) << L3Key ;
      break ;
    case StandKey4Code                  :
      LEDPort = ( 1 ) << L4Key ;
      break ;
    case StandKey5Code                  :
      LEDPort = ( 1 ) << L5Key ;
      break ;
    case StandKey6Code                  :
      LEDPort = ( 1 ) << L6Key ;
      break ;
    case StandKey7Code                  :
      LEDPort = ( 1 ) << L7Key ;
      break ;
  } /* switch */ ;
} /* OptionService */

  • Reakcja programu na odebrany znak z kanału UART → wkłada odpowiedni znak do kolejki FIFO klawiatury

Kod: Zaznacz cały

static void ProcessSerialChar ( uint8_t Ch )
{
  /*-------------------------------------------------------------------------*/
  switch ( Ch )
  {
    case '0'  :
      StoreKey ( StandKey0Code ) ;
      break ;
    case '1'  :
      StoreKey ( StandKey1Code ) ;
      break ;
    case '2'  :
      StoreKey ( StandKey2Code ) ;
      break ;
    case '3'  :
      StoreKey ( StandKey3Code ) ;
      break ;
    case '4'  :
      StoreKey ( StandKey4Code ) ;
      break ;
    case '5'  :
      StoreKey ( StandKey5Code ) ;
      break ;
    case '6'  :
      StoreKey ( StandKey6Code ) ;
      break ;
    case '7'  :
      StoreKey ( StandKey7Code ) ;
      break ;
    default :
      ;
  } /* switch */ ;
} /* ProcessSerialChar */



Załącznik: projekt w AVRSTUDIO
Kbd_avr4.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1260
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: Obsługa prostej klawiatury

Postautor: gaweł » poniedziałek 22 maja 2017, 15:48

Krótki filmik z powyższego przykładu:
keybdemo.mov
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Inne mikroklocki, również peryferyjne”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 4 gości