Kurs STM32 LL cz. 6. Wyjście GPIO i przerwania EXTI

Z poprzedniej lekcji wiemy już, jak sterować wyjściem GPIO. Teraz czas na drugą funkcjonalność – dzisiaj nauczymy się odczytywać stan wejścia na przykładzie przycisku podłączonego do pinu.

Budowa GPIO – praca w funkcji wejścia

Schemat budowy GPIO w STM32 z zaznaczonymi funkcjonalnościami w trakcie pracy jako wejście możemy zobaczyć na grafice poniżej.

Bufor wyjściowy jest w tym czasie nieaktywny. Aktywowany jest za to bufor wejściowy z przerzutnikiem Schmitta oraz dostępne są rezystory podciągające. Dane w rejestrze wejściowym uaktualniane są wraz z każdym cyklem zegara.

W trybie wejścia pin może pracować w jednym ze stanów:

  1. Input with internal pull-up – wejście z wewnętrznym podciągnięciem do góry (plusa zasilania). Pozwala ustalić stan pinu w przypadku pływającego sygnału wejściowego (floating signal). Można zastosować zewnętrzny rezystor podciągający, jeżeli wymaga tego aplikacja.
  1. Input with internal pull-down – wejście z wewnętrznym podciągnięciem do dołu (masy zasilania). Pozwala ustalić stan pinu w przypadku pływającego sygnału wejściowego (floating signal). Można zastosować zewnętrzny rezystor podciągający, jeżeli wymaga tego aplikacja.
  1. Floating input – wejście pływające. Wejście całkowicie zależy od sygnału wejściowego podłączonego do pinu. W przypadku braku ustalonego stanu na wejściu, wbudowany przerzutnik Schmitta losowo przełącza pomiędzy stanami logicznymi (raz jedynka, raz zero) – stan nieustalony.

Jakie są zalety i wady każdego z rozwiązań? Stosowanie rezystorów podciągających może spowolnić działanie pinów w przypadku, gdy do wejścia podłączona jest duża pojemność. Wówczas należy skonfigurować wejście w trybie pływającym (floating). Rezystory podciągające pozwalają natomiast wyeliminować stan nieustalony np. w przypadku aplikacji wykorzystującej przycisk, kiedy w momencie rozłączenia wejście nie jest podpięte do żadnego z pinów zasilania.

Trzeba też pamiętać, że nie należy zostawiać wejść w stanie wysokiej impedancji (niepodłączonych) bez rezystorów podciągających – może to powodować gromadzenie się ładunków i prowadzić do uszkodzenia wejścia.

Ważną uwagę w przypadku projektowania płytek czy podłączenia prototypów jest to, że nieużywane piny należy podłączyć do VDD lub VSS (GND) przez rezystory podciągające. Bezpośrednie podłączenie pinu np. do masy może spowodować uszkodzenie pinu w przypadku, gdy pomyłkowo skonfigurujemy go jako wyjście (dojdzie do zwarcia).

Zasada działania przerzutnika Schmitta

Wejście GPIO wykorzystuje przerzutnik Schmitta. Pozwala on na uzyskanie wymaganych poziomów napięć w przypadku wykrywania stanu niskiego i wysokiego. W technice cyfrowej zabroniony jest stan pośredni, który mógłby wprowadzać oscylacje. 

Przerzutnik Schmitta powoduje, że stan wysoki jest załączany po osiągnięciu napięcia progowego Vh, zaś stan niski, gdy napięcie spadnie poniżej progu Vl. Przedział napięcia pomiędzy Vh i Vl to histereza

Takie działanie powoduje, że w przypadku napięcia pomiędzy Vh i Vl stan będzie zależał od tego, czy napięcie rosło, czy malało. Dlatego do prawidłowej pracy musimy zapewnić odpowiednie wartości napięcia stanu niskiego i wysokiego kompatybilne z mikrokontrolerem.

Zastosowanie histerezy sprawia, że w przypadku oscylacji napięcia pomiędzy Vh i Vl nasz stan nie będzie się co chwila zmieniał, co mogłoby powodować niepożądane wystąpienia np. przerwań.

Wszystkie projekty z kursu dostępne są w moim repozytorium GitHub.

[PROGRAM] Konfiguracja i sterowanie wejściem GPIO – polling

Znamy już podstawową budowę GPIO w funkcji wyjścia. Ta wiedza pozwoli nam poprawnie skonfigurować rejestry.

Na początku musimy uruchomić zegar dla GPIO. Bez tego port nie będzie pracował. W programie będziemy używali wejścia PC13 (oznaczony etykietą USER_BUTTON), dlatego włączamy zegar dla GPIOC.

LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);

Teraz powinniśmy ustawić parametry portu. W przypadku wejścia musimy wybrać rodzaj podciągnięcia. Możemy ustawić podciągnięcie Pull-up, Pull-down lub odłączyć oba rezystory. To, jaką opcję wybierzemy w dużej mierze zależy od tego, jak podłączony jest nasz przycisk na płytce. Zajrzyjmy zatem do dokumentacji Nucleo-G071RB.

Na schemacie widzimy, że przycisk będzie zwierał nam wejście do GND, czyli w momencie wciśnięcia pojawi się stan niski. Wypadałoby zatem użyć podciągnięcia do VCC, aby w momencie, gdy nie jest wciśnięty, pojawiał się stabilny stan wysoki. Na schemacie widzimy jednak, że zastosowano w Nucleo podciągnięcie za pomocą zewnętrznego rezystora. Możemy zatem wewnętrzne podciągnięcie pominąć.

LL_GPIO_SetPinPull(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_PULL_NO);

Następnie wybieramy szybkość (w zasadzie maksymalną częstotliwość pracy) wyjścia. 

LL_GPIO_SetPinSpeed(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_SPEED_FREQ_LOW);

Na koniec ustawiamy tryb pracy pinu, czyli w naszym wypadku jako wejście.

LL_GPIO_SetPinMode(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_MODE_INPUT);

W tym momencie mamy skonfigurowane wszystkie elementy niezbędne do sterowania pinem jako wejście. W rejestrach GPIO mamy jeszcze możliwość konfiguracji rodzaju funkcji alternatywnej (Alternate Function) oraz typ wyjścia, ale przy ustawieniu pinu jako wejście wartość tych rejestrów nie ma znaczenia. Możemy je pominąć.

Teraz, aby odczytać stan wyjścia, musimy sprawdzić zawartość rejestru IDR. Pomoże nam w tym funkcja LL_GPIO_IsInputPinSet().

LL_GPIO_IsInputPinSet(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin)

Wykorzystuje prostą operacją na masce bitowej i zwraca ona “1”, gdy dany pin ma stan wysoki na wejściu lub 0, gdy na wejściu pojawi się stan niski. Dla naszego przycisku, ze względu na sposób podłączenia na płytce) logika będzie odwrócona – w momencie wciśnięcia przycisku na wejściu pojawi się stan niski, a rejestrze IDR odczytamy “0”.

Kontroler EXTI i NVIC – obsługa przerwań

Mikrokontrolery STM32 mają wbudowane mechanizmy obsługiwania wyjątków, które powodują przerwanie działania programu głównego. Należą do nich:

  • NVIC (Nested Vectored Interrupt Controler)
  • EXTI (Extended Interrupt/Event Controller)

Obsługa wyjątków obejmuje zarówno zdarzenia wewnętrzne mikrokontrolera (np. dzielenie przez zero), jak i zdarzenia zewnętrzne występujące na pinach np. zmiana stanu na wejściu.

Wyróżnia się trzy sposoby zarządzania przerwaniami:

  • Obsługa sekwencyjna – po zakończeniu obsługi bieżącego przerwania następuje obsługa kolejnego
  • Obsługa zagnieżdżona – obsługa kolejnego przerwania następuje w trakcie obsługi bieżącego, potem wraca do poprzedniego przerwania
  • Obsługa priorytetowa – przerwania mają nadane priorytety:
    • Jeżeli priorytet wyższy – obsługa zagnieżdżona
    • Jeżeli priorytet niższy lub taki sam – sekwencyjna

Mikrokontrolery ARM, a więc i STM32 wykorzystują obsługę priorytetową. Mechanizm przerwań pozwala na obsługę ważnych zdarzeń w trakcie normalnego działania programu, a przez to uzyskanie w pewnym sensie zadaniowości.

Kontroler NVIC

Kontroler przerwań NVIC jest elementem rdzenia ARM, dlatego identyczną strukturę znajdziemy także w innych mikrokontrolerach ARM. Oferuje priorytety przerwań. W przypadku gdy wystąpi przerwanie o wyższym priorytecie w trakcie obsługi przerwania o niższym priorytecie, w pierwszej kolejności następuje obsługa przerwania o priorytecie wyższym.

Co może być na początku mylące, wyższa wartość numeru priorytetu oznacza niższy priorytet wykonania np. przerwanie o priorytecie “0” będzie wykonane wcześniej niż o priorytecie “1”. Wchodząc w szczegóły warto też wiedzieć, że w przypadku, gdy wystąpią dwa przerwania o tym samym priorytecie jednocześnie, pierwsze obsłużone będzie te o niższym adresie w tablicy wektorów.

Lista wektorów przerwań dostępna dla danego mikrokontrolera jest umieszczona w tabeli wektorów przechowywanej pod adresem bazowym w NVIC. Taką listę w przypadku projektu w STM32CubeIDE znajdziemy w pliku startup_stm32g071rbtx.s (oczywiście lista ta zostało stworzona w oparciu o dokumentację).

Kontroler NVIC w przypadku mikrokontrolera STM32G071 oferuje 32 kanały przerwań i 4 poziomy priorytetów. Trzy przerwania systemowe – Reset, NMI_Handler oraz HardFault_Handler – mają na stałe przypisane najwyższy poziom priorytetu, który nie może być zmodyfikowany. Priorytety pozostałych przerwań systemowych, w tym SVC_Handler, PendSV_Handler oraz SysTick_Handler, mogą być modyfikowane przez użytkownika.

Ze względu na to, że kontroler NVIC jest elementem rdzenia ARM, informacje szczegółowe na jego temat znajdziemy w dokumentacji “Programming manual”.

Kontroler EXTI

Kontroler EXTI pozwala na obsługę zdarzeń występujących na wyprowadzeniach mikrokontrolera oraz wybudzanie procesora z trybu Stop. Każdy kanał może zostać skonfigurowany tak, aby reagował na zbocze narastające (rising), opadając (falling) lub oba zbocza (rising and falling).

Sterownik EXTI może przechwytywać impulsy krótsze niż okres wewnętrznego zegara. Rejestr w kontrolerze EXTI zatrzaskuje każde zdarzenie nawet w trybie Stop, co pozwala oprogramowaniu zidentyfikować pochodzenie wybudzenia procesora z trybu Stop lub zidentyfikować GPIO i zdarzenie zbocza, które spowodowało przerwanie. Zdarzenie (event) nie wywołuje programu obsługi – może spowodować reakcję np. wybudzenie procesora.

Blok kontrolera EXTI przedstawiony jest na poniższych schemacie.

EXTI taktowany jest zegarem z szyny AHB. Blok Event Trigger odpowiada za obsługę logiki zboczy wywołujących zdarzenie. Masking odpowiada za odpowiednie sterowanie wyjściami przerwań, wakeup i zdarzeń wychodzących z kontrolera EXTI, a EXTImux odpowiada za wybór portu dla sygnału przerwania.

Multiplekser EXTI umożliwia wybór portu GPIO, który będzie powodował wystąpienie zdarzenia. Oznacza to, że jednocześnie możemy otrzymywać przerwania tylko od jednego portu na danym numerze pinu – jeżeli korzystamy z przerwań na pinie PA0, to nie możemy już wykorzystać przerwań na pinie PB0, PC0 itd.

Rejestry EXTI

W przypadku kontrolera EXTI mamy do skonfigurowania rejestry odpowiedzialne za rodzaj zbocza powodującego przerwanie, wybór portu na danym kanale EXTI, włączenie przerwania oraz odczyt i czyszczenie flagi przerwania.

W każdym z rejestrów mamy do dyspozycji więcej niż 16 bitów (porty GPIO mają 16 pinów). Wynika to z tego, że EXTI może reagować także na zdarzenia z innych interfejsów np. USART, I2C w celu wybudzenia mikrokontrolera. Za konfigurację wejść GPIO odpowiadają linie od 0 do 15.

EXTI_RTSR1 – wybór zbocza narastającego

EXTI_FTSR1 – wybór zbocza opadającego

EXTI_SWIER1 – programowe wywołanie wystąpienia przerwania

EXTI_RPR1 – rejestr odpowiedzialny za odczytanie lub kasowanie flagi przerwania od zbocza narastającego

EXTI_FPR1 – rejestr odpowiedzialny za odczytanie lub kasowanie flagi przerwania od zbocza opadającego

EXTI_EXTICRx – rejestr pozwalający na wybór, z którego portu będzie przekierowane przerwanie na danej linii 

EXTI_IMR1 – włączenie przerwania

[PROGRAM] Konfiguracja i sterowanie wejściem EXTI

Znamy już rejestry kontrolera EXTI i NVIC. Czas na konfigurację i obsługę przerwania od przycisku na pinie PC13. Na początek konfigurujemy pin jako wejście tak samo jak do tej pory.

LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);

LL_GPIO_SetPinPull(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(USER_BUTTON_GPIO_Port, USER_BUTTON_Pin, LL_GPIO_MODE_INPUT);

Teraz przejdziemy do konfiguracji EXTI. Wybieramy źródło, czyli port i pin, który chcemy obsługiwać.

LL_EXTI_SetEXTISource(LL_EXTI_CONFIG_PORTC, LL_EXTI_CONFIG_LINE13);

Następnie wybieramy zbocze i aktywujemy reakcję na nie. My ustawimy zbocze opadające, co spowoduje wygenerowanie przerwania przy wciśnięciu przycisku (przycisk zwiera do masy).

LL_EXTI_EnableFallingTrig_0_31(LL_EXTI_LINE_13);

Włączamy jeszcze przerwanie na linii 13.

LL_EXTI_EnableIT_0_31(LL_EXTI_LINE_13);

Pozostało skonfigurować kontroler NVIC. Ustawiamy priorytet oraz włączamy przerwanie na EXTI4_15_IRQn.

NVIC_SetPriority(EXTI4_15_IRQn, 0);
NVIC_EnableIRQ(EXTI4_15_IRQn);

Konfiguracja mamy gotową. Do obsługi przerwania potrzebujemy zaimplementować funkcję EXTI4_15_IRQHandler(), którą umieszczamy w pliku stm32g0xx_it.c. Sprawdzamy w niej źródło przerwania (czy jest to linia 13) i kasujemy flagę. Potem dorzucamy kod programu, jaki ma się wykonać po wystąpieniu przerwania. 

void EXTI4_15_IRQHandler(void)
{
	if(LL_EXTI_IsActiveFallingFlag_0_31(LL_EXTI_LINE_13) != RESET)
	{
		LL_EXTI_ClearFallingFlag_0_31(LL_EXTI_LINE_13);
	}
}

Przed skompilowaniem programu musimy jeszcze dołączyć bibliotekę EXTI za pomocą dyrektywy include.

#include "stm32g0xx_ll_exti.h"

Funkcje alternatywne

Każdy z pinów może być ustawiony w trybie funkcji alternatywnej. Wykorzystywany jest wówczas do obsługi jednego z układów peryferyjnych dostępnych na danych wyprowadzeniu np. UART, SPI, I2C, USB, CAN itd.

W trybie funkcji alternatywnych:

  • Wyjście może być skonfigurowane jako push-pull lub open drain
  • Wyjście jest sterowane przez określony układ peryferyjny
  • Przerzutnik Schmitta jest aktywny
  • Można aktywować rezystory podciągające pull-up lub pull-down

Do ustawienia danej funkcji alternatywnej używane są dwa rejestry: GPIOx_AFRH oraz GPIOx_AFRL. Dokładne informacje, co dla każdego z pinów oznacza AF0-AF7 znajdziemy w dokumentacji “Datasheet” mikrokontrolera.

Tryb analogowy

Niektóre wyprowadzenia GPIO mogą być skonfigurowane w trybie analogowym. Są wówczas używane do takich zastosowań, jak przetwornik ADC, DAC wzmacniacz operacyjny OPAMP lub komparator COMP (wewnętrzne układy peryferyjne mikrokontrolera). W trybie analogowym wyjście (output buffer) oraz przerzutnik Schmitta są nieaktywne. Nie ma możliwości skonfigurowania rezystorów pull-up oraz pull-down. Ze względu na to, że w trybie analogowym przerzutnik Schmitta jest także nieaktywny, wejście nie będzie pobierało prądu. Wykorzystywane jest to do oszczędzania zużycia energii w aplikacjach Low Power.

Chciałbyś otrzymywać na bieżąco informacje o nowych artykułach z kursu? Zapisz się do newslettera!

TO NIE TYLKO MAIL Z INFORMACJĄ O NOWEJ LEKCJI, ALE TAKŻE DODATKOWE MATERIAŁY. NIE PRZEGAP NOWEJ TREŚCI I DODATKOWYCH BONUSÓW. PRZEJDŹ DO STRONY KURSU I PODAJ SWÓJ ADRES E-MAIL. NIE ZAPOMNIJ POTWIERDZIĆ CHĘCI DOŁĄCZENIA W PIERWSZEJ WIADOMOŚCI!
Repozytorium GitHub

  1. Dodam jeszcze od siebie, że inne płytki nucleo np te oparte o rdzeń L4 mają opcję włączania przerwań EXTI w rejestrze syscfg. Stąd też funkcja włączająca przerwania EXTI na mojej płytce L4 wygląda tak:
    LL_SYSCFG_SetEXTISource(LL_SYSCFG_EXTI_PORTC, LL_SYSCFG_EXTI_LINE13);
    jest to również inny header, stm32l4xx_ll_system.h. Może komuś się przyda 🙂

    1. Każda rodzina trochę się różni od siebie. Wraz z rozwojem układów producent wprowadza nowe, zazwyczaj lepsze rozwiązania, ale wiąże się to z tym, że trzeba przeważnie wprowadzać zmiany, jeśli korzystamy z innej serii.

      1. Brakuje też informacji o potrzebie dodania biblioteki #include „stm32g0xx_ll_exti.h” do main.h

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *