MEMS Studio to narzędzie umożliwiające szybki start z aplikacjami opartymi na czujnikach MEMS. Dzięki niemu możemy szybko uruchomić akcelerometr, żyroskop, czy czujnik ciśnienia. Możemy odczytywać dane na wykresach w czasie rzeczywistym, dorobić logikę na podstawie wyników pomiarów, albo od razu skorzystać z Maszyn Stanu czy Machine Learningu. Problem pojawia się jednak, kiedy chcemy dodać wspomniane funkcje do istniejącego projektu. A przecież to bazowa ścieżka podczas rozszerzania realnych komercyjnych produktów o nowe funkcjonalności. Dlatego w tym artykule opiszę jak zintegrować pod wygenerowany z MEMS Studio z własnym projektem.
Co możemy zrobić w MEMS Studio?
MEMS Studio to aplikacja służąca do interakcji z czujnikami MEMS od ST. Umożliwia wygenerowanie projektu w Cube IDE dla wybranej konfiguracji sprzętowej, a także jego zbudowanie i wgranie na docelowy sprzęt. W aplikacji możemy również łączyć się z docelowym urządzeniem przy pomocy UART, Bluetooth, czy USB, odczytywać dane z czujników w czasie rzeczywistym, rysować wykresy, zbierać charakterystyki sygnałów, czy zapisywać je do późniejszej analizy.
W MEMS Studio możemy również w wygodny sposób konfigurować dwa najciekawsze moduły znajdujące się bezpośrednio na czujnikach – FSM obsługujący maszyny stanu i MLC czyli Machine Learning Core.

Z kolei Algo Builder pozwala budować własną logikę obróbki danych z czujników w interfejsie graficznym, gdzie przeciągamy bloczki i łączymy je strzałkami tworząc flow aplikacji.
Mamy również dużo przykładowych programów. Niektóre z nich są naprawdę ciekawe. Na przykład system wykrywający odholowywanie samochodu na podstawie danych z akcelerometru.
No i super. Dostajemy pełne środowisko do tworzenia projektów embedded bez pisania kodu. Szczególnie w połączeniu z gotowym sprzętem jak na przykład SensorTile.box PRO to może być dobra opcja dla osób zajmujących się przetwarzaniem danych, a na co dzień nie mających do czynienia z embedded. Wszystko generujemy w aplikacji na PC, wgrywamy na sprzęt, zbieramy dane pomiarowe – warstwa embedded staje się przezroczysta i można skupić się na akwizycji i obróbce danych wysyłanych na przykład do chmury. Programista embedded ma tu niewiele roboty – wszystko jest już gotowe.
W takim razie w czym problem?
Zwykle mamy już jakąś aplikację, która ma wiele modułów i zależności, a obsługa MEMSów ma być tylko jednym z wielu elementów. Przecież nie będziemy porzucać rozwijanego od lat komercyjnego projektu, żeby wygenerować nowy oparty na MEMS Studio i dopisać do niego obsługę naszych modułów. Zamiast całych projektów wolimy raczej wziąć biblioteki i moduły z przykładów i zintegrować je z naszym projektem. I tutaj zaczynają się schody.
Kiedy zajrzymy do wygenerowanego projektu zobaczymy, że jest on naprawdę spory. Zawiera między innymi:
- Obsługę peryferiów takich jak I2C/SPI
- Drivery dla poszczególnych sensorów
- Obsługę konfiguracji FSM i MLC
- Obsługę bloków Algo Buildera
A w przypadku projektów na SensorTile.box dodatkowo dostajemy:
- Bootloader
- Obsługę Bluetooth Low Power
- Kartę SD + System Plików
- Obsługę baterii
Czyli tak jak wspomniałem wcześniej – wszystko jest już gotowe. Napisane w pewien określony sposób, przy użyciu określonych bibliotek. Jeżeli chcemy tego używać jako gotowy system – super sprawa. Ale do własnego projektu będziemy chcieli przenieść tylko część tego kodu. W jaki sposób je odseparować, żeby nic nie zepsuć?
Możemy analizować cały kod, czytać dokumentację, sprawdzać kod przykładów i powoli przepisywać poszczególne elementy sprawdzając, czy wszystko działa. Jednak to żmudna praca. Moduły są ze sobą powiązane siecią zależności. Ciężko je wydzielić jeden po drugim. Możemy też wykorzystać system operacyjny i zamknąć obsługę MEMS Studio w oddzielnym tasku. Wtedy reszta systemu może działać niezależnie, mamy też elastyczność w dostępie do danych z sensorów, zmianach w konfiguracji czujników i przyszłej modyfikacji kodu.
Co dokładnie zrobimy?
Wiedząc, co chcemy osiągnąć i znając mocne i słabe strony projektu wygenerowanego w MEMS Studio możemy sformułować dalszy plan. W dalszej części artykułu wykorzystam:
- Płytkę NUCLEO STM32411RE
- shielda MEMS X-NUCLEO-IKS4A1
I wykonam następujące kroki:
- Wygeneruję projekt w MEMS Studio
- Uruchomię projekt w Cube IDE, zbuduję i sprawdzę jego działanie
- Przeniosę projekt w Cube do CMake – w ten sposób uniezależnie się od edytora.
- Dodam do projektu FreeRTOS
- Uruchomię wygenerowany kod jako task FreeRTOS
- Sprawdzę swoją aplikację w MEMS Studio
W efekcie dostanę aplikację, która działa tak samo jak projekt wyjściowy – pozwala na korzystanie z MEMS Studio. Ale jednocześnie zawiera RTOSa i pozwala na dalszą rozbudowę.
A więc do dzieła!
Konfiguracja projektu w MEMS Studio
Na początek otwieram MEMS Studio, wybieram zakładkę Algo Builder i tworzę nowy projekt na moją platformę sprzętową (NUCLEO STM32F411RE + X-NUCLEO-IKS4A1). Prezentacja opcji MEMS Studio nie jest celem tego artykułu, dlatego pokażę tylko efekt końcowy, który wyklikałem w Algo Builder. A zainteresowanych odsyłam do materiałów, które dołączam na końcu artykułu.

To prosta aplikacja rysująca wykresy z akcelerometru i żyroskopu, pokazująca kąt z magnetometru oraz śledząca orientację płytki za pomocą kwaternionów. Na screenie zaznaczyłem kilka przycisków, które nas będą interesowały. Możemy za ich pomocą:
- Wybrać konfigurację sprzętową
- Przygotować workspace, wygenerować pliki i zbudować projekt
- Mamy również przycisk wgrywający firmware na płytkę

Po wgraniu programu możemy przejść do zakładki Connect (po lewej stronie), wybrać port szeregowy, na którym zgłasza się nasze Nucleo i nawiązać połączenie. Możemy wtedy przejść do zakładki Algo Builder evaluation, puścić Play i zacząć wizualizację danych na wykresie.

W tym momencie mamy działający projekt wygenerowany w Algo builderze. Kolejny krok to uruchomienie projektu w Cube IDE. Ścieżka projektu odpowiada tej wybranej w algo builderze. Możemy też ją znaleźć w logach na dole w zakładce Algo Buildera po zbudowaniu projektu.
Uruchomienie projektu w Cube IDE
Aby uruchomić projekt w Cube IDE musimy go zaimportować. Wybieramy File->Import->Existing Projects into Workspace. A następnie wybieramy ścieżkę naszego projektu dla odpowiedniej płytki Nucleo i dla Cube, czyli:
<root projektu>\Project\CubeIDE\STM32F401RE-Nucleo\STM32F4xx-Nucleo-Project

Dla pewności możemy po raz kolejny zbudować projekt w Cube. Możemy również wgrać go na płytkę, zdebugować albo po prostu obejrzeć sobie zawartość plików źródłowych. Tylko najpierw musimy wybrać dobrą konfigurację w opcjach projektu. Wybieramy Properties->C/C++General->Paths and Symbols i Manage Configurations

Jak widać projekt zawiera całkiem sporo różnych modułów:

Mamy Drivery dla nakładki IKS4A1, dla Nucleo, Algo Buildera, wirtualnego portu COM, mamy biblioteki statyczne Motion. Ogólnie polecam sobie przejrzeć zawartość projektu. A jak już mamy ustawioną poprawną konfigurację to Cube nam bardzo pomoże, bo pokazuje wszystkie źródła faktycznie wykorzystywane w projekcie. Jeżeli jakiś plik jest wyszarzony i przekreślony – należy do innej konfiguracji. A jeżeli jest na dysku, ale nie ma go w projekcie – nie jest kompilowany. To wszystko za chwilę nam się bardzo przyda.
Konfiguracja CMake
Dalszą część projektu będziemy realizować w Visual Studio Code. A naszym celem będzie przeniesienie konfiguracji z Cube IDE do CMake. Właśnie dlatego przyda nam się informacja jakie pliki są wykorzystywane w projekcie. Do skryptów CMake przeniesiemy tylko te faktycznie wykorzystywane. A wszystkie inne usuniemy. Ale nie uprzedzajmy faktów.
Na początek skopiujmy projekt wygenerowany przez MEMS Studio i otwórzmy go w Visual Studio Code. Następnie będziemy sukcesywnie usuwać niepotrzebne pliki i dopisywać potrzebne do CMake. Jest to dosyć żmudna robota. Plików jest bardzo dużo, dlatego polecam sprawdzić repozytorium na GitHubie [https://github.com/ucgosupl/mems-f4], gdzie stworzyłem projekt w CMake i w poszczególnych commitach opisuję jakie pliki dodawałem do CMake i usuwałem z projektu.

CMake to kolejny wielki temat, którego nie mamy tutaj miejsca omawiać od podstaw. Dla nas najważniejsze, że jest to system budowania oparty na skryptach, które możemy uruchomić komendą z terminala i są niezależne od systemu. Musimy tylko mieć zainstalowane odpowiednie narzędzia jak np. kompilator, podać ścieżki przy konfiguracji i CMake już poradzi sobie z kompilacją. CMake wygeneruje nam skrypty Makefile albo ninja-build. Ja polecam tą drugą opcję (https://ninja-build.org/).
Więcej o CMake znajdziesz:
- W serii artykułów na moim blogu [https://ucgosu.pl/2020/12/jak-napisac-skrypt-cmake/]
- W materiałach na moim YT [https://www.youtube.com/watch?v=jcvmCxu39Y8]
Nasz skrypt CMake będzie zawierać:
- Foldery include
- Listę wszystkich plików źródłowych (oddzielnie C i ASM)
- Biblioteki statyczne
- Foldery wyszukiwania bibliotek statycznych
- Dyrektywy Define
Weźmiemy je z naszej struktury plików w skopiowanym projekcie. Ale tak jak wspomniałem wcześniej – znajdziemy tam więcej plików. Niepotrzebne pliki powinniśmy usunąć. A to czy dany plik jest potrzebny, czy nie – rozstrzygniemy posiłkując się projektem w Cube IDE.

Jak widać na screenie – nie wszystkie pliki z driverami sensorów są nam potrzebne. Są również całe foldery dla innych wersji shieldów. W bibliotece HAL również nie wszystkie źródła są wykorzystywane. Potrzebne pliki w skrypcie dodaję do C_SRCS, a niepotrzebne na razie zostawiam, żeby dodać jeśli konfiguracja będzie niepoprawna. Ale docelowo usunę je z dysku.
Możesz zobaczyć wszystkie pliki, które dodałem do projektu w pierwszej iteracji w projekcie na GitHubie [https://github.com/ucgosupl/mems-f4], najlepiej sprawdź commit “project: configure cmake” i tam będzie pierwsza wersja pliku CMakeLists.txt. Tutaj masz linka bezpośrednio do pierwszej wersji skryptu CMake:
https://github.com/ucgosupl/mems-f4/blob/c0ea4e1dd09809b3393cbda52437337bc1ea16d2/src/CMakeLists.txt
Teraz sprawdzam, czy projekt poprawnie się buduje za pomocą skryptów CMake. Aby poprawnie zbudować projekt musimy utworzyć folder na artefakty z kompilacji:
<to najlepiej jako verbose/fragment kodu>
mkdir out
cd out
<koniec kodu>
A następnie skonfigurować CMake na mikrokontroler podając odpowiedni plik toolchaina (szczegóły w materiałach dodatkowych):
<verbose>
cmake -S ../src -B . -GNinja -DCMAKE_TOOLCHAIN_FILE=”../cmake/toolchain-arm-gcc.cmake”
<koniec>
Jeżeli mamy ścieżkę do cmake i do kompilatora arm w zmiennej środowiskowej PATH – projekt powinien się poprawnie skonfigurować. Możemy wtedy zbudować projekt za pomocą ninja:
<verbose>
ninja -v
<koniec>
Poprawny build zakończy się utworzeniem plików elf, hex, bin:
<verbose>
[51/54] C:\Windows\system32\cmd.exe /C „cd /D C:\Projekty\mems\mems-f4\out-test && arm-none-eabi-objcopy -O binary mems-f4.elf mems-f4.bin”
[52/54] C:\Windows\system32\cmd.exe /C „cd /D C:\Projekty\mems\mems-f4\out-test && arm-none-eabi-objdump -x –syms mems-f4.elf > mems-f4.dmp”
[53/54] C:\Windows\system32\cmd.exe /C „cd /D C:\Projekty\mems\mems-f4\out-test && arm-none-eabi-objcopy -O ihex mems-f4.elf mems-f4.hex”
[54/54] C:\Windows\system32\cmd.exe /C „cd /D C:\Projekty\mems\mems-f4\out-test && arm-none-eabi-objdump -h -S mems-f4.elf > mems-f4.lss”
<koniec>
Jeżeli projekt nam się poprawnie nie kompiluje – czytamy komunikaty i sprawdzamy jakich plików brakuje albo które pliki dodaliśmy nadmiarowo. Może też nam brakować Include, define czy biblioteki statycznej. Czasem zdarzają się trudniejsze problemy jak np. biblioteki statyczne kompilowane na inną obsługę liczb zmiennoprzecinkowych. Wtedy w pliku cmake/compiler_flags.cmake musimy wybrać odpowiednie flagi -mfloat-abi i -mfpu. Możemy też poszukać innej wersji biblioteki statycznej .a w plikach projektu.
A kiedy projekt kompiluje się poprawnie – możemy usuwać kolejne pliki z projektu i patrzeć, czy nic nie zepsuliśmy. Kolejni kandydaci do usunięcia to:
- Drivery nieużywanych sensorów
- Wsparcie dla innych procesorów
- Wsparcie dla innych shieldów
- Wszystkie pliki niebędące kodem np. dokumentacje, readme itp.
Nie musimy się obawiać, że coś zepsujemy – mamy projekt w Gicie i zawsze możemy się cofnąć do ostatniej działającej wersji. Jedyny problem, to że proces jest żmudny. W dzisiejszych czasach nikt czegoś takiego nie będzie robić ręcznie (chyba że w celach edukacyjnych). Możemy do projektu podpiąć GitHub Copilot albo OpenAI Codex i na podstawie plików .project i .cproject z Cube IDE wygeneruje nam skrypty CMake.
Dostosowanie projektu pod FreeRTOS
Zanim dodamy do projektu RTOSa musimy trochę posprzątać. Największym nowym problemem, jaki dodamy wraz z systemem operacyjnym jest współbieżność i efekty wyścigów przy używaniu zmiennych globalnych. A niestety wygenerowany projekt używa sporo takich zmiennych. Dlatego teraz się im przyjrze i zadecyduję jak zedytować kod, żeby się ich pozbyć.
Okazuje się, że wiele zmiennych wcale nie musi być globalnych. Sprawdziłem w edytorze gdzie są używane i zadeklarowałem je jako statyczne w odpowiednich plikach. Jednak nie wszystkie zmienne globalne udało się usunąć w ten sposób. Największy problem wzbudzają zmienne:
- DataLoggerActive
- DataLoggerStatusChanged
Służące do obsługi wysyłania danych po serial porcie. Możemy włączyć/wyłączyć logowanie wciskając przycisk. Tak naprawdę to zupełnie poboczna funkcjonalność i moglibyśmy ją całkowicie usunąć z kodu. W końcu naszym celem jest wykorzystanie jedynie obsługi MEMSów. Ale zdecydowałem się na utworzenie funkcji pomocniczych obsługujących logger i zastąpienie wywołań zmiennych globalnych. Taka zmiana jest mniej inwazyjna i po prostu zadziała szybciej. A kiedy będę miał działający kod na FreeRTOS – mogę sobie usuwać niepotrzebny kod.
Kolejna zmiana wymuszona przez RTOSa to przeniesienie logiki z funkcji main do oddzielnego tasku. Na razie po prostu utworzyłem task algobuildera w osobnym pliku i w mainie tylko wywołuję funkcje ab_task_init() i ab_task().
Te zmiany sprawdzisz w repozytorium w commitach:
- project: reduce scope of global variables
- serial: refactor logger active handling
- ab: export to separate task
Dodanie FreeRTOS do projektu
Nareszcie jesteśmy gotowi na dodanie FreeRTOSa do naszego projektu. Jedną z zalet CMake jest fakt, że nie musimy ręcznie dodawać plików FreeRTOSa do naszego projektu i kopiować konfiguracji do skryptu. Możemy skorzystać z mechanizmu FetchContent, który ściągnie nam repozytorium z GitHuba w odpowiedniej wersji i wykorzysta w naszym projekcie:
<verbose>
include(FetchContent)
FetchContent_Declare( freertos_kernel
GIT_REPOSITORY https://github.com/FreeRTOS/FreeRTOS-Kernel.git
GIT_TAG V11.2.0
)
add_library(freertos_config INTERFACE)
target_include_directories(freertos_config SYSTEM
INTERFACE
Middlewares/FreeRTOS_config
)
target_compile_definitions(freertos_config
INTERFACE
projCOVERAGE_TEST=0
)
FetchContent_MakeAvailable(freertos_kernel)
<koniec>
Więcej o FetchContent na moim blogu [https://ucgosu.pl/2021/03/cmake-automatyczna-obsluga-podprojektow-z-gita/].
Aby projekt działał poprawnie musiałem również dodać odpowiedni plik konfiguracyjny FreeRTOSConfig.h oraz wprowadzić zmiany zgodne z moim projektem. Jako baza posłużyłem się przykładowym configiem z przykładów FreeRTOSa dla STM32.
Aby projekt z MEMS Studio działał poprawnie z FreeRTOSem musimy rozwiązać jeszcze jeden konflikt. MEMS Studio używa Systicka do wewnętrznych timeoutów i delayów. Teraz SysTick będzie potrzebny dla RTOSa. Aby nie ruszać wygenerowanej logiki skorzystamy z funkcji HAL_IncTick. Aby to działało we FreeRTOS Config ustawiam odpowiednią flagę:
<verbose>
#define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 0
<koniec>
A w pliku stm32f4xx_it.c dodaje wywołanie rtosowego systicka obok funkcji HAL:
<verbose>
void SysTick_Handler(void)
{
HAL_IncTick();
xPortSysTickHandler();
}
<koniec>
Poza tym w mainie teraz startuję Scheduler, a w funkcji ab_task_init muszę zrobić inicjalizację taska zgodną z API FreeRTOSa:
<verbose>
void ab_task_init(void)
{
xTaskCreate(ab_task, „ab_task”, configMINIMAL_STACK_SIZE*12, NULL, tskIDLE_PRIORITY, NULL);
}
<koniec>
Na wszelki wypadek dałem duży rozmiar stacka, żeby na pewno nie dostać żadnych błędów przepełnienia stosu podczas działania aplikacji. Kod wygenerowany przez MEMS Studio używa przerwań od peryferiów, ale nie wywołuje tam kodu RTOSa. Dlatego nie musimy dodawać nic do kodu poszczególnych przerwań tak długo jak nie będziemy używać tam kolejek, muteksów, semaforów i innych narzędzi systemu operacyjnego. Startowy projekt był wygenerowany bez RTOSa więc aktualnie nie musimy się o to martwić.
Sprawdzenie działania w MEMS Studio
Po tych wszystkich zmianach mogę sprawdzić integrację mojego projektu z MEMS Studio. Płytka poprawnie łączy się po porcie szeregowym i wysyła dane do wykresów. Czyli konfiguracja jest dokładnie taka sama jak na początku, ale mam na płytce FreeRTOSa i mogę uruchamiać swój kod w oddzielnych taskach.
Oczywiście to dopiero baza do dalszych zmian. Mogę dopisać realne taski ze swoim kodem. Mogę w jakiś sposób wchodzić w interakcje z kodem na MEMSy np. czytać dane. Mogę również wprowadzać dalsze zmiany w wygenerowanym kodzie na przykład usuwając niepotrzebne funkcje.
Na koniec jeszcze polecam live na YouTube (https://www.youtube.com/live/GiN-EfckITU), gdzie robię ten sam projekt na żywo. Możesz tam zobaczyć dokładnie realizację poszczególnych kroków.
Źródła
- dokumentacja MEMS Studio: https://www.st.com/en/development-tools/mems-studio.html
- Nagranie live z konfiguracji https://www.youtube.com/live/GiN-EfckITU
- Seria o MEMSach: https://www.youtube.com/watch?v=uc-F-XQoDQI&list=PLFnS_NedY3LlkjHY25sDwLZFEoa8MYk3h
- Repozytorium z projektem: https://github.com/ucgosupl/mems-f4
- Seria o CMake na blogu: https://ucgosu.pl/2020/12/jak-napisac-skrypt-cmake/
- Live YT o migracji z Cube IDE do CMake:
Autor: Maciej Gajdzica
