この記事について
Mastering the FreeRTOS Real Time Kernel-A Hands-On Tutorial Guildの日本語訳
Chapter 6 割り込み管理(Interrupt Management)
6.1 チャプターのイントロダクションとスコープ
イベント
組み込みシステムでは、環境から発生したイベントに対してアクションをとる必要がある。例えば、イーサネットで届いたパケットは、TCP/IPスタックの処理を必要とする。
小規模システムでなければ、複数のソースからイベントを扱う必要がある。これらの処理はそれぞれ異なる処理オーバーヘッドと応答性を要求される。
どの場合でも、ベストのイベント処理実装戦戦略のための判断が必要となる
- どのようにイベントを検知すべきか?通常割り込みで扱うが、ポーリングもできる。
- 割り込みが使われる時には、割り込みサービスルーチン(ISR)内でどれくらいの処理をすべきか?外ではどれくらいか?通常はできるだけISR内を短くする。
- どのようにイベントはmain(non-ISR)と通信するか?非同期処理を処理することができるコードをどのように構築するか?
FreeRTOSは開発者に特定のイベント処理方法を強制することはないが、シンプルでメンテナンス性のある実装を選べる機能を提供する。
タスクのプライオリティと、割り込みのプライオリティの区別をつけることが重要となる。
- タスクはFreeRTOSが動作するハードウェアには関係しないソフトウェア上の機能
- ソフトウェアで書かれているが、割り込みはハードウェアがどのISRを動作させるのかを決めるので、ハードウェアの機能。タスクはISRが動作していない時のみ動くことができるため、最も低い優先度の割り込みは最も高いタスクの優先度となる。タスクがISRをプリエンプトさせることはできない。
FreeRTOSが動くすべてのアーキテクチャは割り込みを扱えるが、割り込みエントリ、プライオリティのような詳細はアーキテクチャによって異なる。
スコープ
このチャプターでは、以下の理解を目的とする
- どのFreeRTOS APIがISRで利用できるか
- 割り込みの処理をタスクに先送るする方法
- binary semaphore、counting semaphoreの生成方法
- binary semaphoreとcounting semaphoreの違い
- ISRの外にqueueを渡すための方法
- いくつかのFreeRTOS portで使える、割り込みの入れ子
6.2 ISRからFreeRTOS APIを使う
割り込みセーフのAPI
ISRからFreeRTOS APIを使う必要になるときはよくあるが、多くのFreeRTOS APIはISR内部では正常に動作しない。もっともよく知られているのはタスクをBlocked状態にするAPIだ。
もし、APIがISRから呼ばれた場合、Blocked状態に遷移する呼び出しタスクが存在しないことになる。FreeRTOSはこの問題を解決するために2つのAPIバージョンを提供している。
1つはタスクから使うもの。もう1つはISRから使うものだ。ISRから使うものには"FromISR"が名前に加えられている。
注意:"FromISR"がついていないFreeRTOS APIをISRから呼んではならない。
割り込みセーフAPIを分ける利点
割り込み用のAPIを別にすることは、タスク, ISRのコードを効率的にすることができ、割り込みエントリーをシンプルにできる。
何故かを知るために、代替案としてタスクとISRから共通で使えるAPIを考えてみる。もし同じバージョンをタスクとISRから呼んでいた場合、
- APIはタスク、ISRどちらから呼ばれたのかを決めるロジックが必要となる。追加のロジックはコードに新しいパスを追加するので、関数が長く複雑でテスト容易性が下がる。
- いくつかのAPI関数パラメータは、タスクから呼ばれた関数では使われない。一方ほかのパラメータは、ISRから呼んだ場合に使われない
- 各FreeRTOS portはタスク or ISRの実行を決める仕組みを提供する必要がある
- タスク or ISR の実行コンテキストを決めるアーキテクチャは簡単ではなく、追加で無駄で複雑な使用方法を要求する。また、非標準の割り込みエントリーコードも必要になる。
割り込みセーフAPIを分ける欠点
2つのAPIに分けることはタスク、ISRを効率的にするが、新しい問題を招く。FreeRTOSのAPIではないが、FreeRTOSのAPIを利用している関数をタスクかISRから呼び出したい時がある。
これは通常、サードパーティのコードを統合するときにおこる。開発者にとってコントロールできない唯一の設計だからだ。
この問題は以下のテクニックで解決することができる。
- 割り込み処理をタスクまで延期する。そのサードパーティAPIを常にタスクから呼び出すようにする
- interrupt nestingをサポートしているportであれば、"FromISR"で終わるバージョンのAPI関数を使う。そのバージョンはタスクとISRから呼び出すことができるため。(逆は成り立たない。"FromISR"で終わらないAPI関数はISRから呼び出してはならない)
- サードパーティのコードは通常、タスクor割り込みどちらから呼ばれたのかをテストできる、RTOS抽象化レイヤーを含んでいる。そしてコンテキストに合うAPI関数を呼んでいる。
xHigherPriorityTaskWoken Parameter
このセクションではxHigherPriorityTaskWoken Parameterのコンセプトを説明する。すべて理解できなくても心配する必要はない。実践な例を追って示す。
コンテキストスイッチが割り込みによって発生した場合、割り込みが終わった時に動くタスクは、割り込みが入った時に止められたタスクとは異なる可能性がある。つまり割り込みはあるタスクを止めて、別のタスクをリターンすることがある。
いくつかのFreeRTOS APIはタスクをBlocked状態からReady状態へ遷移させる。これまでに紹介したように" xQueueSendToBack()"ような関数はqueueを待っているBlocked状態のタスクをアンブロックする。
もし、アンブロックされたタスクが実行中のタスクより高い優先度をもっていれば、FreeRTOSのスケジューリングポリシーによって、高いタスクへのスイッチが発生する。
いつ高い優先度のタスクへのスイッチが発生するかは、そのコンテキスとがどのAPI関数から呼ばれたか?に依存する。
・タスクからAPIが呼ばれた場合
configUSE_PREEMPTIONが1(FreeRTOSConfig.h)の場合、APIが終了する前に、優先度の高いタスクへの切り替えはAPI内で自動的に行われる。
既にFigure 43で見たように、タイマーコマンドキューに書いた場所はFreeRTOSデーモンタスクへのスイッチを行う。コマンドキューを書いたタスクが終了する前に。
・APIが割り込みから呼ばれた場合
割り込みの内部で、高い優先度へのタスクスイッチは自動的に発生しない。代わりにコンテキストスイッチをアプリ開発者へ通知する変数が動作する。
割り込みセーフAPI("FromISR"で終わる)は、この目的のために、pxHigherPriorityTaskWokenと呼ばれるポインターを持っている。
もしコンテキストスイッチが発生すると、割り込みセーフAPIは *pxHigherPriorityTaskWokenをpdTRUEにセットする。
これを検出するためには、pxHigherProirityTaskWokenは使う前にpdFALSEにセットする必要がある。
アプリケーション開発者はISRからコンテキストスイッチをリクエストしない選択をする場合、高い優先度のタスクは次にスケジューラーが動作するまでReady状態を維持する。
ワーストケースではティック割り込みの周期が次のタイミングとなる。
FreeRTOS APIは*pxHighPriorityTaskWokenをpdTRUEすることのみできる。ISRが1つ以上のFreeRTOS APIを呼んだ時には、同じ変数をpxHigherPriorityTaskWokenとして、各APIに渡すことができる。pdFALSEへの初期化は使う前に1回すればよい。
割り込みセーフAPI内では、なぜ自動的にコンテキストスイッチを行わないかの理由を示す
- 不要なコンテキストスイッチを防ぐため
割り込みはタスクが何らかの動作が必要になるまでに、複数回実行されることがある。たとえば、文字列を処理する1つのタスクが、受信をUARTからの割り込みドリブンで行う状況を考える。この場合、UART ISRにとて、1つの文字を受信するたびにタスクスイッチすることは無駄である。タスクは完全な文字列を受け取ってから動作すればよいため。
- 実行シーケンスの制御
割り込みは散発的に、予想できないタイミングで発生する。FreeRTOSのエキスパートユーザーはアプリケーションの特定のポイントで、一時的に予測できないタスクスイッチを回避したいことがある。FreeRTOSのスケジューリングアルゴリズムがそのように動作するにもかかわらず。
- ポータビリティ
全てのFreeRTOSポートで使われているシンプルなメカニズム
- 効率性
小さなプロセッサアーキテクチャをターゲットにしたポートでは、ISRの最後の最後のみでコンテキストスイッチを許可しており、この制約を除去するには追加の複雑なコードが必要となる。また、1つ以上のコンテキストスイッチを要求しない、同じISR内からの1つ以上のFreeRTOS APIコールを許すことができる。
- ティック割り込み内の実行
この本の後で説明されるが、アプリケーションコードをティック割り込みに追加することが可能。ティック割り込み内でのコンテキストスイッチをする結果はFreeRTOSポート依存となる。ベストでも、スケジューラーにとって不必要なコールとなる。
pxHigherPriorityTaskWokenはオプションとなる。もし不要であれば、pxHigherPriorityTaskWokenをNULLにする。
portYIELD_FROM_ISR() と portEND_SWITCHING_ISR() マクロ
このセクションでは、ISRからのコンテキストスイッチ要求に使われるマクロを説明する。
完全に理解できなくても心配する必要はない。後で実践的な例を示す。
taskYIELDはコンテキストスイッチを要求するためにタスクから呼ばれるマクロ。
portYIELD_FROM_ISRとportEND_SWITCHING_ISRは両方ともtaskYIELDの割り込みセーフバージョンとなる。
portYIELD_FROM_ISR() と portEND_SWITCHING_ISR()は、両方とも同じことを同じ方法で行う。いくつかのFreeRTOSポートでは2つのうち1つを提供する。
新しいFreeRTOSポートは両方提供する。この本では、portYIELD_FROM_ISRを使う。
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
割り込みセーフAPIから返されたxHigherPriorityTaskWokenは、直接portYEILD_FROM_ISRマクロのパラメータとして使える。
portYEILD_FROM_ISR xHigherPriorityTaskWokenパラメータがpfFALSEだった場合、コンテキストスイッチの要求はなく、マクロは影響がない。
portYEILD_FROM_ISR xHigherPriorityTaskWokenパラメータがpfFALSEでない場合、コンテキストスイッチは要求され、Running状態のタスクは変わる可能性がある。
割り込みは、たとえRunnningj状態のタスクが割り込み中に遷移したと着ても、常にRunnning状態のタスクにリターンする
ほとんどのFreeRTOSポートはportYIELD_FROM_ISRをISRのどこからでも呼び出せる。小規模システムなどで、ISRの最後の最後に限り呼ぶことができるポートもある。
6.3 割り込み処理の先送り
通常、割り込み処理は以下の理由でできるだけ短くした方が良いです。
- 高い優先度のタスクであっても、割り込みがまったく動いていない状況でしか動作しない
- ISRはタスクの開始時間、実行時間に影響を与える
- FreeRTOSが動作しているアーキテクチャに依存して、どんな新しい割り込み、またはサブセットが動作できない可能性がある
- アプリケーション開発者が、task, ISRが同時にアクセスする変数、メモリバッファ、ペリフェラルなどのリソースをガードすることを考える必要がある
- FreeRTOSのポートによっては割り込みをネストできる。ただし、割り込みのネストは複雑性や予測性が下がる。短い割り込みほどネストにもなりにくい
ISRは割り込みの要因を記録、クリアできなければならない。できるだけ早く割り込みを終わらせることで、割り込みによる処理をタスクで動かすのが、実践的な方法となる。
これは"deferred interrupt processing"と呼ばれる。なぜならISRによる処理がタスクまでdefferするからである。
延期した割り込み処理は、アプリケーション開発者に他のタスクとの優先順位付けを可能にする。そして、すべてのFreeRTOS APIが使えるようになる。
延期した割り込み処理を実行するタスクの優先度が他のタスクより上の場合、その処理は即座に実行される。ちょうどISRで実行されたかのように。
このシナリオをFig.48に示す。Task1は通常のアプリケーションタスク、Task2は割り込みがdefferされたタスク。
Fig.48では、t2で割り込みが始まって実質的にはt4で終わるが、ISRはt2からt3までで終わる。もしdeferされた割り込み処理を使わない場合、t4までISRが動作することになる。
ISRでどこまでの処理をするべきなのかは絶対的なルールはない。defferingタスクは以下で使われることが多い
- 割り込みによってすべき処理はわずかではない。たとえば、割り込みがAD変換の結果を保存するだけであれば、ISRでほとんどすべて行うのがベストパフォーマンスである。しかし、結果をソフトウェアフィルタにかけるのであれば、フィルター処理はタスクで行うのが良い
- コンソールへの書き込み、メモリアロケートのようなISR内で動作できないアクション
- どれくらい時間がかかるか読めない処理
次のセクションでは、このチャプターで紹介したコンセプトのデモを説明する。defferd interrupt processingを実現するために使うFreeRTOS機能も含める。
6.4 同期のためのバイナリセマフォ
割り込みセーフなバイナリセマフォAPIは、特定の割り込み発生のタイミングでタスクをアンブロックするために使われ、効果的にタスクと割り込みを同期できる。
これで割り込み処理を同期タスクの中に移動させることができる。割り込み処理の残りを少なくすることが可能にある。
前のセクションで説明したように、バイナリセマフォは割り込み処理をタスクにdeferするために使う。
Figure.48では、割り込み処理は特にtime criticalで、deferred processing taskの優先度は、他のタスクをプリエンプトできる必要がある。
ISRはportYEILD_FROM_ISRを使って実装される。これで直接deferred processing taskにISRからリターンすることができるようになる。
時間的な間隔をあけることなく、あたかもISRですべて処理されたようになる。
Figure 49にFigure 48と同じ図でテキストをアップデータしたものを示す。どのようにしてセマフォをつかって制御しているかを説明する。
deferred processing taskは発生するイベントを待つためにBlocked状態に入る意味で、セマフォに対して"take"を使う。
イベントが発生したとき、ISRは"gave"をアンブロックするために使う。
"Taking a semaphore"と"giving a semaphore"はシナリオに応じて異なる意味を持つ。割り込み同期シナリオでは、バイナリセマフォは長さ1のqueueとして考えることができる。queueはどの場合でも1つの要素を含み、常にempty or full状態である。xSemaphoreTakeを呼ぶことによって、queueがemptyであれば、割り込み処理がdeferrしたタスクは、効率的にブロック時間にqueueを読み取ろうとすることができる。イベントが発生すると、ISRはxSemaphoreGiveFromISRを使ってトークン(セマフォ)をqueueに入れる。queueはfullになる。この場合、タスクはblocked状態を抜けて、トークンを消して、queueがemptyになる。タスクは終了すると、もう一度queueを読みにいくので、queueがemptyであることが分かる。その結果blocked状態に再度入る。これをFigure 50に示す。
Figure 50は割り込みがセファフォをgiveすることを示す。最初にtakeされていないとしても。そして、タスクがtake氏に行くが、giveし返さない。(タスクはtakeするがgiveしていない)そのため、このシナリオはqueueにwrite, readする概念と似たように書かれている。これは、他のセマフォの使い方のルール(takeしたtaskがgiveする)と異なるので混乱するところだ。このようなシナリオはchapter7で扱う。
xSemaphoreCreateBinary() API Function
FreeRTOS V9.0.0 はコンパイル時に静的にバイナリセマフォを生成できるxSemaphoreCreateBinaryStatic関数も使える。FreeRTOSのすべてのタイプのセマフォはSemaphoreHandle_tに保存される。
セマフォは使う前に生成する必要がある。生成するにはxSemaphoreCreateBinary APIを使う
xSemaphoreTake API関数
セマフォを"Taking"するとは、獲得するまたは受信することを意味する。セマフォは利用可能なときだけtakneされる。
recursive mutexを除くすべてのセマフォはxSemaphoreTake関数で取得する。
xSemaphoreTakeはISRで使用してはならない。
xSemaphoreGiveFromISR API
バイナリと計数セマフォはxSemaphoreGiveFromISRで"give"される。
xSemaphoreGiveFromISRはxSemaphoreGiveFromISRの割り込みセーフバージョンとなる。そのためこの章の最初で説明したpxHigherPriorityTakenWokenを持つ。
Example 16. タスクと割り込みを同期するバイナリセマフォ
この例はバイナリセマフォを使ってISRからタスクへのunblockを効率的に同期する。
単純な周期タスクは500msごとにソフトウェア割り込みを生成する。ソフトウェア割り込みは、ターゲット環境での本物の割り込みが複雑性より使い勝手が良いので使われる。Listing 92は周期タスクの実装を示す。このタスクは割り込みの前後で文字列をprintするので、実行されたときのシーケンスを観察できる。
/* The number of the software interrupt used in this example. The code shown is from
the Windows project, where numbers 0 to 2 are used by the FreeRTOS Windows port
itself, so 3 is the first number available to the application. */
#define mainINTERRUPT_NUMBER 3
static void vPeriodicTask( void *pvParameters )
{
const TickType_t xDelay500ms = pdMS_TO_TICKS( 500UL );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Block until it is time to generate the software interrupt again. */
vTaskDelay( xDelay500ms );
/* Generate the interrupt, printing a message both before and after
the interrupt has been generated, so the sequence of execution is evident
from the output.
The syntax used to generate a software interrupt is dependent on the
FreeRTOS port being used. The syntax used below can only be used with
the FreeRTOS Windows port, in which such interrupts are only simulated. */
vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
}
}
Listing 93は割り込み処理がdeferされる実装を示している。タスクとの同期はバイナリセマフォを使っている。printでstringを出力するので処理のシーケンスを追える。
考慮すべきこととして、Listing93はExample16のようにソフトウェアによって生成された割り込みに適したコードであることだ。HWペリフェラル用に適してはいない。
次のサブセクションでHW割り込み用にするための変更点について示す。
static void vHandlerTask( void *pvParameters )
{
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* Use the semaphore to wait for the event. The semaphore was created
before the scheduler was started, so before this task ran for the first
time. The task blocks indefinitely, meaning this function call will only
return once the semaphore has been successfully obtained - so there is
no need to check the value returned by xSemaphoreTake(). */
xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
/* To get here the event must have occurred. Process the event (in this
Case, just print out a message). */
vPrintString( "Handler task - Processing event.\r\n" );
}
}
Listing94はISRを示す。割り込み処理をdeferするために、セマフォを"give"する処理となっている。
xHigherPriorityTaskWoken変数を使っている。SemaphoreGiveFromISRを呼ぶ前にpsFALSEにしている。その後、xHigherPriorityTaskWoneがpdTRUEであてば、portYIELD_FROM_ISRで使われている。コンテキストスイッチportYIELD_FROM_ISRマクロないでリクエストされる。
この強制的なコンテキストスイッチのために呼ばれる。ISR, MacroはWindow Port, そのほかのportでも大体は適切となる。FreeRTOS.orgのport特有のページを参照にして、実装例をダウンロードして、ポートのシンタックスを見つけてください。
ほとんどのFreeRTOSが動作するアーキテクチャと違って、WindowsぽーとではISRに戻り値が必要となります。portYIELD_FROM_ISRマクロはWindowsポートでは戻り値を返すので、Listing94では明示的にはreturnしてない。
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;
/* 'Give' the semaphore to unblock the task, passing in the address of
xHigherPriorityTaskWoken as the interrupt safe API function's
pxHigherPriorityTaskWoken parameter. */
xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
then calling portYIELD_FROM_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling
portYIELD_FROM_ISR() will have no effect. Unlike most FreeRTOS ports, the
Windows port requires the ISR to return a value - the return statement
is inside the Windows version of portYIELD_FROM_ISR(). */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
main関数ではバイナリセマフォ、タスクの生成、割り込みハンドラの登録、スケジューラ起動を行う。実装はListing95に示す。
割り込みを登録する関数はport依存である。FreeRTOS.org参照のこと。
int main( void )
{
/* Before a semaphore is used it must be explicitly created. In this example
a binary semaphore is created. */
xBinarySemaphore = xSemaphoreCreateBinary();
/* Check the semaphore was created successfully. */
if( xBinarySemaphore != NULL )
{
/* Create the 'handler' task, which is the task to which interrupt
processing is deferred. This is the task that will be synchronized with
the interrupt. The handler task is created with a high priority to ensure
it runs immediately after the interrupt exits. In this case a priority of
3 is chosen. */
xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );
/* Create the task that will periodically generate a software interrupt.
This is created with a priority below the handler task to ensure it will
get preempted each time the handler task exits the Blocked state. */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );
/* Install the handler for the software interrupt. The syntax necessary
to do this is dependent on the FreeRTOS port being used. The syntax
shown here can only be used with the FreeRTOS windows port, where such
interrupts are only simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
}
/* As normal, the following line should never be reached. */
for( ;; );
}
Example16の悔過がFigure51となる。期待したように、vHandlerTaskが割り込みが生成されるとすぐに実行状態になる。そのため周期タスクの処理は分割されて出力される。
Figure52で追加の説明をする。
Example16で使われたタスクの実装改善
Example16ではバイナリセマフォをタスク、割り込みの同期に使っていた。シーケンスは以下のようになる。
- 割り込み発生
- ISRはセマフォをgiveしてタスクをunblockする
- タスクはセマフォをtakeして、ISR後にすぐ実行される。
- タスクは再度セマフォをtakeしにいき、blocked状態になる
この構造は割り込みが低い頻度で発生するときには適している。これを理解するにはタスクが終わる前に、2つ目、3つ目の割り込みが発生する場合を考えてみる
- ISRが再度セマフォをgiveするので、タスクはすぐ2回目の処理を実行することになる。このシナリオをFigure 53に示す。
- ISRが実行されたとき、セマフォはすでに利用可能状態なので、ISRが再度giveすることを妨げる。そのためタスクは3回目のイベントの発生を知ることができない。このシナリオをFigure54に示す。
Example16, Listing 93でdeferされた割り込み処理を扱うタスクはSemaphoreTakeがの間に1つの処理しか実行しない時の構造である。これはExample16に適切で、なぜならソフトウェア割り込みなので、発生するタイミングが予想できるからである。本物のアプリケーションでは、HWによって割り込みが発生するので予測ができない。そのため、割り込みを取りこぼさないためには、deferred interrupt handling taskはxSemaphoreTake間のイベントを実行できるような構造にしなければならない。Listing96でどのようにuartの割り込みをdeferするのかを示す。UART割り込みは1文字受信するごとに発生して、FIFOに受信データを置く。
Example16のdeferred interrupt hadling taskは別の弱点も持つ。xSemaphoreTakeを呼ぶときにタイムアウトを使っていなことだ。代わりにprotMAC_DELAYをxSemaphoreTakeのxTicksToWaitパラメータとして使っている。これはタイムアウトなしでセマフォが利用可能になるまで待つ。タイムアウトなしはシンプルで理解しやすいので例としてはよく使われる。しかし、タイムアウトなしは本物のアプリケーションでは、良くない方法だ。エラーのリカバーが難しくなるからだ。例として、タスクがセマフォを割り込みからgiveされるのを待つが、HWのエラー状態が割り込みの発生を妨げているシナリオを考える。
- タスクがタイムアウトなしの場合、エラー状態がわからないので無限に待つことになる
- タスクがタイムアウトありの場合、xSemaphoreTakeはpdFAILを返すので、エラーを検出、クリアできる。このシナリオをListing 96に示す。
static void vUARTReceiveHandlerTask( void *pvParameters )
{
/* xMaxExpectedBlockTime holds the maximum time expected between two interrupts. */
const TickType_t xMaxExpectedBlockTime = pdMS_TO_TICKS( 500 );
/* As per most tasks, this task is implemented within an infinite loop. */
for( ;; )
{
/* The semaphore is 'given' by the UART's receive (Rx) interrupt. Wait a
maximum of xMaxExpectedBlockTime ticks for the next interrupt. */
if( xSemaphoreTake( xBinarySemaphore, xMaxExpectedBlockTime ) == pdPASS )
{
/* The semaphore was obtained. Process ALL pending Rx events before
calling xSemaphoreTake() again. Each Rx event will have placed a
character in the UART’s receive FIFO, and UART_RxCount() is assumed to
return the number of characters in the FIFO. */
while( UART_RxCount() > 0 )
{
/* UART_ProcessNextRxEvent() is assumed to process one Rx character,
reducing the number of characters in the FIFO by 1. */
UART_ProcessNextRxEvent();
}
/* No more Rx events are pending (there are no more characters in the
FIFO), so loop back and call xSemaphoreTake() to wait for the next
interrupt. Any interrupts occurring between this point in the code and
the call to xSemaphoreTake() will be latched in the semaphore, so will
not be lost. */
}
else
{
/* An event was not received within the expected time. Check for, and if
necessary clear, any error conditions in the UART that might be
preventing the UART from generating any more interrupts. */
UART_ClearErrors();
}
}
}
6.5 計数セマフォ(Couting Semaphores)
バイナリセマフォは長さ1のqueueと考えられる。計数セマフォは1以上の長さを持つqueueと考えられる。タスクはqueueに保存されているデータには興味がなく、queueのアイテム数だけに興味がある。計数セマフォを利用可能にする場合、mconfigUSE_COUTING_SEMAPHORESは1にする。
計数セマフォがgiveされた場合、queueの他の領域が使われる。queueのアイテム数はセマフォのcount値となる。
計数セマフォは通常2つのものに使われる
-
イベントを数える
このシナリオでは、イベントハンドラが、イベント発生時にセマフォをgiveする。count値はそれぞれのgiveでインクリメントされる。タスクはイベントが発生したときにセマフォをtakeして、takeのたびにディクリメントさせる。count値はeventの発生回数によって異なる。メカニズムはFigure 55に示す。 -
リソースの管理
このシナリオではcount値は利用可能なリソースを示す。リソースの取得制御のために、タスクは最初にセマフォを取得する(ディクリメントする)。countがゼロであれば、利用可能なリソースがないことになる。タスクがリソースを使い終わったときにはセマフォをgiveして返す。リソース管理に使われる時のcount値は、利用可能な数で初期化される。7章でこの内容は説明される。
xSemaphoreCreateCounting API
FreeRTOS V9ではxSemaphoreCreateCountingStaticを使って、コンパイル時に静的に確保することができる。セマフォのハンドルはタイプにかかわらずSemaphoreHandle_tに保存される。
セマフォは使う前にxSemaphoreCreateCoutingを使って生成する必要がある。
Example17 計数セマフォをつかった、タスクと割り込みの同期
Example17ではExample16から、xSemaphoreCreateBinaryで計数セマフォに変えている。新しいAPIはListing98に示す
/* Before a semaphore is used it must be explicitly created. In this example a
counting semaphore is created. The semaphore is created to have a maximum count
value of 10, and an initial count value of 0. */
xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
速い周期での割り込みをシミュレートするめに、一回の割り込みで複数のセマフォをgiveしている。変更したコードをListing99に示す。
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;
/* 'Give' the semaphore multiple times. The first will unblock the deferred
interrupt handling task, the following 'gives' are to demonstrate that the
semaphore latches the events to allow the task to which interrupts are deferred
to process them in turn, without events getting lost. This simulates multiple
interrupts being received by the processor, even though in this case the events
are simulated within a single interrupt occurrence. */
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR() then
calling portYIELD_FROM_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling portYIELD_FROM_ISR() will
have no effect. Unlike most FreeRTOS ports, the Windows port requires the ISR to
return a value - the return statement is inside the Windows version of
portYIELD_FROM_ISR(). */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
他の関数は、Example16から変更していない。
このExample17の出力をFigure56に示す。deferされた処理を実行するタスクは3回のイベントを処理している。これらイベントは計数セマフォにラッチされて、タシクが順番に処理することができるようになっている。
6.6 RTOSデーモンタスクにdeferする
ここまで説明したdeferred interrupt handling例は、開発者がタスクを生成する必要がある。また、xTimerPendFuctionCallFromISR APIをRTOSデーモンタスクにdeferするために使うこともできる。各割り込みの処理をtaskに分割することなく。
割り込み処理はデーモンタスクにdeferすることを、"centralized deferred interrupt processing"と呼ぶ。
5章では、どのようにしてFreeRTOS APIのソフトウェアタイマーがデーモンタスクにコマンドを送るのかを説明した。xTimerPendFuctionCallとxTimerPendFuctionCallFromISR APIは同じタイマーコマンドキューを使って、"execute function"コマンドをデーモンタスクに送る。この送信された関数はデーモンタスクのコンテキストで実行される。
centralized deferred interruptの利点は
・リソースの低使用率
各割り込みのために、分割したタスクを生成する必要がない
・ユーザーモデルの単純化
deferred interrupt handling functionは標準C関数となる
centralized deferred interruptの欠点は
・低いフレキシビリティ
それぞれのdeferred interrupt handling taskに優先度を設定することができない。すべてのタスクはデーモンタスクの優先度で実行される。5章で示した通り、configTIMER_TASK_PRIORITYでデーモンタスクの優先度は決まる。
xTimerPendFunctionCallFromISRはtimerコマンドキューの後ろにコマンドを送る。キューに入っているコマンドはxTimerPendFunctionCallFromISRからキューに送られた"execute function"コマンド前に、デーモンタスクから実行される。
異なる割り込みは異なる時間制約を持っているので、同じアプリケーション内でのdeferring interrupt processingで使われるのが普通。
xTimerPendFunctionCallFromISR API
xTimerPendFunctionCallFromISRはxTimerPendFunctionCallの割り込みセーフバージョンとなっている。両方のAPIともに、RTOSデーモンタスクのコンテキストで関数が実行させることができる。関数を実行するには、関数の入力パラメータ値がデーモンタスクのtimer command queueに送られる。そのため実行時にはデーモンタスクの優先度となる。
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend,
void *pvParameter1,
uint32_t ulParameter2,
BaseType_t *pxHigherPriorityTaskWoken );
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );
Example 18. Centralized deferred interrupt processing
Exmaple18にExample16と同じ機能の例を示す。ただし、セマフォと割り込みの処理を実行する特殊なタスクは使っていない。代わりにFreeRTOSのデーモンタスクを使っている。
ISRはExample18のListing102で示したものとなっている。xTimerPendFunctionCallFromISRでvDeferredHandlingFunctionの関数ポインタをデーモンタスクに渡している。deferred interrupt processingはvDeferredhandlingFunctionで実行される。
ISRはulParameterValueを実行ごとにインクリメントする。ulParameterValueはxTimerPendFunctionCallFromISRを呼ぶときのulParameter2の値として使われる。また、vDeferredHandlingFuctionがデーモンタスクで実行される時のulParameterとしても使われる。pvParameter1はこの例では使用されない。
static uint32_t ulExampleInterruptHandler( void )
{
static uint32_t ulParameterValue = 0;
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 pointer to the interrupt's deferred handling function to the daemon task.
The deferred handling function's pvParameter1 parameter is not used so just set to
NULL. The deferred handling function's ulParameter2 parameter is used to pass a
number that is incremented by one each time this interrupt handler executes. */
xTimerPendFunctionCallFromISR( vDeferredHandlingFunction, /* Function to execute. */
NULL, /* Not used. */
ulParameterValue, /* Incrementing value. */
&xHigherPriorityTaskWoken );
ulParameterValue++;
/* Pass the xHigherPriorityTaskWoken value into portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xTimerPendFunctionCallFromISR() then
calling portYIELD_FROM_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling portYIELD_FROM_ISR() will have
no effect. Unlike most FreeRTOS ports, the Windows port requires the ISR to return a
value - the return statement is inside the Windows version of portYIELD_FROM_ISR(). */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
vDeferredHandlingFuctionの実装をListing103に示す。ulParameter2をprintしている。
この例では1つのパラメータしか設定していないが、vDeferredHandlingFunctionはListing101のプロトタイプを持たなければならない。
static void vDeferredHandlingFunction( void *pvParameter1, uint32_t ulParameter2 )
{
/* Process the event - in this case just print out a message and the value of
ulParameter2. pvParameter1 is not used in this example. */
vPrintStringAndNumber( "Handler function - Processing event ", ulParameter2 );
}
Exapmle18のmain関数をListing104に示す。Example16よりシンプルになっている。セマフォやタスク生成がなくなっているから.
vPeriodicTaskはソフトウェア割り込みで実行されるタスクとなっている。デーモンタスクより低い優先度なので、デーモンタスクによってプリエンプトされる。
int main( void )
{
/* The task that generates the software interrupt is created at a priority below the
priority of the daemon task. The priority of the daemon task is set by the
configTIMER_TASK_PRIORITY compile time configuration constant in FreeRTOSConfig.h. */
const UBaseType_t ulPeriodicTaskPriority = configTIMER_TASK_PRIORITY - 1;
/* Create the task that will periodically generate a software interrupt. */
xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, ulPeriodicTaskPriority, NULL );
/* Install the handler for the software interrupt. The syntax necessary to do
this is dependent on the FreeRTOS port being used. The syntax shown here can
only be used with the FreeRTOS windows port, where such interrupts are only
simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* Start the scheduler so the created task starts executing. */
vTaskStartScheduler();
/* As normal, the following line should never be reached. */
for( ;; );
}
Example18の出力をFigure57に示す。デーモンタスクの優先度はソフトウェア割り込みのタスクより高いので、vDeferredHandlingFunctionは割り込みが発生した直後に実行される。vDeferredHandlingfucntionのメッセージ出力は周期タスクの間に表示される。これはセマフォでdeferred interrupt processing taskをunblockするときに似ている。
追加の説明はFigure58に示す。
6.7 ISR内でキューを使う
バイナリ、計数セマフォはイベントとコミュニケーションするために使われる。キューはコミュニケーションとデータを送るために使われる。
xQueueSendToFrontFromISRはxQueueSendToFrontの割り込みセーフバージョン。xQueueSendToBackFromISRはxQueueSendToBackの割り込みセーフバージョン。xQueueReceiveFromISRはxQueueReceiveの割り込みセーフバージョンとなっている。
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,
void *pvItemToQueue
BaseType_t *pxHigherPriorityTaskWoken
);
xQueueSendFromISR()は機能的にxQueueSendToBackFromISR() と同じである。
ISRでqueueを使うことの考察
queueは割り込みからタスクにデータを渡す簡単な方法だが、データが高頻度の場合は効率が悪い。
ISRでUART受信した文字をqueueを使って渡すような、FreeRTOSのデモアプリケーションが多くある。これらは、queueを2つの理由絵使っている。
ISRからqueueを使うこと、意図的にシステムをロードして、FreeRTOSポートをテストすることである。
queueを使うISRの設計は、データの到着が遅いのでなければ、効率的意図では決してない。プロダクトコードではコピーしないことを進める。もっと効率的な方法がある
- データ受信にDMAを使う。この方法はソフトウェアのオーバーヘッドがない。direct to task notification(9章で説明)でデータ受信時のみタスクをunblockする
- スレッドセーフなRAMにデータをコピーする。direct to task notificationをコピーが終わった後、もしくは送信を検出したタイミングでタスクをunblockするために使う。
- ISR内で直接文字を受信する。Figre34のようにキューをつかってそのデータを送る
Example 19. 割り込み内からキューで送る
この例は、xQueueSendToBackFromISR, xQueueReceiveFromISRを同じ割り込み内で使うデモとなっている。そのまえにソフトウェア割り込みを生成しておく。
周期タスクは200msごとに5個をqueueに送る。すべての送信が終わってから、ソフトウェア割り込みを発生させる。このタスクをListing107に示す。
static void vIntegerGenerator( void *pvParameters )
{
TickType_t xLastExecutionTime;
uint32_t ulValueToSend = 0;
int i;
/* Initialize the variable used by the call to vTaskDelayUntil(). */
xLastExecutionTime = xTaskGetTickCount();
for( ;; )
{
/* This is a periodic task. Block until it is time to run again. The task
will execute every 200ms. */
vTaskDelayUntil( &xLastExecutionTime, pdMS_TO_TICKS( 200 ) );
/* Send five numbers to the queue, each value one higher than the previous
value. The numbers are read from the queue by the interrupt service routine.
The interrupt service routine always empties the queue, so this task is
guaranteed to be able to write all five values without needing to specify a
block time. */
for( i = 0; i < 5; i++ )
{
xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
ulValueToSend++;
}
/* Generate the interrupt so the interrupt service routine can read the
values from the queue. The syntax used to generate a software interrupt is
dependent on the FreeRTOS port being used. The syntax used below can only be
used with the FreeRTOS Windows port, in which such interrupts are only
simulated.*/
vPrintString( "Generator task - About to generate an interrupt.\r\n" );
vPortGenerateSimulatedInterrupt( mainINTERRUPT_NUMBER );
vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
}
}
ISRはxQueueReceiveFromISRをqueueのメッセージを読み切るまで実行する。最後の2つのビットは、文字列のindexとなっている。index位置に対応する文字列のポインタは別のqueueでxQueueSendFromISRを使って送られる。この実装をListing108に示す
static uint32_t ulExampleInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulReceivedNumber;
/* The strings are declared static const to ensure they are not allocated on the
interrupt service routine's stack, and so exist even when the interrupt service
routine is not executing. */
static const char *pcStrings[] =
{
"String 0\r\n",
"String 1\r\n",
"String 2\r\n",
"String 3\r\n"
};
/* As always, xHigherPriorityTaskWoken is initialized to pdFALSE to be able to
detect it getting set to pdTRUE inside an interrupt safe API function. Note that
as an interrupt safe API function can only set xHigherPriorityTaskWoken to
pdTRUE, it is safe to use the same xHigherPriorityTaskWoken variable in both
the call to xQueueReceiveFromISR() and the call to xQueueSendToBackFromISR(). */
xHigherPriorityTaskWoken = pdFALSE;
/* Read from the queue until the queue is empty. */
while( xQueueReceiveFromISR( xIntegerQueue,
&ulReceivedNumber,
&xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
{
/* Truncate the received value to the last two bits (values 0 to 3
inclusive), then use the truncated value as an index into the pcStrings[]
array to select a string (char *) to send on the other queue. */
ulReceivedNumber &= 0x03;
xQueueSendToBackFromISR( xStringQueue,
&pcStrings[ ulReceivedNumber ],
&xHigherPriorityTaskWoken );
}
/* If receiving from xIntegerQueue caused a task to leave the Blocked state, and
if the priority of the task that left the Blocked state is higher than the
priority of the task in the Running state, then xHigherPriorityTaskWoken will
have been set to pdTRUE inside xQueueReceiveFromISR().
If sending to xStringQueue caused a task to leave the Blocked state, and if the
priority of the task that left the Blocked state is higher than the priority of
the task in the Running state, then xHigherPriorityTaskWoken will have been set
to pdTRUE inside xQueueSendToBackFromISR().
xHigherPriorityTaskWoken is used as the parameter to portYIELD_FROM_ISR(). If
xHigherPriorityTaskWoken equals pdTRUE 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 );
}
文字列ポインタを受信するタスクはISRから送られるまで、block状態にある。メッセージが届いたら、それぞれの文字列をprintする。実装をListing109に示す。
static void vStringPrinter( void *pvParameters )
{
char *pcString;
for( ;; )
{
/* Block on the queue to wait for data to arrive. */
xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
/* Print out the string received. */
vPrintString( pcString );
}
}
mainは必要なqueue, task, スケジューラ起動を行う。Listing110に示す。
int main( void )
{
/* Before a queue can be used it must first be created. Create both queues used
by this example. One queue can hold variables of type uint32_t, the other queue
can hold variables of type char*. Both queues can hold a maximum of 10 items. A
real application should check the return values to ensure the queues have been
successfully created. */
xIntegerQueue = xQueueCreate( 10, sizeof( uint32_t ) );
xStringQueue = xQueueCreate( 10, sizeof( char * ) );
/* Create the task that uses a queue to pass integers to the interrupt service
routine. The task is created at priority 1. */
xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );
/* Create the task that prints out the strings sent to it from the interrupt
service routine. This task is created at the higher priority of 2. */
xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );
/* Install the handler for the software interrupt. The syntax necessary to do
this is dependent on the FreeRTOS port being used. The syntax shown here can
only be used with the FreeRTOS Windows port, where such interrupts are only
simulated. */
vPortSetInterruptHandler( mainINTERRUPT_NUMBER, ulExampleInterruptHandler );
/* Start the scheduler so the created tasks start executing. */
vTaskStartScheduler();
/* If all is well then main() will never reach here as the scheduler will now be
running the tasks. If main() does reach here then it is likely that there was
insufficient heap memory available for the idle task to be created. Chapter 2
provides more information on heap memory management. */
for( ;; );
}
Figure59が出力結果になる。割り込みは5つの整数を受信して、5つの文字列をレスポインスしている。追加の説明をFigure60に示す。
6.8 割り込みの入れ子
タスク優先度と割り込み優先度で混乱が生じることがよくある。このセクションでは割り込みの優先度に関して議論する。どのISR実行のどの優先度が他に関係するのか。
タスクの優先度は割り込みの優先度にまったく関係しない。ハードウェアがISRの発生時間を決め、ソフトウェアがタスクの実行時間を決める。割り込みはタスクにinterruptできるが、タスクはISRをプリエンプトすることはできない。
ポートはTable39で示した詳細の制約で割り込みのネストをサポートしている。
・Numeric Priority
numeric priorityは割り込み優先度につけられたシンプルな値。たとえば、ある割り込みが7であれば、numeric priorityは7となる。同様に200であればnumeric priorityは200となる
・Logical priority
Logical priorityは割り込みの処理が、他の割り込みより優位にあることを示す。
もし、異なる優先度の2つの割り込みが同じ時間に発生した場合、プロセッサはlogical priorityの高いISRを先に実行する。
割り込みは低い優先度の割り込みをネストしすることで割り込めるが、同じまたは大きい割り込みに対してはネストして割り込めない。
Numeric PriorityとLogical Priorityの関係はプロセッサアーキテクチャに依存する。あるプロセッサアーキテクチャでは、numeric priorityが高い場合はlogicalも高くなる。一方別のアーキテクチャでは、numeric priorityが低いとlogical priorityが高くなる。
full interupt nesting modelとはconfigMAX_SYSCALL_INTERRUPT_PRIORITYがconfigKERNEL_INTERRUPT_PRIORITYよりlogical priorityが高い設定となる。
これをFigure61で示す。シナリオは以下となる
- プロセッサは7つのユニークは優先度を設定できる
- 数字が大きい方がlogicalとして高い優先度を持つ
- configKERNEL_INTERRUPT_PRIORITYは1
- configMAX_SYSCALL_INTERRUPT_PRIORITYは3
Figure61の説明
- 優先度1から3の割り込みはカーネルや他のアプリのクリティカルセクションからpreventされる。これらのISRの実行は割り込みセーフのFreeRTOS APIを使うことができる。クリティカルセクションに関しては7章で説明する
- 優先度4以上の割り込みは、クリティカルセクションの影響を受けない。そのためスケジューラはハードウェアの限界内で、これらに割り込むことはできない。これらISRの実行はどのFreeRTOS APIの実行もできない。
- 通常、機能は厳しい時間制約があり、優先度は configMAX_SYSCALL_INTERRUPT_PRIORITY以上で設定される。スケジューラーによるジッターで応答時間に影響がないように。
ARM Cortex-M, ARM GICユーザーへの注意
Cortex-Mの割り込みは混乱しやすく、エラーを招きやすい。開発をアシストするために、configASSERT()を定義すると、FreeRTOS Cortex-Mポートは自動で割り込み設定を確認する。ConfigASSERTは11.2セクションで説明する。
ARM Cortexコア、ARM GIC(Generic Interrupt Controller)では、numericで低い優先度がlogicalの高い優先度となる。これは直感に反するので忘れやすい。
もし、logicalに低い優先度に設定したい場合は、numericには高い設定をする必要がある。logicalに高い場合はその逆。
Cortex-Mの割り込みコントローラーは優先度を決めるのに8bit使うことができる。0が最も高い優先度で、255のそれより低い優先度を作れる。
しかし、Coretex-Mは通常8bitのサブセットで実装する。これはマクロコントローラのファミリーに依存する。
サブセットが1つだけ実装された場合は、MSBが使われる。残りは実装されない。
実装されなかったbitはどんな値でも良いが、大抵は1に設定される。Figure62に2進数で101がどのようにCortex-Mに実装されるかを示す。
Figure62では、101はMSBにシフトされる。使われないbitは1にセットされる。
いくつかのライブラリ関数は、MSBにシフトされあとの優先度を決めることを期待している。このような関数を使う時には、Figure62に示す優先度はdecimalで95になる。Decimal 95はBinary 101を4つシフトさせた値で、実装されていないbitを1にすることで得られる。
configMAX_SYSCALL_INTERRUPT_PRIORITYは実装されたビットまでシフトして 直接Cortex-Mのレジスタに書く必要がある。
configKERNEL_INTERRUPT_PRIORITYは常に、最も低い優先度にする必要がある。実装されていないbitは1にする。そのため、どのbitが実装されていようが、値は255となる。
Cortex-M割り込みはデフォルトは0、つまり最も高い優先度となる。Cortex-Mの実装は configMAX_SYSCALL_INTERRUPT_PRIORITY を0にすることを許していないので、FreeRTOS APIを使うこの優先度のタスクはデフォルト値で残すべきではない。