マイクロコントローラのスリープレース状態


11

次のコードを実行しているマイクロコントローラーがあるとします。

volatile bool has_flag = false;

void interrupt(void) //called when an interrupt is received
{
    clear_interrupt_flag(); //clear interrupt flag
    has_flag = true; //signal that we have an interrupt to process
}

int main()
{
    while(1)
    {
        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

if(has_flag)条件がfalseと評価され、sleep命令を実行しようとしているとします。 我々はスリープ命令を実行する前に、我々は、割り込みを受けます。割り込みを離れた後、sleep命令を実行します。

次の理由により、この実行シーケンスは望ましくありません。

  • マイクロコントローラーは、目を覚まして呼び出すのではなく、スリープ状態になりましたprocess()
  • その後に割り込みが受信されない場合、マイクロコントローラが起動しないことがあります。
  • への呼び出しprocess()は、次の割り込みまで延期されます。

この競合状態の発生を防ぐために、コードをどのように記述できますか?

編集

ATMegaなどの一部のマイクロコントローラーには、この状態の発生を防ぐスリープイネーブルビットがあります(指摘してくれたKvegaoroに感謝)。JRobertsは、この動作を例示する実装例を提供しています。

PIC18のような他のマイクロにはこのビットがなく、問題はまだ発生します。ただし、これらのマイクロは、グローバル割り込み有効化ビットが設定されているかどうかに関係なく、割り込みがコアをウェイクアップできるように設計されています(これを指摘してくれてありがとう)。このようなアーキテクチャの場合、解決策は、スリープ状態になる直前にグローバル割り込みを無効にすることです。スリープ命令を実行する直前に割り込みが発生した場合、割り込みハンドラーは実行されず、コアがウェイクアップし、グローバル割り込みが再度有効になると、割り込みハンドラーが実行されます。疑似コードでは、実装は次のようになります。

int main()
{
    while(1)
    {
        //clear global interrupt enable bit.
        //if the flag tested below is not set, then we enter
        //sleep with the global interrupt bit cleared, which is
        //the intended behavior.
        disable_global_interrupts();

        if(has_flag) //if we had an interrupt
        {
            has_flag = false; //clear the interrupt flag
            enable_global_interrupts();  //set global interrupt enable bit.

            process(); //process the interrupt
        }
        else
            sleep(); //place the micro to sleep
    }
}

これは実際的な問題ですか、それとも理論的な問題ですか?
AndrejaKo 2013年

理論的には、タイマーを使用して(許容値を入力して)msごとに1回目を覚まし、何もする必要がない場合はスリープ状態に戻ります。
Gradyプレーヤー、

1
私はとして行いinterrupt_flagint割り込みがあるたびにインクリメントします。次にをに変更しif(has_flag)while (interrupts_count)から、スリープします。それにもかかわらず、whileループを終了した後に割り込みが発生する可能性があります。これが問題である場合、割り込み自体で処理を行いますか?
angelatlarge 2013年

1
実行しているマイクロによって異なります。ATmega328の場合、割り込みでスリープモードを無効にできる可能性があります。そのため、説明した競合状態が発生した場合、スリープ機能がオーバーライドされ、再びループバックして処理します。待ち時間が短い割り込み。ただし、タイマーを使用して最大レイテンシ以下の間隔でウェイクアップすることも素晴らしい解決策です
Kvegaoro

1
@TRISAbits:PIC 18xでは、私の回答で説明したアプローチは問題なく機能します(そのパーツを使用するときの私の設計です)。
スーパーキャット2013年

回答:


9

通常、このケースには何らかのハードウェアサポートがあります。たとえば、sei割り込みを有効にするAVRの命令は、次の命令が完了するまで有効化を延期します。これで、次のことができます。

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

この例では見落とされていたであろう割り込みは、この場合、プロセッサがスリープシーケンスを完了するまで保留されます。


素晴らしい答えです!あなたが提供するアルゴリズムは、実際にはAVRで本当にうまく機能します。提案をありがとう。
TRISAbits 2013年

3

多くのマイクロコントローラーでは、特定の割り込み原因(通常は割り込みコントローラーモジュール内)を有効または無効にできるほか、CPUコア内にマスターフラグがあり、割り込み要求を受け入れるかどうかを決定します。割り込み要求がコアに到達すると、コアが実際にそれを受け入れるかどうかに関係なく、多くのマイクロコントローラーはスリープモードを終了します。

このような設計では、信頼できるスリープ動作を実現するための簡単な方法は、メインループチェックでフラグをクリアし、プロセッサをウェイクする必要がある理由を認識しているかどうかをチェックすることです。それらの理由のいずれかに影響を与える可能性があるその時間中に発生する割り込みは、フラグを設定する必要があります。メインループが起動状態を維持する原因を見つけられず、フラグが設定されていない場合、メインループは割り込みを無効にし、フラグを再度確認する必要があります割り込み禁止命令中は、次の命令に関連するオペランドフェッチがすでに実行された後で処理される場合があります。フラグがまだ設定されていない場合は、スリープします。

このシナリオでは、メインループが割り込みを無効にする前に発生する割り込みは、最終テストの前にフラグを設定します。保留になりすぎてスリープ命令の前に処理できない割り込みは、プロセッサがスリープ状態になるのを防ぎます。どちらの状況でも問題ありません。

Sleep-on-exitは使用するのに適したモデルですが、すべてのアプリケーションが実際に「適合する」とは限りません。たとえば、エネルギー効率の高いLCDを備えたデバイスは、次のようなコードで最も簡単にプログラムできます。

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

ボタンが押されておらず、他に何も行われていない場合、get_keyメソッドの実行中にシステムがスリープ状態にならない理由はありません。キーに割り込みをトリガーさせ、ステートマシンを介してすべてのユーザーインターフェイスの相互作用を管理することは可能ですが、上記のようなコードは、多くの場合、小型のマイクロコントローラーに特有の高度にモーダルなユーザーインターフェイスフローを処理する最も論理的な方法です。


素晴らしい答えをくれたsupercatに感謝します。割り込みを無効にしてからスリープ状態にするのは、グローバル割り込みビットが設定/クリアされているかどうかに関係なく、コアが割り込みソースから起動する場合に最適なソリューションです。PIC18割り込みハードウェアの回路図を見てみたところ、このソリューションが機能しました。
TRISAbits 2013年

1

割り込みでウェイクするようにマイクロをプログラムします。

具体的な詳細は、使用しているマイクロによって異なります。

次に、main()ルーチンを変更します。

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}

1
Wake-on-interruptアーキテクチャが問題で想定されています。あなたの答えが質問/問題を解決するとは思いません。
angelatlarge 2013年

@angelatlargeポイントが受け入れられました。役立つと思われるコード例を追加しました。
jwygralak67 2013年

@ jwygralak67:提案に感謝しますが、提供するコードは問題をprocess()ルーチンに移動するだけであり、process()本体を実行する前に割り込みが発生したかどうかをテストする必要があります。
TRISAbits 2013年

2
割り込みが発生しなかったのに、なぜ起きているのですか?
JRobert

1
@JRobert:前の割り込みから目覚め、process()ルーチンを完了し、if(has_flag)テストを終了してスリープの直前に別の割り込みが発生するため、「質問。
TRISAbits 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.