FreeRTOS

Krótka instrukcja jak “recznie” dodać do projektu CubeMX moduł FreeRTOS bez wykorzystania wbudowanego modułu (ponieważ wtedy dodawany jest także CMSIS-OS, jako nakładka na FreeRTOSa, a także wersja z Cube zazwyczaj jest sporo starsza niż aktualnie dostępna w repozytorium jako release), tak przygotowany projekt można wykorzystywać jako template dla kolejnych projektów - ja wykorzystywałem go jako bazę do ćwiczeń mechanizmów FreeRTOSa.

Użyta platforma to nucleo z STM32L476RG.

Za pomocą CubeMX wyklikujemy testową konfiguracje z 3 diodkami - na pinach D2-D4 w notacji arduino (jeśli chodzi o piny STMa będą to kolejno: PA10, PB3 i PB5).

pinout

Okazało się, że PB3 nie może być wykorzystany - z powodu alternatywnej funkcji SWO, która może się przydać w przyszłości - więc kolejny to PB4. Czyli diody będą podłączone do PA10, PB5 i PB4

pinout

Dalej już tylko wybór ścieżki do projektu i projektu dla Atollica:

projekt

  • u mnie pobranie paczki z bibliotekami -> 750MB!

Podstawowa struktura plików + testowy blink działa ok.

projekt

Pobieramy więc FreeRTOSa z jego głównej strony. Aktualnie jest to wersja 10.3.1.

Początkowe poziomy struktury katalogów wyglądają następująco: projekt

Opisy znajdują się w plikach readme. Nas interesuje katalog FreeRTOS/Source. To go musimy przekopiować do projektu. Znajdują się tam “ogólne” pliki systemu + część “porbable” specyficzna dla platformy. Dla mnie to GCC->ARM_CMF4F. Reszty całkowicie można się pozbyć.

Potrzebujemy pliku FreeRTOSConfig.h - jest to plik specyficzny “per aplikacja” z makrami do uruchamiania poszczególnych funkcjonalności systemu. Weźmy go z katalogu z przykładami

  • dla mnie to przykład “CORTEX_M4F_STM32F407ZG-SK”. Należy go wrzucić w katalog z includami w projekcie.

Po przekopiowaniu plików struktura wygląda tak: projekt

FreeRTOS wrzucony w katalog src - to jest akurat dyskusyjne i można to zrobić na mnóstwo lepszych sposobów - o organizacji katalogów zapewne będzie cały wpis kiedyś. Przy takim układzie w configu projektu musimy dodać do ścieżek includów katalogi:

  • Src/FreeRTOS/include
  • Src/FreeRTOS/portable (chociaż ten można też np pominąć i rozdzielić pliki pomiędzy główny i include - znów kwestia ułożenia)

Po kompilacji okaże się, że musimy jeszcze:

  • zdefiniować jaka jest prędkość naszego układu makrem configCPU_CLOCK_HZ
  • Z pliku z przerwaniami należy skasować obsługę do:
    • SVC_Handler
    • PendSV_Handler
    • SysTick_Handler

Ale dla SysTicka polecam inne rozwiązanie - systick jest też używany przez HALowskie rzeczy do ikrementacji liczników itp (w Cube można też przeklikać, żeby inny timer przejął to zadanie)- dlatego można odwrócić zależność i w wygenerowane przerwanie przez CubeMX wkleić wywołanie RTOSowe, a zakomentować odpowiednią linijkę w configu OSa do tego.:

#include "FreeRTOS.h"
#include "task.h"

extern void xPortSysTickHandler(void);

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();

  /* USER CODE BEGIN SysTick_IRQn 1 */
  if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
  {
      xPortSysTickHandler();
  }

  /* USER CODE END SysTick_IRQn 1 */
}

Niestety - extern dlatego, że jest to funkcja nie wyprowadzona do żadnego headera i tak to można zostawić jeśli za wszelką cene nie chcemy ingerować w pliki RTOSa. Swoją drogą tak samo robi np CMSIS-os. Można też przepiąć HAL-owski tick na inne przerwanie

  • jak kto woli - nie będzie wtedy potrzebna taka poprawka, a basicTimerów mamy pod dostatkiem w STMkach.

Kolejnym krokiem będzie zdefiniowanie funkcji “vApplicationStackOverflowHook” - nazwa sama mówi do czego ona jest. Dajmy ją “pustą” w mainie:

void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
{
    while(1);
}

Potrzebujemy jeszcze dorzucić do projektu plik “heap_x.c”, który odpowiada za sposób alokacji pamięci. Tu odsyłam do dokumentacji po szczegóły - ja użyje heap_4 - skopiowałem więc plik z katalogu portable/MemMang do katalogu portable.

I ostatnie to zmiany: configUSE_TICK_HOOK, configUSE_MALLOC_FAILED_HOOK i configUSE_IDLE_HOOK na 0.

Projekt się kompiluje poprawnie.

Szybkie 3 taski z miganiem diodami:

void vTask1( void * pvParameters )
{
    for (;;)
    {
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
        vTaskDelay(1000);
    }
}

void vTask2( void * pvParameters )
{
    for (;;)
    {
        HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
        vTaskDelay(1100);
    }
}

void vTask3( void * pvParameters )
{
    for (;;)
    {
        HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
        vTaskDelay(1200);
    }
}

Dodanie tasków w funkcji main:

  xTaskCreate( vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);
  xTaskCreate( vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);
  xTaskCreate( vTask3, "Task3", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL);
  vTaskStartScheduler();

Projekt do pobrania tutaj (ale razem z dodanym już SEGGER SystemViewer z innego wpisu): pobierz projekt.