同時に、マイクロコントローラーでは、マイクロ秒(パルス周期、反バウンス待ちなど)を同時に処理する必要があることがよくあります。
航空機、自動車、ダウンホールデバイスなど、何時間も、さらには1日も継続的に動作するデバイスもあります(数日間の継続的な作業について話している)。 これらの場合、タイマーと8ビット変数のオーバーフローは受け入れられません。
このすべてを1つのエレガントで普遍的なソリューションに組み合わせたいと思います。数日間にわたってオーバーフローしない、マイクロ秒の精度で時間を測定する手段が必要です。
どうして? 私はしばらく苦しみ、8ビットAVRマイクロコントローラーのソリューションを生み出しました。 これを行うには、8ビットタイマーカウンターと4バイト変数を使用しました。 私はPICやAT89を使用しておらず、他の組み込みプラットフォームとは友達ではありません。 しかし、読者が助けてくれたら、私は彼らのためにそれをするでしょう。
利点-コードは非常に再現性があります(すでに5番目のデバイスを使用しています)。 作業の単純さ(作業のクライアント部分の中断は使用されません); コードのクライアント部分は、条件付きでプラットフォームに依存しません。 中断中-合計1回の操作(ただし、4バイト値の場合)。 外部デバイスなし-リアルタイムタイマー。
私は1つの欠点を見つけました-そのような便利で常に必要なタイマーはビジーです...
この記事は主に初心者にとって興味深いものです-私はここでアメリカを発見していません。
理論
そのため、12MHzのクォーツを備えたAtmega16Aベースのデバイスを自由に使用できます。 タイマーカウンター0を使用します。これは8桁のタイマーです。これで十分です。 なんで? 私達は考慮します:
- クォーツから12 MHzを取得し、8で除算係数を取得します-1500 KHzの周波数を取得します。
- CTCモード(一致するとリセット)を取り、割り込みを150に一致するように設定します-割り込み応答周波数は10 KHzになります。
- この割り込みで変数をインクリメントします(0.1ミリ秒ごとのインクリメントが取得されます)。
- 符号なしの32ビット値の場合、およそ後にオーバーフローします
- 429496729.6ミリ秒。
- 42949.7秒;
- 7158.3分;
- 119.3時間;
- 4.97日。
つまり、このようなソリューションは、(ほぼ)5日間、0.1ミリ秒の精度でタイマーを作成します(ただし、実際のクオーツにはエラーがあります-詳細は後ほど)。 また、タイマー0自体の値も分析すると(2/3マイクロ秒ごとに増加します)、0.67マイクロ秒の精度のカウンターを取得できます。
十分ですか? 私にとって-目のため。 私のプロジェクトでは、0.1ミリ秒のカウンターを使用します。
- グローの持続時間とLED間の休止時間を考慮します。
- UART、USBを使用する場合、タイムアウトを考慮します。
- テスト機器のあらゆる種類の状況-複雑な時空間の組み合わせ;
- ADCおよびその他のセンサーの調査中、指定された間隔に耐えます。
- コンピューターにその(デバイス)操作の時間を伝え、特定の時間間隔で情報を送信します。
- マイクロ秒までのカウンターを考慮に入れて、キーを押したときにアンチバウンス制御を実行し、延長線のパルスを分析します。
そして、これらすべてが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マクロを使用してください。
結果
このタイマーは、私にとって非常に便利なソリューションであることがわかりました。 彼が重宝すると思います。 そして、私は次のプロジェクトにそれを適用しました:
- フェンシングの状況を切り替えます 。 これは、3つのコントローラー(ATmega128を中心、ATmega64を2つの補助(右側と左側))を備えた高さ50メートルのボードです。 3つのコントローラーとそのコンポーネント(ガルバニック接続)はありません-イオニスターに基づく電源、フォトカプラーを介した通信。 中央制御装置は、この時点で一方のイオニスタのグループを充電し、他方のイオニスタの両側に給電します。 ここでは、関係を最小化するために、これらすべてのための多段階スイッチングアルゴリズムを作成する必要がありました。 特に、8個のリレーの調整された作業について話しています。ここでは、タイマーは3.3ms(リレー応答時間の保証)で動作します。 まあ、実際には、両側が10個のリレーを制御し、さらに数百のマルチプレクサーを制御します。 このファームはすべて、明確に定義された時間特性(1ミリ秒の精度、最大持続時間6秒)で動作します。 さて、そして最後に、USB、UARTの通常のタイムアウト。
- 深度センサー ここで、別の問題を解決します(作業中のプロジェクト)。 「1 cmシフトアップ」と「1 cmシフトダウン」の状況を指定する2つのコンダクター(マルチメーター)があります。 方向を指定するには多くの方法があります。 いずれにしても、これらはインパルスの特定の組み合わせです。 このタイマーで、バウンス、つまり安定したパルスの持続時間を決定します。 コンピューターから、最大許容バウンス時間(ここでは10マイクロ秒で十分です)、反バウンス待機、最小/最大パルス持続時間が設定されます。 デバッグモードがあります-センサーは論理アナライザーになります。 これにより、ラインをデバッグし、係数を調整できます。 さて、再び、タイムアウト、LED。
- アナログ信号のセンサー 。 Trite 8チャンネルADC。 ここでは、タイマーを使用して必要な一時停止を維持します。
他のプラットフォームの親愛なる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ミリ秒の精度を達成する方法。 しかし、この別の時間について。