Top -> FreeRtos -> designtips1.html
ソリューション # 1
なぜ RTOS カーネルを使いますか?

<<< | >>>

概 要
多くのアプリケーションが RTOS カーネルを使用しないで生産されている、そのような場合とられるかもしれないアプローチを記述します。
このようなアプローチでの、アプリケーションがあまりにも複雑になった場合の、潜在的な問題を提起します。
そして次の RTOS ベースのソフトウェア設計と比べてください。
実 装
このソリューションは伝統的な無限ループアプローチを使います、それはアプリケーションの各要素が各々実行し完了する関数によって構成されます。
理想的にはハードウェアタイマがタイムクリティカルなプラント制御機能をスケジュールするために使われる。 しかし、データの到着待ちと複雑な計算を待つ制御機能は、割り込みサービスルーチン内で実行するには相応しくない。
オペレーションのコンセプト
コンポーネントが無限ループの中で呼び出される頻度とオーダーはいずれかの優先順位付けを導入することで修正される。 このような順序変更の対比を下に例示します。
スケジューラ構成
RTOS スケジューラーは使わない。
評 価
小さいコードサイズ
サードパーティーソースコードに依存しない
RTOS RAM 、 ROMの処理オーバーヘッドがない
複雑なタイミング必要条件を満足させることが難しい
規模が大きくなると激しい勢いで複雑さの増大を招く
異なる関数間の従属関係のため、タイミングの評価、保守が難しい
結 論

シンプルな無限ループ方法は小さいアプリケーションとアプリケーションのためにフレキシブルなタイミングを取り扱うのが非常に楽です − しかしより複雑で、分析が難しく、より大規模システムでは保守は難しくなります。


この例は前に導入した例題アプリケーションの部分的な実装です
プラント制御機能
制御機能は次の擬似コードによって表すことができます:
void PlantControlCycle( void )
{
    TransmitRequest();
    WaitForFirstSensorResponse();

    if( Got data from first sensor )
    { 
        WaitForSecondSensorResponse();
        
        if( Got data from second sensor )
        {
            PerformControlAlgorithm();
            TransmitResults();
        }
    }
}
ヒューマンインタフェース機能
これはキーパッド、 LCD 、 RS232 コミュニケーションと組み込みWeb サーバを含みます。
次の擬似コードは、これらのインタフェースをコントロールするために、単純な無限ループ構造を示します。
int main( void )
{
    Initialise();
    
    for( ;; )
    {
        ScanKeypad();
        UpdateLCD();
        ProcessRS232Characters();
        ProcessHTTPRequests();   
    }

    // Should never get here.
    return 0;
}

これは2つのことを想定してます:
第一に、通信 IO は割り込みサービスルーチンによってバッファリングされますので周辺機器がポーリングを必要としない。 第二に、ループの中の個別の関数呼び出しは必要条件が満たされるように充分速く実行します。


プラント制御機能をスケジュールする
制御関数の処理時間長は10ms タイマ割り込みから呼ぶだけではだめなことを意味します。このことは無限ループに多少の時間的なコントロールの導入を必要とするでしょう。

例えば...:
// Flag used to mark the time at which a
// control cycle should start (mutual exclusion
// issues being ignored for this example).
int TimerExpired;

// Service routine for a timer interrupt.  This
// is configured to execute every 10ms.
void TimerInterrupt( void )
{    
    TimerExpired = true;
}


// Main() still contains the infinite loop - 
// within which a call to the plant control
// function has been added.
int main( void )
{
    Initialise();
    
    for( ;; )
    {
        // Spin until it is time for the next
        // cycle.
        if( TimerExpired )
        {
            PlantControlCycle();
            TimerExpired = false;

            ScanKeypad();
            UpdateLCD();

            // The LEDs could use a count of
            // the number of interrupts, or a
            // different timer.
            ProcessLEDs();

            // Comms buffers must be large
            // enough to hold 10ms worth of
            // data.
            ProcessRS232Characters();
            ProcessHTTPRequests();   
        }

        // The processor can be put to sleep
        // here provided it is woken by any
        // interrupt.
    }

    // Should never get here.
    return 0;
}

...しかしこれは好ましいソリューションではありません:

* 遅延あるいはフィールドバスの障害がプラント制御関数の実行時間の増加をもたらします。 インタフェース関数のタイミング必要条件はたいていの場合最大値を突破されるでしょう。

* すべての関数を実行している各サイクルがコントロールサイクルタイミングの違反をもたらすかもしれない。

* 実行時間のジッターはサイクルのミスを招くかもしれない。 例えば、 HTTP のリクエストがなかった時ProcessHTTPRequests() の実行時間は取るに足りないが、ページがリクエストされていたときは非常に長い。

* それはそれほど保守性が良くはありません − それは関数が最大時間以内に実行されるという前提に頼っているから。

* 通信用バッファは必要量よりも大きければ、そのサイクルに1度だけサービスを提供すれば良い。

代わりの構造
これまでのところで記述された単純なループ構造では適合しなくなる2つの要因が認められます。

1.それぞれのファンクション・コールの長さ
関数全部を実行するにはあまりに長い時間がかかります。 それぞれの関数を状態に分けることによって解消することができます- ただ一つの状態がコールを実行可能とすることによって。

制御関数を使って例として:
// Define the states for the control cycle function.
typdef enum eCONTROL_STATES
{
    eStart, // Start new cycle.
    eWait1, // Wait for the first sensor response.
    eWait2  // Wait for the second sensor response.
} eControlStates;

void PlantControlCycle( void )
{
static eControlState eState = eStart;

    switch( eState )
    {
        case eStart :
            TransmitRequest();
            eState = eWait1;
            break;
            
        case eWait1;
            if( Got data from first sensor )
            {
                eState = eWait2;
            }
            // How are time outs to be handled?
            break;
            
        case eWait2;
            if( Got data from first sensor )
            {
                PerformControlAlgorithm();
                TransmitResults();
                
                eState = eStart;
            }
            // How are time outs to be handled?
            break;           
    }
}
この関数は構造的に複雑であり、さらにスケジューリング問題を引き起こします。 コード自身は余分な状態が付加されて理解することがいっそう困難になるでしょう − 例えばタイムアウトとエラー条件の処理に。
2.タイマの精度
より短いタイマインタバルがさらに多くにフレキシビリティを与えるでしょう。

ステートマシンとして制御機能を実装した時(各コードが短くなることで)それはタイマ割り込みから呼ぶ事がきる場合があります タイマインタバルはそのタイミング必要条件を満たす周波数において関数呼出しを保証するのに十分短くなければならないでしょう。 この選択肢はタイミング問題とメンテナンス問題に満ちています。

修正されたの無限ループ解決法はそれぞれのループで異なった関数を呼び出すようにに修正されました − 高優先度で制御関数は高頻度でコールする:
int main( void )
{
int Counter = -1;
    Initialise();
    // Each function is implemented as a state 
    // machine so is guaranteed to execute 
    // quickly - but must be called often.
    
    // Note the timer frequency has been raised.
    
    for( ;; )
    {
        if( TimerExpired )
        {
            Counter++;
            switch( Counter )
            {
                case 0  : ControlCycle();
                          ScanKeypad();
                          break;
                case 1  : UpdateLCD();
                          break;
                case 2  : ControlCycle();
                          ProcessRS232Characters();
                          break;
                case 3  : ProcessHTTPRequests();
                          // Go back to start
                          Counter = -1;                          
                          break;
            }
            TimerExpired = false;
        }
    }

    // Should never get here.
    return 0;
}

もっと多くのことが、イベントカウンターを使って導入することができます、もしサービスを必要とするイベントが起こった場合、より低い優先権を持つ関数がコールされる。

    for( ;; )
    {
        if( TimerExpired )
        {
            Counter++;
            // Process the control cycle every other loop.
            switch( Counter )
            {
                case 0  : ControlCycle();
                          break;
                case 1  : Counter = -1;
                          break;
            }

            // Process just one of the other functions.  Only process
            // a function if there is something to do.  EventStatus()
            // checks for events since the last iteration.
            switch( EventStatus() )
            {
                case EVENT_KEY  :   ScanKeypad();
                                    UpdateLCD();
                                    break;
                case EVENT_232  :   ProcessRS232Characters();
                                    break;
                case EVENT_TCP  :   ProcessHTTPRequests();
                                    break;
            }
            TimerExpired = false;
        }
    }

この方法では処理イベントが無駄なCPUサイクルを減らすでしょう、けれどもこのデザインではコントロールサイクルの実行周波数でジッターを生じる。


NEXT >>> Solution #2:完全にプリエンティブなシステム
Top に戻る