モダンOSのお砂場(77) UNO R4でFreeRTOS、再帰的ミューテックス

Joseph Halfmoon

前回ミューテックスを使ってみたので、今回は一歩すすんでリカーシブなミューテックスです。何に一歩進んだのか分からんです。リカーシブにしなければならない理由をチョイと思いつかない凡人です。でもまリカーシブなAPIは存在しているので、使ってみます。確かに再帰呼び出しできますなあ。ピッタンコな応用例を知りたい、私は。

※「モダンOSのお砂場」投稿順Indexはこちら

※Arduino IDE上で「スケッチ」形式のソースからFreeRTOSを使って実験してみてます。ターゲット機はArduino UNO R4 Minima。泣く子も黙る?ArmコアのルネサスRA4M1マイコン搭載機です

RecursiveMutex

その名のとおり再帰関数内部でも呼び出し可能なMutexです。繰り返し呼ばれることになるので、同じMutexを Take, Take, Take,…と再帰が尽きるまで取得しつづけ、今度はGive, Give, Give,…と元に戻るまで返し続けることになります。そういうAPIが存在するということは分かりました。再帰好きなこのボケ老人ですが、これを使うカッコイイ局面などまったく思いつきません。御本家ドキュメントを眺めたら納得いく例など書かれているかと思った私が馬鹿でした。

xSemaphoreTakeRecursive

上記のマニュアルページには、例題コードが書かれているものの単にTake Take、Give Giveとシーケンシャルに呼び出しているのみ。リカーシブコールすらしてませんでした。芸がないぜ。中の人もメンドかったのかい?

そのうち何か良い例が見つかったら復習します。でもそれまで覚えているか?忘却力だし。

なお前回のMutexは手元のArduino環境のデフォで「冷遇」されていてイネーブルになってませんでした。今回のRecursiveMutexは、Mutexとはまた別にFreeRTOSConfig.h内マクロを1にしないとビルドすらできませぬ。

#define configUSE_RECURSIVE_MUTEXES (1)
実験に使用したソース

Arduino IDEのスケッチ形式のソースです。前回Mutexで使ったソースのチョイ直しです。2タスクが「リソースを奪い合う」部分を殊更に再帰関数化してみました。今再帰のどの辺にいるのか自主的に標準出力に報告するようにしたので、Take, Take, Takeと続けざまにRecursiveMutexを取得している様子が分かるようにしてあります。

#include <Arduino_FreeRTOS.h>
#define LED_RED   (7)
#define LED_BLUE  (6)
#define LOOP_W    (1000)
#define TASK1_W   (250)
#define TASK2_W   (150)
#define PRIOBASE  (1)
#define ON        (0)
#define OFF       (1)
#define LONG_TIME (0xffff)

TaskHandle_t loop_task;
TaskHandle_t task1, task2;

SemaphoreHandle_t xSemaphore = NULL;

void initPins() {
  pinMode(LED_RED, OUTPUT);
  digitalWrite(LED_RED, OFF);
  pinMode(LED_BLUE, OUTPUT);
  digitalWrite(LED_BLUE, OFF);
}

void loop_thread_func(void *pvParameters)
{
  TaskHandle_t temp;
  while (1) {
    temp = xSemaphoreGetMutexHolder(xSemaphore);
    if (temp == task1) {
      Serial.println("Task1 has the mutex.");
    } if (temp == task2) {
      Serial.println("Task2 has the mutex.");
    } else {
      Serial.println("Not Task1 or Task2.");
    }
    vTaskDelay(LOOP_W);
  }
}

void rCall1(int lev) {
  if (lev > 0) {
    if (xSemaphoreTakeRecursive( xSemaphore, LONG_TIME) == pdTRUE ) {
      Serial.print("Task1, Level=");
      Serial.println(lev);
      digitalWrite(LED_RED, ON);
      vTaskDelay(TASK1_W);
      digitalWrite(LED_RED, OFF);
      rCall1(lev - 1);
      xSemaphoreGiveRecursive( xSemaphore );
    } 
    vTaskDelay(TASK1_W);
  }
}

void task_func1(void *pvParameters)
{
  while (1) {
    rCall1(3);
  }
}

void rCall2(int lev) {
  if (lev > 0) {
    if (xSemaphoreTakeRecursive( xSemaphore, LONG_TIME) == pdTRUE ) {
      Serial.print("Task2, Level=");
      Serial.println(lev);
      digitalWrite(LED_BLUE, ON);
      vTaskDelay(TASK2_W);
      digitalWrite(LED_BLUE, OFF);
      rCall2(lev - 1);
      xSemaphoreGiveRecursive( xSemaphore );
    } 
    vTaskDelay(TASK2_W);
  }
}

void task_func2(void *pvParameters)
{
  while (1) {
    rCall2(3);
  }
}

void setup()
{
  Serial.begin(115200);
  while (!Serial) { }
  initPins();

  xSemaphore = xSemaphoreCreateRecursiveMutex();

  if (xTaskCreate ( task_func1, "TASK1", 512 / 4, nullptr, PRIOBASE, &task1 ) != pdPASS) {
      Serial.println("Failed to create task1."); return;
  }

  if (xTaskCreate ( task_func2, "TASK2", 512 / 4, nullptr, (PRIOBASE + 1), &task2 ) != pdPASS) {
      Serial.println("Failed to create task2."); return;
  }

  auto const rc_loop = xTaskCreate (
      loop_thread_func, static_cast<const char*>("Loop Thread"), 512 / 4, nullptr, PRIOBASE, &loop_task
    );
  if (rc_loop != pdPASS) {
    Serial.println("Failed to create 'loop' thread");
    return;
  }

  Serial.println("Starting scheduler ...");
  vTaskStartScheduler();
  for( ;; ); /* Never! */
}

/* NEVER CALLED! */
void loop() {
}
実験結果

前回同様「ループタスク」がTask1とTask2を監視してますが、今回は自主的にどちらのTaskがどのレベル(再帰回数)にいるのか報告しているのでそれみればOKっと。黄色がTask2、緑がTask1の自主報告っす。MutexR_ResultsMaker

どちらも3回再帰するようにしてあり、その通りだと。

モダンOSのお砂場(76) UNO R4でFreeRTOS、ミューテックスを使う一手間 へ戻る

モダンOSのお砂場(78)UNO R4でFreeRTOS、Task発Queue経由OLED行 へ進む