この記事について
Mastering the FreeRTOS Real Time Kernel-A Hands-On Tutorial Guildの日本語訳
重要と思われるポイント
Chapter 9 タスク通知 (Task Notifications)
9.1 チャプターの紹介とスコープ
これまで見てきた通り、FreeRTOSを使ったアプリケーションは、独立したタスクから構成され、自立したタスクは共同して他のタスクとコミュニケートする必要があり、便利なシステム機能を提供する。
Intermediary Objetcs(中間オブジェクト)との通信
これまで、タスク同士のコミュニケート方法について説明してきた。それぞれの方法は、communication objectを生成する必要があった。例として、キュー、イベントグループ、様々なタイプのセマフォがある。
コミュニケーションオブジェクトを使う時には、イベントとデータはタスクやISRには直接送られずにコミュニケーションオブジェクトが使われる。同様にイベントはデータも直接タスクからは受信しない。Figure76に示す。
タスク通知(直接タスクに通信する)
"Task Notification"は、コミュニケーションオブジェクトなしで、タスクが他のタスクやISRと同期することを可能にする。タスク通知を使うと、Figure77に示すように直接イベントを送信することができる。
このタスク通知関数はオプションとなっている。configUSE_TASK_NOTIFICATIONS(FreeRTOSConfig.h)を1にすると使用できる。
configUSE_TASK_NOTIFICATIONSを1にすると、各タスクは"Notification State"を持ち、PendigかNot-Pending状態を取り、32bitの"Notification Value"を取ることができる。タスクがnotificationを受けたときには、pending状態になり、notification valueを呼んだ時に、not-pending状態となる。
pending状態の通知に対して、タスクはタイムアウト時間をオプションとして、Blocked状態で待つことができる。
スコープ
このチャプターでは以下の理解を目的にする。
- task notificationの状態と値
- task notificationをcommunication objectの代わりに使う時
- task notificationをcommunication objectと比べた時の利点
9.2 Task Notifications 利点と制限
パフォーマンス上の利点
task notificationを使ってタスクにメッセージを送ると、queue, semaphore, event groupよりも高速になる
RAM使用量
同様に、task notificationをイベント、データ送信に使うと、queue, semaphore, event groupよりRAM消費が少ない。これはcommunication objectは使用前に必ず生成する必要があるが、task notificationを有効にしてもタスク当たり固定の8byteしか消費しない。
制約
task notificationはcommunication objectより、高速でRAM消費が少ないが、すべてのシナリオで使えるわけではない。このセクションでは使用できないシナリオを示す。
ISRへのイベント、データ送信
communication objectsはISRからtask, taskからISRに送信できる。
task notificationは、ISRからtaskは可能だが、taskからISRには送信できない
複数の受信タスク使用
communication objectは、そのhandleを知っていれば、どんなタスクやISRからでもアクセスできる。どんな数のタスクやISRでも、イベントやデータをcommunication objectへ送信できる。
task notificationは受信タスクに直接送るので、通知するタスクによるプロセスは1つとなる。しかし、これが制約となるのはレアケースである。複数のタスクやISRが同じデータを送信することはよくあるが、複数のタスクやISRが同じデータを受信することはレアだからだ。
複数のデータアイテムのバッファリング
キューは、一度に複数のデータを保持できる。キューにデータが送られたあとは、受信されていないデータはキューにバッファリングされている
task notificationはnotification valueをアップデートすることでタスクにデータを送る。notification valueは一度に1つの値しか保持できない。
複数のタスクへのブロードキャスト
イベントグループは、複数のタスクへイベントを送信することができる。
task notificationは、直接受信タスクに送信するので、受信タスクの動作は1つだけになる
送信完了までBlocked状態で待つ
communication objectは、必要なキューが空だったり、イベントがセットされていないときには、オプションとしてBlocked状態で待つことができる。
もし、1つのタスクがすでにpending状態であるタスクにtask notificationを送ろうとすると、受信タスクが状態をリセットするまで、送信タスクがBlocked状態で待つことはできない。ただし、これから例で見せるが、task notificationを使うようなケースでは、これはレアな制約となる。
9.3 Task Notificationの使用
task notification APIオプション
Task Notificationsはセマフォ、イベントグループ、ときにはキューを置き換えることができるパワフルな機能である。この広範囲の利用シナリオは、xTaskNotify APIを使って、task notificationを送信して、xTaskNotifyWaitを使って受信することで実現できる。
しかし、多くのケースでは、xTaskNotify, xTaskNotifyWatで提供される柔軟性はすべて必要ではなく、シンプルな機能で十分なことがある。
そのため、xTaskNotifyGiveシンプルだが、xTaskNotifyよりは柔軟性にかける機能を提供する。ulTaskNotifyTakeはシンプルだが、xTaskNotifyWaitよりは柔軟性にかける機能を提供する。
xTaskNotifyGive API Function
xTaskNotifyGiveはタスクに直接通知をして、受信タスクのnotification valueをインクリメントさせる。xTaskNotifyGiveを呼ぶと、すでにpending状態でなければ、受信タスクのnotification stateをpendingにする。
xTaskNotifyGiveはバイナリorカウンティングセマフォと比べて、より軽量で高速な通知が可能になる。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
パラメータ | 説明 |
---|---|
xTaskToNotify | xTaskCreateで生成したタスクhandle |
戻り値 | xTaskNotifyGiveはxTaskNotifyを呼ぶマクロである。pdPASSだけが返るように、xTaskNotifyにパラメータをマクロで渡す。xTaskNotifyは後述 |
xTaskNotifyGiveFromISR API Function
xTaskNotifyGiveのISRセーフバージョン。
xTaskNotifyGiveはバイナリorカウンティングセマフォと比べて、より軽量で高速な通知が可能になる。
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify,
BaseType_t *pxHigherPriorityTaskWoken );
パラメータ | 説明 |
---|---|
xTaskToNotify | xTaskCreateで生成したタスクhandle |
pxHigherPriorityTaskWoken | notificationを送られたタスクがBlocked状態の場合、notificationの結果、Blocked状態を抜けさせる。vTaskNotifyGiveFromISRがタスクにBlocked状態を抜けさせ、そのタスクの優先度が、現在実行されているタスクよりも高いは、vTaskNotifyGiveFromISRはpxHigherPriorityTaskWokenをpdTRUEに設定する。vTaskNOtifyGiveFromISRがpdTRUEに設定すると、割り込みが終わる前にコンテキストスイッチが発生する。そのため、割り込みのリターンが直接、最も優先度の高いタスクを動かすことを保証できる。ほかのISRセーフAPIと同じように、このパラメータは使う前に、pdFALSEにセットさせる必要がある。 |
ulTaskNotifyTake API Function
ulTaskNotifyTakeはタスクをBlocked状態でnotification valueが0より大きくなることを待つことができる。リターンするまえにtask notification valueをディクリメントまたはクリアすることができる。
ulTaskNotifyTakeは、バイナリセマフォやカウンティングセマフォより軽量で高速である。
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
パラメータ | 説明 |
---|---|
xClearCountOnExit | xClearCountOnExitをpdTRUEにすると、ulTaskNotifyTakeが返る前に、task notification valueを0にする。xClearCountOnExitをpdFALSEにすると、呼び出したタスクのnotification valueが0より大きい場合には、returnする前にディクリメントする |
xTicksToWait | 略 |
戻り値 | xClearCount設定値によって0またはディクリメントされる前のtask notification valueが返る。xTicksToWaitが0でなく、notification valueが0より大きくなるまでBlocked状態で待って、notification valueはタイムアウトする前にアップデートされると、戻り値は0ではない。。タイムアウトした場合は戻り値は0となる。 |
Example24. セマフォの代わりにtask notificationを使う。方法1
Example16では、ISR内からタスクをアンブロックするためにバイナリセマフォを使い、効率的にタスクとISRを同期させた。この例では機能はExample16と同じだが、バイナリセマフォの代わりにtask notificaitionを使う。
Listing148にISRとタスクの同期実装を示す。xSemaphoreTakeの呼び出しはulTaskNotifyTakeに置き換えられている。
ulTaskNotifyTakeのxClearCountOnはpdTRUEにセットされ、受信タスクはulTaskNotifyTakeリターン前にtask notificatino valueをゼロにクリアする。そのため、ulTaskNotifyTake呼び出し間では、すべてのイベントは利用可能である必要がある。Example16ではバイナリセマフォが使われていたため、ペンディングしているイベント数はハードウェアによって決められ、実用的ではなかった。Example24ではペンディングイベントの数はulTaskNotifyTakeからリターンされる。
ulTaskNotifyTake間に発生した割り込みは、task notification valueでラッチされる。タスクがnotification pendingであれば、ulTaskNotifyTakeへのcallはすぐにリターンされることになる。
/* The rate at which the periodic task generates software interrupts. */
const TickType_t xInterruptFrequency = pdMS_TO_TICKS( 500UL );
static void vHandlerTask( void *pvParameters )
{
/* xMaxExpectedBlockTime is set to be a little longer than the maximum expected time
between events. */
const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );
uint32_t ulEventsToProcess;
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Wait to receive a notification sent directly to this task from the
interrupt service routine. */
ulEventsToProcess = ulTaskNotifyTake( pdTRUE, xMaxExpectedBlockTime );
if( ulEventsToProcess != 0 )
{
/* To get here at least one event must have occurred. Loop here until
all the pending events have been processed (in this case, just print out
a message for each event). */
while( ulEventsToProcess > 0 )
{
vPrintString( "Handler task - Processing event.\r\n" );
ulEventsToProcess--;
}
}
else
{
/* If this part of the function is reached then an interrupt did not
arrive within the expected time, and (in a real application) it may be
necessary to perform some error recovery operations. */
}
}
}
ソフトウェア割り込みを生成するために使われる周期タスクは、割り込みが生成される前にメッセージをプリントして、その後再度、割り込みが生成される。
これは、実行出力で観測されるようなシーケンスを実現することができる。
(※Listingには書かれていない別のタスクのことを説明している)
Listing149は割り込みハンドラを示す。割り込み処理をdeferするためのtask notification以外はほとんど処理をしていない。
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
/* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
it will get set to pdTRUE inside the interrupt safe API function if a
context switch is required. */
xHigherPriorityTaskWoken = pdFALSE;
/* Send a notification directly to the task to which interrupt processing is
being deferred. */
vTaskNotifyGiveFromISR( /* The handle of the task to which the notification
is being sent. The handle was saved when the task
was created. */
xHandlerTask,
/* xHigherPriorityTaskWoken is used in the usual
way. */
&xHigherPriorityTaskWoken );
/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside vTaskNotifyGiveFromISR()
then calling portYIELD_FROM_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling
portYIELD_FROM_ISR() will have no effect. The implementation of
portYIELD_FROM_ISR() used by the Windows port includes a return statement,
which is why this function does not explicitly return a value. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
出力はFigure78のようになる。期待したとおりに、Example16が実行されたときと同じ出力がされている。vHandlerTaskは割り込みが生成されるとすぐにRunning状態に入り、周期タスクによるメッセージを分割している。さらなる説明をFigure79に示す。
Example25. セマフォの代わりにtask notificationを使う。方法2
Example24では、ulTaskNotifyTakeのxClearOnExitパラメータをpdTRUEに設定していた。Example25ではExample24を少し変更して、pdFALSEにした時の振る舞いをデモする。
xClearOnExitがpdFALSEの場合、ulTaskNotifyTakeはゼロクリアする代わりに、notification valueをディクリメントする。notification countはそのため、イベントが発生した回数とそれを処理した回数の差分となる。これは、vHandlerTaskを以下の2つの点でシンプルにする
- 処理されるべきイベントの数がnotification valueで保持されるので、ローカルで管理する必要がない
- ulTaskNotifyTakeごとに1つのイベントが処理できれば良い
vHandlerTaskの実装はListing150に示す。
static void vHandlerTask( void *pvParameters )
{
/* xMaxExpectedBlockTime is set to be a little longer than the maximum expected time
between events. */
const TickType_t xMaxExpectedBlockTime = xInterruptFrequency + pdMS_TO_TICKS( 10 );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Wait to receive a notification sent directly to this task from the
interrupt service routine. The xClearCountOnExit parameter is now pdFALSE,
so the task's notification value will be decremented by ulTaskNotifyTake(),
and not cleared to zero. */
if( ulTaskNotifyTake( pdFALSE, xMaxExpectedBlockTime ) != 0 )
{
/* To get here an event must have occurred. Process the event (in this
case just print out a message). */
vPrintString( "Handler task - Processing event.\r\n" );
}
else
{
/* If this part of the function is reached then an interrupt did not
arrive within the expected time, and (in a real application) it may be
necessary to perform some error recovery operations. */
}
}
}
デモの目的は、ISR割り込み毎に複数のtask notificationを1つのタスクに送信するように変更して、早い周期で割り込みが入った状態をシミュレートすることにある。その実装をListing151に示す。
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
/* Send a notification to the handler task multiple times. The first ‘give’ will
unblock the task, the following 'gives' are to demonstrate that the receiving
task's notification value is being used to count (latch) events - allowing the
task to process each event in turn. */
vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );
vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );
vTaskNotifyGiveFromISR( xHandlerTask, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
出力をFigure80に示す。vHandlerTaskは割り込み毎に3つのイベントを処理している。
xTaskNotify, xTaskNotifyFromISR API Functions
xTaskNotifyはxTaskNotifyGiveの高機能バージョンで、以下の方法で受信タスクに通知ができる
- xTaskNotifyGiveと同様に、notification valueをインクリメントする
- 1つか1つ以上のビットをnotification valueにセットできる。イベントグループより軽量で高速な処理が可能になる。
- notification valueの最後の更新から受信タスクがリードした場合に、まったく新しい値をnotification valueにセットできる。これはlengthが1のqueueと似た機能を提供できる。
- notification valueの最後の更新から受信タスクがリードした場合に、まったく新しい値をnotification valueにセットできる。これはxQueueOverWriteと似た機能を提供できる。この振る舞いは、'mailbox'と呼ばれることがある。
xTaskNotifyはxTaskNotifyより柔軟でパワフルだが、使い方が少し複雑になる。
xTaskNotifISRはxTaskNotifyのISRセーフバージョンであるため、pxHigherPriorityTaskWokenパラメータがある。
xTaskNotifyは常に受信タスクのnotification状態がpendingでない場合は、pendingにする。
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );
パラメータ | 説明 |
---|---|
xTaskToNotify | notificationを送られるタスクのhandle |
nlValue | 使い方はeNotifyActionの値に依存する |
eNotifyAction | enum型でnotification valueの更新方法を指定する。table52参照 |
戻り値 | Table52記載の1つの場合以外はpdPASSを返す |
Table 52
eNotifyAction | 説明 |
---|---|
eNoAction | notification valueを更新することなく、受信タスクのnotification状態をpendingにする。ulValueは使われない。 |
eSetBits | ulValueの値と受信タスクのnotification valueのbit単位のORを取る。たとえばulValueが0x01ではbit0がセットされる。またulVauleが0x06ではbit1, bit2がセットされる。イベントグループより軽量で高速な処理を実現できる |
eIncrement | 受信タスクのnotification valueがインクリメントされる。ulValueは使用されない。バイナリ、カウンティングセマフォより軽量で高速である。xTaskNotifyGiveと同じ機能となる |
eSetValueWithoutOverwrite | xTaskNotifyが呼ばれる前に受信タスクのnotificationがpendingの場合、なにも動作せずにpdFALSEを返す。pendingでない場合は、ulValueがnotification valueにセットされる。 |
eSetValueWithOverWrite | 受信タスクがxTaskNotifyが呼ばれる前にpendigかどうかに関わらず、ulValueがnotification parameterにセットされる。 |
xTaskNotifyWait API Function
xTaskNotifyWaitはulTaskNotifyTakeの高機能バージョンである。タイムアウトをオプションとして指定して、タスクはnotificationがpendingになるのをwaitすることができる。xTaskNotifyWaitは関数のentry, exit両方で、notification valueをclearさせるビット指定をすることができる。
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
パラメータ | 説明 |
---|---|
ulBitsToClearOnEntry | xTaskNotifyWaitを呼ぶ前に、notificationがpendingだった場合は、ulBitsToClearOnEntryは関数にentryするときにulBitsToclearOnEntryでnotification valueをクリアする。たとえばulBitsToClearOnEntryが0x01であればbit0のtask notificationがクリアされる。0xFFFFFFFFであればnotification valueは0となる。 |
ulBitsToClearOnExit | notificatinoを受信またはすでにxTaskNotifyWaitを呼んだ時にpending状態だったときには、xTaskNotifyWaitはexitする。このとき、xTaskNotifyWait関数を終える前に、ulBitsToClearOnExitにセットされたビットでtask notificationをクリアする。task notification valueが*pulNotificationValueに保存された後に、このビットはクリアされる。例えば、ulBitsToClearOnExitが0x03の場合、bit0, bit1は関数を抜ける前にクリアされる。 |
pulNotificationValue | notification valueを配るために使われる。ulBitsToClearOnExitでクリアされる前の値が、pulNotificationValueにコピーされる。pulNOtificationValueはオプションで不要であればNULLにする。 |
xTicksToWait | 略 |
戻り値 | 1.pdTRUE:notificationを受信したか、xTaskNotifyWaitを呼んだ時にすでにpendingだったことを示す。xTicksToWaitが0でなく、Blocked状態をタイムアウト前にnotificationがセットされた場合。 2.pdFALSE:task notificationを受信しなかったことを示す。xTicksToWaitが0でなくタイムアウトした場合 |
ペリフェラル(UART)デバイスドライバでtask notificationを使った例
ペリフェラルドライバライブラリはハードウェアインターフェース上の共通操作を関数で提供する。このようなライブラリの例としては、UART, SPI, ADC, Ethenet Portがある。一般に、初期化、データ頭身、データ受信などの関数が含まれている。
いくつかの操作は完了までながい時間がかかる。例としては、高精度のADC、UARTを使った大容量データ送信がある。このような場合、ドライバはいつ操作が完了しているのかを確認するためにレジスタをポーリングする関数を提供する。しかし、ポーリングは生産的な処理をしていない時にはプロセッサの時間を100%有効に使えない。この無駄はマルチタスクシステムでは高価なものになる。ポーリングするタスクが優先度の低いタスクの動作を妨げるためである。
このプロセッシング時間の無駄を回避するために、RTOS向けのデバイスドライバは割り込みドリブンで動作する。長い処理を動かすタスクは、Blocked状態で操作が完了するのを待つことができる。この方法では優先度の低いタスクは長い処理を担当しているタスクがBlocked状態のときに、動くことができる。どのタスクも不要なときに実行時間を無駄にすることがなくなる。
RTOS向けのドライバで、タスクをBlocked状態にするためにbinary semaphoreはよく使われる。Listing154の疑似コードでこれをデモする。UARTポートでのデータ転送関数の概要を提供する。Listig154では
- xUARTはUARTペリフェラルを記述する構造体となり、その情報を保持する。xTxSemaphoreメンバーはSemaphoreHandle_tとなる。セマフォを既に生成されているものとする。
- xUART_Sendは、排他制御の路軸を含まない。1つ以上のタスクがxUART_Send関数を使う場合、アプリケーション開発者はアプリケーションで排他制御をする必要がある。
- xSemaphoreTakeはタスクがUART転送初期化をBlocked状態で待つために使われる
- XSemaphoreGiveFromISRは転送完了時にタスクをBlock状態から抜けさせるために使われる。
/* Driver library function to send data to a UART. */
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
BaseType_t xReturn;
/* Ensure the UART's transmit semaphore is not already available by attempting to take
the semaphore without a timeout. */
xSemaphoreTake( pxUARTInstance->xTxSemaphore, 0 );
/* Start the transmission. */
UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
/* Block on the semaphore to wait for the transmission to complete. If the semaphore
is obtained then xReturn will get set to pdPASS. If the semaphore take operation times
out then xReturn will get set to pdFAIL. Note that, if the interrupt occurs between
UART_low_level_send() being called, and xSemaphoreTake() being called, then the event
will be latched in the binary semaphore, and the call to xSemaphoreTake() will return
immediately. */
xReturn = xSemaphoreTake( pxUARTInstance->xTxSemaphore, pxUARTInstance->xTxTimeout );
return xReturn;
}
/*-----------------------------------------------------------*/
/* The service routine for the UART's transmit end interrupt, which executes after the
last byte has been sent to the UART. */
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Clear the interrupt. */
UART_low_level_interrupt_clear( pxUARTInstance );
/* Give the Tx semaphore to signal the end of the transmission. If a task is Blocked
waiting for the semaphore then the task will be removed from the Blocked state. */
xSemaphoreGiveFromISR( pxUARTInstance->xTxSemaphore, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
Listing 154の実装は完全に動作するが、以下の欠点がある
- このライブラリは複数のセマフォを使わせるため、RAM使用量が増える
- セマフォが生成されるまでライブラリが使えない
- セマフォは広範囲のユースケースで使用できるオブジェクトである。いくつのタスクでもBlocked状態で待たせることができ、セマフォが利用化のになった時にどのタスクがBlocked状態から抜けるのかを選択することができる。このロジックの実行は有限な時間を使い、このプロセッシングオーバーヘッドはListig154のように1つのタスクだけがセマフォを待っているシナリオでは不要である。
Listig155では、上記の欠点をbinary semaphoreの代わりにtask notificationで回避する。
もしライブラリがtask notificationを使った場合、ライブラリのドキュメントは、「ライブラリ関数をcallすると、呼んだタスクのtask notificationのstate, valueを変更する」と明確に記載する必要がある。
Listing155では
- xTxSemap@horeメンバーは、xTaskToNotifyメンバーで置き換わる。xTaskToNotifyはTaskHandle_t型で、UART操作完了を待つタスクのhandleを保持するために使われる
- xTaskGetCurrentTaskHandle FreeRTOS APIは、Running状態のタスクhandle取得のために使われる
- ライブラリはFreeRTOSオブジェクトは生成しないので、RAMのオーバーヘッドが発生せず、明示的な初期化も不要となる
- task notificationは直接、UART処理を待っているタスクに送られる。不要なロジックは実行されない。
UART構造体のxTaskNotifyメンバーは、タスクとISR両方からアクセスされるので、プロセッサがどのようにアップデートするかを考慮する必要がある。
- xTaskToNotifyが1回のメモリライト操作で更新される場合、Listing155のようにクリティカルセクションの外から更新することができる。xTaskToNotifyが32bit変数でFreeRTOSを動かすプロセッサが32bitの場合に当たる
- もしxTaskToNotify更新が複数の操作で行われる場合、クリティカルセクション内である必要がある。さもなければ、ISRがアクセスして不整合を起こす可能性がある。これは、xTaskToNotifyが32bitで、プロセッサが16bitの場合に発生する。
内部的にFreeRTOSはTaskHandle_tはポインタ、でsizeof(TaskHandle_t)は常にsizeof(void*)に等しいように実装している。
/* Driver library function to send data to a UART. */
BaseType_t xUART_Send( xUART *pxUARTInstance, uint8_t *pucDataSource, size_t uxLength )
{
BaseType_t xReturn;
/* Save the handle of the task that called this function. The book text contains notes as to
whether the following line needs to be protected by a critical section or not. */
pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
/* Ensure the calling task does not already have a notification pending by calling
ulTaskNotifyTake() with the xClearCountOnExit parameter set to pdTRUE, and a block time of 0
(don't block). */
ulTaskNotifyTake( pdTRUE, 0 );
/* Start the transmission. */
UART_low_level_send( pxUARTInstance, pucDataSource, uxLength );
/* Block until notified that the transmission is complete. If the notification is received
then xReturn will be set to 1 because the ISR will have incremented this task's notification
value to 1 (pdTRUE). If the operation times out then xReturn will be 0 (pdFALSE) because
this task's notification value will not have been changed since it was cleared to 0 above.
Note that, if the ISR executes between the calls to UART_low_level_send() and the call to
ulTaskNotifyTake(), then the event will be latched in the task’s notification value, and the
call to ulTaskNotifyTake() will return immediately.*/
xReturn = ( BaseType_t ) ulTaskNotifyTake( pdTRUE, pxUARTInstance->xTxTimeout );
return xReturn;
}
/*-----------------------------------------------------------*/
/* The ISR that executes after the last byte has been sent to the UART. */
void xUART_TransmitEndISR( xUART *pxUARTInstance )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* This function should not execute unless there is a task waiting to be notified. Test this
condition with an assert. This step is not strictly necessary, but will aid debugging.
configASSERT() is described in section 11.2.*/
configASSERT( pxUARTInstance->xTaskToNotify != NULL );
/* Clear the interrupt. */
UART_low_level_interrupt_clear( pxUARTInstance );
/* Send a notification directly to the task that called xUART_Send(). If the task is Blocked
waiting for the notification then the task will be removed from the Blocked state. */
vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken );
/* Now there are no tasks waiting to be notified. Set the xTaskToNotify member of the xUART
structure back to NULL. This step is not strictly necessary but will aid debugging. */
pxUARTInstance->xTaskToNotify = NULL;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
task notificationはListing156のように、受信タスクのsemaphoreも置き換えることができる。Listing156では、UARTポートのデータを受信するRTOS向けライブラリ関数を示す。Listing156では
- xUART_Receive関数は排他制御を含まない。複数のタスクがxUART_Receiveを使う場合は、アプリケーション開発者が排他制御を実装する。たとえば、xUART_Receiveを呼び出す前にミューテックスで排他する
- UART受信割り込みはRAMバッファを受信文字で置き換える。xUART_ReceiveはRAM Bufferの文字列を返す
- xUART_Receive uxWantedBytesパラメータは、受信文字数のために使われる。RAMバッファがまだリクエストした文字数を含んでいない場合は、タスクはバッファが貯まるまでBlocked状態で待つ。whileループは、受信バッファがリクエスト文字数を満たすか、タイムアウトするまで待つ動作をリピートする
- タスクはBlocked状態に複数回入る。ブロックタイムはxUART_Receiveが呼ばれてからどれだけ時間が経過したかを考慮して調整する必要がある。この調整はxUART_Receive内で経過した時間が、xRxTimeoutで設定した時間を超過しないことを保証する。FreeRTOS のvTaskSetTimeOutState, xTaskCheckForTimeOutヘルパー関数で調整することができる。
自分のメモ
↑がよくわからなかった。
Blocked状態で待つ時間は、APIをコール時に、そこからの経過時間で設定するのだから、なにも調整することはないように思えるのだが。。
https://www.freertos.org/xTaskCheckForTimeOut.html
を見ると、
パラメータの説明は、
Parameters:
pxTimeOut A pointer to a structure that holds information necessary to determine if a timeout has occurred. pxTimeOut is initialized using vTaskSetTimeOutState().
pxTicksToWait Used to pass out an adjusted block time, which is the block time that remains after taking into account the time already spent in the Blocked state.
Returns:
If pdTRUE is returned then no block time remains, and a timeout has occurred.
If pdFALSE is returned then some block time remains, so a timeout has not occurred.
TimeOut_tの定義は
/*
* Used internally only.
*/
typedef struct xTIME_OUT
{
BaseType_t xOverflowCount;
TickType_t xTimeOnEntering;
} TimeOut_t;
となっている。
「タスク内のBlocked状態の総時間が、タイムアウト時間を超えることに備える調整」
なので、Blocked状態になった時間はカーネルで保持されていて、タイムアウトはそれに対する時間ということ?
なんか難しく考えすぎていた気がする。
「whileループで繰り返す処理の総時間に対してのタイムアウト時間をそもそも設定したい」が要求としてある。
xTaskCheckForTimeOutでこの関数を始めた時間とタイムアウト値を比較することで、ループの総時間がタイムアウト時間を超えたか確認して、こえたらこの関数を抜けたいだけ。
/* Driver library function to receive data from a UART. */
size_t xUART_Receive( xUART *pxUARTInstance, uint8_t *pucBuffer, size_t uxWantedBytes )
{
size_t uxReceived = 0;
TickType_t xTicksToWait;
TimeOut_t xTimeOut;
/* Record the time at which this function was entered. */
vTaskSetTimeOutState( &xTimeOut );
/* xTicksToWait is the timeout value - it is initially set to the maximum receive
timeout for this UART instance. */
xTicksToWait = pxUARTInstance->xRxTimeout;
/* Save the handle of the task that called this function. The book text contains notes
as to whether the following line needs to be protected by a critical section or not. */
pxUARTInstance->xTaskToNotify = xTaskGetCurrentTaskHandle();
/* Loop until the buffer contains the wanted number of bytes, or a timeout occurs. */
while( UART_bytes_in_rx_buffer( pxUARTInstance ) < uxWantedBytes )
{
/* Look for a timeout, adjusting xTicksToWait to account for the time spent in this
function so far. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) != pdFALSE )
{
/* Timed out before the wanted number of bytes were available, exit the loop. */
break;
}
/* The receive buffer does not yet contain the required amount of bytes. Wait for a
maximum of xTicksToWait ticks to be notified that the receive interrupt service
routine has placed more data into the buffer. It does not matter if the calling
task already had a notification pending when it called this function, if it did, it
would just iteration around this while loop one extra time. */
ulTaskNotifyTake( pdTRUE, xTicksToWait );
}
/* No tasks are waiting for receive notifications, so set xTaskToNotify back to NULL.
The book text contains notes as to whether the following line needs to be protected by
a critical section or not. */
pxUARTInstance->xTaskToNotify = NULL;
/* Attempt to read uxWantedBytes from the receive buffer into pucBuffer. The actual
number of bytes read (which might be less than uxWantedBytes) is returned. */
uxReceived = UART_read_from_receive_buffer( pxUARTInstance, pucBuffer, uxWantedBytes );
return uxReceived;
}
/*-----------------------------------------------------------*/
/* The interrupt service routine for the UART's receive interrupt */
void xUART_ReceiveISR( xUART *pxUARTInstance )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* Copy received data into this UART's receive buffer and clear the interrupt. */
UART_low_level_receive( pxUARTInstance );
/* If a task is waiting to be notified of the new data then notify it now. */
if( pxUARTInstance->xTaskToNotify != NULL )
{
vTaskNotifyGiveFromISR( pxUARTInstance->xTaskToNotify, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
ADCデバイスドライバでtask notificationを使った例
前のセクションでは、割り込みからタスクへnotificationを送るために、vTaskNotifyGiveFromISRをどのように使用するかを説明した。vTaskNotifyGiveFromISRはシンプルな関数として使えるが、機能は制限されている。データなしでnotificationを送るだけの機能になっている。
このセクションではtask notification eventと一緒にデータを送るためのvTaskNotifyFromISRの使いかたをデモする。Listing157に疑似コードを示す。Analog to Digital Converter(ADC)を例としている。Listing157では、、
- ADC処理は、少なくも50msごとにスタートすると仮定する
- ADC_ConversionEndISRはADC終了のためのISRで、新しいADC値が使えるようになる度に実行される。
- vADCTaskはADC値を処理する。xADCTaskToNotifyに保存されるタスクhandleは生成されているものとする
- ADC_ConversionEndISRはeActionをeSetValueWithoutOverwriteでxTaskNotifyFromISRを使用して、vADCTaskにnotificationを送る。そのときにADCの結果をnotification valueにwreteする。
- vADCTaskはxTaskNotifyWaitを使って、あたらしいADC値を待つ。
/* A task that uses an ADC. */
void vADCTask( void *pvParameters )
{
uint32_t ulADCValue;
BaseType_t xResult;
/* The rate at which ADC conversions are triggered. */
const TickType_t xADCConversionFrequency = pdMS_TO_TICKS( 50 );
for( ;; )
{
/* Wait for the next ADC conversion result. */
xResult = xTaskNotifyWait(
/* The new ADC value will overwrite the old value, so there is no need
to clear any bits before waiting for the new notification value. */
0,
/* Future ADC values will overwrite the existing value, so there is no
need to clear any bits before exiting xTaskNotifyWait(). */
0,
/* The address of the variable into which the task's notification value
(which holds the latest ADC conversion result) will be copied. */
&ulADCValue,
/* A new ADC value should be received every xADCConversionFrequency
ticks. */
xADCConversionFrequency * 2 );
if( xResult == pdPASS )
{
/* A new ADC value was received. Process it now. */
ProcessADCResult( ulADCValue );
}
else
{
/* The call to xTaskNotifyWait() did not return within the expected time,
something must be wrong with the input that triggers the ADC conversion, or with
the ADC itself. Handle the error here. */
}
}
}
/*-----------------------------------------------------------*/
/* The interrupt service routine that executes each time an ADC conversion completes. */
void ADC_ConversionEndISR( xADC *pxADCInstance )
{
uint32_t ulConversionResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE, xResult;
/* Read the new ADC value and clear the interrupt. */
ulConversionResult = ADC_low_level_read( pxADCInstance );
/* Send a notification, and the ADC conversion result, directly to vADCTask(). */
xResult = xTaskNotifyFromISR( xADCTaskToNotify, /* xTaskToNotify parameter. */
ulConversionResult, /* ulValue parameter. */
eSetValueWithoutOverwrite, /* eAction parameter. */
&xHigherPriorityTaskWoken );
/* If the call to xTaskNotifyFromISR() returns pdFAIL then the task is not keeping up
with the rate at which ADC values are being generated. configASSERT() is described
in section 11.2.*/
configASSERT( xResult == pdPASS );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
Application内でtask notificationを直接使う
このセクションでは次の機能を持つ仮想タスクを使って、task nitificationの強化をする。
- アプリケーションは遅いインターネット接続でデータの送信、受信を行う。ここからリモートデータサーバをクラウドサーバーとする
- クラウドサーバーからのデータリクエスト後、リクエストしているタスクは、Blocke状態でデータの受信を待つ
- クラウドサーバーへのデータ送信後、送信タスクはクラウドサーバーが正常に受信したことを示すAckが返るまでBlocked状態になる
ソフトウェアの設計はFigure81に示す。Figure81では
- クラウドサーバーへの複数インターネット接続の複雑性は、1つのFreeRTOSタスクにカプセル化する。そのタスクはFreeRTOSアプリケーション内のProxyサーバーのように振る舞い、サーバータスクと呼ぶことにする
- アプリケーションタスクはCloudRead()を使って受信データを読む。CloudRead()はクラウドサーバと直接通信はせず、代わりにサーバータスクにキューを通じてreadのリクエストを出す。そして、task notification経由でデータを受信する
- アプリケーションタスクはCloudWrite()を使って、クラウドサーバーにデータをwriteする。CloudWrite()はサーバーと直接通信はせず、代わりにサーバータスクにキューを通じてwriteのリクエストを出す。そして、task notification経由でwrite処理の結果を受信する。
CloudRead(), CloudWrite()でサーバータスクに送られる構造体はListing158に示す。
typedef enum CloudOperations
{
eRead, /* Send data to the cloud server. */
eWrite /* Receive data from the cloud server. */
} Operation_t;
typedef struct CloudCommand
{
Operation_t eOperation; /* The operation to perform (read or write). */
uint32_t ulDataID; /* Identifies the data being read or written. */
uint32_t ulDataValue; /* Only used when writing data to the cloud server. */
TaskHandle_t xTaskToNotify;/* The handle of the task performing the operation. */
} CloudCommand_t;
CloudRead()の疑似コードをListing159に示す。この関数はサーバータスクにリクエストを送って、xTaskNotifyWaitでリクエストデータが利用可能になるまでBlocked状態で待つ。
疑似コードでどのようにサーバータスクがリクエストを処理するかをListing160に示す。データがクラウドサーバから受信されたら、サーバータスクはアプリケーションタスクをunblockして、eActionにeSetValueWithOverwriteを設定してxTaskNotifyを呼ぶことで、アプリケーションタスクにデータを送信する。
Listing160にシンプルシナリオを示す。GetCloudDataはクラウドサーバからのデータ取得を待つ必要がないと仮定する。
/* ulDataID identifies the data to read. pulValue holds the address of the variable into
which the data received from the cloud server is to be written. */
BaseType_t CloudRead( uint32_t ulDataID, uint32_t *pulValue )
{
CloudCommand_t xRequest;
BaseType_t xReturn;
/* Set the CloudCommand_t structure members to be correct for this read request. */
xRequest.eOperation = eRead; /* This is a request to read data. */
xRequest.ulDataID = ulDataID; /* A code that identifies the data to read. */
xRequest.xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Handle of the calling task. */
/* Ensure there are no notifications already pending by reading the notification value
with a block time of 0, then send the structure to the server task. */
xTaskNotifyWait( 0, 0, NULL, 0 );
xQueueSend( xServerTaskQueue, &xRequest, portMAX_DELAY );
/* Wait for a notification from the server task. The server task writes the value
received from the cloud server directly into this task’s notification value, so there is
no need to clear any bits in the notification value on entry to or exit from the
xTaskNotifyWait() function. The received value is written to *pulValue, so pulValue is
passed as the address to which the notification value is written. */
xReturn = xTaskNotifyWait( 0, /* No bits cleared on entry. */
0, /* No bits to clear on exit. */
pulValue, /* Notification value into *pulValue. */
pdMS_TO_TICKS( 250 ) ); /* Wait a maximum of 250ms. */
/* If xReturn is pdPASS, then the value was obtained. If xReturn is pdFAIL, then the
request timed out. */
return xReturn;
}
void ServerTask( void *pvParameters )
{
CloudCommand_t xCommand;
uint32_t ulReceivedValue;
for( ;; )
{
/* Wait for the next CloudCommand_t structure to be received from a task. */
xQueueReceive( xServerTaskQueue, &xCommand, portMAX_DELAY );
switch( xCommand.eOperation ) /* Was it a read or write request? */
{
case eRead:
/* Obtain the requested data item from the remote cloud server. */
ulReceivedValue = GetCloudData( xCommand.ulDataID );
/* Call xTaskNotify() to send both a notification and the value received from the
cloud server to the task that made the request. The handle of the task is
obtained from the CloudCommand_t structure. */
xTaskNotify( xCommand.xTaskToNotify, /* The task’s handle is in the structure. */
ulReceivedValue, /* Cloud data sent as notification value. */
eSetValueWithOverwrite );
break;
/* Other switch cases go here. */
}
}
}
CloudWriteの疑似コードをListing161に示す。このデモの目的のため、CloudWrite()はビット設定のステータスを返す。各ビットがユニークな意味を持つようにする。Listing161の先頭に定義値を示す。
タスクは4つのステータスビットをクリアにして、サーバータスクにsendリクエストを送る。そしてxTaskNotifWaitを呼び、status notificationをBlocked状態で待つ。
/* Status bits used by the cloud write operation. */
#define SEND_SUCCESSFUL_BIT ( 0x01 << 0 )
#define OPERATION_TIMED_OUT_BIT ( 0x01 << 1
#define NO_INTERNET_CONNECTION_BIT ( 0x01 << 2 )
#define CANNOT_LOCATE_CLOUD_SERVER_BIT ( 0x01 << 3 )
/* A mask that has the four status bits set. */
#define CLOUD_WRITE_STATUS_BIT_MASK ( SEND_SUCCESSFUL_BIT |
OPERATION_TIMED_OUT_BIT |
NO_INTERNET_CONNECTION_BIT |
CANNOT_LOCATE_CLOUD_SERVER_BIT )
uint32_t CloudWrite( uint32_t ulDataID, uint32_t ulDataValue )
{
CloudCommand_t xRequest;
uint32_t ulNotificationValue;
/* Set the CloudCommand_t structure members to be correct for this write request. */
xRequest.eOperation = eWrite; /* This is a request to write data. */
xRequest.ulDataID = ulDataID; /* A code that identifies the data being written. */
xRequest.ulDataValue = ulDataValue; /* Value of the data written to the cloud server. */
xRequest.xTaskToNotify = xTaskGetCurrentTaskHandle(); /* Handle of the calling task. */
/* Clear the three status bits relevant to the write operation by calling
xTaskNotifyWait() with the ulBitsToClearOnExit parameter set to
CLOUD_WRITE_STATUS_BIT_MASK, and a block time of 0. The current notification value is
not required, so the pulNotificationValue parameter is set to NULL. */
xTaskNotifyWait( 0, CLOUD_WRITE_STATUS_BIT_MASK, NULL, 0 );
/* Send the request to the server task. */
xQueueSend( xServerTaskQueue, &xRequest, portMAX_DELAY );
/* Wait for a notification from the server task. The server task writes a bitwise status
code into this task’s notification value, which is written to ulNotificationValue. */
xTaskNotifyWait( 0, /* No bits cleared on entry. */
CLOUD_WRITE_STATUS_BIT_MASK, /* Clear relevant bits to 0 on exit. */
&ulNotificationValue, /* Notified value. */
pdMS_TO_TICKS( 250 ) ); /* Wait a maximum of 250ms. */
/* Return the status code to the calling task. */
return ( ulNotificationValue & CLOUD_WRITE_STATUS_BIT_MASK );
サーバータスクがwriteリクエストを管理する疑似コードをListing162に示す。データがクラウドサーバに送られると、サーバータスクはアプリケーションタスクをunblockして、bitwise statusをアプリケーションタスクに送る。xTaskNotifyはeActionをeSetBitsに設定する。CLOUD_WRITE_STATUS_BIT_MASKで定義されたbitだけが、受信タスクのnotification valueで変更することができる。そのため、受信タスクでは他の目的でその他のビットを使うことができる。
void ServerTask( void *pvParameters )
{
CloudCommand_t xCommand;
uint32_t ulBitwiseStatusCode;
for( ;; )
{
/* Wait for the next message. */
xQueueReceive( xServerTaskQueue, &xCommand, portMAX_DELAY );
/* Was it a read or write request? */
switch( xCommand.eOperation )
{
case eWrite:
/* Send the data to the remote cloud server. SetCloudData() returns a bitwise
status code that only uses the bits defined by the CLOUD_WRITE_STATUS_BIT_MASK
definition (shown in Listing 161). */
ulBitwiseStatusCode = SetCloudData( xCommand.ulDataID, xCommand.ulDataValue );
/* Send a notification to the task that made the write request. The eSetBits
action is used so any status bits set in ulBitwiseStatusCode will be set in the
notification value of the task being notified. All the other bits remain
unchanged. The handle of the task is obtained from the CloudCommand_t
structure. */
xTaskNotify( xCommand.xTaskToNotify, /* The task’s handle is in the structure. */
ulBitwiseStatusCode, /* Cloud data sent as notification value. */
eSetBits );
break;
/* Other switch cases go here. */
}
}
}