11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Chapter 4 割り込み管理(Interrupt Management) (FreeRTOS チュートリアル日本語訳)

Last updated at Posted at 2020-08-30

この記事について

Mastering the FreeRTOS Real Time Kernel-A Hands-On Tutorial Guildの日本語訳

Chapter 6 割り込み管理(Interrupt Management)

6.1 チャプターのイントロダクションとスコープ

イベント

組み込みシステムでは、環境から発生したイベントに対してアクションをとる必要がある。例えば、イーサネットで届いたパケットは、TCP/IPスタックの処理を必要とする。
小規模システムでなければ、複数のソースからイベントを扱う必要がある。これらの処理はそれぞれ異なる処理オーバーヘッドと応答性を要求される。
どの場合でも、ベストのイベント処理実装戦戦略のための判断が必要となる

  1. どのようにイベントを検知すべきか?通常割り込みで扱うが、ポーリングもできる。
  2. 割り込みが使われる時には、割り込みサービスルーチン(ISR)内でどれくらいの処理をすべきか?外ではどれくらいか?通常はできるだけISR内を短くする。
  3. どのようにイベントは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から呼び出したい時がある。

これは通常、サードパーティのコードを統合するときにおこる。開発者にとってコントロールできない唯一の設計だからだ。
この問題は以下のテクニックで解決することができる。

  1. 割り込み処理をタスクまで延期する。そのサードパーティAPIを常にタスクから呼び出すようにする
  2. interrupt nestingをサポートしているportであれば、"FromISR"で終わるバージョンのAPI関数を使う。そのバージョンはタスクとISRから呼び出すことができるため。(逆は成り立たない。"FromISR"で終わらないAPI関数はISRから呼び出してはならない)
  3. サードパーティのコードは通常、タスク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デーモンタスクへのスイッチを行う。コマンドキューを書いたタスクが終了する前に。
image.png

・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. 不要なコンテキストスイッチを防ぐため
    割り込みはタスクが何らかの動作が必要になるまでに、複数回実行されることがある。たとえば、文字列を処理する1つのタスクが、受信をUARTからの割り込みドリブンで行う状況を考える。この場合、UART ISRにとて、1つの文字を受信するたびにタスクスイッチすることは無駄である。タスクは完全な文字列を受け取ってから動作すればよいため。
  1. 実行シーケンスの制御
    割り込みは散発的に、予想できないタイミングで発生する。FreeRTOSのエキスパートユーザーはアプリケーションの特定のポイントで、一時的に予測できないタスクスイッチを回避したいことがある。FreeRTOSのスケジューリングアルゴリズムがそのように動作するにもかかわらず。
  1. ポータビリティ
    全てのFreeRTOSポートで使われているシンプルなメカニズム
  1. 効率性
    小さなプロセッサアーキテクチャをターゲットにしたポートでは、ISRの最後の最後のみでコンテキストスイッチを許可しており、この制約を除去するには追加の複雑なコードが必要となる。また、1つ以上のコンテキストスイッチを要求しない、同じISR内からの1つ以上のFreeRTOS APIコールを許すことができる。
  1. ティック割り込み内の実行
    この本の後で説明されるが、アプリケーションコードをティック割り込みに追加することが可能。ティック割り込み内でのコンテキストスイッチをする結果は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されたタスク。
image.png
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と同じ図でテキストをアップデータしたものを示す。どのようにしてセマフォをつかって制御しているかを説明する。
image.png

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で扱う。
image.png

xSemaphoreCreateBinary() API Function

FreeRTOS V9.0.0 はコンパイル時に静的にバイナリセマフォを生成できるxSemaphoreCreateBinaryStatic関数も使える。FreeRTOSのすべてのタイプのセマフォはSemaphoreHandle_tに保存される。

セマフォは使う前に生成する必要がある。生成するにはxSemaphoreCreateBinary APIを使う
image.png

xSemaphoreTake API関数

セマフォを"Taking"するとは、獲得するまたは受信することを意味する。セマフォは利用可能なときだけtakneされる。
recursive mutexを除くすべてのセマフォはxSemaphoreTake関数で取得する。
xSemaphoreTakeはISRで使用してはならない。
image.png
image.png

xSemaphoreGiveFromISR API

バイナリと計数セマフォはxSemaphoreGiveFromISRで"give"される。
xSemaphoreGiveFromISRはxSemaphoreGiveFromISRの割り込みセーフバージョンとなる。そのためこの章の最初で説明したpxHigherPriorityTakenWokenを持つ。
image.png
image.png

Example 16. タスクと割り込みを同期するバイナリセマフォ

この例はバイナリセマフォを使ってISRからタスクへのunblockを効率的に同期する。
単純な周期タスクは500msごとにソフトウェア割り込みを生成する。ソフトウェア割り込みは、ターゲット環境での本物の割り込みが複雑性より使い勝手が良いので使われる。Listing 92は周期タスクの実装を示す。このタスクは割り込みの前後で文字列をprintするので、実行されたときのシーケンスを観察できる。

Listing92
/* 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割り込み用にするための変更点について示す。

Listing93
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してない。

Listing94
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参照のこと。

Listing95
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で追加の説明をする。
image.png
image.png

Example16で使われたタスクの実装改善

Example16ではバイナリセマフォをタスク、割り込みの同期に使っていた。シーケンスは以下のようになる。

  1. 割り込み発生
  2. ISRはセマフォをgiveしてタスクをunblockする
  3. タスクはセマフォをtakeして、ISR後にすぐ実行される。
  4. タスクは再度セマフォをtakeしにいき、blocked状態になる

この構造は割り込みが低い頻度で発生するときには適している。これを理解するにはタスクが終わる前に、2つ目、3つ目の割り込みが発生する場合を考えてみる

  • ISRが再度セマフォをgiveするので、タスクはすぐ2回目の処理を実行することになる。このシナリオをFigure 53に示す。
  • ISRが実行されたとき、セマフォはすでに利用可能状態なので、ISRが再度giveすることを妨げる。そのためタスクは3回目のイベントの発生を知ることができない。このシナリオをFigure54に示す。

image.png
image.png

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に示す。
Listing96
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つのものに使われる

  1. イベントを数える
    このシナリオでは、イベントハンドラが、イベント発生時にセマフォをgiveする。count値はそれぞれのgiveでインクリメントされる。タスクはイベントが発生したときにセマフォをtakeして、takeのたびにディクリメントさせる。count値はeventの発生回数によって異なる。メカニズムはFigure 55に示す。

  2. リソースの管理
    このシナリオではcount値は利用可能なリソースを示す。リソースの取得制御のために、タスクは最初にセマフォを取得する(ディクリメントする)。countがゼロであれば、利用可能なリソースがないことになる。タスクがリソースを使い終わったときにはセマフォをgiveして返す。リソース管理に使われる時のcount値は、利用可能な数で初期化される。7章でこの内容は説明される。
    image.png

xSemaphoreCreateCounting API

FreeRTOS V9ではxSemaphoreCreateCountingStaticを使って、コンパイル時に静的に確保することができる。セマフォのハンドルはタイプにかかわらずSemaphoreHandle_tに保存される。
セマフォは使う前にxSemaphoreCreateCoutingを使って生成する必要がある。
image.png
image.png

Example17 計数セマフォをつかった、タスクと割り込みの同期

Example17ではExample16から、xSemaphoreCreateBinaryで計数セマフォに変えている。新しいAPIはListing98に示す

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に示す。

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回のイベントを処理している。これらイベントは計数セマフォにラッチされて、タシクが順番に処理することができるようになっている。
image.png

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に送られる。そのため実行時にはデーモンタスクの優先度となる。

Listing100
BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t xFunctionToPend, 
                                          void *pvParameter1, 
                                          uint32_t ulParameter2, 
                                          BaseType_t *pxHigherPriorityTaskWoken ); 
Listing101
void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 ); 

image.png
image.png
image.png

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はこの例では使用されない。

Listing102
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のプロトタイプを持たなければならない。

Listing103
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はソフトウェア割り込みで実行されるタスクとなっている。デーモンタスクより低い優先度なので、デーモンタスクによってプリエンプトされる。

Listing104
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に示す。
image.png
image.png

6.7 ISR内でキューを使う

バイナリ、計数セマフォはイベントとコミュニケーションするために使われる。キューはコミュニケーションとデータを送るために使われる。

xQueueSendToFrontFromISRはxQueueSendToFrontの割り込みセーフバージョン。xQueueSendToBackFromISRはxQueueSendToBackの割り込みセーフバージョン。xQueueReceiveFromISRはxQueueReceiveの割り込みセーフバージョンとなっている。

Listing105
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue,  
                                     void *pvItemToQueue 
                                     BaseType_t *pxHigherPriorityTaskWoken  
                                    ); 
Listing106
BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue,  
                                    void *pvItemToQueue 
                                    BaseType_t *pxHigherPriorityTaskWoken  
                                   ); 

xQueueSendFromISR()は機能的にxQueueSendToBackFromISR() と同じである。
image.png
image.png

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に示す。

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に示す

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に示す。

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に示す。

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に示す。
image.png
image.png

6.8 割り込みの入れ子

タスク優先度と割り込み優先度で混乱が生じることがよくある。このセクションでは割り込みの優先度に関して議論する。どのISR実行のどの優先度が他に関係するのか。
タスクの優先度は割り込みの優先度にまったく関係しない。ハードウェアがISRの発生時間を決め、ソフトウェアがタスクの実行時間を決める。割り込みはタスクにinterruptできるが、タスクはISRをプリエンプトすることはできない。

ポートはTable39で示した詳細の制約で割り込みのネストをサポートしている。
image.png

・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

image.png

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に実装されるかを示す。
image.png

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を使うこの優先度のタスクはデフォルト値で残すべきではない。

11
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?