wtorek, 17 listopada 2015

[5] STM32 M3 - Nucleo - F103RB - Przerwania NVIC i EXTI

W tym poście chciałbym opisać sposób obsługi przerwań NVIC i EXTI przez mikrokontrolery STM32.

Wstęp


Przerwanie jest to pewien sygnał, który zostaje zgłoszony do mikrokontrolera w momencie wyłapania przerwania. Na czas obsługi przerwania układ zawiesza wykonywanie pozostałych operacji, po czym przechodzi do wykonania operacji w przerwaniu. Po jej wykonaniu mikrokontroler wraca do normalnej pracy. W momencie wywołania sygnału układ zapisuje swój aktualny stan, po to by po powrocie do normalnej pracy zacząć działanie od przerwanej czynności.


Źródłami przerwania mogą być takie układu jak np. SPI, DMA, UART, TIMER, lub pewne układy zewnętrzne jakie zostały podłączone do mikrokontrolera.

Główną zaletą przerwań jest to, że pozwalają na wykonywanie określonych operacji w momencie kiedy jest to konieczne, bądź gdy chce tego użytkownik. Nie trzeba czekać aż dany stan na linii zostanie sprawdzony w czasie normalnej pracy.

NVIC


NVIC (ang. Nested Vectored Interrupt Controller) jest to tzw. kontroler przerwań sprzętowych. Mikrokontroler zastosowany w opisywanej wersji Nucleo pozwala na obsługę 43 maskowalnych kanałów przerwań. Możliwe jest ustawienie 16-stu wartości priorytetów przerwań. Najważniejszym jest wartość 0. Czyli jeśli zostaną zgłoszone dwa przerwania, gdzie każde będzie miało inną wartość, wtedy zostanie wykonane to z niższym (bliższym zero) priorytetem. Oznacza to, że nawet jeśli jest już wykonywane przerwanie o wyższym numerze, to zostanie przerwane jego działanie na rzecz przerwania o numerze bliższym 0.

Jeśli ważność przerwań jest taka sama, to kolejność zostaje określona przez podpriorytet.

Występują też przerwania bardzo ważne, których priorytetu nie da się zmienić, i zawsze będą miały pierwszeństwo w wykonywaniu. Do nich należą:
  • Reset - zresetowanie układu mikrokontrolera (Podpriorytet -3);
  • NMI - przerwanie niemaskowalne (Podpriorytet -2);
  • HardFault - wystąpienie błędu lub przerwania, którego nie da się obsłużyć (Podpriorytet -1);
W trakcie programowania są do wyboru następujące opcje ustawiania, które są zawarte w strukturze NVIC_InitTypeDef:
  • NVIC_IRQChannel - określa źródło przerwania (kanał);
  • NVIC_IRQChannelPreemptionPriority - określa priorytet grupowy;
  • NVIC_IRQChannelSubPriority - określa podpriorytet;
  • NVIC_IRQChannelCmd - ustawia włączenie danego przerwania;

EXTI


EXTI (ang. External Interrupt) są to zewnętrzne źródła przerwania. Każda z dostępnych linii GPIO może zostać skonfigurowana tak aby zgłaszała przerwanie. 
Dostępne są następujące możliwości powiązania pinów z funkcjami obsługi przerwania:
  • Pin 0 - EXTI0_IRQHandler();
  • Pin 1 - EXTI1_IRQHandler();
  • Pin 2 - EXTI2_IRQHandler();
  • Pin 3 - EXTI3_IRQHandler();
  • Pin 4 - EXTI4_IRQHandler();
  • Piny od 5 do 9 - EXTI9_5_IRQHandler();
  • Piny od 10 do 15 - EXTI10_15_IRQHandler();

Struktura EXTI składa się z następujących parametrów:
  • EXTI_Line - deklaracja linii, która zostanie wykorzystana do obsługi przerwania;
  • EXTI_Mode - wybranie trybu obsługi danego zgłoszenia. Można wybrać zewnętrzne bądź wewnętrzne;
  • EXTI_Trigger - jakie zbocze sygnału będzie wywoływało przerwanie rosnące, opadające czy oba;
  • EXTI_LineCmd - włącza dany kanał przypisany do odpowiedniego przerwania zewnętrznego;

Program


Program będzie dosyć prosty, zaprezentuje w nim sposób inicjalizacji przerwania dla przycisku wbudowanego. Zostanie zapalona dioda bądź zgaszona dioda wbudowana. Jedno wciśnięcie przycisku powoduje zapalenie, drugie natomiast powoduje zgaszenie diody.

Poniżej przedstawiam całość programu wraz z komentarzem.

  1. #include "stm32f10x.h"
  2.  
  3. #define PinDioda        GPIO_Pin_5
  4. #define PinPrzycisk     GPIO_Pin_13
  5. #define LineDioda       GPIOA
  6. #define LinePrzycisk    GPIOC
  7. #define ClockGPIOA      RCC_APB2Periph_GPIOA
  8. #define ClockGPIOC      RCC_APB2Periph_GPIOC
  9.  
  10. void NVIC_EXTIInit(void);
  11. void GPIOInit(void);
  12. void EXTI15_10_IRQHandler(void);
  13.  
  14. volatile int zapal = 0;
  15.  
  16. int main(void)
  17. {
  18.  GPIOInit();
  19.  NVIC_EXTIInit();
  20.  
  21.  while (1) {
  22.  }
  23. }
  24.  
  25. void NVIC_EXTIInit(void)
  26. {
  27.  EXTI_InitTypeDef EXTIInit;
  28.  NVIC_InitTypeDef NVICInit;
  29.  
  30.  //Wybór konfigurowanego przerwania
  31.  NVICInit.NVIC_IRQChannel = EXTI15_10_IRQn;
  32.  //Priorytet grupowy
  33.  NVICInit.NVIC_IRQChannelPreemptionPriority = 0x00;
  34.  //Podpriorytet
  35.  NVICInit.NVIC_IRQChannelSubPriority = 0x00;
  36.  //Włączenie obsługi
  37.  NVICInit.NVIC_IRQChannelCmd = ENABLE;
  38.  NVIC_Init(&NVICInit);
  39.  
  40.  //Ustawienie źródła przerwania
  41.  GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13);
  42.  
  43.  //EXTI_StructInit(&EXTIInit);
  44.  
  45.  //Wybór linii 13
  46.  EXTIInit.EXTI_Line = EXTI_Line13;
  47.  //Ustawienie generowanego przerwania, możliwe jest wybranie zdarzenia
  48.  EXTIInit.EXTI_Mode = EXTI_Mode_Interrupt;
  49.  //Wyzwolenie zboczem opadającym, bo przycisk zwarty do masy
  50.  EXTIInit.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
  51.  //Włączenie przerwania
  52.  EXTIInit.EXTI_LineCmd = ENABLE;
  53.  EXTI_Init(&EXTIInit);
  54. }
  55.  
  56. //Obsługa przerwania
  57. void EXTI15_10_IRQHandler()
  58. {
  59.  //Zidentyfikowanie źródła przerwania
  60.  if (EXTI_GetITStatus(EXTI_Line13) != RESET)
  61.  {
  62.   //Podanie głównego kodu wykonywanego w procedurze obsługi przerwania
  63.   //Jeśli przycisk został wciśnięty
  64.  
  65.   if ((GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0) && (zapal == 0))
  66.   {
  67.    GPIO_SetBits(GPIOA, GPIO_Pin_5);
  68.    zapal = 1;
  69.   }
  70.   else if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0)
  71.   {
  72.    GPIO_ResetBits(GPIOA, GPIO_Pin_5);
  73.    zapal = 0;
  74.   }
  75.  
  76.   //Koniec wpisywania głównego kodu wykonywanego w przerwaniu
  77.   //Wyzerowanie flagi obsługi przerwania
  78.   EXTI_ClearITPendingBit(EXTI_Line13);
  79.  }
  80. }
  81.  
  82. //Inicjalizacja pinów dla przycisku i diody
  83. void GPIOInit(void)
  84. {
  85.  GPIO_InitTypeDef GpioInit;
  86.  
  87.  RCC_APB2PeriphClockCmd(ClockGPIOA, ENABLE);
  88.  RCC_APB2PeriphClockCmd(ClockGPIOC, ENABLE);
  89.  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  90.  
  91.  GPIO_StructInit(&GpioInit);
  92.  GpioInit.GPIO_Pin = PinDioda;
  93.  GpioInit.GPIO_Mode = GPIO_Mode_Out_PP;
  94.  GPIO_Init(LineDioda, &GpioInit);
  95.  
  96.  GpioInit.GPIO_Pin = PinPrzycisk;
  97.  GpioInit.GPIO_Mode = GPIO_Mode_IPU;
  98.  GPIO_Init(LinePrzycisk, &GpioInit);
  99. }