STM32Cube w kilku krokach [1]. Jak zacząć?

Uwaga! Druga część cyklu jest dostępna pod adresem.

STM32 to dzisiaj olbrzymia rodzina mikrokontrolerów z rdzeniem ARM Cortex-M. W jej skład wchodzą zarówno stosunkowo proste jednostki STM32F0 jak i zaawansowane STM32F4, czy najnowsze STM32F7 z rdzeniem Cortex- M7. 32 bitowe mikrokontrolery to układy przeznaczone do bardziej wymagających zadań sterowania i kontroli i z tego powodu są wyposażone w wiele układów peryferyjnych i konfigurowany, rozbudowany układ taktowania. Z powodu różnych zadań stawianych mniej zaawansowanym i bardziej zaawansowanym konstrukcjom nie jest możliwe, by konfigurowanie na przykład peryferii w STM32F0 mogło się odbywać w taki sam sposób jak STM32F4. Również w obrębie tej samej rodziny mogą występować spore różnice wynikające z wyposażenia poszczególnych układów. To wszystko powoduje, że wstępny etap projektu polegający na konfigurowaniu peryferii, układu przerwań, układu taktowania i napisaniu prostych driverów urządzeń peryferyjnych może być bardziej pracochłonny niż napisanie właściwej aplikacji. Szczególnie uciążliwe jest zapoznawanie  się z dokumentacją zawierającą opis rejestrów konfiguracyjnych i wielu programistów nie maiło by nic przeciw temu by ktoś za nich ten etap pracy wykonał.

Programiści Corteksów od jakiegoś czasu mają do dyspozycji biblioteki CMSIS (Cortex Microcontroller Software Interface Standard) jest uniwersalnym interfejsem programowym, który umożliwia obsługę układów peryferyjnych i rdzenia Cortex wykorzystując do tego celu zestandaryzowane funkcje, makra i definicje. Funkcje CMSIS wspierają też użycie systemów operacyjnych czasu rzeczywistego, middleware, oraz aplikacji używających interfejsy komunikacyjne np. I2C, UART, SPI, ale także Ethernet. Każdy z producentów mikrokontrolerów dostarcza swoje implementacje bibliotek z funkcjami obsługującymi własne peryferia.

Idea CMSIS opiera się na założeniu, że programy pisane na mikrokontrolery z rdzeniami Cortex-M, ale produkowane przez różnych producentów mogą być w prosty sposób przenoszone na różne platformy sprzętowe. Drugim założeniem było uproszczenie etapu konfiguracji i inicjalizacji układów peryferyjnych i rdzenia. Żeby było to możliwe funkcje CMSIS muszą mieć pewien stopień abstrakcji. Oznacza to, że „ukrywają” przed programistą działania na rejestrach konfiguracyjnych. Tworzy się w ten sposób umowna warstwa HAL – Hardware Abstraction Layer. Takie podejście zostało różnie odebrane w środowisku programistów. Część z nich zaczęła chętnie stosować gotowe biblioteki, ale dla innych CMSIS było tylko nie potrzebnym komplikowaniem programowania. Wątpliwości, czy niechęć wynikały po części z przyzwyczajeń i chęci panowania nad wszystkim co się dzieje w programie. Trochę to przypominało przejście od programowania w asemblerze do programowania w C. Drugim dużo poważniejszym argumentem przeciw CMSIS była spora liczba błędów i nadmierne wykorzystywanie zasobów mikrokontrolera. Dodatkowo procedury obsługi peryferii potrafiły czekać w nieskończoność na zmianę stanu jakiegoś bitu zatrzymując skutecznie działanie całej aplikacji. Cześć z programistów wybrało rozwiązanie pośrednie: patrzymy jak to zrobiono w CMSIS i na podstawie tego robimy po swojemu.

Ponieważ CMSIS jest obecne już przez wiele lat, to wiele błędów zostało poprawionych, a funkcje zoptymalizowane i dostosowane do wykorzystania w różnych konfiguracjach implementacji interfejsu API: polling, przerwania, lub DMA. Jednak samo użycie bibliotek, chociaż sporo ułatwia ( przy założeniu, że nie mają błędów) nie zwalnia całkowicie od ręcznej konfiguracji mikrokontrolera. I tutaj z pomocą przychodzi rozwiązanie oferowane przez STM nazwane STMCube.

 

Rozwiązanie problemów: STM32Cube dla STM32

Ideą przyświecającą powstaniu STM32Cube było ułatwienie konstruktorom życia przez znaczne zredukowanie wysiłku włożonego w projekt i tym samym ograniczenie czasu potrzebnego na wykonanie projektu. STM Cube składa się z:

  • Graficznego środowiska STM32CubeMX generującego kod w języku C na podstawie własnego projektu
  • Obszernej platformy programowej dostarczanej osobno dla każdej z rodzin mikrokontrolerów i zawierającą warstwę STM32Cube HAL zapewniającą obsługę wszystkich mikrokontrolerów STM32, oraz zgodne z tą warstwą komponenty programowe middleware takie jak RTOS, stos USB, stos TCP/IP i biblioteka graficzna.

 

Rys. 1. Okno startowe STM32CubeMX

 

Pracę z STM32Cube rozpoczynamy od pobrania ze strony stm.com i zainstalowania pakietu STM32CubeMX. Jak w innych tego typu programach narzędziowych praca odbywa się w oparciu o plik projektu. Na ekranie powitalnym są wyróżnione dwie akcje: tworzenie nowego projektu – New Project i otwieranie wcześniej zapisanego projektu – Load Project. Zaczynamy od tworzenia nowego projektu.  Jednym z pierwszych zadań konstruktora urządzeń embeded jest wybór mikrokontrolera. Nie jest to zadanie proste nawet wtedy, kiedy już zdecydowaliśmy się na mikrokontroler rodziny STM32. Portfolio tego producenta jest obszerne i składa się z bardzo dużej liczby produkowanych układów o różniących się nie tylko ilością układów peryferyjnych, rozmiarem pamięci, ale nawet architekturą rdzenia. Przejrzenie tylko pierwszych stron kart katalogowych może zabrać sporo czasu. STM32CubeMX stara się pomóc poruszać w gąszczu możliwości  oferując rozbudowany układ selektora mikrokontrolerów – rysunek 2.

Okno wyboru ma dwie zakładki: MCU Selector i Board Selector. W przypadku MCU Selector można filtrować potrzebne zasoby wybierając:

  • serię STM32: STM32F0, STM32F1, STM32F2, STM32F3, STM32F4, STM32L0 i STM32L1
  • Linie STM32: na przykład STM32F0x0 Value Line
  • Obudowę układu: na przykład LQFP64
  • Inne filtry: na przykład ze względu na ilość linii I/O

Alternatywnie dla filtrów można w oknie Peripherial Selection wybrać mikrokontroler ze względu na rodzaj i ilość układów peryferyjnych. Zakładka MCU Selector pozwala w szybki sposób znacznie zwęzić obszar poszukiwań i w konsekwencji wybrać odpowiednią jednostkę dla naszego projektu.

Duga zakładka Board Selector jest używana, kiedy chcemy wykonać projekt uruchamiany na płytkach ewaluacyjnych oferowanych przez STM. Modułów uruchomieniowych jest sporo i można je podzielić na trzy grupy:

  • Moduły Nucleo z mikrokontrolerami serii STM32F0, STM32F3, STM32F4, STM32L0 i STM32L1
  • Moduły Discovery z mikrokontrolerami serii STM32F0, STM32F3, STM32F4, STM32L0 i STM32L1
  • Moduły Eval Board z mikrokontrolerami serii STM32F0, STM32F1, STM32F2, STM32F3, STM32F4 i STM32L1

Podobnie jak w zakładce MCU Selector tak i tu można filtrować wybierając kryteria: producenta ( na razie tylko STMicroelectronics), rodzaju modułu (Nucleo, Discovery, Eval) i serii MCU (STM32F0, STM32F1 itp.)

Rys. 2. Okno wyboru mikrokontrolera

 

Wybór modułu dodatkowo ułatwia zdjęcie wyświetlające się po prawej stronie okna (rysunek 3). Ja do dalszych testów działania STM32Cube wybrałem moduł STM32F4Discovery z mikrokontrolerem STM32F407VG. W zakładce Peripherial Selection można zobaczyć (oznaczone na zielono) układy peryferyjne możliwe do wykorzystania: akcelerometr, układ audio, przyciski, kompas, żyroskop, interfejs USB. Pod zdjęciem modułu są wyświetlone jego podstawowe właściwości, a po kliknięciu na przycisk Load User Manual jest automatycznie pobierana i otwierana instrukcja użytkownika. Dodatkowo po kliknięciu na Link to ST WebSite otwiera się firmowa strona z opisem modułu i wszystkim dostępnymi materiałami typu noty aplikacyjne, przykładowe programy, schematy itp.

 

Rys. 3. Okno wyboru modułu ewaluacyjnego

 

Zatwierdzenie wybranego modułu powoduje przejście do kolejnego etapu tworzenia projektu. Otwiera się główne okno projektu z zakładkami:

  • Pinout
  • Clock Configuration
  • Configuration
  • Power Consumption Calculator

 

Zakładka Pinout

W zakładce Pinout przypisuje się wyprowadzeniom mikrokontrolera funkcje alternatywne. Wyprowadzenie może być linią GPIO wejściową, lub wyjściową, może być wejściem analogowym, lub spełniać rolę wyprowadzenia sygnałów modułów peryferyjnych. Okno zakładki Pinout składa się z rozwijanej listy z nazwami modułów peryferyjnych i okna z narysowaną obudową mikrokontrolera z wyprowadzeniami – rysunek 4. Wyprowadzenia są zaznaczane kolorami:

  • Szarym, kiedy do wyprowadzenia nie jest przypisana żadna funkcja
  • Zielonym, kiedy do wyprowadzenia jest przypisana i zdefiniowana funkcja ( na przykład sygnał modułu I2C, lub GPIO)
  • Pomarańczowym, kiedy do modułu jest przypisana funkcja, ale nie jest aktywna
  • Żółtym wyprowadzenia zasilania

 

Rys. 4. Okno Pinout

Najłatwiej konwencję kolorowania wyprowadzeń jest pokazać na przykładzie.

  1. Klikamy na wyprowadzenie PC5 i wybieramy GPIO_Input. Ponieważ funkcja portów nie wymaga dodatkowej konfiguracji, to wyprowadzenie przebarwia się na zielono.
  2. Jeżeli chcemy żeby wyprowadzenie PC5 było wejściem przetwornika ADC, to ponownie na nie klikamy i wybieramy ADC1_IN15. Wyprowadzenie przebarwia się na pomarańczowo.
  3. Żeby wyprowadzenie zdefiniowane jako ADC1_IN15 przebarwiło się na zielono trzeba w oknie Configuration wybrać ADC1 i zaznaczyć IN15 – zostało to pokazane na rysunku 5

W taki sam sposób są konfigurowane pozostałe wyprowadzenia używane w projekcie. Każdy układ peryferyjny prawidłowo przypisany do wyprowadzenia jest wyświetlany na zielono w oknie Configurations zakładki Pinout. Ma to tez inne konsekwencje: prawidłowo przypisany do wyprowadzenia układ peryferyjny pojawi się w oknie Configuration, gdzie można go dokładnie skonfigurować. Definiowanie przypisania w oknie Pinout nie pozwala przypisać funkcji peryferii do już zajętego wyprowadzenia. W naszym przykładzie w konfiguracji przypisania przetwornika ADC1 są zaznaczone na czerwono wejścia IN0, IN4. IN5, IN6, IN7, IN10 i IN3. Na przykład wyprowadzenie PA4 jest już przypisane do I2S3_WS i nie może zostać przypisane do ADC1_IN4.

STM32CubeMX skutecznie zabezpiecza przed próbą przypisania do jednego wyprowadzenia funkcji więcej niż jednego modułu peryferyjnego.

Rys. 5. Konfigurowanie wyprowadzenia PC5 jako wejścia ADC1_IN15

 

Po przypisaniu do wyprowadzenia PC5 wejścia IN15 przetwornika ADC1 przechodzimy do zakładki Configuration, gdzie przetwornik zostanie skonfigurowany. Zakładka Configurations jest podzielona na dwa okna. W oknie Configurations są wyświetlone wszystkie elementy, które mogą zostać skonfigurowane w projekcie. W naszym przypadku są to elementy Middleware FATFS i FREERTOS, oraz układy peryferyjne możliwe do konfiguracji, w tym również nasz przetwornik ADC1. Drugie okno zawiera pola: Middlewares, Multimedia, Control, Analog, Connectivity i System.

W polu Analog jest wyświetlana ikona naszego przetwornika ADC1, a w polu System ikony: DMA, GPIO, NVIC i RCC. Żeby skonfigurować przetwornik trzeba kliknąć na ikonę ADC1 w polu Analog i pojawia się okno ADC1 Configuration – rysunek 6.

Okno konfiguracji ma cztery zakładki: Parameter Settings, NVIC Settings, DMA Settings i GPIO Settings

W zakładce Parameter Settings ustawia się parametry pracy przetwornika, miedzy innymi tryb pracy, częstotliwość taktowania (preskaler PCLK2), rozdzielczość w bitach, umieszczenie danych w słowie (data Alignment) itp.

W zakładce NVIC odblokowuje się przerwania od modułu ADC1, ADC2 i ADC3. W zakładce DMA można zdefiniować kanał DMA do przesyłania danych z konwersji bezpośrednio do pamięci.

Ostatnia zakładka GPIO Settings jest przeznaczona do konfigurowania trybu pracy wyprowadzenia ADC1_IN15. Ponieważ w trakcie przypisywania wejście PC5 zostało skonfigurowane jako Analog Input, to w naszym przypadku nie potrzeba wykonywać tu żadnych zmian.

Rys. 6. Konfiguracja przetwornika ADC1

 

Można teraz zobaczyć jak STM32CubeMX poradzi sobie z wygenerowaniem plików źródłowych dla konfiguracji przetwornika. W pierwszym kroku trzeba określić sposób generowania plików źródłowych. Klikamy na Project-> Settings (Alt P). Trzeba tu ustawić:

  • Nazwę projektu
  • Ścieżkę dostępu do katalogu projektu
  • Środowisko projektowe IDE

STM32CubeMX może wygenerować kompletny projekt dla EWARM, uVision4 i uVision 5 (MDK-ARM), True STUDIO i SW4STM32. Ja wybrałem pakiet uVision4 firmy Keil (ARM). W zakładce Code Generator okna Project Settings można zdecydować, czy generator kodu skopiuje wszystkie biblioteki warstwy HAL do projektu, czy tylko skopiuje tylko te biblioteki, które są potrzebne po wygenerowaniu kodu przez STM32CubeMX.

W przypadku projektów generowanych przez tego typu aplikacje zawsze jest problem co zrobić z kodem już napisanym przez użytkownika w momencie, kiedy decydujemy się zmienić konfigurację peryferii lub middleware przez STM32CubeMX. Najlepiej gdyby automatycznie nowo generowany kod nie niszczył kodu użytkownika, a tylko robił w plikach niezbędne zmiany konfiguracyjne. Dlatego w opcjach generowania kodu należy zaznaczyć Keep User Code when re-generatting. Pewnym ograniczeniem tej funkcji jest to, że kod przeznaczony do zachowania musi znaleźć się pomiędzy komentarzami /* USER CODE BEGIN */ i /* USER CODE END */. Jeżeli nasze procedury łącznie z definicjami zmiennych zostaną umieszczone poza komentarzami, to przy kolejnym generowaniu projektu z konfiguracja zostaną usunięte. Trzeba o tym pamiętać, żeby sobie nie zniszczyć efektów swojej pracy.

Kod inicjalizacji przetwornika wygenerowany przez STM32CubeMX został pokazany na listingu 1.

 

Listi. 1. Konfiguracja przetwornika ADC1 wygenerowana przez STM32CubeMX

Skonfigurowany przetwornik trzeba jeszcze zainicjalizować. Inicjalizacja będzie wymagała włączenia taktowania modułu ADC i ustawienia wyprowadzenia PC5 jako wejścia analogowego. STM32Cube MX generuje automatycznie funkcję inicjalizacyjną HAL_ADC_MspInit (Msp to akronim od MCU support package). Ta funkcja jest umieszczana przez generator w pliku stm32f4xx_hal_msp.c, a jej wywołanie wygląda następująco:

HAL_ADC_MspInit(&hadc1);

 

List. 2. Inicjalizacja przetwornika ADC1


Uwaga! Druga część cyklu jest dostępna pod adresem.


Tomasz Jabłoński

Autor: