Skocz do zawartości

Prosta klasa dla obsługi przycisku w Arduino/ESP


Dantey

Pomocna odpowiedź

Cześć. Postanowiłem zamieścić tu przykład kodu w ramach podzielenia się swoja "twórczością". Jeśli zły dział, proszę przenieść.

Post głównie w celach edukacyjnych dla osób, które jeszcze nie miały okazje zrezygnować z miliona zmiennych Pin_1, Pin_2 itd. Dużo przyjemniej i czytelniej jest mieć wszystko skompresowane do obiektów, nawet jeśli to bedzie przerost formy nad treścią. Po prostu sam chciałbym coś takiego przeczytać rok-dwa temu 🙂 

Swego czasu, gdy trochę więcej poświęcałem czasu na embedded, natrafiłem na jednym z kursów M. Salamona fajną (według mnie) metodę implementacji obsługi przycisku. Postanowiłem sobie ją trochę przerobić, gdyż oryginał był pisany pod C i używał callbacków, które średnio mi dobrze pasowały, bo za każdym razem napotykałem rozmaite problemy, z którymi średnio sobie umiałem poradzić. Napisałem więc sobie to jako klasę, a kolejno dopisałem drugą klasę, która dziedziczy po niej, a jednocześnie implementuje to co dzieje się po wciśnięciu tego przycisku. Jest to taki chyba najprostszy przykład zastosowania programowania obiektowego dla arduino. 

Zamieszczam Link do GitHub z plikami .h i .cpp. gdyż wstawienie tu całego kodu byłoby trochę przesadą?

Celem w ogóle użycia dziedziczenia było zrobienie swoistej "templatki" dla klasy, która implementuje obsługę przycisku, ale nie definiuje tego co dzieje się po wciśnięciu tego przycisku. Zapewne 80% przycisków w projektach po prostu wystawia jakiś stan na konkretny Pin, więc można było darować sobie to dziedziczenie, no ale.. zrobiłem sobie to głównie pod siebie na przyszłość. 

W temacie programowania obiektowego jestem początkujący więc wszelakie uwagi od bardziej doświadczonych kolegów mile widziane, bo niestety ale wszystko to moje własne próby sklejenia czegoś z informacji, które zdobyłem. 

Opisując skromnie co dzieje się w implementacji i jakie są założenia:

1. Brak możliwości stworzenia podstawowej klasy

// virtual destructor
virtual ~TypeButton() = 0;
// virtual methods
virtual void ButtonPressed() = 0;
virtual void ButtonNotPressed() = 0;

Chciałem stworzyć sobie klasę, która będzie miała zaimplementowaną swoista "blokade", żeby nie kusiło stworzenia obiektu, który nie posiada implementacji dla funkcji obsługującej przycisk. W tym celu zmieniłem destruktor na wirtualny i przypisałem mu 0. W ten sposób ustawiłem destruktor czysto wirtualny, czyli taki którego nie można wywołać. Co za tym idzie, nie można również stworzyć obiektu. 

2. Funkcje Pressed i NotPressed są wykonywane raz
Funkcje wykonywane są raz tylko przy zmianie stanów z Debbounce -> Pressed i z Pressed-> Idle. Zabezpieczało mnie to przed wchodzeniem ciągle, w funkcje, która ustawia stan na wysoki/niski.

3. Działanie głównie opiera się na sprawdzaniu w pętli metody ButtonTask()

void TypeButton::ButtonTask()
{
    switch (this->State)
    {
    case IDLE:
        ButtonIdle();
        break;
    case DEBBOUNCE:
        ButtonDebbounce();
        break;
    case PRESSED:
        ButtonIsPressed();
        break;
    default:
        break;
    }
}

Metoda za każdym razem sprawdza aktualny stan obiektu, z którego została wywoływana (this->State) i wykonuje funkcje, zależnie od tego w jakim stanie znajduje się aktualnie przycisk.

4. Opierając się na Klasie-rodzic można dorabiać dalej własne implementacje klasy-dziecka, które bedą wykonywać inne zadania. Kwestia inwencji własnej. 

Ogólnie, jak wspomniałem wcześniej wygląda to jak przerost formy nad treścią, ale obecnie bardzo mi ułatwia pisanie prototypów bo nie musze znowu martwić czy dobrze napisałem kod do przycisku. No i było bardzo fajną lekcją w kontekście robienia klas/metod wirtualnych. 


Z napotkanych problemów:
Mimo, że zaimplementowałem destruktor jako pure virtual to i tak musiałem dodać w pliku .cpp na początku:

TypeButton::~TypeButton(){}

Bez tego dostawałem ciągłe błedy o tym, że w klasie-dziecko jest nieznany typ destruktora własnie TypeButton::~TypeButton(). Trochę nie mogłem zrozumieć czemu musiałem to zrobić. Wygrzebałem to w jednym z tematów na Stack Overflow i było to tam opisane czymś w rodzaju "Czasem trzeba dodać tego typu deklaracje mimo pure virutal destruktora". Albo ja nie uważałem podczas uczenia się, ale rzadko kiedy widziałem by ktoś miał taki problem. Może ktoś dużo mądrzejszy mi to wytłumaczy? 🙂

To by było na tyle. Wszelakie uwagi mile widziane

Link do komentarza
Share on other sites

@Gieneq Tak widziałem podobne. Wiedziałem, że ten mój kod to wymyślanie koła na nowo i nie tworze niczego czego już nie ma. Bardziej w kwestii, "a może ktoś bedzie szukał po polsku, to może mój post go nakieruje na poszukanie czegoś albo jak napisać po swojemu" 😉

Link do komentarza
Share on other sites

Jak chodzi o ponowne wynajdowanie koła to warto przy okazji sprawdzić, czy aby na pewno nasze, nowe jest lepsze, albo chociaż równie dobre jak to które już wcześniej wynaleziono.

Używanie dziedziczenia może być nieco "drogie", a w przypadku Arduino pamięci jest niewiele, procesor nie jest demonem szybkości i tego typu wodotryski trzeba dozować z umiarem. Kolejny problem to sam wzorzec, czyli konieczność tworzenia podklasy do obsługi zdarzenia - to bardzo przypomina pierwsze wersje appletów Javy, które jednak okazały się mocno niedoskonałe.

Proponuję na spokojnie się zastanowić, czy na pewno dziedziczenie to dobry pomysł, co będzie jeśli użytkownik zażyczy sobie nie tylko funkcjonalności związanej z ButtonSwitch, ale potrzebuje obsłużyć jakieś inne interfejsy - czy będzie musiał używać wielokrotnego dziedziczenia? No i co jeśli potrzebujemy mieć więcej niż jeden przycisk?

Wbrew pozorom callback-i to bardzo elastyczne rozwiązanie i może mieć wiele zalet w porównaniu z wielokrotnym dziedziczeniem, albo tworzeniem mnóstwa właściwie niepotrzebnych klas... Natomiast w przypadku C++11 można używać o wiele "ładniejszych" opakowań (np. std::function) i uzyskać elastyczność callbacków, bez niskopoziomowości C czy też niepotrzebnego dziedziczenia.

Bardzo zachęcam do rozwijania pomysłu, ale zostawiając trochę miejsca na zmianę kodu.

  • Lubię! 2
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

@Elvis Dzięki za odpowiedź. 

Historia zrobienia tej klasy wyglądała trochę właśnie na zasadzie, że próbowałem napisać to na callbackach a ktoś mi powiedział, że przy C++ takie rzeczy są nie potrzebne "bo masz przecież dziedziczenie". W takim razie w przyszłości przyjże się temu co napisałeś, bo co do kwestii czy to dziedziczenie jest tu potrzebne to sam wiem, że właściwie można było sobie to darować, o czym zresztą też wspomniałem. 

Przy Callbackach miałem problemy jeśli chodzi o definiowanie typu do jakiego się odwołuje. Nawet jak zrobiłem sobie jakiś stary Typedef czy Using, miałem errory, które mówiły, coś w stylu że nie można przekonwertować typu X do Y mimo że oba były według mnie te same, więc wkurzyłem się i to olałem. Prawdopodobnie wrócę do tego jak znajde dobry przykład, który bede mógł zrozumieć 😅

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.