
Witajcie moi drodzy
W tym temacie poznamy podstawy przerwań i timerów w PIC18F2550. Na początek będzie krótki wstęp teoretyczny, a potem wszystko przerobimy dokładnie na praktycznych przykładach.
Do lektury tematu przyda się ogólna wiedza na temat przerwań i programowania w C, choćby taka z Arduino.
Temat oczywiście nie zastąpi w pełni czytania noty katalogowej PIC18F2550 gdzie wszystko jest dokładnie opisane, ale na pewno zapewni kilka dobrych przykładów co i jak użyć w praktyce, wraz z pełnym kodem, wytłumaczeniem i prezentacją działania.
Wszystkie przykłady będą napisane w języku C pod darmowy kompilator SDCC, do każdego przykładu zamieszczę pełny kod i plik projektu do pobrania.
Spis części (osobnych tematów) tutoriala
Tutorial podzielony jest na osobne tematy i tutaj znajdują się do nich linki.
Część 1 - Konfiguracja środowiska pracy
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18304424#18304424
Część 2 - Hello world, piny IO, cyfrowe wejścia i wyjścia
https://www.elektroda.pl/rtvforum/topic3647884.html
Część 3 - Ustawienia oscylatora. Oscylator wewnętrzny, zewnętrzny, rezonator kwarcowy, PLL
https://www.elektroda.pl/rtvforum/topic3657704.html
Część 4 - Timery, przerwania
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18580858#18580858
Część 5 - Obsługa wyświetlacza siedmiosegmentowego
https://www.elektroda.pl/rtvforum/topic3676650.html
Spis treści będzie uzupełniany wraz z pisaniem przeze mnie kolejnych części.
Ten temat to jest część 4, czyli Timery, przerwania. Zaczynamy.
Czym jest Timer?
W wielkim skrócie: timery sprzętowe to gotowe mechanizmy w mikrokontrolerze do zliczania. Zliczać mogą czas (poprzez zliczanie cyklów z oscylatora) lub np. impulsy (na wybranym pinie).
Timery mogą służyć do wykonywania danej akcji co ustalony czas.
Timerowi ustawia się ile ma zliczyć cykli, i po zliczeniu wszystkich następuje przepełnienie i Timer wywołuje przerwanie.
Oczywiście timera można używać też bez przerwania - po prostu samodzielnie odczytywać z jego rejestrów ile zliczył.
Okres timera konfigurujemy przede wszystkim poprzez dwa ustawienia:
- ustawienie preskalera timera (dzielnika zegara wejściowego), by z grubsza określić jego częstotliwość działania
- ustawienie licznika przepełnienia timera, by dokładnie określić kiedy timer wywoła przerwanie
Szczegóły konfiguracji zależą już od konkretnego Timera. Timery mogą oferować różne ustawienia preskalera, źródła zegara, itp.
Czym jest przerwanie?
Przerwanie jest to mechanizm pozwalający tymczasowo przerwać wykonywanie głównego programu, obsłużyć jakieś zdarzenie (ang. event), i wrócić z powrotem do wykonywania głównego kodu.
Od strony assemblera przerwanie wygląda tak, że w momencie pojawienia się zewnętrznego zdarzenia bieżący stan wykonywania kodu jest zapisywany, potem dokonywany jest skok w wyznaczone miejsce w programie, wykonywane są instrukcje stamtąd, a potem następuje powrót.
Na szczęście my programując w C nie musimy o to zadbać - więc obsługa przerwań dla nas będzie naprawdę bardzo łatwa.
Przerwania mogą mieć różne źródła, przykładowo:
- przerwanie wewnętrzne, np. od Timera - wywoływane co co jakiś czas, np. co 100ms
- przerwanie zewnętrzne - wywoływane np. gdy wykryje się narastające zbocze na określonym pinie
Poniżej dokładnie przyjrzymy się przerwaniom i timerom w PIC18F2550.
Timery w PIC18F2550
PIC18F2550 oferuje aż 4 timery:

Mamy w nim kolejno:
- Timer0 - wewnętrzny timer/zewnętrzny licznik impulsów, 16 bitowy
- Timer1 - wewnętrzny timer/zewnętrzny licznik impulsów, 16 bitowy
- Timer2 - wewnętrzny timer, 8 bitowy
- Timer3 - wewnętrzny timer/zewnętrzny licznik impulsów, 16 bitowy
Każdy z nich może wywoływać przerwanie w momencie swojego przepełnienia. Trzy z nich (mogą być) 16 bitowe, czyli mogą zliczać 2^16 (65536) impulsów. Dwa z nich obsługują zliczanie impulsów z innego źródła niż zegara wewnętrznego (te źródła są na określonych pinach). Timery te obsługują też ustawienie preskalera (osobno dla każdego timera), co pozwala zliczać np. co czwarty impuls z danego źródła.
Przerwania w PIC18F2550
PIC18F2550 obsługuje przerwania z różnych źródeł. Należą do nich m. in:
- przerwania od Timerów (przepełnienie się licznika Timera 0, 1, 2, 3)
- przerwania zewnętrzne (na pinach INT0, INT1, INT2, np. wciśnięcie przycisku)
- przerwanie od UART (wysłanie lub odbiór znaku)
- przerwania od ADC (gdy ADC zakończy konwersje)
- przerwanie od USB (moduł sprzętowego USB)
- przerwania CCP (Compare/Capture/PWM)
Rejestry związane z przerwania w PIC18F nazywane są wedle określonego nazewnictwa, a mianowicie:
- bit xxxIE, tzw. 'interrupt enable', jeśli ustawimy go na 1 to dane przerwanie zostaje włączone. Przykładowo dla Timera 0 jest to TMR0IE z rejestru INTCON
- bit xxxIF, tzw. 'interrupt flag', jest on automatycznie ustawiany na 1 jeśli dane zdarzenie miało miejsce, musi zostać ustawiony na 0 w programie w funkcji obsługującej przerwanie. Przykładowo dla Timera 0 jest to TMR0IF z rejestru INTCON.
Ponadto mamy kilka globalnych ustawień które kontrolują przerwania:
- bit GIE, tzw. 'global interrupt enable', który powinien być ustawiony na 1 jeśli chcemy by przerwania się wykonywały. Znajduje się on w rejestrze INTCON
- bit IPEN, tzw. 'interrupt priorities enable', jeśli jest ustawiony na 1 to pozwala na obsługę dwóch priorytetów przerwań (niskiego i wysokiego)
- bit PEIE, tzw. 'peripheral interrupt enable', jest to osobny bit który pozwala włączyć przerwania zewnętrzne
Oprócz tego potrzebna jest też oczywiście funkcja która obsłuży przerwanie. Funkcją tą w PIC18F nazywamy ISR, czyli 'interrupt service routine'. Funkcję tą oznacza się nieco inaczej w zależności od kompilatora, ale w SDCC służy do tego słowo kluczowe __interrupt. Na przykład:
Code: c
Sama nazwa funkcji tutaj nie ma znaczenia. Liczba 1 oznacza tutaj numer przerwania.
To w tej funkcji musimy obsłużyć poprawnie flagę xxxIF (wyłapać ją by rozpoznać jakie przerwanie nastąpiło i ustawić na 0):
Code: c
Na przykład, dla Timera 0:
Code: c
Poniżej przedstawię działanie przerwań w PIC18F2550 i SDCC na praktycznych przykładach.
Zainteresowanych dokładniejszymi informacji odysłam do noty katalogowej PICa:
Kalkulator Timerów dla PIC
Timerów dla PIC18F2550 nie trzeba liczyć ręcznie - jest do tego dostępne darmowe, niezależne od użytego IDE/kompilatora narzędzie.
Nazywa się ono Timer Calculator i pochodzi od Mikroelektroniki.
Wersja dla Windowsa jest do pobrania stąd:
https://www.mikroe.com/timer-calculator
A właściwie to stąd, bo na czas pisania tego tematu powyższa strona ma przekierowanie na LibStock:
https://libstock.mikroe.com/projects/view/398/timer-calculator
Kopia zapasowa wersji 4.0 z linku powyżej (na wypadek gdyby wygasł):
Timer Calculator jest w stanie wygenerować poprawny kod ustawienia Timera dla danego oscylatora i danego okresu wywoływania przerwania. Czyli np. podajemy, że chcemy timer 1 co 100ms przy zegarze 8MHz i z tego narzędzia uzyskujemy gotowy kod:

UWAGA: Ale ten gotowy kod nie jest w pełni kompatybilny z SDCC, należy podmienić nieco zapis rejestrów na inny (ale analogiczny do pokazanego). W razie wątpliwości zapraszam do zapoznania się z przykładami z tego tematu, dla każdego Timera dam przykład. Natomiast samych wartości hex z kalkulatora (ustawienia rejestrów konfiguracji i liczników) można używać bez zmian. Sam będę opierać się na tym kalkulatorze tworząc ten temat.
Timer Calculator wspiera różne rodzaje PICów i nie tylko.
Niekompletna lista mikrokontrolerów wspieranych przez Timer Calculator:
- praktycznie wszystkie PICe, PIC16F, PIC18F, dsPIC, PIC24, PIC32
- różne AVRy, ATMega, ATTiny, ATXMega
- STM32
- AT90
Pełna lista dostępna w programie. Zapraszam do pobierania.
Przypomnienie - schemat układu testowego
Przed przejściem już do praktycznych przykładów chciałbym przypomnieć, że wszystkie pokazane tu programy są uruchamiane na takim układzie złożonym na płytce stykowej:


Układ ten obejmuje minimum elementów potrzebnych do uruchomienia PICa, podłączenie do programatora PICKIT3 i zasilania, itp.
Schemat całości:

Więcej informacji (jak również opis jak wgrywać wsad na PICa) znajduje się w poprzednich częściach tutoriala.
Timer0 - 8/16 bitowy timer
Timer 0 może działać w dwóch trybach: 8 i 16 bitowym. Timer 0 może działać w roli licznika impulsów na pinie T0CKI (pin RA4). Timer 0 zlicza impulsy (z zegara lub z pinu) i w momencie przepełnienia się jego licznika wywołuje przerwanie.
Z Timerem 0 powiązane są rejestry:
- T0CON - tutaj konfigurujemy działanie Timera 0 (włączamy go, ustawiamy prescaler)
- TMR0L - jest to niższy bajt licznika Timera 0
- TMR0H - jest to wyższy bajt licznika Timera 0
Zawartość rejestru T0CON pokazuje obrazek:

W rejestrze T0CON są następujące bity:
- TMR0N - flaga pozwala na włączenie Timera 0
- T08BIT - flaga określająca, czy Timer 0 jest w trybie 8-bitowym czy 16-bitowym
- T0CS - 'clock source', źródło zegara Timera 0 (czy zliczamy impulsy na pinie T0CKI czy z wew. zegara PICa)
- T0SE - 'source edge', określa czy zliczamy na narastającym czy opadającym zboczu
- PSA - 'prescaler assigment bit', pozwala nam włączyć lub wyłączyć prescaler dla tego timera. 1 oznacza, że prescaler jest pomijany.
- T0PS2, T0PS2, T0PS1 - prescaler Timera 0
Wartość prescalera dla danej kombinacji bitów T0PS* przedstawia tabela:

Zaraz przekonamy się jak to działa w praktyce.
Timer0 - przykładowy kod (blink led)
Na bazie wiedzy z poprzednich akapitów (i być może też noty katalogowej PIC18F2550 oraz kalkulatora Timerów od Mikro C) bardzo łatwo jest opracować prosty program, który miga diodą właśnie z pomocą Timera i przerwania.
Tutaj zaprezentuję takich kilka, ten będzie pierwszy i najdokładniej opisany.
Program ten będzie wywoływać przerwanie co 1 sekundę i w funkcji jego obsługi zmieniać stan diody LED.
Użyjemy do tego Timera 0.
Co musimy w tym celu zrobić?
1. ustawić bit TMR0ON z T0CON by włączyć timer 0
2. ustawić bit GIE z INTCON by włączyć globalnie przerwania
3. ustawić bit T0IE z INTCON by włączyć przerwania od timera 0
4. jeśli chcemy, to ustawić odpowiednio wartość prescalera z rejestru T0CON (tu możecie posłużyć się gotowym kalkulatorem Timer Calculator)
5. ustawić odpowiednio wartości TMR0H i TMR0L by dobrać ilość impulsów po której nastąpi przepełnienie (tu możecie posłużyć się gotowym kalkulatorem Timer Calculator)
6. dodać funkcję obsługującą przerwanie (w SDDC z dopiskiem __interrupt ) i wyłapać z pomocą if w niej odpowiednią flagę przerwania (tutaj T0IF z INTCON) i po obsłudze przerwania ustawić ją na 0
Tylko tyle trzeba aby pomigać diodą LED z pomocą Timera 0.
Jak określić ręcznie (bez kalkulatora od Mikro C) okres Timera?
1. Potrzebujemy znać częstotliwość instrukcji PICa, jest ona równa częstotliwości zegara podzielonej na 4.
Jeśli używamy wewnętrznego oscylatora 1MHz (domyślnego), to wynosi ona 1MHz/4 = 250kHz
2. Potrzebujemy dobrać sobie dowolny prescaler do dodatkowego podziału tej częstotliwości. Jest on trzybitowy, bity T0PS2,T0PS1,T0PS0. Weźmy na przykład ustawienie 001, czyli preskaler 1:4.
250kHz/4 = 62.5kHz czyli impuls co 0.016ms
3. Teraz zostało wybrać co ile tych impulsów o okresie 0.016ms chcemy mieć przerwanie. Ustawia się to w TMR0H i TMR0L.
1 sekunda to jest 1000ms
1000ms/0.016ms = 62500
Wychodzi nam, że chcemy mieć przerwanie co 62500 zliczonych impulsów przez Timer 0.
4. Teraz należy pamiętać, że Timer 0 z każdym impulsem inkrementuje wartość TMR0L, a nie dekrementuje. Nie możemy po prostu ustawić tam naszego 62500.
Musimy policzyć, ile od 62500 brakuje do pełnych dwóch bajtów, czyli 2^16
2^16 = 65536
65536 - 62500 = 3036
5. Musimy ustawić rejestry TMR0L/TMR0H na 3036. W tym celu użyjemy notacji szesnastkowej.
3036 w postaci szesnastkowej zapisujemy tak: 0x0BDC
6. Pamiętając, że młodszy (niższy) bajt jest po prawej, możemy to podzielić na:
TMR0H = 0x0B;
TMR0L = 0xDC;
Gotowe! Tyle wystarczy, by mieć przerwanie co ustaloną przez nas sekundę.
UWAGA: jeśli w punkcie 3 okaże się, że policzona przez ilość impulsów jest większa od 2^16, to po prostu musimy się cofnąć do pkt 2 i dobrać inny prescaler. A w skrajnych przypadkach może być potrzeba zliczania impulsów ręcznie w kodzie, bo też nie każda wartość prescalera jest dostępna.
Poniższy kod pokazuje pełna implementację tego co powyżej omówiłem:
Code: c
Powyższy kod wymaga jeszcze podkreślenia paru rzeczy.
- TMR0H i TMR0L musi być ustawiane przez nas na pożądaną ilość impulsów ZA KAŻDYM RAZEM, bo licznik te rejestry modyfikuje. Dlatego są one ustawiane i w funkcji przerwania i w uruchomieniu Timera
- Te tajemnicze 0x81 wpisane do T0CON to jest po prostu 1000 0001, czyli jak spojrzymy do dokumentacji T0CON jest to równoznaczne z wpisaniem 1 do TMR0ON i 1 do T0PS0.
- przypomnę, że powyższy kod działa na domyślnym ustawieniu wewnętrznego oscylatora, czyli 1MHz. Przy zmianie tej częstotliwości zmieni się oczywiście też okres Timera 0 i jeśli dalej będziemy chcieli mieć 1 sekundowy okres przerwania, to trzeba będzie poprawić konfigurację Timera.
Jak działa miganie diodą z pomocą przerwania w tym kodzie?
Program wykonuje się normalnie, ustawia tryb pinu C0 na wyjście a potem wywołuje funkcję InitTimer0. Funkcja ta uruchamia Timer 0, a program wykonuje się dalej i wchodzi do nieskończonej pętli while(1). W tej pętli możemy wykonywać dowolne operacje jakie chcemy - obliczenia, obsługuję logiki programu, itp. a Timer 0 będzie już działać w tle.
Dopiero w momencie gdy Timer 0 się przepełni, to w sposób niewidoczny do nas wykonywanie głównej pętli programu zostaje przerwanie i zacznie wykonywać się funkcja myTimerInterrupt. To właśnie nazywa się przerwaniem. W tej funkcji nastąpi zmian stanu pinu od diody LED. Po wykonaniu tej funkcji program wróci do funkcji main i wznowi wykonywanie tego co robił wcześniej.
Filmik pokazuje wyżej przedstawiony kod w akcji:
Jak widać, wszystko działa.
Pełna paczka p18f2550_blink_tmr0_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Timer1 - 16 bitowy timer
Timer 1 działa w trybie 16 bitowym. Timer 1 może działać w roli licznika impulsów na pinie T13CKI (pin RC0). Timer 1 pozwala na podłączenie osobnego oscylatora na pinach T1OSO (RC0) oraz T1OSI (RC1). Timer 1 zlicza impulsy (z zegara lub z pinu) i w momencie przepełnienia się jego licznika wywołuje przerwanie.
Jeśli chcemy użyć Timera 1 w połączeniu z zewnętrznym oscylatorem kwarcowym to używamy tych pinów:

Z Timerem 1 powiązane są rejestry:
- T1CON - tutaj konfigurujemy działanie Timera 1 (włączamy go, ustawiamy prescaler)
- TMR1L - jest to niższy bajt licznika Timera 1
- TMR1H - jest to wyższy bajt licznika Timera 1
Zawartość rejestru T1CON pokazuje obrazek:

W rejestrze T1CON są następujące bity:
- TMR1ON - flaga pozwala na włączenie Timera 1
- T1RUN - flaga określająca, czy zegar Timera 1 pochodzi z oscylatora Timera 1 czy innego źródła
- TMR1CS - 'clock source', źródło zegara Timera 1 (czy zliczamy impulsy na pinie T13CKI czy z wew. zegara PICa). Tutaj nie ma możliwości wyboru tego czy naliczanie odbywa się na zboczu rosnącym czy opadającym tak jak w Timerze 0, dla Timera 1 zliczanie jest zawsze na zboczu rosnącym
- T1OSCEN - 'oscillator enable', pozwala włączyć lub wyłączyć oscylator skojarzony z Timerem 1 (piny T1OSO i T1OSI)
- T1SYNC - flaga określa czy synchronizacja dla zewnętrznego zegara Timera 1 jest włączona. Ma znaczenie tylko gdy TMR1CS = 1, w przeciwnym razie jest ignorowana.
- T1CKPS1, T1CKPS0 - prescaler Timera 1
W przeciwieństwie do Timera 0, tutaj mamy tylko 4 kombinacje (2 bity, a nie 3).
Wartość prescalera dla danej kombinacji bitów T1CKPS* przedstawia tabela:

Jak widać, Timer 1 jest podobny do Timera 0, nazwy rejestrów i ich działanie jest zorganizowane w sposób analogiczny, ale istnieją pewne różnice w funkcjonalności i ograniczenia (np. węższy wybór prescalera, brak możliwości wyboru zbocza przy ustawieniu TMR1CS), co sprawia że każdy timer nadaje się do nieco innych zastosowań.
Zapraszam do zapoznania się z przykładem migania diodą z pomocą Timera 1 poniżej:
Timer1 - przykładowy kod (blink led)
Analogicznie tak jak w przypadku Timera 0, z pomocą Timera 1 możemy wywoływać przerwanie i mrugać diodą LED.
Konfiguracja Timera 1 tutaj też pochodzi z Timer Calculatora i myślę, że nie wymaga tłumaczenia.
Pełny kod programu:
Code: c
Program został sprawdzony w praktyce, działa poprawnie i miga diodą LED.
Pełna paczka p18f2550_blink_tmr1_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Timer2 - 8 bitowy timer
Timer 2 działa podobnie jak Timery 1 i 0, z tą różnicą, że jest ośmiobitowy.
Nie oferuje więc rejestrów "TMR2H i TMR2L", po prostu takie rejestry nie istnieją - mamy za to rejestr PR2 i TMR2.
Tutaj jest nieco inaczej - przerwanie nie wywołuje się w momencie przepełnienia TMR2, lecz wywołuje się w momencie kiedy TMR2 jest równy PR2.
Timer 2 konfigurujemy w rejestrze T2CON:

W rejestrze T2CON są następujące bity:
- TMR2ON - pozwala włączyć Timer 2
- T2OUTPS3:T2OUTPS0 - czterobitowe słowo postscalera dla Timera 2
- T2CKPS1:T2CKPS0 - dwa bity określające prescaler Timera 2
Tabela wartości T2OUTPS* (dostępne ustawienia postscalera Timer 2):

Tabela wartości T2CKPS* (dostępne ustawienia prescalera Timer 2):

Widać, że działanie Timera 2 jest podobne do pozostałych Timerów. Szczegółowe informacje dostępne są w nocie katalogowej PICa.
Timer2 - przykładowy kod (blink led)
Tutaj też migamy diodą, ale z pomocą Timera 2.
Ten przykład jest nieco inny niż w przypadku Timera 0 i Timera 1, gdyż Timer 2 jest już tylko ośmiobitowy, odpowiedzialny za jego licznik rejestr nazywa się PR2.
Tutaj też ze względu na to, że timer ma tylko 8 bitów okres timera został wybrany na 0.25s a nie na 1s - po prostu nie dałoby się uruchamiać go co 1 sekundę, można to zrealizować jedynie programowo (zliczać 0.25s cztery razy).
Sam Timer Calculator od Mikro C poprawnie wykrywa ten przypadek:

Reszta tak jak w poprzednich przykładach, oto pełny kod:
Code: c
Program został sprawdzony w praktyce, działa poprawnie i miga diodą LED.
Pełna paczka p18f2550_blink_tmr2_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Timer3 - 16 bitowy timer
Timer 3 to ostatni z czterech Timerów oferowanych przez PIC18F2550.
Działa on analogicznie do timerów 0 i 1.
Operuje na rejestrach TMR3H i TMR3L.
Konfigurujemy go poprzez rejestr T3CON:

Do Timera 3 można też podłączyć zewnętrzny oscylator powiązany z Timerem 1, czyli ten na pinach T1OSO/T1OSI. Włącza się go z pomocą bitu TMR3CS.
Timer3 - przykładowy kod (blink led)
Przykład migania diodą z pomocą Timeru 3 jest analogiczny do Timerów 0 i 1, więc nie wymaga raczej ponownego tłumaczenia.
Pełny kod przykładu:
Code: c
Program został sprawdzony w praktyce, działa poprawnie i miga diodą LED.
Pełna paczka p18f2550_blink_tmr3_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Przerwania zewnętrzne - INT0 - przycisk
Teraz zaprezentuję działanie przerwania zewnętrznego INT0 w PIC18F2550.
Przerwanie to odbywa się w momencie gdy stan na pinie INT0 ulegnie zmianie - to od nas zależy, czy wybierzemy zmianę w postaci zbocza rosnącego (rising edge) czy opadającego (falling edge).
Pin INT0 dzieli swoją funkcję z RB0/AN12 i znajduje się tutaj:

W ramach tego przykładu zrealizujemy na nim przycisk.
Przyciśnięcie przycisku będzie skutkować wywołaniem przerwania, w którym zmienimy stan diody LED.
Tym razem wybrałem microswitch który ma tylko dwie nóżki (będzie go łatwiej umieścić na płytce stykowej bez stosowania dodatkowych zworek):


I umiejscowiony na płytce:


Co zrobimy w kodzie?
- ustawimy RB0/INT0 jako wejście (rejestr TRISB)
- wyłączymy funkcje analogowe dla AN12 (bo są na tym samym pinie; w rejestrze ADCON1)
- włączymy rezystory pull-up wbudowane dla portu B (w rejestrze INTCON2)
- włączymy przerwania od INT0 (rejestr INTCON)
- włączymy ogólnie przerwania
- wybierzemy, że chcemy przerwanie na wzrastającym zboczu
- w obsłudze przerwania zmienimy stan diody LED
Oto i gotowy kod:
Code: c
Teraz odpalimy całość i przetestujemy.
Filmik z działania (a raczej pokazujący niepoprawne działanie):
Jak widać, coś jest zdecydowanie nie tak. Nie zawsze na skutek naciśnięcia przycisku zmienia się poprawnie stan diody LED. A właściwie to zmienia się - ale kilka razy - na skutek drgania styków.
W ten sposób wraca do nas temat debouncingu.
Tym razem rozwiążemy go sprzętowo. Na pin INT0 dodamy kondensator 100nF:

I po umieszczeniu na płytce:


Filmik z działania po dodaniu sprzętowego debouncingu:
O wiele lepiej! Przycisk działa poprawnie a dodany kondensator 100nF w dostateczny sposób rozwiązuje problem drgania styków.
Pełna paczka p18f2550_blink_int0_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Przerwania zewnętrzne - INT1 - przycisk
Ten przykład jest analogiczny do poprzedniego. Ochotników zachęcam do próby samodzielnego napisania tego kodu i dopiero potem porównania swojej wersji z moim rozwiązaniem.
Przycisk tym razem podłączony będzie na pinie INT1 (RB1, AN10):

Reszta pozostaje bez zmian.
Ja niestety musiałem jeszcze zmienić przycisk, bo.... ten stary miał za krótkie piny.
Nowy przycisk:

Nowe podłączenie na płytce stykowej:


Zaktualizowany kod:
Code: c
I standardowo - filmik z działania:
Wszystko działa poprawnie (bez kondensatora 100nF do sprzętowego debouncingu znów byłyby problemy).
Pełna paczka p18f2550_blink_int1_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Timer0 - jako licznik impulsów
Teraz zaprezentuję w prosty sposób jak Timer 0 może posłużyć za licznik impulsów (tryb Counter).
W celu skonfigurowania Timer 0 jako licznik impulsów po prostu musimy przełączyć jego źródło z wewnętrznego zegara na zegar zewnętrzny, czyli ustawić bit T0CS z rejestru T0CON na wartość 1:

Taka konfiguracja Timera pozwala na zliczanie impulsów na pinie T0CKI, czyli w przypadku obudowy DIP28 i PIC18F2550 na pinie RA4:

Rejestr T0CON pozwala nam również zdecydować czy zwiększać Timer chcemy na zboczu opadającym czy rosnącym. Odpowiada za to bit T0SE:

Na bazie tej wiedzy przygotuję dwa kolejne przykłady.
Timer0 - jako licznik impulsów - prosty przykład
Na początek prosty przykład. Tutaj źródłem impulsów będzie przycisk, chociaż oczywiście tak nie musi być, z pomocą tego samego mechanizmu można nawet wykonać pomiar częstotliwości.
Podłączymy go do pinu T0CKI wraz z rezystorem pull-up i kondensatorem zapewniającym debouncing:

Tak jak na schemacie (w niebieskiej obwódce to co dodaliśmy):

Cały układ po podłączeniu:

Wartości TMR0H i TMR0L ustawimy na 0xFF, by licznik przepełniał się z pierwszym wciśnięciem microswitcha.
Code: c
Pełny kod przykładu:
Code: c
Przykład wgrałem na PICa i przetestowałem. Oczywiście działa w pełni poprawnie, każde wciśnięcie przycisku zmienia stan diody LED.
Filmik przedstawia działanie programu w praktyce:
Jak widać na filmiku, każde naciśnięcie przycisku zmienia stan diody LED.
Timer0 - jako licznik impulsów - przerwanie co kilka impulsów
Oczywiście nic nie stoi na przeszkodzie by generować przerwanie co kilka impulsów. Taka jest właśnie idea liczników.
Możemy łatwo zmodyfikować kod przykładu powyżej, by wykonywać funkcje myTimerInterrupt np. co trzy wciśnięcia przycisku.
W tym celu zaktualizujemy ustawienie TMR0L/TMR0H:
Code: c
Przypominam jedynie, że zliczamy "od tyłu", bo każdy cykl Timera zwiększa liczniki TMR0H/TMR0L a przerwanie wywołuje się w momencie jego przepełnienia. Dlatego 0xFD, a nie 0x03.
Filmik z działania programu:
Jak widać na filmiku, co trzecie wciśnięcie przycisku zmienia stan diody LED.
Pełna paczka p18f2550_counterBlink_intOsc8MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Timer0 - jako licznik impulsów - przykład z UART
Już mniej więcej zapoznaliśmy się z funkcją licznika w Timerze 0, więc teraz pora na coś bardziej zaawansowanego.
Będziemy zliczać impulsy i wysyłać ich bieżącą liczbę po UART do komputera.
A dokładniej - będziemy wysyłać zawartość rejestrów TMR0H i TMR0L do komputera, by zobrazować sobie co tak dokładnie dzieje się w PICu.
W tym celu skorzystamy z kodu komunikacji UART który umieściłem już w poprzednim temacie z cyklu:
https://www.elektroda.pl/rtvforum/viewtopic.php?p=18453286#18453286
Oczywiście, do całego układu podłączymy przejściówkę USB-UART (ja użyłem HW-597, ale może być dowolna) by móc jakoś odebrać dane i wyświetlić je np. w Realterm:


Do tego przyda nam się funkcja zamieniająca liczbę całkowitą (typ integer) na string (tablicę charów), ale taką funkcję można łatwo znaleźć w sieci, np tutaj:
https://www.geeksforgeeks.org/implement-itoa/
Funkcja tam dostępna jest napisana w stylu C++ i trzeba ją przenieść do C, ale nie wymaga to dużej ilości zmian. M. in. trzeba rozwiązać kwestie tego, że w C nie ma 'bool', 'true', 'false', itp. Reszta bez problemu skompiluje się nawet na 8-bitowym PICu.
Z pomocą informacji powyżej opracowałem pełny kod przykładu, zapraszam do zapoznania się:
Code: c
Program co chwila wysyła do komputera zawartość rejestrów TMR0H i TMR0L. W momencie wciśnięcia przycisku do rejestru TMR0L zostaje dodana wartość 1, co widać dobrze w konsoli:

Sama dioda LED wcale nie miga wtedy, gdyż funkcja przerwania myTimerInterrupt wywołana zostanie dopiero w momencie przepełnienia TMR0L i TMR0H, a przepełnienie tych dwóch bajtów wymagałoby wciśnięcia przycisku jakieś... 2^16 razy.
Pełna paczka p18f2550_counter_intOsc8MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Kilka timerów w jednym programie
Myślę, że jeszcze w ramach uzupełnienia przydałoby się pokazać jak korzystać z kilku timerów/przerwań w obrębie jednego programu.
Tak jak wcześniej wspomniałem, funkcja obsługi przerwania będzie tutaj dalej jedna - jedynie w tej funkcji będziemy sprawdzać które przerwanie/zdarzenie ją wywołało z pomocą flag xxxIF.
Czyli coś w stylu:
Code: c
Samo działanie czterech timerów zwizualizujemy oczywiście z pomocą czterech diod LED, podłączonych wraz z rezystorami na osobnej płytce:



Diody połączyłem tutaj katodami i podłączyłem do masy układu, ale na odwrót też oczywiście by zadziałało.
Dla timerów przyjąłem poszczególne okresy:
- Timer 0 - 2 sekundy
- Timer 1 - 1.5 sekundy
- Timer 2 - 0.25 sekundy
- Timer 3 - 1 sekunda
I użyłem kalkulatora od Mikro C by szybko policzyć ich preskalery/wartości TMR*.
Przyjąłem, że każdy timer w obsłudze swojego przerwania zmienia stan jednego pinu.
- Timer 0 - pin C0
- Timer 1 - pin C1
- Timer 2 - pin C2
- Timer 3 - pin C6
Zapraszam do zapoznania się z gotowym kodem:
Code: c
I standardowo; rezultat działania na filmiku:
Pełna paczka p18f2550_blink4x_tmrs_intOsc1MHz.zip z przykładem do pobrania (kod w C, skompilowany .hex, .bat do kompilacji w SDCC):
Przydatne materiały do zapoznania się
Mój temat powinien stanowić tylko mały wstęp do przygody z przerwaniami i timerami w PIC18F. Wszystko na ich temat jest opisane dokładniej w dokumentach od Microchipa, które dla Was tutaj zebrałem i umieściłem do pobrania:
Nota katalogowa naszego PICa - PIC18F2550 datasheet:
Tutorial o timerach od Microchipa, niestety dla innego kompilatora, ale też wartościowy - Timers: Timer0 Tutorial (Part 1):
Część druga tutoriala od Microchipa - Timers: Timer0 Tutorial (Part 2):
Fragment PIC Midrange Manual - Section 8. Interrupts:
Nota aplikacyjna Using the PORTB Interrupt on Change as an External Interrupt:
Powiązane projekty i tematy które będą jeszcze omówione
Wszystkie przykłady pokazane w tym temacie są bardzo proste, mogą stanowić dobry punkt wyjścia do tworzenia własnych kodów, lecz nie pokazują w pełni do czego możemy wykorzystać timery i przerwania w praktyce.
W związku z tym w przyszłości planuję osobne części tutoriala które zademonstrują jak używa się timerów i przerwań w praktycznych zastosowaniach. Części te obejmą m. in:
- obsługę wyświetlacza 7-segmentowego z kilkoma cyframi z pomocą timera i przerwania (sami posterujemy segmentami przez tranzystory i rezystory)
- prosty system pomiaru częstotliwości z pomocą sprzętowego licznika PIC18F (zrobimy własny miernik częstotliwości i zweryfikujemy jego działanie np. poprzez pomiar częstotliwości z sieci przez transformator)
Jak ktoś ma jeszcze jakieś pomysły na praktyczne projekty używające przerwań i timerów to zapraszam do dawania sugestii.
Podsumowanie
Tutaj przedstawiłem podstawy podstaw timerów i przerwań w PIC18F na najprostszych przykładach. Każdy przykład został przeze mnie przetestowany w praktyce i opisany, wszystkie kody są dostępny do pobrania. Bardziej zainteresowanych tematem i bardziej szczegółowymi informacjami zapraszam do lektury źródeł umieszczonych w poście.
Temat będzie jeszcze redagowany, w razie błędów/uwag proszę o sugestie na PW.
Następna część tutoriala niedługo - skupię się na niej na wyświetlaczach siedmiosegmentowych.
Cool? Ranking DIY