[NOWY FRAMEWORK] Microchip Harmony dla PIC32, część 2
Otwieramy: Harmony Framework Configuration->Drivers i zaznaczamy Use Timer Driver (rysunek 12). W tym momencie musimy się zdecydować czu będzie to driver statyczny, czy dynamiczny. Ponieważ aplikacja jest bardzo prosta, wybieramy opcję STATIC. Kolejne ustawienie dotyczy przerwań. Po zaznaczeniu Interrupt Mode MHC wygeneruje pliki z funkcją do obsługi przerwania timera, oraz skonfiguruje przerwania. Domyślnie funkcja obsługi przerwania tylko zeruje flagę przerwania. Użytkownik musi sam dodać swoje procedury. Konfiguracja samego drivera Timera polega na:
- wybraniu ID Timera. Ponieważ chcemy użyć 32 bitowego licznika złożonego z liczników Timer4/5, to wybieramy TMR_ID_4,
- określeniu priorytetu i subpriorytetu przerwania,
- wybraniu źródła zliczanych impulsów (w naszym przypadku PBCLK – zegar taktujący peryferiami),
- wartość współczynnika podziału preskalera,
- konfiguracja licznika 16/32 bity – w naszym przypadku 32 bity,
- ilość impulsów wpisywanych do rejestru PR. Po odliczeniu tych impulsów licznik jest zerowany, zgłasza przerwanie i odliczanie rozpoczyna się na nowo.
Samo konfigurowanie jest proste i intuicyjne. Eliminuje konieczność żmudnego wczytywania się w zawartość rejestrów konfiguracyjnych (rysunek 13). Po zapisaniu zamian i wygenerowaniu plików w MPLAB Harmony Configurator można używać funkcji drivera DRV_TMR0.
Rys. 13. Konfiguracja drivera Timera
Wróćmy, do naszego zadania, czyli do migania diodą LED. Mamy teraz do dyspozycji dwa mechanizmy: maszynę stanów i przerwania. Zacznijmy od prostszej w realizacji czyli od przerwań. Prostszej, bo konfigurator skonfigurował nam licznik i system przerwań.
Procedura obsługi przerwania jest umieszczana w pliku system_interrupt.c (katalog Source Files/app/system_config/default), a funkcje drivera w pliku drv_tmr_static.c (katalog Source Files/app/system_config/default/frmework/driver/tmr/src). Na listingu 9 pokazano procedurę przerwania wygenerowaną przez MHC.
List. 9. Procedura obsługi przerwania od Timer4
#include#include #include "app.h" #include "system_definitions.h" // ***************************************************************************** // ***************************************************************************** // Section: System Interrupt Vector Functions // ***************************************************************************** // ***************************************************************************** void __ISR(_TIMER_5_VECTOR, ipl1AUTO) _IntHandlerDrvTmrInstance0(void) { PLIB_INT_SourceFlagClear(INT_ID_0,INT_SOURCE_TIMER_5); }
Załóżmy, że dioda ma być zapalona przez 1 sekundę i zgaszona przez jedną sekundę. Przerwanie będzie zgłaszane dla uproszczenia też co jedną sekundę. Częstotliwość zegara PBCLK została ustawiona na 80MHz, a preskaler na 256. Na wejściu licznika będzie przebieg o częstotliwości 80MHz/256=312,5kHz. Do rejestru PR4 (Time Period w konfiguracji drivera w MHC) trzeba wpisać zatem wartość 312500. Ponieważ licznik jest 32-bitowy, to nie będzie z tym problemu. Procedurę obsługi przerwania uzupełniamy o funkcję BSP_LEDToggle(BSP_LED_2) zmieniającą przy każdym wywołaniu na przeciwny stan na linii sterującej diodę LED2 modułu PIC32 USB Starter Kit II – listing 10.
List. 10. Zmodyfikowana procedura obsługi przerwania
void __ISR(_TIMER_5_VECTOR, ipl1AUTO) _IntHandlerDrvTmrInstance0(void) { BSP_LEDToggle(BSP_LED_2);//zmiana stanu diody LED PLIB_INT_SourceFlagClear(INT_ID_0,INT_SOURCE_TIMER_5);//zerowanie flagi przerwania }
Żeby całość działała poprawnie trzeba jeszcze włączyć zliczanie i ewentualnie wyzerować rejestry licznika. Można to zrobić w procedurze APP_Tasks w aktywnym stanie APP_STATE_INIT – listing 11.
List. 11. Fragment procedury APP_Tasks uzupełnionej o inicjalizację zliczania układu drivera licznika
void APP_Tasks (void ) { /* Check the application's current state. */ switch (appData.state ) { /* Application's initial state. */ case APP_STATE_INIT: { SYS_PORTS_PinDirectionSelect(PORTS_ID_0, SYS_PORTS_DIRECTION_OUTPUT, PORT_CHANNEL_D ,BSP_LED_1); SYS_PORTS_PinDirectionSelect(PORTS_ID_0, SYS_PORTS_DIRECTION_OUTPUT, PORT_CHANNEL_D ,BSP_LED_2); DRV_TMR0_Start();//uruchom licznik DRV_TMR0_CounterClear();//zeruj rejestr licznika appData.state=APP_LED_ON; break; }
Statyczna implementacja drivera timera dostarcza tylko kilku podstawowych funkcji:
- DRV_TMR0_Start – rozpoczęcie zliczania,
- DRV_TMR0_Stop – zatrzymanie zliczania,
- DRV_TMR0_CounterClear – wyzerowanie licznika,
- DRV_TMR0_CounterValueSet – zapisanie licznika wartością początkową,
- DRV_TMR0_CounterValueGet – odczytanie wartości licznika,
- DRV_TMR0_Initialize – inicjalizacja timera.
Wykorzystując te funkcje można implementować nie blokujące opóźnienia w maszynie stanów. Ja do celów testowych zaimplementowałem maszynę stanów z dwoma stanami: APP_LED_ON i APP_LED_OFF. Wykorzystałem tu też mechanizm przerwania do zmiany na wartość przeciwną najmłodszego bitu zmiennej (toggle bit). Wartość tego bitu jest testowana w maszynie stanów i na jej podstawie jest zapalana i gaszona cyklicznie dioda LED.
List. 12. Funkcja maszyny stanów do migania diodami LED
void APP_Tasks (void ) { switch (appData.state ) { /* stan inicjalizacji aplikacji */ case APP_STATE_INIT: { SYS_PORTS_PinDirectionSelect(PORTS_ID_0, SYS_PORTS_DIRECTION_OUTPUT, PORT_CHANNEL_D ,BSP_LED_1); SYS_PORTS_PinDirectionSelect(PORTS_ID_0, SYS_PORTS_DIRECTION_OUTPUT, PORT_CHANNEL_D ,BSP_LED_2); DRV_TMR0_Start();//start zliczania impulsów DRV_TMR0_CounterClear(); appData.state=APP_LED_ON;//zaczynamy do zapalenia diody break; } /*testowanie stanu zapalenia diod 1 i 2*/ case APP_LED_ON : { led=led&1;//czy zapalić diodę LED if(led==1) {SYS_PORTS_PinSet(PORTS_ID_0,PORT_CHANNEL_D ,BSP_LED_1);//zapalenie diod 1i 2 SYS_PORTS_PinSet(PORTS_ID_0,PORT_CHANNEL_D ,BSP_LED_2); appData.state=APP_LED_OFF;}//po zapaleniu sprawdzaj czy zgasić } /*testowanie stanu zgaszenia diod 1 i 2*/ case APP_LED_OFF : { led=led&1; if(led==0) {SYS_PORTS_PinClear(PORTS_ID_0,PORT_CHANNEL_D ,BSP_LED_1); SYS_PORTS_PinClear(PORTS_ID_0,PORT_CHANNEL_D ,BSP_LED_2); appData.state=APP_LED_ON;} } case APP_NOP: { ; } /* The default state should never be executed. */ default: { /* TODO: Handle error in application's state machine. */ break; } } }
Po zainicjowaniu systemu maszyna stanów przyjmuje stan APP_LED_ON. Kiedy ten stan jest aktywny, to sprawdzana jest wartość najmłodszego bitu zmiennej led. Jeżeli ten bit jest jedynką, to maszyna stanów wystawia jedynki na liniach sterujących diodami LED_1 i LED_2, a potem zmienia stan na APP_LED_OFF. W stanie APP_LED_OFF jest sprawdzane czy najmłodszy bit zmiennej led jest wyzerowany. Po wyzerowaniu są wystawiane stany niskie na liniach sterujących diodami LED_1 i LED_2 i zmieniony stan maszyny na APP_LED_ON. W ten sposób w takt zmiany wartości zmiennej led wykonywanej co 1 sekundę w obsłudze przerwania od licznika Timer4/5 są zapalane i gaszone diody LED_1 i LED_2.