W tej części zajmiemy się stworzeniem pierwszego pustego projektu, na którym będziemy potem bazować. No to do dzieła 😉
Kilka słów wstępu
Dużym plusem FreeRTOS jest to, że istnieje wiele portów systemu dla różnych architektur procesorów i kompilatorów, dzięki czemu uruchomienie systemu jest całkiem proste. Ponadto przygotowane są aplikacje demonstracyjne dla różnych rodzajów mikrokontrolerów, a gotowe projekty są dostępne dla różnych IDE, np. Keil czy IAR.
Wszystkie aktualne, niezbędne pliki: kody źródłowe systemu oraz aplikacje demonstracyjne można pobrać w spakowanej formie na stronie producenta. Dostępne dema są już odpowiednio skonfigurowane, wystarczy uruchomić i skompilować projekt, wgrać na płytkę i wszystko powinno działać. Dokładny opis aplikacji demonstracyjnych znajduje się tutaj. Jak pewnie zauważysz są one zazwyczaj przygotowane pod rozbudowane płytki ewaluacyjne, których ceny do niskich nie należą. Jednak, bez obaw – możesz wykorzystać inną płytkę. Wystarczy, że mikrokontroler jest ten sam. Producent systemu rekomenduje, aby na początku własne projekty tworzyć poprzez modyfikowanie istniejących aplikacji. Niestety, gdy odpalimy po raz pierwszy taki demonstracyjny projekt, nie mając jeszcze żadnej wiedzy na temat tego systemu, sama zawartość głównego pliku main.c może przytłoczyć. Są tam demonstrowane już najróżniejsze mechanizmy systemu, o których początkujący jeszcze nie ma pojęcia, a do tego trzeba zmodyfikować kod tak, aby działał pod naszą płytką. Kolejna sprawa – jak wspominałem na początku, przykładowe projekty są dostępne dla różnych IDE, ale niekoniecznie dla tego, w którym zazwyczaj pracujesz… Dlatego w tym wpisie zajmiemy się stworzeniem własnego pustego, czystego projektu! Zrobimy to na przykładzie mikrokontrolera STM32F103 i w środowisku AC6 – System Workbench for STM32, ale wykorzystanie innego mikrokontrolera czy środowiska nie powinno stanowić dużego problemu. Gotowy projekt dla STM32F103, ale także dla STM32F429 znajdziesz na moim GitHubie.
Zaczynamy!
Mam nadzieję, że pobrałeś już wszystkie potrzebne pliki i z grubsza przejrzałeś zawartość folderów z katalogu FreeRTOS:
- Demo: zawiera projekty demonstrujące, o których wspomniałem we wstępie. Co ciekawe, są tam także dema, które można odpalić pod windowsem, dzięki czemu możesz potestować i pouczyć się obsługi systemu bez posiadania żadnego mikrokontrolera. Informacje o tym jak uruchomić FreeRTOS na PC znajdziesz tutaj oraz tutaj.
- License: zawiera warunki licencyjne
- Source: zawiera pliki źródłowe systemu (najważniejsze są tasks.c, queue.c oraz list.c, z których składa się jądro systemu. Pozostałe są wykorzystywane do dodatkowych funkcjonalności i nie są niezbędne do poprawnego działania systemu) oraz folder include, gdzie znajdują się pliki nagłówkowe. Ponadto jest tam folder Portable, w którym znajdują się pliki dla poszczególnych kompilatorów oraz rdzeni procesorów.
Struktura projektu
Na początku tworzymy standardowy projekt – jak każdy inny. Jeżeli korzystasz z bibliotek standardowych to je dodajesz – jeżeli HAL to HAL, jeżeli tylko CMSIS to tylko CMSIS. Następnie tworzymy source folder na pliki systemu o nazwie FreeRTOS. U mnie wygląda to tak:Do folderu FreeRTOS wrzucamy folder Source z pobranej paczki. W folderze Source/Portable zostawiamy tylko folder MemMang oraz folder odpowiadający używanemu kompilatorowi – w tym wypadku jest to GCC. W folderze GCC pozostawiamy tylko folder odpowiadający naszemu mikrokontrolerowi – w typ wypadku jest to STM32F103, który jest oparty na rdzeniu ARM Cortex-M3, więc zostawiamy folder ARM_CM3. W folderze MemMang mamy przykładowe implementacje zarządzania pamięcią – należy zostawić tylko jeden z nich, a pozostałe usunąć. Ja zostawiłem heap_4.c. Na tym etapie nie jest to jednak bardzo istotne zagadnienie, więc pominę opis poszczególnych plików. W razie czego, więcej na ten temat można dowiedzieć się tutaj. Aktualnie, zawartość projektu powinna wyglądać następująco:
Plik konfiguracyjny
FreeRTOS jest systemem skalowalnym, tzn. jego funkcjonalności i możliwości, a co za tym idzie, także wielkość, możemy dostosowywać do swoich potrzeb i zasobów mikrokontrolera. W związku z tym, każdy projekt wymaga dołączenia pliku konfiguracyjnego o nazwie FreeRTOSConfig.h. Typową strukturę tego pliku znajdziemy tutaj. Na tej samej stronie są zamieszczone także opisy wszystkich dyrektyw. Jak widać wszystko ustawiamy w tym pliku i jest to całkiem proste. Dla przykładu, wyłączenie wywłaszczania sprowadza się wyłącznie do zmiany wartości configUSE_PREEMPTION na 0. Są jednak dyrektywy, których wartości zależą od rodzaju procesora i ich ustawienie nie jest już tak banalne, dlatego na początku polecam skorzystanie z gotowego pliku konfiguracyjnego dostępnego w aplikacji demonstracyjnej dopasowanej do naszego procesora. Jedyne o czym należy pamiętać to ustawienie odpowiedniej częstotliwości zegara procesora za pomocą dyrektywy configCPU_CLOCK_HZ oraz ustawienie dyrektyw configUSE_IDLE_HOOK oraz configUSE_TICK_HOOK na zero. Te, jak i wiele innych będę jeszcze opisywał w kolejnych artykułach dotyczących poszczególnych mechanizmów systemu. Plik konfiguracyjny umieszczamy w folderze inc.
Ścieżki bibliotek
Pliki mamy już dodane do projektu, ale trzeba jeszcze dodać ścieżki do bibliotek w ustawieniach kompilatora. W Eclipse i jego pochodnych, wchodzimy w Project->Properties. W oknie, które się pojawiło rozwijamy C/C++ Build, gdzie wybieramy Settings. W MCU GCC Compiler: includes dodajemy ścieżki:
1 2 |
"${ProjDirPath}/FreeRTOS/Source/include" "${ProjDirPath}/FreeRTOS/Source/portable/GCC/ARM_CM3" |
Całość widoczna jest na poniższym zdjęciu (kliknij, aby powiększyć):
W innych IDE wygląda to często bardzo podobnie. Przy okazji pokażę jak można znacznie przyspieszyć proces budowania. W C/C++ Build wybieramy zakładkę Behavior, następnie w boxie Build Settings zaznaczamy Enable parallel build:
Wektory przerwań
Projekt już powinien się zbudować jednak system jeszcze działać nie będzie. Do poprawnej pracy systemu niezbędne jest jeszcze podpięcie procedury obsługi przerwania od timera systemego SysTick zaimplementowanej w systemie. W przypadku procesorów z rdzeniem ARM Cortex-M konieczne jest jeszcze podpięcie dwóch dodatkowych: PendSV oraz SVCCall.
Zrealizować to można na dwa sposoby:
- bezpośrednia podmiana nazw funkcji w tablicy wektorów przerwań w pliku startowym: SysTick_Handler na xPortSysTickHandler, PendSV_Handler na xPortPendSVHandler oraz SVC_Handler na vPortSVCHandler.
- dodanie dodatkowych dyrektyw w pliku konfiguracyjnym FreeRTOSConfig.h, które spowodują zmapowanie handlerów z FreeRTOSa do ich nazw w standardzie CMSIS:
123#define vPortSVCHandler SVC_Handler#define xPortPendSVHandler PendSV_Handler#define xPortSysTickHandler SysTick_Handler
Główna funkcja programu
Został do przygotowania już tylko plik główny 🙂 Na początku dołączamy potrzebne biblioteki. Na razie wystarczą pliki FreeRTOS.h oraz task.h. Wraz z poznawaniem nowych funkcjonalności systemu będą dochodziły kolejne. Na początku funkcji main wywołujemy prvSetupHardware(), która jest zdefiniowana na samym dole. Tutaj jest ona pusta, ale wszystkie ustawienia sprzętowe, takie jak np. ustawienia zegara, warto wykonywać w tym miejscu. W dalszej części jest miejsce na tworzenie zadań i innych mechanizmów systemowych, a na końcu wywołujemy funkcję rozpoczynającą pracę systemu. Przy poprawnej pracy systemu nigdy nie powinniśmy z niej wrócić.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include "stm32f10x.h" #include "FreeRTOS.h" #include "task.h" static void prvSetupHardware(void); int main(void) { // Hardware configuration prvSetupHardware(); // Creating tasks // .. // .. // Start the scheduler vTaskStartScheduler(); // should never return // Will only get here if there was not enough heap space while(1); return 0; } static void prvSetupHardware(void) { // It's place to hardware configuration, like e.g. clock external source } |
Nazewnictwo zmiennych oraz funkcji
Jak może zauważyłeś funkcje w powyższym kodzie mają charakterystyczne przedrostki. Otóż, do nazewnictwa zmiennych oraz funkcji przyjęto specjalny standard.
Nazwy zmiennych
Nazwy zmiennych są poprzedzane pierwszą literą typu zmiennej, jak np. c dla char (np. cName), s dla short lub l dla long. Nazwy zmiennych wskaźnikowych dodatkowo poprzedzane są znakiem p. Jest jeszcze charakterystyczny prefiks x, który jest wykorzystywany do specjalnych typów zdefiniowanych w systemie.
Nazwy funkcji
Każda funkcja poprzedzona jest przedrostkiem pochodzącym od typu zwracanej wartości oraz pliku w jakim została zdefiniowana. Dla przykładu vTaskStartScheduler(): v pochodzi od void, a Task, ponieważ jest zdefiniowana w pliku task.c. Wykorzystywany jest także specjalny prefiks prv, który przeznaczony jest dla funkcji statycznych (prywatnych): static void prvSetupHardware().
Podsumowanie
Jest! Mamy projekt, który w zasadzie nic nie robi… 😀 no niestety, niektórych rzeczy się nie przeskoczy. W kolejnym wpisie stworzymy już jakieś zadania, dzięki czemu będziemy mieli naoczny efekt naszej pracy – bazę już mamy 🙂 Domyślasz się co to będzie? 😀
We wpisie wspomniałem kilka zagadnień, ale ich nie rozwijałem, ponieważ uznałem, że bez sensu byłoby zasypywanie Cię toną teorii, która aktualnie byłaby i tak ciężka do zrozumienia, a ponadto mogłaby zniechęcić albo przestraszyć. Część tych rzeczy będzie rozwijana w kolejnych wpisach – w praktyce. Najpierw nauczmy się jak korzystać z systemu, a z czasem będzie można eksplorować jego wnętrzności 😉
W razie jakiś problemów cały projekt możesz pobrać z mojego GitHuba – wystarczy go zaimportować w AC6 Workbench for STM32.
Trzymaj się, do zobaczenia w kolejnym wpisie! 😉