0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FreeRTOS理解その12(タスクプライオリティ)

0
Posted at

タスクプライオリティ(優先度)による動作の振る舞いを知る

今回のお題は、FreeRTOSにてタスクプライオリティにより、複数のタスクがどのように振る舞うかを検証する。

環境、タスク構成

Arduino利用。3つのタスクを設ける。

  • シリアルタスク(SerialIntr):PCからの入力時に、キューにメッセージを送信
  • タスクA(TaskA):0から63までインクリメントした数値を「常に」PCに出力(63の次は0に戻る)
  • タスクB(TaskB):前述したキューでメッセージを受信したとき「のみ」PCにメッセージを出力

PCに出力するタイミングがポイント。

ソースコード

FreeRTOS導入

# include <Arduino_FreeRTOS.h>
# include <queue.h>

TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;

タスクハンドラ、キューの定義。

シリアルタスク

void SerialIntr(void *arg) {
  static uint8_t val = 1;
  for (;;) {
    if (Serial.available()) {
      (void)Serial.readString(); 
      xQueueSend(qMsg, &val, portMAX_DELAY);
      val++;
    }
    vTaskDelay(1);  
  }
}

PCから入力されたデータは読み捨てる。キューにメッセージ(数値)を積む。数値は1から始まり、キューに積むたびにインクリメントする。

タスクA

void TaskA(void *arg) {
  uint8_t val;
  char buf[8];
  static uint8_t count = 0;
  uint32_t i, sum;
  for (;;) {
    sprintf(buf, "%2d ", count);
    Serial.print(buf);
    count++;
    if (!(count % 32)) {
      Serial.println("");
    }
    if (count == 64) {
      count = 0;
    }
    vTaskDelay(1);    // ①
  }
}

単に、数値(0-63)をPCへ出力。63まで出力したら0に戻る。①のvTaskDelay(1)でタスクスイッチが行われる(後述)。

タスクB

void TaskB(void *arg) {
  uint8_t val;
  char buf[32];
  static uint32_t count = 1;
  for (;;) {
    if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch)    ②
      Serial.println("");
      sprintf(buf, "TaskB Start: N=%d", count);
      Serial.println(buf);
      for (int i = 0; i < 10; i++) {
        sprintf(buf, " Cnt=%d", i);
        Serial.println(buf);
        //vTaskDelay(1); // Task Switch    ③
      }
      count++;    
    }
  }
}

キューにメッセージが積まれるまでにずっと待つ(②)。メッセージを受けると、タスクBが開始したこと及びカウンタ10回分をPCに出力する。ここでも、③のvTaskDelay(1)でタスクスイッチが行われるが、ケースにより未使用(後述)。

Arduinoお決まり部分

void setup() {
  Serial.begin(9600);
  qMsg = xQueueCreate(1, sizeof(uint8_t));
  xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial);    // ④
  xTaskCreate(TaskA, "TaskA", 256, NULL, 1, &xTaskA);    // ⑤
  xTaskCreate(TaskB, "TaskB", 256, NULL, 2, &xTaskB);    // ⑥
  vTaskStartScheduler();
}

void loop() {
  // Nothing
}

タスクプライオリティ(④、⑤、⑥の'3'、'2'、'1')、数値が大きいほど優先度が高い。ここでは、⑤や⑥の値を変化させる(後述)。

結果

Case 1: タスクBのプライオリティ2、タスクAのプライオリティ1

タスクBの実行中、タスクAは動作しない。
image.png
タスクAが'54'出力後、次の'55'が出力されるまで、タスクB出力しかされていないことがわかる。

Case 2: タスクBのプライオリティ2、タスクAのプライオリティ1、タスクBのfor文内でvTaskDelay()実行(③)

タスクB実行中でも、vTaskDelay()によるタスクスイッチがあれば、タスクAは動くことがわかる。
image.png
タスクB出力中でも、タスクAの出力('38'-'46')が見える。

Case 3: タスクBのプライオリティ1、タスクAのプライオリティ2

上述の⑤および⑥の数値の変更。
image.png

タスクB実行中に、タスクBにvTaskDelay()(上述③)がなくても、タスクAは動作する。ただし、タスクAとタスクBとの出力が同時に発生し、正しく表示されていない(上記の「6Cnt=6」および「6Cnt=8」、'60'と'61'とが隠れている)。

Case 3: 番外

正しく表示させるために、PCへの出力(シリアル出力)に排他制御を設ける。

TaskPriorityR1.ino
# include <Arduino_FreeRTOS.h>
# include <queue.h>
# include <semphr.h>  // ⑦

TaskHandle_t xSerial, xTaskA, xTaskB;
QueueHandle_t qMsg;
SemaphoreHandle_t semSerial;  // ⑧

void SerialIntr(void *arg) {
  static uint8_t val = 1;
  for (;;) {
    if (Serial.available()) {
      (void)Serial.readString(); 
      xQueueSend(qMsg, &val, portMAX_DELAY);
      val++;
    }
    vTaskDelay(1);  
  }
}

void TaskA(void *arg) {
  uint8_t val;
  char buf[8];
  static uint8_t count = 0;
  uint32_t i, sum;
  for (;;) {
    sprintf(buf, "%2d ", count);
    count++;
    if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑨
      Serial.print(buf);
      if (!(count % 32)) {
        Serial.println("");
      }
      xSemaphoreGive(semSerial);  // ⑩
    }
    if (count == 64) {
      count = 0;
    }
    vTaskDelay(1);
  }
}

void TaskB(void *arg) {
  uint8_t val;
  char buf[32];
  static uint32_t count = 1;
  for (;;) {
    if (xQueueReceive(qMsg, &count, portMAX_DELAY) == pdPASS) { // Sleep(= Task Switch)
      sprintf(buf, "TaskB Start: N=%d", count);
      if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑪
        Serial.println("");
        Serial.println(buf);
        xSemaphoreGive(semSerial);  // ⑫
      }
      for (int i = 0; i < 10; i++) {
        sprintf(buf, " Cnt=%d", i);
        if (xSemaphoreTake(semSerial, portMAX_DELAY) == pdTRUE) {  // ⑬
          Serial.println(buf);
          xSemaphoreGive(semSerial);  // ⑭
        }
        //vTaskDelay(1); // Task Swtich
      }
      count++;    
    }
  }
}

void setup() {
  Serial.begin(9600);
  qMsg = xQueueCreate(1, sizeof(uint8_t));
  semSerial = xSemaphoreCreateMutex();  // ⑮
  xSemaphoreGive(semSerial);  // ⑯
  xTaskCreate(SerialIntr, "Serial", 256, NULL, 3, &xSerial);
  xTaskCreate(TaskA, "TaskA", 256, NULL, 2, &xTaskA);
  xTaskCreate(TaskB, "TaskB", 256, NULL, 1, &xTaskB);
  vTaskStartScheduler();
}

void loop() {
  // Nothing
}
  • ⑦、⑧、⑮、⑯:シリアル出力用セマフォの導入および初期化
  • ⑨、⑩:タスクAでのセマフォ取得待ちおよび開放
  • ⑪、⑫、⑬、⑭:タスクBでのセマフォ取得待ちおよび開放

image.png

正しく表示されている。

おわりに

期待どおりの結果。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?