06 ARM SAM4 – Obsługa przycisku – przerwania i filtry

Kod do obsługi przycisków przedstawiony wcześniej nie jest efektywny i ma zastosowanie tylko w prostych programach. Wynika to z tego, że sprawdzamy czy przycisk jest naciśnięty w głównej pętli programu while(1). Niesie to za sobą utratę mocy obliczeniowej na rutynowe sprawdzanie stanu wejść oraz jeśli pętla główna jest bardzo długa to możemy nie wykryć naciśnięcia przycisku.
Aby rozwiązać powyższe problemy należy zastosować narzędzie nazywane przerwaniami. Jest to krótki fragment programu wykonywany w momencie wykrycia przez procesor zmiany stanu dowolnego wejścia lub wbudowanych w procesor urządzeń. Dzięki temu w głównej pętli programu nie ma żadnych zbędnych instrukcji i nie tracimy mocy procesora, natomiast procesor równolegle cały czas sprawdza co się dzieje z wejściami jeśli wykryje zmianę to wykona wskazany przez nas fragment programu. Przykład zastosowania poniżej. Zaczniemy od uruchomienia przerwań odpowiedzialnych za port A.

NVIC_EnableIRQ(PIOA_IRQn)
PIOA
->PIO_IER = Button

Funkcja NVIC_Enabled odpowiada za załączenie wykrywania przerwań na podanym urządzeniu, w tym wypadku jest to portA ( PIA_IRQn), dla portu B było by PIOB_IRQn, a dla C PIOC_IRQn.
Następnie musimy ustawić rejestr przerwań PIO_IER dla portu A i interesującego nas wejścia. W każdym przypadku wykorzystania przerwań należy w kodzie programu umieścić kod o poniższej składni:

void PIOA_Handler(void)
{
}

Pomiędzy nawiasami umieszczamy kod wykonujący interesującą nas funkcję. Jeśli przerwanie ma dotyczyć innych portów to analogicznie zmieniamy nazwę funkcji na PIOB_Hanlder lub PIOC_Handler. Pomiędzy nawiasami musimy umieścić kod odpowiedzialny za weryfikacje naciśnięcia przycisku. W poprzednim przykładzie zapalaliśmy diodę jeśli przycisk był naciśnięty i do odczytania stanu przycisku wykorzystywaliśmy rejestr PIO_PDSR. Teraz zamiast zapalać diodę, będziemy za każdym naciśnięciem przycisku zmieniać jej stan.

void PIOA_Handler(void)
{
if (!(PIOA->PIO_PDSR & Button))
{
toggle_led();
}
}

Przy zastosowaniu powyższej funkcji pojawią się problemy, za każdym na ciśnięciem przycisku będzie on się losowo zapalał i gasił. Wynika to z tego, że przy naciśnięciu przycisku, nie generuje się pojedyńcza zmiana stanu tylko kilka, wynika to z mechanicznego przeskakiwania styków. Układy ARM mają możliwość filtrowania takich przeskoków np. poprzez dłuższe sprawdzania stanu styku niż jedne cykl zegara. Należy do tego wykorzystać rejestr PIO_ISR, który zwraca nam informacje, czy wejście jest załączone czy nie po ustablizowaniu się sygnału wejściowego. Poniżej przykład użycia:

void PIOA_Handler(void)
{
if (PIOA->PIO_ISR & Button)
toggle_led();
}
}

Jednak aby móc korzystać z rejestru PIO_ISR musimy pierw go ustawić za pomocą poniższych rejestrów:

PMC->PMC_PCER0 = 1<<ID_PIOA;
PIOA->PIO_ESR = Button;
PIOA->PIO_REHLSR = Button;
PIOA->PIO_AIMER = Button;

W pierwszej lini załączamy zasilania dla peryferiów portu A.

Poniżej działający kod aplikacji:


#include "sam.h"
#define LEDA (1<<10)
#define Button (1<<5)
#define LEDA_on PIOC->PIO_CODR = LEDA
#define LEDA_off PIOC->PIO_SODR = LEDA
static uint8_t ul_toggle_led = 0;
void toggle_led(void)
{
if (ul_toggle_led == 0)
{
LEDA_on;
ul_toggle_led = 1;
}
else
{
LEDA_off;
ul_toggle_led = 0;
}
}

void PIOA_Handler(void)
{
if (PIOA->PIO_ISR & Button)
{ 
toggle_led();
 }
}
int main(void)
{ 
/* Initialize the SAM system */ 
SystemInit();
WDT->WDT_MR=WDT_MR_WDDIS; // Wyłączenie funkcji watchdog 
PIOC->PIO_OER = LEDA; // Ustawia rejestr C jako wyjście 
PIOC->PIO_PUDR =LEDA; // Wyłącza rezystory podciągające 
LEDA_off; 
PIOA->PIO_PUER = Button; 
PMC->PMC_PCER0 = 1<<ID_PIOA; 
PIOA->PIO_ESR = Button; 
PIOA->PIO_REHLSR = Button; 
PIOA->PIO_AIMER = Button; 
NVIC_EnableIRQ(PIOA_IRQn); 
PIOA->PIO_IER = Button;   
 while (1) 
{ }
}