Ten post chciałbym poświęcić na opisanie przerwań zewnętrznych w tytułowym układzie, które zostaną zaprogramowane bezpośrednio na rejestrach mikrokontrolera.
Rejestry:
Możny wyróżnić następujące definicje przerwań EXTI definiowanych za pomocą NVIC dla opisywanego mikrokontrolera:
- EXTI0_IRQn
- EXTI1_IRQn
- EXTI2_IRQn
- EXTI3_IRQn
- EXTI4_IRQn
- EXTI9_5_IRQn
- EXTI15_10_IRQn
Definicje dla pozostałych przerwań można znaleźć w pliku stm32f4xx.h.
Co do rejestrów to są one właściwie takie same jak dla mikrokontrolera STM32F1, które opisałem w jednym z wcześniejszych postów.
Programowanie:
W pierwszej kolejności należy włączyć odpowiednie zegary:
Uruchamiane są zegary dla GPIOA, GPIOD oraz dla SYSCFG:
- RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
- RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
- __DSB();
Kolejnym elementem jest deklaracja pinów dla przycisku oraz wyprowadzeń:
- //PA0 jako iwejscie dla przycisku
- GPIOA->MODER |= 0x00;
- GPIOA->OSPEEDR |= GPIO_Speed_50MHz;
- GPIOA->OTYPER |= GPIO_OType_PP;
- GPIOA->PUPDR |= GPIO_PuPd_NOPULL;
- //PD12 - PD15 jako wyjscia
- GPIOD->MODER |= (GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0);
- GPIOD->OSPEEDR |= GPIO_Speed_50MHz;
- GPIOD->OTYPER |= GPIO_OType_PP;
- GPIOD->PUPDR |= GPIO_PuPd_NOPULL;
- SYSCFG->EXTICR[0] = SYSCFG_EXTICR1_EXTI0_PA; //Podlaczenie pinu PA0 do zewnętrznego przerwania z EXTI_Line0
Następnym krokiem jest definicja przerwań dla danej linii:
- //Konfiguracja EXTI oraz NVIC
- EXTI->FTSR = EXTI_FTSR_TR0;
- EXTI->IMR = EXTI_IMR_MR0;
- __enable_irq();
- NVIC_SetPriority(EXTI0_IRQn,0);
- NVIC_ClearPendingIRQ(EXTI0_IRQn);
- NVIC_EnableIRQ(EXTI0_IRQn);
Ustawiana jest sposób w jaki na linii będzie to przerwanie generowane, w tym przypadku zboczem opadającym. W rejestrze IMR wystawiane jest przerwanie na określoną nogę układu. Następnie przerwania zostają włączone, ta komenda nie jest właściwie konieczna, ponieważ zostają one włączone automatycznie.
Kolejna część dotyczy konfiguracji NVIC, ustawiana jest tam odpowiednia linia wraz z wartością priorytetu dla danego przerwania. Można wprowadzać wartości od 0 do 15, przy czym 0 ma najwyższy priorytet.
Teraz czas na obsługę przerwania czyli tzw. handler:
- void EXTI0_IRQHandler(void)
- {
- if(EXTI->PR & EXTI_PR_PR0)
- {
- delay_simple(10000);
- EXTI->PR = EXTI_PR_PR0;
- switch(zmienna)
- {
- case 0:
- {
- GPIOD->BSRRL = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 1:
- {
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRL = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 2:
- {
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRL = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 3:
- {
- zmienna = 0;
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRL = GPIO_Pin_15; //((uint16_t)0x8000)
- break;
- }
- }
- }
- }
Po wywołaniu przerwania i wejściu do procedury obsługi, następuje sprawdzenie czy zostało ono wywołane z danej nogi mikrokontrolera, ta część właściwie ma więcej pożytku dla przerwań zawierających wspólne handlery tzn. dla pinów od 5 do 9 oraz od 10 do 15. Natomiast nie zaszkodzi sprawdzić to również w tym przypadku.
Gdy już nastąpi sprawdzenie następuje wejście do funkcji i wyzerowanie flagi ustawionej w rejestrze EXTI_PR, który informuje o wystąpieniu przerwania na konkretnym pinie. Jest jeszcze coś takiego jak flaga ustawiona przez układ NVIC (w tym przypadku kasowanie odbywa się wartością 0). Występuje ona gdy przerwanie zostało zgłoszone ale czeka na swoją kolej ponieważ aktualnie układ zajmuje się ważniejszym przerwaniem. Tej flagi nie trzeba kasować, jest ona zerowana automatycznie.
Jak już wspomniałem w poprzednich postach flaga w rejestrze PR jest kasowana zaraz po wejściu do przerwania tak aby wystąpienie innego nie zostało pominięte.
Jeszcze informacja co użytej wcześniej instrukcji __enable_irq(). Występuje także instrukcja __disable_irq();. Jak można się łatwo domyśleć jedna włącza druga natomiast wyłącza przerwania. Z ich użyciem spotkałem się w procedurze obsługi przerwania co wyglądało następująco:
- void EXTI0_IRQHandler(void)
- {
- if(EXTI->PR & EXTI_PR_PR0)
- {
- __disable_irq();
- EXTI->PR = EXTI_PR_PR0;
- //Program.....
- __enable_irq();
- }
- }
Po wejściu do obsługi przerwania są wyłączane, program się wykonuje, po czym zostają one ponownie włączone. Z takiego rozwiązania można skorzystać wtedy gdy zależy nam na tym aby to przerwanie nie było przerwane przez inne przerwanie, oczywiście tylko takie które można wyłączyć. Natomiast jeśli chcemy nie przeoczyć żadnego wystąpienia, to należy nie wykorzystywać tego typu instrukcji.
Poniżej wklejam jeszcze całą funkcje:
- volatile uint8_t zmienna = 0;
- int main()
- {
- RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
- RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
- __DSB();
- //PA0 jako input dla przycisku
- GPIOA->MODER |= 0x00;
- GPIOA->OSPEEDR |= GPIO_Speed_50MHz;
- GPIOA->OTYPER |= GPIO_OType_PP;
- GPIOA->PUPDR |= GPIO_PuPd_NOPULL;
- //PD12 - PD15 jako wyjscia
- GPIOD->MODER |= (GPIO_MODER_MODER12_0 | GPIO_MODER_MODER13_0 | GPIO_MODER_MODER14_0 | GPIO_MODER_MODER15_0);
- GPIOD->OSPEEDR |= GPIO_Speed_50MHz;
- GPIOD->OTYPER |= GPIO_OType_PP;
- GPIOD->PUPDR |= GPIO_PuPd_NOPULL;
- SYSCFG->EXTICR[0] = SYSCFG_EXTICR1_EXTI0_PA;
- //Konfiguracja EXTI oraz NVIC
- EXTI->FTSR = EXTI_FTSR_TR0;
- EXTI->IMR = EXTI_IMR_MR0;
- //__enable_irq();
- NVIC_SetPriority(EXTI0_IRQn,0);
- NVIC_ClearPendingIRQ(EXTI0_IRQn);
- NVIC_EnableIRQ(EXTI0_IRQn);
- while(1)
- {}
- }
- void EXTI0_IRQHandler(void)
- {
- if(EXTI->PR & EXTI_PR_PR0)
- {
- EXTI->PR = EXTI_PR_PR0;
- switch(zmienna)
- {
- case 0:
- {
- GPIOD->BSRRL = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 1:
- {
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRL = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 2:
- {
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRL = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRH = GPIO_Pin_15; //((uint16_t)0x8000)
- zmienna++;
- break;
- }
- case 3:
- {
- zmienna = 0;
- GPIOD->BSRRH = GPIO_Pin_12; //((uint16_t)0x1000)
- GPIOD->BSRRH = GPIO_Pin_13; //((uint16_t)0x2000)
- GPIOD->BSRRH = GPIO_Pin_14; //((uint16_t)0x4000)
- GPIOD->BSRRL = GPIO_Pin_15; //((uint16_t)0x8000)
- break;
- }
- }
- }
- }