タイムトリガー設計-組み込みシステム用のソフトウェア設計への別のアプローチ

むかしむかし、組み込みシステム用のアプリケーション設計の原則に関する記事を書きました。 それから、無限ループとリアルタイムOSの2つの基本原則があると言いました。 しかし、最近、3番目のアプローチ、いわゆるタイムトリガーデザインもあると聞きました。



このアプローチの概要として、Michael J. Pontの著書「Time-triggered Embedded Systemsのパターン」が興味のある人に使用されました-www.safetty.net/publications/pttes

ここで概念の概要を説明します。



このコンセプトは、次のアイデアに基づいています。



この一連の原則は、協調タスクスケジューラ(協調スケジューラ)とも呼ばれます。 クラシックRTOSは、プリエンプティブスケジューラと呼ばれるものを使用します。



著者は、実装の単純さ、非常に低いオーバーヘッドコスト、そしてどんなに奇妙に思えても信頼性という利点を挙げています。

欠点は、より徹底的な設計の必要性です。 たとえば、要件の1つは、タスクの実行時間ができるだけ短いこと、理想的には中断期間よりも大幅に短い場合です。



この原則を示す擬似コード。



void main(void) { scheduler_init(); add_task(Function_A, 2); add_task(Function_B, 10); add_task(Function_C, 15); scheduler_start(); while(1) { dispatch_tasks(); } }
      
      







今のところ、すべてが明確になっているはずです-シェダーを初期化し、ティックで指定された頻度で実行される3つのタスクを追加し、シェダーを開始し、タスクマネージャーの無限のサイクルに進みます。



タスクのコンテキストを記述する構造:



 typedef struct { void (* pTask)(void); uint32 Period; uint32 PeriodCur; uint8 RunMe; } task_descriptor_t;
      
      







実際、RTOSと比較して、「オーバーヘッド」ははるかに少なく-関数へのポインター、起動の頻度、現在の値-起動を待つティックの数、およびタスクを起動する回数です。



 task_descriptor_t all_task_list[MAX_TASKS];
      
      







タスクリストは、あらかじめ決められた長さの規則的な配列です。



シェダラー自身がハングアップしてタイマーを中断し、特定の周波数、たとえば1ミリ秒(同じティック)で発生するように構成されます。



 void scheduler_update(void) interrupt { foreach (task in all_task_list) { task.PeriodCur--; if (task.PeriodCur == 0) { task.PeriodCur = task.Period; task.RunMe++; } } }
      
      







ハンドラーでは、タスクのリスト全体を調べて、起動前に残っているティックの現在の値をデクリメントし、0に達すると、それを上書きして起動カウンターをインクリメントします。



そして最後に-ディスパッチャー。 無限ループで回転します。



 void dispatch_tasks(void) { foreach (task in all_task_list) { if (task.RunMe > 0) { task.pTask(); task.RunMe--; } } }
      
      







それでもタスクのリストを調べて、タスクの開始カウンターが0より大きい場合は、その関数を直接呼び出してこのタスクを開始し、開始カウンターをデクリメントします。



実際にすべて!



実際、実装は途方もなく簡単です(したがって、どこにでも簡単に移植できます)。 実際、無限ループよりも優れています。 実際、セマフォ、キュー、クリティカルセクションなどの同期ツールは必要ありません。 実際、コンテキストの切り替えは必要ありません。



しかし、私はそれが好きではありません。 そして、ここに理由があります。



  1. システムに1つだけの割り込みが必要であるということは、周辺機器とのすべての作業がポーリングモードで行われなければならないことを意味します。 それはその制限を課します。 また、システムの反応時間を短縮します。
  2. タスクリストを確認します。 つまり コントロールがリストの最後のタスクに到達する前に、最悪の場合、リストの前のタスクがすべて呼び出されます。 外部イベントに対する反応時間は、やはり最も予測可能なものからはほど遠いものです。
  3. タスクの1つに何かが発生した場合、リストの最後のタスクに到達しない可能性があります。 協調モードがあり、各タスク自体がディスパッチャに制御を返す必要があります!
  4. 単一タスクの実行時間の制限。 それから、単純な線形コードの代わりに、多かれ少なかれ長時間のアクションの場合、ステートマシンをフェンスする必要があります。




最後の段落については、同じ本で素晴らしい例が見つかりました。 そこにある棚に重点を置いていることに加えて、本の3分の2は周辺機器とプロトコルを操作する組み込みシステムの基本についての物語に捧げられています。 この本のSPIを使用した例を次に示します。



 /*------------------------------------------------------------------*- SPI_X25_Write_Byte() Store a byte of data on the EEPROM. -*------------------------------------------------------------------*/ void SPI_X25_Write_Byte(const tWord ADDRESS, const tByte DATA) { // 0. We check the status register SPI_X25_Read_Status_Register(); // 1. Pin /CS is pulled low to select the device SPI_CS = 0; // 2. The 'Write Enable' instruction is sent (0x06) SPI_Exchange_Bytes(0x06); // 3. The /CS must now be pulled high SPI_CS = 1; // 4. Wait (briefly) SPI_Delay_T0(); // 5. Pin /CS is pulled low to select the device SPI_CS = 0; // 6. The 'Write' instruction is sent (0x02) SPI_Exchange_Bytes(0x02); // 7. The address we wish to read from is sent. // NOTE: we send a 16-bit address: // - depending on the size of the device, some bits may be ignored. SPI_Exchange_Bytes((ADDRESS >> 8) & 0x00FF); // Send MSB SPI_Exchange_Bytes(ADDRESS & 0x00FF); // Send LSB // 8. The data to be written is shifted out on MOSI SPI_Exchange_Bytes(DATA); // 9. Pull the /CS pin high to complete the operation SPI_CS = 1; }
      
      





シンプルで明確な線形コードのようです。 ただし、上記の設計に適用しようとすると、次の問題が発生します。





まあ、何らかの理由で著者が、自分の設計に適応した、SPIを使用した上記の例のコードの例を提供しなかったことは、どういうわけか奇妙に思えます。 おびえた、私は推測する。



いわゆるいわゆる もう1つの割り込みが許可され、その状況で別の優先度の高いタスクを実行できるハイブリッドシェダー。 それでも、これは一般的な本質を変えるものではありません。



本の第2部では、複数のマイクロコントローラーのシステムに設計を適合させる方法について説明します。 主なアイデアは、マスターマイクロコントローラーがタイマーをティックの割り込みソースとして使用し、他のすべてが外部割り込みをティック割り込みとして使用することです。マスターは、残りのマイクロコントローラーの外部割り込み入力に接続されたGPIO出力の1つを介して割り込みハンドラーから送信します。 したがって、すべてのマイクロコントローラーの同期が達成されます。 このアイデアは興味深いものですが、残念ながら上記の問題を特に解決するものではありません。



一般に、このアプローチにはおそらく生命に対する権利があります。 99%の時間を何もしない小さなマイクロコントローラーが、特定の時間の即時応答を必要としないいくつかの外部イベントを処理する場所。 一方、ここではスーパーサイクルは正常に機能しますが、いくつかの割り込みを使用できます。



しかし、より多くのイベントがあり、周辺機器を操作するために割り込みを使用する方がはるかに簡単であり、反応時間と安定性が多かれ少なかれ重要であり、コントローラーのパフォーマンスを最大限に使用する必要がある状況では、私は依然としてRTOSの使用を支持しています。 システムに一定の予測不能性と同期ツールの適切な使用の必要性があるようにしましょう-タスクの相互のより厳密な分離とシェダラーの特性からの利点はまだ大きいようです。



All Articles