STM32のFreeRTOSを使ってみる

タスクのスケジューリング


STM32のFreeRTOSでタスクを複数同時に起動したときの動作を確認してみました。

まずは同じ優先度のタスクを4つ連続して起動してみました。
以下のコードをビルドしてSTM32に書き込みます。
ConsumptionTick()は指定したTick数、時間稼ぎをする関数です。
#include <STM32FreeRTOS.h>
#include <stdarg.h>

SemaphoreHandle_t xSemaphore;

void _printf(const char *format, ...) {
    xSemaphoreTake(xSemaphore, portMAX_DELAY);
    va_list va;
    va_start(va, format);
//    TickType_t _nowTick = xTaskGetTickCount();
//    char * _taskName = pcTaskGetTaskName( NULL );
//    printf("[%s:%d] ",_taskName, _nowTick);    
    vprintf(format, va);
    va_end(va);
    xSemaphoreGive(xSemaphore);
}

// 時間稼ぎ(Gain time)
void ConsumptionTick(int delay) {
    TickType_t startTick;
    TickType_t endTick;
    TickType_t nowTick;
    startTick = xTaskGetTickCount();
    endTick = startTick + delay;
    //printf("startTick=%d endTick=%d\n",startTick,endTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if (nowTick > endTick) break;
    }
}

// Task Body
void task(void *pvParameters)
{
    UBaseType_t prio;
    TickType_t nowTick;

    prio = uxTaskPriorityGet( NULL );
    nowTick = xTaskGetTickCount();
//    printf("[%s:%d] start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
    _printf("[%s:%d] start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
    ConsumptionTick(200);
    nowTick = xTaskGetTickCount();
//    printf("[%s:%d] end\n",pcTaskGetName(0),nowTick);
    _printf("[%s:%d] end\n",pcTaskGetName(0),nowTick);
    vTaskDelete( NULL );
}


//------------------------------------------------------------------------------
void setup() {
  portBASE_TYPE xTask1, xTask2, xTask3, xTask4;

  Serial.begin(115200);
  Serial.println("setup() start");
  Serial.print("configTICK_RATE_HZ:");
  Serial.println(configTICK_RATE_HZ);
  Serial.print("portTICK_PERIOD_MS:");
  Serial.println(portTICK_PERIOD_MS);
  Serial.print("freeRTOS version:");
  Serial.println(tskKERNEL_VERSION_NUMBER);

#if 1
// Task1 is started in priority=2
// Task2 is started in priority=2
// Task3 is started in priority=2
// Task4 is started in priority=2
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
#endif

#if 0
// Task1 is started in priority=2
// Task2 is started in priority=3
// Task3 is started in priority=4
// Task4 is started in priority=5
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 4, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
#endif

  /* Check everything was created. */
  configASSERT( xTask1 );
  configASSERT( xTask2 );
  configASSERT( xTask3 );
  configASSERT( xTask4 );

  /* Create Mutex for printf */
  xSemaphore = xSemaphoreCreateMutex();
  /* Check everything was created. */
  configASSERT( xSemaphore );
 
  // start scheduler
  Serial.println("Start Scheduler.....");
  vTaskStartScheduler();
  while (1) {
    Serial.println("Insufficient RAM");
    delay(1000);
  }
}

//------------------------------------------------------------------------------
// WARNING loop() called from vApplicationIdleHook(), so don't use this function.
// loop must never block
void loop() {
  // Not used.
}

結果は以下の様になりました。
こちらに FreeRTOSのタスク・スケジューリングの詳細が公開されていますが、pre-empteve方式を採用しています。
全てのタスクは同じ優先度で実行可能状態になりますが、全てのタスクに「公平な」割合のプロセッサ時間を与えているので
ほぼ同時にタスクが完了します。
setup() start
configTICK_RATE_HZ:1000
portTICK_PERIOD_MS:1
freeRTOS version:V9.0.0
Start Scheduler.....
[Task1:0] start Priority=2
[Task2:1] start Priority=2
[Task3:1] start Priority=2
[Task4:1] start Priority=2
[Task1:211] end
[Task2:214] end
[Task3:222] end
[Task4:233] end



次に、後から起動するタスクの優先度を上げてみました。
以下の部分を変更し、ビルドしてSTM32に書き込みます。
#if 0
// Task1 is started in priority=2
// Task2 is started in priority=2
// Task3 is started in priority=2
// Task4 is started in priority=2
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
#endif

#if 1
// Task1 is started in priority=2
// Task2 is started in priority=3
// Task3 is started in priority=4
// Task4 is started in priority=5
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 4, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
#endif

結果は以下の様になりました。
優先度の高いタスクが終わると、次の優先度のタスクが動き始めます。
優先度の高いタスクにCPU実行権を与えるのは、RTOSの基本です。
setup() start
configTICK_RATE_HZ:1000
portTICK_PERIOD_MS:1
freeRTOS version:V9.0.0
Start Scheduler.....
[Task4:0] start Priority=5
[Task4:209] end
[Task3:210] start Priority=4
[Task3:414] end
[Task2:415] start Priority=3
[Task2:619] end
[Task1:620] start Priority=2
[Task1:824] end



FreeRTOSではprintf()を使うことができますが、現在の実装には一部不具合(??)が有ります。
_printf()の代わりにprintf()を使うように変更して、ビルドしてSTM32に書き込みます。
{前略}

// Task Body
void task(void *pvParameters)
{
    UBaseType_t prio;
    TickType_t nowTick;

    prio = uxTaskPriorityGet( NULL );
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
//    _printf("[%s:%d] start Priority=%d\n",pcTaskGetName(0),nowTick,(int)prio);
    ConsumptionTick(200);
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] end\n",pcTaskGetName(0),nowTick);
//    _printf("[%s:%d] end\n",pcTaskGetName(0),nowTick);
    vTaskDelete( NULL );
}

{中略}

#if 1
// Task1 is started in priority=2
// Task2 is started in priority=2
// Task3 is started in priority=2
// Task4 is started in priority=2
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
#endif

#if 0
// Task1 is started in priority=2
// Task2 is started in priority=3
// Task3 is started in priority=4
// Task4 is started in priority=5
  xTask1 = xTaskCreate(task, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
  xTask2 = xTaskCreate(task, "Task2", configMINIMAL_STACK_SIZE, NULL, 3, NULL);
  xTask3 = xTaskCreate(task, "Task3", configMINIMAL_STACK_SIZE, NULL, 4, NULL);
  xTask4 = xTaskCreate(task, "Task4", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
#endif

{後略}

結果は以下の様になります。
各タスクが一斉に始まり、一斉にプリントしますが、Serialオブジェクトがスレッドセーフではないので、表示が混じります。
これはとっても深刻な問題で、Arduno core for STM32が提供するIOオブジェクトがスレッドセーフではないので、
IOを行うタスクが同時に動くことが出来ないことを示しています。
_printf()はprintf()のラップ関数ですが、MUTEXを使ってSerialオブジェクトを排他制御しています。
IOを行うタスクでは、このようにオブジェクトを排他制御する必要が有ります。
setup() start
configTICK_RATE_HZ:1000
portTICK_PERIOD_MS:1
freeRTOS version:V9.0.0
Start Scheduler.....
0] start Priority=2
rity=2
0] start Priority=2
rity=2
0] start Priority=2
0] start Priority=2
rity=2
rity=2
[Task3:218] end
[Task4:225] end
[Task1:230] end
[Task2:238] end

続く...