STM32F0 Externí přerušení

Externí přerušení je v tomto případě název trochu zavádějící. Budeme se totiž zabývat blokem EXTI, který má na starosti správu mnoha přerušení, nejen těch externích. EXTI detekuje příchod až 32 různých asynchronních událostí. Každé události odpovídá jedna linka (EXTI line). Jen pro přehled si uveďme jejich seznam:

Linky, které nenajdete v seznamu nejsou použité. Nás teď budou zajímat hlavně linky 0 až 15, protože je na ně možné namapovat piny procesoru. Každé z těchto linek je možné vybrat jako zdroj jedno GPIO, tedy na linku 0 může být připojen buď pin PA0,PB0, PC0 mebo PF0 (jiné piny na F051R8 nemáme). Analogicky k lince 1 můžeme připojit piny PA1,PB1,PC1...PF1. Není tedy možné mít zároveň zapnuté externí přerušení například z PC3 a PB3. EXTI krom přerušení obsluhuje také takzvané eventy, které slouží k probouzení z režimu spánku (což přerušení umí také). U každé linky (interní i externí) si můžeme zvolit na kterou hranu má přerušení reagovat (případně zda má reagovat na obě hrany). Vektory přerušení odpovídající linkám 0-15 jsou ale pouze tři. Pokud si povolíme přerušení například od PC7 a PB12 (tedy linky 7 a 12), obě události zavolají tutéž rutinu přerušení (EXTI4_15_IRQn), typicky tedy budete muset uvnitř rutiny rozpoznat zdroj přerušení a zařídit se podle toho. Což vás může zpomalovat. Stejně jako u většiny přerušení v STM32, budete také muset smazat vlajku příslušného zdroje, jinak by se přerušení volalo stále dokola, ale to už uvidíte v prvním příkladě.

Externí přerušení I

Na vstupy PC6 a PC7 nám přichází signál, na který máme reagovat. Po příchodu vzestupné hrany na PC6 chceme na výstupu PC8 vytvořit několika mikrosekundový kladný pulz, který bude představovat užitečnou činnost procesoru. Podobně po příchodu vzestupné hany na PC7 chceme na výstupu PC9 také vytvořit pulz. Volba vstupů PC6 a PC7 nás nutí využít pro obě přerušení jen jednu rutinu (EXTI4_15_IRQn). Tato volba nám znemožňuje upřednostnit jedno přerušení před druhým, což uvidíme na závěrečném oscilogramu. Nyní ale přejděme ke konfiguraci. Nebudu zde zveřejňovat celý zdrojový kód (ten si stáhněte), ale jen důležité partie. Jako první musíte spustit clock do SYSCFG jehož je EXTI součástí. Konfiguraci lze provádět opět pomocí struktur. Ve struktuře vybíráme které linky chceme konfigurovat, hranu kterou chceme detekovat a zda chceme přerušení od vybraných linek povolit nebo zakázat. Zakazování přerušení lze dělat elegantněji pomocí příslušné funkce (LL_EXTI_DisableIT_0_31()). Dále musíme namapovat k použitým linkám GPIO aby bylo jasné ze kterého GPIO má brát EXTI signál. Nakonec je potřeba nastavit v NVIC prioritu našemu přerušení a povolit ho.

Zdrojový kód ke stažení

void exti_init(void){
LL_EXTI_InitTypeDef exti;
// clock pro SYSCFG (obstarává externí přerušení)
LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);
exti.Line_0_31 = LL_EXTI_LINE_6 | LL_EXTI_LINE_7; // linky 6 a 7
exti.Mode = LL_EXTI_MODE_IT; // režim přerušení
exti.Trigger = LL_EXTI_TRIGGER_RISING; // vzestupná hrana
exti.LineCommand = ENABLE; // chceme je povolit
LL_EXTI_Init(&exti); // aplikuj nastavení

// namapuj linky EXTI6 a EXTI7 na GPIOC (tedy na PC6 a PC7)
LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTC,LL_SYSCFG_EXTI_LINE6);
LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTC,LL_SYSCFG_EXTI_LINE7);

// povol přerušení s příslušnou prioritou
NVIC_SetPriority(EXTI4_15_IRQn,2);
NVIC_EnableIRQ(EXTI4_15_IRQn);
}

V rutině přerušení (jejíž kód je níže) musíme nejrpve zjistit která linka přerušení vyvolala. Až ji identifikujeme můžeme vytvořit pulz na vybraném výstupu. Smyčka delay() reprezentuje nějaký náročný výpočet trvající odhadem 40us. Před výstupem z rutiny nesmíme zapomenout smazat vlajku linky kterou jsme obsloužili. Není od věci ji mazat hned po identifikaci zdroje přerušení. Protože dokud je vlajka nastavena, není EXTI schopno detekovat na této lince příchod další události. Pokud ji smažeme na začátku rutiny a během jejího vykonávání přijde další událost, vlajka se znovu nastaví a po skončení rutiny dojde k jejímu opětovnému zavolání a my budeme moct reagovat na tuto druhou událost. Kdybychom ji mazali na konci rutiny, všechny události přicházející během ní bychom nemohli zaznamenat.

void EXTI4_15_IRQHandler(void){
if(LL_EXTI_ReadFlag_0_31(LL_EXTI_LINE_6)){
 // přišlo přerušení z PC6
 LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6); // vyčisti vlajku
 LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_8); // info výstup PC6
 delay(100); // dělej něco užitečného
 LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_8);
 }
if(LL_EXTI_ReadFlag_0_31(LL_EXTI_LINE_7)){
 // přišlo přerušení z PC7
 LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7); // vyčisti vlajku
 LL_GPIO_SetOutputPin(GPIOC,LL_GPIO_PIN_9); // info výstup PC7
 delay(100); // dělej něco užitečného
 LL_GPIO_ResetOutputPin(GPIOC,LL_GPIO_PIN_9);
 }
}

Na oscilogramu níže můžete vidět výsledek malého pokusu. Oba signály na které reagujeme jsme poslal v krátkém časovém sledu za sebou. Můžete vidět, že jako první přijde vzestupná hrana na PC7, program během přibližně 4us zareaguje a nastaví PC9 do log.1 a začne provádět "užitečný výpočet". Pár mikrosekund potom přichází hrana na PC6, ale program na ni nemůže okamžitě reagovat. Nachází se totiž v přesně té samé rutině přerušení, která by se měla zavolat s příchodem hrany na PC6. Teprve až dokončí svou "užitečnou" činnost, ukončí rutinu přerušení a ihned na to je volána znovu, tentokrát signálem z PC6 a reaguje na to nastavením PC8 do log.1 a prováděním další "užitečné" činnosti. Touto ukázkou jsem chtěl naznačit, že ačkoli má STM32F0 možnost využívat vnořená přerušení s nastavitelnou prioritou, nic nám to nepomůže když obě událoti vedou to téže rutiny přerušení. Ale nebojte se za chvíli si předvedeme jak to zachránit lepší volbou vstupů...


Reakce softwaru na hrany přicházející těsně po sobě

Externí přerušení II

Oproti předchozí ukázce, se teď pokusíme připravit situaci kdy může mít jedno externí přerušení vyšší prioritu než jiné. Abychom toho mohli docílit, musíme si signály přivést na jiné vstupy, tak aby měl každý z nich jiný vektor přrušení. Jeden si proto přivedeme na PC0 (a jeho činnost budeme indikovat na přepínáním PC8) a druhý na PC2 (s indikací na PC9). Přerušení EXTI2_3_IRQn dáme vyšší prioritu. Přerušní od signálu na PC2 by tedy mělo být schopné přerušit vykonávání rutiny přerušení od PC0. Aby to nebylo tak suché, zkusíme si kód optimalizovat na rychlost reakce. V předchozí ukázce reagoval výstup v čase přibližně 4us, což bylo dáno tím že program musel nejprve rozhodnout o zdroji přerušení. To teď nemusí, protože je zdroj pro každou rutinu jen jeden a je tedy zřejmé který signál přerušení vyvolal. Dále využijeme CMSIS a nasavení výstupního pinu napíšeme pomocí přístupu do registrů a navíc tuto instrukci přesuneme úplně na začátek rutiny. Viz následující zdrojový kód.

Zdrojový kód ke stažení

// přerušení od PC0
void EXTI0_1_IRQHandler(void){
 uint16_t i;
 // nekontroluji zdroj přerušení, protože je povolen jen jeden (PC0)
 GPIOC->BSRR = LL_GPIO_PIN_8; // PC8 do log.1 (info výstup)
 LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0); // vyčisti vlajku
 // dělej užitečnou činnost
 for(i=0;i<30;i++){
  LL_GPIO_TogglePin(GPIOC,LL_GPIO_PIN_8); // přepínej pin
 }
 GPIOC->BRR = LL_GPIO_PIN_8; // PC8 do log.0 (info výstup)
}

// přerušení od PC2
void EXTI2_3_IRQHandler(void){
 uint16_t i;
 // nekontroluji zdroj přerušení, protože je povolen jen jeden (PC2)
 GPIOC->BSRR = LL_GPIO_PIN_9; // PC9 do log.1 (info výstup)
 LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2); // vyčisti vlajku
 // dělej užitečnou činnost
 for(i=0;i<30;i++){
  LL_GPIO_TogglePin(GPIOC,LL_GPIO_PIN_9); // přepínej pin
 }
 GPIOC->BRR = LL_GPIO_PIN_9; // PC9 do log.0 (info výstup)
}

// konfigurace Externích přerušení
void exti_init(void){
 LL_EXTI_InitTypeDef exti;
 // clock pro SYSCFG (obstarává externí přerušení)
 LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_SYSCFG);
 exti.Line_0_31 = LL_EXTI_LINE_0 | LL_EXTI_LINE_2; // linky 0 a 2
 exti.Mode = LL_EXTI_MODE_IT; // režim přerušení
 exti.Trigger = LL_EXTI_TRIGGER_RISING; // vzestupná hrana
 exti.LineCommand = ENABLE; // chceme je povolit
 LL_EXTI_Init(&exti); // aplikuj nastavení

 // namapuj linky EXTI6 a EXTI7 na GPIOC (tedy na PC6 a PC7)
 LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTC,LL_SYSCFG_EXTI_LINE0);
 LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTC,LL_SYSCFG_EXTI_LINE2);

 // povol přerušení s příslušnou prioritou
 NVIC_SetPriority(EXTI0_1_IRQn,2);
 NVIC_SetPriority(EXTI2_3_IRQn,1);
 NVIC_EnableIRQ(EXTI0_1_IRQn);
 NVIC_EnableIRQ(EXTI2_3_IRQn);
}

Zkusíme-li poslat na vstupy sled dvou těsně po sobě jdoucích hran můžeme vidět, že se naše očekávání vyplnila. Nejprve přichází hrana na kanál PC0, program během 0.8us stihne vstoupit do rutiny přerušení (EXTI0_1_IRQn) a nastavit výstup PC8 do log.1. Chvíli dělá svou "užitečnou" činnost (osciluje s PC8). V tom přichází vzestupná hrana na vstup PC2, program reaguje tím, že přeruší momentálně vykonávanou rutinu přerušení (signál na PC8 se přestane měnit) a vstoupí do rutiny s vyšší prioritou (EXTI2_3_IRQn). V ní nastaví PC9 do log.1 a vykonává jinou "užitečnou" činnost (osciluje výstupem PC9). Teprve až rutina s vyšší prioritou skončí, vrátí se program do rutiny s nižší prioritou (EXTI0_1_IRQn) a svou "práci" dokončí.


Reakce softwaru na hrany přicházející těsně po sobě - vnořená přerušení

Home
V1.00 23.7.20175
By Michal Dudka (m.dudka@seznam.cz)