Tym razem odpalimy sobie kawałek timera i pomigamy diodą bez problematycznych delayów. Przykłady omówię bazując na PIC18F4550 z kwarcem 4MHz, bo takie akurat elementy mam na swoim ZEPIC'u.
Przede wszystkim należy skonfigurować bity konfiguracyjne (odpowiedniki fuse bitów w AVR) i wkleić tę konfigurację do pliku programu. Potem już postępujemy typowo, czyli inkludujemy to, co nam jest potrzebne i piszemy funkcję główną. Poniżej program bez bitów konfiguracyjnych, by skupić się na timerze i przerwaniu:
Kod: Zaznacz cały
#ifndef _XTAL_FREQ
#define _XTAL_FREQ 4000000UL
#endif
#include <xc.h>
#include <stdbool.h>
int main(void)
{
/* GPIO config */
TRISD &= ~_TRISD_RD0_MASK; /* RD0 as output */
PORTD &= ~_PORTD_RD0_MASK; /* LED disabled */
/* TIMER0 config */
T0CON = _T0CON_TMR0ON_MASK;
/* TIMER0 overflow interrupt enable */
INTCON |= _INTCON_PEIE_MASK | _INTCON_T0IE_MASK;
/* Enable interrupts */
ei();
while(true)
{
}
}
void interrupt IRQ_h(void)
{
if((INTCON & _INTCON_T0IE_MASK) && (INTCON & _INTCON_T0IF_MASK) )
{
/* Reset flag */
INTCON &= ~_INTCON_T0IF_MASK;
/* Led toggle */
LATD ^= _LATD_LATD0_MASK;
}
}
Dwie pierwsze instrukcje ustalają wyjściowy kierunek oraz stan niski na pinie D0. Następnie konfigurowany jest timer 0. Taktowany jest z wewnętrznego źródła, tryb pracy 16 bitowy i preskaler 1:2. Polecam zerknąć do dokumentacji, by zapoznać się z tym rejestrem. Kolejnym krokiem jest odblokowanie możliwości generowania przerwania przez timer0, włączenie przerwań dla urządzeń peryferyjnych oraz globalne włączenie przerwań. Tutaj muszę się odrobinkę szerzej wypowiedzieć.
PIC18F dysponuje dwoma priorytetami przerwań. Domyślnie jednak włączony jest tylko ten o wyższym priorytecie. Gdy priorytety są wyłączone, wszystkie źródła przerwań powodują skok pod adres 0x0008, czyli wektor obsługi przerwań o wysokim priorytecie. W związku z mozliwością pracy z priorytetami i bez, jeden z rejestrów posiada dwa bity o podwójnym znaczeniu. Bez obaw, one są bardzo dobrze opisane w dokumentacji, a ja jedynie w skrócie opiszę, o co chodzi:
rejestr: INTCON
Gdy priorytety są wyłączone:
bit 7: GIE/GIEH – Włącza lub wyłącza przerwania, które nie są określone jako peryferyjne
bit 6: PEIE/GIEL – Włącza lub wyłącza przerwania określone jako peryferyjne.
Gdy priorytety są włączone:
bit 7: GIE/GIEH – Włącza lub wyłącza przerwania o wysokim priorytecie
bit 6: PEIE/GIEL – Włącza lub wyłącza przerwania o niskim priorytecie.
Nie ma podziału na przerwania peryferyjne i zwykłe.
Tutaj podpowiem, że dla PIC18F4550 przerwaniami peryferyjnymi są następujące ich źródła:
Przepełnienie TIMER0, zmiana stanu na pinach PORTB oraz przerwania zewnętrzne INT0, INT1 i INT2.
W naszym przypadku priorytety nie są włączone, więc wystarcza włączenie przerwań poleceniem ei(); To polecenie nie robi nic innego, jak włącza bit GIE/GIEH w rejestrze INTCON. No dobrze, wróćmy do programiku. Pin portu D skonfigurowany, timer skonfigurowany, przerwanie od przepełnienia tego timera włączone, no i włączone są przerwania nieperyferyjne. Pętla nieskończona jest pusta, bo miganie diodą wciśnięte jest bezpośrednio w podprogram obsługi przerwań.
Nazwa podprogramu obsługi przerwania może być niemal dowolna, ale należy dodać specyfikator interrupt, by kompilator wiedział, że własnie ta funkcja (bo tak ona wygląda) jest obsługą przerwania. Brak określonego priorytetu oznacza, że podprogram dotyczy priorytetu wysokiego.
Wewnątrz przerwania sprawdzamy, czy jest włączone przerwanie od przepełnienia TIMER0 oraz flagę tego przerwania. Jeśli oba bity są ustawione, kasujemy flagę przerwania i migamy diodą. Dlaczego musimy sprawdzać status przerwania oraz flagę? W niektórych programach wykorzystujemy TIMER0 bez włączonego przerwania, a przerwania generują inne źródła, np. zmiana na któryms pinie PORT B. Gdyby program wpadł w obsługę przerwania i nie sprawdzał flag oraz faktu włączenia konkretnych źródeł, przerwania wykonywałyby się równiez wtedy, gdy tego nie chcemy. Tak więc mamy pełną dowolność działania kosztem jednej linijki kodu więcej.
Gdybyśmy chcieli skorzystać z priorytetów, należy je przede wszystkim włączyć:
Kod: Zaznacz cały
INTCON |= _INTCON_IPEN_MASK;
Po czym w rejestrach IPR1 lub IPR2 ustawić odpowiedni priorytet dla interesującego nas źródła przerwania. Wyjątkiem jest TIMER0, którego priorytet ustawiany jest w rejestrze INTCON2. Gdy już to jest gotowe, wystarczy napisać dwa podprogramy obsługi przerwań:
Kod: Zaznacz cały
void interrupt high_priority IRQ_h(void)
{
}
void interrupt low_priority IRQ_l(void)
{
}
No i gotowe!
Pozdrawiam