Vaja 3: Izjeme in prekinitve

Namen tokratne vaje je, da se spoznate s konceptom izjem in prekinitev v STM32H7, prekinitvenim krmilnikom (NVIC) ter SysTick izjemo.

Izjeme in prekinitve v STM32H7

Kot ste spoznali na predavanjih, izjeme (angl. exceptions) in prekinitve (angl. interrupt) lahko prekinejo procesor pri trenutnem delovanju. Procesor ob izjemi ali prekinitvi začne izvajati prekinitveno servisni program (PSP), ki je za prispelo izjemo/prekinitev določen. Vsaka izmed izjem ali prekinitev ima svojo prioriteto. V primeru, da se hkrati pojavita dve izjemi/prekinitvi, se bo najprej servisirala prekinitev z višjo prioriteto. Prioritete so v ARM Cortex M7 označene s številkami, pri čemer nižja številka pomeni višjo prioriteto.

ARM uporablja izraz izjema (exception) kot nadpomenko za vse dogodke, ki prekinejo delovanje procesorja, prekinitve pa tretira kot eno izmed vrst izjem. ARM pozna sledeče tipe izjem:

  • Reset: Izjema reset se zgodi ob vklopu ali t.i. warm resetu, ki ga lahko dosežemo na primer s pritiskom črne tipke na razvojni plošči. Ta vrsta izjeme je vedno omogočena in ima prioriteto -3. Ko se pojavi izjema Reset se procesor ustavi, ne glede na to kaj izvaja v danem trenutku. Po deaktivaciji Reset izjeme, se izvede prekinitveni servisni program. Zadnji korak prekinitveno servisnega programa za reset je običajno klic uporabniškega programa.

  • NMI: NMI je kratica za non-maskable interrupt (prekinitev, ki je ni mogoče maskirati/onemogočiti). Ima najvišjo prioriteto (-2) izmed vseh vrst izjem, razen izjeme Reset.

  • HardFault: Ta izjema se pojavi ob napaki med procesiranjem kakšne izmed preostalih izjem/prekinitev. HardFault ima prioriteto -1. Iz servisiranja izjeme HardFault se običjano ni možno vrniti v normalno izvajanje programa - potreben je Reset.

  • MemManage: Izjema, ki se pojavi ob kršitvah pri dostopih do pomnilniških lokacij. Zgodi se na primer, ko skušamo dostopati do pomnilniške lokacije, za katero v trenutenm načinu delovanja nimamo pravic dostopa. Za pravice dostopa do pomnilnika običajno skrbi enota, ki ji rečeme Memory Protection Unit (MPU).

  • BusFault: Izjema, ki se zgodi ob napakah na podatkovnem ali ukaznem vodilu. Pojavijo se ob napakah ob dostopu do pomnilnika ali pomnilniško-preslikanih registrov vhodno-izhodnih naprav. Največkrat ob dostopu do naslovov, ki niso veljavni (niso lokacije v pomnilniku ali register V/I naprave).

  • UsageFault: Izjema, ki se pripeti zaradi nedefiniranega ukaza, neporavnanega naslova, nepravilnega stanja pri izvajanju ukazov ali napaki pri vračanju iz prekinitveno servisnega programa. Največkrat se nam bodo pripetili ob dostopu do neporavnanih naslovov (če recimo kazalec na 32-bitno spremenljivko nastavimo na vrednost, ki ni deljiva s 4) ali deljenju z 0.

  • SVCall: Izjema, ki jo proži ukaz SVC - supervisor call. Operacijski sistemi uporabljajo SVC ukaz za dostop do funkcij jedra OS (kernel) ali gonilnikov naprave.

  • PendSV: Izjema, ki se proži programsko. Običajno jih uporabljajo operacijski sistemi, da označijo začetek menjave konteksta (context switch).

  • SysTick: Izjema, ki jo generira sistemski časovnik, ko doseže vrednost 0. Ponavadi se jo uporablja v operacijskih sistemih za zagotavljanje delovanja časovnega rezinjenja (time sharing). V knjižnici STM HAL se jo uporablja tudi za zagotavljanje delovanja funkcije HAL_Delay().

  • Interrupt: Prekinitve so ostale izjeme, ki jih prožijo druge periferne naprave: naprave za komunikacijo kot npr. UART, GPIO pini preko naprave EXTI, in tako dalje. V nadaljevanju semestra bomo ob spoznavanju perifernih naprav, spoznali tudi prekinitve, ki jih te naprave lahko prožijo.

Oznake izjem/prekinitev

Vsaka izjema in prekinitev imajo dve številski oznaki, oznaka izjeme (exception number) in oznaka prekinitve (interrupt request number - IRQn). Tabela s številskimi oznakami je prikazana spodaj. Kot vidite imajo prekinitve prekinitveno oznako 0+, medtem ko imajo preostale izjeme negativne prekinitvene oznake. V programih se zaradi poenostavitve uporablja zgolj oznake prekinitve - IRQn.

Oznake izjem in prekinitev
Oznaka izjeme Ime Oznaka prekinitve (IRQn)
1 Reset /
2 NMI -14
3 HardFault -13
4 MemManage -12
5 BusFault -11
6 UsageFault -10
11 SVCall -5
14 PendSV -3
15 SysTick -1
16+ prekinitve 0+

Izjeme 7-10 ter 12 in 13 so rezervirane. Oznaka 12 se običajno uporablja za razhroščevanje.

V tabeli spodaj so zapisane konstante za izjeme in prekinitve v STM32H750, ki so definirane v stm32h750xx.h. Spodnje kostante bomo uporabljali v naših programih kadar bomo upravljali s prekinitvami in izjemami.

typedef enum
{
/******  Cortex-M Processor Exceptions Numbers *****************************************************************/
  NonMaskableInt_IRQn         = -14,    /*!< 2 Non Maskable Interrupt                                          */
  HardFault_IRQn              = -13,    /*!< 3 Cortex-M Hard Fault Interrupt                                   */
  MemoryManagement_IRQn       = -12,    /*!< 4 Cortex-M Memory Management Interrupt                            */
  BusFault_IRQn               = -11,    /*!< 5 Cortex-M Bus Fault Interrupt                                    */
  UsageFault_IRQn             = -10,    /*!< 6 Cortex-M Usage Fault Interrupt                                  */
  SVCall_IRQn                 = -5,     /*!< 11 Cortex-M SV Call Interrupt                                     */
  DebugMonitor_IRQn           = -4,     /*!< 12 Cortex-M Debug Monitor Interrupt                               */
  PendSV_IRQn                 = -2,     /*!< 14 Cortex-M Pend SV Interrupt                                     */
  SysTick_IRQn                = -1,     /*!< 15 Cortex-M System Tick Interrupt                                 */
/******  STM32 specific Interrupt Numbers **********************************************************************/
  WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt ( wwdg1_it, wwdg2_it)                   */
  PVD_AVD_IRQn                = 1,      /*!< PVD/AVD through EXTI Line detection Interrupt                     */
  TAMP_STAMP_IRQn             = 2,      /*!< Tamper and TimeStamp interrupts through the EXTI line             */
  RTC_WKUP_IRQn               = 3,      /*!< RTC Wakeup interrupt through the EXTI line                        */
  FLASH_IRQn                  = 4,      /*!< FLASH global Interrupt                                            */
  RCC_IRQn                    = 5,      /*!< RCC global Interrupt                                              */
  EXTI0_IRQn                  = 6,      /*!< EXTI Line0 Interrupt                                              */
  EXTI1_IRQn                  = 7,      /*!< EXTI Line1 Interrupt                                              */
  EXTI2_IRQn                  = 8,      /*!< EXTI Line2 Interrupt                                              */
  EXTI3_IRQn                  = 9,      /*!< EXTI Line3 Interrupt                                              */
  EXTI4_IRQn                  = 10,     /*!< EXTI Line4 Interrupt                                              */
  DMA1_Stream0_IRQn           = 11,     /*!< DMA1 Stream 0 global Interrupt

Prekinitveni krmilnik NVIC

Prekinitve in večino izjem lahko programsko upravljamo, jih omogočimo ali onemogočimo ali pa jim določamo prioriteto. Za upravljanje skrbi prekinitveni krmilnik, angleško Nested Vectored Interrupt Controller oz. krajše NVIC. Prioriteto določamo na dveh nivojih, vsak izmed nivojem z možnimi vrednostmi med 0 in 15. V primeru, da hkrati prispe več prekinitvenih zahtev, se najprej preveri prioriteta na prvem nivoju. V primeru, da ima ena izmed prekinitev višjo prioriteto (nižjo vrednost) na prvem nivoju, se najprej servisira ta prekintitev. V primeru, da je prioriteta na prvem nivoju enaka pa se preveri drugi nivo. V primeru ujemanja tudi na drugem nivoju, se prva servisira prekinitev z nižjo oznako.

Programski vmesnik za delo z NVIC v STM HAL je zelo enostaven, zanimive so predvsem štiri funkcije:

  • HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)

Funkcija nastavimo prioriteto na obeh nivojih za podano prekinitveno oznako. Primera:

HAL_NVIC_SetPriority(EXTI2_IRQn, 12, 11);
HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
  • HAL_NVIC_EnableIRQ(IRQn_Type IRQn)

Funkcija omogoči prekinitev za podano oznako. Primera:

HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
  • HAL_NVIC_DisableIRQ(IRQn_Type IRQn)

Funkcija onemogoči prekinitev za podano oznako. Primera:

HAL_NVIC_DisableIRQ(RTC_WKUP_IRQn);
HAL_NVIC_DisableIRQ(EXTI4_IRQn);
  • HAL_NVIC_SystemReset(void)

Funkcija programsko sproži reset.

Prekinitveno servisni programi

Če je izjema/prekinitev omogočena, NVIC pošlje oznako izjeme/prekinitve procesorju. Začetni naslovi (imena funckij v asm ali C) prekinitveno servisnih programov se nahajajo v t.i. vektorski tabeli, ki se nahaja v datoteki startup_stm32h750xbhx.s. Začetni del vektorske tabele je prikazan spodaj. Prvi element vektorske tabele je skladovni kazalec (stack pointer), temu pa sledi seznam začetnih naslovov vseh prekinitveno servisnih programov.

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_AVD_IRQHandler                /* PVD/AVD 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 */

Če se zgodi izjema HardFault, se bo torej izvedla funkcija z imenom HardFault_Handler. Ta je lahko napisana v zbirniku ali C-ju. Spodaj je podana tabela z oznakami in pripadajočimi imeni prekinitveno servisnih programov za izjeme in nekaj prekinitev.

Oznake izjem in imena prekinitveno servisnih programov
Oznaka izjeme Prekinitveno servisni program
Reset (ni oznake v HAL) Reset_Handler
NonMaskableInt_IRQn NMI_Handler
HardFault_IRQn HardFault_Handler
MemoryManagement_IRQn MemManage_Handler
BusFault_IRQn BusFault_Handler
UsageFault_IRQn UsageFault_Handler
SVCall_IRQn SVC_Handler
PendSV_IRQn PendSV_Handler
SysTick_IRQn SysTick_Handler
WWDG_IRQn WWDG_IRQHandler
PVD_AVD_IRQn PVD_AVD_IRQHandler
TAMP_STAMP_IRQn TAMP_STAMP_IRQHandler
RTC_WKUP_IRQn RTC_WKUP_IRQHandler
FLASH_IRQn FLASH_IRQHandler
RCC_IRQn RCC_IRQHandler
EXTI0_IRQn EXTI0_IRQHandler
EXTI1_IRQn EXTI1_IRQHandler

Nekateri prekinitveno servisni programi so realizirani v zbirniku (na primer Reset_Handler). Te najdemo v datoteki startup_stm32h750xbhx.s. PSP-je v jeziku C pa najdemo v stm32h7xx_it.c. Tam jih bomo pisali tudi mi.

Izjema SysTick

Kot že omenjeno zgoraj, izjemo SysTick generira sistemski časovnik, ko doseže vrednost 0. Ponavadi se to uporablja v operacijskih sistemih za zagotavljanje delovanja časovnega rezinjenja (time sharing).

V funkciji HAL_Init(), ki jo uporabljamo kadar se poslužujemo knjižnice STM HAL, se inicializira sistemski časovnik in omogoči izjemo SysTick s prioriteto (15, 0) in frekvenco 1kHz. Izjema se torej proži vsako milisekundo. Funkcija SysTick_Handler se torej izvede vsako milisekundo. V stm32h7xx_it.c najdemo realizacijo PSP-ja:

void SysTick_Handler(void)
{

  HAL_IncTick();

}

Klic HAL_IncTick(); omogoča delovanje funckiji HAL_Delay(), ki smo jo uporabili na zadnji vaji.

Naloga 3.1

  • S pomočjo izjeme SysTick realizirajte funkcijo My_Delay(int ms), ki deluje na enak način kot HAL_Delay(int ms), ki smo jo uporabljali na zadnjih vajah.

  • V while zanki na koncu main() funkcije realizirajte utripanje LED diod. Ob resetu naj LED utripajo z zakasnitvijo treh sekund:

while(1) {
  // prizgi LED
  My_Delay(zakasnitev);
  // ugasni LED
  My_Delay(zakasnitev);
}
  • Če držimo gumb za več kot 1 sekundo, se zakasnitev pri utripanju LED diod zmanjša za tretjino. Iz treh sekund torej na dve sekundi, potem na 1,33 sekunde, in tako naprej.