Niedawno rozpocząłem przygodę z mikrokontrolerami z rdzeniem Cortex M3. W związku z tymn na początek, chciałbym przedstawić sposób obsługi portów wejścia wyjścia tego układu.
Programy zamieszczone w tym poście zostały przetestowane na płytce Nucleo F103RB.
Podstawową rzeczą od jakiej należy zacząć programowanie linii GPIO jest ich odpowiednia inicjalizacja. W tym mikrokontrolerze każdy pin może zostać zdeklarowany jako wyjście typu Push Pull lub jako Open Drain. W przypadku deklaracji pinu wejściowego możliwe jest ustawienie rezystora podciągającego (Pull Up lub Pull Down) lub całkowite jego wyłączenie. Kolejną możliwością jest deklaracja pinu jako wejście alternatywne (Alternate Function). Ta opcja stosowana jest w przypadku stosowania interfejsów komunikacyjnych takich jak np. SPI.
Ostatnim elementem jest częstotliwość działania GPIO. Od tego parametru zależy jak często będzie możliwa zmiana stanu na linii. Im częściej taki stan będzie zmieniany, tym większy będzie poziom emitowanych zakłóceń elektromagnetycznych (EMC).
Szybkość zmian sygnału określa także czasy narastania i opadania sygnału. Możliwe jest ustawienie wartości 50MHz, 10MHz oraz 2MHz. Dla nich te czasy będą wynosić odpowiednio 5ns, 25ns oraz 125ns.
Szczegółowe dane dotyczące pojemności obciążenia oraz wszystkich wartości zostały zawarte w nocie katalogowej mikrokontrolera (Datasheet). Tabelkę z tymi wartościami przedstawiłem na rysunku 1.1.
Korzystając z biblioteki można ustawić następujące deklarację wraz z parametrami:
Inicjalizacja
Podstawową rzeczą od jakiej należy zacząć programowanie linii GPIO jest ich odpowiednia inicjalizacja. W tym mikrokontrolerze każdy pin może zostać zdeklarowany jako wyjście typu Push Pull lub jako Open Drain. W przypadku deklaracji pinu wejściowego możliwe jest ustawienie rezystora podciągającego (Pull Up lub Pull Down) lub całkowite jego wyłączenie. Kolejną możliwością jest deklaracja pinu jako wejście alternatywne (Alternate Function). Ta opcja stosowana jest w przypadku stosowania interfejsów komunikacyjnych takich jak np. SPI.
Ostatnim elementem jest częstotliwość działania GPIO. Od tego parametru zależy jak często będzie możliwa zmiana stanu na linii. Im częściej taki stan będzie zmieniany, tym większy będzie poziom emitowanych zakłóceń elektromagnetycznych (EMC).
Szybkość zmian sygnału określa także czasy narastania i opadania sygnału. Możliwe jest ustawienie wartości 50MHz, 10MHz oraz 2MHz. Dla nich te czasy będą wynosić odpowiednio 5ns, 25ns oraz 125ns.
Szczegółowe dane dotyczące pojemności obciążenia oraz wszystkich wartości zostały zawarte w nocie katalogowej mikrokontrolera (Datasheet). Tabelkę z tymi wartościami przedstawiłem na rysunku 1.1.
Rys. 1.1. Parametry ustawień szybkości taktowania linii
Korzystając z biblioteki można ustawić następujące deklarację wraz z parametrami:
- GPIO_Pin - pozwala na wybranie wykorzystywanego pinu. Do wyboru są piny od 1 do 15 na każdej z dostępnych linii.
- GPIO_Speed - Określa maksymalną częstotliwość zmian stanu na linii;
- GPIO_Speed_2MHz;
- GPIO_Speed_10MHz;
- GPIO_Speed_50MHz;
- GPIO_Mode - pozwala na wybranie trybu pracy linii.
- Wyjście:
- GPIO_Mode_Out_PP;
- GPIO_Mode_Out_OD;
- Wejście;
- GPIO_Mode_AIN;
- GPIO_Mode_IN_FLOATING;
- GPIO_Mode_IPU;
- GPIO_Mode_IPD;
- Wejście funkcji alternatywnej:
- GPIO_Mode_AF_OD;
- GPIO_Mode_AF_PP;
Wszystkie linie które nie są wykorzystywane należy wyłączyć bądź skonfigurować jako wejścia analogowe. Pozwala to na zmniejszenie ilości zakłóceń zewnętrznych oraz zmniejszenie poboru mocy całego układu. Jeśli nie ma takiej możliwości należy ustawić wejścia z rezystorami podciągającymi lub dodać taki rezystor zewnętrzny. Linie nie mogą być zwarte do masy ani do zasilania, ponieważ przez to możliwe jest nieodwracalne uszkodzenie układu [1].
Do linii wejściowej typu push pull podłączone są dwa tranzystory typu PMOS i NMOS. Gdy na wejście określonej linii podawany jest stan wysoki to PMOS zostaje otwarty, natomiast NMOS zamknięty. Podanie stanu niskiego powoduje odwrotne działanie tych tranzystorów. W pierwszym przypadku prąd przepływa od zewnętrznego źródła zasilania do masy. Drugi przypadek powoduje podawanie napięcia VDD np. na diodę podłączoną do tego pinu, co uniemożliwi przepływ prądu.
Do linii wejściowej typu push pull podłączone są dwa tranzystory typu PMOS i NMOS. Gdy na wejście określonej linii podawany jest stan wysoki to PMOS zostaje otwarty, natomiast NMOS zamknięty. Podanie stanu niskiego powoduje odwrotne działanie tych tranzystorów. W pierwszym przypadku prąd przepływa od zewnętrznego źródła zasilania do masy. Drugi przypadek powoduje podawanie napięcia VDD np. na diodę podłączoną do tego pinu, co uniemożliwi przepływ prądu.
Program 1 - mruganie diodą wbudowaną
Standardowo jako pierwszy program wykonane zostanie mruganie diodą wbudowaną podłączoną do pinu PA5.
W pierwszych liniach programu dołączono wymaganą bibliotekę. Następnie zostały wpisane deklaracje wykorzystywanego portu, pinu oraz zegara. Taki sposób zapisu ułatwia programowanie, ponieważ aby zmienić jeden parametr wystarczy go zmienić w tej linijce.
Poniżej nich znajdują się wstępne deklaracje funkcji opóźniającej oraz funkcji deklarującej ustawienie wykorzystywanego pinu.
W programie głównym, w pętli, następuje zapalenie oraz zgaszenie diody.
#include "stm32f10x.h" #define Pin GPIO_Pin_5 #define Line GPIOA #define Clock RCC_APB2Periph_GPIOA void Delay(int); void GpioInit(void); int main(void) { GpioInit(); while (1) { //Zapalenie diody podłączonej do PA5 GPIO_SetBits(Line, Pin); Delay(500); //Zgaszenie diody podłączonej do PA5 GPIO_ResetBits(Line, Pin); Delay(800); } } void GpioInit(void) { //inicjalizacja obiektu GPIO GPIO_InitTypeDef GpioInit; //Włączenie zegara RCC_APB2PeriphClockCmd(Clock, ENABLE); GPIO_StructInit(&GpioInit); //Wybranie konfigurowanego pinu GpioInit.GPIO_Pin = Pin; //Ustawienie pinu jak wyjście PushPull GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; //Inicjalizacja linii z podanymi ustawieniami GPIO_Init(Line, &GpioInit); } void Delay(int time) { int i; for (i = 0; i < time * 5000; i++) { } }
W tym programie wykorzystałem podstawową ale niedokładną metodę deklaracji funkcji opóźniającej. Znacznie lepszym rozwiązaniem jest posłużenie się wbudowanym timerem SysTick, który mierzy czas z bardzo dużą dokładnością. Zostanie on prze zemnie opisany w kolejnym poście.
Program 2 - Obsługa wbudowanego przycisku
Program ten działa podobnie do poprzedniego, tylko tym razem dołożony został wbudowany przycisk. Został on podłączony do pinu PC13.
Program różni się od poprzedniego dodatkową deklaracją pinu do którego został podłączony przycisk,
#include "stm32f10x.h" #define PinDioda GPIO_Pin_5 #define PinPrzycisk GPIO_Pin_13 #define LineDioda GPIOA #define LinePrzycisk GPIOC #define ClockGPIOA RCC_APB2Periph_GPIOA #define ClockGPIOC RCC_APB2Periph_GPIOC void Delay(int); void GpioInit(void); int main(void) { GpioInit(); while (1) { //Sprawdzenie czy przycisk został wciśnięty if (GPIO_ReadInputDataBit(LinePrzycisk, PinPrzycisk) == 0) { //Jeśli tak to zapal diode GPIO_SetBits(LineDioda, PinDioda); } else { //Jeśli nie to zgaś GPIO_ResetBits(LineDioda, PinDioda); } } } void GpioInit(void) { //inicjalizacja obiektu GPIO GPIO_InitTypeDef GpioInit; //Włączenie zegara dla linii GPIOA RCC_APB2PeriphClockCmd(ClockGPIOA, ENABLE); //Włączenie zegara dla linii GPIOC RCC_APB2PeriphClockCmd(ClockGPIOC, ENABLE); //Inicjalizacja pinu Dioda GPIO_StructInit(&GpioInit); //Wybranie konfigurowanego pinu GpioInit.GPIO_Pin = PinDioda; //Ustawienie pinu jak wyjście PushPull GpioInit.GPIO_Mode = GPIO_Mode_Out_PP; //Inicjalizacja linii z podanymi ustawieniami GPIO_Init(LineDioda, &GpioInit); //Inicjalizacja pinu Przycisk GpioInit.GPIO_Pin = PinPrzycisk; //Tryb jako wejscie z rezystorem podciągającym PullUp GpioInit.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(LinePrzycisk, &GpioInit); }
Bibliografia
[1] Galewski M. "STM32 Aplikacje i ćwiczenia w języku C"