Kurs STM32L4 – #3 – wejścia/wyjścia, czyli GPIO w praktyce

Kurs STM32L4 – #3 – wejścia/wyjścia, czyli GPIO w praktyce

Kurs STM32L4 zaczniemy rzecz jasna od migania diodą – to już taka tradycja przy poznawaniu nowych mikrokontrolerów.

Oczywiście miganie diodą to tylko jeden z wielu przykładów, dzięki którym poznamy w praktyce jedne z najważniejszych peryferiów, czyli GPIO.

Czego dowiesz się z tej części kursu STM32L4?

W poprzedniej części kursu zainstalowaliśmy środowisko STM32CubeIDE, stworzyliśmy „pusty” projekt i wgraliśmy taki program na mikrokontroler. Teraz przejdziemy do projektów, dzięki którym zapoznamy się z działaniem GPIO. Nie każdy program zadziała od razu – zdarzy się nam specjalnie popełnić błąd, aby za chwilę go „wytknąć” i rozwiązać problem, zupełnie jak podczas normalnego programowania.

Linijka LED, czyli jeden z (pozornie prostych) przykładów z tej części kursu

Linijka LED, czyli jeden z (pozornie prostych) przykładów z tej części kursu

Uniwersalne wejścia/wyjścia – GPIO w praktyce

Miganie diodą jest podczas nauki programowania mikrokontrolerów odpowiednikiem wyświetlenia „Hello World!” na ekranie komputera, od czego zaczyna się chyba większość kursów programowania. Nie inaczej będzie tym razem. Zamigajmy więc diodą! Aby było to możliwe, musimy poznać GPIO (ang. general-purpose input/output), czyli uniwersalne wejścia/wyjścia.

Jak oznaczone są GPIO w STM32?

Do wykonania tego ćwiczenia niezbędny będzie zestaw NUCLEO-L476RG. Zgodnie z informacją, którą  podaliśmy w poprzedniej części kursu, na pokładzie tej płytki znaleźć można m.in. diodę LD2 – jest ona podłączona do pinu PA5. Możemy zatem wykorzystać ją w naszym pierwszym programie.

Dwa przyciski i dioda LD2 na NUCLEO-L476RG

Dwa przyciski i dioda LD2 na NUCLEO-L476RG

Mikrokontroler STM32L476RG ma cztery porty oznaczone literami: A, B, C oraz D. Porty mogą mieć maksymalnie po 16 pinów (numerowanych od zera, czyli np. od PB0 do PB15). Nie wszystkie porty mają jednak wszystkie piny, np. port D udostępnia tylko jeden pin (PD2). Same litery nie mają też żadnego znaczenia – to tylko sposób na uporządkowanie dużej liczby wyprowadzeń. Na przykład litera A w oznaczeniu PA1 nie oznacza, że jest to wejście analogowe (tak jak w przypadku Arduino UNO).

Pierwsze kroki z Device Configuration Tool

Zaczynamy od utworzenia nowego projektu w STM32CubeIDE. Opis całej procedury znajduje się w poprzedniej części kursu, więc nie będziemy tego tutaj powtarzać. Dla przypomnienia, w największym skrócie: STM32CubeIDE > File > New > STM32 Project. Wybieramy układ STM32L476RG i zostawiamy domyślne ustawienia. Po chwili zobaczymy widok zbliżony do poniższego.

Domyślna perspektywa Device Configuration Tool

Domyślna perspektywa Device Configuration Tool

Tym razem zatrzymamy się na chwilę na perspektywie Device Configuration Tool, która jest też często nazywana CubeMX, bo narzędzie widoczne w ramach tej perspektywy jest dostępne również jako oddzielny program – STM32CubeMX. Więcej informacji na jego temat (oraz stosowną dokumentację) można znaleźć na stronie tego projektu.

Omówienie interfejsu CubeMX

Graficzny interfejs użytkownika nie jest skomplikowany, ale czasami osoby odpowiedzialne za rozwój tego narzędzia wprowadzają w nim pewne zmiany. Dlatego najlepiej skupić się na lokalizacjach oraz nazwach „rzeczy”, które trzeba klikać – bez zbędnego przywiązywania wagi do tego, jak one wyglądają.

Główna część okna z graficznym konfiguratorem

Główna część okna z graficznym konfiguratorem

W górnej części okna widoczne są cztery zakładki:

  • Pinout & Configuration – narzędzie do konfiguracji pinów; ta zakładka jest domyślnie wybrana, dlatego widzimy schemat mikrokontrolera oraz jego wyprowadzenia.
  • Clock Configuration – konfiguracja zegarów mikrokontrolera (jest to obszerny temat, któremu poświęcimy w całości osobną część kursu – na razie wystarczą nam domyślne ustawienia).
  • Project Manager – ustawienia samego projektu (na razie korzystamy z ustawień domyślnych).
  • Tools – narzędzie przydatne do szacowania poboru mocy przez mikrokontroler.
Zawartość zakładki, która pozwala na konfigurowanie zegarów

Zawartość zakładki, która pozwala na konfigurowanie zegarów

Wybranie konkretnej zakładki powoduje zmianę pozostałych części interfejsu. Aktualnie interesuje nas pierwsza z nich, czyli Pinout & Configuration, więc przyjrzyjmy się jej dokładniej. W górnej części okna tego widoku znajdziemy dodatkowe menu, które zawiera dwie pozycje:

  • Software Packs – pozwala na instalowanie dodatkowych bibliotek, np. związanych ze sztuczną inteligencją, czujnikami MEMS czy komunikacją bezprzewodową.
  • Pinout – menu zawierające opcje związane z konfiguracją pinów.
Dodatkowe menu wewnątrz zakładki związanej m.in. z GPIO

Dodatkowe menu wewnątrz zakładki związanej m.in. z GPIO

Poniżej po lewej stronie widoczna jest lista dostępnych modułów peryferyjnych (np. System Core, Analog, Timers itd.). Moduły mogą być wyświetlane w podziale na kategorie lub alfabetycznie. Jest też dostępna wyszukiwarka. Zaznaczenie modułu powoduje wyświetlenie dodatkowego okna z opcjami.

Dodatkowe menu po wybraniu konkretnego peryferium

Dodatkowe menu po wybraniu konkretnego peryferium

Centralną część okna zajmuje schemat wyprowadzeń mikrokontrolera. Klikając lewym przyciskiem myszki w wybrany pin, możemy zmienić jego funkcję (zaraz się tym zajmiemy). Z kolei prawy przycisk wywołuje menu z innymi opcjami, dzięki którym można np. przypisać pinowi własną nazwę.

Konfiguracja pinu jako wyjście

Jak już wiemy, do sterowania wbudowaną diodą potrzebujemy pinu PA5. Jeśli nie możemy odnaleźć danej nóżki „ręcznie”, pomocna będzie mała wyszukiwarka, która dostępna jest w dolnej części okna. Wystarczy wpisać PA5 w pole wyszukiwarki, a program podświetli odpowiedni pin.

Po znalezieniu PA5 klikamy w niego lewym przyciskiem myszki. Z rozwijanego menu wybieramy opcję GPIO_Output – taka konfiguracja oznacza wyjście, czyli dokładnie to, czego teraz potrzebujemy. Po tej operacji pin PA5 zostanie odpowiednio oznaczony, abyśmy wiedzieli, że jest już używany.

Oznaczenie wybranego pinu

Oznaczenie wybranego pinu

Nadawanie pinom nazw (etykiet)

Używanie numerów pinów bywa problematyczne, szczególnie jeśli później chcemy zmienić konfigurację sprzętową (zmiana nóżki, do której podłączony jest dany element). Jeśli w programie posługujemy się numerami, to ewentualna zmiana połączeń wymagałaby odnalezienia w kodzie wszystkich fragmentów, które odwoływały się do danego pinu, w celu ich zaktualizowania. Znacznie lepszym rozwiązaniem jest nadanie pinowi nazwy i używanie jej zamiast konkretnego numeru.

W celu nadania nazwy pinowi klikamy w niego prawym przyciskiem myszki i wybieramy opcję Enter User Label. Pojawi się wtedy okienko z małym polem tekstowym, w które wpisujemy nazwę (żadnych spacji, polskich znaków i symboli – trzymamy się prostych nazw alfanumerycznych). W tym przypadku posłużymy się nazwą, która jest nadrukowana wprost na płytce Nucleo, czyli LD2.

Nadawanie etykiety dla wybranego pinu

Nadawanie etykiety dla wybranego pinu

Konfigurację wszystkich pinów możemy sprawdzić (oraz ewentualnie zmienić), wybierając GPIO z listy modułów peryferyjnych mikrokontrolera (po lewej stronie CubeMX). Jeśli używamy widoku z podziałem na kategorie, to GPIO znajdziemy w kategorii System Core. Korzystając z szarych strzałek (prawy górny róg zrzutu) możemy ukrywać graficzny pinout mikrokontrolera, aby maksymalizować widok tabeli.

Menu z podglądem pinów używanych w aktualnym projekcie

Menu z podglądem pinów używanych w aktualnym projekcie

To okno daje nam nieco więcej możliwości konfiguracji, ale na razie wartości domyślne w zupełności wystarczą (później wrócimy do tego menu). Teraz warto tylko upewnić się, że wszystkie ustawienia są zgodne ze zrzutem ekranu z kursu, czyli: GPIO output level = Low, GPIO mode = Output Push Pull, GPIO Pull-up/Pull-down = No pull-up and no pull-down, Maximum output speed = Low, User Label = LD2.

Gotowe zestawy do kursów Forbota

 Komplet elementów  Gwarancja pomocy  Wysyłka w 24h

Zamów zestaw elementów i wykonaj ćwiczenia z tego kursu! W komplecie płytka NUCLEO-L476RG oraz m.in. wyświetlacz graficzny, joystick, enkoder, czujniki (światła, temperatury, wysokości, odległości), pilot IR i wiele innych.

Zamów w Botland.com.pl »

Generowanie kodu

Nasza konfiguracja sprzętowa jest już gotowa, bo wszystkie inne peryferia, które są niezbędne do pracy układu i obsługi GPIO, zostały automatycznie skonfigurowane „w tle”. Nie musimy przejmować się już niczym więcej (oczywiście w dalszych częściach kursu zagłębimy się w konfigurację wielu innych opcji).

Konfiguracja gotowa, więc wystarczy ją zapisać. Wybieramy File > Save (lub klikamy ikonkę z dyskietką). Po uruchomieniu zapisu zostaniemy zapytani o to, czy program ma wygenerować kod – tak, chcemy, aby to zrobił. Przy okazji możemy też zostać zapytani, czy chcemy przełączyć perspektywę na C/C++, na to również wyrażamy zgodę.

Budowa i zawartość projektu 

Zapisanie zmian spowoduje automatyczne wygenerowanie szablonu kodu (może to zająć kilkadziesiąt sekund). Gdy program będzie już gotowy i zostaniemy przełączeni do perspektywy C/C++, warto poświęcić chwilę, aby zapoznać się ze strukturą projektu – zerkamy zatem do eksploratora projektu.

Widoczny jest tam m.in. plik z rozszerzeniem „ioc”. Jest to plik z ustawieniami dla CubeMX – gdy go otworzymy, wrócimy do edycji ustawień mikrokontrolera. Z kolei „standardowy kod źródłowy” znajdziemy w dwóch katalogach: Core oraz Drivers.

Drivers to sterowniki, czyli biblioteki dostarczone przez STMicroelectronics. Znajdziemy tam bibliotekę CMSIS, odpowiedzialną za niskopoziomowy dostęp do rejestrów układu, oraz samą bibliotekę HAL, na której bazuje ten kurs (jest ona dostępna w katalogu STM32L4xx_HAL_Driver).

Nas bardziej interesować będzie zawartość katalogu Core. Znajdziemy w nim trzy podkatalogi:

  • Inc – pliki nagłówkowe,
  • Src – kod źródłowy naszego programu,
  • Startup – plik startowy uruchamiany przed funkcją main.

Gdy rozwiniemy katalog Core oraz znajdujący się w nim podkatalog Src, na liście plików znajdziemy szybko main.c. Nasz program będziemy pisać właśnie w tym pliku, warto więc go otworzyć i zapoznać się z jego strukturą.

Lokalizacja pliku main.c

Lokalizacja pliku main.c

Na samym początku wygenerowanego pliku znajdziemy informację o licencji BSD. Dalej rozpoczyna się już główna część automatycznie generowanego projektu. Raz jeszcze przypominamy, że trzeba uważać na komentarze w postaci USER CODE BEGIN / USER CODE END. Na przykład:

Ogólna zasada jest taka, że to, co znajdzie się między taką parą komentarzy, to nasza sprawa, ale to, co jest poza nimi, kontroluje CubeMX. Trzeba tutaj wyraźnie podkreślić, że podczas zapisu plików CubeMX bez żadnego uprzedzenia usunie wszystkie zmiany, jakie wprowadziliśmy poza tymi komentarzami!

Pisaliśmy o tym już wcześniej, ale powtarzamy kolejny raz, bo stosunkowo łatwo się pomylić i dopisać z rozpędu własny kod w niepoprawnym miejscu. Wszystko będzie działało do momentu dokonania zmian w konfiguracji mikrokontrolera. Gdy zapiszemy zmiany w CubeMX, szablon kodu zostanie ponownie wygenerowany i nasza praca przepadnie bezpowrotnie.

Sekcje oznaczone komentarzami, w których możemy wpisywać swój kod, znajdują się w wielu ważnych miejscach. Rozwiązanie to właściwie nas nie ogranicza, bo praktycznie w każdym kluczowym dla nas miejscu będzie opcja, aby dopisać tam swój kod. Trzeba jednak przyzwyczaić się do tego, aby kod wpisywać pomiędzy wspomnianymi komentarzami.

Fragment kodu, który przed chwilą pokazaliśmy, to konkretnie miejsce na dodawanie naszych plików nagłówkowych, więc gdybyśmy chcieli np. dołączyć plik stdio.h, to zrobilibyśmy to tak:

Dodanie tego pliku w poniższy sposób sprawi, że nasza linijka „wyparuje” podczas najbliższych zmian, które wprowadzimy w CubeMX:

Przeglądając dalej plik main.c, dojdziemy w końcu do funkcji main:

Widzimy w niej wywołania trzech funkcji: HAL_init, SystemClock_Config, MX_GPIO_Init oraz miejsca na pisanie kodu użytkownika. Podczas kursu domyślna inicjalizacja będzie dla nas wystarczająca, więc nie będziemy tutaj nic zmieniać, trzeba jednak pamiętać, że CubeMX będzie dodawał wywołania funkcji konfiguracyjnych dla kolejnych modułów peryferyjnych, gdy będziemy zmieniać konfigurację projektu.

Teraz najważniejsze, czyli pętla główna programu – znajdziemy ją trochę niżej. Widoczne są tam dwa ważne miejsca – przed pętlą główną możemy dodać kod, który będzie wykonany dokładnie raz, natomiast wewnątrz pętli while wpisujemy kod, który będzie wykonywany „w kółko”.

Pora, aby nareszcie napisać własny program. Do migania diodą potrzebne będą nam dwie funkcje – jedna umożliwi zmianę stanu wyjścia na przeciwny, a druga generowanie opóźnień.

Zmiana stanu pinu na przeciwny

Użyliśmy już CubeMX do wygenerowania konfiguracji sprzętowej. Teraz pora, aby wykorzystać kolejny element wsparcia, które dostarcza STMicroelectronics. Mowa o bibliotece HAL, dzięki której tworzenie programów na tak rozbudowane mikrokontrolery jest stosunkowo proste.

Biblioteka ta zawiera ogrom funkcji, dzięki którym możemy sterować dowolnymi peryferiami układu. Teraz może wydawać się to zupełnie normalne (szczególnie dla osób znających Arduino), ale jeszcze całkiem niedawno (~10 lat temu) pisanie programów na mikrokontrolery wiązało się ze studiowaniem długich not katalogowych i operowaniem na rejestrach. Było to mozolne i na pewno mało przyjazne dla osób początkujących (ale to temat na zupełnie inny artykuł).

Wróćmy jednak do meritum. Biblioteka HAL posiada funkcję HAL_GPIO_TogglePin, która zmienia stan pinu na przeciwny. Prototyp (nagłówek) tej funkcji wygląda następująco:

Nie musimy się jednak przejmować tymi (pozornie) dziwnie wyglądającymi wartościami, które trzeba podać jako argumenty. Program zdefiniował dla nas automatycznie odpowiednie stałe. Aby zmienić stan diody LD2, wystarczy więc wywołać w pętli while następującą funkcję:

Podczas konfiguracji GPIO do pinu PA5 przypisaliśmy nazwę LD2. Generator kodu utworzył więc dwie stałe: LD2_GPIO_Port, która automatycznie oznacza port A, oraz LD2_Pin, która odpowiada pinowi oznaczonemu jako 5. Zestawiając te dwie stałe, możemy zatem odwołać się do pinu PA5.

Wprowadzanie prostych opóźnień do programu

Do migania diodą potrzebujemy jeszcze opóźnienia, inaczej częstotliwość migania będzie tak wysoka, że nie będziemy widzieli migania, tylko słabsze świecenie diody. Z pomocą przychodzi nam tutaj funkcja HAL_Delay, dzięki której można wprowadzić najprostsze opóźnienie (ma ono swoje wady, ale podczas pisania pierwszego programu, który miga diodą, taka opcja całkowicie nam wystarczy).

Prototyp (nagłówek) tej funkcji wygląda prosto:

Przyjmuje ona jeden parametr, którym jest opóźnienie wyrażone w milisekundach. Znamy już funkcje, które potrzebne są do migania diodą, więc możemy napisać program. Oczywiście musimy to zrobić w odpowiednim miejscu (patrzymy na komentarze w kodzie).

Komentarze, które opisują działanie programu, są ważne – przyzna to (prawie) każdy programista. W tym kursie nie komentujemy jednak każdej linijki kodu, bo są one dokładnie omawiane w treści kursu. Co więcej, nazwy funkcji z biblioteki HAL bardzo często mówią wprost co robi dana funkcja. Nie dodajemy też osobnych komentarzy, które nie są istotne – typu: „Czekaj 500 ms”.

Podpowiadanie składni

Jeśli podczas pisania programu zapomnimy, jak dokładnie brzmi nazwa funkcji lub jakie są parametry, które przyjmuje, to możemy skorzystać z podpowiadania składni. W tym celu wystarczy zacząć pisać, np. „HAL_GPIO_”, i nacisnąć kombinację klawiszy CTRL + Spacja. Wyświetlona zostanie wtedy lista funkcji, które zaczynają się od takiej nazwy.

Automatyczne podpowiadanie składni

Automatyczne podpowiadanie składni

Kompilacja programu

Gotowy program kompilujemy, klikając ikonę z młotkiem (lub wybierając odpowiednią opcję z menu). Jeśli wszystko poszło poprawnie, to tak jak w poprzedniej części kursu na dole okna zobaczymy odpowiedni komunikat. Oczywiście będą tam również informacje o ewentualnych błędach.

Informacja o poprawnej kompilacji projektu

Informacja o poprawnej kompilacji projektu

Teraz możemy uruchomić nasz program na płytce Nucleo. Klikamy przycisk Debug i zatwierdzamy domyślne opcje. Program zostanie uruchomiony i zatrzymany na pierwszej linii kodu funkcji main. Przyciskając ikonę Resume (lub naciskając klawisz F8), uruchomimy program – dioda będzie migać.

Efekt działania pierwszego programu – migająca dioda

Efekt działania pierwszego programu – migająca dioda

Konfiguracja pinu jako wejście

Potrafimy już sterować diodą, możemy teraz rozbudować przykładowy program o obsługę przycisku, który również znajduje się na pokładzie Nucleo – chodzi o niebieski przycisk, opisany jako USER.

Dwa przyciski i dioda LD2 na NUCLEO-L476RG

Dwa przyciski i dioda LD2 na NUCLEO-L476RG

W dokumentacji płytki Nucleo znajdziemy informację o podłączeniu przycisku do pinu PC13. Nie ma potrzeby, aby tworzyć nowy projekt – możemy zaktualizować obecny. Wystarczy, że w widoku projektu klikniemy 2 razy plik z rozszerzeniem „ioc” – przeniesiemy się wtedy do graficznego konfiguratora.

W uruchomionym edytorze odszukujemy pin PC13, zmieniamy jego tryb na wejście, czyli GPIO_Input. Na koniec dodajemy mu etykietę USER_BUTTON. Tabela z konfiguracją pinów, która widoczna jest w zakładce GPIO, powinna zawierać już dwa wpisy.

Nowy wpis na liście używanych GPIO

Nowy wpis na liście używanych GPIO

Wszystkie zmiany w konfiguracji sprzętu są już gotowe, możemy zapisać projekt i wygenerować nowy kod. Po chwili STM32CubeIDE wyświetli nam zaktualizowaną wersję programu. Na pierwszy rzut oka nie zobaczymy żadnej różnicy. W pętli while nadal powinniśmy widzieć dwie funkcje, które odpowiadają za miganie diody.

Do migania używaliśmy funkcji HAL_GPIO_TogglePin, która zmieniała stan pinu na przeciwny. Teraz zmienimy nasz program tak, żeby dioda świeciła tylko wtedy, gdy użytkownik naciska przycisk.

Odczytywanie stanu wejścia cyfrowego

Do odczytu stanu przycisku wykorzystamy funkcję HAL_GPIO_ReadPin, a do zapalania i gaszenia diody – HAL_GPIO_WritePin. Ich nagłówki wyglądają podobnie do HAL_GPIO_Toggle:

Pierwsza funkcja zwraca wartość typu GPIO_PinState, natomiast druga przyjmuje taką wartość jako trzeci parametr. Zobaczmy więc, jak wygląda definicja tego typu.

Jak widzimy, typ ten może przyjmować tylko dwie wartości:

  • GPIO_PIN_RESET – stan niski; oznacza napięcie bliskie 0 V (zwarcie do masy),
  • GPIO_PIN_SET – stan wysoki; odpowiada mu napięcie zasilania mikrokontrolera, czyli 3,3 V. 

To, jakie napięcia będą włączały diodę albo pojawiały się po naciśnięciu przycisku, zależy od schematu naszego układu. Na płytce Nucleo dioda LD2 jest podłączona tak, że włączamy ją stanem wysokim, a wyłączamy niskim. Natomiast na pinie połączonym z przyciskiem stan niski pojawia się, gdy przycisk zostanie wciśnięty, a wysoki – gdy będzie on zwolniony.

Nasz program powinien zatem wyglądać następująco:

Funkcja HAL_GPIO_ReadPin, podobnie do TogglePin, przyjmuje dwa parametry, czyli port oraz numer pinu, który chcemy odczytać. W związku z tym, że pinowi PC13 nadaliśmy już nazwę USER_BUTTON to możemy wykorzystać zdefiniowane wartości USER_BUTTON_GPIO_Port oraz USER_BUTTON_Pin

Poprzednio tylko zmienialiśmy stan diody na przeciwny, teraz chcemy dokładniej kontrolować jej stan, użyliśmy więc funkcji HAL_GPIO_WritePin. Jej pierwsze dwa parametry to – tak jak wcześniej – numer portu oraz pinu. Pojawił się też trzeci parametr – to informacja o tym, czy na danym wyjściu ma być ustawiony stan wysoki (GPIO_PIN_SET), czy stan niski (GPIO_PIN_RESET).

Efekt działania programu – dioda świeci po naciśnięciu przycisku

Efekt działania programu – dioda świeci po naciśnięciu przycisku

Podłączenie zewnętrznej diody

Umiemy już sterować diodą, która jest na płytce Nucleo, czas podłączyć zewnętrznego LED-a. Przydadzą nam się do tego płytka stykowa oraz elementy z zestawu. Podłączmy więc czerwoną diodę świecącą przez rezystor 330 R do pinu PA0.

Schemat ideowy oraz montażowy do tego przykładu

Schemat ideowy oraz montażowy do tego przykładu

Możemy teraz edytować konfigurację sprzętową naszego projektu. Robimy to tak samo jak poprzednio. Dodajemy tylko kolejny pin, który będzie skonfigurowany jako wyjście. W poprzednim projekcie diodę na płytce Nucleo nazwaliśmy LD2 (bo tak jest podpisana na Nucleo). Nowej diodzie nadajmy teraz nazwę LED1, aby była zgodna z powyższym schematem ideowym. Po dodaniu drugiej diody na liście użytych GPIO powinny być widoczne już trzy wpisy.

Konfiguracja kolejnego wejścia dla zewnętrznej diody

Konfiguracja kolejnego wejścia dla zewnętrznej diody

Wrócimy do migania diodą, ale tym razem zróbmy to za pomocą funkcji HAL_GPIO_WritePin oraz HAL_Delay. Teraz będziemy zapalać diodę na 20% czasu. Chodzi tutaj głównie o możliwość naocznego rozróżnienia stanów logicznych na wyjściu układu.

Takie sterowanie jest też stosowane w praktyce, bo pozwala na zaoszczędzenie prądu. Gdybyśmy włączali diodę na 500 ms, a następnie wyłączali ją na kolejne 500 ms, to nasz prąd średni wynosiłby 50% prądu potrzebnego do ciągłego świecenia diody. Teraz włączamy diodę na 200 ms, a wyłączamy na 800 ms, więc średnie zużycie prądu w takiej sytuacji spadnie do 20%.

Nowy program powinien więc wyglądać następująco:

Po uruchomieniu przykładu dioda podłączona do Nucleo powinna zacząć migać – 200 ms świecenia oraz 800 ms ciemności. Widać więc wyraźnie, że stanem wysokim (GPIO_PIN_SET) włączamy diodę, a stanem niskim (GPIO_PIN_RESET) gasimy ją.

Długi dodatek (?) na temat linijki LED

W tym miejscu moglibyśmy zakończyć, bo zupełne podstawy GPIO byłyby omówione. Zdecydowaliśmy się jednak na dodanie stosunkowo długiego „dodatku”, w którym pokażemy, w jaki sposób można rozwiązać pozornie prosty temat linijki składającej się z wielu kolorowych diod. Co więcej, temat ten został specjalnie „rozciągnięty”, aby pokazać w praktyce, że jeden problem można rozwiązać na wiele sposobów, które mogą generować różne problemy.

Gorąco zachęcamy, aby wszyscy przeanalizowali ten „dodatek”, nawet jeśli część tego opisu będzie zbyt zawiła od strony programistycznej – pojawią się tutaj przesunięcia bitowe, tablice, struktury i wskaźniki.

Linijka LED, czyli używanie wielu (różnych) wyjść

Skoro potrafimy podłączyć jedną diodę, dlaczego nie podłączyć ich więcej? W zestawie do tego kursu znajdują się diody w różnych kolorach, które można podłączyć na płytce stykowej w formie kolorowej linijki świetlnej. 

Na początek podłączmy diody do portu B (od pinu PB5 do PB14). Podłączenie diod do tego samego portu sprawi, że napisanie programu będzie łatwiejsze. Następnie sprawdzimy, co można zrobić, gdy nie będziemy mogli sobie pozwolić na takie „sprzętowe” ułatwienie.

Schemat ideowy i montażowy do przykładu z linijką LED

Schemat ideowy i montażowy do przykładu z linijką LED

Podczas podłączania tylu diod, na pewno przyda się mała ściąga z oznaczeniem lokalizacji pinów:

Lokalizacja pinów używanych w ćwiczeniu

Lokalizacja pinów używanych w ćwiczeniu

Skoro nasz układ jest już gotowy, czas przystąpić do konfiguracji pinów w CubeMX. Na potrzeby tego ćwiczenia można utworzyć nowy projekt, bo finalną wersję programu warto zapisać sobie na później – to będzie przydatny kawałek kodu. Po utworzeniu projektu dla układu STM32L476RG ustawiamy piny PB5-PB14 jako wyjścia; od razu nadajemy im też odpowiednie etykiety (od LED1 do LED10).

Podczas ustawiania tylu pinów jako wejścia widać od razu, że piny posiadają do wyboru wiele różnych funkcji. Konfiguracja w roli wyjść to tylko mały wycinek ich możliwości – inne tryby pracy pinów omówimy w odpowiednich momentach w trakcie realizacji kolejnych ćwiczeń.

Konfiguracja wielu wejść dla linijki LED

Konfiguracja wielu wejść dla linijki LED

Teraz możemy napisać pierwszą wersję naszego programu. Zacznijmy od prostej pętli, dzięki której na chwilę włączymy każdą z diod. Na pierwszy rzut oka ten program wydaje się prosty: włączamy diodę, czekamy 100 ms, wyłączamy ją, wracamy na początek pętli, włączamy kolejną diodę itd.

Efekt działania programu będzie następujący:

Przykładowy efekt na linijce świetlnej

Przykładowy efekt na linijce świetlnej

Całość działa jednak tylko dlatego, że zastosowano tutaj małą „sztuczkę”. Funkcje HAL_GPIO_WritePin i HAL_GPIO_ReadPin znamy już z poprzedniego przykładu, nowością jest jednak użycie tu przesunięcia bitowego, które sprawia, że „jakoś” włączamy kolejną diodę.

Żeby wyjaśnić, co robi taki zapis, najpierw trzeba odszukać definicję LED1_Pin. Można to zrobić „ręcznie” (sprawdzając plik main.h), a można też w kodzie programu kliknąć lewym przyciskiem myszki w LED1_Pin, jednocześnie trzymając wciśnięty klawisz CTRL.

Podświetlenie stałej, gdy trzymamy wciśnięty klawisz CTRL

Podświetlenie stałej, gdy trzymamy wciśnięty klawisz CTRL

Po kliknięciu zostaniemy automatycznie przeniesieni do fragmentu, który zawiera konkretne definicje.

Jak widzimy, LED1_Pin to odwołanie do GPIO_PIN_5, natomiast LED1_GPIO_Port to GPIOB. Kolejne diody są podłączone do tego samego portu oraz do kolejnych pinów – GPIO_PIN_6, GPIO_PIN_7 itd.

Teraz warto zobaczyć, jak wyglądają definicje tych stałych. Trzymamy więc klawisz CTRL i klikamy w jedną ze stałych (np. GPIO_PIN_5). Tym razem zostaniemy przeniesieni do pliku stm32l4xx_hal_gpio.h.

To już nie jest kod generowany przez CubeMX. Tym razem trafiliśmy do pliku z biblioteki HAL. Widzimy tutaj, że każda stała skrywa pod sobą jedną liczbę zapisaną w systemie szesnastkowym. Gdyby były one zapisane binarnie, to całość wyglądałaby (w uproszczeniu) następująco:

  • GPIO_PIN_5 = 0000 0010 0000
  • GPIO_PIN_6 = 0000 0100 0000
  • GPIO_PIN_7 = 0000 1000 0000 
  • GPIO_PIN_8 = 0001 0000 0000
  • GPIO_PIN_9 = 0010 0000 0000

Jak widać, każda wartość to 1 na kolejnej pozycji. Wiedząc, że diody mamy podłączone do kolejnych pinów tego samego portu, możemy użyć przesunięcia bitowego, aby „obliczyć” wartość, która w bibliotece HAL odpowiada danemu wyprowadzeniu. Takie rozwiązanie ma swoje plusy, bo wynikowy program jest bardzo krótki i mając odpowiednią wiedzę, można było uzyskać ten efekt bardzo szybko.

Taka wersja programu ma też niestety poważne minusy, ponieważ działa, tylko jeśli podłączymy diody do kolejnych pinów tego samego portu. Spróbujmy więc nieco zmienić nasz program, aby był znacznie łatwiejszy w interpretacji i bardziej uniwersalny.

Własne funkcje pomocnicze

Poprzedni program działał poprawnie i pokazywał często spotykane (w przypadku mikrokontrolerów) metody optymalizacji kodu. Niestety taki kod jest mało czytelny, a konieczność wyboru kolejnych pinów w obrębie jednego portu bywa trudna do realizacji sprzętowej.

Zacznijmy od utworzenia nowej funkcji, która będzie odpowiedzialna za sterowanie świeceniem diody. Dotychczas wywoływaliśmy bezpośrednio HAL_GPIO_WritePin, chcąc zapalić lub zgasić diodę. Teraz ten sam kod przeniesiemy do naszej nowej funkcji pomocniczej.

Załóżmy, że nasza nowa funkcja będzie nazywała się led_set. Następna kwestia to parametry. Musimy jakoś identyfikować diody podłączone do płytki. Wiele osób w takiej sytuacji zapewne zastosowałoby np. typ wyliczeniowy (enum). Na początek wybierzemy jednak tutaj najprostszą opcję i na wzór Arduino będziemy diody numerować od zera zmienną typu int.

Ostatnia decyzja to zastosowanie jednej lub dwóch funkcji, bo możemy mieć oddzielną funkcję do włączania i wyłączania diody albo jedną do obu zadań (ale za to z dodatkowym parametrem). Tym razem wybierzmy jedną funkcję z dodatkowym parametrem typu bool, który będzie miał wartość true, gdy będziemy chcieli włączyć diodę, lub false, gdy będziemy chcieli ją wyłączyć.

Pierwsze podejście do naszej nowej funkcji pomocniczej wygląda więc następująco (funkcję dodajemy w odpowiednim miejscu nad funkcją main):

W środku funkcji deklarujemy zmienną typu GPIO_PinState, której przypisujemy odpowiednią wartość w zależności od wartości parametru turn_on. Następnie sprawdzamy, czy podany numer diody mieści się w spodziewanym zakresie (0–9), i za pomocą funkcji HAL włączamy lub wyłączamy daną diodę.

Próba kompilacji programu nie uda się jednak, bo kompilator nie rozpoznaje typu bool. Aby naprawić ten błąd, musimy dołączyć nagłówek stdbool.h. Na początku naszego pliku main.c znajdziemy miejsce przewidziane na dodawanie nagłówków – dopisujemy w nim odpowiednią linijkę.

Teraz zmieniamy kod w pętli głównej – w taki sposób, aby korzystał z nowej funkcji.

Po tej zamianie program powinien działać tak samo dobrze jak poprzednia wersja, a skoro działa, to możemy zabrać się nareszcie za udoskonalanie naszej funkcji led_set.

Diody podłączone do różnych portów

W poprzedniej wersji używaliśmy zawsze stałej LED1_GPIO_Port, bo wiedzieliśmy, że LED1_GPIO_Port, LED2_GPIO_Port, LED2_GPIO_Port i kolejne miały taką samą wartość. Co więcej, diody musiały być podłączone do kolejnych wyprowadzeń, co bardzo ograniczało wybór pinów. Teraz chcemy, żeby nasz program działał poprawnie, nawet jeśli kolejność będzie zupełnie inna.

Jako pierwsze rozwiązanie nasuwa się użycie instrukcji warunkowej switch, bo dla każdej diody można ręcznie napisać odpowiedni przypadek. Taka wersja funkcji mogłaby wyglądać tak jak poniżej (przy okazji: warunek, który przypisuje odpowiednią wartość do zmiennej state, został zapisany w skróconej formie – działa identycznie jak poprzednio).

Jak widać, kod jest długi, ale ma kilka zalet. Przede wszystkim każda dioda może być podłączona do dowolnego portu i pinu. Mamy więc dużą elastyczność, ale za to długi i może niezbyt piękny program. Dla kilku diod to pewnie całkiem dobre rozwiązanie, jednak nawet już przy 10 sztukach widać, że warto byłoby zmniejszyć duplikację kodu.

Używanie tablic zamiast duplikacji kodu

Teraz dla odmiany spróbujmy użyć tablic. Wróćmy do pierwszej wersji programu i zastanówmy się, jak moglibyśmy go poprawić. Najpierw pozbędziemy się przesunięcia bitowego – wystarczy, że użyjemy tu odpowiedniej tablicy.

Wartości w tej tablicy będą odpowiadały wartościom, które wcześniej po prostu obliczaliśmy. Możemy więc naszą funkcję led_set zapisać następująco:

Program możemy po raz kolejny przetestować i sprawdzić, czy działa poprawnie. Kod faktycznie działa, ale ma minimum 2 wady: diody nadal muszą być podłączone do tego samego portu, a odwoływanie się do tablicy za pomocą indeksu podanego jako parametr może powodować błędy.

Zacznijmy od sprawdzenia, czy wartość parametru led jest na pewno poprawna (wystarczy warunek):

Zajmijmy się odwołaniem do LED1_GPIO_Port. Jak łatwo się domyślić, potrzebujemy teraz drugiej tablicy, w której będziemy przechowywali odnośniki do użytych portów. Porty GPIO są w bibliotece HAL reprezentowane przez typ GPIO_TypeDef, dodajemy więc tablicę wskaźników do takiego typu.

Teraz nasz program wygląda następująco:

Używanie struktury zamiast dwóch tablic

Dodawanie kolejnych tablic dla parametrów sprawia jednak, że dość trudno jest powiązać ze sobą dane opisujące określoną diodę. Możemy postąpić jeszcze lepiej. Wystarczy zdefiniować strukturę, która opisze podłączenie kolejnych diod.

Tworzymy więc nowy typ o nazwie pin_t i zapisujemy w nim port oraz pin, do którego podłączone są nasze diody. Kolejna wersja funkcji może wyglądać następująco:

Taki zapis sprawi, że nasza pozornie prosta linijka świetlna będzie działała niezależnie od tego, jakie piny i porty wybierzemy do tego, aby podłączyć w praktyce poszczególne diody. Nie trzeba nic już zmieniać w kodzie naszej funkcji.

Zaprezentowana wersja funkcji oczywiście nie jest idealna, należałoby pomyśleć o jej dalszym udoskonalaniu oraz np. o przeniesieniu jej do oddzielnego pliku. W tym przykładzie chcieliśmy jednak pokazać przy okazji też coś innego – funkcje udostępniane przez bibliotekę HAL mogą być używane we własnych funkcjach „pomocniczych” i często znacznie lepiej jest napisać własną małą bibliotekę „opakowującą” HAL, zamiast bezpośrednio odwoływać się do niej w naszym programie.

Sterowanie linijką LED za pomocą przycisku

Wiemy już, jak sterować naszą linijką diod, możemy teraz nieco skomplikować ten przykład i uczynić go bardziej interaktywnym poprzez dodanie obsługi przycisku – wykorzystajmy przycisk, który jest już na Nucleo do tego, aby sterować diodami. Zacznijmy od programu, który będzie włączał kolejne LED-y po naciśnięciu przycisku.

Wracamy więc do widoku CubeMX i konfigurujemy pin PC13 w trybie wejścia. Tak samo jak poprzednio, nazywamy go USER_BUTTON. Teraz możemy pisać nasz program. Zaczniemy od dodania nowej funkcji. Sprawdziliśmy już, że własne funkcje do sterowania diodami poprawiają czytelność programu – możemy to samo zrobić z przyciskiem.

Zaktualizowana konfiguracja projektu

Zaktualizowana konfiguracja projektu

Do odczytu stanu można użyć wywołania funkcji HAL_GPIO_ReadPin, która zwraca GPIO_PIN_RESET, gdy przycisk jest wciśnięty. Proste i skuteczne, ale można uprościć sobie życie, pisząc własną funkcję, np.: is_button_pressed. Funkcja będzie zwracała wartość logiczną: true, jeśli przycisk będzie wciśnięty, i false, jeśli będzie puszczony. Jej kod może wyglądać następująco (dodajemy ją oczywiście nad funkcją main w tym samym bloku komentarzy, w którym jest funkcja włączająca diody):

Skoro napisaliśmy funkcję, to wypadałoby sprawdzić, czy działa. Napiszmy więc program podobny do poprzedniego, ale z nową funkcją do odczytu przycisku oraz własnymi funkcjami sterującymi diodami:

Teraz możemy przetestować włączanie diod przyciskiem – powinno to działać zgodnie z oczekiwaniami, czyli naciśnięcie niebieskiego przycisku na płytce Nucleo włączy pierwszą diodę (nr 0) w linijce. My jednak chcieliśmy zapalać kolejne diody, a nie ciągle tą samą (i tylko wtedy, gdy wciśnięty jest przycisk).

Zacznijmy od napisania programu w taki sposób, jak dosłownie brzmi zadanie – czyli jeśli jest wciśnięty przycisk, to wyłącz obecnie włączoną diodę i włącz kolejną. Kod może wyglądać następująco (zwróć uwagę na 2 linijki, które zostały dopisane przez początkiem pętli while:

Warto taki program od razu uruchomić. Jego działanie może wydawać się nieco zaskakujące i wygląda to tak, jakby mikrokontroler robił coś zupełnie innego, niż opisuje nasz program: po naciśnięciu przycisku zapalają się wszystkie diody, a po zwolnieniu zapalona pozostaje jedna.

Popatrzmy jeszcze raz na działanie programu: gdy przycisk jest przyciśnięty, włączamy kolejne diody – ale nie czekamy na zwolnienie przycisku, tylko po chwili sprawdzamy ponownie i przechodzimy do kolejnej diody. W związku z tym, że takie przełączanie odbywa się bardzo szybko, wydaje się, że wszystkie diody są włączone – program działałby wtedy nieprawidłowo. Aby sprawdzić, czy takie podejrzenia są prawdziwe, dodajmy opóźnienie po przejściu do kolejnej diody.

Uruchamiamy ten program i testujemy. Widać, że teraz jest dużo lepiej i bliżej naszych oczekiwań – naciskając przycisk, zapalamy kolejne diody. Jednak program ma co najmniej dwa niedociągnięcia. Po pierwsze, jeśli przyciśniemy przycisk i będziemy go trzymać, to zapalane będą kolejne diody, a mieliśmy przechodzić do kolejnej tylko po przyciśnięciu. Druga niedoskonałość będzie widoczna, jeśli szybko będziemy naciskać przycisk – program nie zawsze reaguje na kolejne wciśnięcia.

Problemy w naszym programie wynikały z wywołania HAL_Delay. Jeśli przycisk był naciśnięty na dłużej niż 500 ms, to wykrywaliśmy dwa przyciśnięcia. Co więcej, jeśli w ciągu 500 ms użytkownik nacisnął przycisk więcej niż raz, to kolejne przyciśnięcia były ignorowane. Zmieńmy więc program tak, żeby nie zatrzymywać się na 500 ms, tylko czekać na zwolnienie przycisku.

Wreszcie program działa dokładnie tak, jak oczekiwaliśmy, ale to jeszcze oczywiście nie koniec.

Podłączanie własnego przycisku

Przykład z diodami działa pięknie, więc warto dodać do niego następną funkcję. Mamy przycisk do włączania kolejnych diod, lecz przydałby się taki, który pozwoli włączać poprzednie. Na płytce Nucleo znajdziemy drugi przycisk, ale działa on jako reset, więc tym razem podłączymy dodatkowy przycisk.

Schemat ideowy oraz montażowy dla wersji z dodatkowym przyciskiem

Schemat ideowy oraz montażowy dla wersji z dodatkowym przyciskiem

Po podłączeniu układu na płytce możemy przejść do CubeMX i dodać konfigurację kolejnego pinu. Tak jak poprzednio, używamy pinu PC13, do którego podłączony jest nasz USER_BUTTON, dodajemy tylko informację o kolejnym przycisku, który podłączamy do pinu PC8 – nazwijmy go USER_BUTTON2.

Nowa konfiguracja z drugim przyciskiem

Nowa konfiguracja z drugim przyciskiem

Mamy już funkcję is_button_pressed, która dotychczas nie miała parametrów, bo mieliśmy tylko jeden przycisk. Teraz chcemy obsługiwać dwa przyciski (ale zawsze możemy podłączyć więcej), więc dodamy parametr button – będzie on przechowywał numer przycisku, który chcemy sprawdzić. W związku z tym, że mamy tylko dwa przyciski, możemy na szybko napisać kod z instrukcją switch.

Teraz możemy wrócić do głównego programu. W dotychczasowym kodzie dodajemy jako parametr wywołania is_button_pressed wartość 0 oraz dopisujemy obsługę drugiego przycisku, który będzie zapalał poprzednią diodę.

Program jest gotowy do testów, więc go uruchamiamy i… okazuje się, że nie działa zgodnie z planem. Uzyskany efekt może być dość losowy, zapalone diody mogą dość szybko się zmieniać (tworząc przypadkiem nawet całkiem ładny efekt świetlny). Natomiast samo przyciśnięcie przycisku na płytce stykowej działa, tzn. sprawia, że diody przestają się samoczynnie przełączać.

Powodem naszych problemów jest brak rezystora podciągającego. Podłączyliśmy przełącznik, który zwiera pin wejściowy do masy układu, jednak gdy przycisk nie jest naciśnięty, to wejście naszego układu „wisi w powietrzu” i zbiera zakłócenia. Taka konfiguracja jest najczęściej efektem błędu w projekcie, musimy więc ją poprawić. Mamy co najmniej dwie możliwości – pierwsza to fizyczne podłączenie rezystora. Możemy to zrobić w bardzo prosty sposób.

Schemat ideowy i montażowy z zewnętrznym rezystorem podciągającym

Schemat ideowy i montażowy z zewnętrznym rezystorem podciągającym

Druga możliwość to aktywowanie rezystora podciągającego, który jest wewnątrz mikrokontrolera. Jest to funkcja, którą spotkać można w większości układów – trzeba tylko wiedzieć, jak ją włączyć. W tym przypadku jest to bardzo proste. Wystarczy, że w CubeMX zmienimy konfigurację pinu PC8 i z listy rozwijanej wybierzemy tryb Pull-up.

Aktywacja rezystora podciągającego na wejściu

Aktywacja rezystora podciągającego na wejściu

Nie musimy nic więcej zmieniać w kodzie – wystarczy zapisać nową konfigurację sprzętową, pozwolić na wygenerowanie kodu, skompilować i uruchomić program. Układ zadziała już (prawie) idealnie.

Drgania styków

Jeśli spędzimy trochę czasu, testując wcześniejszy program, to zauważymy dość ciekawą zależność – wciśnięcie przycisku na płytce Nucleo zawsze przełącza na kolejną diodę, ale nasz przycisk (na płytce stykowej) czasem przeskakuje np. o dwie diody. Ten efekt jest losowy, występuje bardzo rzadko, ale czasem się pojawia. Odpowiedzialne za niego są tzw. drgania styków.

Ich występowanie związane jest z działaniem przełącznika, który sprawia, że podczas przyciskania albo – częściej – zwalniania przycisku pojawiają bardzo szybkie zmiany na wejściu układu. Zmiany te mogą być przez program interpretowane jako dodatkowe wciśnięcia przycisku.

Drgania styków w praktyce – widziane okiem oscyloskopu

Drgania styków w praktyce – widziane okiem oscyloskopu

Tutaj też mamy wiele możliwości naprawienia naszego programu. Pierwsza to sprzętowa eliminacja przerwań. W tym celu możemy np. wykorzystać tzw. filtr RC. Na płytce Nucleo znajduje się filtr, dlatego używając przycisku USER_BUTTON, nie obserwowaliśmy dodatkowych przyciśnięć.

Czym jest filtr RC? Jak działa i co warto o nim wiedzieć?
Czym jest filtr RC? Jak działa i co warto o nim wiedzieć?

Filtr RC to hasło, które pojawiło się już w naszym kursie elektroniki. Wspominaliśmy tam, że pewne połączenie rezystora i... Czytaj dalej »

Drgania styków można też eliminować programowo. Jest to nieco trudniejszy temat, ale w najprostszej implementacji wystarczy po wciśnięciu lub zwolnieniu przycisku odczekać kilkadziesiąt milisekund. Ten czas wystarczy na ustabilizowanie odczytów z przełącznika. Na ten moment możemy to rozwiązać za pomocą jednego krótkiego opóźnienia (po puszczeniu przycisku USER_BUTTON2).

Teraz nasz program będzie działał już poprawnie:

Sterowanie linijką LED za pomocą dodatkowego przycisku

Sterowanie linijką LED za pomocą dodatkowego przycisku

O co chodzi z tym generowaniem kodu?

Wiele razy wspominaliśmy, że CubeMX generuje kod. Cały proces jest skomplikowany, ale efekt działania tego generatora jest bardzo prosty. CubeMX dosłownie generuje kod programu, który normalnie musielibyśmy pisać sami, i wkleja go za nas do programu. Dowód? Wystarczy zerknąć do funkcji MX_GPIO_Init, która jest w pliku main.c. Znajdziemy tam kod, który powstał na bazie tego, co wyklikaliśmy w CubeMX – warto prześledzić ten kod w ramach ciekawostki.

Zadanie domowe

  1. Wróć do przykładu, w którym dioda włączała się na 200 ms i gasła na 800 ms. Zastanów się, jak odwrócić działanie tego programu bez zmieniania jego kodu. 
  2. Podłącz 10 diod do innych, losowo wybranych pinów. Zmień konfigurację w CubeMX i sprawdź, czy program nadal działa zgodnie z naszymi założeniami.
  3. Dodaj do programu kolejny przycisk, który będzie resetował linijkę.

Podsumowanie – co powinieneś zapamiętać?

Za nami pierwsze programy, w których wykorzystaliśmy GPIO – peryferia te od teraz będą towarzyszyły nam prawie zawsze. Zwykłe miganie diodą czy obsługa przycisków to podstawy, dzięki którym można obsłużyć bardzo dużo popularnych peryferiów. Najważniejsze, abyś po lekturze tej części kursu potrafił samodzielnie skonfigurować projekt korzystający z GPIO. Nie musisz samodzielnie z głowy odtwarzać przykładu z linijką, strukturami i wskaźnikami.

Czy wpis był pomocny? Oceń go:

Średnia ocena 5 / 5. Głosów łącznie: 16

Nikt jeszcze nie głosował, bądź pierwszy!

Artykuł nie był pomocny? Jak możemy go poprawić? Wpisz swoje sugestie poniżej. Jeśli masz pytanie to zadaj je w komentarzu - ten formularz jest anonimowy, nie będziemy mogli Ci odpowiedzieć!

W następnym artykule zajmiemy się innym bardzo ważnym peryferium – konkretnie UART-em, dzięki któremu możliwe będzie przesłanie informacji z mikrokontrolera do komputera (chociaż to nie jedyne zastosowanie dla UART-u). Przy okazji sprawdzimy również, czym dokładnie jest debugger i do czego może się on przydać podczas testowania programów.

Nawigacja kursu

Główny autor kursu: Piotr Bugalski
Współautor: Damian Szymański, ilustracje: Piotr Adamczyk
Oficjalnym partnerem tego kursu jest firma STMicroelectronics
Zakaz kopiowania treści kursów oraz grafik bez zgody FORBOT.pl

GPIO, kurs, kursSTM32L4, programowanie, stm32, stm32l4

Trwa ładowanie komentarzy...