Zmiana stanu sygnału na pinie GPIO może, po odpowiednim jego zaprogramowaniu, spowodować wygenerowanie przerwania. Przerwanie to może zostać wygenerowane przy narastającym zboczu (0 -> 1), opadającym (1 -> 0), lub obu.
Może to znaleźć wiele ciekawych zastosowań.
Zliczanie przechodzących osób, liczby obrotów (obliczanie prędkości obrotowej) czegoś wirującego, uruchomienie alertu (światło, dźwięk, sms..) po otwarciu drzwi, zasunięcie rolet w oknach po zmierzchu....
Wystarczy na pin podać sygnał z czujnika (mikrowyłącznik, transoptor, czujnik elektromagnetyczny) i już możemy działać.
Czujnik oczywiście musi podawać sygnał cyfrowy, w standardzie CMOS, 3,3V. Jeśli produkuje sygnał w standardzie TTL, 5V, musimy go 'przerobić', na przykład dzielnikiem z dwu rezystorów.
Jeśli sygnał naszego czujnika to 'otwarty kolektor' lub zwykły mechaniczny styk, jesteśmy w szczególnie dobrej sytuacji. Piny GPIO wyposażone są w rezystory, za pomocą których możemy podpiąć je do masy lub napięcia zasilania. Zwyczajnie programując pin, bez żadnych kabelków.
Pin oczywiście musi być zaprogramowany na INPUT.
Podpinamy wtedy zwykły mechaniczny wyłącznik pomiędzy pin a masę i gotowe. Zwieramy - jest zero logiczne, rozwieramy - jedynka logiczna.
W naszym przykładzie nie użyjemy zewnętrznego źródła impulsów, nie wniosłoby to niczego istotnego do objaśniania zasady działania, Raspberry sam je wygeneruje.
No to zaczynamy.
Jeśli jeszcze nie masz biblioteki wiringPi, zainstaluj ją jak opisałem przy okazji omawiania czujnika HC-SR04.
Omówimy programik imp.c, skompilować można go ręcznie, albo skorzystać z gotowego skryptu.
Wygeneruje on zadaną liczbę impulsów na pinie 29 GPIO, zliczać je będziemy na pinie 28.
Zwieramy więc pin 29 z 28, jak na zdjęciu.
Aby uniknąć zwierania dwu pinów zaprogramowanych na OUTPUT, na wszelki wypadek uprzednio ustawimy je jako wejściowe, komendami:
gpio mode 28 input gpio mode 29 input
Kompilujemy nasz program:
root@mobile3:/home/rcp# cc imp.c -o imp -lwiringPi
i uruchamiamy
root@mobile3:/home/rcp# ./imp 755 Start. Bieżący stan licznika: 750 Zliczono 755 impulsów
Program wygenerował 755 impulsów na pinie 29 GPIO, które przez zworkę zostały podane na pin 28. Tam zostały zliczone, program wyświetlił ich liczbę, a w trakcie zliczania co 10 impulsów wyświetlał wynik pośredni.
Impulsy były generowane z prędkością 50 na sekundę. Nie jest to jednak granica możliwości Raspberry, testowałem ten program z impulsami generowanymi tysiąc razy szybciej. Dopiero wtedy zaczynał gubić pojedyncze impulsy, około 1%.
Można więc polegać na Raspberry zaprzęgniętym do takiej pracy. Opisywane tu rozwiązanie zastosowałem w praktyce do pomiaru ilości tankowanego paliwa w naszym systemie obsługi zakładowej stacji paliw .
Raspberry zlicza tam impulsy z przepływomierza PIUSI K600B/3, współpracuje z bazą danych MySQL na dostępnym przez sieć serwerze, weryfikując uprawnienia do tankowania kierowców i samochodów, zapisując dane o tankowaniach, mierzy poziom paliwa w zbiorniku stacji czujnikiem HC-SR04 .
Rozwiązanie działa już od roku, dzięki Raspberry zatankowano już 80 tysięcy litrów paliwa, prawie 600 tankowań.
No więc jak to się robi?
Kluczem jest właściwe oprogramowanie pinu GPIO na który podajemy zliczane impulsy.
Korzystając z funkcji biblioteki wiringPi programujemy wybrany przez nas pin jako wejściowy:
pinMode(28, INPUT);
I programujemy przerwania. W chwili zmiany stanu sygnału na tym pinie wygenerowane zostanie przerwanie, które zostanie obsłużone przez wskazaną procedurę, w tym przypadku int_impuls.
Przerwanie może zostać wygenerowane przy opadającym zboczu sygnału, jak w tym przykładzie, przy narastającym (INT_EDGE_RISING), lub narastającym i opadającym (INT_EDGE_BOTH).
wiringPiISR (28, INT_EDGE_FALLING, &int_impuls);
Jeśli źródłem impulsów jest mechaniczny wyłącznik lub czujnik z wyjściem otwarty kolektor włączamy opornik zwierający pin z napięciem zasilającym. W naszym przykładzie linia ta opatrzona jest komentarzem, bo źródłem impulsów będzie inny pin GPIO:
// pullUpDnControl (28, PUD_UP) ;
Procedura obsługująca przerwania w naszym przypadku jest prosta, po każdym przerwaniu zwieksza o jeden wartość zmiennej globalnej impulsow i co dziesięć impulsów wyświetla aktualny jej stan, tak żeby nam się nie nudziło podczas eksperymentów:
int impulsow; void int_impuls(void) { impulsow++; if(impulsow%10 == 0) { printf ("Bieżący stan licznika: %d\r", impulsow); fflush(0); } }
Gdybyśmy użyli zewnętrznego źródła impulsów, byłby właściwie koniec.
Na ekranie wyświetlałaby się informacja ile ich odebrano, moglibyśmy przejść do rozbudowy naszego programu, na przykład żeby co 3600 impulsów uruchomić mechanizm kukułki w zegarze, jeśli impulsy przychodziły w tempie jeden na sekundę.
I taka praktyczna uwaga w tym miejscu, opisywany to sposób zliczania impulsów jest bardzo szybki, jeśli użyjemy mechanicznego wyłącznika warto pomyśleć o odfiltrowaniu, choćby prostym kondensatorem, bardzo krótkich impulsów pojawiających się wskutek drgania styków w chwili jego włączania i rozłączania.
Bez tego zamiast jednego impulsu możemy zaobserwować ich kilka.
No to robimy generator, po to przecież zwarliśmy 28 pin GPIO z 29.
Programujemy pin 29 jako wyjściowy, ustawioamy na nim stan logiczny 0:
pinMode(29, OUTPUT); digitalWrite(29,LOW);
Teraz w pętli, powtarzanej tyle razy, ile podaliśmy w parametrze wywołania naszego programu, na 10 milisekund ustawiamy na pinie 29 logiczną jedynkę, po czym na kolejne 10 milisekund logiczne 0.
Da nam to 50 prostokątnych impulsów na sekundę:
int n; for(n=0;n<atoi(argv[1]);n++) { digitalWrite(29,HIGH); Delay(10); // 10 milisekund impuls digitalWrite(29,LOW); Delay(10); // 10 milisekund przerwa }
Podczas wykonywania powyższej pętli przy każdym opadającym zboczu sygnału na pinie 29 (digitalWrite(29,LOW); wygenerowane zostanie przerwanie i uruchomi się kod procedury int_impuls(), która zwiększy o 1 wartość zniennej globalnej impulsow.
Po jej skończeniu wyświetlony zostanie końcowy rezultat zliczania impulsów:
printf ("\n\nZliczono %d impulsow\n\n", impulsow);
Na koniec jeszcze istotna uwaga. Nasz program korzysta z przerwań. Funkcje sleep i nanosleep nie kontynuują swojego działania po wystąpieniu przerwania.
Dlatego użyliśmy tu procedury Delay() która po wystąpieniu przerwania kontynuuje odmierzanie czasu.
Proszę zwrócić uwagę na dużą literę D w nazwie.
Funkcja o podobnej nazwie, ale z małą literką d istnieje w bibliotece wiringPi, jednak nie radzi sobie z przerwaniami i odmierza czas w milisekundach, a nie w mikrosekundach. Pomyłkowe jej użycie może spowodowac dziwne i trudne do zdiagnozowania objawy!void Delay(int microsec) { struct timespec tim3; tim3.tv_sec = 0; tim3.tv_nsec = microsec * 1000; while(nanosleep(&tim3,&tim3)==-1) continue; }