fbpx

Miganie diodą na STM32, czyli GPIO Output

Ostatnio dowiedzieliśmy się jak postawić projekt na STM32 pod pisanie na rejestrach. Tym razem pokażę Ci jak zamigać diodą 😎

W dzisiejszym wpisie przejdziemy konfigurację GPIO Output oraz nauczymy się kontrolować wyjście pojedynczego pinu GPIO. Do ćwiczeń posłuży nam wbudowana dioda LD4 znajdująca się na NUCLEO-C031C6.

Seria STM32 na Rejestrach na YouTube

Wpisy te powstają równolegle do serii na moim YouTube o tej samej tematyce. Jeśli wolisz wersję video to zapraszam Cię właśnie tam. Artykuły te są skrótem z tego, co pokazuję na YouTube.

Link do Playlisty Youtube

Konfiguracja GPIO Output w STM32

Prace rozpoczynam od pustego projektu, który utworzyłem w poprzednim artykule. Najpierw musimy się dowiedzieć, na którym pinie mikrokontrolera znajduje się dioda LED na płytce NUCLEO-C031C6. Najłatwiej będzie tutaj skorzystać ze schematu. Schemat znajdziesz na stronie Nucleo w zakładce CAD Resources. Link do naszego schematu.

To, co nas interesuje jest na stronie numer 4 i wygląda następująco:

Dioda LD4 podłączona jest przez MOSFET-N do pinu PA5 na porcie GPIOA. Oznacza to, że zapalenie diody wymusi podanie wysokiego stanu na bramkę tranzystora, zgaszenie – stanu niskiego.

Zegary taktujące peryferium

STM32 są zbudowane w taki sposób, że do każdego z peryferiów trzeba oddzielnie doprowadzić zegar taktujący. Każdy port GPIO z kolei ma oddzielne taktowanie.

Do kontroli zegarów służy blok RCC (Reset and Clock Control) i to w nim musimy włączyć taktowanie. W naszym wypadku dla GPIOA. Szczegółowy sposób pracy z dokumentacją pokazuję na YouTube. Tutaj w artykule będę wklejał gotowy kod, który należy wpisać, aby uzyskać efekt.

Aby włączyć zegar dla GPIOA w STM32C031C6T6 należy napisać

// Enable Clock for PORTA
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;

UWAGA! Inne STM32 mogą mieć bity włączające zegary GPIO w innych rejestrach RCC. Musisz za każdym razem się upewniać w Reference Manualu!

Konfiguracja pinu

Gdy już mamy taktowanie na GPIOA to możemy się brać za jego konfigurację. Wcześniej peryferium to było nieaktywne i nic byśmy nie zrobili. Co musimy ustawić?

Interesuje nas kilka rejestrów:

  • MODER – tutaj ustawiamy tryb w jakim znajdują się konkretne piny GPIO. Tutaj ustalamy czy dany pin jest Input / Output / Alternate Function / Analog Input
  • OTYPER – Typ wyjścia. Wskazujemy tu czy chcemy skorzystać z wyjścia typu push-pull, czy open-drain
  • OSPEEDR – Prędkość narastania zbocza na wyjściu. Parametr ten mówi o tym jak strome ma być zbocze przy zmianie stanu. Przydatne przy minimalizacji i optymalizacji różnych zakłóceń
  • PUPDR – rejestr, w którym podłączamy (lub nie) wbudowane w mikrokontroler rezystory pull-up i pull-down. Wartość rejestrów jest różna dla różnych rodzin i musisz to sprawdzać w Datasheecie.

Nasza dioda jest na PA5. Interesuje więc nas zestaw tych rejestrów dla GPIOA oraz ustawienia z numerem 5. Czasem będą to pojedyncze bity, czasem kilka. Co musimy ustawić?

Zwróć uwagę na jedną rzecz, która jest w Reference Manualu przy każdym rejestrze.

Wszystkie rejestry mikrokontrolera po resecie mają ustawioną konkretną, z góry założoną wartość. Oznacza to, że nie wszystkie rejestry będziesz musiał ustawiać przy początkowej konfiguracji zaraz po starcie mikrokontrolera.

Póki co będziemy ustawiać wszystko. Tak, aby pokazać Ci pełną konfigurację i nie zostawić niedomówień.

MODER – tutaj ustawiamy GPIO Output, czyli wartość 0b01.

// Configure GPIO Mode - Output
GPIOA->MODER |= GPIO_MODER_MODE5_0;
GPIOA->MODER &= ~(GPIO_MODER_MODE5_1);

OTYPER – Diodami LED, które katodą są bezpośrednio podłączone do pinu GPIO trzeba sterować trybem push-pull. Tutaj mamy MOSFET, który w dodatku ma jeszcze jakiś pull-up niwelujący stany nieustalone i ładujący bramkę. Zróbmy tutaj push-pull. Nie ma to aż tak dużego znaczenia co wybierzemy, bo wbudowany rezystor pull-up i tak by przeważył ten sprzętowy.

Push-pull jest już ustawiony po resecie, więc jest to ustawienie nadmiarowe.

// Configure Output Mode - Push-pull
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT5);

OSPEEDR – Prędkość narastania warto trzymać jak najniżej. Niekiedy się na da, bo na GPIO potrzebujemy napisać szybki niestandardowy interfejs. Miganie diodą LED jest wolne, więc możemy ustawić najniższy z możliwych, czyli Low. Jest on domyślnie ustawiony po resecie.

// Configure GPIO Speed - Low
GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED5);

PUPDR – Korzystamy z wyjścia typu push-pull. Ustawienie wbudowanych pull-upów nie ma większego sensu. Musimy je wyłączyć, co w zasadzie jest stanem domyślnym po resecie.

// Configure Pull-up/Pull-down - no PU/PD
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5);

Na koniec warto całą konfigurację diody LED zamknąć w wygodnej funkcji, która nazwą mówi do czego ona służy.

void ConfigureLD4(void)
{
	// Enable Clock for PORTA
	RCC->IOPENR |= RCC_IOPENR_GPIOAEN;

	// Configure GPIO Mode - Output
	GPIOA->MODER |= GPIO_MODER_MODE5_0; // It's default reset state. Not necessary.
	GPIOA->MODER &= ~(GPIO_MODER_MODE5_1);

	// Configure Output Mode - Push-pull
	GPIOA->OTYPER &= ~(GPIO_OTYPER_OT5); // It's default reset state. Not necessary.

	// Configure GPIO Speed - Low
	GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED5); // Two bits together. It's default reset state. Not necessary.

	// Configure Pull-up/Pull-down - no PU/PD
	GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5); // It's default reset state. Not necessary.
}

Kontrola wyjścia GPIO

Do kontrolowania tego, co znajduje się na wyjściu GPIO Output mamy dwie (a nawet trzy!) metody.

Pierwsza jest “zwykła” przez rejestr ODR (Output Data Register). Na pozycję naszego pinu wpisujemy stan, który nas interesuje. Tutaj musimy pamiętać o tym, aby nie zmienić stanu pozostałych wyjść. Przez to ma to wadę w postaci złożoności. Musimy wykonać kilka operacji (odczyt rejestru, zmianę jednego bitu, zapis powrotny), aby osiągnąć zmianę jednego bitu.

Drugim sposobem jest dostęp atomowy, czyli wykonujący się w jednej operacji (sam zapis). Metoda ta bazuje na specjalnym rejestrze BSRR. Reaguje on tylko na wpisanie jedynki, więc bity które mają zero pozostają bez reakcji.

BSRR jest 32-bitowy i dzieli się na pół. Górna połówka odpowiada za reset odpowiedniego pinu, dolna za set. Czyli górnymi 16-bitami ustawiamy 0 na wskazanym ponie, dolnymi ustawiamy 1. Akcje te mają odzwierciedlenie w rejestrze ODR.

Skorzystamy z dostępu atomowego. Jest to o wiele wydajniejsze i skuteczniejsze. Aby ustawić stan wysoki na pinie PA5 musimy do rejestru BSRR wpisać jedynkę na bit oznaczony jako BS5 (Bit Set).

UWAGA. Na YouTube zrobiłem błąd… musimy wpisać jedynie jedynkę na interesujący nas pin. Inne nas nie interesują, a wpisanie zera nie zmienia stanu wyjściowego. Dlatego robimy przypisanie wprost, bez żadnej operacji logicznej. To nam gwarantuje dostęp atomowy 👍

GPIOA->BSRR = GPIO_BSRR_BS5

Chcąc uzyskać stan niski na PA5 wpisujemy do BSRR jedynkę na pozycji bity BR5 (Bit Reset), czyli bit 21 tego rejestru.

GPIOA->BSRR = GPIO_BSRR_BR5

Tak oto możemy kontrolować świecenie diody. Fajnie byłoby jeszcze wyprowadzić wygodne makra, które od razu powiedzą nam co robią.

#define LD4_ON GPIOA->BSRR = GPIO_BSRR_BS5
#define LD4_OFF GPIOA->BSRR = GPIO_BSRR_BR5

Miganie diodą – opóźnienie

Miganie diodą polega na cyklicznym zapalaniu i gaszeniu diody. Mikrokontroler to bardzo szybka bestia. Abyśmy w ogóle zobaczyli to miganie, musimy opóźnić te dwie cykliczne operacje.

Prawdziwym opóźnianiem zajmiemy się w kolejnych lekcjach, natomiast dzisiaj skorzystamy z głupawego opóźnienia na pętlach for.

void Delay(void)
{
	uint32_t i;

	for(i = 0; i < 99999; i++)
	{

	}
}

Ma on wiele wad jak np. to, że nie kontrolujemy czasu opóźnienia w prosty sposób, oraz to, że pętla ta może zostać zoptymalizowana po włączeniu optymalizacji w kompilatorze. Na dzisiaj jednak nam wystarczy 😎

W mainie teraz musimy po pierwsze wywołać funkcję konfigurującą pin diody LD4, a w pętli nieskończonej włożyć zapalanie i gaszenie diody rozdzielone naszym głupawym opóźnieniem. Będzie to wyglądało tak:

int main(void)
{

	ConfigureLD4();

    /* Loop forever */
	while(1)
	{
		// Set LED on PA5
		LD4_ON;
		Delay();

		// Reset LED on PA5
		LD4_OFF;
		Delay();
	}
}

Po skompilowaniu i wgraniu o naszego Nucleo zobaczysz migającą diodę!

Cały kod dla wygodnego przejrzenia:

#include "main.h"

#define LD4_ON GPIOA->BSRR = GPIO_BSRR_BS5
#define LD4_OFF GPIOA->BSRR = GPIO_BSRR_BR5

// PA5 - LD4

void ConfigureLD4(void);
void Delay(void);

int main(void)
{

	ConfigureLD4();

    /* Loop forever */
	while(1)
	{
		// Set LED on PA5
		LD4_ON;
		Delay();

		// Reset LED on PA5
		LD4_OFF;
		Delay();
	}
}

void ConfigureLD4(void)
{
	// Enable Clock for PORTD
	RCC->IOPENR |= RCC_IOPENR_GPIOAEN;

	// Configure GPIO Mode - Output
	GPIOA->MODER |= GPIO_MODER_MODE5_0; // It's default reset state. Not necessary.
	GPIOA->MODER &= ~(GPIO_MODER_MODE5_1);

	// Configure Output Mode - Push-pull
	GPIOA->OTYPER &= ~(GPIO_OTYPER_OT5); // It's default reset state. Not necessary.

	// Configure GPIO Speed - Low
	GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED5); // Two bits together. It's default reset state. Not necessary.

	// Configure Pull-up/Pull-down - no PU/PD
	GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5); // It's default reset state. Not necessary.
}

void Delay(void)
{
	uint32_t i;

	for(i = 0; i < 99999; i++)
	{

	}
}

Podsumowanie

Konfiguracja i korzystanie z GPIO nie są takie trudne jak mogłoby się początkowo wydawać. Sprawia problemy jeśli nie wiemy, co robić. Dlatego powstaje ta seria 🙂 Teraz bez problemu poradzisz sobie z innymi pinami GPIO i to również na innych portach.

W kolejnym wpisie zajmiemy się tym brzydkim opóźnieniem. Zamienimy go na takie kontrolowane z użyciem SysTick Timera. To jeszcze nie będzie najpiękniejsze opóźnienie, ale będzie chociaż kontrolowalne.

Daj znać w komentarzu czy Ci się podobał ten wpis! Może masz jakąś propozycję co pokazać w ramach cyklu STM32 na Rejestrach? Podziel się tym artykułem ze znajomymi.

Zapraszam Cię również do mojego sklepu, gdzie kupisz ciekawą elektronikę do programowania jak np. NUCLEO-C031C6, z którego korzystamy w tej serii: https://sklep.msalamon.pl

Projekt z tego artykułu znajdziesz na: https://github.com/lamik/stm32narejestrach_2

4/5 - (1 vote)

Podobne artykuły

.

2 komentarze

Zegar · 03/08/2023 o 10:51

Doczekałem się. 🙂 Jednak czas nie zawsze szybko ucieka!

    Mateusz Salamon · 03/08/2023 o 11:19

    Ucieka, ucieka 😀 Zaraz trzeba kolejny film przepisać 😀

Dodaj komentarz

Avatar placeholder

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