Skocz do zawartości

Miganie diodami z własnej funkcji


Pomocna odpowiedź

Dzięki za przykłady kodu. Sporo pokazują i wyjaśniają.

Jednak wciąż nie rozumiem, dlaczego zmienna, która ma przechowywać wartości np. od 0 do 2 (ogólnie: mniej niż od 0 do 255) ma deklarowany typ int zamiast byte.

Podobnie jak funkcja zwracająca jakąś wartość (wartość jakiejś zmiennej) - choćby ta funkcja mogła zwrócić tylko 0 lub 1 - to dlaczego ma to być funkcja z deklaracją typu int zamiast byte?

Ten typ int to tylko taki "trynd" - czy jakaś formalna konieczność? Że niby byte jest do innych celów, czy jak?

(...) Zamiast twardych stałych w kodzie, można korzystać np. z wartości zmiennych globalnych tworzących np. strukturę opisującą działanie diodki(...)

Chyba mam marne IQ i źle wybieram kursy www i książki. Bo tej pory nie udało mi się się nigdzie przeczytać o "twardych stałych" ani o "zmiennych globalnych tworzących strukturę opisującą działanie".

Co nie zmienia faktu, że chyba i tak muszę nieco więcej poczytać o języku C. I o języku C++. I o różnicach ich składni.

Ale zanim uda mi się nadrobić tę lekturę, i w końcu pogrążę się w (moich ulubionych) rozmyślaniach - tym razem razem na temat obsługi "niesfornego przycisku" - chciałbym uzgodnić intencje.

Wcześniej napisałem, że "układ ma zmieniać stan po wciśnięciu przycisku".

Może wyraziłem się nieprecyzyjnie - ale chodziło mi o tzw. "klik". Czyli krótkie wciśnięcie przycisku i, zaraz potem, jego zwolnienie.

Nie chodzi mi o to, żeby układ zmieniał swój stan na skutek wciśnięcia i trzymania wciśniętego przycisku.

Czy myślimy o tym samym?

Link to post
Share on other sites

int jest typem uniwersalnym, podstawowym, predefiniowanym w kanonicznej składni języka C. Dlatego wiele publikowanych algorytmów się nim posługuje. Pamiętaj, że programów w takich językach nie piszemy ani dla komputerów ani dla ich procesorów. Piszemy je dla siebie - ludzi. Gdybyśmy tworzyli programy dla elektroniki obecnej w scalakach, miałyby one postać ciągów liczb. Po to wymyślono języki wysokiego poziomu, by zapomnieć o kodach szesnastkowych, instrukcjach asemblerowych itp rzeczach. I dlatego programy powinny być zrozumiałe dla wszystkich czytających je ludzi oraz - co też ważne - powinny być przenośne - z maszyny na maszynę.

Byte jest typem pochodnym i tylko przyzwyczajenie sprawia, że myślimy o nim jak o 8 bitach, a nie ma na to ścisłej definicji. Co prawda na długość typów int i char także, ale te przynajmniej dla programistów jest intuicyjnie zrozumiałe bo język jakoś jednak je definiuje. Swoją drogą warto przypomnieć sobie źródłowe definicje typów int i char. W "prawdziwym" C odpowiednikiem byte jest typ char, ale skoro w Arduino dostaliśmy 8-bitowy typ byte, to możemy go używać. Oczywiście Ty także, nie widzę przeciwwskazań.

Jeżeli już w tym temacie jesteśmy, to w celu precyzyjnego określenia długości powstały typy uintxx_t (także dostępne w języku Arduino) gdzie wprost określasz długość: uint8_t, uint16_t, uint32_t i uint64_t plus ich odpowiedniki ze znakiem: int8_t, int16_t itd.. Może przerzuć się na to, są bardzo wygodne i nie wprowadzają wieloznaczności jak int. Dopóki nie sprawdzisz w podręczniku konkretnej implementacji C to nie wiesz jak długi jest int więc nie masz pojęcia o zakresie liczb w nim się mieszczących. Widząc zmienną uint32_t od razu wiesz, że jest w stanie zliczyć sekundy całego XX wieku 🙂

Może słowo "twardych" powinno być w cudzysłowie, ale i tak dużo ich stosuję. Zaczyna mi to wyglądać na niezdrową manierę. Chodziło mi o sztywne parametryzowanie funkcji nic nie mówiącymi liczbami. Ktoś czytając kod nie będzie miał pojęcia dlaczego akurat 100 i 10 (pamiętaj: programy są dla ludzi). Już użycie stałych:

const int

okres_mrugania_led = 100,
wypelnienie_led = 10;

jest o niebo lepsze, bo kod staje się oczywisty nawet bez komentarzy:

if (licznik >= okres_mrugania_led)

licznik = 0;

Sam przyznasz.

Oczywiście rozumiem, że trzeba wykrywać "klik". Tylko, żeby to dobrze zrobić musisz mieć stabilny stan przycisku. Gdybyś wczytywał go do procesora co 10ms i w celu wykrycia naciśnięcia porównywał z poprzednio wczytanym (bo nie możesz na nic czekać w funkcji, musisz poprzedni stan zapamiętać i wyjść by zwolnić procesor do innych zadań) a Twój przycisk by odbijał się przez 50ms, to za każdym przyciśnięciem program dostawałby kilka zdarzeń "klik". Sekwencja wciśnięcia czyli był_puszczony→jest_wciśnięty byłaby wykrywana wielokrotnie. Witamy w rzeczywistym świecie sprężynek, blaszek, odbić itp zjawisk. Dlatego pierwszym poziomem obsługi każdej klawiatury - nawet jednoprzyciskowej) jest tzw. debouncing czyli usuwanie odbić zestyków. I Ty masz to "bezdelayowo" (zaczynam się martwić o te cudzysłowy) zrobić. Zdradzając przyszłość, następna funkcja będzie korzystać z wyników tej, by w sposób pewny wykryć przyciśnięcie czyli klik. Mając podstawowe wczytywanie stabilnego stanu klawiszka, w bardzo prosty sposób można ją będzie rozbudować o raportowanie puszczenia oraz o autopowtarzanie - zupełnie jak dorosłe klawiatury PC. Także z programowanym pierwszym opóźnieniem i szybkością następnych "autonaciśnięć".

Struktura to taki twór w C łączący wiele różnych obiektów w jakąś znaczeniową całość. Możesz oczywiście zadeklarować trzy różne zmienne led_period, led_pwm i led_mode, ale wstawienie ich do struktury od razu pokazuje, że są to trzy kawałki jakiejś całości mającej wspólną nazwę i miejsce w pamięci. Są jeszcze inne zalety takiego podejścia, ale o tym kiedy indziej.

Czy jeszcze coś Cię martwi? Bo o Twoje IQ jestem dziwnie spokojny.

Link to post
Share on other sites

OK. No to funkcja odkłócająca "niesforny przycisk:

#define pinPrzycisku 2

int sito()
 {
  const int czasSprawdzic = 5;
  static int zlicz, stabilny;
  int aktualny;
  if (++zlicz == czasSprawdzic)
   {
     aktualny = digitalRead(pinPrzycisku);
   }
  if (aktualny != stabilny)
   {
     stabilny = aktualny;
     zlicz = 0;
     return stabilny;
   }
   else
   {
     zlicz = 0;
     return stabilny;
   }

 }
Link to post
Share on other sites

Dobrze kombinujesz, ale pomyśl jak wykona się ta linia:

if (aktualny != stabilny)

gdy wejdziesz do funkcji sito() gdy zlicz będzie w zakresie 0..3.

Spróbuj odczytywać stan przycisku za każdym razem. Zawsze gdy stan stan się zmienił, odpalasz licznik czasu drgań. Gdy licznik zlicza, zawsze oddajesz stan który zapamiętałeś jako ostatni stabilny po to, by odbiorca danych nie widział zakłóceń. Dopiero gdy licznik przestał zliczać i stoi → dawno nie było zmiany → uznajesz nowy stan stabilny i oddajesz go.

Link to post
Share on other sites

No fakt. Umknęło mi to.

Jeśli wywołam funkcję gdy zlicz będzie miał wartość z zakresu od 0 do 4, to aktualny będzie zawsze 0. Jeśli stabilny będzie "pamiętał" też 0, to nic złego się nie stanie: zwrócę z funkcji ten sam stabilny.

Ale jeśli stabilny będzie "pamiętał" 1, to zmienię i zwrócę z funkcji nową wartość zmiennej stabilny bez... sprawdzenia stanu przycisku.

Teraz powinno być dobrze:

#define pinPrzycisku 2

int sito()
 {
  const int czekaj = 5;
  static int zlicz, stabilny;

  int aktualny = digitalRead(pinPrzycisku);

  if (aktualny != stabilny)
   {

     if (++zlicz == czekaj)
       {
         zlicz = 0;
         stabilny = aktualny;
         return stabilny;   
       }
     else 
       return stabilny;

   }
   else 
     return stabilny;
 }

Chyba, że znów coś przeoczyłem 😉

Link to post
Share on other sites

Hm, chyba trudniejsze to jest niż myślałem. Z diodką poszło jak z płatka. Na pewno kiedy piszesz taki krótki kod, przechodzisz sobie różne ścieżki i scenariusze zastanawiając się czy zachowanie programu jest zgodne z Twoimi oczekiwaniami. W ten sposób można szybko przetestować w myślach proste funkcje już w czasie ich tworzenia. To teraz prześledź sytuację w której od początku na pinie wejściowym jest jakiś stan stabilny, np. zero a potem procesor dostaje odbicia w postaci naprzemiennych jedynek i zer. Wg. mnie powinien wciąż być w stanie detekcji zmian i oddawać uznane za ostatnio stabilne zero. Tutaj chyba tak nie będzie. Jeszcze jedna próba? 🙂

I jeszcze garść informacji: zmienna statyczna domyślnie w chwili startu programu C jest zerowana. Kompilator generuje pewien kod procesora który nie ma swojego odpowiednika w Twoim programie, a który m.in. zeruje cały obszar pamięci RAM przeznaczony na zmienne statyczne. Jeżeli więc takiej zmiennej nie zainicjujesz jakąś wartością, możesz przyjąć że jest ona = 0.

Zupełnie inaczej jest ze zmiennymi automatycznymi. "Powstają" one w chwili wywołania danej funkcji i dostają przypadkowe miejsce w pamięci na tzw. stosie. O ile start programu i tak jest długi więc można tam zerować duże obszary pamięci, o tyle wywołanie i wykonanie funkcji musi być tak szybkie jak to tylko możliwe. Dlatego domyślnie kompilator nie generuje kodu zerującego zmienne automatyczne i mają one wartości przypadkowe. Twój "int aktualny" z poprzedniego przykładu nie będzie zerem w chwili wejścia do funkcji. To znaczy może kiedyś i będzie, ale równie dobrze może to być każda inna wartość. Co więcej, inna za każdym startem tej samej funkcji.

EDIT:

Wskazówka: wydaje się, że oprócz stanu stabilnego musisz zapamiętywać stan właśnie wczytany - bo to dzięki niemu wykrywasz zmianę podczas następnego wywołania funkcji. Po każdej zmianie odpalaj licznik i w czasie jego zliczania z definicji oddajesz ostatni stan stabilny - bo na wejściu coś się ostatnio działo. Gdy licznik doliczy do końca i zatrzyma się to znaczy, że dawno zmian nie było i stan wejścia jest nowym stanem stabilnym. Do roboty 🙂

Link to post
Share on other sites

Jeszcze jedna próba 😕

Zakładając, że drgania styków objawiają się tylko i wyłącznie "naprzemiennym ciągiem jedynek i zer" - tzn. nie jest możliwa sytuacja, że uda się złapać po kolei dwa (lub więcej) zera albo dwie (lub więcej) jedynki - to odkłócanie "niesfornego przycisku" powinna załatwić funkcja w takiej postaci:

#define pinPrzycisku 2

int sito()
 {
  const int czekaj = 5;
  static int zlicz, poprzedni, stabilny;

  int odczyt = digitalRead(pinPrzycisku);

  if (odczyt != poprzedni)
   {
     ++zlicz;
     if (zlicz < czekaj)
       {
         return stabilny;   
       }
     if (zlicz == czekaj)
       {
          zlicz = 0;
          if (odczyt != stabilny)
          {
            stabilny = odczyt; 
            poprzedni = odczyt;
            return stabilny;
          }  
       }

   }
   else
     {
       poprzedni = odczyt; 
       return stabilny;
     }
 }
Link to post
Share on other sites

Uff, na pewno wyjdę na marudę i takiego co to rozumy zjada, ale chyba nie możesz zakładać tak specyficznego zachowania przycisku. Tym bardziej, że wydaje się to całkowicie niepotrzebne. Zadanie tej funkcji jest proste: przekazać stan z wejścia przyciskowego na wyjście. Zwykle (w stanie stabilnym) to jest jedyna rzecz jaką trzeba zrobić. Każda wykryta zamiana powinna "wyzwalać" licznik, który dopóki odlicza, "odłącza" wejście od wyjścia. W tym czasie do wyjścia trzeba "podłączyć" ostatnio zapamiętany stan stabilny. Dokładnie to samo opisałem w ostatniej wskazówce. Dobrą stroną tego problemu jest to, że wyraźnie ćwiczysz się w pisaniu funkcji mających swój prywatny stan i robiących różne rzeczy w kolejnych wywołaniach.

Żeby jednak nie przedłużać, poniżej wrzucam przykład kodu (uwaga: spoiler), który robi dokładnie to co chcemy. Jeżeli chcesz walczyć z problemem samodzielnie, nie czytaj 🙂

#define pinPrzycisku 2 

int sito() 
 { 
  const int czas_odbijania = 5; 
  static int licznik_opoznienia, poprzedni, stabilny; 

  int odczyt = digitalRead(pinPrzycisku); 

  if (odczyt != poprzedni) 
     licznik_opoznienia = czas_odbijania;

  if (licznik_opoznienia)
     licznik_opoznienia--;
  else
     stabilny = odczyt;

  poprzedni = odczyt;
  return stabilny;
}

Funkcja jest o tyle uniwersalna, że przecież nie musi obsługiwać tylko jednego przycisku. Wystarczy, że zamiast digitalRead() dokonasz np. odczytu wprost z portu. Zakładając, że na naszej klawiaturze mamy 6 przycisków (np. + - < > Enter i ESC wystarczają do obsługi fajnego menu ekranowego) podłączonych do powiedzmy 6 linii portu C, robimy:

int odczyt = PINC & 0x3F;

i mamy obsłużonych 6 linii. Jeżeli funkcja wykryje zmianę na którejkolwiek z nich, odpala licznik i czeka aż "klawiatura" będzie stabilna. Dopiero wtedy jej stan oddaje na wyjście.

Wczytanie stanu może być bardziej skomplikowane, np. kilka przycisków na porcie B, kilka na porcie A. Ponieważ zwykle korzystamy z rezystorów podciągających do stanu 1 a przyciski zwierają wejścia do GND, warto odczyty poodwracać by dalej program pracował na aktywnych jedynkach:

int odczyt = ((~PINB & 0xC0) >> 3) | (~PINA & 0x07);

I to wciąż tylko jedna linia w kodzie. Reszta filtrowania odbić zawsze działa tak samo. Trochę trudniej byłoby, gdyby klawiatura była większa, np. w matrycy 8x8=64 przyciski, ale tylko trochę 🙂

Jeżeli uporasz się samodzielnie z przyciskiem lub obejrzysz moje rozwiązanie, możesz teraz zastanowić się, jak za pomocą prostej logiki wykryć zmianę z 0 na 1. Kolejna funkcja, wołająca tę sito() znów powinna przechowywać otrzymany stan poprzedni, dowiadywać się o aktualny i porównywać je by wykryć ten oczekiwany "klik". Dla utrudnienia możesz założyć, że z sito() dostajesz 4 lub 6 bitów stanu przycisków. Wystarczą dwa odpowiednio użyte operatory logiczne (między stanem poprzednim a obecnym) by dostać 1 na bitach które zmieniły się z 0 na 1 🙂

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

EDIT:

Tak się zastanawiam jak Ty piszesz i testujesz to u siebie. Tylko w głowie? Przecież jeśli masz Arduino i coś tam nawet programowałeś, to przecież nic nie stoi na przeszkodzie by wymyślane przez siebie funkcje testować od razu na sprzęcie. Akurat przycisk jest niewdzięcznym elementem, bo wypadałoby mieć oscyloskop itd, ale przecież możesz stworzyć sobie sterylne środowisko do testowania tego co akurat piszesz.

Przykładowo: jeśli robisz nową sito(), to zmodyfikuj ja delikatnie tak, by zamiast wczytywania stanu przycisku z portu (to raczej powinno działać) pobierała argument:

int sito(int odczyt)

usuwając (zamieniając na komentarz) jednocześnie linię:

// int odczyt = digitalRead(pinPrzycisku);

Teraz Twoja nowa funkcja jest przygotowana do testowania. Robisz dla niej środowisko, np cos takiego:

uint8_t
  kolejne_odczyty[] = {0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int
  test_cntr,
  switch_state,
  odpowiedz;

setup()
{
  for (test_cntr = 0; test_cntr < sizeof(kolejne_odczyty); test_cntr++)
  {
     switch_state = kolejne_odczyty[test_cntr];
     odpowiedz = sito(switch_state);
     Serial.print("Test nr: "); Serial.print(test_cntr); 
     Serial.print("   stan przycisku: "); Serial.print(switch_state);
     Serial.print("   odpowiedz funkcji: "); Serial.println(odpowiedz);
  }
}

loop()
{
}

i masz nieograniczoną możliwość testowania własnych genialnych pomysłów. Korzystaj z takich mechanizmów. Zadając różne wymuszenia poprzez zmianę zawartości tablicy kolejne_odczyty[] możesz przekonać się jak funkcja reaguje na dziwne zachowania przycisku i czy rzeczywiście robi to co miała zrobić - filtruje odbicia.

Przy odrobinie zmian możesz stan przycisku wczytywać wprost z portu szeregowego zamiast odczytywać z tablicy i na bieżąco zadawać nowe wymuszenia bez rekompilacji kodu.

Link to post
Share on other sites

No tak. Czasem najciemniej bywa pod latarnią...

"Dobrych stron" jest więcej: teraz już wiem jak bardzo łatwo zgubić z oczu główny cel, kiedy zacznie się przyglądać rzeczom z pozoru ważnym, a ostatecznie zupełnie nieistotnym.

Proste rzeczy "programuję" i analizuję "w głowie". Bardziej złożone (czyli z cyklu: "to się w głowie nie mieści...") - na papierze. "Na sprzęcie" zwykle testuję dopiero to, co uważam za "zakończony" szkic programu.

Teraz - w razie potrzeby - będę tak sprawdzał także pojedyncze funkcje programu (szkicu).

Widzę też, że wybierając dział do zadania pytania - zbyt optymistycznie uznałem, że jestem początkujący. Powinienem był zapytać w dziale Zupełnie zieloni...

Inaczej mówiąc: jeśli przykład z mojego pytania da się dokończyć przy pomocy takich podstawowych ("piaskownicowych"?) poleceń, jak w funkcjach obsługi LED czy przycisku, to możemy kontynuować.

Jeżeli jednak w kolejnych etapach będę miał stworzyć kod, w którym koniecznością jest używanie poleceń odnoszących się bezpośrednio do linii, portów oraz bitów (i operowania na bitach) - to dalszy ciąg musimy odłożyć do czasu, gdy będę o takich poleceniach wiedział więcej (i to dużo więcej) niż tylko to, że istnieją, są dostępne i jak (z grubsza) wyglądają w treści kodu programu.

Zakładając jednak, że z tego typu utrudnień możemy zrezygnować, to kolejna funkcja wydaje mi się bardzo prosta.

Podejrzewam, że będzie ona wywoływana tak samo jak pozostałe: co 10 ms.

Nie muszę tu na nic czekać, więc nie będzie licznika.

Przy każdym wywołaniu funkcji do zmiennej nowy_stan przypisuję wartość, którą zwraca funkcja sito(). Jeśli przycisk nie jest wciśnięty to mam 1 (bo INPUT_PULLUP). Wciśnięty to 0.

Czyli interesuje mnie tylko sytuacja, kiedy stan zmienia się z 1 na 0.

Sprawdzam więc czy nowy_stan jest mniejszy niż poprzedni_stan. Jeśli tak, to było kliknięcie i zwracam 1. W każdym innym przypadku uznaję, że kliknięcia nie było i zwracam 0. W obu przypadkach przepisuję też wartość zmiennej nowy_stan do zmiennej poprzedni_stan.

int wykryj_klik()
 {
   static int nowy_stan, poprzedni_stan;

   nowy_stan = sito();
   if (nowy_stan < poprzedni_stan)
     {
       poprzedni_stan = nowy_stan;
       return 1;
     }
   else
     {
       poprzedni_stan = nowy_stan;
       return 0;
     }
 }
Link to post
Share on other sites

Ej, nie przesadzaj, nie projektujemy tu rakiet a operatory logiczne opanujesz po pierwszym przeczytaniu i użyciu. Dobra, biegłość - po dziesiątym.

Twoja funkcja będzie działała pod warunkiem, że dostaje wartości 0 lub 1 i to jest OK. Czasem warto pisać bardziej ogólne rozwiązania, bo te zwykle żyją dłużej. A jak już raz rozkminiasz jakiś problem, to nie warto wracać do niego za miesiąc, bo wtedy zrobiłeś na jeden przycisk a teraz potrzebujesz trzech. Acha, i warto też na jak najwcześniejszym etapie tak obrabiać dane, by potem były proste i zrozumiałe - program wygląda lepiej a i Ty siadając do niego za rok nie będziesz się zastanawiał co autor miał na myśli. Co z tego, że przycisk jest zwarciem do masy - to tylko interfejs sprzętowy zrobiony tak dlatego, bo dysponujemy opornikami podciągającymi na wejściach portów. Gdyby już sito oddawała stan odwrócony (bo tylko ona współpracuje ze sprzętem), wszystko co jest za nią działałoby na naturalnych dla człowieka jedynkach jako stanach aktywnych. Wydaje się, że to tylko kwestia przyjętej konwencji, ale gdzieś w programie trzeba napisać, że klik to jednak zmiana z 1 na zero - co dla potencjalnego czytającego może być dziwne.

W każdym razie mając do dyspozycji np. 8 bitów stanu przycisków porównywanie arytmetyczne które zaproponowałeś nie zadziała. Natomiast wykonanie czegoś takiego:

 int wykryj_klik()
{
  static uint8_t poprzedni;
  uint8_t aktualny, kliki;

  aktualny = sito();
  kliki = (poprzedni ^ aktualny) & aktualny;
  poprzedni = aktualny;
  return kliki;
}

spowoduje, że najpierw XOR wykryje różnice (na wszystkich 8 bitach na raz) a potem AND zostawi tylko te bity ustawione, które zmieniły się na 1. Acha, założyłem, że jednak zwarcia przełączników sito() oddaje w postaci jedynek.

Nieważne, Twoja też działa i to wystarczy.

Skoro masz już wykrywanie zdarzenia "klik" i masz też mruganie w zależności od stanu parametru funkcji, to teraz została tylko jedna rzecz: funkcja która dostaje odpowiedź z wykryj_klik() i oddaje wartość odpowiadającą nowemu stanowi mrugania. np:

loop() uint8_t klik, stan_led;
{
  klik = wykryj_klik(sito());
  stan_led = glowna_funkcja(klik);
  migaj(stan_led);
  delay(10);
}

Tak więc czekamy na glowna_funkcja() - to ona będzie odpowiadać za interpretację kolejnych kliknięć i zmieniać najważniejszej zmiennej: stan_led. Oczywiście ponazywaj to wszystko jak chcesz 🙂

Link to post
Share on other sites

Po kolei:

(...) nie przesadzaj, nie projektujemy tu rakiet a operatory logiczne opanujesz po pierwszym przeczytaniu i użyciu. Dobra, biegłość - po dziesiątym.

Twoja funkcja będzie działała pod warunkiem, że dostaje wartości 0 lub 1 i to jest OK

Tu zacznę, sorki za wyrażenie, "od tyłu".

1. Moja funkcja będzie działała pod warunkiem, że dostaje wartości 0 lub 1.

Przepraszam za (może głupie) pytanie - ale co innego ta moja funkcja może dostać?

Co innego oprócz 0 lub 1 może dostać, jeśli wywoływana w niej funkcja sito() może zwrócić tylko 0 lub 1?

2. "Nie projektujemy tu rakiet" - no, to wiadomo 🙂

"A operatory logiczne opanujesz po pierwszym przeczytaniu i użyciu. Dobra, biegłość - po dziesiątym."

-----------------------I TU WAŻNA UWAGA ----------------------------------------------------------

Chyba się nie rozumiemy.

A to zapewne przez definicję słowa "początkujący".

Gdybym określił siebie definicją "zupełnie zielony" - to nic by nie dało.

Mógłbym nawet określić siebie definicją: "tylko zielonkawy, ledwo wykiełkowany". Ale to też by nic nie dało.

Przez to, że w temacie zaawansowania w programowaniu mikrokontrolerów: Twoje_definicje != Moje_definicje;

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

Operatory logiczne, matematyczne, porównania, przypisania - znam. I wydaje mi się, że rozumiem, jak działają.

To nie operatory są istotne, tylko to, co napisałem w poprzednim poście:

"(...) Widzę też, że wybierając dział do zadania pytania - zbyt optymistycznie uznałem, że jestem początkujący. Powinienem był zapytać w dziale Zupełnie zieloni...

Inaczej mówiąc: jeśli przykład z mojego pytania da się dokończyć przy pomocy takich podstawowych ("piaskownicowych"?) poleceń, jak w funkcjach obsługi LED czy przycisku, to możemy kontynuować.

Jeżeli jednak w kolejnych etapach będę miał stworzyć kod, w którym koniecznością jest używanie poleceń odnoszących się bezpośrednio do linii, portów oraz bitów (i operowania na bitach) - to dalszy ciąg musimy odłożyć do czasu, gdy będę o takich poleceniach wiedział więcej (i to dużo więcej) niż tylko to, że istnieją, są dostępne i jak (z grubsza) wyglądają w treści kodu programu. (...)"

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

Pisząc tak zupełnie wprost:

1. Nie mam zupełnie pojęcia o zmiennych typu: uint8_t, uint16_t, uint32_t i uint64_t plus o ich odpowiednikach ze znakiem: int8_t, int16_t.

2. Nie mam zupełnie pojęcia o liniach, potach i bitach.

3. Polecenia typu: "PINC & 0x3F" lub "((~PINB & 0xC0) >> 3) | (~PINA & 0x07))" - są dla mnie kompletnie niezrozumiałe. No może poza tym, że PIN oznacza nóżkę mikrokontrolera. C, B lub A określa, która to nóżka (port "wyjście" albo "wejście"). Ale reszta - to dla mnie "ciemna magia" 😉

4. Nie zadawaj mi też proszę "utrudnień" pisząc o liczbie bitów, np: 4, 6, albo 8.

O tym też nie mam zielonego pojęcia!

Bo jestem tylko "początkujący".

Czy teraz już rozumiesz mój "poziom zaawansowania"?

(...) Tak więc czekamy na glowna_funkcja() - to ona będzie odpowiadać za interpretację kolejnych kliknięć i zmieniać najważniejszej zmiennej: stan_led. Oczywiście ponazywaj to wszystko jak chcesz (...)

Oczywiście z mojej strony - projekt kolejnej funkcji nastąpi.

Choć z oczywistych względów (opisanych powyżej) raczej nie użyję typu "uint8_t".

Mam nadzieję, że się nie obrazisz.

Chcę poznać dalszy ciąg, i dzięki temu nauczyć czegoś nowego.

Ale ja naprawdę jestem "zupełnie zielony". Albo nawet "zielonkawy, ledwo wykiełkowany". I już choćby już tylko z tego powodu jest mi trudno.

Nie dawaj mi zadań typu: "żeby było trudniej..." albo: "żeby było ciekawiej...".

I proszę: nie pisz o rzeczach, które dla Ciebie są pewnie trywialne, ale o których ja (jako początkujący) nie mam jeszcze... zielonego pojęcia.

Link to post
Share on other sites

uintX_t to #define który w bibliotece odpowiada faktycznie typowi który ma DOKŁADNIE X bitów i nie ma znaku (lub wyświetli błąd jeżeli taki typ nie istnieje (raczej się nie zdarza dla X = 8, na pewno nie w niczym popularnym).

W Arduino jest: #define uint8_t byte.

Link to post
Share on other sites

Ojej, zirytowałeś się, przepraszam, chyba coś poszło nie tak 😐

Mamy dużo czasu, nie jest to szybka pomoc w uruchomieniu kawałka kodu tylko wprowadzenie do pisania zgrabnych programów więc założyłem sobie (może zbyt pochopnie), że po przeczytaniu mojego postu siadasz do kompa i próbujesz wyszukać w sieci rzeczy co do których nie jesteś pewien jak działają lub po prostu zadajesz kolejne pytanie. Gdzieś na początku napisałeś, że jesteś programistą a ja zrozumiałem, że może nie rozumiesz do końca struktury mikrokontrolera (bo tak często mają ludzie od "dużych" komputerów lub sterowników PLC), ale wyrażenia arytmetyczne - ich sens - rozumiesz. Mamy tu na Forbocie kurs Arduino. Tam chłopaki bardzo fajnie tłumaczą jak program może współpracować ze sprzętem - jak wczytywać stany pinów, kontrolować timery, przetworniki itd. Hm, teraz myślę, że rzeczywiście o PIN może nic nie być - to rejestr używany przez ludzi piszących programy bare-metal na AVR czyli rozumiejących architekturę procka i znaczenie jego rejestrów wewnętrznych. W skrócie: to "okienko" przez które program widzi od razu 8 linii wejściowych. Wejściowych, więc z PIN można tylko czytać:

moje_wejscia = PINA;

oznacza, że właśnie program "wciągnął" do zmiennej moje_wejscia stan 8 linii tzw. portu A.

W sumie zobacz: i tak jesteśmy daleko. Przyznaję, że nie planowałem tego wątku na wiele postów naprzód i zarówno mój pierwszy opis (ten z wajchą) jak i późniejsze przykłady nie były zbyt "edukacyjne". Ale proszę byś zrozumiał, że trochę trudno wejść w czyjeś buty z doskoku. Liczyłem, że będziesz nadganiał czytając w międzyczasie np. podręcznik składni języka C czy opis procesora ATmega328...

Dobra, szkoda czasu na rozpamiętywanie. Jesteśmy w następującym punkcie:

1. Mamy pętlę główną, która obracając się w nieskończoność wywołuje kolejno różne funkcje. Ponieważ przyjęliśmy, że będzie się to działo co 10ms i jest to dla (niektórych) funkcji ważne, rolę odmierzacza czasu wziął na siebie na razie nieśmiertelny delay(). Piszę "na razie", bo za chwilę i z niego zrezygnujemy 🙂

2. Mamy uniwersalną funkcję wczytywania i odkłócania stanu jednego klawiszka sito(). Oddaje jego stan pozbawiony "szpilek" tak charakterystycznych dla zestyków mechanicznych. Popatrz ile ludzi na forum borykało się z tym problemem. Niektórzy - nie wiedząc, że można pisać program w sposób który tu prezentujemy, próbowali podłączać switche do wejść przerwań sprzętowych tylko po to, by ich program od razu reagował an wciśnięcia np... w jakiejś pętli mrugania diodką.

3. Mamy funkcję, która zamienia wczytane i odkłócone stany przycisku na zdarzenie klik zachodzące w chwili wciśnięcia.

4. Mamy funkcję która umie zapalać, gasić lub autonomicznie mrugać diodką w zależności od tego z jakim argumentem ją wywołamy.

To jest cała warstwa oprogramowania, która współpracuje ze sprzętem. Wszystko co teraz napiszesz będzie czystą abstrakcją. Żaden fragment kodu nie musi już czytać z portów lub coś do nich wysyłać. Zrób zmienną która będzie głównym stanem programu. Ponieważ jego zadaniem jest mruganie diodką, niech jej nazwa ma z tym związek. Funkcja która została do napisania będzie dostawała zwykle 0, bo z punktu widzenia procesora nie naciskasz klawiszka zbyt często. Wtedy nie powinna stanu tej głównej zmiennej zmieniać. Mrugaliśmy to mrugamy nadal, nie świeciliśmy to nie świecimy nadal. Ale raz na jakiś czas wykryj_klik() odda stan 1 i jest to sygnał dla funkcji zarządzającej stanem, by zmieniła go na inny. Jaki? To już sam wymyśl. Ponieważ masz już funkcję obsługującą ciemność, świecenie i mruganie - może wykorzystaj to. Niech za każdym wywołaniem z argumentem 1 funkcja modyfikuje główną zmienną tak, by stan diodki zmieniał się wg cyklu 0, 1 mruganie? Albo - tak prościej - niech LED mruga lub niemruga. To może być Twoja pierwsza maszyna stanów z prawdziwego zdarzenia. W każdym stanie klik będzie oznaczać przejście do innego stanu. Napiszę Ci schemat oparty na konstrukcji switch i jest to bardzo ogólny wzorzec takich maszyn w C:

int glowny_fsm(int klik)
{
  static uint8_t fsm_state = LED_OFF;    // Zmienna statyczna - to przypisanie wykona sie tylko raz, na początku programu (może być byte lub nawet int :) )

  switch(fsm_state)
  {
     case LED_OFF:
        if (klik)
           fsm_state = LED_ON;
        break;
     case LED_ON:
        if (klik)
           fsm_state = LED_BLINK;
        break;
     case LED_BLINK:
        if (klik)
           fsm_state = LED_OFF;
        break;
  }
  return fsm_state;
}

Mamy tu trzy stany i trzy proste warunki przejścia do innego stanu. Prościutka maszyna stanów przechodzi przez nie po kolei tylko wtedy, gdy dostaje klik <> 0. Nie musi niczym mrugać, nie musi wczytywać żadnych przycisków, w sumie nie musi o niczym wiedzieć. Zajmuje się tylko swoim stanem i (być może) ma nadzieję, że ktoś z niego skorzysta. To w takich funkcjach siedzą algorytmy większości programów.

Spróbuj sklecić cały kod i go odpalić na sprzęcie. Możesz testować funkcje osobno, możesz sprawdzać ich interakcje.

A typów uintXX_t nie bój się. Napisałem Ci, że są to int-y bez znaku o długościach (bitowych) takich jak liczba w typie: uint8_t jest 8-bitowy więc zajmuje jeden bajt w pamięci i jest bardzo często używany w procesorach 8-bitowych, bo jest ich "naturalną" porcją pamięci, informacji itd. A w Arduino możesz używać zamiennie byte, choć uintXX_t są bardziej uniwersalne. Jeśli zaczniesz używać byte to co napiszesz gdy chcesz mieć zmienną 16-bitową? int? A może int jest 32- lub 64-bitowy jak w PC? Za to uint16_t jest jednoznaczny. To samo z uint32_t czy uint64_t - od razu widzisz jakie są długie, ile pamięci zajmują i jak bardzo biedny procesorek AVR będzie się z nimi męczył. Pamiętaj, że cokolwiek dłuższego niż 8-bitów wymaga od zabawkowego procesorka niezłej ekwilibrystyki, z reguły zajmuje dużo miejsca w pamięci programu i jest wielokrotnie wolniejsze niż operacje na bajtach. Wszędzie gdzie możesz i gdzie zakres przetwarzanych liczb na to pozwala, używaj typów 8-bitowych. Wszystko jedno jak je nazwiesz.

W każdym razie daj znać jak poszło i nie zniechęcaj się. W razie kłopotów wrzuć cały kod i swoje obserwacje z jego działania, będziemy myśleć 🙂

Pamiętaj, że ogromną pomocą jest monitor portu szeregowego. Możesz tam wypisywać stany zmiennych więc od razu widać co idzie nie tak.

  • Pomogłeś! 1
Link to post
Share on other sites

To nie tak.

Rzadko kiedy się denerwuję. Ze mnie jest taki "niespotykanie spokojny człowiek".

Raczej nigdy nie denerwuję się gdy mi coś nie wychodzi.

A jeśli już taki "wyjątkowy wyjątek" się zdarzy, to denerwuję się tylko na siebie.

Więc:

1. Po "pierwsze primo" - nie przepraszaj. Bo nie masz za co.

2. Po "drugie primo" - to ja przepraszam.

I Ciebie i pozostałych kolegów z tego Forum. Przepraszam za moje czasami zbyt "impulsywne" wpisy, spowodowane moją niecierpliwością.

To prawda, że w "przerwach" mogłem zajrzeć do podręcznika (którego nie mam) lub odpytać "wujka Google"i uzupełnić swoją wiedzę w kwestiach, które były (są) dla mnie "mgliste".

Jednak ta "zabawa" zaproponowana przez Ciebie okazała się tak wciągająca, że mając zadaną kolejną "pracę domową" - nie potrafiłem się skupić na czytaniu. Czegokolwiek.

3. Po "trzecie primo"- dziękuję Ci za cierpliwość.

Tu chcę również podziękować kolegom z forum o nickach: "deshipu" oraz "Chumanista", którzy w tzw. międzyczasie też próbowali pomóc.

Uznałeś mnie za "programistę":

(...) Gdzieś na początku napisałeś, że jesteś programistą (...)

Niczego takiego na pewno nigdy i nigdzie nie pisałem. Bo sam o sobie tak nie myślę.

Co prawda kiedyś w Borland Delphi - chyba 6 (chyba - bo dokładnie nie pamiętam) - napisałem kilka bardzo prostych programów dla Windows (wtedy) Xp.

No i te kilka stron www korzystających z PHP 5 z obsługą MySQL.

I tylko dlatego ośmieliłem się uznać za... początkującego.

Ale tak, jak piszesz:

(...) szkoda czasu na rozpamiętywanie (...)

Dalej piszesz:

(...) Funkcja która została do napisania będzie dostawała zwykle 0, bo z punktu widzenia procesora nie naciskasz klawiszka zbyt często. Wtedy nie powinna stanu tej głównej zmiennej zmieniać. Mrugaliśmy to mrugamy nadal, nie świeciliśmy to nie świecimy nadal. Ale raz na jakiś czas wykryj_klik() odda stan 1 i jest to sygnał dla funkcji zarządzającej stanem, by zmieniła go na inny. Jaki? To już sam wymyśl (...)

A "chwilę dalej" piszesz:

.

(...) Napiszę Ci schemat oparty na konstrukcji switch i jest to bardzo ogólny wzorzec takich maszyn w C (...)

I podajesz przykład kodu:

    int glowny_fsm(int klik)
   {
      static uint8_t fsm_state = LED_OFF;    // Zmienna statyczna - to przypisanie wykona sie tylko raz, na początku programu (może być byte lub nawet int :) )

      switch(fsm_state)
      {
         case LED_OFF:
            if (klik)
               fsm_state = LED_ON;
            break;
         case LED_ON:
            if (klik)
               fsm_state = LED_BLINK;
            break;
         case LED_BLINK:
            if (klik)
               fsm_state = LED_OFF;
            break;
      }
      return fsm_state;
   }

I ten Twój przykład - to, w zasadzie, wszystko czego potrzeba.

(...) Spróbuj sklecić cały kod i go odpalić na sprzęcie (...)

No i spróbowałem.

Niestety nie obyło się bez problemów.

A problemy objawiły się przez nazewnictwo.

I to nie tyle zmiennych - ile parametrów funkcji.

Do funkcji migaj() mamy przekazać parametr o nazwie "led_mode".

A w jednym z ostatnich postów, Ty ten parametr nazwałeś "stan_led".

Oczywiście - to tylko kwestia przyjętej konwencji... 🙂

Co nie zmienia faktu, że mamy jeszcze jeden "babol":

W jednym z ostatnich postów, proponując kod, który ma być umieszczony w głównej pętli "loop()" napisałeś:

 uint8_t klik, stan_led;
{
  klik = wykryj_klik(sito());
  stan_led = glowna_funkcja(klik);
  migaj(stan_led);
  delay(10);
}

Otóż chyba funkcja "wykryj_klik()" nie może być tak wywoływana.

Przy takim zapisie jaki proponujesz - kompilator zgłasza błąd o treści:

"too many arguments to function 'unit8_t wykryj_klik()".

Nie żebym się "mądrzył", czy żebym chciał uchodzić za takiego, co to "rozumy zjada".

Ale jeśli funkcja wykryj_klik() niejako "sama z siebie" w swoim "ciele" wywołuje funkcję sito() - to nie można (przypisując jej wynik do zmiennej o nazwie "klik") wywoływać jej w sposób zaproponowany przez Ciebie.

W każdym razie: spróbowałem sklecić cały kod.

Potem usunąłem (tak myślę) błędy zgłaszane przez kompilator.

I wyszło mi coś takiego:

#define pinPrzycisku 2
 #define pinLED 13  
 #define LED_OFF 0
 #define LED_ON 1
 #define LED_BLINK 2

void setup() {
 // put your setup code here, to run once:
 pinMode (pinPrzycisku, INPUT_PULLUP);
 pinMode (pinLED, OUTPUT);
}

void loop() {

  uint8_t klik, led_mode;
  klik = wykryj_klik();
  led_mode = glowny_fsm(klik);
  migaj(led_mode);
  delay(10);

}

void migaj(uint8_t led_mode)
 {
   static uint8_t licznik; 
   switch(led_mode)
     {
       default:
       case LED_OFF:
         digitalWrite(pinLED, LOW);
         licznik = 0;
       break;

       case LED_ON:
             digitalWrite(pinLED, HIGH);
             licznik = 0;
             break;

           case LED_BLINK:
             if (++licznik >= 100) // Licznik działa w cyklu 100
               licznik = 0;
             if (licznik < 10)
               digitalWrite(pinLED, HIGH);
             else
               digitalWrite(pinLED, LOW);
             break;
         }
     }

// Odkłocanie przycisku
uint8_t sito()
 {
  const uint8_t czas_odbijania = 5;
  static uint8_t licznik_opoznienia, poprzedni, stabilny;

  int odczyt = digitalRead(pinPrzycisku);

  if (odczyt != poprzedni)
     licznik_opoznienia = czas_odbijania;

  if (licznik_opoznienia)
     licznik_opoznienia--;
  else
     stabilny = odczyt;

  poprzedni = odczyt;
  return stabilny;
}

// Wykrywanie wciśnięcia przycisku:
uint8_t wykryj_klik()
 {
   static uint8_t nowy_stan, poprzedni_stan;

   nowy_stan = sito();
   if (nowy_stan < poprzedni_stan)
     {
       poprzedni_stan = nowy_stan;
       return 1;
     }
   else
     {
       poprzedni_stan = nowy_stan;
       return 0;
     }
 }

// Funkcja glowny_fsm od której wszystko zależy :-)
uint8_t glowny_fsm(uint8_t klik)
 {
   static uint8_t fsm_state = LED_OFF;    // Zmienna statyczna - to przypisanie wykona się tylko raz,
                                          // na początku programu.
   switch(fsm_state)
     {
       case LED_OFF:
         if (klik)
           fsm_state = LED_ON;
         break;
       case LED_ON:
         if (klik)
           fsm_state = LED_BLINK;
         break;
       case LED_BLINK:
         if (klik)
           fsm_state = LED_OFF;
         break;
      }
      return fsm_state;
   }

Jak widać powyżej - "nie taki diabeł straszny jak go malują".

Mam tu na myśli zastąpienie typu (bliżej nieokreślonego) "int" typem (dokładnie określonym) "uint8_t" 😃

Potem wgrałem ten kod do mojego Arduino UNO i odpaliłem go.

Czyli "na sprzęcie". Cały kod - od początku do końca. Od razu.

I... działa. I działa tak, jak... miało działać.

Po wgraniu powyższego szkicu do mojego Arduino UNO - program startuje od stanu LED "wyłączona".

Wciśnięcie przycisku powoduje zmianę stanu LED na "włączona" (świeci ciągle).

Kolejne wciśnięcie przycisku powoduje zmianę stanu LED na "migaj" (wypełnienie: przez 100 ms świeć, przez 900 ms zgaś).

Każde kolejne wciśnięcie przycisku powoduje (sekwencyjnie) przejście LED do kolejnego stanu.

Cykl stanów LED to: LED zgaszona --> LED świeci --> LED miga.

I w obojętnie, w którym stanie LED by nie była - układ natychmiast reaguje na wciśnięcie przycisku.

I to niezależnie od tego, czy przycisk wcisnę na bardzo krótko (kilik), czy trochę dłużej (krótkie wciśnięcie), czy na bardzo długo (wciśnięcie i trzymanie wciśniętego przycisku).

FAJNE! 😃

Już mnie "nęci" samodzielne przystosowanie tego kodu do tego, żeby ten 1 przycisk sterował w taki sposób 2 różnymi LED-ami "podpiętymi" do 2 pinów na płytce Arduino. Czyli żeby dojść do tego o co pytałem - zadając moje pytanie.

Albo - idąc dalej - żeby układ rozróżniał "klik" od "wciśnięcia", a "wciśnięcie" od "wciśnięcia i przytrzymania"... 😉

A gdybym chciał z pomocą Arduino UNO zbudować "symulator" kolejowego (znanego ze szlaków PLK) pół-samoczynnego semafora 5 komorowego (potrzeba 7 LED-ów), zmieniającego wyświetlany sygnał (1,2,3 LED-y świecące ciągle, lub 2 LED-y świecące ciągle i 1 LED migająca, albo tylko 1 LED migająca) sekwencyjnie po wciśnięciu 1 przycisku, i jeszcze do tego dla każdego sygnału wyświetlić (początkowo w Monitorze portu szeregowego - a docelowo na dołączonym wyświetlaczu LCD 16x2) - opis każdego kolejnego sygnału semafora - to myślę, że już będę wiedział jak to zrobić.

Marku - wielkie dzięki!

Przede wszystkim za cierpliwość dla mnie.

Ale także - za wskazanie sposobu.

Za pokazanie drogi (zapewne jednej z wielu możliwych), którą należy podążać aby osiągnąć coś, co z pozoru wydaje się być "nieosiągalne".

Napisałeś gdzieś w trakcie:

(...) zrozumiałem, że może nie rozumiesz do końca struktury mikrokontrolera (bo tak często mają ludzie od "dużych" komputerów lub sterowników PLC (...)

Tak było.

Zresztą nie wiem, czy użycie czasu przeszłego jest w moim przypadku do końca zasadne.

Być może należałoby w dalszym ciągu powiedzieć: "tak jest".

Bo dalej nie tylko, że "nie rozumiem" struktury mikrokontrolera - to nawet jej... nie znam.

Braki mojej wiedzy w tym temacie - uzupełnię. Nie będę tu obiecywał, że "najszybciej jak się da". Ale tak mnie to zaciekawiło i "wciągnęło" - że po prostu muszę się dowiedzieć najwięcej jak tylko będę mógł. I to nie po to, żeby komuś, czy sobie, cokolwiek udowadniać.

Po prostu - mogę mieć wieczorem problem z zaśnięciem, gdy będę miał świadomość, że "dziś mogłem się jeszcze czegoś więcej dowiedzieć, przeczytać..." 😃

A co najbardziej przeszkadza takim "początkującym z mikrokontrolerami" jak ja?

To czy ktoś kiedykolwiek "programował" cokolwiek dla komputera PC - to z jednej strony trochę pomaga.

Bo tak naprawdę wszystkie języki programowania "wysokiego poziomu" są do siebie podobne. Czy to Pascal, czy C, czy C++, czy Java, czy PHP, czy inne...

W każdym języku są algorytmy, pętle, warunki, warunki wielokrotnego wyboru, itp, itd.

Cała różnica polega tylko na niuansach związanych ze składnią danego języka (sposobem wydawania poleceń).

Więc to ogólnie pomaga. Bo taki człowiek rozumie już na wstępie co to są stałe, zmienne, zasięg zmiennych, przesłanianie zmiennych... Rozumie zapis "for...", "if...", "while..." itp, itd.

Ale jednocześnie przeszkadza. I to tym bardziej, że w internecie (na youtube też) - aż roi się od poradników i tutoriali, w których autor odpowiadając na pytanie "czym jest Arduino?" albo "czym jest mikrokontroler? - stwierdza: "tak naprawdę to miniaturowy komputer". W domyśle: klasy PC. A na pytanie "co mogę zrobić korzystając z Arduino?" lub "co mogę zrobić wykorzystując mikrokontroler?" - odpowiada: "wszystko, co tylko podsunie ci wyobraźnia".

I to w sumie prawda. Ale jak blisko jest stąd do skrótu myślowego, że mikrokontroler to miniaturowy komputer PC?

Mikrokontroler różni się od komputera tym, że nie ma systemu operacyjnego. I w danej chwili potrafi zrobić tylko jedną rzecz...

Oczywiście wykonując wiele takich "pojedynczych rzeczy" dostatecznie szybko - możemy "udawać", że mikrokontroler ma... "wielowątkowość". Ale trzeba wiedzieć, jak to zrobić.

Tymczasem i w internecie i w książkach o Arduino - jak "grzyby po deszczu" mnożą się proste przykłady "zrobienia" prostych rzeczy, które wprost sieją funkcją delay().

Ta funkcja - sama z siebie - nie jest zła.

Tylko trzeba jej używać "z głową" 😉

Link to post
Share on other sites

O żesz.. A myślałem, że to ja pisze długie posty. Na szczęście wciąż wygrywam w kategorii przynudzania 🙂 Aż dziwne, że Admini nic z tym jeszcze nie zrobili.

Z funkcją wykryj klik() faktycznie nie spojrzałem do kodu. Założyłem, że tylko sito() - jako jedyna wciągająca dane ze sprzętu jest bezparametrowa a pozostałe już operują na danych dostarczonych w postaci argumentów. W sumie to kwestia przyjętej konwencji. wykryj_klik() w obecnej formie jest mniej uniwersalna, bo nie możesz jej wywoływać dla różnych przycisków wczytywanych różnymi funkcjami sito1(), sito2() itd. Dla drugiego (i trzeciego i dwudziestego) klawisza będziesz musiał pisać kolejne "wykrycia". Wyjściem jest napisanie i jednej i drugiej tak, by za jednym "strzałem" obie obsługiwały i oddawały zdarzenie klik dotyczące całej, wieloprzyciskowej klawiatury, np. na końcu oddawały numer przycisku w którym wykryto klik lub zero gdy nic się nie wydarzyło. To było to "dla utrudnienia" o którym wspomniałem kiedyśtam.

Jeżeli myślisz o rozbudowaniu programu o raporty dotyczące aktualnego stanu systemu to wystarczy, byś stworzył funkcję np. raport_stanu(led_mode) która będzie wykrywać (oczywiście na podstawie prywatnie zapamiętanego stanu poprzedniego) zmianę stanu i tylko wtedy wypisywać komunikat na Serial.println(). Dzięki temu będziesz dostawał zwięzłe raporty o stanie diodek/systemu tylko przy zmianach.

Mam nadzieję, że budowanie w domu " pół-samoczynnego semafora 5 komorowego" nie jest zabronione prawem, bo brzmi co najmniej tak groźnie jak "elektromagnetyczna wyrzutnia pocisków podkalibrowych" 😉

Podczas dzielenia programu na funkcje staraj się gromadzić w nich podobne rzeczy sterowane tymi samymi "wejściami". Np. jeśli kilka diodek ma być zintegrowanych w jednym miejscu, niech obsługuje je jedna funkcja sterowana jedną zmienną stanu, ale w przypadku gdy np. dwie diodki pokazują coś zupełnie różnego warto napisać dwie osobne funkcje z ew. osobnymi licznikami mrugania. Może być też tak, że wiele diodek na wspólnym panelu nie powinno mrugać w spsób niezsynchronizowany, bo to sprawia wrażenie bałaganu. Wtedy można zrobić osobną funkcję do kręcenia licznikiem mrugania (lub dwoma - szybkiego i wolnego) a funkcje obsługi diodek albo je zapalają, albo gaszą albo wczytują stan globalnego licznika (tego lub tego) i na jego podstawie mrugają. Możliwości - jak widzisz - jest nieskończenie dużo a najważniejsze, że zadania są wydzielone i w zasadzie autonomiczne. Nie musisz przy takim podejściu zastanawiać się jak wcisnąć obsługę mrugania 5 diodek do oczekiwania na klik tego czy innego przycisku.

Z tym programistą to rzeczywiście musiałem coś pokręcić co nie zmienia faktu, że jesteś na najlepszej drodze.

Gdybyś miał ochotę na dalsze ulepszenia systemu głównej pętli, daj znać. Można wywalić i ten jeden, ostatni delay() bazując na sprzętowych timerach w które procesor ATmega328 jest wyposażony. Wtedy wszystkie funkcje będą wywoływane naprawdę co 10ms (czy jakiś tam inny czas) a to z kolei umożliwia, by któraś z nich liczyła np. czas rzeczywisty (godziny, minuty, sekundy) wyświetlany przez jakąś inną np. w pewnym, ustalonym miejscu na LCD. W innych miejscach wyświetlacza mogą pojawiać się komunikaty o stanie programu czy wpisywanych z klawiatury wartościach i nagle wydaje się, że w systemie żyje wiele jednoczesnych procesów - zupełnie jak w dorosłych PC czy małych smartfonach. Najważniejsze, by całość była oparta o szybki podział zadań i wyeliminowanie marnowania 100% mocy procesora w bezsensownych opóźnieniach.

Tą samą metodą można zrobić równoległe poruszanie się wielu serwomechanizmów z dowolnymi prędkościami i różnymi punktami startu/końca ruchu, sterowanie silnikami z "jednoczesną" obsługą wielu czujników itd. Są oczywiście klasy zagadnień których w ten sposób - za pomocą prostej pętli i stałego kwantu czasu - nie rozwiążesz i te zwykle przenoszone są do obsługi za pomocą przerwań sprzętowych, ale i o tym możemy kiedyś pogadać 🙂 bo jedne z drugimi dobrze współpracują i tylko od dobrego podziału zadań zależy, czy system będzie sprawnie reagował na wszystko co się wokół niego dzieje.

Dawaj znać o postępach a ten semafor, to nie wiem.. pokaż go może gdy już będzie coś widać. Czyli jak zwykle: o sukcesach meldować, problemy rozwiązywać we własnym zakresie 🙂

Link to post
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

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.