技術同人誌「GR-ROSEでいこう！」の「GR-ROSEでFreeRTOS」サポートページ

# 記事の概要

2020/02/29 (土)に行われる技術書典8において頒布予定の合同誌[「GR-ROSEでいこう」](https://techbookfest.org/event/tbf08/circle/6329821655203840?fbclid=IwAR3VTYWfUghZ-Yr2wUf2keTqt3SBFgkwcQIm6GHZQh4Ph59rUgx5IJD0rdo)に記事「GR-ROSEでFreeRTOS」を投稿しました。

本記事は「GR-ROSEでFreeRTOS」のサポートページになります。
記事中のサンプルプログラムの紹介、訂正や補足説明などを随時追加いたします。

<img width="20%" alt="EPdR_YgU0AAaCHC.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/473207a1-2d20-215d-ac35-8bb3b3305e01.jpeg">
（表紙イラスト：[octo](https://skima.jp/item/detail?item_id=54294&fbclid=IwAR1arMCLzwRcJDC3HAgGWF1-wwYAKod4HCKNXosPWgXrK-yScrljTjGXo1k)さん）@[fluffyocto](https://twitter.com/fluffyocto)


# 一覧
- FreeRTOSとは何か？
    - リアルタイムOSの機能（「GR-ROSEでいこう」掲載）
    - リアルタイムOSでの開発設計（「GR-ROSEでいこう」掲載）
- タスクの実装
    - タスク本体の作成（「GR-ROSEでいこう」掲載）
    - タスクの作成： API関数 xTaskCreate()（「GR-ROSEでいこう」掲載）
    - タスクの状態（「GR-ROSEでいこう」掲載）
    - 実例：タスクの状態遷移（「GR-ROSEでいこう」掲載）
        - [タスクパラメータの利用](#タスクパラメータの利用)
    - [実例：タスクの優先順位の設定](#実例タスクの優先順位の設定)
    - [実例：タスクの優先順位の変更](#実例タスクの優先順位の変更)
    - [アイドルタスク](#アイドルタスク)
- キューの実装
    - キューとは何か（「GR-ROSEでいこう」掲載）
    - キューの作成（「GR-ROSEでいこう」掲載）
    - キューの送信（「GR-ROSEでいこう」掲載）
    - キューの受信（「GR-ROSEでいこう」掲載）
    - 実例：タスク間のデータ通信（「GR-ROSEでいこう」掲載）
        - [タスク間のデータ通信のプログラム](#タスク間のデータ通信のプログラム)
- [時間管理の実装](#時間管理の実装)
- 割り込み管理の実装
    - セマフォとは何か（「GR-ROSEでいこう」掲載）
    - セマフォを利用した割り込み管理（「GR-ROSEでいこう」掲載）
    - バイナリセマフォの作成（「GR-ROSEでいこう」掲載）
    - バイナリセマフォのTake（「GR-ROSEでいこう」掲載）
    - バイナリセマフォのGive（「GR-ROSEでいこう」掲載）
        - パラメータ pxHigherPriorityTaskWoken（「GR-ROSEでいこう」掲載）
    - 実例：バイナリセマフォによる割り込み管理（「GR-ROSEでいこう」掲載）
        - [バイナリセマフォによる割り込み管理のプログラム](#バイナリセマフォによる割り込み管理のプログラム)
    - 実例：タスク優先度の自動切り替え（「GR-ROSEでいこう」掲載）
        - [タスク優先度の自動切り替えのプログラム](#タスク優先度の自動切り替えのプログラム)
    - [カウンティングセマフォの必要性](#カウンティングセマフォの必要性)
    - [カウンティングセマフォの作成](#カウンティングセマフォの作成)
    - [実例：カウンティングセマフォによる割り込み管理](#実例カウンティングセマフォによる割り込み管理)
    - [一元的遅延割り込み処理](#一元的遅延割り込み処理)
    - ポーリング（定間隔処理）の利用（「GR-ROSEでいこう」掲載）

# 参考資料
- Gadget Renesas(がじぇるね) FreeRTOS Tips
    - https://www.renesas.com/jp/ja/products/gadget-renesas/reference/gr-rose/rtos-overview.html
- FreeRTOS カーネル 開発者ガイド
    - https://docs.aws.amazon.com/ja_jp/freertos-kernel/latest/dg/about.html
    - https://docs.aws.amazon.com/ja_jp/freertos-kernel/latest/dg/freertos-kernel-dg.pdf
- FreeRTOSの使い方(PIC32MX版)
    - http://www.picfun.com/RTOS/FreeRTOSframe.html
- マイコン徹底入門:RTOS編:フリーのリアルタイムOS活用法
    - http://miqn.net/rtos/index.html


# タスクの実装
###タスクパラメータの利用
パラメータpvParametersを利用して、外部からタスクにデータを渡すこともできます。

例えばタスクに渡したい文字データがあったとします。

```c
static const char *textLoop2 = "loop2"; 

void setup() {
```

タスク作成時に、タスクパラメータにその文字データを設定します。

```c
xTaskCreate(loop2, "LOOP2", 512, (void*)textLoop2, 2, NULL);
```

タスク本体内にて、pvParametersを通して設定した文字データを利用できます。

```c:sketch.cpp
void loop2(void *pvParameters) {
	char *name;
	name = ( char * ) pvParameters; 

	while (1) {
		digitalWrite(PIN_LED2, !digitalRead(PIN_LED2));
		Serial.println(name);
		vTaskDelay(500);
	}
}
```

プログラムを実行してシリアル通信を確認すると、タスクパラメータに渡したメッセージが表示できていることが分かります。

![fig01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/66f1ff7f-3aa7-7217-8da2-cd8daef13806.png)

なおGR-ROSEを使用したシリアル通信は、PCとUSBで接続し、Tera Termなどのターミナルソフトを立ち上げて確認してください。

##実例：タスクの優先順位の設定
タスクの優先順位が動作に与える影響を確認します。

その為にLED1のみを点灯させ続けるタスクとLED2のみを点灯させ続けるタスクの２つを作成し、優先度だけを変えてみます。

API関数 xTaskCreate の引数 uxPriority の値が大きいほどタスクの優先度は高くなります。uxPriority の最小値は０、最大値は定数 'configMAX_PRIORITIES' で設定した値になります。

両方のタスクの優先度をであるmainタスク（優先度３）より高くするため、LED1のタスクの優先度を５、LED2のタスクの優先度を４にしています。

```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"

void led_task1(void *pvParameters);
void led_task2(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	xTaskCreate(led_task1, "LED_TASK1", 512, NULL, 5, NULL);
	xTaskCreate(led_task2, "LED_TASK2", 512, NULL, 4, NULL);
}

void loop() {
}

void led_task1(void *pvParameters) {
	while (1) {
		digitalWrite(PIN_LED1, 0);
		digitalWrite(PIN_LED2, 1);
	}
}

void led_task2(void *pvParameters) {
	while (1) {
		digitalWrite(PIN_LED1, 1);
		digitalWrite(PIN_LED2, 0);
	}
}
```

これを実行するとLED1しか点灯しません。それはLED1タスクの優先度が高いため、常にLED1タスクのみがRunning状態にあり、LED2タスクはReady状態から抜けることができないからです。

![fig03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/d81cc51a-b0c5-3656-17f2-7b3f233afaed.jpeg)

## 実例：タスクの優先順位の変更

API関数 vTaskPrioritySet() を使用すればタスクの優先順位を変更することができます。

```c
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
```

- pxTask
    - 優先度を変更したいタスクのハンドルを設定します。ハンドルを使用するには、xTaskCreateの引数のpxCreatedTaskパラメータを設定していないといけません
    - NULLを設定すると、現在実行中のタスクの優先度を変更します
- uxNewPriority 
    - 対象タスクの優先度を設定します

「実例：タスクの優先順位の設定」のプログラムを修正して、LED1タスクとLED2タスクの優先度の大小を入れ替えながら、両タスクが動作するようにしたものを以下に示します。

```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"

TaskHandle_t LedTask1Handle = NULL;

void led_task1(void *pvParameters);
void led_task2(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	xTaskCreate(led_task1, "LED_TASK1", 512, NULL, 5, &LedTask1Handle);
	xTaskCreate(led_task2, "LED_TASK2", 512, NULL, 4, NULL);
}

void loop() {
}

void led_task1(void *pvParameters) {
	UBaseType_t uxPriority;
	uxPriority = uxTaskPriorityGet( NULL); //自タスクの優先度を取得
	while (1) {
		digitalWrite(PIN_LED1, 1);
		digitalWrite(PIN_LED2, 0);
		vTaskPrioritySet( NULL, uxPriority-2 ); //自タスクの優先度を下げる
	}
}

void led_task2(void *pvParameters) {
	UBaseType_t uxPriority;
	uxPriority = uxTaskPriorityGet( LedTask1Handle ); //LED1タスクの優先度を取得
	while (1) {
		digitalWrite(PIN_LED1, 0);
		digitalWrite(PIN_LED2, 1);
		vTaskPrioritySet( LedTask1Handle, uxPriority+2 ); //LED1タスクの優先度を上げる
	}
}
```

これを実行するとLED1とLED2の両方が点灯します。（正確には交互に点灯と消灯を繰り返していますが、間隔が短いために両方が常に点灯しているように見えます。）

プログラムでは以下のことをしています。

1. 優先度の高いLED1タスクがRunning状態になる
2. API関数 uxTaskPriorityGet の引数をNULLに設定し、実行中タスク（LED1タスク）の優先度を取得
3. LED1を点灯させてから、API関数 uxTaskPrioritySet の引数をNULLに設定し、実行中タスク（LED1タスク）の優先度を現在の値から２つ下げる
4. LED2タスクの優先度がLED1タスクより大きくなり、LED2タスクがRunning状態に、LED1タスクがReady状態へ遷移する
5. API関数 uxTaskPriorityGet でLED1タスクのハンドラを介して、LED1タスクの優先度を取得
6. LED2を点灯させてから、API関数 uxTaskPrioritySet でLED1タスクのハンドラを介して、LED1タスクの優先度を現在の値から２つ上げる
7. LED1タスクの優先度がLED2タスクより大きくなり、LED1タスクがRunning状態に、LED2タスクがReady状態へ遷移する。
8. 2～7をループ

##アイドルタスク
CPUは常に１つのタスクは実行していないといけませんが、実際には機器を使用していない間は、作成したタスクの全てがNot Running状態であることが多くなります。

その場合、API関数 vTaskStartScheduler() 実行時に自動生成されるアイドルタスクがRunning状態になっています。
アイドルタスクの優先度は最低の０です。このタスクは一定周期でAPI関数 vApplicationIdleHook() を呼び出します。

```c
void vApplicationIdleHook( void ); 
```

この関数内に処理を追加して、作成した全タスクが停止中の監視処理や低消費電力モードからの復帰処理などを行わせることができます。

# キューの実装
###タスク間のデータ通信のプログラム
```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

void loop2(void *pvParameters);

QueueHandle_t queue_test;
int send_data = 0;

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	// serial output to USB
	Serial.begin(9600);
	delay(1000);

	queue_test = xQueueCreate( 20, sizeof( int ) );

	// loop2 task creation for sensing
	xTaskCreate(loop2, "LOOP2", 512, NULL, 2, NULL);
}

void loop() {
	BaseType_t send_result;

	digitalWrite(PIN_LED1, !digitalRead(PIN_LED1));
	send_result = xQueueSendToBack( queue_test, &send_data, 0 );
	if( send_result == pdPASS ){
		Serial.print("Transmission data : ");
		Serial.println(send_data);
		send_data += 1;
	}
	vTaskDelay(100);
}

void loop2(void *pvParameters) {
	BaseType_t receive_result;
	int received_data;
	char *name;
	name = ( char * ) pvParameters;

	while (1) {
		digitalWrite(PIN_LED2, !digitalRead(PIN_LED2));

		while (1) {
			receive_result = xQueueReceive( queue_test, &received_data, 0 );
			if( receive_result == errQUEUE_EMPTY ) break;
			Serial.print("Received data : ");
			Serial.println(received_data);
		}
		vTaskDelay(500);
	}
}
```

# 時間管理の実装
本章ではソフトウェアタイマー（以降はタイマーと略します）の実装方法を説明し、時間管理の実例を紹介します。

## タイマーの種類
タイマーは設定時間が経過すると以下のコールバック関数を呼び出します。

```c
void ATimerCallback( TimerHandle_t xTimer );
```

タイマーには２種類あります。

- ワンショットタイマー
    - タイマー開始後、コールバック関数を１度呼び出すとタイマー停止します
- 自動再ロードタイマー
    - タイマー開始後、設定時間の周期で繰り返しコールバック関数を呼び出します

## タイマーコマンドキュー
タイマー関連のAPI関数を実行すると、それらのコマンド（タイマ開始、タイマ停止、タイマリセットなど）はタイマーコマンドキューに格納されます。
キューを利用することで、API関数を実行した順番通りにタイマーコマンドが実行できます。

タイマーコマンドキューの長さは、FreeRTOSConfig.h における configTIMER_QUEUE_LENGTH で設定できます。

API関数を沢山実行し過ぎて、タイマーコマンドキューが満杯になってしまうとコマンドを受け付けなくなります。そのような場合に各API関数がキューの空きを待つか、コマンド送信失敗とするかは、後述する関数のパラメータ xTicksToWait で設定します。
 
## タイマーとタスク優先度
タイマーのコールバック関数もタスクから実行されています。このタスクはスケジューラー起動時に自動生成され、RTOSデーモンタスクもしくは タイマーサービスタスクと呼ばれています。

RTOSデーモンタスクのタスク優先度は FreeRTOSConfig.h における configTIMER_TASK_PRIORITY で設定します。

### 「デーモンタスクの優先度　＜　API関数呼び出しタスクの優先度」の場合
デーモンタスクの優先度がAPI関数呼び出しタスクの優先度より低い場合の状態遷移図を以下に示します。

![fig06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/d81de9bb-4d94-f0db-ba90-e9b01a4e89b5.jpeg)

1. あるタスク（ここではここではmainタスクとする）がAPI関数のタイマー開始関数を実行。タイマーコマンドキューへ開始コマンドを送信。
2. 自動でデーモンタスクはBlocked状態からReady状態に遷移
3. mainタスクがBlocked状態へ遷移すると、デーモンタスクがタイマーコマンドキューのコマンド処理を実行
4. デーモンタスクは、タイマーコマンドキューが空になると再びBlocked状態へ遷移。タイマーがカウント開始

ここで注意することが２点あります。

１つはデーモンタスクが処理を開始する前に、タイマー開始関数は終了してしまっていること。

もう１つはタイマー開始関数を実行してから、タイマーがカウントを始めるまで、mainタスクがBlocked状態に遷移するのを待たないといけないことです。

もしmainタスクがRunning状態であり続けると、コマンドが有効にならないのでタイマーが開始できません。

### 「デーモンタスクの優先度　＞　API関数呼び出しタスクの優先度」の場合
デーモンタスクの優先度がAPI関数呼び出しタスクの優先度より高い場合の状態遷移図を以下に示します。

![fig07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/df4e3658-8f1d-1dcd-28e4-6b24e9c00810.jpeg)

1. mainタスクがAPI関数のタイマー開始関数を実行。タイマーコマンドキューへ開始コマンドを送信
2. 自動でデーモンタスクはBlocked状態からReady状態に遷移。mainタスクより優先度が高いので、直ちにRunning状態になり、タイマーコマンドキューのコマンド処理を実行
3. デーモンタスクは、タイマーコマンドキューが空になると再びBlocked状態へ遷移。タイマーがカウント開始
4. mainタスクがReady状態からRunning状態へ遷移。タイマー開始関数を終了させるなど、中断していた処理を続行

今回の場合は、タイマー開始関数実行直後にデーモンタスクがコマンド処理を行い、タイマーがカウント開始します。

先ほどと異なり、mainタスクが常にRunning状態であり続けても、コマンド処理は実行されるのでタイマーは開始されます。

なお、このAPI関数実行後の高優先度タスクへの自動切り替えは、FreeRTOSConfig.h において configUSE_PREEMPTION を１に設定している場合のみ有効になるのでご注意ください。

## タイマーの作成
タイマーはAPI関数 xTimerCreate() で作成します。

```c
TimerHandle_t xTimerCreate( 
    const char * const pcTimerName, 
    TickType_t xTimerPeriodInTicks, 
    UBaseType_t uxAutoReload, 
    void * pvTimerID, 
    TimerCallbackFunction_t pxCallbackFunction );
```

- pcTimerName
    - タイマーの名前をつけます。この名前はデバッグ時に使用されます
- xTimerPeriodInTicks
    - タイマー周期です。コールバック関数を呼び出すまでの待機時間を設定します
    - ディフォルトの単位はmsecです。pdMS_TO_TICKS()で変更することもできます
- uxAutoReload
    - pdFALSEを設定するとワンショットタイマーになります
    - pdTRUEを設定すると自動再ロードタイマーになります
- pvTimerID
    - タイマーIDを設定します
    - IDの用途はユーザーの自由です。整数値を保存して管理したり、オブジェクトのポインタを設定するなど任意の目的に使用できます
- pxCallbackFunction 
    - タイマーのコールバック関数を設定します

返り値であるTimerHandle_t は作成したタイマーへのハンドルで、タイマーを参照する際に使用します。タイマー作成に失敗した場合はNULLを返します。

## タイマーの開始と停止とリセット
作成したタイマーはDormant状態にあり、タイマーのカウントは停止しています。
API関数 xTimerStart() を実行することでタイマーはRunning状態になり、タイマーのカウントが開始します。

```c
BAseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
```

- xTimer
    - どのタイマーを開始するか設定します。タイマー作成関数の戻り値であるハンドルを使用します
- xTicksToWait
    - タイマーコマンドキューが満杯の場合、キューはタイマー開始コマンドを受け付けません。その場合にタイマーコマンドキューが空くのを待機する時間を設定します
    - ディフォルトの単位はmsecです。pdMS_TO_TICKS()で変更することもできます
    - ０を設定した場合は、タイマーコマンドキュー満杯時には即座に'pdFAIL'で終了します
    - FreeRTOSConfig.h において INCLUDE_vTaskSuspendを１に設定している場合は、このパラメータを portMAX_DELAY に設定すれば無限時間待機します
    - スケジューラー起動前に本関数を実行した場合、この設定値は無効になり、０を設定したのと同じことになります

この関数の返り値は、'pdPASS'と'pdFAIL'です。タイマー開始コマンドが正常にタイマーコマンドキューへ送信されたかどうかを示しています。

ワンショットタイマーの場合は、コールバック関数の実行後にタイマーは停止します。
API関数 xTimerStop() を実行してタイマーを停止させることもできます。

```c
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait ); 
```

また、タイマーのカウントをリセットしたい場合はAPI関数 xTimerReset() を使用します。

例えば、３０秒以上ボタンを押さないと低消費電力モードに遷移する場合、ボタンが押される度にカウントをリセットしないといけません。

```c
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait ); 
```

この２つの関数のパラメータと返り値は xTimerStart() と同じなので説明を省略します。

## タイマーの設定時間変更

タイマーの設定時間を変更するには、API関数 xTimerChangePeriod() を使用します。

```c
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewTimerPeriodInTicks, TickType_t xTicksToWait );
```

- xTimer
    - どのタイマーを変更するか設定します。タイマー作成関数の戻り値であるハンドルを使用します
- xTicksToWait
    - タイマーコマンドキューが満杯の場合、キューが空くまでの待機時間を設定します。設定方法は xTimerStart() と同じです
- xTimerPeriodInTicks
    - 変更後の待機時間を設定します。単位の設定は xTimerStart() と同じです
    
タイマーがRunning状態中に設定時間を変更すると、カウントをリセットして、新しい設定時間が有効になります。

## 実例：タイマーの利用
タイマーの実例は、「Gadget Renesas(がじぇるね) FreeRTOS Tips」で紹介されているサンプルが参考になります。
https://www.renesas.com/jp/ja/products/gadget-renesas/reference/gr-rose/rtos-timer.html

ここではLEDをタイマーで制御する実例を紹介します。

ワンショットタイマーは、リセットから５秒後にコールバック関数を呼び出し、LED1を消灯させます。

自動再ロードタイマーは、周期的にLED2を点滅させます。またmainタスクにおいて、１秒ごとにタイマーの設定時間を変更させています。

```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

// Timers
TimerHandle_t xAutoReloadTimer, xOneShotTimer;
void oneShotTimerCallback(TimerHandle_t xTimer);
void autoReloadTimerCallback(TimerHandle_t xTimer);
TickType_t period_increment;

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);
	digitalWrite(PIN_LED1, 1);

	// Timers
	  xOneShotTimer    = xTimerCreate("OneShot", 5000, pdFALSE, 0, oneShotTimerCallback);
	  xAutoReloadTimer = xTimerCreate("Reload" , 100,  pdTRUE , 0, autoReloadTimerCallback);

	// Start Timers
	if((xOneShotTimer != NULL) && (xAutoReloadTimer != NULL)){
	    xTimerStart(xOneShotTimer, 0);
	    xTimerStart(xAutoReloadTimer, 0);
	}
}

void loop() {
	vTaskDelay(1000);
	period_increment += 100;
	if (period_increment > 1000) period_increment = 100;
	xTimerChangePeriod(xAutoReloadTimer, period_increment, 0);
}

void oneShotTimerCallback(TimerHandle_t xTimer){
	digitalWrite(PIN_LED1, 0);
}

void autoReloadTimerCallback(TimerHandle_t xTimer){
	digitalWrite(PIN_LED2, !digitalRead(PIN_LED2));
}
```

ちなみにサンプルプログラムのデーモンタスクの優先度 configTIMER_TASK_PRIORITY は６に設定されています。

試しに６を１に変更してmainタスクより優先度を低くし、かつmainタスクを常にRunning状態にしてみると、デーモンタスクが実行されないのでタイマーが開始されず、LEDが点滅しないことが確認できます。この時の状態遷移図を以下に示します。

実際のアプリケーションで同様のことが起きないようにご注意ください。

![fig08.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/6e28753d-b6e3-629a-1e24-7a72f882bd90.jpeg)

なお、ここで再度configTIMER_TASK_PRIORITY を６に戻すと、mainタスクが常にRunning状態でもデーモンタスクが実行されるので、タイマーが開始してLEDが点滅することが確認できます。

#割り込み管理の実装
###バイナリセマフォによる割り込み管理のプログラム

```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

// LEDs
volatile int state1 = LOW;
volatile int state2 = LOW;

// Interrupts
void SwitchBlinkGreenISR();
void SwitchBlinkRedISR();

SemaphoreHandle_t xBinarySemaphore;
void vHandlerTask(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	// Interrupts
	attachInterrupt(12, SwitchBlinkGreenISR, FALLING);  //IRQ7 (12)
	attachInterrupt(11, SwitchBlinkRedISR, FALLING);    //IRQ6 (11)

	xBinarySemaphore = xSemaphoreCreateBinary();
	xTaskCreate( vHandlerTask, "Handler", 512, NULL, 3, NULL );
}

void loop() {
	digitalWrite(PIN_LED1, state1);
	digitalWrite(PIN_LED2, state2);
}

void SwitchBlinkGreenISR()
{
	xSemaphoreGiveFromISR( xBinarySemaphore, NULL );
}

void SwitchBlinkRedISR()
{
	state2 = !state2;
}

void vHandlerTask(void *pvParameters){
  while(1){
	xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
	state1 = !state1;
  }
}
```

###タスク優先度の自動切り替えのプログラム
####中断タスクの優先度が割り込みタスクの優先度より高い場合のプログラム
```c:sketch.cpp
/* GR-ROSE Sketch template V1.05 */
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

// LEDs
volatile int state1 = LOW;
volatile int state2 = LOW;

// Interrupts
void SwitchBlinkGreenISR();
void SwitchBlinkRedISR();
bool interruptFlag;

SemaphoreHandle_t xBinarySemaphore;
void vHandlerTask(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	// Interrupts
	attachInterrupt(12, SwitchBlinkGreenISR, FALLING);  //IRQ7 (12)
	attachInterrupt(11, SwitchBlinkRedISR, FALLING);    //IRQ6 (11)
	interruptFlag = false;

	// serial output to USB
	Serial.begin(9600);
	delay(1000);

	xBinarySemaphore = xSemaphoreCreateBinary();
	xTaskCreate( vHandlerTask, "Handler", 512, NULL, 1, NULL );
}

void loop() {
	if ( true == interruptFlag ) {
		Serial.println("main Task");
		interruptFlag = false;
		vTaskDelay(1000);
	}
	digitalWrite(PIN_LED1, state1);
	digitalWrite(PIN_LED2, state2);
}

void SwitchBlinkGreenISR()
{
	interruptFlag = true;
	xSemaphoreGiveFromISR( xBinarySemaphore, NULL);
}

void SwitchBlinkRedISR()
{
	state2 = !state2;
}

void vHandlerTask(void *pvParameters){
  while(1){
	xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
	state1 = !state1;
	Serial.println("Interrupt Task");
  }
}
```

####割り込みタスクの優先度が中断タスクの優先度より高い場合のプログラム（自動切り替えしない）

```c:sketch.cpp
/* GR-ROSE Sketch template V1.05 */
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

// LEDs
volatile int state1 = LOW;
volatile int state2 = LOW;

// Interrupts
void SwitchBlinkGreenISR();
void SwitchBlinkRedISR();
bool interruptFlag;

SemaphoreHandle_t xBinarySemaphore;
void vHandlerTask(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	// Interrupts
	attachInterrupt(12, SwitchBlinkGreenISR, FALLING);  //IRQ7 (12)
	attachInterrupt(11, SwitchBlinkRedISR, FALLING);    //IRQ6 (11)
	interruptFlag = false;

	// serial output to USB
	Serial.begin(9600);
	delay(1000);

	xBinarySemaphore = xSemaphoreCreateBinary();
	xTaskCreate( vHandlerTask, "Handler", 512, NULL, 10, NULL );
}

void loop() {
	if ( true == interruptFlag ) {
		Serial.println("main Task");
		interruptFlag = false;
		vTaskDelay(1000);
	}
	digitalWrite(PIN_LED1, state1);
	digitalWrite(PIN_LED2, state2);
}

void SwitchBlinkGreenISR()
{
	interruptFlag = true;
	xSemaphoreGiveFromISR( xBinarySemaphore, NULL);
}

void SwitchBlinkRedISR()
{
	state2 = !state2;
}

void vHandlerTask(void *pvParameters){
  while(1){
	xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
	state1 = !state1;
	Serial.println("Interrupt Task");
  }
}
```
####割り込みタスクの優先度が中断タスクの優先度より高い場合のプログラム（自動切り替えする）

```c:sketch.cpp
/* GR-ROSE Sketch template V1.05 */
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

// LEDs
volatile int state1 = LOW;
volatile int state2 = LOW;

// Interrupts
void SwitchBlinkGreenISR();
void SwitchBlinkRedISR();
bool interruptFlag;

SemaphoreHandle_t xBinarySemaphore;
void vHandlerTask(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);
	pinMode(PIN_LED2, OUTPUT);

	// Interrupts
	attachInterrupt(12, SwitchBlinkGreenISR, FALLING);  //IRQ7 (12)
	attachInterrupt(11, SwitchBlinkRedISR, FALLING);    //IRQ6 (11)
	interruptFlag = false;

	// serial output to USB
	Serial.begin(9600);
	delay(1000);

	xBinarySemaphore = xSemaphoreCreateBinary();
	xTaskCreate( vHandlerTask, "Handler", 512, NULL, 10, NULL );
}

void loop() {
	if ( true == interruptFlag ) {
		Serial.println("main Task");
		interruptFlag = false;
		vTaskDelay(1000);
	}
	digitalWrite(PIN_LED1, state1);
	digitalWrite(PIN_LED2, state2);
}

void SwitchBlinkGreenISR()
{
	BaseType_t xHigherPriorityTaskWoken;
	xHigherPriorityTaskWoken = pdFALSE;

	interruptFlag = true;
	xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}

void SwitchBlinkRedISR()
{
	state2 = !state2;
}

void vHandlerTask(void *pvParameters){
  while(1){
	xSemaphoreTake(xBinarySemaphore, portMAX_DELAY);
	state1 = !state1;
	Serial.println("Interrupt Task");
  }
}
```
##カウンティングセマフォの必要性
割り込みタスク実行中に、割り込みイベントが発生するとセマフォは満杯になります。
そんな時に、更に割り込みイベントが発生すると、以下の図に示すように、既にセマフォは満杯なので、そのイベントはセマフォに格納できず消えてしまいます。

![fig11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150620/d82afc5a-a0fa-14b6-57fe-0a772aa7eaad.jpeg)

複数のイベントを保持できるカウンティングセマフォならば、３回目の割り込みイベントも保持できます。

##カウンティングセマフォの作成
カウンティングセマフォはAPI関数 xSemaphoreCreateCountingStatic() で作成します。

```c
SemaphoreHandle_t xSemaphoreCreateCounting( 
    UBaseType_t uxMaxCount,                                             
    UBaseType_t uxInitialCount ); 
```

- uxMaxCount
    - セマフォのカウントの最大値です。保持できるイベントの最大数を意味します
- uxInitialCount
    - 作成するセマフォのカウント初期値です。ここで設定した番号のセマフォイベントから割り込み処理を開始します
    - セマフォ作成時点でイベントが発生していない場合は０を設定します


uxInitialCount については、リソース管理する場合は uxInitialCount を uxMaxCount と同じに設定して、タスクがセマフォを取得する度に減算し、０になるとリソースがなくなったと判定します。

割り込み管理する場合、セマフォはイベントをカウントするために使います。セマフォ作成時点では何の割り込みイベントも起きていないので０を設定しておきます。

返り値であるSemaphoreHandle_t は作成したカウンティングセマフォへのハンドルで、カウンティングセマフォを参照する際に使用します。カウンティングセマフォ作成に失敗した場合はNULLを返します。

##実例：カウンティングセマフォによる割り込み管理
「実例：バイナリセマフォによる割り込み管理」を修正してカウンティングセマフォを使用します。

１０回割り込みが発生しないと、割り込みタスクが開始しません。
スイッチを１０回押すと、シリアル通信で文字「ABCDEFGHIJ」が表示されます。

```c:sketch.cpp
#include <Arduino.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

// LEDs
volatile int state1 = LOW;

// Interrupts
void SwitchBlinkGreenISR();
int switch_count;
char switc_buf[10];

SemaphoreHandle_t xCountingSemaphore;
void vHandlerTask(void *pvParameters);

void setup() {
	// LEDs
	pinMode(PIN_LED1, OUTPUT);

	// Interrupts
	attachInterrupt(12, SwitchBlinkGreenISR, FALLING);  //IRQ7 (12)

	// serial output to USB
	Serial.begin(9600);
	delay(1000);

	xCountingSemaphore = xSemaphoreCreateCounting(10, 0);
	xTaskCreate( vHandlerTask, "Handler", 512, NULL, 1, NULL );
}

void loop() {
	digitalWrite(PIN_LED1, state1);
	if (10 == switch_count) {
		vTaskDelay(1000);
	}
}

void SwitchBlinkGreenISR()
{
	Serial.println(switch_count);
	switc_buf[switch_count] = 'A'+switch_count;
	switch_count += 1;
	xSemaphoreGiveFromISR( xCountingSemaphore, NULL );
}

void vHandlerTask(void *pvParameters){
  while(1){
	xSemaphoreTake(xCountingSemaphore, portMAX_DELAY);
	state1 = !state1;
	Serial.println(switc_buf[10-switch_count]);
	switc_buf[10-switch_count] = ' ';
	switch_count -= 1;
  }
}
```

このプログラムでは１０回分の割り込みイベントがカウンティングセマフォに格納され、mainタスクがBlocked状態に遷移後、割り込みタスクが１０回実行されます。

試しにカウンティングセマフォをバイナリセマフォに置き換えれば、１０回スイッチを押した後に「A」しか表示されないことが確認できます。これは最初の割り込みイベント以外が無効になったためです。

##一元的遅延割り込み処理
今までの例では割り込み処理は、割り込みタスクを作成していましたが、全ての割り込み処理を一元的にデーモンタスクで実行させることもできます。これを一元的遅延割り込み処理と呼びます。

デーモンタスクに割り込み処理をさせるには、API関数 xTimerPendFunctionCallFromISR() を用います。なお 関数名にTimerがついているのは、デーモンタスクがタイマーのコールバック関数呼び出し専用に使われていた頃の名残りです。

API関数 xSemaphoreGiveFromISR() で割り込みタスクにセマフォを渡していたのと同じように、API関数 xTimerPendFunctionCallFromISR() でデーモンタスクにセマフォを渡します。

```c
BaseType_t xTimerPendFunctionCallFromISR( 
    PendedFunction_t xFunctionToPend, 
    void *pvParameter1, 
    uint32_t ulParameter2, 
    BaseType_t *pxHigherPriorityTaskWoken );
```

- xFunctionToPend
    - デーモンタスクで実行される関数へのポインタを設定します。その関数内で割り込み処理を実行させます
    - 関数は後述する形式に準拠する必要があります
- pvParameter1
    - xFunctionToPendで指定した関数に渡すことができる任意のパラメータです
- ulParameter2
    - xFunctionToPendで指定した関数に渡すことができる任意のパラメータです
- pxHigherPriorityTaskWoken 
    - 詳細は xSemaphoreGiveFromISR() のパラメータと同じになります

パラメータ xFunctionToPend に設定する関数は以下の形式を準拠してください。

```c
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );
```

実例は「FreeRTOS カーネル 開発者ガイド」をご参照ください。

具体的な実装は、セマフォを渡す際、 xSemaphoreGiveFromISR() の代わりに xTimerPendFunctionCallFromISR() を使用し、割り込み処理を実行するために、割り込みタスクを作成する代わりに関数 vPendableFunction() を作成することになります。