Skocz do zawartości

Miganie diodami z własnej funkcji


encelados

Pomocna odpowiedź

Witam.

Długo szukałem na forum rozwiązania mojego problemu przy pomocy "szukajki" i... nie znalazłem. Dlatego ośmielam się zapytać.

Pewnie mój problem wyda się Wam śmieszny, jednak proszę o pomoc.

Problem jest taki:

- do pinów cyfrowych (8 i 7) mam podpięte LED-y (jako OUTPUT),
- do pinu cyfrowego 10 mam podpięty tactswitch, zwierający ten pin z masą, z zewnętrznym rezystorem podciągającym 10k i układem RC redukującym drgania styków.

Przyciskiem (na pinie 10) chcę sekwencyjnie przełączać wyświetlanie czterech sygnałów:

1. Dioda zielona (pin 8) świeci światłem ciągłym.

2. Dioda żółta (pin 7) świeci światłem ciągłym.

3. Dioda zielona (pin 8) miga.

4. Dioda żółta (pin 7) miga.

Logika działania jest taka:

w programie występuje zmienna typu byte o nazwie sygnal. Na starcie ma przypisaną wartość = 1. Każde wciśnięcie przycisku (pin10==LOW) ma zmieniać wartość tej zmiennej o 1. Ponieważ sygnałów jest tylko 4, to gdy zmienna sygnal > 4 ustawiam ją z powrotem na 1.

Napisałem dwie funkcje "swiec" i "migaj", do których za pośrednictwem instrukcji wyboru "case" przekazuję numery pinów (7 lub 8), które mają być postawione w stan wysoki (HIGH) na stałe (funkcja "swiec"), lub które mają realizować miganie ("funkcja "migaj").

W funkcji "migaj" miganie realizuję za pomocą instrukcji "do-->while", prostą instrukcją z przykładowego szkicu "Blink". Warunkiem "while" jest "digitalRead(10)==HIGH" - czyli dopóki nie wciśnięto przycisku, dioda ma migać. Jeśli wcisnę przycisk po 5 sekundach - ma nastąpić zmiana sygnału z dotychczasowego na kolejny po tych 5 sekundach. Ale jeśli wcisnę przycisk dopiero po... 2 godzinach - to przez te 2 godziny ma być wyświetlany sygnał dotychczasowy i dopiero po tych 2 godzinach ma nastąpić zmiana (natychmiastowa) na sygnał następny.

I wszystko działa pięknie. Pomiędzy sygnal=1 i =2 i =3 przełączanie jest natychmiastowe. Do momentu, kiedy przyciskiem wywołam sygnal=3 i... naciskam znowu przycisk.

Zamiast spodziewanego natychmiastowego przełączenia w tryb sygnal=4 (miganie żółtej diody) - ustaje miganie diody zielonej (signal=3) i... obie diody nie świecą.

Następuje jakiś stan pośredni (tylko zatrzymanie pętli "do-->while" ?).

Do wejścia w tryb signal=4 (miganie diody żółtej) potrzebne jest kolejne wciśnięcie (czasem kilka kolejnych, krótkich wciśnięć przycisku) - lub jedno dłuższe wciśnięcie przycisku.

Istota problemu:

Jak to zrobić, żeby w trybie migania diody naciśnięcie przycisku powodowało natychmiastowe wyświetlenie następnego sygnału (migającego lub ciągłego - obojętnie)?

No i najważniejsze? Co robię źle?

Mój szkic:

 #define upziel 8     // definicja pinu led zielonej
#define uporange 7   //definicja pinu led żółtej
byte sygnal;         // deklaracja zmiennej sygnal

void setup() {
 // Przypisanie zmiennej sygnal warości początkowej:
 sygnal=1;       
 // Piny diod jako wyjścia:
 pinMode(13, OUTPUT); // Żeby wbudowana LED nie świeciła 
 pinMode(upziel, OUTPUT);  
 pinMode(uporange, OUTPUT);
 // Przycisk zwierający pin10 do masy, nie wciśnięty ma stan HIGH
 // przez zewnętrzny, podciągający R=10k   
 pinMode(10, INPUT);
 digitalWrite(upziel, HIGH); // Na poczatek, żeby cokolwiek świeciło

}

// Funkcja swiec powodująca zapalenie światłem ciągłym jednej z diod.
// Nr pinu przekazany jako argument typu byte.
void swiec (byte lamp1)
 {
   // Najpierw gasimy wszystko co wcześniej było zapalone:
   digitalWrite(upziel, LOW);
   digitalWrite(uporange, LOW);
   //Czekamy 250 ms
   delay(250);
   // i zapalamy diodę przekazaną jako argument funkcji:
   digitalWrite(lamp1, HIGH);
 }

// Funkcja migaj powodująca miganie jednej z diod.
// Nr pinu przekazany jako argument typu byte.
void migaj (byte lamp1)
 {
   // Najpierw gasimy wszystko co wcześniej było zapalone:
   digitalWrite(upziel, LOW);
   digitalWrite(uporange, LOW);

   // i migamy diodą przekazaną jako argument funkcji w pętli do->while:
     do
       {
         digitalWrite(lamp1, HIGH);
         delay(500);
         digitalWrite(lamp1, LOW);
         delay(500);
       }
     while (digitalRead(10)==HIGH);
 }  


void loop() {
// Czekamy na wciśnięcie przycisku
 delay(1000);
 while (digitalRead(10) == HIGH) {} //Jeśli przycisk nie jest wciśnięty 
// Jeśli wciśnięto - to zwiększ zmienną sygnal o jeden:  
 sygnal++;
 //Jeśli sygnal > 4 - to ustaw sygnal na 1:
 if (sygnal>4)
   {
     sygnal = 1; 
   } 

// Określamy, co dzieje się po zmianie zmiennej sygnal:

switch(sygnal)
   {
     case 1:
       {
         swiec(8);
         break;  
       }

     case 2:
       {
         swiec(7);
         break;  
       }

     case 3:
       {
         migaj(8);
         break;  
       }

     case 4:
       {
         migaj(7);
         break;  
       }
   }
}
Link do komentarza
Share on other sites

Nie jęcz. To, że czegoś nie umiesz zrobić nie oznacza, że jest to niemożliwe. Arduino jest prostą platformą i zwykle korzystają z niej początkujący. To oznacza, że większość projektów prezentowanych w sieci to trywialne: Zrób A, Zrób B, Zrób C, Wróć do początku. Ty chcesz robić (choć może jeszcze o tym nie wiesz) dwie rzeczy na raz. Podczytywanie stanu klawiszka i zmiana zmiennej sterującej - to jedno. Mruganie lub świecenie czymśtam - to drugie. A może i trzecie, czyli wyśwtelacz LCD lub port szeregowy.

Powiem Ci tak: to jest trywialne jeśli ma się pojęcie o programowaniu i wyszło się poza piaskownicę Arduino (lub nigdy do niej nie weszło). Może się zdarzyć, że na Forum nikt nie ma czasu by pisać podręcznik o tym jak rozwiązać Twój problem a z poziomu Twojego wiedzy widać, że jedno zdanie nie wystarczy. Skoro nie znalazłeś w sieci odpowiedzi to nawet nie umiesz zadać odpowiedniego pytania bo nie znasz terminów, którymi te rzeczy się nazywa. Jeśli zdobędziesz się na cierpliwość, spróbuję Ci pomóc a samodzielnie, w tzw. międzyczasie spojrzyj na to:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

Jeżeli coś z tego wyniesiesz, to tylko lepiej. To proste podejście, ale jest łatwe do zrozumienia i działa.

Procesor w Arduino nie jest demonem mocy, ale umiejętnie wykorzystany obsłuży równocześnie i 1000 takich rzeczy jakie bezskutecznie chcesz zrobić. Trzeba tylko wiedzieć jak to zrobić.

BTW: Chyba jesteśmy rówieśnikami i trochę wstyd mi, że taki stary chłop wychodzi na przedszkolaka wyśmiewając rzeczy, których po prostu nie pojmuje.

[ Dodano: 22-02-2016, 09:52 ]

A więc wyciąłeś połowę swojego posta żeby nie brzmiał tak płaczliwie. I dobrze, bo poważnym ludziom nie przystoi taka postawa.

Ale wracając do meritum: czytałeś co Ci wskazałem? Zrozumiałeś artykuł (do końca) i swój problem? Czaisz już dlaczego nie możesz używać delay()? Dlaczego funkcje, które mają się wykonywać współbieżnie z innymi nie mogą zawłaszczać procesora głupim opóźnianiem tylko jak najszybciej robić co trzeba i kończyć się? Czy potrzebujesz jeszcze jakichś wyjaśnień?

Przedstawiony na wskazanej stronie sposób śledzenia czasu poprzez odczyt miliseknd z zegara systemowego jest jedną z wielu metod równoległego wykonywania zadań. Duże komputery radzą sobie inaczej, bo ich procesory mają sprzętowe wsparcie dla takich rzeczy, potężne systemy operacyjne i mnóstwo pamięci RAM. Typowy PC - gdy siedzisz i piszesz maila - wykonuje jednocześnie pewnie z kilkaset rzeczy i tylko dyscyplina programistów sprawia, że żadna z nich nie zajmuje procesora tak, żebyś to zauważał. W Arduino nie masz aż takich możliwości, ale spokojnie wyobrażam sobie kilka metod współbieżnego wykonywania nawet skomplikowanych - z punktu widzenia użytkownika czynności. Najważniejszą koncepcją, właśnie gdy nie dysponujesz wygodnym wywłaszczaniem zadań - jak w większych procesorach, jest tzw. maszyna stanów (Finite State Machine, FSM) czyli funkcja napisana tak, by sama pamiętała co już zrobiła, co ma zrobić i kiedy oraz co nie mniej ważne: która swoje operacje wykonuje szybko, bez żadnych opóźnień. Taką funkcję wywołujesz często a ona i tak zrobi co trzeba w momencie gdy będzie to potrzebne. Trochę trzeba się do tej koncepcji przyzwyczaić, ale potem jest już z górki. Nagle okazuje się, że na śmiesznym procesorku AVR możesz robić bardzo fajne, interaktywne i responsywne urządzenia bo jak się tak bliżej przyjrzeć, to po wyrzuceniu delay'ów rzeczywiste obciążenie CPU zadaniami o jakich piszesz jest na poziomie ułamków procentów.

Kolej na Twoje pytania 🙂

  • Lubię! 1
  • Pomogłeś! 1
Link do komentarza
Share on other sites

To pokaż swój nowy kod robiący wszystko na raz 🙂 Być może posłuży za przykład innym, którzy mają podobny problem a jeśli nie, można go będzie bezlitośnie skrytykować.. Nie, nie to marny żart..

Na przykładzie Twoich problemów można pokazać kilka opcji ich rozwiązania. Wspomniane podczytywanie zegara systemowego wydaje się proste, ale moim zdaniem jeszcze prostsze (jak już się to raz zrobi) jest bezpośrednie wykorzystanie własnego przerwania okresowego od timera i wrzucenie tam wszystkich funkcji obsługujących procesy współbieżne. A to mogą być pomiary ADC (nawet z jakimiś obliczeniami), przyciski lub całe klawiatury, obsługa wyświetlaczy czy nawet główny wątek programu rozpisany jako właśnie FSM. Wtedy kod jest znacznie czystszy a główna pętla loop() zwija się do pustej linii w której procesor może np. zasypiać do stanu idle oszczędzając mnóstwo prądu, lub obsługiwać jakiś wątek poboczny, ale np. czasochłonny. Przy okazji wyłaża kłopoty z synchronizacją, przekazywaniem komunikatów, dostępem do zmiennych globalnych, tworzeniem kolejek, obiektów mutex i semaforów, zawłaszczaniem zasobów sprzętowych i ogólnym bałaganem - jak to zwykle bywa gdy wiele rzeczy dzieje się na raz...

Dla ułatwienia jeszcze tylko przypomnę, że w Arduino port szeregowy udostępniany klasą Serial jest obsługiwany w przerwaniach. Konstruktor klasy Serial tworzy w pamięci RAM dwa bufory: nadawczy i odbiorczy, a odbiór i wysłanie znaków nie blokuje systemu na czas transmisji. W Twoim przypadku oznacza to, że wysyłanie nawet dłuższych ciągów do monitora portu szeregowego w PC możesz traktować jak operację szybką. Nie musisz rozdrabniać jej na nadawanie pojedynczych znaków, bo przepisanie stringu do bufora nadawczego system robi w kilkanaście us i nie blokuje innych rzeczy. Dzięki temu proces (funkcja, maszyna stanów) odpowiedzialny za wysyłanie komunikatów przez UART bardzo się upraszcza. Ktoś akurat tę współbieżność zrobił za Ciebie 🙂

Czekamy na wieści z pola bitwy.

  • Lubię! 1
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

Trochę czasu mi to zajęło (naprawdę tydzień?)

"Wieści z pola bitwy" 🤣 są następujące:

Układ:

- do pinów cyfrowych (8 i 7) mam podpięte LED-y (jako OUTPUT): pin 8 LED zielona, pin 7 LED żółta;

- do pinu cyfrowego 10 mam podpięty tactswitch, zwierający ten pin z masą.

Tym razem bez zewnętrznego pullup-u. Wykorzystałem wewnętrzny rezystor podciągający (wejście jako: INPUT_PULLUP).

Do odczytywania stanu przycisku i redukcji drgań styków użyłem biblioteki "Bounce" opisanej w książce Simona Monka "Arduino dla początkujących - Podstawy i szkice".

Szkice opisane w książce oraz wspomnianą bibliotekę można pobrać z tego linku:

http://helion.pl/przyklady/ardupo.zip

Po rozpakowaniu ZIP-a należy rozpakować plik "Bounce.zip" i jego zawartość umieścić w folderze "Moje Dokumenty-->Arduino-->libraries".

Ta biblioteka pozwala w miarę pewnie odczytywać zmianę stanu przycisku.

A teraz mój szkic.

Problem równoległego wykonywania zadań jest ciekawy, bo nie ma jednoznacznego rozwiązania.

Mój szkic pewnie nie jest optymalnym rozwiązaniem. Być może posłuży za przykład innym, którzy mają podobny problem. A jeśli nie - to czekam na "bezlitosną krytykę" 😉

Ale póki co - robi to co powinien (czyli to, co sobie zaplanowałem). Kolejne wciśnięcia przycisku zmieniają sekwencyjnie stan układu (1 LED świeci --> 2 LED świeci --> 1 LED miga --> 2 LED miga). No i po każdej zmianie, aktualny stan układu wyświetlany jest w oknie "Szeregowy monitor" .

#include <Bounce2.h>

#define BUTTON_PIN 10
#define ledGreen 8
#define ledYellow 7

int ledState = LOW;
int sygnal = 1;

// Instantiate a Bounce object
Bounce debouncer = Bounce(); 

void setup()
{
 // Setup the button
 pinMode(BUTTON_PIN,INPUT_PULLUP);
 // After setting up the button, setup Bounce object
 debouncer.attach(BUTTON_PIN);
 debouncer.interval(5);

 pinMode(13,OUTPUT);         // Żeby wbudowana LED na pinie 13 nie świeciła
 pinMode(ledYellow, OUTPUT); // Dioda żółta - wyjście
 pinMode(ledGreen, OUTPUT);  // Dioda zielona - wyjście
 Serial.begin(9600);         // Ustawienie prędkości transmisji
 swiec(ledGreen);            // Na dobry początek ;-)
 Serial.println("Stan 1");   // Też na dobry początek ;-)
}

void loop()
{

 boolean stateChanged = debouncer.update();
 int state = debouncer.read();
 // Wykrywamy wciśnięcie przycisku
 if (stateChanged && state == LOW)
    {
      sygnal++;        // Zwiększamy zmienną "sygnal"
      if (sygnal > 4)
        {
          sygnal = 1;
        }
      // Po wciśnięciu przycisku wyświetlamy stan układu
      Serial.print("Stan ");
      Serial.print(sygnal);
      Serial.println("");
    }


 // Instrukcja wielokrotnego wyboru "switch ... case" zależna od zmiennej "sygnal":
 switch(sygnal)
 {
   case 1:
     swiec(ledGreen);
     break;
   case 2:
     swiec(ledYellow);
     break;
   case 3:
     migaj(ledGreen);
     break;
   case 4:
     migaj(ledYellow);
     break;
 }
}

 // Funkcja swiec - powoduje świecenie LED światłem ciągłym.
 // O tym, która LED świeci - decyduje nr pinu (z #define)
 // przekazany jako parametr.
 // Funkcja niczego nie zwraca.
void swiec (int pin)
 {
   // Najsamprzód gasimy wszystko co było wcześniej zapalone:
   digitalWrite(ledGreen, LOW);
   digitalWrite(ledYellow, LOW);
   // I zapalamy diodę wskazaną jako argument funkcji:
   digitalWrite(pin, HIGH);
 }

 // Funkcja migaj - powoduje miganie LED.
 // O tym, która LED miga - decyduje nr pinu (z #define)
 // przekazany jako parametr.
 // Funkcja monitoruje przycisk i zwraca zmienną "sygnal".
int migaj (int pin)
 {
   // Najsamprzód gasimy wszystko co było wcześniej zapalone:
   digitalWrite(ledGreen, LOW);
   digitalWrite(ledYellow, LOW);
   // I migamy diodą wskazaną jako argument funkcji
   // w nieskończonej pętli "do...while(true)":
   do
   {
     static int ledState;
     static long previousMillis;
     long interval = 400;    // częstotliwość migania LED

     // Zmienne lokalne do wykrywania wciśnięcia przycisku
     boolean stateChangedM = debouncer.update();
     int stateM = debouncer.read();

     // Wykrywamy wciśnięcie przycisku
     if (stateChangedM && stateM == LOW)
       {
         sygnal++;        // Zwiększamy zmienną "sygnal"
         if (sygnal > 4)
           {
             sygnal = 1;
           }
         // Po wciśnięciu przycisku wyświetlamy stan układu
         Serial.print("Stan ");
         Serial.print(sygnal);
         Serial.println("");  
     return sygnal;  // Przerywamy funkcję i zwracamy zmienną "sygnal"
       }

     // Sprawdzamy czy nie czas mrugnąć LED. Dzieje się tak, jeśli 
     // różnica pomiędzy bieżącym czasem (currentMillis) a czasem
     // ostatniego mrugnięcia LED (previousMillis) jest większa
     // niż zmienna "interval", która jednocześnie określa
     // częstotliwość migania LED.
   unsigned long currentMillis = millis();
   if(currentMillis - previousMillis > interval)
     {
       // Zapisujemy czas aktualnego mignięcia LED 
       previousMillis = currentMillis;   
       // jeśli LED jest wyłączona - włączamy ją i na odwrót:
       if (ledState == LOW)
         ledState = HIGH;
       else
         ledState = LOW;

       // Ustawiamy stan pinu wyjścia LED zgodnie ze zmienną "ledState":
       digitalWrite(pin, ledState);
     }
   } while(true);
 }

Jak widać nadal korzystam ze swoich dwu funkcji: "swiec" i "migaj", do których przekazuję jako parametr numer (a właściwie nazwę z "#define") pinu odpowiedniej LED.

Funkcja "swiec" jest prosta "jak drut": niczego nie zwraca, najpierw gasi oba LED-y, a następnie zapala LED przekazaną jej jako argument.

Funkcja "migaj" też najpierw gasi oba LED-y, ale jest już trochę bardziej skomplikowana. Przede wszystkim zrezygnowałem z "niesławnej" funkcji "delay()" i do migania używam przykładu z poleconej strony:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

Oczywiście, żeby LED migała - instrukcja migania musi działać wewnątrz funkcji w pętli imitującej główną pętlę "loop" programu. Stąd instrukcję migania zamknąłem w pętli "do ... while()". Jest to pętla nieskończona, bo ma działać dowolnie długo: albo sekundę - albo kilka godzin. Pętla jest nieskończona, po słowie "while" ma warunek "(true)", co sprawia, że pętla działa w nieskończoność bo jej warunek zawsze jest prawdziwy.

Pojawił się jednak problem: program z instrukcji "switch ... case" wywołuje (przy zmiennej "sygnal" == 3 lub 4) funkcję "migaj". A w tej pojawia się nieskończona pętla. Pomimo umieszczenia w głównej pętli "loop" procedury odczytu stanu przycisku, i związanej z tym odczytem modyfikacji zmiennej "sygnal", program po wywołaniu funcji "migaj" nie reagował już na kolejne wciśnięcia przycisku.

Dlatego w funkcji "migaj", na początku pętli nieskończonej "do ... while()" dodałem... także procedurę odczytu stanu przycisku i związaną z nią modyfikację zmiennej "sygnal". Funkcja "migaj" jest funkcją zwracającą zmienną globalną "sygnal" i przerywa swoje działanie ("return sygnal;) po wciśnięciu przycisku.

Próbowałem rzecz całą zrobić korzystając z przerwań. Wtedy oczywiście przycisk podłączony do pinu nr 2 a nie nr 10. Zadeklarowałem zmienną "sygnal" jako:

volatile int sygnal = 1;

Następnie dodałem obsługę przerwania:

attachInterrupt(0, pauza, FALLING);

A sama funkcja "pauza" obsługująca przerwanie ma zwiększać zmienną "sygnal" i ją zwracać:

int pauza()
 {
   sygnal++;
     if (sygnal >4)
       {
         sygnal = 1;
       }
     return sygnal;  
 }

Ale przy próbie kompilacji mam błąd:

invalid conversion from 'int (*)()' to 'void (*)()' [-fpermissive]
Coś nie tak z wywołaniem obsługi przerwań?
Link do komentarza
Share on other sites

Kod jest makabrycznie skomplikowany (i nie pojmuję go, ale też nie wnikałem), ale nie to mnie martwi. Oczywiście cieszę się, że działa i robi to co chciałeś. Gorzej, że nie przyswoiłeś sobie głównej tezy tej "lekcji": funkcje które muszą być wykonywane współbieżnie z innymi (a przynajmniej ma to tak wyglądać z punktu widzenia użytkownika) nie mogę mieć pętli. Znaczy konstrukcje "for", "do" czy "while" nie są same z siebie groźne, dopóki nie łączą się z zabieraniem czasu procesora. Możesz wykonać w pętli np. sortowanie elementów tablicy czy policzenie średniej ze 100 elementów tablicy, ale zawsze musisz kontrolować czas wykonania. Co więcej - nie możesz "wdrukować" swojego algorytmu (tutaj: mrugania diodkami) wprost do takiej funkcji tak jak robiłeś to przy użyciu delay().

Zobacz: z tego jednego powodu musiałeś wstawiać jakieś odczyty stanu klawisza w pętli (co pociągnęło rozbudowę całego kodu) żeby nie umierała interaktywność programu. Czy gdyby program miał mieć 100x bardziej skomplikowany graf przejść (np. wyobraź sobie stoper z międzyczasami, mrugający diodami, liczący czas, obsługujący LCD, czujnik przejazdu przez start/metę, przyciski, słup z żarówkowym semaforem startowym, unoszoną taśmą i jeszcze pięć innych rzeczy) to umieścisz w 100 miejscach odczyt klawiszka? Już ten kod trudno zrozumieć a tamtego sam nie pojmiesz już nazajutrz (a może najsamprzód 🙂 - fajne ).

Chciałbym, żebyś mnie dobrze zrozumiał: nie krytykuję Twoich dokonań. Zrobiłeś to jak umiałeś i brawo, że doszedłeś tak daleko. Proponuję jednak krótką zabawę. Ja wymyślam pytanie, Ty próbujesz napisać kod. Nie idziemy dalej dopóki samodzielnie nie zrobisz tego o co proszę - to gwarantuje zrozumienie. Wchodzisz?

A w sprawie powyższego programu Koledzy na pewno wynajdą mnóstwo ciekawych uwag i nie omieszkają ich tu wrzucić - możesz być pewien 🙂

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

Dziękuję za opinię.

W programowaniu mikrokontrolerów jestem początkujący. Zdawałem sobie sprawę, że mój szkic jest niedoskonały 🙂

Być może niepotrzebnie zafiksowałem się na świeceniu czy miganiu LED-ami z funkcji - chodziło mi oto, żeby po każdym wciśnięciu przycisku nie przepisywać fragmentu kodu ze zmienionymi numerami pinów, tylko taki fragment zapisać jako funkcję i potem tylko go wywoływać.

Funkcja migania czy ta z "delay" czy ta bez - umieszczona jest w głównej pętli "loop", która jest cyklicznie powtarzana. Dlatego dioda miga. Jeśli kod migania umieszczę w innym miejscu (np. w funkcji) - dioda zapali się, zgaśnie i... koniec. Tak to rozumiem. Dlatego użyłem pętli w mojej funkcji.

Ale o mikrokontrolerach rzeczywiście zbyt wiele nie wiem. Chciałbym dowiedzieć się więcej niż można wyczytać w prostych kursach www czy w prostych książkach.

Proponuję jednak krótką zabawę. Ja wymyślam pytanie, Ty próbujesz napisać kod. Nie idziemy dalej dopóki samodzielnie nie zrobisz tego o co proszę - to gwarantuje zrozumienie. Wchodzisz?

Odpowiedź brzmi: wchodzę.

Link do komentarza
Share on other sites

OK, zaczniemy więc od rzeczy najciekawszej, czyli od maszyny stanów. Nie będę tu podawał jakichś naukowych definicji - na pewno sam znajdziesz kilka. Przydatność takiego czegoś w programach mikrokontrolerowych gdzie trzeba robić wiele rzeczy na raz spróbuję pokazać Ci na przykładzie. Sytuacja będzie dość hipotetyczna, ale na poczekaniu nie wymyślę niczego lepszego.

Wyobraź sobie, że siedzisz w pokoju bez okien. Nie wiesz co się dzieje na zewnątrz, ale też specjalnie Cię to nie obchodzi. Na domiar złego jesteś zatrudniony jako powiedzmy latarnik zapalający i gaszący jakieś światło na wieży. Nie wiesz gdzie ono jest i w sumie też masz to gdzieś, ale w pokoju na ścianie jest wajcha: do góry - włączone, do dołu - wyłączone.

Twoim zdaniem jest włączanie tego raz na godzinę na czas 45 minut i wyłączanie na pozostałe 15 minut - tylko w nocy. W dzień masz to gasić na amen a czasami przelatuje samolot i wtedy niezależnie od wszystkiego masz to zapalić żeby nie przywalił w wieżę. Nie masz zegarka (he, he), ale co minutę przychodzi do Ciebie posłaniec od - powiedzmy Dyrekcji (Więzienia?), krzyczy przez drzwi co masz robić i na chwilę (5s?) zapala Ci światło w pokoju. On też ma gdzieś swoją pracę i nie obchodzi go jak sobie radzisz - a takich "elektryków" musi obsłużyć w ciągu minuty wielu.

Gdy ze światłem na wieży będzie coś nie tak, ma Ci nie dawać jedzenia - starasz się więc i kombinujesz. Możesz dostawać trzy polecenia: zapalaj, gaś i mrugaj. Masz gwóźdź więc możesz drapać po ścianach coś tam sobie zaznaczając. Co byś zrobił w tej sytuacji? Czasu masz tylko tyle, by ew. rzucić okiem na ścianę, wymyślić co zrobić z wajchą, podbiec do ściany i ew. przełączyć. Potem żarówka w pokoju gaśnie i możesz jedynie spać - tego przepisy owego miejsca nie precyzują, ale rozmowy z kolegami przez ścianę są raczej zabronione. Inne rozrywki również...

Oczywiście w sytuacji gdy koleś krzyknie "Zapal!" lub "Zgaś" sytuacja jest prosta: lecisz i wykonujesz polecenie natychmiastowo, ale co jeśli usłyszysz "Mrugaj!"?

Ciekaw jestem Twojego pomysłu, bo będzie to prototyp działania funkcji, którą zaraz napiszesz.

Link do komentarza
Share on other sites

Nie jestem pewien czy dobrze zrozumiałem ten przykład.

(...) Twoim zdaniem jest włączanie tego raz na godzinę na czas 45 minut i wyłączanie na pozostałe 15 minut - tylko w nocy. W dzień masz to gasić na amen a czasami przelatuje samolot i wtedy niezależnie od wszystkiego masz to zapalić żeby nie przywalił w wieżę. (...)

Możesz dostawać trzy polecenia: zapalaj, gaś i mrugaj (...)

Domyślam się, że mrugaj - to jak ten samolot... 🙂

Ale w sumie to nieistotne. Bo radaru też nie mam 🤣

1. Nie mam zegarka i też mam to gdzieś. Bo to posłaniec jest moim zegarkiem (a właściwie jego cykliczne wizyty).

2. Rozumiem, że to ów posłaniec zawsze przyniesie mi odpowiednie polecenie. Bo gdyby było inaczej - mówił by mi tylko jestem, już czas, a ja musiałbym liczyć jego wizyty i odpowiednio ustawić wajchę.

3. Nie widzę latarni i tego czy świeci czy nie - ale mam na ścianie wajchę w ustawieniu z poprzedniej wizyty posłańca.

Więc jeśli koleś przyniesie polecenie mrugaj, to robię na ścianie jakiś znaczek - niech to będzie literka M, która mówi mi, że dostałem polecenie mrugania. I przestawiam wajchę w przeciwne położenie. Teraz, jeśli każde kolejne polecenie będzie mrugaj - to za każdym razem przestawiam wajchę w przeciwną pozycję. Mrugam.

Jeśli dostanę inne polecenie - to przekreślam literkę M na ścianie i porównuję nowe polecenie z ustawieniem wajchy. Jeśli nowe polecenie to zapalaj i wajcha jest w górę (lub gaś i wajcha jest w dół) - nie przestawiam jej. Jeśli nowe polecenie nie zgadza się z pozycją wajchy, to ją przestawiam w położenie nowego polecenia.

Link do komentarza
Share on other sites

Moje intencje były inne i "mrugaj" miało oznaczać ciągłe zapalenie na 45 minut i zgaszenie na 15 czyli cykl godzinowy, ale wszystko jedno. Chodziło o to, że nie możesz samodzielnie liczyć czasu a decyzje musisz podejmować na podstawie otrzymywanych rozkazów (a sam fakt ich wydania jest dla Ciebie kwantem czasu) i to w sposób błyskawiczny. Wiem, że rozumiesz sytuację a gdyby miało być tak jak planowałem, zamiast jednego M na ścianie robiłbyś kreski pokazujące liczbę minut od ostatniego zapalenia/zgaszenia.

Tak właśnie działają FSM czyli maszyny stanów: najcenniejsze co mają to ich stan - czyli jakaś zmienna która nie znika po opuszczeniu funkcji i w której można zapamiętać w jakim stanie byliśmy ostatnio. Przeczytaj coś dzisiaj i zakresach dostępności zmiennych w C (scope) oraz o czymś co kiedyś nazwało się klasami pamięci (storage class). Jeżeli chcesz napisać funkcję która ma swoją "ścianę" to potrzebujesz zmiennej w której będziesz mógł wpisać i zostawić swój stan aż do następnego wywołania tej funkcji. Mógłbyś to zrobić w najprostszy sposób, czyli tak:

int fsm_state;

void my_fsm(void)
{
....
 if (fsm_state == cośtam)
   fsm_state = cośinnego;
....
}

ale takie postępowanie nie jest dobre, bo zmienna globalna jest dostępna dla całego programu a to znaczy, że każdy może ją popsuć.

Gdybyś zrobił tak:

void my_fsm(void)
{
int fsm_state;
....
 if (fsm_state == cośtam)
   fsm_state = cośinnego;
....
}

wtedy rzeczywiście reszta programu "nie widzi" zmiennej fsm_state więc jest trochę bezpieczniej, ale z kolei fsm_state znika po wyjściu z funkcji. Najlepszym wyjściem będzie więc to:

void my_fsm(void)
{
static int fsm_state;
....
 if (fsm_state == cośtam)
   fsm_state = cośinnego;
....
}

To daje gwarancję, że nikt nie dobierze się przypadkowo do zmiennej fsm_state (spróbuj gdzieś poza funkcją jej użyć a dostaniesz błąd kompilacji) a jednocześnie jest ona umieszczona w "normalnej" pamięci RAM i "zostaje" po zakończeniu działania i wyjściu z funkcji. O tym właśnie poczytaj: zmienne statyczne i automatyczne. A także ogólnie o takich rzeczach jak: sterta i stos oraz o tym jak miejsce deklaracji zmiennej wpływa na jej widoczność. W razie czego pytaj.

Acha i praca praktyczna: wyobraź sobie, że masz już w programie mechanizm wywołujący (różne) funkcje co np. 10ms, czyli 100 razy/s. Korzystając z wiedzy o zmiennych statycznych napisz jakąś prostą funkcję wołaną w ten sposób, która mruga diodą LED z częstotliwością 1Hz (np. 100ms ciągłego świecenia, 900ms ciemności). Oczywiście musi się wykonywać maksymalnie krótko - oprzyj jej działanie na zmiennej, której stan nie znika od wywołania do wywołania. Czyli: zmiana stanu, prosta decyzja co robimy, akcja, koniec.

Link do komentarza
Share on other sites

Ta funkcja wywoływana co 10ms mogłaby wyglądać tak:

void migaj()
 {
   static int licznik;  // Kreska na ścianie
   licznik++;
     if (licznik == 1)
       {
         digitalWrite(13, HIGH);  // Zapalamy latarnię
       }
     if (licznik == 10)    // Minęło 100ms 
       {
         digitalWrite(13, LOW);    //Gasimy latarnię
       }
     if (licznik == 100)   // Minęło kolejne 900 ms
       {
         licznik = 0;   // Powtarzamy wszystko
       }
 }
Link do komentarza
Share on other sites

OK, o to właśnie chodzi. Zużywasz tylko tyle czasu procesora ile musisz do wykonania wszystkich potrzebnych w danej chwili zadań. Można to było napisać przy pomocy switch zamiast if, lub trochę inaczej pogrupować kod ale nie ważne - działa i możesz ją dowolnie testować pisząc sobie prostą pętlę while:

while(1)
{
  migaj();
  delay(10);
}

lub po prostu wpisując do Arduinowego loop:

void loop()
{
  migaj();
  delay(10);
}

Delay? - tu na razie wolno 🙂 To tylko proteza prawdziwego wywoływacza funkcji służaca do testowania kodu.

To teraz następny stopień: skoro masz już funkcję mrugającą to zmodyfikuj ją tak, by w zależności od przekazanego parametru, np. led_mode robiła jeszcze dwie rzeczy:

gdy led_mode == 0 gasiła diodkę,
gdy led_mode == 1 świeciła diodką,
gdy led_mode == 2 mrugała w takim cyklu jak powyższy.

Acha, żeby było ciekawiej chcemy, by cykl mrugania zawsze zaczynał się od początku - od pełnej długości pierwszego mrugnięcia niezależnie od tego w jakim stanie licznika mruganie się skończyło.

Ponieważ na pewno taka prosta rzecz pójdzie jak po maśle, możesz przemyśleć następną rzecz: funkcję, która będzie podczytywać stan przycisku. Przyciski nie są tak miłymi elementami jak się wydaje i nie możemy im ufać z tej prostej przyczyny, że są mechaniczne. Podczas zmiany stanu (zwierania lub rozłączania) ich sprężynki potrafią wielokrotnie odbić się jedna od drugiej i wyprodukować nam ciąg impulsów. Nie jest on długi - zależy od konstrukcji przycisku i możemy spokojnie założyć, że nie trwa więcej jak 50ms. Tak więc Twoja następna funkcja (wołana jak zwykle co 10ms) musi nie tylko rozpoznawać czy switch jest zwarty lub rozwarty, ale przede wszystkim odkłócać go. Zastanów się jak przy użyciu licznika i kilku zmiennych (np. przechowującej poprzedni stan przycisku i ostatni uznany za stabilny) oddawać tylko stabilne wartości. Możesz to sobie rpozrysować w czasie. Oczywiście żadnych delay'ów, kwant czasu = 10ms, funkcja typu int oddająca stabilne 0 lub 1 🙂 Od tego będzie już bardzo krótka droga do detekcji wciśnięć bez łaski czyichś tajemniczych bibliotek.

Link do komentarza
Share on other sites

Funkcja zmodyfikowana z parametrem led_mode.

Parametr funkcji jest typu byte bo ma zakres od 0 do 2. W porównaniu do typu int - oszczędzam jeden bajt na rozmiarze zmiennej.

Czy taki typ parametru jest prawidłowy?

void migaj(byte led_mode)
 {
   static int licznik;  // Kreska na ścianie    

   if (led_mode == 0)    // Gasimy LED
     {
       digitalWrite(13, LOW);
       licznik = 0;
     }
   if (led_mode == 1)    // Zapalamy LED
     {
       digitalWrite(13, HIGH);
       licznik = 0;
     } 
   if (led_mode == 2)    // Mrugamy LED
     {
       licznik++;
         switch(licznik)
           {
             case 1:
               digitalWrite(13, HIGH);  // Zapalamy LED
               break;
             case 10:    // Minęło 100ms
               digitalWrite(13, LOW);    //Gasimy LED
               break;
             case 100:   // Minęło kolejne 900 ms
               licznik = 0;  // Powtarzamy wszystko
               break;
           }
     }
 }
Link do komentarza
Share on other sites

Właśnie tak, uzyskałeś funkcję która w autonomiczny sposób steruje diodą. Od tej pory, zmieniając tylko parametr wywołania (i zapewniając, że wołasz tę funkcję tak często jak sobie założyłeś) masz zrobioną warstwę fizycznego sterowania diodką. Części programu będące wyżej w hierarchii nie muszą już się niczym martwić. Wystarczy, że zmienią stan jednej swojej zmiennej a LED posłusznie wykona rozkaz. Przy dekodowaniu komend/stanów bardziej naturalną jest konstrukcja switch, której już użyłeś. Jeżeli jeszcze dodasz definicje symboli odpowiedzialnych za stan diodki, program zaczyna być samoopisujący się:

#define LED_OFF 0
#define LED_ON 1
#define LED_BLINK 2

void migaj(byte led_mode) 
 { 
   static int licznik;  // Kreska na ścianie    

   switch(led_mode)
     {
       default:
       case LED_OFF:
         digitalWrite(13, LOW); 
         licznik = 0;
         break;

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

       case LED_BLINK:
         if (++licznik >= 100)            // Od razu widać, że licznik działa w cyklu 100
           licznik = 0;
         if (licznik < 10)
           digitalWrite(13, HIGH);
         else
           digitalWrite(13, LOW);
         break;
     } 
 }

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

volatile struct
{
   int
       period,
       pwm;
   byte
       mode;
} my_led;

i wtedy odpowiedni kawałek kodu wygląda jeszcze lepiej:

          if (++licznik >= my_led.period)
           licznik = 0;
         if (licznik < my_led.pwm)
.....

a inna część programu będzie mogła sterować pracą LEDy za pomocą prostych przypisań:

my_led.period = 200;
my_led.pwm = 50;
my_led.mode = LED_BLINK;

i od tej pory (jeżeli wciąż podstawą czasu systemu jest 10ms) diodka mruga z częstotliwością 0.5Hz i wypełnieniem 25%. Sama 🙂

No to część "wyjściową" interfejsu użytkownika mamy z głowy. Czekamy na obsługę niesfornego przycisku.

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.