FreeRTOSの例を使用したRTOSでのメッセージの処理

Logo_FreeRTOS こんにちは。 この記事では、スレッド間のメッセージングのためのFreeRTOSのHandlerパターンの1つの可能な実装について説明します。 この記事は、主にマイクロコントローラー、DIY愛好家、RTOSやマイクロコントローラーを勉強している人向けのプロジェクトでオペレーティングシステムを使用している人を対象としています。

読者は、キューやフローなど、RTOSに関連する基本用語に精通していることを前提としています。 FreeRTOSについての詳細は、 qdxの投稿FreeRTOS:紹介およびFreeRTOS:プロセス間通信で読むことができます。

FreeRTOSを使用したマイクロコントローラーのプロジェクトに参加した人は、標準APIがかなり貧弱であるため、追加のコードを書く必要が生じるという事実に出くわすかもしれません。 私の場合、フロー間の相互作用のためのツール、つまりユニファイドメッセージングシステムの欠如がありました。 通常、スレッドと同期の間で情報を交換するには、1つまたは別の形式のキューが使用されます。 さらに、キューに含まれる情報のタイプは毎回異なり、コードの再利用の可能性を減らします。

統合メッセージフォームを使用すると、多くの場合、複数のスレッドを1つのワーカースレッドに結合して、キュー内の受信メッセージを処理できます。



この考え方はAndroidでHandlerクラスを使用するのと似ているため、名前(クラスフィールドと構造の名前を含む)はそこから恥知らずに借用されます。

このアプローチは、単一のスレッドを使用して複数のタイプのメッセージを処理し、キューからメッセージを抽出し、対応するハンドラーを呼び出し、次のメッセージに進むことに基づいています。

スレッドはキューでブロックされているため、メッセージがない場合、制御は他のスレッドに転送されます。 新しいメッセージがキューに入れられるとすぐに、スレッドのロックが解除され、メッセージが処理されます。 メッセージは、割り込みハンドラー、他のスレッド、他のハンドラー、または自分でキューに入れることができます。



他のスレッドと同様に、ワーカースレッド(またはルーパー)は、優先度の高い別のスレッドに置き換えることができます。 優先順位の異なる複数のルーパーを使用すると、最も重要なメッセージをタイムリーに処理できます。 理想的には、ハンドラごとに一意の優先度を持つ1つのスレッド(残念ながら、常に妥協点があります)。

なぜこれがすべて必要なのですか?


まず、このアプローチは柔軟性を提供します。 これにより、多くのイベントに応答する複雑なカプセル化オブジェクトを作成できます。 最近のプラクティスの例としては、RFIDリーダークラスがあります。これは、最初はコマンドラインでのみ機能すると想定されていました。 その結果、ハンドラーはステートマシンになり、フォルダー、タイマー、モーションセンサー、およびバッテリーレベルモニターからのメッセージがコマンドラインからのメッセージに追加されました。

チャート




実装例


単純なC ++プログラムの例で上記を考慮してください。 Threadクラスの説明は行いません。Threadの子孫は、スレッドの本体であるrun()メソッドをオーバーライドする必要があることに言及するだけで十分です。



各メッセージは構造です:


struct MESSAGE { /** Handler responsible for handling this message */ Handler *handler; /** What message is about */ char what; /** First argument */ char arg1; /** Second argument */ char arg2; /** Pointer to the allocated memory. Handler should cast to the proper type, * according to the message.what */ void *ptr; };
      
      





ルーパーストリームの実装例:


 Looper::Looper(uint8_t messageQueueSize, const char *name, unsigned short stackDepth, char priority): Thread(name, stackDepth, priority) { messageQueue = xQueueCreate(messageQueueSize, sizeof(Message)); } void Looper::run() { Message msg; for (;;) { if (xQueueReceive(messageQueue, &msg, portMAX_DELAY)) { // Call handleMessage from the handler msg.handler->handleMessage(msg); } } } xQueueHandle Looper::getMessageQueue(){ return messageQueue; }
      
      





抽象ハンドラーの実装例(すべてのメソッドではありません):


 Handler::Handler(Looper *looper) { messageQueue = looper->getMessageQueue(); } bool Handler::sendMessage(char what, char arg1, char arg2, void *ptr) { Message msg; msg.handler = this; msg.what = what; msg.arg1 = arg1; msg.arg2 = arg2; msg.ptr = ptr; return xQueueSend(messageQueue, &msg, 0); }
      
      





ハンドラーの実装例:


Looperが呼び出す1つの仮想メソッドをオーバーライドする必要があります。

 void ExampleHandler::handleMessage(Message msg) { #ifdef DEBUG //      ,    debugTx->putString("ExampleHandler.handleMessage("); debugTx->putInt(msg.what, 10); debugTx->putString(")\n"); #endif TxBuffer *responseTx; switch (msg.what) { case EVENT_RUN_SPI_TEST: responseTx = (TxBuffer*)msg.ptr; testSpi(); //     responseTx->putString("Some response\n"); break; case EVENT_BLINK: //     led->blink(msg.arg1, msg.arg2); break; } }
      
      





mainの実装例:


mainは、スレッド、ハンドラー、およびその他の初期化を作成するために使用されます。

 int main( void ) { //   Looper looper = Looper(10, "LPR", 500, configNORMAL_PRIORITY); //     ExampleHandler exampleHandler = ExampleHandler(&looper); //    CommandInterpreter interpreter = CommandInterpreter(); //  .    //   Strings_SpiExampleCmd,    //     EVENT_RUN_SPI_TEST interpreter.registerCommand(Strings_SpiExampleCmd, Strings_SpiExampleCmdDesc, &exampleHandler, EVENT_RUN_SPI_TEST); interpreter.registerCommand(Strings_BlinkCmd, Strings_BlinkCmdDesc, &exampleHandler, EVENT_BLINK); vTaskStartScheduler(); /* Should never get here, stop execution and report error */ while(true) ledRGB.set(PINK); return 0; }
      
      







サンプルソース

おわりに


このアプローチを使用すると、いくつかの利点があります。



メッセージハンドラー(ハンドラー)にはいくつかの制限があります。



もちろん、すべてのスレッドが提案されたモデルを使用できるわけではありません。 ハードリアルタイムを確保する必要がある場合、同じスレッドに複数のハンドラーを配置することはできません(1つは可能です)。 ただし、実際には、他のすべてのフローは非常に単純であり、他のフローとの対話を実際に必要としないことが示されています。 これらは、(シリアルポートまたはUSBから)何かを読み取って責任のあるハンドラーにメッセージを送信するストリーム、または時間のかかる操作を実行するストリーム(表示)のいずれかです。 ファームウェアのメインロジックは、ハンドラーを使用して正常に記述できます。

ご清聴ありがとうございました。



All Articles