Język C dla mikrokontrolerów 8051. Przerwania.

O sposobie, w który są obsługiwane przerwania w języku C pisałem już kiedyś, przy okazji omawiania podstaw programowania. Dziś wykorzystamy programowanie przerwań do budowy prostego licznika. Uzyjemy w nim przerwania generowanego przez Timer 1 do obsługi wyświetlacza LED oraz innego, generowanego przez opadające zbocze sygnału na wejściu INT0 do zliczania impulsów zegarowych. W programie wykorzystamy też wskaźniki i ich arytmetykę – będzie okazja co nieco się nauczyć.

Zbudowałem swój licznik korzystając z płytki AVR Starter Kit oraz kawałka płytki uniwersalnej. Oczywiście, jeśli używasz innego zestawu eksperymentalnego – nie jest to żadną przeszkodą. Prawdopodobnie będziesz tylko musiał zbudować sobie wyświetlacz LED.

Płytka wyświetlacza LED

Schemat połączeń wyświetlacza LED pokazano na rysunku 1. Niestety – jeżeli chcesz eksperymentować z tym przykładem programowania, musisz sobie taki układ zbudować. Moim zdaniem przyda on ci się nie tylko do eksperymentów, ale również można go wykorzystać w dowolnym innym układzie wykorzystującym wyświetlacze LED. Trzeba jednak uważać – obsługa wyświetlacza oparta jest o przerwanie generowane przez Timer 1, dlatego też nie polecam takiego układu na przykład do częstościomierza. Chyba, że zgodzisz się na wyłączenie wyświetlania w czasie pomiaru częstotliwości. Zaowocuje to migotaniem przy pomiarze przebiegów o niskiej częstotliwości. Jednak tam, gdzie mikrokontroler nie jest zbyt mocno obciążony i gdzie nie ma ścisłych zależności czasowych – śmiało możesz tego układu użyć. Z powodzeniem na przykład stosuję go w układzie termometru cyfrowego mimo różnic poglądów na temat zawieszania transmisji z DS1820 na czas obsługi przerwań - nie mam z tym kłopotu w mojej aplikacji.

schemat wyświetlacza

 

 

 

 

 

 



 

Rysunek 1. Schemat modułu wyświetlacza z artykułu.

Do konstrukcji wyświetlacza użyłem rejestrów przesuwających 74HCT595. Zrobiłem tak z dwóch powodów. Po pierwsze cena tych układów jest bardzo niska a obciążalność jego wyjść jest wystarczająca do zasilania typowego wyświetlacza LED w stanie niskim. Po drugie zaś, układ nie wyprowadza informacji na wyjścia do momentu pojawienia się osobnego impulsu zegarowego, który ją tam przepisze. Nie ma więc efektu migotania cyfr w czasie wpisywania danych do rejestrów. Połączyłem szeregowo dwa układy 74HCT595 tworząc w ten sposób rejestr 16-to bitowy. Jako pierwszy w szeregu znajduje się rejestr segmentów cyfr, jako drugi rejestr załączający poszczególne cyfry. Wejście szeregowe danych taktowane jest sygnałem o częstotliwości 4,8 kHz natomiast cyfry przełączane są z częstotliwością około 300 Hz.
Schemat modułu wyświetlacza LED pokazano na rysunku 1. Użyłem wyświetlaczy LED ze wspólną anodą. Zasilanie anod załączane jest przez tranzystory MOS z kanałem typu P (BS250). Ich stosowanie jest bardzo wygodnie, ponieważ nie wymagają żadnych dodatkowych elementów takich, jak na przykład rezystory. Wartości rezystorów podłączonych do poszczególnych segmentów wyświetlacza musisz dobrać sobie do posiadanych cyfr. Numery wyprowadzeń wyświetlacza LED potraktuj jako orientacyjne. Istotne są literowe oznaczenia segmentów. Na wejściach rejestrów szeregowych znajdują się rezystory pull-up tak, aby można było wyświetlacz podłączyć do dowolnego z portów mikrokontrolera.
Wyświetlacz do sterowania wymaga trzech linii – jednej danych i dwóch zegarowych. Ja wykorzystałem P1.1, P1.2 i P1.3 Oczywiście zmieniając program możesz użyć dowolnych innych. Również wykonując drobne modyfikacje w programie, można podłączyć do 8 wyświetlaczy LED o wspólnej anodzie. W skrócie funkcjonowanie wyświetlacza wygląda następująco: dane przy pomocy opadającego zbocza sygnału zegarowego podawanego na wyprowadzenie 11 (SRCLK) wpisywane są z wejścia szeregowego na wyprowadzeniu 14 (SER) do wewnętrznego rejestru. Mikrokontroler przesyła pełne słowo 16-to bitowe tak, aby działały oba układy rejestrów. Następnie, po wpisaniu 16 bitów, na wyprowadzenie 12 (RCLK) jest podawany impuls zegarowy, którego opadające zbocze powoduje przepisanie danych z wewnętrznego szeregowo – równoległego rejestru do wyjściowego rejestru typu zatrzask.
Program napisany został tak, że w danym momencie czasu świeci tylko jedna cyfra. Jeśli przełączanie cyfr będzie wystarczająco szybkie, ludzkie oko tego nie zauważy. Jest to typ wyświetlania zwany multipleksowanym (wyświetlanie dynamiczne). Charakteryzuje się on małym poborem prądu - w danym momencie zasilana jest tylko jedna cyfra. Tyle na temat zasady działania, zajmijmy się teraz programem.

Mikrokontroler jako licznik

Licznik wykorzystuje dwa przerwania. Pierwsze, zewnętrzne, powodowane przez opadające zbocze napięcia na wejściu INT0, używane jest zwiększania wartości licznika. Drugie – wewnętrzne, generowane cyklicznie - pochodzące od Timer’a 1, przepisuje stan bufora display do rejestrów wyświetlacza.
Program rozpoczyna się od deklaracji. Linia danych wyświetlacza zadeklarowana zostaje jako P1^0, linia zegara szeregowego jako P1^1, linia zegara wyjściowego rejestru latch jako P1^2. Oprócz tego inicjujemy zmienną typu unsigned int zawierającą zliczane impulsy oraz stałą typu char z zawartością inicjującą rejestr TH1 Timer’a 1. Stała ta to pośrednio częstotliwość, z jaką wywoływane jest przerwanie obsługujące wyświetlacz LED.  Dalej znajduje się uporządkowana w kolejności rosnącej (od 0 do 9) tablica określająca wygląd wyświetlanego znaku (patterns), tablica z kodami kolejności załączania cyfr (digits) oraz tablica - bufor wyświetlacza w RAM (display). Jak łatwo zauważyć pierwsze dwie pierwsze umieszczone są w obszarze pamięci ROM mikrokontrolera (słowo kluczowe code) i mają przypisane wartości. Trzecia znajduje się w obszarze RAM. Jest ona odwzorowaniem stanu wyświetlacza, abstrahując od numeru wyświetlanej aktualnie cyfry. Każda z tablic ma przypisany właściwy jej wskaźnik, czyli zmienną która będzie wskazywać na element tablicy. W momencie zadeklarowania, każdy wskaźnik ustawiany jest na pierwszy element tablicy. Wyrażenie Wskaźnik = &Tablica powoduje przypisanie zmiennej Wskaźnik adresu, pod którym umieszczona jest Tablica. Wskaźniki to fantastyczne narzędzie języka C.
Funkcja Translate zamienia argument x, którym jest dwubajtowa liczba całkowita bez znaku na odpowiadającą tej liczbie zawartość bufora display. Jednym słowem zamienia liczbę na odpowiadający jej wygląd wyświetlacza LED. Metoda jest bardzo prosta, chociaż zapis początkowo może się wydać niezrozumiały. Po wywołaniu funkcji wyłączane są przerwania Timer’a 1. Zostało to zrobione w celu uniknięcia migotania wyświetlacza. Później, wskaźnikowi TDisplay, przypisywane jest wskazanie na ostatni element bufora wyświetlacza. Od niego to rozpocznie się translacja na kody LED. Przebiega ona według następującego schematu: do wartości wskaźnika TPatterns dodaj resztę z dzielenia argumentu przez 10 a następnie skopiuj wskazywaną w wyniku działania stałą typu char z tablicy patterns pod adres wskazywany przez TDisplay. Podziel liczbę przez 10, przesuń wskazanie na następną pozycję w buforze wyświetlacza i powtórz operację dla następnej cyfry. I tak 6 razy – dla każdej z cyfr LED.
Jako swego rodzaju rozszerzenie funkcjonalności, umieszczono wygaszanie zer nieznaczących na początku cyfry. Pętla – rozpoczynając od początku bufora - sprawdza znak znajdujący się pod wskazanym przez wskaźnik bufora wyświetlacza adresem (TDisplay) i porównuje go ze znakiem na początku tablicy digits to znaczy wzorcem „0”. Jeśli są to te same znaki, kod „0” w buforze wyświetlacza zostaje zamieniony na 0xFF, co odpowiada całkowitemu wygaszeniu cyfry. Tak dzieje się aż do momentu napotkania wzorca różnego od wzorca „0”. Wówczas to instrukcja break (spotkaliśmy ją w konstrukcjach warunku switch) przerywa działanie pętli for. Po zakończeniu pętli, załączane jest wyświetlanie – odpowiada mu zezwolenie na przyjmowanie przerwań Timer’a 1.
Dalej napotkamy procedury obsługi przerwań. Wyróżnia je słowo kluczowe interrupt umieszczone w nagłówku funkcji. Funkcja IncrementCounter obsługuje przerwanie zewnętrzne INT0. W zasadzie nie robi nic za wyjątkiem zwiększenia stanu zmiennej counter i wywołania funkcji Translate. Druga z nich, to procedura obsługi przerwania Timer’a 1 - DisplaySend,zajmująca się konstrukcją i przesłaniem słowa do wyświetlacza LED. Przyjrzyjmy się dokładniej stosowanym w niej metodom.
Timer 1 pracuje w trybie 16-to bitowym. Przerwanie zgłaszane jest przez Timer w momencie przepełnienia, to znaczy zmiany stanu z 0xFFFF na 0x0000. Wówczas to wywoływana jest procedura obsługi przerwania. Cykl odliczania rozpoczyna się na nowo od wartości 0 do 0xFFFF. Jeśli nie zdecydujemy inaczej, to od tego momentu do następnego przerwania upłynie czas 65536 cykli maszynowych (to jest 1/12 częstotliwości oscylatora). W przypadku mojego modelu, byłby to czas około 100 milisekund – z całą pewnością byłby on powodem migotania cyfr, ponieważ jest zbyt długi. Można czas do wywołania przerwania skrócić, ustawiając na pożądaną wartość najpierw młodszy a później starszy bajt timer’a. Ponieważ nie zależy mi na bardzo dokładnym odmierzaniu czasu, zdecydowałem się na ustawienie (odświeżenie) tylko starszego bajtu.
Dalej zwiększane są wartości wskaźników TDisplay (wskazanie na cyfrę w buforze wyświetlacza) oraz TDigits  to znaczy wskaźnik do tablicy z kodami załączenia cyfr. Oba te wskaźniki zmieniane są synchronicznie. To znaczy przesunięcie się na następną cyfrę musi powodować również zmianę kodu załączenia wyświetlacza.
Warunek if (TDisplay == 0) służy do zbadania, czy napotkano znak końca bufora w RAM. Jeśli tak, wskaźniki ustawiane są ponownie na początek wskazywanych tablic.
Jako, że kod załączenia musimy wysłać jako pierwszy, na początek zmiennej x  przypisywana jest wartość kodu załączenia cyfry. Później przesuwana jest ona w lewo o 8 pozycji a następnie sumowany jest z nią wzorzec znaku do wyświetlenia z bufora display. To są wszystkie operacje, które musza być wykonane w celu poprawnej budowy słowa do sterowania wyświetlaczem. Teraz docieramy do pętli for, która ma za zadanie wysłanie wszystkich 16 bitów słowa do wyświetlacza.
Znajdująca się wewnątrz pętli operacja przesuwania w lewo zmiennej x, ma na celu przeniesienie pojedynczego bitu słowa do flagi C, która to w następnym poleceniu (dataline = CY) wpływa na stan bitu portu – wyjścia danych. Impuls zegarowy, zmiana stanu shiftline z wysokiego na niski, kończy proces wysłania bitu. Pętla for powtarza operację powtarzana jest 16 razy, dla wszystkich bitów zmiennej. Transmisję kończy przepisanie danych z wewnętrznego rejestru do rejestru wyjściowego poprzez zmianę stanu linii latchline. I to jest koniec obsługi przerwania Timer’a 1.
Program głowny main() zawiera tylko proste ustawienia mikrokontrolera oraz, na samym początku, zapisanie znaku końca bufora wyświetlacza. Ustawiane są:
- rejestr TMOD na wartość 0x11, to znaczy oba Timer’y jako 16-to bitowe a impulsy pobierane są z wewnętrznego zegara,
-  ustawiany jest starszy bajt licznika Timer’a 1,
- włączane są przerwania.
Program główny kończy pętla while(1), w której mikrokontroler oczekuje na impulsy przychodzące na INT0 oraz zajmuje się obsługą wyświetlania.
Pewnym zaskoczeniem był dla mnie drobny fakt napotkany podczas testowania programu. Jego pierwowzorem był identycznie funkcjonujący program w języku asembler. Faktycznie nie przejmowałem się mocno jego optymalizacją, ale gdy napisałem program w C mina mi zrzedła. Programy – mniej więcej równoważne funkcjonalnie – ten napisany w asemblerze zajmował 169 bajtów a ten napisany w C 119 bajtów. Stało się tak chyba z jednego powodu. Po pierwsze w asemblerze dosyć trudno pisze się programy operujące na adresach. Takie programy są po prostu mało czytelne. W C nie ma z tym większego problemu. Można używać pewnych dróg na skróty. I co wy na to wszyscy twierdzący, że programy napisane w językach wysokiego poziomu zajmują dużo pamięci? Oczywiście możesz też powiedzieć, że jestem kiepskim programistą...
Na koniec mam jeszcze małą sugestię. A może by tak dołożyć prostą procedurę komunikacji, chociażby przez port RS232 i nawet bez translacji poziomów napięć, zbudować alternatywę dla układów sterowników wyświetlaczy LED, których cena detaliczna jest lekko mówiąc – przerażająca? Można by było wykorzystać tani mikrokontroler, na przykład AT89C2051. A może AVR? Wówczas nie potrzeba rezonatora kwarcowego. Myślę, że wraz z cyframi będzie on tańszy niż jeden układ sterownika LED.

 

Jacek Bogusz
j.bogusz@easy-soft.net.pl

http://www.tomaszbogusz.blox.pl/

ZałącznikWielkość
Program z artykułu (led6x7.zip)5.29 KB
Schemat wyświetlacza (schemat-led6x7.jpg)896.13 KB

Dodaj nowy komentarz

Zawartość pola nie będzie udostępniana publicznie.