4
2

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理解その4(ボタン押下によるタスク切り替えその3)

Last updated at Posted at 2020-09-11

ボタン押下によるタスク切換えの実現

今回は、改良版かつ少々実用的なケースをトライ。

vTaskSuspend()とvTaskResume()

FreeRTOS APITask Controlにある

  • vTaskSuspend()
  • vTaskResume()
    とを使う。vTaskSuspend()の引数に”NULL”を指定すると自らSleep(Suspend)するところもミソの一つ。

用いたデバイスおよび開発環境

ESP32を搭載するM5Stack。最近は安くなった。このサイトでは、現時点で3575円。だいぶ前に買った時は5000円くらいもした。今は、第2世代も発売されているようだ。開発環境はArduino IDEを利用。

ソースコード

タスク構成

タスクは全部で3つ(切り替え対象となるタスクが2つ、ボタンを扱うタスク)。

# define BTN 38 // Middle button in M5Stack

xTaskHandle xTask1;
xTaskHandle xTask2;
xTaskHandle xTaskBtn;

uint8_t t_flag = 0; // for which task to run

どのタスクが実行されるかのフラグ”t_flag”を設けた。なお、ESP32では、FreeRTOSを明示的に記載しなくても(#includeしなくても)、Buildしてくれる。

(切り替えの対象となる)タスク2つ

void Task1(void *arg) {
  uint8_t i = 0;
  while (1) { // infinite loop
    vTaskSuspend(NULL); // Sleep by oneself
    while (t_flag == 1) {
      if (i == 0) {
        Serial.println("  Task1 is running.");
        i++;
      }
      delay(1);
    }
    Serial.println("  Task1 is stopped.");
    i = 0;
  }
}

void Task2(void *arg) {
  uint8_t i = 0;
  while (1) { // infinite loop
    vTaskSuspend(NULL); // Sleep by oneself
    while (t_flag == 2) {
      if (i == 0) {
        Serial.println("  Task2 is running.");
        i++;
      }
      delay(1);
    }
    Serial.println("  Task2 is stopped.");
    i = 0;
  }
}

フラグ”t_flag”が'1'の時はTask1が、'2'の時はTask2が実行される。まず起動時に自らSleepする(vTaskSuspend(NULL))。別タスク(ボタンを扱うタスク)から起こされると(vTaskResume())とフラグと比較し、自分宛てであれば実行。そうでなければ、再度Sleepする。各タスクでは、タスクが実行されているメッセージ、止まっているメッセージを表示するのみ。
なお、ESP32では、delay()を呼び出すと、vTaskDelay()が呼び出されるらしい。たとえば、こんな記事、他にも類似の記事が複数見つかる。

ボタンを扱うタスク

void TaskBtn(void *arg) {
  unsigned long t = 0, p;
  while (1) { // infinite loop
    while (!digitalRead(BTN)) {
      if (!t) {
        t = millis();
      }
    }
    if (t) {
      p = millis()-t;
      if (p > 3000) {
        Serial.println("Long Push");
        t_flag = 2;
        vTaskResume(xTask2);
      } else if (p > 500) {
        Serial.println("Short Push");
        t_flag = 1;
        vTaskResume(xTask1);
      } else {
        Serial.println("Very Short Push");
        t_flag = 0;
      }
      t = 0;
    }
    delay(1);
  }
}

ボタンの長押しでTask2を起こし(t_flagに"2"を代入してvTaskResume(xTask2)を呼び出し)、普通押しでTask1(t_flagに"1"を代入してvTaskResume(xTask1)を呼び出し)、クイック押しで動作中タスクをSuspend(何もしない、t_flagに"0"を代入)としている。また、それぞれ該当するメッセージを表示。

Arduinoお決まり部分

void setup() {
  Serial.begin(9600);
  pinMode(BTN, INPUT);

  xTaskCreate(&TaskBtn, "TaskBtn", 2048, NULL, 1, &xTaskBtn);
  xTaskCreate(&Task1, "Task1", 2048, NULL, 0, &xTask1);
  xTaskCreate(&Task2, "Task2", 2048, NULL, 0, &xTask2);
}

void loop() {
  delay(1); // Seem to have "Must"
}

初期設定およびタスク生成。ESP32のArduino環境では、vTaskStartScheduler()を呼び出す必要はないらしい(参考記事)。また、loop()もタスクの一つらしく、タスクスイッチを起こすためのdelay()(=vTaskDelay())を入れた方がよいようだ(loop()自体がタスク、参考記事)。

実験

シリアルコンソールの表示は下記のとおり。

Short Push
  Task1 is running.
Very Short Push
  Task1 is stopped.
Long Push
  Task2 is running.
Very Short Push
  Task2 is stopped.
Short Push
  Task1 is running.
Long Push
  Task2 is running.
  Task1 is stopped.
Short Push
  Task1 is running.
  Task2 is stopped.
Very Short Push
  Task1 is stopped.
Very Short Push

期待どおり、ボタンの長押しでTask2が実行され、、普通押しでTask1が実行され、クイック押しでタスクがStopしている。成功。

終わりに

もっと美しい方法(フラグを使わない方法)もあるだろう。ここでは、あえてフラグを使った。

4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?