Help us understand the problem. What is going on with this article?

FreeRTOSでマルチタスク (on ESP32)

More than 1 year has passed since last update.

ESP32上でFreeRTOSによるマルチタスクを使う必要があったのでメモ。

利点

  • 複数のタスクを、他の処理時間に影響を受けずに実行できる
  • 優先順位や実行時間の振り分けはRTOSにお任せできる
  • 複数のタスクを、シンプルに記述することができる

RTOSにおける最低限の主なAPI

  • キュー:共有データの送受信
    • キューにデータをFIFOで積む
    • キューにデータがあった場合のみ処理が実行される
  • セマフォ:動作許可とそれによる同期処理
    • バイナリセマフォ:ほぼミューテックスと一緒 (FreeRTOSはこっち)
    • カウンティングセマフォ:アクセス数監視と排他制御
  • ミューテックス:複数スレッドからの共有資源アクセスの排他制御

API

キューデータ通信

  • xQueueCreate()
  • xQueueSend()
  • xQueueReceive()

セマフォ同期

  • xSemaphoreCreateBinary()
  • xSemaphoreGiveFromISR()
  • xSemaphoreTake()

ミューテックス排他制御

  • xSemaphoreCreateMutex()
  • xSemaphoreGive()
  • xSemaphoreTake()

FreeRTOSにおけるセマフォとミューテックス

  • 動作の許可を示すバイナリセマフォを同期で使う時はセマフォ
  • 排他制御で使う時はミューテックスと呼ぶ
  • 使用するAPIは、どちらもxSemaphoreGive()とxSemaphoreTake()
  • 正しくは違うがざっくりいうとこんな感じ

これら2つの明確な違いは、

  • セマフォ同期のvSemaphoreCreateBinary()では、初期値で動作許可が無い
  • ミューティックス排他制御のxSemaphoreCreateMutex()では、初期値で動作許可がある

サンプルコード

マルチタスクを作成するAPI

xtaskCreatePinnedToCore.cpp
 BaseType_t xTaskCreatePinnedToCore(
    TaskFunction_t pvTaskCode,        // task_func_name
    const char *constpcName,          // task_name
    const uint32_t usStackDepth,      // stack_memory_size
    void *constpvParameters,          // ptr that will be used as the param for the task being created
    UBaseType_t uxPriority,           // task_priority (0-25, priority is 0 < 25)
    TaskHandle_t *constpvCreatedTask, // task_handle_pointer
    const BaseType_t xCoreID          // core ID
 )

シンプルなマルチタスク

simple_multitask.cpp
 void task0(void* arg)
 {
     while (1)
     {
         static int count = 0;
         Serial.print("task 0 : ");
         Serial.println(count++);
         delay(1000);
     }
 }

 void task1(void* arg)
 {
     while (1)
     {
         static int count = 0;
         Serial.print("task 1 : ");
         Serial.println(count++);
         delay(2000);
     }
 }

 void setup()
 {
     Serial.begin(115200);

     // create tasks
     xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
     xTaskCreatePinnedToCore(task1, "Task1", 4096, NULL, 1, NULL, 1);
 }

 void loop()
 {
     static int count = 0;
     Serial.print("main   : ");
     Serial.println(count++);
     delay(500);
 }

キューによる通信

queue.cpp
 QueueHandle_t xQueue;

 static void vSendTask (void *pvParameters)
 {
     BaseType_t xStatus;
     int32_t SendValue = 10;

     while (1)
     {
         xStatus = xQueueSend(xQueue, &SendValue, 0);

         if(xStatus != pdPASS) // send error check
         {
             while(1)
             {
                 Serial.println("rtos queue send error, stopped");
                 delay(1000);
             }
         }
         if(SendValue == 10) SendValue = 20;
         else                SendValue = 10;

         delay(2000);
     }
 }

 static void vRecvTask(void *pvParameters)
 {
     BaseType_t xStatus;
     int32_t ReceivedValue = 0;
     const TickType_t xTicksToWait = 500U; // [ms]

     while (1)
     {
         // loop in xTicksToWait and
         // when data is received, this func is triggerred
         xStatus = xQueueReceive(xQueue, &ReceivedValue, xTicksToWait);

         Serial.println("check if data is received");

         if(xStatus == pdPASS) // receive error check
         {
             Serial.print("received data : ");
             Serial.println(ReceivedValue);
         }
         else
         {
             if(uxQueueMessagesWaiting(xQueue) != 0)
             {
                 while(1)
                 {
                     Serial.println("rtos queue receive error, stopped");
                     delay(1000);
                 }
             }
         }
     }
 }

 void setup()
 {
     Serial.begin(115200);

     // create queue : type = int32_t, size = 5
     xQueue = xQueueCreate(5, sizeof(int32_t));

     if(xQueue != NULL)
     {
         xTaskCreate(vSendTask, "TaskA", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *) NULL);
         xTaskCreate(vRecvTask, "TaskB", configMINIMAL_STACK_SIZE, NULL, 2, (TaskHandle_t *) NULL);
         Serial.println("tasks registered");
     }
     else
     {
         while(1)
         {
             Serial.println("rtos queue create error, stopped");
             delay(1000);
         }
     }
 }

 void loop()
 {
 }

セマフォによる割り込み同期

semaphore.cpp
 SemaphoreHandle_t xBinarySemaphore;

 void handleIRQ(void)
 {
     BaseType_t xHigherPriorityTaskWoken;
     xHigherPriorityTaskWoken = pdFALSE;

     xSemaphoreGiveFromISR(xBinarySemaphore, &xHigherPriorityTaskWoken); // can be used in ISR
 }

 static void vISRTask (void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 1000U; // [ms]

     Serial.println("check for isr");

     while(1)
     {
         xStatus = xSemaphoreTake(xBinarySemaphore, xTicksToWait);

         Serial.println("check for interrupt");

         if(xStatus == pdTRUE)
         {
             static int count = 0;
             Serial.print("take : ");
             Serial.println(count++);
         }
     }
 }


 void setup()
 {
     Serial.begin(115200);

     xBinarySemaphore = xSemaphoreCreateBinary();

     if(xBinarySemaphore != NULL)
     {
         xTaskCreate(vISRTask, "Task", configMINIMAL_STACK_SIZE, NULL, 1, (TaskHandle_t *)NULL);
     }
     else
     {
         while(1)
         {
             Serial.println("rtos semaphore create error, stopped");
             delay(1000);
         }
     }
 }

 void loop()
 {
     // virtual interrupt
     Serial.println("give");
     handleIRQ();
     delay(2000);
 }

ミューテックスによる排他制御

mutex.cpp
 SemaphoreHandle_t xMutex = NULL;
 int sharedResource = 0;

 static void vTask1(void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 1000UL; //
     xSemaphoreGive(xMutex);

     while(1)
     {
         xStatus = xSemaphoreTake(xMutex, xTicksToWait);

         Serial.println("check for mutex (task1)");

         if(xStatus == pdTRUE)
         {
             sharedResource = 100;
             Serial.print("shared resource change by task1 : ");
             Serial.println(sharedResource);
         }

         xSemaphoreGive(xMutex);
         delay(500);
     }
 }

 static void vTask2(void *pvParameters)
 {
     BaseType_t xStatus;
     const TickType_t xTicksToWait = 500UL;
     xSemaphoreGive(xMutex);

     while(1)
     {
         xStatus = xSemaphoreTake(xMutex, xTicksToWait);

         Serial.println("check for mutex (task2)");

         if(xStatus == pdTRUE )
         {
             sharedResource = 1;
             Serial.print("shared resource change by task2 : ");
             Serial.println(sharedResource);
         }

         xSemaphoreGive(xMutex);
         delay(1000);
     }
 }

 void setup()
 {
     Serial.begin(115200);

     xMutex = xSemaphoreCreateMutex();

     if( xMutex != NULL )
     {
         // higher priority task is done before lower priority task
         // if there are some same priority tasks, they are kicked in order of registration
         xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1,(TaskHandle_t *) NULL);
         xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2,(TaskHandle_t *) NULL);
     }
     else {
         while(1)
         {
             Serial.println("rtos mutex create error, stopped");
             delay(1000);
         }
     }
 }

 void loop()
 {
 }

その他:マルチタスクにおけるdelay()について

  • esp32-arduinoでは、delay() は vTaskDelay() で実装されてるので、タスク内でも使える

その他:関数などのプレフィックスについて

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした