Ads_700x200

czwartek, 11 października 2012

Obsługa klawiszy - zwłoka czasowa CD...3

Witam,

To już ostatnia część w której postaram się zamknąć sprawy związane z właściwym podejściem do obsługi klawiszy a szczególnie drgań styków. Uwaga! to nie oznacza, że prezentowane tu rozwiązania są jedyne i najlepsze na świecie ;) .... przede wszystkim chciałem w uzupełnieniu do książki "Mikrokontrolery AVR Język C Podstawy programowania" a szczególnie do opisanej tam funkcji SuperDebounce(), zaprezentować tym cyklem artykułów jak mniejszymi krokami można dojść do napisania czy też pisania takich funkcji we własnym zakresie. Wiąże się to oczywiście z podejściem w programie jak do mini systemu czasu rzeczywistego, w którym takie pojęcia jak: pseudo-wielowątkowość, zdarzenia czy timery programowe to już chleb powszedni. A pisanie funkcji nieblokujących, jest jak jedzenie dobrej bułki z masłem. Przede wszystkim, mam nadzieję, że czytając te 3 artykuły każdy zobaczy, że tych określeń nie ma co się bać i że można w przyjazny sposób je zagospodarować w każdym swoim, nawet najprostszym programie. Napiszemy sobie zatem dzisiaj tylko jedną funkcję do obsługi pojedynczego klawisza. Będzie to taki protoplasta funkcji SuperDebounce z książki, tyle że tym razem przedstawiony jest jego rozwój od samego zarodka, poprzez jajko aż do końcowej postaci ;) Nasz cel:


  • nieblokujący charakter funkcji
  • wykorzystanie struktury w celu minimalizacji przekazywania argumentów
  • prostota zastosowania
  • wykorzystanie timerów programowych
  • realizacja przykładu w prostym systemie wielozadaniowym


przejdźmy zatem do rzeczy...




Na początek przedstawię cały kod źródłowy, bazujący zresztą DOKŁADNIE na tym co omawialiśmy wcześniej, a w dalszej części postaram się omówić wszystkie jego elementy w miarę dokładnie tak aby każdy mógł zrozumieć. Ale w razie czego, proszę śmiało dopytywać:
-------------------------------
-------------------------------
Podzieliłem kod na kawałki i oznaczyłem poszczególne fragmenty liczbami oraz literami. Zabieramy się za analizę:
Na początku po dołączeniu wymaganych plików nagłówkowych definiujemy sobie za pomocą makr (1) piny na których będziemy mieć podłączone 3 diody LED oraz jeden KLAWISZ. Aby zobrazować działanie każdego procesu/akcji wykorzystamy pojedynczo diody LED. A zatem funkcje diod:


  1. LED1 - będziemy zmieniać jej stan w wyniku zadziałania pierwszej akcji, wywoływanej w monecie krótkiego kliknięcia klawiszem
  2. LED2 - będziemy zmieniać jej stan w wyniku zadziałania drugiej akcji, czyli wtedy gdy przytrzymamy wciśnięty klawisz przez dłuższy czas (określony osobnym parametrem)
  3. LED3 - realizacja (tak przy okazji) innego niezależnego procesu w pętli głównej poza obsługą samego klawisza i wykonywaniem naszych akcji


Poniżej pkt (2), widzimy definicję stuktury TBUTTON, która będzie przechowywać nam istotne informacje na temat działania klawisza, jego parametrów takich jak port, pin, czas oczekiwania, wywoływane akcje (funkcje) i kilka własnych parametrów na potrzeby funkcji). W porównaniu do poprzednio omawianych metod klawisza, które we wcześniejszych przykładach były przekazywane jako argumenty, było praktycznie to samo poza dwoma nowymi:

wait_time_s - w tym parametrze będziemy podawać czas (wygodnie w sekundach) po jakim ma nastąpić uruchomienie drugiej akcji klawisza gdy jest wciąż wciśnięty

flag - to dodatkowa flaga na wewnętrzne potrzeby działania maszyny stanów wewnątrz samej funkcji

W dalszej części kodu programu:



Powołujemy do życia mechanizm timerów programowych (3) a co za tym idzie zmienne volatile o nazwach Timer1 oraz Timer2. Dalej w punkcie (4) definiujemy sobie dwie własne (dowolne) funkcje użytkownika, które będziemy mogli później przekazać do obsługi klawisza. Przy czym jak się umawialiśmy pierwsza funkcja change_led1() będzie nam zmieniać stan diody LED1 po krótkim kliknięciu, a funkcja change_led2() będzie zmieniać stan diody LED2 po długotrwałym wciśnięciu klawisza. Kolejny punkt programu to deklaracja naszej funkcji do obsługi klawisza (5), do której jako argument będziemy przekazywać tym razem TYLKO i wyłącznie wskaźnik do struktury opisującej parametry powołanego do życia klawisza. Można by nawet rzec śmiało takiego pseudo obiektu ;). No i w końcu (6), powołujemy w naszym prostym  programie przykładowym pierwszy obiekt klawisza. Możemy teraz rzucić okiem na główną funkcję main(). Proszę tylko pamiętać, że w całości przedstawiony kod źródłowy tylko ze względów dydaktycznych nie został podzielony na kilka plików w tym nagłówkowych, po to aby prościej było mi go tutaj omówić.


W takim razie nasz pkt. (7), to podobnie jak we wcześniejszych kodach inicjalizacja pinów diod LED i klawisza, natomiast nowość w tym kodzie pkt (8), to uruchomienie i skonfigurowanie przerwania Timera2, który będzie nam służył na potrzeby działania wielu innych timerów programowych. Będzie on pełnił rolę podstawy czasu, z rozdzielczością czasową 10ms. Naturalnie ty możesz wybrać dowolny inny timer do tego celu. Ja jak zwykle z lubością wykorzystuję tutaj tryb CTC ponieważ łatwiej jest mi i szybciej, uruchomić przerwanie, a na dodatek nie trzeba w nim przeładowywać rejestru licznika TCNTx (tak jak byłoby to konieczne, gdybyśmy użyli zwykłego trybu licznika a nie CTC). Dobieramy zatem tak preskaler i wartość rejestru porównania OCR2 aby uruchomić przerwanie Compare Match od Timera2, co 10 ms. Wreszcie nadchodzi pkt (9), to tutaj jeszcze przed pętlą główną inicjalizujemy nasze powołane do życia klawisze (może ich być przecież kilka).... a my dla uproszczenia przykładu mamy tylko jeden. Należy zatem określić do jakiego portu podłączony jest klawisz: PINC, PIND, PINA czy może PINB. W dalszej kolejności określić maskę konkretnego pinu do którego podłączony jest fizycznie klawisz a także określić w sekundach czas zwłoki na podjęcie drugiej akcji po wciśnięciu i dłuższym przytrzymaniu klawisza. Na koniec przekazujemy dokładnie do struktury dwie akcje, na których nam zależy. UWAGA! naturalnie to nie jest wymogiem, nie musimy wprowadzać koniecznie dwóch akcji. Można tylko jedną, wtedy do drugiego parametru przypisać musimy NULL. Co ciekawe w trakcie działania programu dzięki takiemu podejściu można w bardzo prosty sposób zmieniać sposób działania tego samego klawisza w zależności od miejsca w kodzie projektu i potrzeb. Rozpoczyna się pętla główna programu i cykliczne wywoływanie naszej funkcjie (10), dzięki czemu uruchamiane są nasze akcje w ściśle określonych momentach. Ostatni punkt (11), to jak wspominałem na początku niejako oddzielny proces powołany do życia w naszym programie. Chciałem bowiem na tak prostym przykładzie pokazać, że przepięknie działa nieblokująca funkcja obsługi klawiszy i jednocześnie mamy widoczne dla oka efekty działania tego dodatkowego procesu. Jest on symbolizowany przez mrugającą sobie radośnie trzecią diodę LED niezależnie od pozostałych rzeczy, które dzieją się w pętli głównej programu.


Nadszedł w końcu ostatni fragment kodu do omówienia i chyba najważniejszy. Ale zacznę od końca czyli pkt (13). To jest właśnie procedura obsługi przerwania Timera2, a w niej już bardzo prosty kod który za każdym przerwaniem sprawdza czy przypadkiem, któryś z timerów programowych nie jest większy od zera. Jeśli tak to co 10ms jest zmniejszany o jeden aż dojdzie do zera. Mechanizm prosty, jednak wielu ludzi zadaje mi pytanie dlaczego tak pokrętnie jest zrobione to sprawdzanie i wprowadzona dodatkowa zmienna o nazwie 'n', zamiast po prostu w warunku sprawdzać i dekrementować samą zmienną timera np tak:

if ( Timer1 ) Timer1--;

Powód jest taki, że wbrew pozorom, ten krótszy kod w języku C spowoduje wygenerowanie dłuższego i mniej efektywnego (a co za tym idzie wolniejszego) kodu w asemblerze. Możesz sam to sprawdzić zmieniając zapis na ten uproszczony i sprawdzając z jednej strony jak zwiększyła się zajętość pamięci programu Flash albo jeszcze podejrzeć procedurę obsługi przerwania w pliku *.lss (czyli w asemblerze). Wtedy wyraźniej zobaczysz dlaczego ten niby prostszy kod będzie się wykonywał nieco dłużej.
Możemy w końcu przejść do omówienia kodu naszej funkcji obsługi klawisza pkt (12). Tym razem jej poszczególne elementy oznaczyłem literkami od A do E. Na początku (A),  powołujemy w ramach funkcji zmienną lokalną i przypisujemy jej od razu wartość stanu logicznego odczytaną z wybranego pinu klawisza. Zwróć uwagę jak fajnie tym razem odwołujemy się do wskaźnika do przekazanej do funkcji struktury. Ponieważ jest to wskaźnik to tym razem zamiast kropki, w celu odwoływania się do pól struktury stosujemy znak strzałki. (B), tutaj rozpoczyna się pierwsza i znana ci już bardzo dobrze z poprzednich artykułów część warunku IF(), który w tym momencie sprawdza czy: zmienna klock jest równa zero (a powinno tak być, gdy klawisz jest zwolniony i ustały ew drgania styków), oraz czy wciśnięty jest sam klawisz. Tyle że tu badamy już stan zmiennej key_press. Następnie sprawdzamy kolejnym warunkiem IF() czy przypadkiem przekazana akcja (wskaźnik do funkcji użytkownika) nie jest pusty (NULL) a jeśli nie to jest wywoływana w tym momencie akcja krótkiego kliknięcia. Gdyby wskaźnik był równy NULL to oczywiście nic by się w tym miejscu nie stało. Następnie podobnie jak w poprzednich artykułach inicjalizowana jest zmienna klock ale także ustawiana na 1 zmienna pomocnica flag. Poza tym w tym miejscu inicjalizowany jest pierwszy timer programowy Timer1 wartością związaną z ustawioną zwłoką w sekundach, tyle że następuje tu przeliczenie sekund na jednostki 10-milisekundowe gdyż taką mamy rozdzielczość naszej podstawy czasu (sprzętowy Timer2). Zakładając, że trzymamy nadal wciśnięty klawisz, to i tak funkcja zakończy swoje działanie z uwagi na to że w dalszej części warunku IF() są jeszcze dwa słówka else. Nie mniej jednak po kolejnym wywołaniu tej funkcji w kolejnym obiegu pętli głównej, mamy już taką sytuację, że klawisz nadal jest wciśnięty a zatem zmienna key_press znowu będzie równa zero, zatem pierwsza część warunku się nie wykona, nie wykona się także część warunku oznaczona literką (D), natomiast gdy upłynie ustawiony czas zwłoki czyli Timer1 będzie równy 0 i jednocześnie zmienna flag będzie równa 1, a będzie gdyż ją zainicjalizowaliśmy po wykonaniu krótkiego kliknięcia to zrealizuje się druga akcja. Jednocześnie skasowana zostanie zmienna flag na zero i dokąd użytkownik dalej będzie trzymał ew wciśnięty klawisz to już nic nie będzie się działo. Ale gdy zwolni klawisz to wykona się środkowa część warunku (C), przy kolejnym obiegu pętli ponieważ nadal zmienna klock posiada wartość = 1 i jednocześnie key_press także jest =1 (klawisz zwolniony). Ta część warunku będzie się wykonywać 256 razy dotąd aż zmienna klock przekręci się z 255 na 0 i jednocześnie zablokuje tę część warunku. A to właśnie jak już wiesz z poprzednich artykułów, będzie czas potrzebny na ustanie ew dragń styków w momencie zwalniania klawisza.

Dobrnęliśmy do końca. Nie przedstawiłem wprawdzie najbardziej rozbudowanej wersji funkcji, która mogłaby jeszcze obsługiwać tzw AUTO-REPEAT, czyli powtarzać np drugą akcję co ściśle określony czas, ale nie chciałem po prostu dublować tego co już zostało w najbardziej rozbudowanej formie przedstawione w książce w postaci wspomnianej już funkcji SuperDebnounce().

Dlatego szczerze polecam Tobie tę książkę jeszcze raz, jeśli do tej pory nie miałeś okazji jej przeczytać a jeszcze niezbyt pewnie czujesz się w zagadnieniach związanych z pseudo-wielowątkowością i timerami programowymi ponieważ to w niej staram się wyjaśniać takie rzeczy od podstaw. Mowa tu o książce pt: "Mikrokontrolery AVR Język C Podstawy programowania".




część II

część III

.

37 komentarzy:

  1. Od razu wywalam uproszczony zapis z mojego programu płynnych przejść na wyśietlaczu led. Do tej pory nawet przez myśl by mi nie przeszło, że uproszczony zapis wykonuje sie dłużej i zajmuje więcej miejsca w pamięci flash. Świetny cykl artykułów!

    OdpowiedzUsuń
  2. OMG Mirek. Wybierałem się na następny kierunek (zaraz po maszynach i urządzeniach górniczych) związany coś z elektroniką i programowaniem, ale... po paru wykładach (na lewo) byłem zielony. Gdyby wykładowcy byli tacy jak Ty i zajęcia były by prowadzone taki sposób jak w twoich artykułach... pomarzyć jesteśmy geniuszami. Od tego czasu JUPIIII na kolosie jestem numerem jeden.

    Mirek. Brawo za zaangażowanie w programowanie!

    OdpowiedzUsuń
  3. Od paru dni analizuję ten kod i zawiesiłem się w miejscu:

    register uint8_t key_press = (*btn->KPIN & btn->key_mask);

    Panie Mirku proszę o rozrysowanie/wytłumaczenie jak to:
    (*btn->KPIN & btn->key_mask);
    sprawdza stan na pinie ?

    Czytałem o wskaźnikach, ale w tak skomplikowanym zestawieniu * i & po prostu nie ogarniam.

    Spodziewam się banalnego wytłumaczenia, niestety sam go nie wymyślę -poddaję się.

    OdpowiedzUsuń
    Odpowiedzi
    1. Już staram się pomóc ;) ... po pierwsze napisz mi czy analizowałeś najpierw przykłady z drugiej części artykułu - gdzie było to w prostszej wersji napisane - ponieważ korzystaliśmy wprost z argumentów przekazanych do funkcji. Wadą było wtedy to że tych argumentów musimy aż tyle targać do każdego wywołania. Dlatego przygotowaliśmy strukturę TBUTTON prawda ? to na razie jest jasne chyba ?

      Więc do funkcji teraz przekazujemy tylko pięknie adres tej struktury &button , czyli wskaźnik tak na prawdę tak ? Bo użyliśmy operatora wyciągania adresu &

      i teraz na wejściu do funkcji od razu chcemy wiedzieć czy wciśnięty jest klawisz. Ale nie piszemy tego tak

      key_press = PINC & (1<key_mask;

      to jest to samo dokładnie tyle że odwołujemy się do pól struktury. Twoją konsternację może budzić fakt że to nie jest zapisane np tak:

      key_press = *btn.KPIN & btn.key_mask;

      tylko jakieś strzałki są - ale zauważ że gdy odwołujemy się bezpośrednio do pól struktury to korzystamy z kropki, a gdy odwołujemy się do struktury przez wskaźnik ( u nas to jest btn ) ... to musimy zamiast kropek wpisać ->

      teraz jaśniej ?

      jeśli nie to pytaj śmiało dalej i męcz mnie - chętnie odpowiem

      Usuń
    2. Oj NIESTETY ale blog nie nadaje się do dyskusji o KODZIE programu - wcina znaki niektóre i są błędy w tym co napisałem wyżej ... więc jak chcesz lepiej to dawaj na nasze forum:

      www.forum.atnel.pl

      i tam załóż wątek z tym pytaniem - i zobacz jak tam fajnie można pokazywać kody z kolorowaniem nawet składni - OK

      Usuń
    3. Minęło już sporo czasu od tego wpisu ale... Z tego co się zdążyłem dowiedzieć równoznaczny z tym był by zapis: (*btn).KPIN

      Usuń
  4. Auto repeat (czy jak to tam z angielskiego) w tym kodze szybko uzyskałem, zamieniłem miejscami warunek D z E, do warunku E zabrałem "btn->flag = 0;" a dodałem opóźnienie "Timer1 = ....;". Tyle modyfikacji i mamy ładne działającą funkcję.
    Teraz to ja ją sobie zastosuje...

    OdpowiedzUsuń
    Odpowiedzi
    1. I o to chodzi - to co pokazuję to tylko przykłady - rozwiązać to można na milion sposobów, a najważniejsze to zrozumieć istotę działania ;) .... Skoro wprowadzasz własne modyfikacje to znaczy że wiesz o co chodzi .... i super ! to było moim celem i cieszę się że mogłem chociaż troszkę zainspirować do utworzenia własnej wersji ;)

      Usuń
  5. nie wiem, ale jak normalnie czytam te twoje programy, to normalnie wszystko rozumiem, nawet nie musze czytać opisów, a jestem słabiutki z programowania- szacunek. Ale jak dla mnie to jeste inny sposób implementacji, tego co napisałeś w drugim artykule z uwzglednieniem pewnych czasów, ja wolałbym pewnych przedziałów jako klawisz wciśniety bardzo krótko, albo dłużej niz krótka znaczy więcj niz przedział wciśnięcia krótko.
    Fajne artykuły, dobrze się czytało, dzięki

    OdpowiedzUsuń
  6. Odnośnie obsługi samych timerów, czy nie dużo prościej byłoby włączyć timer żeby sobie cały czas liczyć (typowe overflow) i następnie podczas obsługi przycisku wpisywać timerowi wartość np 0 (volatile index;) i następnie w POLLingu sprawdzać czy zmienna index uzyskała wartość >= od naszej wartości oczekiwanej (proporcjonalna do czasu oczekiwania) i wtedy dopiero zrealizować jakiś tam warunek obsługi buttona?
    I w tym warunku umieścić TMISK który pozwoli wyłączyć timer aby zaoszczędzić trochę zużycia energii?

    OdpowiedzUsuń
    Odpowiedzi
    1. Pomyśl Patryk chwilę, i powiedz mi co to znaczy "prościej" ?

      Nie ma prościej. Możliwości i sposobów realizacji obsługi klawiszy są dziesiątki jak nie tysiące. Nie ma lepszego, gorszego, trudniejszego czy prostszego.

      Za to są takie sposoby jakie odpowiadają wymaganiom np konkretnego projektu albo takie jakie wymyślił sobie programista i spełniają swoje zadanie.

      Wszystkie przykłady, które pokazuję na blogu czy w książkach mają inspirować innych także do poszukiwania własnych rozwiązań a nie ścigania się kto zrobi: najlepiej, najprościej, najmądrzej, naj... naj... naj...

      Takie podejście, że ktoś mówi że mój sposób jest "naj... naj... naj..." to znajdziesz albo na elektrodzie albo na pewnym blogu. Ja się z nikim nie ścigam ani nie udowadniam, że coś zrobiłem najlepiej. I przeważnie to - bardzo trudno pojąć wielu śpecom od naj... naj... naj...

      Tymczasem, sposób o jakim ty piszesz pewnie że będzie dobry i pewnie że przyda się w wielu przeróżnych przypadkach, tak samo jak pewnie w wielu się nie przyda albo tak samo jak to że pewnie można jeszcze .... "inaczej" - ja nie używam określenia "prościej" czy naj ;)

      to tyle

      Usuń
  7. Cześć!
    Czy nie było by wskazane by w funkcji key_press warunek if(btn->kfun1) zapisać raczej if(btn->kfun1==NULL). Szukam może dziury w całym ale mądre książki zalecają ten sposób. Wiem że w przypadku mikrokontrolerów AVR (i innych architekur z którymi ma styczność przeciętny Kowalski) nie ma to znaczenia ale... :-)
    Jeszcze a propos sprawdzania warunków, spotkałem się ze zdaniem, że warto je zapisywać w odwróćonej kolejności, tzn np. if(STALA==zmienna) bo dzięki temu przy, jakże częstym, zgubieniu jednego znaku '=' kompilator wywali błąd. Ma to sens ale jakoś nie mogę się do tego przyzwyczaić

    OdpowiedzUsuń
    Odpowiedzi
    1. Przecież ten warunek:

      if(btn->kfun1)

      w ogóle nie jest równoznaczny z tym co proponujesz:

      if(btn->kfun1==NULL)

      przeanalizuj to dobrze bo się pomyliłeś chyba mocno ;). Gdybyśmy chcieli sprawdzać czy ten wskaźnik na funkcję jest = 0 (NULL) to można byłoby go tak zapisać:

      if( !btn->kfun1 )

      ale my sprawdzamy czy NIE JEST ZEREM ;) - teraz jaśniej ?

      Usuń
    2. kfun1 i kfun2 są w strukturze zadeklaraowane jako wskaźniki do funkcji więc btn->kfun1 daje wskaźnik...chociaż nie klei się to z dalszą częścią wyrażenia: jeśli btn->kfun1() to wywołanie to brakuje gwiazdki i nawiasów. No więc wygląda że nie rozumiem jak wskaźnik do funkcji nie dającej wyniku zamienia się w wartość.

      Usuń
    3. No widzisz ;) różnica jeśli chodzi o wskaźnik do funkcji jest taka, że nie trzeba pisać gwiazdki i pewnie to ciebie myli. Pomyśl po co gwiazdka przy wskaźniku do funkcji ? komu potrzebna byłaby wartość spod tego wskaźnika ? i do czego ? ... nie no owszem może się zdarzyć taka potrzeba w jakimś nietypowym przypadku ale normalnie nie. Dlatego gwiazdkę można pomijać w tym przypadku.

      Usuń
    4. Znów nie myślę. Oczywiście przecież testujemy czy funkcja jest przypisana a nie czy nie jest.
      A w takim razie czemu wywołanie funkcji to btn-kfun1() a nie (*btn->kfun)()?

      Usuń
    5. bo zarówno:

      btn-kfun1()

      jak i:

      *btn->kfun()

      oznacza JEDNO I TO SAMO ;) ... zrozum - to jest wskaźnik na FUNKCJĘ a nie na zmienną. Kompilator wie że to funkcja i wie że nie będzie tego nikt traktował jak zmienną czyli np albo odczytywał wartość tej zmiennej - zresztą nawet niby jak ? jaki typ miałby być tej zmiennej? Dlatego dopuszczalne jest takie uproszczenie i zgodne z syntkatyką w AVR GCC

      Usuń
    6. Ja to właściwie rozumiem, w końcu nazwa fukcji to wskaźnik, definicja funkcji to blok pamięci o jakimś adresie a wywołanie fukcji czy to przez wskaźnik czy "normalnie" to skok pod ten adres, tylko że wszystkie moje mądre książki podają wywołanie jako (*ptr)() i z tym jestem oswojony. Dosyć stare są, a w tym czasie sporo się widać zmieniło. Np. w mojej knidze definicja funkcji wygląda tak:
      int fun(x,y)
      int x,y
      {
      }

      Dzięki i pozdrawiam :-)

      Usuń
    7. I mądre książki nawet jak stare to zwykle prawdę mówią ... jedyne czego ty nie uwzględniasz czasem pewnie, to specyfiki różnych kompilatorów. I tylko o to chodzi. Dlatego też napisałem wyraźnie wyżej że w AVR GCC jest to równoznaczne.

      Usuń
  8. Aha...czyli nie jest to standard C tylko ficzer AVR GCC (czy raczej GCC). Przyjmuję do wiadomości i jeszcze raz dziękuję :-)

    OdpowiedzUsuń
    Odpowiedzi
    1. Tzn ja się wypowiadam akurat tylko o AVR GCC bo w nim robię, ale czy to działa czy nie np w innych kompilatorach dla procków np:

      IAR
      mikroC
      CodeVision
      HI-TECH C (dla PIC)

      i innych - nie wiem, choć podejrzewam, że też to będzie tak działać. Na PC nie sprawdzałem więc nie wiem i nie podpowiem.

      Usuń
  9. Ja kiedyś do zwłoki czasowej używałem kostek 74123 (jeszcze przed mikrokontrolerami), jak mamy 1 przycisk to nawet '555 wystarczy. Tam ustalamy to za pomocą 1 kondensatora i 1 opornika.

    Wiem ,że ktoś może powiedzieć ,że po co jak można programowo, ale czasem to męka przy większych programach. A jak mamy miejcie na płytce ,a przeważnie zostaje go sporo to czemu nie użyć zwłoki sprzętowej.

    Jest jakiś taki przesąd ,że wszystko ładuje się w mikrokontroler ,w zapomnienie odeszły już zwykłe bramki ,czy rejestry przesuwne ,itp. A czasem sprytnie podłączone mogą zastąpić po kilkanaście linijek kodu. Oczywiście jak jest na nie miejsce na PCB i zależy od projektu.

    P.S. zna ktoś może układ scalony ,który obsłużyłby do 5 przycisków?

    OdpowiedzUsuń
    Odpowiedzi
    1. Wg mnie, ale to tylko moje zdanie to niestety jakiś totalny anachronizm stosowanie układów NE555 plus jeszcze bramek czy przerzutników do obsługi klawiszy - co ma zmniejszyć ilość linijek kodu ;) to wręcz hmmm no masakra ;)

      Kolega pisze tak - jakby te linijki kodu sprawiały jakąś nie wiem trudność, jakby trzeba było na każdy klawisz poświęcać ich nie wiadomo ile ... ło matko! ;) .... a jeszcze do tego miejsce na płytce ...

      sam widzisz - jest jakiś konkretny powód że zwykłe bramki odeszły w zapomnienie, bo teraz masz pan mikrokontrolery. A co to ? jakieś inne układy ? .... przecież idąc twoim tokiem rozumowania - to można by powiedzieć - że po co w ogóle bramki używać skoro można wzmacniacze operacyjne ... albo jeszcze dalej się cofnijmy układy lampowe ;) czy może budować wszystko na pojedynczych tranzystorach ? ;)

      nie nie - brrrrrr dobrze że te czasy już odeszły i nie trzeba się męczyć z kocimi bramkami i dziesiątkami scalaków na PCB ;)

      Usuń
  10. Witam,
    czy mogę w tej strukturze ustawić wartośc maski na dwa piny (np. 0x02)? Tak żebym mógł odczytać czy obydwa przyciski zostały wciśnięte naraz i dodatkowo dwie struktury zawierające obsługę każdego pinu z osobna?

    OdpowiedzUsuń
  11. Witam, jak do tego kodu dodać obsługę jeszcze jednego przycisku do sterowania dwóch kolejnych LED-ów?

    OdpowiedzUsuń
    Odpowiedzi
    1. Z takimi pytaniami zapraszam serdecznie na nasze przyjazne forum:

      www.forum.atnel.pl

      Usuń
  12. Witam, taki problem mam mianowicie chciałbym przerobić strukturę TBUTTON tak aby do funkcji kfun1 można wpisywać argumenty. Gdy próbuję to zrobić kompilator wyrzuca mi błąd: "error: void value not ignored as it ought to be"

    typedef struct {
    volatile uint8_t *KPIN;
    uint8_t key_mask;
    void (*kfun1)(void);
    uint8_t klock;
    } TBUTTON, TBUTTON1;

    void zapisz_kat(uint8_t adres, uint8_t kat) {
    }

    button.KPIN = &PINC;
    button.key_mask = (1<<PC4);
    button.kfun1 = zapisz_kat(serwo2_baza_adr, kat_serwo2);

    Co robię źle i jak można to rozwiązać ?

    OdpowiedzUsuń
    Odpowiedzi
    1. Przede wszystkim takie pytania z kodem to zadawać na naszym przyjaznym forum: www.forum.atnel.pl

      Usuń
  13. Rozumiem, że wciśnięcie przycisku i przytrzymanie go, wiąże się również z pierwsza akcją, czyli zmianą stanu pierwszej diody, a po 3 sekundach również drugiej diody, tak? Zastanawiam się, jak to zrobić, aby przytrzymanie przycisku dłużej nie wpływało na tą pierwszą diodę (która reaguje na krótkie wciśnięcie)...

    OdpowiedzUsuń
    Odpowiedzi
    1. To na blogu to wstęp i pewna pośrednia dawka informacji pomiędzy najprostszym tylko poglądowym opisem obsługi klawiszy a takim ciut bardziej zaawansowanym z pisaniem kodu pseudo-wielowątkowego jaki opisuję w swojej pierwszej książce
      http://atnel.pl/mikrokontrolery-avr-jezyk-c.html
      dlatego mogę ją spokojnie polecić bo ona wprowadza gładko czytelnika w taki sposób pisania kodu aby móc wykonywać wiele czynności jednocześnie ;) i nie jest to takie trudne jak niektórzy opowiadają ;)

      Usuń
  14. A propos nie optymalnej kompilacji fragmentu

    if ( Timer1 ) Timer1--;

    Czy próbowałeś Mirku porównać to z użyciem operatora preinkrementacji:

    if ( Timer1 ) --Timer1;

    Użycie operatora postinkrementacji wymusza utworzenie kopii inkrementowanego obiektu, co może być przyczyną dłuższego kodu, o ile kompilator nie wykona odpowiedniej optymalizacji.

    Można by też sprawdzić konstrukcję:

    if ( Timer1 ) Timer1 -= 1;

    Niestety sam nie mam jak teraz tego sprawdzić. W każdym razie użycie dodatkowej zmiennej do zmiany wartości timera kłuje mnie w oko ;)

    Pozdrawiam

    OdpowiedzUsuń
    Odpowiedzi
    1. Panie kolego musisz zrozumieć, że optymalizacja kodu nie zawsze oznacza, jak najmniejsze zużycie pamięci programu (Flash), optymalizacji dokonuje się również i to bardzo często ze względu na CZAS wykonywania się operacji, co w tym przypadku ma absolutnie niebagatelne znaczenie. Tak więc to, że ciebie to kuje w oko nie jest za bardzo istotne. Tłumaczyłem to wiele razy i na blogu ale też na forum. Panie kochany - to nie jest sposób, którego używam od wczoraj, więc gdyby w tym był jakiś błąd to nie sądzisz, że dawno bym go zauważył ? ;)

      Oczywiście, że opisany tu sposób spowoduje utworzenie kopii inkrementowanego obiektu ;) i TAK MA BYĆ panie kochany. Ale żeby to dobrze zrozumieć trzeba znać asembler. Liczę, że go znasz - więc zajrzyj proszę sobie po kompilacji jak będziesz miał czas ;) jak to będzie wyglądało w przypadku twoich propozycji zmian i zobacz co to da, gdy będzie się to odbywało na rejestrach procesora w porównaniu do wykonywania tych operacji na komórkach pamięci RAM. Kod będzie nieco dłuższy ale za to zdecydowanie mniej cykli procesora zużytych zostanie na tę operację .... eeeeh no i już chyba po raz ..... kilkusetny tłumaczę to samo ;) ale co tam

      tyle, że sam w ramach zadania domowego - żebyś mógł zrozumieć o co tu chodzi zajrzyj do asemblera i wtedy daj znać co ci wyszło ok ? ;)

      Usuń
  15. Witam :)
    Mirku, bardzo fajnie, logicznie wytłumaczone działanie kodu.
    Jednak, wydaje mi się, że wkradł Ci się jeden babol - literówka w miejscu: "... gdy zwolni klawisz to wykona się środkowa część warunku (C), ... ". Nie powinno być (D) zamiast (C) ?
    Pozdrawiam Piotr

    OdpowiedzUsuń
    Odpowiedzi
    1. Wiesz co ? zabij mnie ale teraz gdy tak z marszu zaglądam a robię coś całkowicie innego .... to nie odpowiem od razu i wprost czy to babol czy nie ....

      Usuń
  16. No to zabieram się za wymianę przycisków w moich wszystkich programach.
    Pięknie wytłumaczone kawał dobrej roboty.Teraz w końcu to pojąłem.Dziękuję.

    OdpowiedzUsuń
  17. Proszę o pomoc. Zaimplementowałem sobie Superdebounce, ale potrzebuje 9 klawiszy, i tu nie ma problemu, śmiga. Każdy klawisz musi reagować na wciśnięcie innym calbackiem z innym argumentem. i tu niestety się wyłożyłem, co bym nie napisał zgłasza błąd. Nie mam pojęcia jak podstawić do KEY[x].short = (*fun)(*arg)...
    potem rejestrowanie: KEY[x] = wyslij(mux);
    KEY[x+1] = zmien(chnl); itd
    i w procedurze superdebounce wywolanie if(*fun) wyslij(*uint8_t);
    wszedzie tu dostaje błędy. jak to poprawnie napisać?

    OdpowiedzUsuń