初心者:2/3マイクロ秒のステップでマイクロコントローラーをカウンターし、数日でオーバーフローします

多くの場合、マイクロコントローラデバイスを使用する場合、「擬人化」時間(1秒の何分の一秒のLEDが点灯するか、ダブルクリックの最大時間など)をカウントする必要があります。 、分、さらには時間(私はその日について言うのが怖い...)。

同時に、マイクロコントローラーでは、マイクロ秒(パルス周期、反バウンス待ちなど)を同時に処理する必要があることがよくあります。

航空機、自動車、ダウンホールデバイスなど、何時間も、さらには1日も継続的に動作するデバイスもあります(数日間の継続的な作業について話している)。 これらの場合、タイマーと8ビット変数のオーバーフローは受け入れられません。

このすべてを1つのエレガントで普遍的なソリューションに組み合わせたいと思います。数日間にわたってオーバーフローしない、マイクロ秒の精度で時間を測定する手段が必要です。

どうして? 私はしばらく苦しみ、8ビットAVRマイクロコントローラーのソリューションを生み出しました。 これを行うには、8ビットタイマーカウンターと4バイト変数を使用しました。 私はPICやAT89を使用しておらず、他の組み込みプラットフォームとは友達ではありません。 しかし、読者が助けてくれたら、私は彼らのためにそれをするでしょう。

利点-コードは非常に再現性があります(すでに5番目のデバイスを使用しています)。 作業の単純さ(作業のクライアント部分の中断は使用されません); コードのクライアント部分は、条件付きでプラットフォームに依存しません。 中断中-合計1回の操作(ただし、4バイト値の場合)。 外部デバイスなし-リアルタイムタイマー。

私は1つの欠点を見つけました-そのような便利で常に必要なタイマーはビジーです...

この記事は主に初心者にとって興味深いものです-私はここでアメリカを発見していません。





理論



そのため、12MHzのクォーツを備えたAtmega16Aベースのデバイスを自由に使用できます。 タイマーカウンター0を使用します。これは8桁のタイマーです。これで十分です。 なんで? 私達は考慮します:

  1. クォーツから12 MHzを取得し、8で除算係数を取得します-1500 KHzの周波数を取得します。
  2. CTCモード(一致するとリセット)を取り、割り込みを150に一致するように設定します-割り込み応答周波数は10 KHzになります。
  3. この割り込みで変数をインクリメントします(0.1ミリ秒ごとのインクリメントが取得されます)。
  4. 符号なしの32ビット値の場合、およそ後にオーバーフローします

    • 429496729.6ミリ秒。
    • 42949.7秒;
    • 7158.3分;
    • 119.3時間;
    • 4.97日。


つまり、このようなソリューションは、(ほぼ)5日間、0.1ミリ秒の精度でタイマーを作成します(ただし、実際のクオーツにはエラーがあります-詳細は後ほど)。 また、タイマー0自体の値も分析すると(2/3マイクロ秒ごとに増加します)、0.67マイクロ秒の精度のカウンターを取得できます。

十分ですか? 私にとって-目のため。 私のプロジェクトでは、0.1ミリ秒のカウンターを使用します。



そして、これらすべてが1つのATmega16コントローラーに落ち着いて干渉します! そして、これはアセンブラーではなく、クロスプラットフォームCです! 外部のリアルタイムカウンターはありません!

悪くないでしょ?



AVRのセットアップ



AVRですべてを行う方法

まず、「DeciMilliSecond」と呼ぶ外部変数を開始します。

//  main.h typedef unsigned long dword; //  32-  extern volatile dword dmsec; // 0.1msec //  main.c volatile dword dmsec;
      
      





@ no-smokingが正しく指摘したように、この変数は、コンパイラーが最適化を試みないように揮発性でなければなりません。

関数でこの変数の初期化を行います:

 dmsec = 0;
      
      





次に、タイマー0の動作モードを設定します。

 // .  0 – 0.1msec Timer0_Mode (TIMER_Mode_CTC | TIMER0_Clk_8); Timer0_Cntr (149); Timer_Int (Timer0_Cmp);
      
      





同時に、いくつかのMCU_init.hで必要なものをすべて宣言します。

 //  mcu_init.h #include <mega16.h> // . TIMSK #define Timer0_Cmp (1 << 1) //   0 // . TCCRn #define WGM1 (1 << 3) #define CS1 (1 << 1) // .     0 #define TIMER0_Clk_8 CS1 //  8 // .    #define TIMER_Mode_CTC WGM1 // CTC (  ) // .   #define Timer_Int(Mode) TIMSK = (Mode) #define Timer0_Mode(Mode) TCCR0 = (Mode) #define Timer0_Cntr(Cntr) OCR0 = (Cntr)
      
      





それでは、可能であれば、割り込みを許可します。

 #asm ("SEI")
      
      





中断を説明するために残ります。 これは前のすべてよりも簡単です。

 #include <mega16.h> interrupt [TIM0_COMP] Timer0_Compare (void) { ++dmsec; }
      
      





すべて、タイマーが記述され、構成され、実行されています!



PICのセットアップ



尊敬されているPICファンが私に提案したことは次のとおりです。



ピーク時には、これはTimer2モジュールを使用して簡単に繰り返されます。 偶然に似たような割り込み機能があるのはその中にあります。



PR2 = 75-タイマーがゼロにリセットされ、割り込みを生成する値

T2CON.T2CKPS = 2-プリスケーラー1:16

T2CON.T2OUTPS = 0-ポストスケーラーなし

T2CON.TMR2ON = on-タイマーはオンです



IPR1.TMR2IP = 1-高優先度割り込み

PIR1.TMR2IF = off-割り込みフラグをクリア

PIE1.TMR2IE = on-TMR2とPR2の一致による割り込みを有効にします

INTCON.GIE =​​ on-割り込み処理を有効にします



ご覧のとおり、PR2は2倍少ないため、ここでのプリスケーラは2倍です。

これらの設定は、48 MHzのシステム周波数で10 kHzの周波数で割り込みを生成します(Fosc / 4はUSBフルスピードの標準周波数)。



使用する



このタイマーのクライアントのコードはクロスプラットフォームです(AVRのタイマー0の値にアクセスする場合を除く)。

USB共有コードのスニペットは次のとおりです。

 #include "main.h" //   dmsec, next_USB_timeout #include "FT245R.h" //      USB #include "..\Protocol.h" //     -  // ** // **    USB // ** void AnalyzeUSB (void) { #define RECEIVE_BYTE(B) while (!FT245R_IsToRead)\ { if (dmsec > end_analyze) return; }\ B = FT245_ReadByte (); #define RECEIVE_WORD(W) //   2  #define RECEIVE_DWORD(W) //   4  dword end_analyze, d; NewAnalyze: if (!FT245R_IsToRead) //  ? return; end_analyze = dmsec + max_USB_timeout; // timeout    next_USB_timeout = dmsec + MaxSilence_PC_DEV; // timeout    RECEIVE_BYTE (b) //   switch (b) { case SetFullState: RECEIVE_DWORD (d); //   is_initialized = 1; //  ChangeIndicator (); break; } // switch (pack) goto NewAnalyze; #undef RECEIVE_BYTE //  #define #undef RECEIVE_WORD #undef RECEIVE_DWORD }
      
      





マクロ関数RECEIVE_BYTE、RECEIVE_WORD、RECEIVE_DWORDは、特定の交換フェーズのタイムアウトを考慮した読み取り手順を実装します。 その結果、反対側に何かが掛かっている場合、マイクロコントローラーは休止状態になりません。 注意してください-WatchDogは必要ありませんでした! そして、0.1ミリ秒の精度でタイムアウトを設定する変数/定数max_USB_timeoutのおかげです。

同様に、変数next_USB_timeoutの「空中の沈黙」の分析が実装されています。 これにより、マイクロコントローラーは1)コンピューターがどこかで消えたことを検出し、2)何らかの方法でシグナルを送信することができます(私の場合、「エラー」LEDが点灯します)。 定数/変数MaxSilence_PC_DEVを使用すると、ミリ秒の小数から数日まで、最も広い範囲で「無音」の概念を変更できます。

同様に、他のすべての瞬間が実現されます。

マイクロ秒カウンターを使用する必要がある場合、比較関数が表示されます。

 #define GetUSec(A,B) { #asm ("CLI"); A = dmsec; B = TCNT0; #asm ("SEI"); } // ** // **         2/3usec // ** dword Difference (dword prev_dmsec, byte prev_usec) { dword cur_dmsec; byte cur_usec; dword dif; // .    GetUSec (cur_dmsec, cur_usec); //   dif = cur_dmsec - prev_dmsec; dif <<= 8; if (cur_usec < prev_usec) dif += 255 + (dword) cur_usec - prev_usec; else dif += cur_usec - prev_usec; return dif; }
      
      





この関数には、前の瞬間(dmsecおよびタイマー0の前の値)が渡されます。

まず、GetUSecマクロを使用して割り込みを停止し、コピー時にdmsecとカウンターの値が破損しないようにします。 そして現在の時刻をコピーします。

次に、オーバーフローを考慮して、2/3マイクロ秒形式との時差を示します。

さて、今回は戻ります。

そして、アンチバウンスやその他のアクティビティを制御するために、通常の場合に使用します。 現在の時点を特定するときに割り込みを一時停止することも忘れないでください-または、GetUSecマクロを使用してください。



結果



このタイマーは、私にとって非常に便利なソリューションであることがわかりました。 彼が重宝すると思います。 そして、私は次のプロジェクトにそれを適用しました:



他のプラットフォームの親愛なるHabrausersは、対応するタイマーの初期化コードとそれにアクセスするためのルールを教えてくれます。これをここに追加します。 他のプラットフォームでは、他の時間を選択する必要がある可能性があります。 ただし、いずれにしても、タイマー自体は数マイクロ秒以内、カウンター変数は100マイクロ秒の倍数でなければなりません。 判明したように、1ミリ秒では不十分な場合があります。



UPD

水晶振動子の安定性に関するいくつかの言葉



nerudoはコメントで私を絶対に修正したので、2/3マイクロ秒の増分でほぼ5日と考えることができますが、これらの計算の誤差はゼロではありません...

私が使用している非常に12 MHzのHC49 / Sクォーツを取ります。 KSSメーカーは、精度+ -15 ... + -50 x 1e-6は同じppmであると主張しています。 間違っていなければ、これは1秒で15マイクロ秒のエラーが発生することを意味します。 したがって、4.97日で268ミリ秒のエラーが発生します。 クォーツを突然取った場合、たとえば1 ppmの場合、同時に18ミリ秒になります。

噴水ではない-一方で。 一方、ハイエンドのクロノメーターは製造していません! 少なくとも私はそのような仕事を自分に設定していません。 この事実を考慮する必要があります。

また、この問題に直面しました-間隔を空けた2つのデバイス間で45分間で1ミリ秒の精度を達成する方法。 しかし、この別の時間について。



All Articles