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:
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ů...
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čí.
Home
V1.00 23.7.20175
By Michal Dudka (m.dudka@seznam.cz)