06 lipca 2018

Przerwania zewnętrzne

Skoro odczyt stanu przycisku opanowany, czas uruchomić coś bardzo blisko powiązanego - czyli przerwania do linii GPIO. Układy STM32 mają dość specyficznie rozwiązane przyjmowanie przerwań. Teoretycznie można obsługiwać przerwania od każdej linii, ale nie od wszystkich jednocześnie. Pin o danym numerze może być źródłem przerwania tylko od jednego portu na raz. Czyli jeśli wejście PA0 (port A, pin 0), ustawimy jako przerwanie, inne porty nie będą mogły obsługiwać przerwań na ich pinie 0. Tak to sobie inżynierowie projektujący układ wymyślili.
W przypadku płytki ewaluacyjnej nie jest to problem - i tak tylko jeden przycisk jest dostępny. Ale projektując nieco bardziej skomplikowany układ trzeba takie ograniczenia brać pod uwagę.
Większość programu będzie taka sama, albo chociaż bardzo podobna do poprzeniego, opiszę więc tylko to co nowe.
Na początek warto wybrać port, którego pin 0 będzimy używać. Do konfiguracji używany jest rejestr (rejestry) SYSCFG->EXTICRx



To właśnie w tym rejestrze wybieramy, który port będzie przypisany do przerwania na danym wejściu - dlatego wbierając jeden z portów tracimy możliwość użycia określonego pinu na pozostałych.
Wybieram PA0, czyli piszę:

SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;

Teraz wypada skonfigurować przerwania zewnętrzne. Moduł EXTI jest wyposażony w 6 rejestrów. Rejestry RTSR oraz FTSR pozwalają ustawić wyzwalanie przerwania odpowiednio po wykryciu zbocza narastającego i opadającego.


Naciśnięcie przycisku zmienia stan niski na wysoki, mamy więc zbocze narastające:

EXTI->RTSR |= EXTI_RTSR_TR0;

Teraz trzeba włączyć generowanie przerwania dla pinu 0. Odpowiada za to rejestr IMR:


Kod wygląda tak:

EXTI->IMR |= EXTI_IMR_MR0;

Następnie uruchomiamy samo przerwanie. Kontroler przerwań w przypadku Cortex-M4 nazywa się NVIC i został zaprojektowany przez firmę ARM (więc jest taki sam dla wszystkich mikrokontrolerów tej rodziny). Oznacza to niestety, że dokumentacji musimy poszukać na stronach ARM, a nie ST.
Na szczęście w naszym przypadku chcemy tylko zapalić jeden bit w rejestrze ISER. Uruchamiamy przerwanie kodem:

NVIC->ISER[EXTI0_IRQn / 32] |= 1u << (EXTI0_IRQn % 32);

Na koniec trzeba jeszcze przygotować procedurę obsługi przerwania. Sam wektor przerwań jest zdefiniowany w pliku startup_stm32f429xx.s.


/******************************************************************************
*
* The minimal vector table for a Cortex M3. Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
* 
*******************************************************************************/
   .section  .isr_vector,"a",%progbits
  .type  g_pfnVectors, %object
  .size  g_pfnVectors, .-g_pfnVectors
   
g_pfnVectors:
  .word  _estack
  .word  Reset_Handler

  .word  NMI_Handler
  .word  HardFault_Handler
  .word  MemManage_Handler
  .word  BusFault_Handler
  .word  UsageFault_Handler
  .word  0
  .word  0
  .word  0
  .word  0
  .word  SVC_Handler
  .word  DebugMon_Handler
  .word  0
  .word  PendSV_Handler
  .word  SysTick_Handler
  
  /* External Interrupts */
  .word     WWDG_IRQHandler                   /* Window WatchDog              */                                        
  .word     PVD_IRQHandler                    /* PVD through EXTI Line detection */                        
  .word     TAMP_STAMP_IRQHandler             /* Tamper and TimeStamps through the EXTI line */            
  .word     RTC_WKUP_IRQHandler               /* RTC Wakeup through the EXTI line */                      
  .word     FLASH_IRQHandler                  /* FLASH                        */                                          
  .word     RCC_IRQHandler                    /* RCC                          */                                            
  .word     EXTI0_IRQHandler                  /* EXTI Line0                   */                        
  .word     EXTI1_IRQHandler                  /* EXTI Line1                   */                          
  .word     EXTI2_IRQHandler                  /* EXTI Line2                   */ 

Funkcja EXTI0_IRQHandler odpowiada naszej procedurze obsługi przerwania - możemy oczywiście zmienić jej nazwę, jednak żeby nie komplikować programu zostawimy domyślną.
Procedurę napisałem żeby przetestować eliminację drgań - po każdym przyciśnięciu zmieniam stan świecenia diod. Jest to prosty test czy przerwania pojawiają się wielokrotnie po naciśnięciu przycisku.
Jeszcze jedna uwaga - po odebraniu przerwania należy wyzerować flagę w rejestrze EXTI->PR. Dzięki temu możliwe będzie obsłużenie kolejnego naciśnięcia.
Cały program wygląda następująco:


#include <stdint.h>
#include <stdbool.h>
#include "stm32f429xx.h"

void EXTI0_IRQHandler(void)
{
        static bool set = false;

        if (set) {
                GPIOG->BSRR = GPIO_BSRR_BS13;
                GPIOG->BSRR = GPIO_BSRR_BR14;
                set = false;
        } else {
                GPIOG->BSRR = GPIO_BSRR_BS14;
                GPIOG->BSRR = GPIO_BSRR_BR13;
                set = true;
        }

        EXTI->PR = EXTI_PR_PR0;
}

int main(int argc, char *argv[])
{
        RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOGEN;

        GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0;
        GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_1;

        SYSCFG->EXTICR[0] &= ~SYSCFG_EXTICR1_EXTI0;

        EXTI->RTSR |= EXTI_RTSR_TR0;
        EXTI->IMR |= EXTI_IMR_MR0;

        NVIC->ISER[EXTI0_IRQn / 32] |= 1u << (EXTI0_IRQn % 32);

        while (1) {
        }

        return 0;
}


1 komentarz:

  1. hej Piotr, super artykuł. Czy możesz mi wysłać swoją wizytówkę na maila?

    OdpowiedzUsuń