ESP32を使ったFreeRTOSのサンプルプログラム

#はじめに
<B>CQ出版 Interface誌 2018年10月号</B>へ執筆した<B>ESP実験コーナ 「FreeRTOS」をはじめる</B>で紙面の都合上削除された<B>FreeRTOS</B>のサンプルプログラムを紹介します。
<B>ESP32</B>や<B>FreeRTOS</B>を使ったプログラム作成の参考になればと思います。

紹介するサンプルプログラムは以下の５本です。
　(1) タスク制御
　(2) タイマ
　(3) メッセージ・キュー
　(4) セマフォ
　(5) イベント・グループ

サンプルプログラムの動作環境は<B>ESP32-DevKit</B>と<B>Arduino IDE</B>を使っています。

<font size="7" color="#0000c0"><B>◆Interface誌 2018年10月号紹介ページ</B></font>
　https://interface.cqpub.co.jp/magazine/201810/
　![MIF201810l-280x396.jpg](https://interface.cqpub.co.jp/wp-content/uploads/MIF201810l-280x396.jpg)
<font size="7" color="#0000c0"><B>◆記事紹介ページ</B></font>
　https://interface.cqpub.co.jp/wp-content/uploads/if10_156.pdf

<font size="7" color="#0000c0"><B>◆ESP32-DevKit</B></font>
　https://www.espressif.com/en/products/hardware/esp32-devkitc/overview
　http://akizukidenshi.com/catalog/g/gM-11819/

#(1)タスク制御
２つのタスクを生成、LEDを使ってそれぞれのタスクが異なる周期で動作するのを確認するようにしてあります。
生成される２つのタスクは<B>setup()</B>や<B>loop()</B>とは別のタスクです。
LEDはIO22とIO23に接続しています。
　![ESP32_LED1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/5a19218b-e2dc-1dd6-2f7a-5d632c08954f.jpeg)　![Fig-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370759/ede89940-9e39-90dd-1d73-c5271f80c563.jpeg)

``` os1_task.ino
/*
 *  Copyright(C) 2018 by Yukiya Ishioka
 */

#include <freertos/FreeRTOS.h>    /* FreeRTOSを用いるためのヘッダファイル */

#define  LED1PIN   22    /* LED1のポート番号 */
#define  LED2PIN   23    /* LED2のポート番号 */

void  task1( void *param )
{
  while( 1 ) {
    vTaskDelay(500);
    digitalWrite(LED1PIN, HIGH);  /* LED on */
    vTaskDelay(500);
    digitalWrite(LED1PIN, LOW);   /* LED off */
  }
}

void  task2( void *param )
{
  while( 1 ) {
    vTaskDelay(200);
    digitalWrite(LED2PIN, HIGH);  /* LED on */
    vTaskDelay(200);
    digitalWrite(LED2PIN, LOW);   /* LED off */
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.print("setup()\n");

  pinMode(LED1PIN, OUTPUT);
  pinMode(LED2PIN, OUTPUT);
  digitalWrite(LED1PIN, LOW);  /* LED1 off */
  digitalWrite(LED2PIN, LOW);  /* LED2 off */

  /* create task */
  xTaskCreatePinnedToCore( task1,   /* タスクの入口となる関数名 */
                           "TASK1", /* タスクの名称 */
                           0x800,   /* スタックサイズ */
                           NULL,    /* パラメータのポインタ */
                           1,       /* プライオリティ */
                           NULL,    /* ハンドル構造体のポインタ */
                           0 );     /* 割り当てるコア (0/1) */

  xTaskCreatePinnedToCore( task2,
                           "TASK2",
                           0x800,
                           NULL,
                           2,
                           NULL,
                           0 );
}

void loop()
{
  Serial.print("loop()\n");
  vTaskDelay(1000);
}
```

#(2)タイマ
タイマは、指定した周期でタイマ生成時に登録したタイマハンドラを呼び出すことができます。
<B>setup()</B>内で2000ミリ秒（２秒）周期でタイマハンドラ<B>timer_handler()</B>を繰り返し呼び出すタイマを生成します。
タイマハンドラ内でシリアルへの出力処理を入れ、２秒ごとに「<B>timer_handler()</B>」がシリアルモニタ表示されタイマが動作していることが分かります。また、<B>loop()</B>が１秒ごとに呼び出され、そこではシリアルモニタへ「<B>loop()</B>」が出力されます。
タイマが<B>loop()</B>とは別に動作することが分かると思います。

``` os2_timer.ino
/*
 *  Copyright(C) 2018 by Yukiya Ishioka
 */

#include <freertos/FreeRTOS.h>    /* FreeRTOSを用いるためのヘッダファイル */
#include <freertos/timers.h>      /* タイマを用いるためのヘッダファイル */

TimerHandle_t thand_test;


void timer_handler( void *param )
{
  Serial.print("timer_handler()\n");
}


void setup()
{
  Serial.begin(115200);
  Serial.print("setup()\n");

  /* create timer */
  thand_test = xTimerCreate( "TIM_TEST",  /* タイマの名称 */
                             2000,        /* 遅延時間 */
                             pdTRUE,      /* 自動繰り返しの有無 */
                             NULL,        /* ID変数のポインタ */
                             timer_handler ); /* タイマハンドラの関数名 */

  xTimerStart( thand_test, 0 ); /* タイマの開始 */
  vTaskDelay(500);
}


void loop()
{
  Serial.print("loop()\n");
  vTaskDelay(1000);
}
```

##(3)メッセージ・キュー
メッセージ・キューはタスクやハンドラが送信したメッセージ文字列を他のタスクで受信することができます。
受信側のタスクではメッセージを受信するまでタスクをスリープして待ち、メッセージが来たら起床してタスクの動作を再開させることができます。
サンプルでは<B>setup()</B>内でタスクを１つ生成し、生成されたタスクはメッセージ受信待ちでスリープします。
その後、<B>loop()</B>で５秒ごとにメッセージ文字列を送信します。
受信側のタスクはメッセージを受信するとそのメッセージをシリアルへ出力し、再びメッセージ待ちでスリープします。
５秒ごとに「message 1」「message 2」「message 3」「message 4」「message 5」の各メッセージが順番にシリアルモニタへ出力され、メッセージ・キューの動作が分かります。

``` os3_msg.ino
/*
 *  Copyright(C) 2018 by Yukiya Ishioka
 */

#include <freertos/FreeRTOS.h> /* FreeRTOSを用いるためのヘッダファイル */
#include <freertos/queue.h>    /* メッセージキューを用いるためのヘッダファイル */

QueueHandle_t  qhand_test;

#define DEF_ITEM_SIZE   32

void  task1( void *param )
{
  char  buff[ DEF_ITEM_SIZE ];

  while( 1 ) {
    /* メッセージ受信待ち */
    xQueueReceive( qhand_test, buff, portMAX_DELAY );
    Serial.printf( "xQueueReceive(): msg=%s\n", buff );
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.print("setup()\n");

  /* create event-group */
  qhand_test = xQueueCreate( 10, 
                             DEF_ITEM_SIZE );

  /* create task */
  xTaskCreatePinnedToCore( task1,
                           "TASK1",
                           0x800,
                           NULL,
                           1,
                           NULL,
                           0 );
}

char  *msg[] = {
    "message 1",
    "message 2",
    "message 3",
    "message 4",
    "message 5",
    NULL
};

void loop()
{
  BaseType_t  ret;
  static int  msg_pnt = 0;

  Serial.print("loop()\n");

  /* メッセージ送信 */
  ret = xQueueSend( qhand_test, msg[msg_pnt], 0 );
  if( ret != pdPASS ) {
    Serial.print("xQueueSend() fail.\n");
  }
  msg_pnt++;
  if( msg[msg_pnt] == NULL ) {
    msg_pnt = 0;
  }

  vTaskDelay(5000);
}
```

#(4)セマフォ
セマフォはタスク間の同期をとるなどの待ち合わせに用いられます。
サンプルでは<B>setup()</B>内でセマフォ１つとタスク２つ生成し、
　タスク１がセマフォを獲得したら２秒間保持してセマフォを解放、
　タスク２がセマフォを獲得したら３秒間保持してセマフォを解放
を繰り返します。
各タスクはセマフォを獲得したら以下のように「task1」「task2」をシリアルモニタへ出力されることでセマフォの獲得・解放の動作が分かります。
なお、<B>loop()</B>がタスク１、タスク２とは関係なく動作していることが分かるよう、１秒ごとに「loop()」がこの出力の中に入り込みます。

```  
「task1」
　　↓　２秒
「task2」
　　↓　３秒
「task1」
　　↓　２秒
「task2」
　　：
```  


``` os4_sem.ino
/*
 *  Copyright(C) 2018 by Yukiya Ishioka
 */

#include <freertos/FreeRTOS.h> /* FreeRTOSを用いるためのヘッダファイル */
#include <freertos/semphr.h>    /* メセマフォを用いるためのヘッダファイル */

SemaphoreHandle_t  shand_test;

void  task1( void *param )
{
  while( 1 ) {
    xSemaphoreTake( shand_test, portMAX_DELAY );
    Serial.print("task1()\n");
    vTaskDelay(2000);
    xSemaphoreGive( shand_test );
  }
}

void  task2( void *param )
{
  while( 1 ) {
    xSemaphoreTake( shand_test, portMAX_DELAY );
    Serial.print("task2()\n");
    vTaskDelay(3000);
    xSemaphoreGive( shand_test );
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.print("setup()\n");

  shand_test = xSemaphoreCreateBinary();
  xSemaphoreGive( shand_test );

  /* create task */
  xTaskCreatePinnedToCore( task1,
                           "TASK1",
                           0x800,
                           NULL,
                           1,
                           NULL,
                           0 );

  /* create task */
  xTaskCreatePinnedToCore( task2,
                           "TASK2",
                           0x800,
                           NULL,
                           1,
                           NULL,
                           0 );
}

void loop()
{
  Serial.print("loop()\n");
  vTaskDelay(1000);
}
```

#(5)イベント・グループ
イベント・グループははタスク間の同期をとるなどの待ち合わせに用いられますが、セマフォとは異なり１回の待ちで複数の要因で待つことができ、また、どの要因が発生したかも知ることができます。
サンプルでは<B>setup()</B>内でイベント・グループ１つとタスク２つ生成し、
　①タスク１がイベント・グループのビット０とビット１の待ちでスリープします。
　②タスク１がイベント・グループのビット０とビット１のいずれかを受信したらどのビットを受信したかシリアルへ出力します。
　③タスク２は１秒周期でイベント・グループのビット１をセットします。
　④<B>loop()</B>は５秒ごとにイベント・グループのビット０をセットします。
シリアルモニタへ出力されるメッセージでイベント・グループのセットと受信の動作が分かります。


``` os5_evg.ino
/*
 *  Copyright(C) 2018 by Yukiya Ishioka
 */

#include <freertos/FreeRTOS.h>      /* FreeRTOSを用いるためのヘッダファイル */
#include <freertos/event_groups.h>  /* イベントグループを用いるためのヘッダファイル */

EventGroupHandle_t  ehand_test;

void  task1( void *param )
{
  EventBits_t  ret;

  while( 1 ) {
    /* ビットセット待ち */
    ret = xEventGroupWaitBits( ehand_test, 0x03, pdTRUE, pdFALSE, portMAX_DELAY );

    if( ret & 0x01 ) {
      Serial.print("receive bit 0\n");
    } 
    if( ret & 0x02 ) {
      Serial.print("receive bit 1\n");
    } 
  }
}

void  task2( void *param )
{
  while( 1 ) {
    vTaskDelay(1000);
    xEventGroupSetBits( ehand_test, 0x02 );
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.print("setup()\n");

  /* create event-group */
  ehand_test = xEventGroupCreate();

  /* create task */
  xTaskCreatePinnedToCore( task1,
                           "TASK1",
                           0x800,
                           NULL,
                           1,
                           NULL,
                           0 );

  /* create task */
  xTaskCreatePinnedToCore( task2,
                           "TASK2",
                           0x800,
                           NULL,
                           1,
                           NULL,
                           0 );
}

void loop()
{
  Serial.print("loop()\n");

  /* ビットをセット */
  xEventGroupSetBits( ehand_test, 0x01 );

  vTaskDelay(5000);
}
```