Ads_700x200

tme

środa, 10 października 2012

Obsługa klawiszy - drgania styków CD...2


Witam,

Nadszedł czas na kolejny artykuł z cyklu drgań styków przedstawianych prawie wszędzie w krzywym zwierciadle. Ostatnio zajmowaliśmy się wyjaśnieniem od podstaw dlaczego tak wielu ludzi błędne przedstawia zagadnienia związane drganiami styków w programach, które piszemy dla mikrokontrolerów obojętnie jakiej rodziny.

Drgania styków ? - to BAJKI ! więc jak to jest naprawdę?


W tym temacie omówiłem podstawy związane ze zjawiskiem drgań styków. Na podstawie tegoż artykułu wiemy już iż w zdecydowanej większości przypadków drgania styków powstałe w pierwszej fazie, przy wciśnięciu klawisza, można w zasadzie zignorować i nie martwić się o ich występowanie czy efekty uboczne w większości naszych prostych aplikacji. Dzisiaj właśnie od tego zaczniemy, od podstaw tworzenia najprostszych z możliwych ale za to absolutnie skutecznych funkcji do tych celów. Zobaczysz, w jak prosty sposób można napisać najprostszą obsługę klawisza tak aby nie występowało zjawisko, które błędnie nazywane jest przez wiele osób efektem drgania styków i to tych drgań przy wciśnięciu klawisza. Sam uśmiejesz się jak to może być proste jeśli to dobrze przemyśleć. Weźmy zatem za cel przykład z poprzedniego artykułu. Nasze pierwsze zadanie to napisanie kodu obsługi klawisza, który po każdorazowym wciśnięciu ma zmieniać stan diody LED na przeciwny. Innymi słowy mówiąc, dioda ma pięknie zapalać się i gasnąć, bez żadnego migotania, bez najmniejszego kłopotu i to niezależnie od tego jak długo wciśniemy klawisz. Dodatkowo jeśli klawisz będzie wciśnięty non stop, to dioda LED ma nie zmieniać swojego stanu, zatem do rzeczy ....



Zanim przejdziemy do kodu, rozważmy ważną kwestię. Aby w minimalnym stopniu, poprawnie obsłużyć nasz klawisz, musimy wybrać moment wykonywania naszej akcji (moment zmiany stanu diody LED). Zakładając, że użytkownik programu będzie miał prawo wciskać ten klawisz z dowolną szybkością, stosując dowolny rodzaj klawisza, musimy rozpoznać przynajmniej dwa stany. Jeden z nich to moment WCIŚNIĘCIA, natomiast drugi to moment ZWOLNIENIA klawisza. Załóżmy zatem dalej, że nasza akcja zostanie wywołana bezpośrednio w momencie wciśnięcia klawisza a wykrycie momentu jego zwolnienia użyjemy pomocniczo do zmiany naszej maszyny stanów. Pamiętamy jednocześnie, że obsługa ta, ma być absolutnie nieblokująca, co oznacza, że w programie, w jednej pętli głównej możemy skorzystać z obsługi większej ilości takich klawiszy (więcej niż jeden), a jednocześnie bez żadnych opóźnień wykonywać inne operacje. To zwykle marzy się każdemu początkującemu, tylko że wciąż do głowy przychodzą pytania typu "w jaki sposób pisać funkcje nieblokujące?". My w tym momencie od razu zaczniemy od takiego przykładu. Żadnych DELAY'ów ;) i proszę bardzo - okazuje się, że można:

--------------------------------------

--------------------------------------

W powyższym kodzie, dziwnym może wydawać się ten _delay_ms(10), ale tak jak w opisie okazuje się, że wprowadziłem po prostu sztuczne opóźnienie po załączeniu podciągania programowego, bo czas narastania stanu wysokiego był tak powolny, że szybciej zadziałał pierwszy IF i po starcie programu, okazywało się, że klawisz jest prawie zawsze wciśnięty. Gdy dodamy byle jakie opóźnienie albo zewnętrzny rezystor np 4,7K to można zapomnieć o tym _delay'u ;)
Dobrze cóż widzimy w tym kodzie ? Za każdym obiegiem pętli można powiedzieć, że sprawdzamy dwa warunki. W zasadzie jeden ale rozdzielony słówkiem else. W pierwszym momencie sprawdzamy czy nasza zmienna pomocnicza do obsługi klawisza key_lock, jest równa zero oraz czy wciśnięty jest klawisz? Jeśli tak to najpierw nadajemy naszej zmiennej key_lock wartość = 1 a następnie wykonujemy naszą najprostszą akcję, czyli zmieniamy stan diody LED. Uwaga w tym momencie jak wyraźnie widzisz - w ogóle nie interesują nas drgania styków ;) .... po co sobie nimi głowę zawracać przy takiej aplikacji? Nawet jeśli wystąpią, to na pewno nie samoistnie, tylko w przypadku wciśnięcia klawisza - więc mogą sobie drgać te styki ;) Jeżeli jednak klawisz nie był wciśnięty to sprawdzamy po else, czy z kolei klawisz jest zwolniony a także czy zmienna key_lock posiada już wartość = 1. Jeśli nie, to naturalnie obsługa klawisza się kończy. Jeśli zaś wcześniej została wykonana akcja, i dojdzie do ponownego obiegu pętli głównej, to z uwagi na to, że key_lock jest już = 1 pierwsza część warunku się nie wykona (nawet jeśli nadal trzymamy wciśnięty klawisz. Taka sytuacja będzie się powtarzała aż go zwolnimy. Wtedy po którymś kolejnym obiegu, nie wykona się pierwsza część warunku ale już będzie się mogła wykonać druga część, ponieważ key_lock =1 i jednocześnie jest już klawisz zwolniony. Skoro tak, to zerujemy key_lock, i znowu w pętli można czekać na następne wciśnięcie klawisza. UWAGA! niestety w tym momencie, gdy zwalniamy klawisz, także mogą wystąpić drgania styków, albo efekt drgań styków spowodowany np zbyt powolnie narastającym zboczem (porównaj ostatni oscylogram z poprzedniego artykułu gdy dołożyliśmy np kondensator 100nF).




Po środku zbocza (żółty kolor - opisany fragment jako unknown) mogą wystąpić stany nieustalone i to może zostać zinterpretowane jako drgania styków (opisane jako: Source of error). Z tego powodu, choć rzadko to zdarzyć się może iż nasz sposób jeszcze będzie czasem zawodny. Sprawdź sam, ten kod i wykonaj sporo prób uderzając szybko i niedbale palcem w mikro-przełącznik. Mnie czasem udawało się wywołać przykry efekt - gdzie dioda LED np szybko mignęła ale natychmiast zgasła przy zwalnianiu klawisza. To właśnie te drgania (opadające powoli zbocze) może być powodem. Czy to oznacza, że musimy sięgnąć po jakiś DELAY ??? Ależ skąd!!! I teraz znowu odwołam się do poprzedniego artykułu. Pod koniec pokazałem na oscylogramach jak krótki stosunkowo może być ten czas drgań przy zwalnianiu przycisku, gdy np nie zastosujemy kondensatora 100nF. A skoro tak, to wykorzystajmy naszą zmienną key_lock w bardziej zaawansowany sposób. Do tej powy (w powyższym kodzie) zmieniała ona tylko swój stan albo na 0 albo na 1. My natomiast wykorzystamy ją jako mikro programowy timer ;) do realizacji opóźnienia, dzięki któremu przeczekamy te drgania styków przy zwalnianiu klawisza ;) Wystarczy, że będziemy tę zmienną dalej inkrementować aż sama dojdzie do wartości 255 i w następnym kroku się automatycznie wyzeruje. Tym sposobem stworzymy kolejną ale nadal super krótką procedurę obsługi klawisza, która nie wniesie żadnego DELAY'a. Nawet nie zużyjemy ŻADNEGO timera sprzętowego w tym celu! Spójrz sam, ale przedstawiam tu tylko samą pętlę główną ponieważ tylko ją lekko zmodyfikowałem w porównaniu do kodu przedstawionego wyżej:
--------------------------------------
--------------------------------------
Hmmm co się tu dzieje? ;) język C jest genialny pod tym względem. Warunek po else nadal działa, ale tylko wtedy gdy klawisz jest zwolniony i jednocześnie zmienna pomocnicza key_lock ma wartość różną od zera. A przecież posiada wartość różną od zera po zainicjowaniu wartością = 1 przy wykonaniu akcji. Dzięki drugiej części warunku nadal jest inkrementowana zmienna key_lock. (jak widać na obrazku niżej)





Dzieje się tak aż 256 razy. Inkremetacja o jeden, za każdym obiegiem pętli, a zatem w żaden sposób nie spowalniamy jej biegu, nie mniej jednak, jeśli teraz po zwolnieniu klawisza trwają drgania styków, to w trakcie zanim zmienna zwiększy się od 1 do 255 i zera ... upłynie akurat tyle czasu aby one zniknęły! Tę funkcję możesz już testować do bólu. Nie uda się jej złapać na błędnym działaniu - przynajmniej jeśli chodzi o mikro-przełączniki. Gdybyś zaś zechciał skorzystać z jakiegoś wielkiego mechanicznego stycznika, gdzie czas drgań przy zwolnieniu może być sporo dłuższy to nie ma kłopotu. Wystarczy aby zmienna key_lock była 16-bitowa a następnie zainicjalizować ją wartością nie = 1 ale np 50000, tak aby licznik w niej dobiegł do wartości 65535 (czyli do końca), wykonując tym razem o wiele więcej obiegów pętli głównej a jednocześnie wydłużając czas oczekiwania na zniknięcie drgań styków przy zwolnieniu klawisza. Tak ta modyfikacja może wyglądać:
--------------------------------------
--------------------------------------
Próbuj kody do woli, przeprowadzaj testy i ciesz się tą funkcjonalnością. Zdajesz sobie jednak sprawę, że na razie nauczyliśmy się tylko reagować na wciśnięcie klawisza, tymczasem przydałoby się nieraz mieć możliwość reakcji na jego zwolnienie, po to aby no przytrzymać go nieco dłużej - i akcja miałaby się wykonać bezpośrednio po jego zwolnieniu. Jednym słowem mówiąc, teraz chcemy reakcji nie na zdarzenie klawisza o nazwie PRESS (wciśnięcie) tylko na PUSH_UP (zwolnienie). Proszę bardzo:
--------------------------------------

--------------------------------------
Teraz już pewnie sam się domyślasz, że można byłoby tak przerobić tę obsługę żeby mieć akcję w odpowiedzi zarówno na wciśnięcie jak i zwolnienie klawisza ;) A zatem, gdy wciskamy akcja wykonuje się natychmiast, za to możemy nadal trzymać wciśnięty klawisz i dopiero gdy go zwolnimy to można wykonać drugą akcję. Niestety nie ma tu na razie żadnej koordynacji czasowej a więc w takiej postaci byłaby to nieco ułomna obsługa klawisza. Tym zajmiemy się później. Teraz natomiast nauczmy się kolejnej rzeczy. Od razu takiego myślenia strukturalnego (nie ma to związku stricte ze strukturami w C) .... ale chodzi o to aby od razu umieć tak pisać fragmenty kodu w main aby dało się je wyprowadzić ładnie jako funkcje na zewnątrz pętli i głównej funkcji main(). Zanim tego dokonamy, napiszmy sobie obsługę dwóch klawiszy aby sprawdzić czy to nadal będzie tak samo wyśmienicie działać ? ;)
--------------------------------------
--------------------------------------
proszę bardzo, teraz jeden klawisz wykonuje nam akcje po wciśnięciu a drugi po zwolnieniu. Jeśli to uruchomimy do testu na własnym zestawie czy płytce stykowej to pięknie wyjdzie działanie w tzw "praniu" ;) .... W związku z tym przejdźmy teraz do wersji kodu, gdzie wyprowadzimy sobie dwie niezależne funkcje do obsługi klawiszy: key_press() key_push_up() ale uwaga, od razu skorzystamy z super możliwości języka C i tak napiszemy te funkcje, aby można było do ich wnętrza przekazać zewnętrzne funkcje użytkownika, które będą wykonywane jako akcje. Mogą to być dowolne funkcje my jednak dla testów, napiszemy je tak aby nadal tylko zmieniały stan dwóch różnych diod LED na przeciwny. Utworzymy zatem funkcje: change_led1() change_led2() Spójrzmy zatem na właściwy kod programu:
--------------------------------------
--------------------------------------
Proszę teraz zwrócić uwagę jak mocno uporządkowaliśmy pętlę główną while(1). Wciąż wywoływane są dwie nasze funkcje, działają w sposób nieblokujący, automatycznie wywołują akcje przekazane do nich w postaci wskaźników do funkcji dzięki czemu pętla główna może jeszcze wykonywać setki innych czynności równocześnie. Naturalnie pod warunkiem, że kolejne czynności to także będą funkcje nieblokujące! W kolejnym i ostatnim odcinku z tego cyklu spróbujemy się jeszcze zastanowić jak wprowadzić tu zależności czasowe z użyciem już typowego timera programowego a to wszystko mam nadzieję, że teraz pozwoli dużo lepiej zrozumieć istotę najbardziej rozbudowanej już funkcji jaką jest SuperDebounce() opisaną w książce pt: "Mikrokontrolery AVR Język C Podstawy programowania".

zapraszam na kolejną i ostatnią zarazem część tego cyklu artykułów:

http://mirekk36.blogspot.com/2012/10/obsuga-klawiszy-zwoka-czasowa-cd3.html




część I

część II






.

59 komentarzy:

  1. Jeszcze do dzisiaj w moim projekcie ze sterowaniem 230V miałem identyczny problem jeżeli chodzi o to opóźnienie związane z podciąganiem przycisków. Nie mogłem dojść o co chodzi, bo praktycznie za każdym razem po podłączeniu zasilania do uP żarówka paliła się mimo, że optotriak był wyłączony na samym początku głównej funkcji programu int main(void).
    Zgodnie z wykonanymi operacjami na portach - no była ogromna konsternacja, bo triak teoretycznie nie miał prawa być otwarty, a odbiornik świeci.
    Tym razem przyciski podłączone były bezpośrednio pod procka i opóźnienie wykonywania pętli głównej programu załatwiło problem.
    Teraz już rozumiem, że faktycznie procesor nie zdążył jeszcze podciągnąć do VCC klawisza, więc stan na tym pinie musiał być niski, stąd przycisk był rzekomo wciśnięty i żarówka się paliła.
    Naprawdę fajny cykl artykułów z tymi klawiszami.
    Czekam koniecznie na ostatni finałowy odcinek. :)
    Dzięki!

    OdpowiedzUsuń
  2. pogromca mitów! :) a mam takie pytanie czy można by tak urozmaicić funkcję key_press, aby była "idiotoodporna"? w moim przypadku znaczy to tyle, że jak mam 1. funkcje przypisaną do 1. przycisku, wykonuje się i ktoś zamiast ją wyłączyć (drugi raz nacisnąć 1. przycisk) naciska 2. przycisk i uruchamia 2. funkcję, co jest niezgodne z przyjętym założeniem.. istnieje może jakiś cudowny kruczek elektroniczek, który w tym wypadku najpierw wyłączy 1. funkcję a następnie włączy 2. funkcję? dla projektu z piętnastoma przyciskami - funkcjami ;)

    OdpowiedzUsuń
    Odpowiedzi
    1. Dla projektu z 15 przyciskami to już chyba warto byłoby się pokusić o obsługę np klawiatury matrycowej a nie w ten sposób - nie sądzisz ?

      Usuń
    2. jak najbardziej tak, ale w moim przypadku przyciski muszę mieć porozmieszczane na płytce w różnych miejscach i odstępach, żeby potem na obudowie były oznaczone jako różne grupy funkcjonalne

      Usuń
    3. p.s. pobawię się jakąś zmienną globalną i ifem sprawdzającym tą zmienną przed wykonaniem funkcji key_press... zobaczymy...

      Usuń
    4. No to może nie zrozumiałem twojego pytania do końca, ale dlatego zapraszam do stawiania takich pytań na naszym forum, gdzie można pięknie przedstawić fragment kodu , którym próbujesz coś zrobić i wskazać co sprawia ci kłopot. Na pewno chętnie pomogę. A tu na blogu nie ma jak poprawnie przedstawić kodu w komentarzach. Zapraszam więc serdecznie na forum ok?

      www.forum.atnel.pl

      Usuń
  3. Na ostatnim listingu w funkcji key_push_up w linijce:
    "else if( key2_lock && key_press ) {"
    powinno być *klock zamiast key2_lock . Taki mały błąd a jednak istotny jeśli funkcja ma być uniwersalna dla kilku przycisków :)

    OdpowiedzUsuń
    Odpowiedzi
    1. Tak święta racja ;) dziękuję bardzo za uwagę i już poprawiam. Robiłem to na zasadzie kopiuj wklej i dla jednego klawisza niechcący działało ze względu na pozostawione zmienne globalne.

      To się nazywa UWAGA merytoryczna ;) jeszcze raz dziękuję.

      Usuń
  4. Witam. Jestem poczatkujący w dziedzinie mikrokontrolerów i języka C:) Mam 2 pytania:
    1)gdy deklarujemy uint8_t key_lock; to automatycznie key_lock=00000000 (binarnie)?? bo później nie jest tej zmiennej nic przypisane a brana jest do warunku w if.
    2) skąd wiemy, ze gdy klawisz jest wciśniety to !(PINC & KEY1) da ,,prawde,, i zostanie wykonana instukcja?? Bo zeby dała prawde to musi byc różne od 0.

    OdpowiedzUsuń
    Odpowiedzi
    1. 1. Widzisz - takiego pytania byś nawet nie zadał po przeczytaniu książki:

      http://atnel.pl/mikrokontrolery-avr-jezyk-c.html

      bo to jak inicjalizowana jest zmienna globalna wynika ze standardu języka C. I nie zrozum mnie źle - nie chodzi mi o to aby cię zniechęcić do zadawania pytań - jedynie chciałbym zwrócić uwagę, że ta książka na prawdę pomogłaby ci szybko wystartować w C. A teraz odpowiem na pytanie:

      Tak zmienna key_lock będzie = 0 w momencie startu programu tak jak wszystkie inne zmienne globalne ok ? ;)

      2. No widzisz warunek (PINC & KEY1) da nam wartość TRUE jeśli klawisz jest NIE WCIŚNIĘTY natomiast wartość FALSE gdy klawisz będzie wciśnięty. Wykrzyknik dokonuje negacji logicznej co sprowadza się że całe wyrażenie

      !(PINC & KEY1)

      przyjmie wartość TRUE gdy klawisz będzie wciśnięty (czyli zwarty do GND) - teraz jaśniej ? ;) ale oczywiście jak to sobie rozpisać i te przesunięcia bitowe - na prawdę - polecam jeszcze raz książkę bo tam wszystko pięknie pokazałem - sprawdź sam.

      Usuń
    2. Czy dobrze rozumiem, ze:
      1) PINC mówi nam jakie są stany na PORTC?
      2) do sprawdzenia warunku w if bedzie on (PINC) = 1xxxxxxx (x - nie wiemy czy 0 czy 1) i w ostatecznosci warunek wyglada tak:
      if(key_lock==0 && 01111111)?
      key_lock da TRUE no i teraz nie wiem jak odczytac ten drugi warunek :/
      3) jak dałem zmienną key_lock jako lokalną w main to też działa poprawnie. Czy mam przez to rozumieć, że zmienne lokalne też w momencie statu programu równają się 0?

      Usuń
    3. 1. Nigdy w życiu - proszę może poczytać sobie to:

      http://mirekk36.blogspot.com/2012/01/avr-porty-wewy-dir-podstawy.html

      z tego bloga to się wyjaśni więcej. PINC to rejestr wejściowy a PORTC to rejestr wyjściowy za to DDRC to rejestr kierunku.

      2. źle kolega to rozpisuje - jednak odeślę do książki - bo tak nie wiem jak mam to opisać - przykro mi.

      3. Zmienne lokalne mogą mieć przypadkowo wartość = 0 , takie zmienne trzeba zawsze inicjalizować.

      Usuń
    4. To jednak będę musiał się w tą książkę zaopatrzyć ;)

      Usuń
    5. Na prawdę szczerze mogę polecić. A w razie czego w trakcie czytania czy po - jestem do dyspozycji jeśli chodzi o pytania ;)

      Usuń
  5. Rozważam kod z pierwszego listingu.
    Jeżeli klawisz został wciśnięty (zwarty do masy) i key_lock jest 0 to nadaj key_lock wartość 1 i wykonaj działania, a jeżeli klawisz puszczony( podciągnięty do zasilania) i ley_lock jest 1 to wpisz do key_lock wartość 0.
    Jeżeli drgania styków istnieją i wg. pomiarów trwają od 1us do 50 us to znaczy ,że w ciągu tego czasu stany zmieniają (0<>1).
    Przy częstotliwości zegara 8Mhz i 1 cykl zegarowy trwa 125ns.
    To teraz co będzie jeżeli:
    Klawisz naciśnięty = 0 -> key_lock=1 itd. następują drgania (klawisz przyjmie następnie wartość 1 to tak jakby był puszczony) a puszczony i key_lock=1 dają key_lock=0 i cała zabawa zaczyna się od początku.
    Proszę mnie poprawić gdzie leży błąd w moim rozumowaniu.

    OdpowiedzUsuń
    Odpowiedzi
    1. To jest po prostu piękny przykład aby zrozumieć co to w ogóle są drgania styków i dlaczego mało wspólnego mają one z tym co opisują dziesiątki różnych stron a także blog na którego się powołałem w pierwszym odcinku tego cyklu.

      ALE UWAGA! ... jak chcesz to zrozumieć to nie patrz tylko na pierwszy przykład - bo to wstęp aby zrozumieć mechanizm który omawiam dalej. I właściwy przykład o którym mówię to

      DRUGI rozumiesz - drugi a nie pierwszy ....


      A do największych mitów dochodzi gdy ktoś tam twierdzi, że np te drgania styków trwają całe dziesiątki ms (przy czym mówię tu o popularnych micro-switchach a nie "heblach" na stycznikach do 1000A.

      Więc biorąc ten DRUGI przykład to - jeśli key_lock=0 i przycisk zwarty do GND to wykonuje się "operacja" i w tym momencie następuje ZATRZASK! Jeśli by tak było jak myślisz - że teraz te wydumane (przy czym piszę wydumane w sensie straszenia na temat ich długości) drgania styków ... musiałyby spowodować że zatrzask PUŚCI, a tymczasem LIPA panie kochany - zatrzask trzyma ponieważ gorsze są stany nieustalone przy puszczaniu przycisku i nawet jeśli występują to keylock, się nie wyzeruje od razu - musi upłynąć czas na jego inkrementację i przekręcenie się na wartość = 0

      oczywiście przy większym taktowaniu niż 8MHz ten czas może być za krótki dlatego w kolejnym przykładzie pokazuję jak sobie z tym poradzić stosując zmienną 16-bitową ... chociaż to i tak jest na wyrost - ponieważ i tak zwykle "OPERACJA" wykonywana w ramach wciśnięcia klawisza trwa na tyle długo że PRZYSŁANIA te wszystkie DRGANIA styków, których wszyscy się tak boją przez te blogi ;) przeróżne.

      Usuń
  6. Tak,dokładnie tak jak piszesz drugi przykład to taki delay bez zawracania głowy procesorowi.
    Przy taktowaniu 8Mhz ,tak jak pisałem wcześniej ,wystarcza inicjować key_lock=100. Dziękuję za wyjaśnienie.
    Jedynie co mnie "dręczy" to czemu w dokumentacjach mikroprzełączników podany jest max. czas drgań np. 5 ms. Ciekawe czy to wynik badań czy wartość z kapelusza dużo większa ,żeby mieć "dupochron" na tych co będą się czepiać tych drgań.

    OdpowiedzUsuń
    Odpowiedzi
    1. Już ci wyjaśniam, tzn wyjaśniam tak jak ja to widzę. Wg mnie te 5 ms to nie "dupochron" .... chociaż może troszkę i tak ;)

      5 ms to dopuszczalny wg producenta MAKSYMALNY czas tzw efektu drgań styków. Czas ten tak na prawdę zwykle jest przy nowym klawiszu dużo krótszy, ale ... ale w trakcie życia klawisza może się wydłużać , na co wpływ ma chociażby starzenie się materiału, śniedzenie styków, czy inne "farfocle" że tak niefachowo się wyrażę ;) ...

      tyle, że powiedz mi kto dzisiaj będzie używał tak kiepskich klawiszy - bo tak ja na to patrzę. Zwróć uwagę co się dzieje, gdy przycisk jest już stary i wyrobiony. Wtedy ten czas stanowczo się wydłuża ale często spowodowany jest w ogóle jego mechanicznym zużyciem i raczej nie jest to do naprawy. Prędzej opłaca się wymienić taki klawisz niż babrać się w przygotowywanie procedur, które będę miały wziąć pod uwagę 5, 10, 20 a może i 30 ms drgań styków ... no sam powiedz?

      Dlatego to co piszesz wyżej, że wystarczy inicjować key_lock=100, to właśnie efekt doświadczeń praktycznych, aczkolwiek troszkę nawet krótki, hmm za krótki ... stąd moja propozycja przekręcenia się go - bo zlicza do 255 i dopiero wtedy się przekręca - a zatem mamy spory zapas czasowy na efekty starzenia się nawet takiego klawisza. Przy większym taktowaniu też warto ten zapas sobie zrobić. Czyli reasumując - podchodzić do not PDF z głową, a nie na zasadzie, że jak ktoś tak mówi - to zawsze trzeba to zrobić. Bo jak ci ktoś każe rękę w ogień włożyć to przecież nie włożysz ;)

      I jeszcze raz przypomnę - należy też wziąć pod uwagę samą AKCJĘ wywoływaną przez taką zaprezentowaną obsługę, bo akurat zapalenie diody LED to jeden cykl zegarowy, ale zwykle w programie to bywa w tym miejscu o wiele więcej linii kodu i czasu na to się zużywa prawda... ?

      A zatem nawet gdyby to było w ostateczności 1 ms, 3,4 a nawet 5 ms .... to po zatrzaśnięciu key_lock - już czochra nas fakt że styki drgają ;) i to jest fajne ...

      realizować to można na tysiąc różnych sposobów programowo, także na timerach sprzętowych co pokazuję w swojej książce ... choć to bywa mniej zrozumiałe dla początkujących. DLATEGO WŁAŚNIE postanowiłem przybliżyć to zagadnienie w nieco prostszy sposób ... i pokazać jedną z właściwych dróg jak to rozwiązywać. A nie JEDYNĄ NAJLEPSZĄ - co zarzucają mi na pewnym dziwnym blogu ;)

      Usuń
  7. Dorzucę ostatnie przemyślenie. Rzeczywiście dam key_lock=1 ponieważ czy czas będzie 125 us czy 5 ms to ja i tak nie zauważę różnicy w "akcji". Może to jednak zabezpieczyć przed zużywaniem styków i nieoczekiwanym rozjeżdżaniu się mojego MENU do którego używam tej funkcji. Dzięki za wyjaśnienie. A tak na marginesie programuję troszeczkę ale uwielbiam takie miejsca jak te gdzie ktoś tłumaczy jak "krowie na rowie" o co biega bez stosowania skrótów myślowych geniuszów ;)

    OdpowiedzUsuń
  8. W funkcjach key_press i key_push_up, w argumentach jest wskaźnik do zmiennych globalnych key_lock. Rozumiem, że przekazanie przez referencję jest po to by zachować wartość zmiennej między wywołaniami funkcji. Jednak zmienne te istnieją wyłącznie na użytek funkcji, na zewnątrz nie są w żaden sposób wykorzystane. Czy w związku z tym nie można by ich schować wewnątrz funcji z modyfikatorem static?

    OdpowiedzUsuń
    Odpowiedzi
    1. Pomyśl ... jeśli użyjesz ich jako zmiennych lokalnych ze słówkiem static i będziesz chciał używać np 2-3 takich funkcji w jednym czasie to co się stanie ? ... zmienna static będzie zawsze przechowywała stan tylko jednego klawisza. No chyba żebyś dla każdego klawisza pisał np za każdym razem nową funkcję, tzn o innej nazwie - to wtedy tak można zrobić.

      Usuń
    2. No tak, czułem, że gdzieś jest myk, ale nie przymyślałem tego do końca. A przecież widać to jak na dłoni. Należą mi się baty ;-) Dzięki.

      Usuń
    3. A tam zaraz baty ;) ... a myślisz, że ja w pierwszej wersji pomysłu sam się na to nie naciąłem ? ;)

      Usuń
    4. Nawet nie muszę myśleć, toż to oczywiste ;-)
      Za to pomyślałem, że tak doświadczony programista musiał mieć jakiś zamysł ale szukałem jakiś pokrętnych wyjaśnień.

      Usuń
    5. A tam jaki ze mnie doświadczony programista? jestem początkujący w C tyle że może np na tym polu ociupinkę dalej coś tam poćwiczyłem niż ty ... więc się nie przejmuj. A zapytać zawsze warto bo często albo osoba pytająca/zwracająca uwagę, czegoś się dowie albo czasem także ja. Wtedy uczymy się nawzajem ;)

      Usuń
  9. Witam,
    Chciałbym się upewnić czy dobrze myślę, że
    uint8_t key_lock;

    key_lock++
    kiedy zwiększy się od 1 do 255 i zera

    to już potem nie będzie się znowu zwiększać do 255 i zera i znowu, i znowu?
    Tylko jak już się raz przekręci to będzie 0b0000'0000?

    OdpowiedzUsuń
    Odpowiedzi
    1. No jak się może wciąż zwiększać i zwiększać .... jeśli jest uint8_t to przekręci się na ZERO i koniec ;) .... procedura obsługi klawisza może się powtórzyć

      Usuń
  10. Witam,
    teraz analizuję przykład
    //**************** ZWOLNIENIE PRZYCISKU - push_up
    nie mogę dojść:
    1) gdzie następuje zerowanie zmiennej key_lock? po tym jak wcisnę i zwolnię klawisz.
    2) jaka byłaby różnica gdyby w warunku if (!++key_lock) zmienić preinkrementację na postinkrementację - w działaniu programu jest taka, że nie można zmienić stanu przycisku, gdy zastosuję postinkrementację.

    Ad. 2. Próbowałem to zaobserwować pisząc sobie programik w C pod VS wyświetlający mi w konsoli wynik takiego warunku, ale w nim nie widać różnicy, czy zastosuję czy -post czy -pre inkrementację.
    -----------------------------------------------------------------------------
    int main()
    {
    unsigned char liczba = 1;

    while (1)
    {
    printf("Na poczatku liczba = %d\n", liczba);
    if(!liczba++)
    {
    //...
    }
    printf(" Tereaz liczba = %d\n", liczba);
    printf("-----------------------\n");
    Sleep(1000);
    }
    }
    -----------------------------------------------------------------------------

    OdpowiedzUsuń
    Odpowiedzi
    1. ad.1 - no właśnie w tym miejscu if( ! ++key_lock )

      ad.2 - masz może moją książkę ?

      http://atnel.pl/mikrokontrolery-avr-jezyk-c.html

      jeśli tak to przeanalizuj w niej rozdział o tych wariantach post i pre , inkrementacji i dekrementacji. Bo tu post-inkremetnacja nie zadziała

      Usuń
  11. Witam,

    /**************** ZWOLNIENIE PRZYCISKU - push_up

    Rozpisałem na kartce i doszedłem, że zerowanie w tym przykładzie następuje przez ten mikro timer programowy :) podobnie jak w poprzednim przykładzie w tym IFie
    if( !++key_lock )

    Tylko dalej mam problem z tym, dlaczego jest w tym warunku takie właśnie wyrażenie ( !++key_lock ). A wyrażenie z postinkrementacją ( !key_lock++), gdy wprowadzam taką zmianę w programie psuje jego poprawne działanie (nie można zmienić stanu diody).

    Myślę takim tokiem:
    Postinkrementacja ma większy priorytet od negacji logicznej więc wykona się najpierw zwiększenie o jeden, a potem negacja logiczna,
    ale w przypadku gdy po drugiej stronie szali stawiam
    preinkrementację z notacją logiczną - to o ile w tym przypadku jedno i drugie ma ten sam priorytet, to łączność jest prawostronna, co oznacza że działanie wykonywane jest od prawej strony ---- no i wychodzi na to samo co w tym przypadku z postinkrementacją. Więc czemu w program jest różnica? Czegoś nie dostrzegam? Na co tu trzeba zwrócić uwagę?

    OdpowiedzUsuń
    Odpowiedzi
    1. Więc twój tok myślenia jest zły - bo to że w tym przykładzie nie zadziała postinkremetnacja nie ma nic wspólnego z większym priorytetem negacji :(

      Po prostu nadal nie rozumiesz dokładnie różnicy pomiędzy pre- i post- inkrementacją

      w skrócie : .... post- inkrementacja następuje w tym warunku IF zawsze po sprawdzeniu negacji, więc gdy key_lock2 jest już równe 0, to niestety nie zadziała wyższy warunek if() tzn ten wyżej

      Usuń
    2. Doczytałem w BB i dalej nie wiedziałem, w czym rzecz.. więc zacząłem opisywać problem czego nie rozumiem na forum... tak szczegółowo, jak ja to widzę... i już kiedy chciałem klikać Wyślij, jak grom z jasnego nieba odpowiedź nadleciała odpowiedź w mojej głowie :D

      ...wysłałem jednak moje zmagania na forum, po kolei jak sam ze sobą debatowałem, może się komuś przydadzą, kto będzie miał podobny problem przy startowaniu ;)
      http://forum.atnel.pl/topic5339.html

      Usuń
    3. sliczna analiza przypadku na forum ;)

      Usuń
  12. A ile mniej więcej trwa czas odczekania w ms ,gdzie liczymy od 0 do 255 ,drugi kod od góry z czerwonym komentarzem (tu trwa eliminacja drgań przycisku) ,zaznaczone.

    Ten kod bardzo mi sie spodobał, zdradziłbyś sekret jak obliczać ile to będzie trwało w ms ,domyślam się ,że uzależnione od użytego taktowania mikrokontrolera. Możesz podać taktowanie i przybliżony czas trwania odczekania w ms, dzięki.

    OdpowiedzUsuń
    Odpowiedzi
    1. Jak widziałeś można też liczyć od 0 do 65535

      i rzeczywiście dobierać to opóźnienie w zależności od taktowania procka ale tak naprawdę również od tego co się dzieje w pętli głównej.

      Nie zastanawiam się tu i nie rozważam ile to trwa ms ... zresztą zliczanie od 0 do 255 w tym prostszym przypadku nie trwa nawet 1 ms ;) ...

      a pomimo to działa ;) ... a nie tak jak wszędzie wszyscy wciskają że tam zawsze musi być nie wiadomo ile milisekund ;) .....

      owszem znajdziesz koci klawisz albo jakiś wielki przełącznik, który będzie miał duuużo większe tzn duuuużo dłuższe te drgania to zastosujesz sobie dłuższy czas ... zliczanie większego zakresu albo w ogóle inną procedurę. To nie jest UNIWERSALNY kod na wszystko - to jest pewna wersja kodu, która przydaje się gdy mamy akurat taką ochotę i potrzebę - i przy zwykłych typowych microswitchach bardzo dobrze się sprawdza ...

      dlatego sprawdź sam - a jak chcesz to weź oscyla i pomierz sam sobie zarówno czasy drgań styków swojego klawisza i czasy reakcji tej procedurki ;)

      wtedy najwięcej się dowiesz o co w tym chodzi ... bo przeliczanie tego na milisekundy nie ma najmniejszego sensu po prostu

      Usuń
  13. Mirku zaskoczę cie ,zrobiłem pomiary oscyloskopem ,na taktowaniu 8MHz i przy zliczaniu 255 ,czas to ~60ms ,jeśli czegoś nie skopałem w obliczeniach :)

    Jako "debugera" użyłem diody LED i toggle w pętli While. Pusta pętla powtarzała się co 850kHz.

    Sama dioda w warunku iF

    Przy uint8 ,co 4kHz

    ,a przy uint16 co 10Hz nawet było widać jak dioda mryga

    Jak chcesz (bo nie bedę ci skrzynki spamować) mogę podesłać Ci kody i pomiary na maila ,nie mam tu jak tego wkleić ,możesz zerknąć. Myslę jednak ,że tak jak i mnie gryzie cię ciekawość. Jakby co daj znać chętnie prześlę swoją "laborkę" może powstanie z tego jakiś poradnik i przyda się wszystkim. Pozdrowki :)

    OdpowiedzUsuń
    Odpowiedzi
    1. Tzn no nie zaskoczysz mnie bo tak jak mówię - wszystko zależy od taktowania i tego co się dzieje w pętli głównej - można to albo oscylem sprawdzać, albo podglądając kod w asemblerze i zliczając takty, albo nawet diodą LED tak "na oko" jak piszesz ... no ale co w tym dziwnego ? ;) po to pisałem ten artykuł i ten kod ... przy taktowaniu 20MHz będzie inaczej przecież niż np przy taktowaniu 1MHz i dlatego łatwo można sobie do doświadczalnie regulować ... po to pisałem ten kod źródłowy ;)

      Usuń
  14. Jedyne co mnie kompletnie zdziwiło ,to to że "pusta" pętla przy kwarcu 8MHz miała częstotliwość wykonywania 850kHz.
    Zawsze myślałem ,że jak taktowanie 8MHz to i pętla leci co 8MHz ,a tu tak jednak nie jest.

    Będę dalej próbował ,poprzez wrzucanie tooglującej diody w bardziej rozbudowane pętle i zobaczę jak często się powtarzają.

    Teraz lepiej rozumiem dlaczego te wszystkie delaye oraz przerwania nie lubią się nawzajem i dlaczego potem wszystko się sypie.

    OdpowiedzUsuń
  15. To, że drgania styków jednak występują już wiemy, ale przy eliminacji tego zjawiska padło powiedzenie "Żadnych DELAY'ów", tylko to co napisałeś "else if( key_lock && (PINC & KEY1 ) ) key_lock++;" jest niczym innym jak pętlą opóźniającą (zliczamy sobie do 256 czyli czekamy 255 cykli maszynowych), tylko że czas opóźnienia dla zegara rzędu MHz będzie w mikrosekundach a nie milisekundach.

    Przy obsłudze jednego przycisku (zliczanie ile razy wciśnięty, max. 4) miałem niby wszystko ok, czyli najpierw sprawdzenie czy przycisk wciśnięty - wykonanie operacji - sprawdzenie czy przycisk puszczony i raz na kilka wciśnięć potrafiło przeskoczyć np. z 2 na 4 albo z 3 na 1 zamiast po kolei. Nie pamiętam ile mikrosekund opóźnienia wstawiłem ale dopiero wtedy program zaczął działać stabilnie.

    OdpowiedzUsuń
    Odpowiedzi
    1. Bo nie ma ŻADNYCH DELAYÓW ;) ... kolega być może na razie czegoś mocno nie rozumie ale już podpowiadam ...

      oczywiście że mechanizm key_lock jest swego rodzaju opóźnieniem ale absolutnie nie taki jak DELAY bo działa w sposób w 100% NIEBLOKUJĄCY - A O TO CHODZI panie kochany ;)

      odnośnie drugiej części - to proszę przeczytać uważniej końcówkę artykułu gdzie piszę o możliwości zmiany typu dla zmiennej key_lock na 16-bitowy aby wydłużyć ten czas i dobrać dla swoich klawiszy ... dokładnie na tym tej sposób polega. Jeden z wielu i działa .. tylko trzeba sobie go skonfigurować i dostosować.

      Usuń
  16. Dziękuję za ten artykuł, w końcu to rozumiem :)

    OdpowiedzUsuń
  17. Działa wam ostatni program z obsługą w 2 funkcjach ? Mi po skopiowaniu nawet coś nie za bardzo, bo jeden led się świeci cały czas, a po naciśnięciu klawisza, nic się nie dzieje :/

    OdpowiedzUsuń
  18. Witam
    Przepraszam ale brakuje kodów w tym poradniku. Naprzykład dla wyjaśnienia _delay_ms(10)

    OdpowiedzUsuń
  19. Czy mógłbym prosić o wyjaśnienie ostatniego listingu? Chodzi mi dokładnie o specyfikatory "volatile" oraz "register". Mam to rozumieć w ten sposób, że zmienna key_press posiada specyfikator register po to, aby kompilator mógł zapewnić szybsze sprawdzenie stanu klawisza poprzez umieszczenie tej zmiennej w rejestrze, a nie w pamięci ram?
    Natomiast jak się ma zmienna KPIN z "volatile"? Dlaczego jest tam wykorzystany akurat ten specyfikator?
    dodam od razu, że przeczytałem BlueBook'a, ale nadal nie do końca rozumiem te mechanizmy.
    Jaki byłby efekt gdybym nie zastosował w tym kodzie tych specyfikatorów?

    OdpowiedzUsuń
    Odpowiedzi
    1. Przecież na tej stronie nie ma w ostatnim listingu specyfikatora volatile

      Usuń
    2. Jest w tej funkcji:
      void key_press(uint8_t * klock, volatile uint8_t * KPIN, uint8_t key_mask, void (*kfun)(void) )
      Zmienna KPIN posiada specyfikator volatile : volatile uint8_t * KPIN

      Przepraszam za komentarz niżej, wpisałem nie w to okienko odpowiedź.

      Usuń
  20. Jest w tej funkcji:
    void key_press(uint8_t * klock, volatile uint8_t * KPIN, uint8_t key_mask, void (*kfun)(void) )
    Zmienna KPIN posiada specyfikator volatile : volatile uint8_t * KPIN

    OdpowiedzUsuń
    Odpowiedzi
    1. ach sorki, przepraszam - oczywiście, że jest w argumetach, chodzi o to, że argument w postaci adresu portu jest volatile więc tak samo musi być przekazywany w postaci argumentu z tym specyfikatorem

      Usuń
    2. Dziękuję za odpowiedź.

      Usuń
    3. Jeszcze jedno pytanie. Nie rozumiem dlaczego przekazana funkcja "kfun" jest sprawdzana w postaci: if(kfun) kfun(); i dopiero wywoływana? Nie można jej od razu wywołać bez sprawdzania?

      Usuń
    4. Ale ja to obszernie w książce wyjaśniam, a pisałeś że masz książkę. Nie mniej jednak przypomnę. TAKA JEST ZASADA, bo możesz sobie bez tego warunku if() odpalać takie funkcje callbackowe, tylko zastanów się DOBRZE co się stanie, gdy ktoś zamiast wskaźnika na funkcję przekaże NULL ... to co wtedy będzie gdy nie będzie tego IF'a ?

      Usuń
    5. Czy ja mógłbym prosić o stronę gdzie to jest opisane ? Dawno nie bawiłem się AVR i wszystko wyleciało mi z głowy. I też nie mogę znaleźć opisu do tego zagadnienia.
      Pozdrawiam.

      Usuń
  21. Jeszcze raz dziękuję za rozwianie moich wątpliwości. Owszem, posiadam książkę, jednak zadaję pytania na bieżąco do problemów, których nie rozumiem. Zanim dojdę do danego rozdziału w książce mija trochę czasu, a mimo wszystko jest to obszerny materiał. Jeszcze raz dziękuję.

    OdpowiedzUsuń
  22. Nie wyświetla się większość kodów

    OdpowiedzUsuń
    Odpowiedzi
    1. Wyświetla, tylko czasem trzeba troszkę poczekać na doładowanie strony, bo koci skrypt kolorowania składni ściąga się z jakiejś tam innej strony niestety :(

      Usuń
  23. Witam - pytam gdyż nie do końca rozumiem pewną część kodu:
    :if(!key_lock && !(PINC & KEY1)) ...key_lock=1;
    Zastanawiam się ciągle nad zmienną PINC - czyli jak rozumiem tą zmienną odpowiedzialną za "bajkowe drgania"
    załóżmy, że wcisnąłem klawisz - w/w warunek spełnił się i key_lock=1 i zapaliła się dioda ale:
    co jeśli w trakcie przejścia do drugiego warunku:
    "else if(key_lock && (PINC & KEY1))"
    "w czasie" tzn chodzi mi o moment gdy pierwszy warunek juz się spełnił i program ma zacząć pętle nr dwa a tu trach - nastąpiło drganie styku i PINC przyjmuje, że dalej ma podpięte vcc - to co wtedy bo wg mojego (błędnego skoro toto działa) rozumowania powinien się spełniń warunek nr 2 - no chyba, że chodzi o to, że poprzez strome zbocze nie jest możliwa taka akcja w momencie wciskania klawisza

    OdpowiedzUsuń
    Odpowiedzi
    1. Po pierwsze tu nie mamy pętli tylko warunki IF to akurat bardzo istotne i jak mówisz o pętlach to może dlatego się mylisz gdzieś tam. Ale ok przede wszystkim zapominasz chyba o NAJWAŻNIEJSZYM, gdy spełni się pierwszy warunek to on wraz ze zmienną key_lock, tworzy taki jakby ZATRZASK ! Zmienna key_lock to taka maszyna stanów. Gdy zostanie zatrzaśnięta to później już nie ważne ile nastąpi drgań styków - mogą sobie drgać a ty regulujesz ten czas właśnie wartością znowu wpisaną do zmiennej key_lock. To właśnie w trakcie jej zmniejszania czy tam zwiększania (obojętne) ... mogą trwać drgania styków a program główny się nie blokuje.

      Usuń