Funkcje API w obsłudze GPIO

Operowanie bezpośrednio na rejestrach 32–bitowego procesora lub mikrokontrolera nie należy do zadań prostych. Mimo, iż samo napisanie (stosunkowo zaawansowanych) aplikacji przy użyciu nazw rejestrów jest możliwe, to wprowadzanie zmian do istniejącego kodu po upływie na przykład kilku miesięcy, dodatkowo przez osobę, która nie jest autorem programu, jest w zasadzie niemożliwe do wykonania w sensownym czasie. Z tego powodu do takich operacji programiści wykorzystują funkcje o mniej lub bardziej kojarzących się nazwach. Jeżeli mamy do czynienia z bardzo skomplikowanym projektem wykorzystującym wiele peryferiów mikrokontrolera, to napisanie stosownych funkcji jest pracochłonne i wymaga dobrej znajomości architektury mikrokontrolera.

Firma STMicroelectronics zauważyła ten problem i udostępnia kompletne biblioteki API, które pozwalają w pełni kontrolować MCU. W pewnych przypadkach może oczywiście zajść potrzeba bezpośredniego odwołania się do rejestru, we wszystkich pozostałych funkcje API znacznie skracają czas potrzebny na napisanie i uruchomienie aplikacji.

Porty wejścia/wyjścia
Porty we/wy mikrokontrolerów STM32 mogą pełnić do ośmiu funkcji. Oprócz pracy jako alternatywne wejście lub wyjście, określone wyprowadzenie może być skonfigurowane jako: wejście „pływające”, pull–up, pull–down, lub jako wejście analogowe. W konfiguracji wyjścia wyprowadzenie może pracować w konfiguracji z otwartym drenem lub push–pull. Uproszczoną budowę portu GPIO przedstawiono na rys. 1.

Rys. 1. Schemat blokowy linii GPIO w mikrokontrolerach STM32

Sposób konfigurowania i obsługiwania GPIO (General Purpose Input Output) wyjaśnimy na niezbyt wyrafinowanym przykładzie, jednak dzięki temu będzie on przejrzysty i czytelny.
Układ ma odzwierciedlać stany swoich wejść na wyjściach, innymi słowy diody sygnalizacyjne mają się zapalać w takt zmian poziomu logicznego na wejściach. Jest to zrealizowane przy pomocy joysticka znajdującego się na płycie ewaluacyjnej. Biorąc po uwagę budowę płyty uruchomieniowej, wejścia z podłączonym joystickiem należy skonfigurować jako „pływające” (input floating), natomiast wyjścia jako push–pull. Praca wyjść w konfiguracji push–pull oznacza, że dzięki odpowiedniemu podłączeniu wewnętrznych tranzystorów MOS, ustawienie wyjścia w stan logicznej „1” spowoduje pojawienie się na końcówce układu napięcia zasilania, natomiast ustawienie w programie logicznego „0” będzie skutkowało podaniu na wyprowadzenie układu potencjału masy.
By dobrze wykorzystać możliwości portów we/wy, w pierwszej kolejności należy je odpowiednio do określonego zadania skonfigurować. Najpierw jednak zostanie przedstawiony mechanizm, jaki wykorzystują do pracy funkcje API.
Dla każdego urządzenia, czy jest to GPIO, kontroler przerwań, czy jakikolwiek inny element systemu, są stworzone odrębne typy danych. W przypadku portów we/wy nazywają się one GPIO_TypeDef, zaś do inicjacji jest wykorzystywany typ GPIO_InitTypeDef. Z punktu widzenia programisty największe znaczenie ma typ inicjujący, ponieważ to właśnie zmienną tego typu jawnie tworzymy w pisanym kodzie. Typ GPIO_TypeDef zapewnia dostęp do poszczególnych rejestrów mikrokontrolera i jest wykorzystywany przede wszystkim przez funkcje API, natomiast zmienna typu GPIO_InitTypeDef musi istnieć w każdej aplikacji wykorzystującej porty we/wy, ponieważ jest wykorzystywana do inicjalizowania i konfigurowania portów. Na list. 1 przedstawiono kluczowy fragment kodu odpowiedzialny za konfigurację portów oraz operacje na nich.

List. 1. Fragment programu odpowiedzialny za konfigurację portów oraz operacje na nich

 

Utworzona na początku zmienna GPIO_InitStruct jest, de facto, strukturą. Inicjowanie pinów lub w szczególnym przypadku całego portu odbywa się w ten sposób, że wypełnia się poszczególne pola struktury, a następnie przekazuje tak przygotowaną zmienną przez referencję do funkcji inicjującej. W przedstawianej sytuacji, w naszym kręgu zainteresowań leżą trzy pola struktury GPIO_InitStruct. W pierwszej kolejności ustalamy, które z pinów będą konfigurowane, następnie wybieramy żądany tryb pracy, w tym przypadku będzie to wyjście push-pull lub wejście pływające (input floating). Następnie ustalamy maksymalną prędkość, z jaką będą mogły pracować wyprowadzeni układu. Tak przygotowaną zmienną należy przekazać poprzez referencję w argumencie do funkcji inicjującej GPIO_Init, podając przy tym również, do jakiego portu mają być zastosowane wybrane ustawienia. Odczytywanie stanu wyprowadzenia, dzięki zdefiniowanym przez firmę STMicroelectronics bibliotekom jest bardzo proste, gdyż podajemy jedynie nazwę konkretnego portu oraz pinu – instrukcje tego typu zawiera nieskończona pętla while(1) z list. 1.
Producent mikrokontrolerów STM32 zaleca, aby wszystkie nieużywane wyprowadzenia były skonfigurowane jako analogowe wejścia, czego konsekwencją jest mniejsze zużycie energii oraz większa odporność na EMI. O ile w mniejszych jednostkach 8-bitowych miało to mniejsze znaczenie, to należy pamiętać, że tutaj mikrokontroler pracuje z dużo większą częstotliwością, ponadto znaczna część rodziny STM32 posiada relatywnie dużą liczbę wyprowadzeń, zatem takie ich ustawienie ma istotne znaczenie dla optymalizacji pracy systemu.

Funkcje alternatywne i „remapping”
W niektórych przypadkach fizyczne rozmieszczenie elementów na docelowej płytce drukowanej nie pozwala na podłączenie urządzenia, bądź elementu zewnętrznego do wyprowadzenia, które jest domyślnie powiązane z interesującą projektanta alternatywną jego funkcją. W takich sytuacjach na ratunek przychodzi możliwość „przemapowania” (remapping) GPIO. Jeśli więc przypisanie danej funkcji alternatywnej, np. portu USART nie odpowiada potrzebom projektowanej aplikacji, to można ową funkcję przepisać do innego, bardziej odpowiedniego dla aktualnego zastosowania wyprowadzenia. Takie zabiegi mogą być przeprowadzane tylko dla ściśle określonych wyprowadzeń, co jest szczegółowo podane w nocie katalogowej każdego mikrokontrolera z rodziny STM32.

List. 2. Przykładowe przemapowanie pinów związanych funkcjonalnie z Timerem 3

Tab. 1. Możliwe sposoby przypisania linii Timera 3 do GPIO

Funkcja Domyślnie Częściowy remapping Całkowity remapping
TIM3_CH1 PA6 PB4 PC6
TIM3_CH2 PA7 PB5 PC7
TIM3_CH3 PB0   PC8
TIM3_CH4 PB1   PC9

Przykłady możliwości zmiany funkcji różnych pinów dla czterech kanałów Timera 3 są przedstawione w tab. 1, natomiast jedno z możliwych rozwiązań programowych przedstawiono na list. 2. Do zmiany przypisania domyślnego funkcji alternatywnej służy funkcja GPIO_PinRemapConfig. Informacje, które należy przekazać funkcji to określenie interesujących nas peryferiów, podanie czy przemapowanie ma być tylko częściowe, czy całkowite oraz czy włączamy, czy wyłączamy remapping. Przedstawiony fragment kodu sprawi, że Timer 3 będzie sterował wyjściami od GPIO_Pin_6 do GPIO_Pin_9. Należy oczywiście pamiętać o włączeniu taktowania dla funkcji alternatywnej i samego portu (tutaj będzie to port GPIOC) w bloku konfiguracji sygnałów zegarowych i zerowania – funkcja RCC_Conf.

Krzysztof Paprocki
paprocki.krzysztof@gmail.com

Autor: