Programowanie STM32 w C++ – pierwszy projekt

Język C++ zyskuje coraz większą popularność na całym świecie. Współczesne firmy, szukając narzędzi do realizacji swoich projektów, coraz częściej wybierają C++ zamiast C, który jest dominującym językiem od wielu lat. Zmiana ta nie jest przypadkowa – C++ oferuje szeroki wachlarz możliwości, które odpowiadają na potrzeby współczesnego rynku IT. Decyzja o migracji na C++ wynika z potrzeby zwiększenia wydajności, elastyczności oraz ułatwienia rozwoju bardziej złożonych aplikacji. Choć C++ jest językiem o długiej historii, wciąż pozostaje jednym z najważniejszych narzędzi w rękach programistów, wykorzystywanym w szerokim zakresie – od oprogramowania systemowego po zaawansowane aplikacje inżynierskie. Jego rozwój i adaptacja są odpowiedzią na wymagania współczesnego programowania, gdzie liczy się nie tylko funkcjonalność, ale i efektywność kodu. Dzięki ciągłemu udoskonalaniu standardów C++ staje się językiem, który może sprostać rosnącym wymaganiom technologii w wielu branżach.

W tym artykule pokażę Ci, jak rozpocząć przygodę z programowaniem mikrokontrolerów STM32 w C++, wykorzystując popularne środowisko STM32CubeIDE. Zaprezentuję krok po kroku, jak skonfigurować projekt w tym języku oraz jak napisać prosty program do sterowania diodą LED.

Dlaczego warto programować w C++?

C++ to język, który w porównaniu do C oferuje znacznie więcej zaawansowanych funkcji, takich jak obiekty, klasy i mechanizmy zarządzania pamięcią. Choć język C jest bardzo popularny w świecie mikrokontrolerów, C++ ma swoje niezaprzeczalne zalety:

  1. Modularność i organizacja kodu: Dzięki obiektowości C++, możliwe jest tworzenie klas i obiektów, które pozwalają na modularne podejście do projektowania aplikacji. W mikrokontrolerach umożliwia to łatwiejsze zarządzanie różnymi peryferiami (np. czujnikami, wyświetlaczami, interfejsami komunikacyjnymi), ponieważ każdy komponent może być zorganizowany w osobną klasę. Taki kod jest bardziej przejrzysty i łatwiejszy do rozbudowy.
  2. Wsparcie dla niskopoziomowego programowania: C++ zachowuje pełną kompatybilność z językiem C, co pozwala na korzystanie z mechanizmów niskopoziomowych, takich jak manipulowanie wskaźnikami czy dostęp do rejestrów mikrokontrolera. Dzięki temu można pisać efektywny i szybki kod, który wykorzystuje pełny potencjał sprzętu.
  3. Efektywność pamięciowa: Choć C++ oferuje zaawansowane mechanizmy obiektowe, wciąż pozwala na dokładne kontrolowanie zarządzania pamięcią. Mechanizmy takie jak wskaźniki pomagają w zarządzaniu pamięcią, minimalizując ryzyko wycieków, co jest kluczowe w systemach o ograniczonych zasobach, takich jak mikrokontrolery.
  4. Rozbudowana biblioteka standardowa: C++ oferuje szeroki zestaw standardowych struktur danych i algorytmów, które mogą być użyte w programowaniu mikrokontrolerów. Dzięki takim narzędziom można szybciej zaimplementować bardziej zaawansowane funkcje bez konieczności pisania ich od podstaw.
  5. Bezpieczeństwo typu: C++ oferuje silny system typów, który zmniejsza ryzyko błędów typowych dla języków niskiego poziomu, takich jak nieprawidłowe manipulowanie wskaźnikami. Dzięki temu programiści mogą uniknąć wielu potencjalnych problemów związanych z bezpieczeństwem kodu, co jest istotne, zwłaszcza w systemach wbudowanych.
  6. Lepsza obsługa większych projektów: W miarę rozwoju projektu mikrokontrolera, organizacja kodu staje się coraz bardziej kluczowa. C++ oferuje mechanizmy, które umożliwiają łatwiejszą strukturację kodu w dużych projektach, takie jak przestrzenie nazw (namespaces) oraz możliwość tworzenia złożonych interfejsów między różnymi komponentami systemu.
  7. Wysoka wydajność: C++ pozwala na generowanie zoptymalizowanego kodu, który jest bliski sprzętowi. Dzięki temu aplikacje mogą działać z minimalnym opóźnieniem i zużyciem zasobów, co jest szczególnie ważne w aplikacjach wymagających dużej wydajności, takich jak systemy sterowania czy komunikacja w czasie rzeczywistym.
  8. Kompatybilność z C: Ponieważ C++ jest w pełni kompatybilny z C, programiści mogą łatwo wykorzystać istniejące biblioteki napisane w C lub połączyć kod C++ z aplikacjami opartymi na C. Taka kompatybilność ułatwia integrację z istniejącymi systemami i pozwala na wykorzystanie sprawdzonych, gotowych rozwiązań.

Konfiguracja projektu w STM32CubeIDE

STM32CubeIDE to zintegrowane środowisko programistyczne (IDE), które łączy edytor kodu, kompilator i narzędzia do konfiguracji mikrokontrolerów STM32. Można je pobrać i zainstalować ze strony producenta STMicroelectronics.

Tworzenie nowego projektu

Po zainstalowaniu i uruchomieniu STM32CubeIDE, wybierz File > New > STM32 Project. Wybierz swój mikrokontroler lub płytkę (w moim przypadku Nucleo-G474RE).

Wybór języka C++

W polu „Targeted Language” wybierz C++. Dzięki temu projekt będzie mógł korzystać ze wszystkich funkcji języka C++, takich jak klasy, dziedziczenie, polimorfizm, etc.

Konfiguracja peryferiów

Podczas tworzenia projektu STM32CubeMX (które jest częścią STM32CubeIDE) umożliwia konfigurację peryferiów mikrokontrolera. Możesz wybrać, które piny będą używane do sterowania diodą LED, UART, SPI i innymi peryferiami. W naszym przypadku nie musimy nic konfigurować – pin, do którego podłączona jest dioda LED jest skonfigurowany domyślnie (PA5 jako LD2). Teraz możemy wygenerować projekt Project > Generate Code.

Zmiana nazwy pliku

Po utworzeniu projektu w STM32CubeIDE, zmień domyślny plik main.c na main.cpp. Kliknij prawym przyciskiem myszy na plik main.c w folderze Src i wybierz Rename. Zmień nazwę pliku na main.cpp. Dzięki temu będziesz mógł korzystać z funkcji C++ w swoim projekcie.

Tworzenie pliku main.c zamiast main.cpp przez STM32CudeIDE pomimo wybranego języka C++ jest dość dziwną przypadłością. Nie wiem jaki jest powód, ale musimy sobie z nim jakoś radzić 🙂 Zmiana nazwy to najprostsza metoda, ale nie do końca wygodna – za każdym razem, gdy będziemy ponownie generować projekt, musimy pamiętać o zmianie nazwy. W kolejnych wpisach pokaże inne sposoby na obejście tego problemu, ale na razie przyjmijmy, że to jest najszybsza metoda na tę chwilę.

Budowanie projektu

Mając tak przygotowany projekt, możemy skompilować pliki wynikowe. Proces wywołujemy identycznie jak dla projektów pisanych w języku C. Wybierz zatem Project > Build All lub Ctrl + B. W konsoli otrzymamy logi z procesu budowania.

21:04:15 **** Build of configuration Debug for project Cpp_LED_Driver ****
make -j16 all 
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_cortex.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_cortex.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_cortex.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_cortex.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma_ex.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma_ex.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma_ex.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_dma_ex.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_exti.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_exti.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_exti.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_exti.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ex.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ex.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ex.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ex.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ramfunc.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ramfunc.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ramfunc.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_flash_ramfunc.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_gpio.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_gpio.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_gpio.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_gpio.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr_ex.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr_ex.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr_ex.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_pwr_ex.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc.o"
arm-none-eabi-gcc "../Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc_ex.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc_ex.d" -MT"Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc_ex.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Drivers/STM32G4xx_HAL_Driver/Src/stm32g4xx_hal_rcc_ex.o"
arm-none-eabi-gcc -mcpu=cortex-m4 -g3 -DDEBUG -c -x assembler-with-cpp -MMD -MP -MF"Core/Startup/startup_stm32g474retx.d" -MT"Core/Startup/startup_stm32g474retx.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Startup/startup_stm32g474retx.o" "../Core/Startup/startup_stm32g474retx.s"
arm-none-eabi-g++ "../Core/Src/main.cpp" -mcpu=cortex-m4 -std=gnu++14 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-use-cxa-atexit -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/main.d" -MT"Core/Src/main.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/main.o"
arm-none-eabi-gcc "../Core/Src/stm32g4xx_hal_msp.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/stm32g4xx_hal_msp.d" -MT"Core/Src/stm32g4xx_hal_msp.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/stm32g4xx_hal_msp.o"
arm-none-eabi-gcc "../Core/Src/stm32g4xx_it.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/stm32g4xx_it.d" -MT"Core/Src/stm32g4xx_it.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/stm32g4xx_it.o"
arm-none-eabi-gcc "../Core/Src/syscalls.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/syscalls.d" -MT"Core/Src/syscalls.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/syscalls.o"
arm-none-eabi-gcc "../Core/Src/sysmem.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/sysmem.d" -MT"Core/Src/sysmem.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/sysmem.o"
arm-none-eabi-gcc "../Core/Src/system_stm32g4xx.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32G474xx -c -I../Core/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc -I../Drivers/STM32G4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32G4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -fcyclomatic-complexity -MMD -MP -MF"Core/Src/system_stm32g4xx.d" -MT"Core/Src/system_stm32g4xx.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/system_stm32g4xx.o"
arm-none-eabi-g++ -o "Cpp_LED_Driver.elf" @"objects.list"   -mcpu=cortex-m4 -T"D:\STM32_Workspace_17.0\Cpp_LED_Driver\STM32G474RETX_FLASH.ld" --specs=nosys.specs -Wl,-Map="Cpp_LED_Driver.map" -Wl,--gc-sections -static --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -Wl,--start-group -lc -lm -lstdc++ -lsupc++ -Wl,--end-group
Finished building target: Cpp_LED_Driver.elf
 
arm-none-eabi-size  Cpp_LED_Driver.elf 
arm-none-eabi-objdump -h -S Cpp_LED_Driver.elf  > "Cpp_LED_Driver.list"
   text	   data	    bss	    dec	    hex	filename
   5996	     12	   1572	   7580	   1d9c	Cpp_LED_Driver.elf
Finished building: default.size.stdout
 
Finished building: Cpp_LED_Driver.list
 

21:04:16 Build Finished. 0 errors, 0 warnings. (took 1s.105ms)

Możemy zauważyć, że dla pliku main.cpp wykorzystany jest kompilator g++ odpowiadający za kompilację projektów pisanych w C++. Również plik wyjściowy jest kompilowany przy pomocy g++.

Klasa do sterowania diodą LED

Tradycja mówi, że każdy tutorial w embedded powinien zaczynać się od „Hello World”, czyli sterowania diodą. Potrzymamy tę tradycję i stworzymy klasę do sterowania LED-ami.

Utworzymy plik led.hpp. Ze względu na to, że implementacja metod będzie bardzo krótka, wszystko umieścimy w pliku nagłówkowym. Klasa led służy do sterowania diodą LED podłączoną do mikrokontrolera STM32 przy użyciu biblioteki HAL. Pozwala na włączanie, wyłączanie oraz przełączanie stanu diody. Dlaczego używamy biblioteki HAL? Bo jest bardzo wygodna i prosta w użyciu. W połączeniu z generatorem CubeMX pozwala w łatwy sposób skonfigurować mikrokontroler bez potrzeby zagłębiania się w szczegóły budowy rejestrów. To idealne rozwiązanie na początek (i nie tylko).

Najpierw stworzymy stałe przechowujące informacje o porcie i pinie. Umieścimy je w sekcji prywatnej. Dzięki temu nie będą ona widoczne na zewnątrz klasy. Będą to:

  • GPIO_TypeDef* const port – wskaźnik na strukturę portu GPIO, do którego podłączona jest dioda LED.
  • const uint16_t pin – numer pinu w podanym porcie GPIO.

Warto zwrócić tutaj uwagę na to, jaka jest różnica między „GPIO_TypeDef* const port” a „const GPIO_TypeDef* port”. GPIO_TypeDef* const port to stały wskaźnik do zmiennej – nie można zmienić wskaźnika, ale można zmieniać zawartość, na którą wskazuje. Natomiast const GPIO_TypeDef* port to wskaźnik do stałej – można zmieniać wskaźnik, ale nie można modyfikować zawartości, na którą wskazuje. W klasie LED, gdzie port GPIO nie powinien się zmieniać, ale chcemy sterować jego rejestrami (HAL_GPIO_WritePin), lepsza będzie stała referencja do zmiennego obiektu. Dzięki temu zapobiegamy przypadkowej zmianie wskaźnika na inny port oraz możemy sterować GPIO (zmieniać jego rejestry).

Konstruktor inicjalizuje obiekt klasy led, przypisując odpowiednie wartości do portu i pinu. Zauważ, że C++ pozwala nam od razu przypisać argumenty do prywatnych składowych klasy za pomocą listy inicjalizacyjnej. „: port(gpioPort), pin(gpioPin) oznacza, że pola port i pin są inicjalizowane wartościami przekazanymi do konstruktora przed wykonaniem ciała konstruktora. Dzięki temu unikamy najpierw wywołania domyślnego konstruktora dla tych pól, a następnie ich przypisania – co jest bardziej efektywne.

led(GPIO_TypeDef *gpioPort, uint16_t gpioPin) : port(gpioPort), pin(gpioPin) {}

Do sterowania diodą potrzebujemy trzech metod publicznych:

  • void on() – Ustawia pin w stan wysoki (GPIO_PIN_SET), włączając diodę.
  • void off() – Ustawia pin w stan niski (GPIO_PIN_RESET), wyłączając diodę.
  • void toggle() – Zmienia stan pinu na przeciwny (jeśli był włączony, zostanie wyłączony i vice versa).

Cała klasa wygląda następująco:

class led {
private:
	GPIO_TypeDef* const port;
	const uint16_t pin;

public:
	led(GPIO_TypeDef *gpioPort, uint16_t gpioPin) : port(gpioPort), pin(gpioPin) {}

	void on() {
		HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
	}

	void off() {
		HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
	}

	void toggle() {
		HAL_GPIO_TogglePin(port, pin);
	}
};

Klasa led zapewnia wygodny interfejs do sterowania diodą LED podłączoną do mikrokontrolera STM32, upraszczając obsługę operacji takich jak włączanie, wyłączanie i przełączanie stanu. Dzięki zastosowaniu wskaźnika do struktury portu GPIO oraz numeru pinu, użytkownik może łatwo tworzyć obiekty reprezentujące różne diody, co poprawia czytelność i organizację kodu. Hermetyzacja operacji GPIO w dedykowanej klasie sprawia, że kod staje się bardziej modułowy i łatwiejszy w utrzymaniu, ponieważ zmiany w sposobie sterowania diodami można wprowadzać w jednym miejscu bez konieczności modyfikowania całego programu.

Dodatkowo, użycie metod on(), off() i toggle() eliminuje konieczność bezpośredniego wywoływania funkcji HAL w głównej części programu, co zmniejsza ryzyko błędów wynikających z nieprawidłowego użycia interfejsu HAL. Konstruktor klasy umożliwia łatwą inicjalizację diody bez potrzeby ręcznej konfiguracji pinu w każdym miejscu kodu, co poprawia jego przejrzystość. Dzięki temu klasa led jest szczególnie przydatna w projektach wykorzystujących wiele diod, ponieważ pozwala zarządzać nimi w sposób uporządkowany i efektywny.

Przykład użycia klasy

W celu użycia klasy led musimy stworzyć obiekt klasy i użyć go do migania diodą. Przed funkcją main() umieszczamy deklarację obiektu.

namespace {
    led userLed{LD2_GPIO_Port, LD2_Pin};
}

Wykorzystano tutaj anonimową przestrzeń nazw (namespace {}) do utworzenia obiektu userLed reprezentującego diodę LED podłączoną do LD2_GPIO_Port i LD2_Pin. Anonimowa przestrzeń nazw (ang. unnamed namespace) to przestrzeń nazw bez określonej nazwy, używana do ograniczenia widoczności zmiennych, funkcji lub klas do pojedynczego pliku. Jest to alternatywa dla słowa kluczowego static stosowanego przed funkcjami i zmiennymi globalnymi w języku C.

Jakie są zalety anonimowej przestrzeni nazw?

  • Ograniczenie widoczności – zmienne i funkcje z anonimowej przestrzeni nazw są widoczne tylko w pliku, w którym zostały zadeklarowane.
  • Unikanie konfliktów nazw – jeśli wiele plików ma tę samą nazwę zmiennej/funkcji w anonimowej przestrzeni nazw, nie powoduje to konfliktów.
  • Bezpieczeństwo – ukrywa implementację przed innymi plikami, ograniczając możliwość przypadkowego użycia.

Czy anonimowa przestrzeń nazw to to samo co static? Nie do końca! Przed C++11 używano static dla zmiennych i funkcji globalnych, aby ograniczyć ich widoczność do jednego pliku. Jednak anonimowa przestrzeń nazw jest bardziej elastyczna i zalecana w nowoczesnym C++ zamiast static dla funkcji i zmiennych globalnych.

W petli while(1) dodajemy kod do przełączania stanu diody oraz opóźnienie za pomocą standardowej funkcji HAL Library.

  while (1)
  {
	userLed.toggle();
	HAL_Delay(500);
  }

Kod po skompilowaniu możemy uruchomić na płytce Nucleo. LED będzie migała z częstotliwością 1 Hz.

Podsumowanie

W tym artykule nauczyliśmy się, jak skonfigurować projekt w STM32CubeIDE do programowania mikrokontrolerów STM32 w języku C++ oraz jak stworzyć prostą klasę do sterowania diodą LED. Dzięki podejściu obiektowemu możemy stworzyć bardziej modularny i łatwy do zarządzania kod. C++ pozwala również na wykorzystywanie bardziej zaawansowanych funkcji, takich jak dziedziczenie czy polimorfizm, co daje większą elastyczność w pisaniu oprogramowania na mikrokontrolery.

C++ w środowisku mikrokontrolerów STM32 pozwala nie tylko na łatwiejsze zarządzanie kodem, ale także daje większą kontrolę nad zasobami systemu, co jest kluczowe w programowaniu systemów wbudowanych. Jeśli masz jakiekolwiek pytania dotyczące omawianego materiału, śmiało pytaj! Sekcja komentarzy jest do Twojej dyspozycji 🙂

Komentarz

  1. W końcu przystępnie dla kogoś, kto chce się uczyć.
    Cykl Sterowania Napędami to jest bomba, ale ten kurs…. mistrzostwo!
    Tak Trzymaj. Czekamy na następne odcinki.
    Wielkie Dzięki

Dodaj komentarz

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