4

この質問は、OSなしで小型マイクロコントローラをプログラミングすることについてです。特に今はPICに興味がありますが、質問は一般的です。

私は時間を保つために次のパターンを数回見ました:

タイマー割り込みコード(タイマーが毎秒起動するとします):

...
if (sec_counter > 0)
  sec_counter--;
...

メインラインコード(割り込みなし):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

メインラインコードが繰り返されたり、カウンターがさまざまな値(秒だけでなく)に設定されたりする場合があります。

sec_counterメインラインコードのへの割り当てがアトミックでない場合、ここに競合状態があるように思われます。たとえば、PIC18では、割り当ては4つのASMステートメントに変換されます(一度に各バイトをロードし、その前にメモリバンクから適切なバイトを選択します)。割り込みコードがこの途中にある場合、最終的な値が破損している可能性があります。

不思議なことに、割り当てられた値が256未満の場合、割り当てアトミックであるため、問題はありません。

私はこの問題について正しいですか?このような動作を正しく実装するためにどのパターンを使用しますか?いくつかのオプションがあります。

  • sec_counterへの各割り当ての前に割り込みを無効にし、後で有効にします-これはきれいではありません
  • 割り込みを使用しないでください。ただし、開始されてからポーリングされる別のタイマーを使用してください。これはクリーンですが、タイマー全体を使い果たします(前の場合、1秒の起動タイマーは他の目的にも使用できます)。

他のアイデアはありますか?

4

11 に答える 11

2

PICアーキテクチャはそれが得るのと同じくらいアトミックです。これにより、メモリファイルへのすべての読み取り-変更-書き込み操作が「アトミック」であることが保証されます。リードモディファイライト全体を実行するには4クロックかかりますが、4クロックすべてが単一の命令で消費され、次の命令は次の4クロックサイクルを使用します。これがパイプラインの仕組みです。8クロックでは、2つの命令がパイプラインにあります。

値が8ビットより大きい場合、PICは8ビットマシンであり、大きいオペランドは複数の命令で処理されるため、問題になります。それは原子的な問題をもたらします。

于 2009-05-21T13:33:24.990 に答える
2

カウンターを設定する前に、必ず割り込みを無効にする必要があります。醜いかもしれませんが、それは必要です。ISR メソッドに影響するハードウェア レジスタまたはソフトウェア変数を設定する前に、常に割り込みを無効にすることをお勧めします。C で記述している場合は、すべての操作を非アトミックと見なす必要があります。生成されたアセンブリを何度も見なければならないことがわかった場合は、C を放棄してアセンブリでプログラムする方がよい場合があります。私の経験では、これはめったにありません。

議論された問題に関して、これは私が提案するものです:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

そしてカウンターを設定します:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

追加の変数が必要なため、すべてを関数でラップすることをお勧めします。

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

このようにして、開始メソッドを抽象化します (必要に応じて醜さを隠します)。たとえば、メソッドの呼び出し元に影響を与えることなく、ハードウェア タイマーを開始するように簡単に変更できます。

于 2009-05-21T12:59:32.157 に答える
1

256未満の移動がアトミックであることにそれほど関心はありません。8ビット値の移動は1つのオペコードであるため、可能な限りアトミックになります。

PICなどのマイクロコントローラーでの最善の解決策は、タイマー値を変更する前に割り込みを無効にすることです。メインループで変数を変更し、必要に応じて処理するときに、割り込みフラグの値を確認することもできます。変数の値を変更する関数にして、ISRから呼び出すこともできます。

于 2009-05-23T15:35:58.650 に答える
1

sec_counter 変数へのアクセスはアトミックではないため、確定的な動作が必要な場合は、メインライン コードでこの変数にアクセスする前に割り込みを無効にし、アクセス後に割り込み状態を復元することを避ける方法はありません。これはおそらく、このタスク専用の HW タイマーを使用するよりも優れた選択です (タイマーが余っている場合を除きます。その場合は、タイマーを使用したほうがよいでしょう)。

于 2009-05-21T14:33:04.153 に答える
1

Microchip の無料の TCP/IP スタックをダウンロードすると、経過時間を追跡するためにタイマー オーバーフローを使用するルーチンが含まれています。具体的には「tick.c」と「tick.h」です。これらのファイルをプロジェクトにコピーするだけです。

これらのファイル内で、それらがどのように行うかを見ることができます。

于 2009-05-21T19:48:53.237 に答える
1

値を書き込み、それが必要な値であることを確認するのが最も簡単な方法のようです。

do {
 sec_counter = value;
} while (sec_counter != value);

ところで、C を使用している場合は、変数を volatile にする必要があります。

値を読み取る必要がある場合は、2 回読み取ることができます。

do {
    value = sec_counter;
} while (value != sec_counter);
于 2009-05-21T12:31:17.160 に答える
0

main()にあるコード部分を適切な関数に移動し、ISRによって条件付きで呼び出されるようにします。

また、ティックの遅延や欠落を回避するために、このタイマーISRをハイプリオ割り込みとして選択します(PIC18には2つのレベルがあります)。

于 2009-05-21T14:05:23.897 に答える
0

1 つのアプローチは、割り込みにバイト変数を保持させ、カウンターがヒットする 256 回ごとに少なくとも 1 回呼び出される何かを持たせることです。次のようにします。

// ub==unsigned char; ui==符号なし整数; ul==符号なしロング
ub now_ctr; // これは割り込みにヒットします
ub prev_ctr;
ul big_ctr;

ボイド poll_counter(ボイド)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

割り込みのカウンターを大きなカウンターの LSB と同期させてもかまわない場合のわずかなバリエーション:

ul big_ctr;
ボイド poll_counter(ボイド)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}
于 2010-08-19T18:42:33.877 に答える
0

さて、比較アセンブリ コードはどのようなものでしょうか。

カウントが下向きに行われること、およびゼロ比較であることを考慮すると、最初に MSB をチェックし、次に LSB をチェックすると安全なはずです。破損している可能性がありますが、0x100 と 0xff の中間にあり、破損した比較値が 0x1ff であっても問題ありません。

于 2009-05-21T12:47:36.613 に答える
0

タイマーを現在使用している方法では、サイクルの途中で変更する可能性があるため、とにかく秒全体をカウントしません。じゃあ、気にしなければ。私の意見では、値を読み取り、その差を比較するのが最善の方法です。OP が数回余分にかかりますが、マルチスレッドの問題はありません (タイマーが優先されるため)。

時間の値についてより厳密な場合は、カウントダウンが 0 になるとタイマーを自動的に無効にし、タイマーの内部カウンターをクリアして、必要に応じて有効にします。

于 2009-05-21T13:15:15.853 に答える