FreeRTOSのタイマー


下記の記事から続くFreeRTOSの機能に関する記事です。FreeRTOSのソフトウェアタイマーに関して説明します。

https://qiita.com/tom_S/items/ea653880132e1fcfed72

FreeRTOSのソフトウェアタイマーは、ユーザーによって指定した時間が経過した際に、ユーザーが定義したコールバックを呼び出します。
タイマーのコールバックのコンテキストはFreeRTOS側で用意されているタイマー用タスクから呼び出されます。このタイマー用タスクの優先度とスタックサイズに関してはFreeRTOSConfig.hで変更可能です。
また、コールバックの実装ではvTaskDelay()、vTaskDelayUntil()を呼んではならず、キューやセマフォにアクセスする際ゼロ以外のブロック時間を指定してはなりません。

## タイマーのコンフィグ設定

FreeRTOSConfig.hで定義されている下記のマクロで設定を変更します。(下記の値は仮の値)
```C
#define configUSE_TIMERS		        ( 1 )       /* タイマーを使用する場合は1 */
#define configTIMER_TASK_PRIORITY		( 2 )       /* タイマータスクの優先度 */
#define configTIMER_QUEUE_LENGTH		10         /* 生成可能なタイマーの数 */
#define configTIMER_TASK_STACK_DEPTH	(512)     /* タイマータスクのスタックサイズ */
```

configTIMER_TASK_PRIORITYの優先度を上げれば上げるほど、精度の高いタイマーとなります。ただ、タイマーのコールバックの処理が長いとタイマー以外の処理に悪影響を及ぼすので注意が必要です。
また、FreeRTOSのタイマー割り込みの頻度に関しては、portmacro.hで定義されている下記のマクロを変更することで設定を変えられます。デフォルトでは1msに一回のタイマー割り込みが入る設定がされています。

```C
/* FreeRTOSのAPIに指定したい時刻を渡す時には「指定したい時刻(ms) / portTICK_PERIOD_MS」を渡せばよい */
#define portTICK_PERIOD_MS                 ( ( TickType_t ) 1000 / configTICK_RATE_HZ )
```

加えて、これはFreeRTOSのAPIに指定したい時刻を渡す時（vTaskDelayに指定する引数等）には「指定したい時刻(ms) / portTICK_PERIOD_MS」を渡せばよい設定となっています。
ただし、これを修正するのはあまり推奨できないと思われます。
例えば1μsに一度割込みを行う設定に変更した場合、CPUに負荷がかかり、vTaskDelay()の呼び出し時などの値も見直す必要があるからです

## API 

タイマーに関連するAPIを説明します。ここで説明するAPIは、FreeRTOSのQueueの機能を使ってタイマータスクへ、APIの目的に沿った通知を行います。
また、「～FromISR」という名前のAPIは割り込みの関数内で呼び出すことが想定されています。「～FromISR」が付くAPIを呼び出すことにより、FreeRTOS側で管理するタイマータスクが実行可能状態となり、割り込みが呼び出される前に実行状態だったタスクからタイマータスクへの切り替えが発生する場合があります。
　この切り替えが発生するかどうかが、引数で指定する「BaseType_t *pxHigherPriorityTaskWoken」が示すポインタの実体に格納されます。pdTRUEが格納されていれば、タスクの切り替えが発生するので、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になります。コンテキストの切り替えはマイコンによって実装が違うので説明は割愛します


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

typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );

```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーを生成する |
| 引数 | pcTimerName | タイマー名 |
|  | xTimerPeriod | コールバック呼び出しまでの時間(ms単位で指定) |
|  | uxAutoReload | タイマーの繰り返し設定。pdTRUEならば、一度コールバックを呼び出した後もタイマーをリセットして繰り返す |
|  | pvTimerID | タイマーで保持する構造体のポインタ |
|  | pxCallbackFunction | 設定した時間が経過した後に呼ばれる関数 |
| 返り値|  | タイマーID |


```C
BaseType_t xTimerIsTimerActive( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーが開始されているかどうかを返す |
| 引数 | xTimer | 状態を確認するタイマーのID |
| 返り値|  | タイマーが開始されていればpdTRUEを、そうでなければpdFALSEを返す |

```C
 void *pvTimerGetTimerID( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーで保持する構造体のポインタ(xTimerCreate()呼び出し時のpvTimerIDに当たるポインタ)を返す |
| 引数 | xTimer | タイマーのID |
| 返り値|  | タイマーで保持する構造体のポインタ |

```C
const char * pcTimerGetName( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマー名を返す |
| 引数 | xTimer | タイマーのID |
| 返り値|  | タイマー名 |

```C
void vTimerSetReloadMode( TimerHandle_t xTimer,
                           const UBaseType_t uxAutoReload );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーの繰り返し設定 |
| 引数 | xTimer | タイマーのID |
|  | uxAutoReload  | タイマーの繰り返し設定。pdTRUEならば、一度コールバックを呼び出した後もタイマーをリセットして繰り返す |


```C
BaseType_t xTimerStart( TimerHandle_t xTimer,
                            TickType_t xBlockTime );
 BaseType_t xTimerStartFromISR (TimerHandle_t xTimer,
                  BaseType_t *pxHigherPriorityTaskWoken);
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーを開始する |
| 引数 | xTimer | 開始するタイマーのID |
|  | xBlockTime  | タイムアウト時間。タイマーが開始できない場合に待つ時間 |
|  | pxHigherPriorityTaskWoken | 割り込み完了後にタスクの切り替えが発生するかどうかを示す。この値が呼び出し後にpdTRUEになる場合は、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になる |
| 返り値|  | タイマー開始が成功したら1を返す。それ以外の値はエラー値となる |

```C
BaseType_t xTimerStop( TimerHandle_t xTimer,
                        TickType_t xBlockTime );
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
                 BaseType_t *pxHigherPriorityTaskWoken);
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーを停止する |
| 引数 | xTimer | 停止するタイマーのID |
|  | xBlockTime  | タイムアウト時間。タイマーが停止できない場合に待つ時間 |
|  | pxHigherPriorityTaskWoken | 割り込み完了後にタスクの切り替えが発生するかどうかを示す。この値が呼び出し後にpdTRUEになる場合は、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になる |
| 返り値|  | タイマー停止が成功したら1を返す。それ以外の値はエラー値となる |

```C
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
                                TickType_t xNewPeriod,
                                TickType_t xBlockTime );
BaseType_t xTimerChangePeriodFromISR(TimerHandle_t xTimer,
                                TickType_t xNewPeriod,
                                BaseType_t *pxHigherPriorityTaskWoken);
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーの設定時間を変更する |
| 引数 | xTimer | タイマーのID |
|  | xNewPeriod  | タイマーの設定時間。設定したい時間(ms) / portTICK_PERIOD_MS を指定することで、ms単位で指定可能 |
|  | xBlockTime  | タイムアウト時間。タイマーの時間設定ができない場合に待つ時間 |
|  | pxHigherPriorityTaskWoken | 割り込み完了後にタスクの切り替えが発生するかどうかを示す。この値が呼び出し後にpdTRUEになる場合は、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になる |
| 返り値|  | タイマー設定が成功したら1を返す。それ以外の値はエラー値となる |

```C
BaseType_t xTimerDelete( TimerHandle_t xTimer,
                          TickType_t xBlockTime );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーを削除する |
| 引数 | xTimer | タイマーのID |
|  | xBlockTime  | タイムアウト時間。タイマーが削除できない場合に待つ時間 |
| 返り値|  | タイマー削除が成功したら1を返す。それ以外の値はエラー値となる |

```C
BaseType_t xTimerReset( TimerHandle_t xTimer,
                         TickType_t xBlockTime );
BaseType_t xTimerResetFromISR(TimerHandle_t xTimer,
                   BaseType_t *pxHigherPriorityTaskWoken );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーをリセットする |
| 引数 | xTimer | タイマーのID |
|  | xBlockTime  | タイムアウト時間。タイマーがリセットできない場合に待つ時間 |
|  | pxHigherPriorityTaskWoken | 割り込み完了後にタスクの切り替えが発生するかどうかを示す。この値が呼び出し後にpdTRUEになる場合は、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になる |
| 返り値|  | タイマーリセットが成功したら1を返す。それ以外の値はエラー値となる |

```C
void vTimerResetState( void );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーの内部情報を全てリセットする |

```C
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーで保持する構造体のポインタ(xTimerCreate()呼び出し時のpvTimerIDに当たるポインタ)を設定する |
| 引数 | xTimer | タイマーのID |
|  | pvNewID | 新しいタイマーで保持する構造体のポインタ |


```C
TaskHandle_t xTimerGetTimerDaemonTaskHandle( void );
```
| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマータスクのIDを取得する |
| 返り値|  | タイマータスクのID |

```C
 TickType_t xTimerGetPeriod( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーに設定されている、コールバック呼び出しまでの時間を取得する |
| 引数 | xTimer | タイマーのID |
| 返り値|  | タイマーに設定されている、コールバック呼び出しまでの時間 |

```C
 TickType_t xTimerGetExpiryTime( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | コールバックが次に呼び出される時刻を取得する。「(この関数の返り値) - (現在時刻)」 で次にコールバックが呼び出されるまでの時間を計算できる |
| 引数 | xTimer | タイマーのID |
| 返り値|  | コールバックが次に呼び出される時刻 |

```C
BaseType_t  xTimerGetReloadMode( TimerHandle_t xTimer );
UBaseType_t uxTimerGetReloadMode( TimerHandle_t xTimer );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマーの繰り返し設定の状態を確認する |
| 引数 | xTimer | タイマーのID |
| 返り値|  | pdTRUEであれば、タイマーの繰り返し設定が有効になっている |

```C
BaseType_t xTimerPendFunctionCall(PendedFunction_t xFunctionToPend,
                            void *pvParameter1,
                            uint32_t ulParameter2,
                            TickType_t xTicksToWait );
BaseType_t xTimerPendFunctionCallFromISR(PendedFunction_t xFunctionToPend,
                       void *pvParameter1,
                       uint32_t ulParameter2,
                       BaseType_t *pxHigherPriorityTaskWoken );
```

| 項目1 |  項目2 | 説明 |
| ------------- |  ------------- | ------------------------------ |
|機能|  | タイマータスクが次に起動した際に呼び出したい関数を設定する |
| 引数 | xFunctionToPend | タイマータスクから呼び出したい関数 |
|  | pvParameter1 | xFunctionToPendの呼び出し時の引数 |
|  | pvParameter2 | xFunctionToPendの呼び出し時の引数 |
|  | xTicksToWait  | タイムアウト時間。タイマータスクのQueueが満杯の際に待つ最大時間 |
|  | pxHigherPriorityTaskWoken | 割り込み完了後にタスクの切り替えが発生するかどうかを示す。この値が呼び出し後にpdTRUEになる場合は、ユーザー側でコンテキストの切り替えを行う関数を呼び出すように実装が必要になる |
| 返り値 |  | 成功したら1を返す。それ以外の値はエラー値となる |

## サンプルソースコード

タイマーの生成、スタート・停止を行うサンプルソースを下記に示します。

```C
/* タイマーに設定した制限時間経過後に、タイマータスクから呼ばれる */
void vCallbackFunction( TimerHandle_t xTimer )
{
	const uint32_t ulMaxExpiryCountBeforeStopping = 10;
	uint32_t ulCount;

	ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );

	if( ulCount >= ulMaxExpiryCountBeforeStopping )
	{
		xTimerStop( xTimer, 0 );
	}
	else
	{
	    vTimerSetTimerID( xTimer, ( void * ) ulCount );
	}
}

static void timerSample(void)
{
    void* pvTimerID;
    TimerHandle_t timerH = xTimerCreate("timer1", // timer名
		                1000,     // コールバック呼び出しまでの時間
						pdFALSE,   // タイマーの繰り返し設定。pdTRUEならば、一度コールバックを呼び出した後もタイマーをリセットして繰り返す
						pvTimerID, // タイマーで保持するポインタ。もしくはＩＤとして利用
						vCallbackFunction // 設定した時間が経過した後に呼ばれる関数
						);
    xTimerStart(timerH,
		      0      // タイムアウト時間。タイマーが生成できない場合に待つ時間
			  );

}

```


## 参考資料

https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/05-Software-timers/01-Software-timers