#はじめに
CQ出版 Interface誌 2018年10月号へ執筆したESP実験コーナ 「FreeRTOS」をはじめるで紙面の都合上削除されたFreeRTOSのサンプルプログラムを紹介します。
ESP32やFreeRTOSを使ったプログラム作成の参考になればと思います。
紹介するサンプルプログラムは以下の5本です。
(1) タスク制御
(2) タイマ
(3) メッセージ・キュー
(4) セマフォ
(5) イベント・グループ
サンプルプログラムの動作環境はESP32-DevKitとArduino IDEを使っています。
◆Interface誌 2018年10月号紹介ページ
https://interface.cqpub.co.jp/magazine/201810/
◆記事紹介ページ
https://interface.cqpub.co.jp/wp-content/uploads/if10_156.pdf
◆ESP32-DevKit
https://www.espressif.com/en/products/hardware/esp32-devkitc/overview
http://akizukidenshi.com/catalog/g/gM-11819/
#(1)タスク制御
2つのタスクを生成、LEDを使ってそれぞれのタスクが異なる周期で動作するのを確認するようにしてあります。
生成される2つのタスクはsetup()やloop()とは別のタスクです。
LEDはIO22とIO23に接続しています。
/*
* 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)タイマ
タイマは、指定した周期でタイマ生成時に登録したタイマハンドラを呼び出すことができます。
setup()内で2000ミリ秒(2秒)周期でタイマハンドラtimer_handler()を繰り返し呼び出すタイマを生成します。
タイマハンドラ内でシリアルへの出力処理を入れ、2秒ごとに「timer_handler()」がシリアルモニタ表示されタイマが動作していることが分かります。また、loop()が1秒ごとに呼び出され、そこではシリアルモニタへ「loop()」が出力されます。
タイマがloop()とは別に動作することが分かると思います。
/*
* 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)メッセージ・キュー
メッセージ・キューはタスクやハンドラが送信したメッセージ文字列を他のタスクで受信することができます。
受信側のタスクではメッセージを受信するまでタスクをスリープして待ち、メッセージが来たら起床してタスクの動作を再開させることができます。
サンプルではsetup()内でタスクを1つ生成し、生成されたタスクはメッセージ受信待ちでスリープします。
その後、loop()で5秒ごとにメッセージ文字列を送信します。
受信側のタスクはメッセージを受信するとそのメッセージをシリアルへ出力し、再びメッセージ待ちでスリープします。
5秒ごとに「message 1」「message 2」「message 3」「message 4」「message 5」の各メッセージが順番にシリアルモニタへ出力され、メッセージ・キューの動作が分かります。
/*
* 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)セマフォ
セマフォはタスク間の同期をとるなどの待ち合わせに用いられます。
サンプルではsetup()内でセマフォ1つとタスク2つ生成し、
タスク1がセマフォを獲得したら2秒間保持してセマフォを解放、
タスク2がセマフォを獲得したら3秒間保持してセマフォを解放
を繰り返します。
各タスクはセマフォを獲得したら以下のように「task1」「task2」をシリアルモニタへ出力されることでセマフォの獲得・解放の動作が分かります。
なお、loop()がタスク1、タスク2とは関係なく動作していることが分かるよう、1秒ごとに「loop()」がこの出力の中に入り込みます。
「task1」
↓ 2秒
「task2」
↓ 3秒
「task1」
↓ 2秒
「task2」
:
/*
* 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)イベント・グループ
イベント・グループははタスク間の同期をとるなどの待ち合わせに用いられますが、セマフォとは異なり1回の待ちで複数の要因で待つことができ、また、どの要因が発生したかも知ることができます。
サンプルではsetup()内でイベント・グループ1つとタスク2つ生成し、
①タスク1がイベント・グループのビット0とビット1の待ちでスリープします。
②タスク1がイベント・グループのビット0とビット1のいずれかを受信したらどのビットを受信したかシリアルへ出力します。
③タスク2は1秒周期でイベント・グループのビット1をセットします。
④loop()は5秒ごとにイベント・グループのビット0をセットします。
シリアルモニタへ出力されるメッセージでイベント・グループのセットと受信の動作が分かります。
/*
* 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);
}