Kurs STM32 LL cz. 2. Przygotowanie projektu

W pierwszej części poznaliśmy podstawowe informację dotyczące kursu. Omówiłem niezbędne oprogramowanie oraz płytkę Nucleo-G071RB, z której będziemy korzystali w trakcie ćwiczeń. Teraz czas na przygotowanie szablonu projektu i uruchomienie pierwszego programu.

Projekt STM32CubeIDE z biblioteką Low Layer

Przygotowując materiały do kursu zastanawiałem się na tym, w jaki sposób najsensowniej będzie tworzyć projekt pod biblioteki Low Layer. Domyślnie mamy do tego przystosowany STM32CubeMX i z jego poziomu możemy wygenerować projekt. Z drugiej strony, w przypadku treści, jakie kurs będzie obejmował, zastosowanie generatora może być trochę mylące – chciałbym przedstawić cały proces konfiguracji i inicjalizacji układów peryferyjnych bez użycia struktur i kodu wygenerowanego przez STM32CubeMX. Dzięki temu poznamy każdy element tego procesu. Jednocześnie przejście z przedstawionego przeze mnie kodu na kod oparty tylko na bibliotekach CMSIS będzie banalnie prosty – w zasadzie sprowadza się do podmienienia nazw opisowych z biblioteki Low Layer na ustawienie bitów na rejestrach.

Jednak stworzenie projektu bez generatora jest trochę trudniejsze – choć wcale nie tak bardzo skomplikowane. Analizując za i przeciw obu rozwiązań postanowiłem, że przykłady będą opracowane za pomocą sposobu drugiego – bez użycia generatora. Jednak dla osób, które wolałyby stworzyć projekt kilkoma kliknięciami przedstawię także pokrótce, w jaki sposób wygenerować i przystosować projekt pod przykłady przedstawione w kursie. Dzięki temu “upieczemy dwie pieczenie na jednym ogniu”.

Wszystkie projekty z kursu dostępne są w moim repozytorium GitHub.

Pusty projekt pod STM32G071RB

W pierwszej kolejności przedstawię procedurę tworzenia pustego projektu bez użycia generatora STM32CubeMX. Mam nadzieję, że dzięki temu więcej osób skorzysta z tej opcji.

Mam nadzieję, że masz już zainstalowane środowisko STM32CubeIDE. Uruchamiamy zatem środowisko. Wybieramy z menu “File->New->STM32 Project”. Otworzy się okno MCU Finder.

Następnie wybieramy w zakładce “MCU/MPU Selector” mikrokontroler STM32G071RBT6.

Klikamy “Next”. Nadajemy nazwę projektu. Wybieramy język “C”, docelowy typ “Executable” oraz typ projektu “Empty”.

Klikamy “Finish”. Bazowy projekt został utworzony. Dodanych zostało kilka podstawowych plików – resztę niezbędnych do skompilowania projektu pod biblioteki Low Layer musimy dodać samodzielnie.

Otwieramy paczkę STM32CubeG0. Jeżeli jeszcze jej nie pobrałeś, teraz na to najwyższa pora. Najpierw stworzymy kilka folderów, które ułatwią nam zachowanie porządku w projekcie. Ich struktura będzie bardzo podobna do tego, co generuje STM32CubeMX – dzięki temu zachowamy pewną analogię.

Dodajemy zatem folder “Drivers” (prawym na nazwę projektu “New->Folder”. Następnie w folderze “Drivers” (prawym na folder “Drivers” i ponownie “New->Folder”) dodajemy dwa foldery: “CMSIS” oraz “STM32G0xx_LL_Driver”. 

W folderze “CMSIS” dodajemy foldery: “Device” oraz “Inc” oraz dodatkowo w folderze “Device” jeszcze jeden folder “Inc”.

W folderze “STM32G0xx_LL_Driver” dodajemy natomiast dwa foldery: “Inc” oraz “Src”. Struktura folderów powinna wyglądać jak na screenie poniżej.

Teraz musimy przekopiować odpowiednie pliki z paczki STM32CubeG0.  

  • Z folderu “Drivers\CMSIS\Device\ST\STM32G0xx\Include” do folderu “Drivers\CMSIS\Device\Inc” kopiujemy pliki trzy pliki: “stm32g0xx.h”, “stm32g071xx.h” oraz “system_stm32g0xx.h”.
  • Z folderu “Drivers\CMSIS\Include” do folderu “Drivers\CMSIS\Inc” kopiujemy wszystkie pliki.
  • Z folderu “Drivers\STM32G0xx_HAL_Driver\Inc” do folderu “Drivers\STM32G0xx_LL_Driver\Inc” kopiujemy wszystkie pliki z prefixem “stm32g0xx_ll” poza plikiem “stm32g0xx_ll_usb.h”. Nie wszystkich plików będziemy używali, ale akurat plik “stm32g0xx_ll_usb.h” wymaga plików biblioteki HAL (jak wspomniałem wcześniej LL jest przeznaczona do mniej skomplikowanych interfejsów) i nie pozwoli nam poprawnie skompilować projektu.
  • Z folderu “Drivers\STM32G0xx_HAL_Driver\Src” do folderu “Drivers\STM32G0xx_LL_Driver\Src” kopiujemy wszystkie pliki z prefixem “stm32g0xx_ll” poza plikiem “stm32g0xx_ll_usb.h”.
  • Z folderu “Drivers\CMSIS\Device\ST\STM32G0xx\Source\Templates” do folderu “Src” (tego głównego, w którym jest plik “main.c”) kopiujemy plik “system_stm32g0xx.c”.

Pliki mamy dodane, teraz czas na drobną konfigurację projektu. W menu “Project->Properties” wybieramy zakładkę “C/C++ General -> Path and Symbols”. Następnie w “Includes” dodajemy kilka folderów, gdzie umieściliśmy pliki “.h” (za pomocą “Add-> Workspace”).

Następnie w zakładce dodajemy folder “Drivers\STM32G0xx_LL_Driver\Src”. Po przejściu do zakładki “Source Location” wybieramy „Add Folder”, a następnie z drzewa folderów zaznaczamy „Drivers -> STM32G0xx_LL_Driver -> Src” i klikamy „OK”.

Zaznaczony folder pojawi się w oknie ze ścieżkami.

Na koniec w zakładce “Symbols” dopisujemy “STM32G071xx” (“Add” i tylko “Name”).

Zatwierdzamy “Apply and Close”. Możemy skompilować projekt – jeżeli nic nie pominąłeś, powinien zbudować się bez żadnych błędów.

Proces ten w zasadzie możemy wykonać raz i zachować sobie tak przygotowany projekt jako wzorzec. Potem, przerabiając kolejne lekcje, można wzorzec kopiować, podmieniając mu tylko nazwę – dzięki temu kolejne projekty będziemy tworzyć w kilka sekund :))

Projekt z STM32CubeMX

Drugą drogą do stworzenia projektu pod biblioteki Low Layer jest użycie generatora STM32CubeMX, który jest dostępny jako oddzielna aplikacja lub element wbudowany w środowisko STM32CubeIDE.

Aby wygenerować projekt, wybieramy z menu “File->New->STM32 Project”. Otworzy się okno MCU Finder.

Następnie wybieramy w zakładce “MCU/MPU Selector” mikrokontroler STM32G071RBT6.

Klikamy “Next”. Nadajemy nazwę projektu. Wybieramy język “C”, docelowy typ “Executable” oraz typ projektu “STM32Cube”.

Klikamy “Finish”. Projekt został utworzony. Jednocześnie powinno otworzyć się nam okno konfiguratora.

Jeżeli ktoś chciałby skorzystać z pełni możliwości tego narzędzia, w tym miejscu może za pomocą graficznego interfejsu skonfigurować używane peryferia. Jeżeli programowałeś STM32 przy pomocy bibliotek HAL-a – wygląda to identycznie.

Użycie pełnych możliwości generatora ma swoje wady i zalety. Konfigurując graficznie peryferia korzystamy z wygenerowanego kodu w taki sposób, jak to było przy bibliotekach HAL-a. Jest to wygodne i szybkie. Celem kursu jest jednak nie tylko poznanie bibliotek Low Layer, ale przede wszystkim poznanie budowy, konfiguracji i obsługi układów peryferyjnych mikrokontrolera. Dlatego my będziemy wszystkie rejestry ustawiać ręcznie (korzystając z API biblioteki Low Layer). Jest to rozwiązanie pośrednie pomiędzy klasycznym programowanie na rejestrach przy pomocy samego CMSIS (tzw. Bare Metal), a bibliotekami Low Layer. Uważam, że takie podejście będzie miało znacznie większe walory edukacyjne.

Ze względu na to, że nie chcemy korzystać z konfiguracji wbudowanej w CubeMX, wykorzystamy tylko kilka elementów konfiguratora. Pozwoli nam to uzyskać podobny efekt do tego, co stworzyliśmy budując ręcznie projekt – tutaj pliki przekopiuje nam generator. Wadą w porównaniu do pustego projektu będzie to, że uzyskamy pliki ze znacznikami (trochę “zaśmiecone”), co może być czasami uciążliwe przy pisaniu projektów.

Przystępujemy zatem do działania. W zakładce “Pinout & Configuration” możemy wykonać dwie czynności. 

Po pierwsze, jeżeli chcemy korzystać z jakiegoś układu peryferyjnego, musimy go włączyć. Nie jest dla nas ważne jakie bedą parametry konfiguracyjne – wystarczy tylko aktywować dany interfejs, aby generator załączył nam odpowiednie pliki biblioteki Low Layer. Dla przykładu do użycia interfejsu UART wystarczy zaznaczyć w polu “Mode” jedną z opcji, np. “Asynchronous”.

Po drugie możemy wykorzystać generator do utworzenia definicji do obsługi pinów. Jeżeli będziemy korzystać z jakiegoś pinu, aktywujemy go wybierając lewym przyciskiem jego funkcję i klikamy prawym przyciskiem, aby przypisać mu etykietę “Enter User Label”. Takie etykiety będziemy tak czy inaczej w kursie tworzyć, więc używając CubeMX można je wygenerować automatycznie.

Drugą zakładką używaną przez nas w kursie będzie “Project Manager”. W zakładce “Advanced Settings” musimy wprowadzić kilka zmian, aby ograniczyć ilość generowanego kodu. Nam w kursie będzie on niepotrzebny, bo napiszemy go od podstaw. W ustawieniach zaawansowanych musimy przede wszystkim zmienić rodzaj używanych bibliotek z HAL na LL.

Drugą rzeczą jest ograniczenie ilości niepotrzebnego nam kodu. Dokonamy tego, odznaczając w kolumnie “Generate Code” okienko przy wszystkich peryferiach (dla RCC niestety generator nam na to nie pozwala) oraz zaznaczając przy wszystkich peryferiach okno w kolumnie “Do Not Generate Function Call”. 

W ten sposób uzyskamy w miarę pusty projekt gotowy do pracy. Wystarczy tylko wygenerować kod (“Project->Generate Code” lub ALT+K).

Jak już wspominałem, wersja z wykorzystanie generatora CubeMX jest szybsza, ale moim zdanie mniej “elegancka”. W przypadku pierwszego projektu uzyskujemy czystszy projekt o identycznych funkcjonalnościach, który może być w bardzo łatwy sposób przerobiony do pracy na rejestrach bez użycia bibliotek Low Layer (wystarczy ich nie dodawać do projektu). Używając CubeMX musimy dodatkowo pilnować w wygenerowanych plikach znaczników, aby przy ponownej generacji kodu coś nam “magicznie” nie zniknęło. Niemniej przedstawiłem oba sposoby, dając każdemu wybór własnej, lepszej dla siebie drogi.

Pierwszy projekt – “Hello World” dla embedded, czyli miganie diodą

Czas na sprawdzenie, czy nasz projekt został poprawnie przygotowany. A najlepszym na to sposobem będzie uruchomienie embedded-owego “Hello World”, czyli miganie diodą.

Możesz pracować na projekcie utworzonym w poprzedniej części. Jeżeli chciałbyś wprowadzać modyfikacje na nowym – wystarczy skopiować utworzony szablon. Pamiętaj jednak, aby zaktualizować ścieżki “Path” do nowego projektu. W przeciwnym wypadku projekt nie będzie działał prawidłowo.

Czas na pierwszy program. Żeby zachować od początku porządek w projekcie, dodajmy plik “main.h”, w którym zamieszczać będziemy m.in. definicje pinów i portów. Stworzymy go w folderze głównym “Inc” – klikamy prawym przyciskiem na folder i wybieramy “New->Header File”.

W nim załączymy pliki nagłówkowe bibliotek oraz dodamy definicje dla pinu PA5 – do niego podłączona jest dioda LD4 (zielona) na płytce Nucleo.

#include "stm32g0xx_ll_rcc.h"
#include "stm32g0xx_ll_bus.h"
#include "stm32g0xx_ll_system.h"
#include "stm32g0xx_ll_cortex.h"
#include "stm32g0xx_ll_utils.h"
#include "stm32g0xx_ll_gpio.h"
 
#define LED_GREEN_Pin LL_GPIO_PIN_5
#define LED_GREEN_GPIO_Port GPIOA

W pliku “main.c” załączamy plik nagłówkowy “main.h”.

#include "main.h"

W funkcji głównej “main()” umieszczamy kod, który wywoła miganie diody.

LL_SetSystemCoreClock(16000000);
LL_Init1msTick(16000000);
 
LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
 
LL_GPIO_SetPinOutputType(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinPull(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinMode(LED_GREEN_GPIO_Port, LED_GREEN_Pin, LL_GPIO_MODE_OUTPUT);
 
while (1)
{
     LL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
     LL_mDelay(100);
}

To, co w nim jest umieszczone, na razie nie jest takie istotne. Niedługo wszystko się wyjaśni. Teraz kompilujemy projekt (“Project->Build Project”). Jeżeli nie pojawiły się żadne błędy, wgrywamy kod wynikowy na płytkę (“Run->Run”). Przy pierwszej próbie wgrania pojawi się okno konfiguracji. Ustawienia domyślne będą dla nas wystarczające – wybieramy “OK”. Jeżeli program się wgrał poprawnie na Nucleo – dioda zacznie migać.To oznacza, że przygotowanie środowiska i projektu pod STM32 i biblioteki Low Layer zakończyło się powodzeniem.

Repozytorium GitHub

Chciałbyś otrzymywać na bieżąco informacje o nowych artykułach z kursu? Zapisz się do newslettera!

TO NIE TYLKO MAIL Z INFORMACJĄ O NOWEJ LEKCJI, ALE TAKŻE DODATKOWE MATERIAŁY. NIE PRZEGAP NOWEJ TREŚCI I DODATKOWYCH BONUSÓW. PRZEJDŹ DO STRONY KURSU I PODAJ SWÓJ ADRES E-MAIL. NIE ZAPOMNIJ POTWIERDZIĆ CHĘCI DOŁĄCZENIA W PIERWSZEJ WIADOMOŚCI!

  1. Cześć. Dzięki za świetny kursik. Brakowało takiego na naszym rodzimym rynku 🙂 Mam taką sugestie. LL_SetSystemCoreClock(16000000) – ta funkcja pod spodem ustawia jedno pole, mianowicie SystemCoreClock.
    void LL_SetSystemCoreClock(uint32_t HCLKFrequency)
    {
    /* HCLK clock frequency */
    SystemCoreClock = HCLKFrequency;
    }
    natomiast w pliku system_stm 32g0xx.c mamy default już ustawiony:
    uint32_t SystemCoreClock = 16000000UL;
    także użycie tej funkcji w przykładzie wydaje się zbiędne.

    1. Można pominąć tą instrukcje, ale zostawiłem ja dla porządku. W momencie, jak będziemy chcieli ustawić inną częstotliwość, nie zapomnimy o tej funkcji.

  2. Cześć. Dzięki za świetny kursik. Brakowało takiego na naszym rodzimym rynku 🙂 Mam taką sugestie. LL_SetSystemCoreClock(16000000) – ta funkcja pod spodem ustawia jedno pole, mianowicie SystemCoreClock.
    void LL_SetSystemCoreClock(uint32_t HCLKFrequency)
    {
    /* HCLK clock frequency */
    SystemCoreClock = HCLKFrequency;
    }
    natomiast w pliku system_stm32g0xx.c mamy default już ustawiony:
    uint32_t SystemCoreClock = 16000000UL;
    także użycie tej funkcji w przykładzie wydaje się zbiędne.

  3. Dziękuję za poradę. Jakoś nie mogłem wpaść na ten sposób rozwiązania. Trochę o czasu mi zajęło z powodu poważnej awarii komputera.
    Pozdrawiam
    Tadeusz Baszyński

  4. Witam.
    Czy może Pan bardziej dokładnie opisać jak w zakładce
    „Paths and Symbols -> Source Location”
    dodać folder:
    „/Kurs_STM32_LL_Template/Drivers/STM32G0xx_LL_Driver/Src”.
    Raz po kilku godzinach prób udało się to zrobić. Następne próby są nieudane.
    Pozdrawiam
    Tadeusz Baszyński

    1. Uzupełniłem wpis o dodatkowy opis i screen. Mam nadzieję, że teraz się uda. Zaznaczę tylko, że jeżeli folder jest już dodany, drugi raz nie będzie widoczny przy próbie dodania przez „Add folder”. Jeżeli nadal będzie miał Pan problem, proszę o maila na kontakt@stm32wrobotyce.pl. Być może występuje u Pana jakiś szczególny przypadek, postaram się indywidualnie pomóc go rozwiązać.

Dodaj komentarz

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