LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

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.

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

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

GPIO_InitTypeDef GPIO_InitStruct;

int main(void)
{
  RCC_Conf();   
  NVIC_Conf();

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | 
               GPIO_Pin_8 | GPIO_Pin_9;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIO_InitStruct);

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);

  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_12 | 
               GPIO_Pin_14;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &GPIO_InitStruct);    

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

  GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
  GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOE, &GPIO_InitStruct);

  while (1)
  {
    if(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_14))
        GPIO_ResetBits(GPIOC, GPIO_Pin_6);
    else 
        GPIO_SetBits(GPIOC, GPIO_Pin_6);

    if(GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_8))
        GPIO_ResetBits(GPIOC, GPIO_Pin_9);
    else 
        GPIO_SetBits(GPIOC, GPIO_Pin_9);

    if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_1))
        GPIO_ResetBits(GPIOC, GPIO_Pin_8);
    else 
        GPIO_SetBits(GPIOC, GPIO_Pin_8);

    if(GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_0))
        GPIO_ResetBits(GPIOC, GPIO_Pin_7);
    else 
        GPIO_SetBits(GPIOC, GPIO_Pin_7);
  }
}

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.