Arduinoタイマー割り込み

タイマー割り込みを使用すると、コードで他に何が起こっているかに関係なく、非常に明確な時間間隔でタスクを実行できます。 このインストラクションでは、比較一致またはCTCモードでタイマーのクリアで割り込みを設定および実行する方法を説明します。 サンプルコードを探している場合は、手順2に直接ジャンプします。

通常、Arduinoスケッチを作成する場合、Arduinoはloop(){}関数にカプセル化されたすべてのコマンドを記述された順序で実行しますが、loop()でイベントのタイミングを計るのは困難です。 他のコマンドよりも実行に時間がかかるコマンドや、条件文(if、while ...)に依存するコマンド、および多くのコマンドで構成されるArduinoライブラリ関数(digitalWriteやanalogReadなど)があります。 Arduinoタイマー割り込みを使用すると、loop()関数で発生する通常のイベントシーケンスを、正確な時間間隔で瞬間的に一時停止し、別のコマンドセットを実行できます。 これらのコマンドが完了すると、Arduinoはloop()内の場所に再び戻ります。

割り込みは次の場合に役立ちます。

等間隔の入力信号を測定する(一定のサンプリング周波数)

2つのイベント間の時間を計算する

特定の周波数の信号を送信する

定期的に着信シリアルデータをチェックする

はるかに...

割り込みを行う方法はいくつかありますが、ここでは、比較一致またはCTCモードでのタイマーのクリアと呼ばれる、最も便利で柔軟なタイプに焦点を当てます。 さらに、このインストラクションでは、Arduino Uno(およびATMEL 328/168を備えた他のArduino ... Lilypad、Duemilanove、Diecimila、Nano ...)のタイマーについて具体的に説明します。 ここで紹介する主なアイデアは、Megaおよび古いボードにも当てはまりますが、セットアップは少し異なり、下の表はATMEL 328/168に固有のものです。

ステップ1:プリスケーラと比較一致レジスタ

Unoには、timer0、timer1、およびtimer2と呼ばれる3つのタイマーがあります。 各タイマーには、タイマーのクロックのティックごとに増分されるカウンターがあります。 CTCタイマー割り込みは、カウンターが指定された値に達するとトリガーされ、比較一致レジスタに保存されます。 タイマーカウンターがこの値に達すると、タイマークロックの次のティックでクリア(ゼロにリセット)され、その後再び比較一致値までカウントアップし続けます。 比較一致値を選択し、タイマーがカウンターをインクリメントする速度を設定することにより、タイマー割り込みの頻度を制御できます。

最初に説明するパラメーターは、タイマーがカウンターをインクリメントする速度です。 Arduinoクロックは16MHzで動作します。これは、タイマーがカウンターをインクリメントできる最速です。 16MHzでは、カウンターの各ティックは1 / 16, 000, 000秒(〜63ns)を表すため、カウンターは9/16(カウンターは0のインデックス付き)に達するまで10 / 16, 000, 000秒かかり、値に達するには100 / 16, 000, 000秒かかります99の。

多くの場合、カウンタ速度を16MHzに設定するのが速すぎることがわかります。 Timer0とtimer2は8ビットタイマーで、255の最大カウンター値を保存できます。Timer1は16ビットタイマーです。65535の最大カウンター値を保存できます。カウンターが最大値に達すると、ゼロに戻ります。 (これはオーバーフローと呼ばれます)。 これは、16MHzで、比較一致レジスタを最大カウンター値に設定しても、8ビットカウンターでは256 / 16, 000, 000秒(〜16us)ごとに、そして6ビットカウンターでは65, 536 / 16, 000, 000(〜4 ms)秒ごとに割り込みが発生することを意味します16ビットカウンター。 明らかに、1秒間に1回だけ中断したい場合、これはあまり役に立ちません。

代わりに、プリスケーラと呼ばれるものを使用して、タイマーカウンターの増分速度を制御できます。 プリスケーラーは、次の式に従ってタイマーの速度を決定します。

(タイマー速度(Hz))=(Arduinoクロック速度(16MHz))/プリスケーラー

そのため、1プリスケーラーは16 MHzでカウンターをインクリメントし、8プリスケーラーは2 MHzでカウンターをインクリメントし、64プリスケーラー= 250 kHzなどとなります。 上記の表に示されているように、プリスケーラーは1、8、64、256、および1024に等しくすることができます(次のステップでCS12、CS11、およびCS10の意味を説明します)。

これで、次の式を使用して割り込み頻度を計算できます。

割り込み周波数(Hz)=(Arduinoクロック速度16, 000, 000Hz)/(プリスケーラ*(比較レジスタ+ 1と比較))
比較一致レジスタのインデックスがゼロであるため、+ 1が含まれています

上記の式を並べ替えると、希望する割り込み頻度を与える比較一致レジスタ値を求めることができます。

コンペアマッチレジスタ= [16, 000, 000Hz /(プリスケーラ*希望の割り込み周波数)]-1
タイマー0と2を使用する場合、この数値は256未満、timer1の場合は65536未満でなければならないことに注意してください。

1秒ごとに割り込みが必要な場合(周波数1Hz):
比較一致レジスタ= [16, 000, 000 /(プリスケーラ* 1)] -1
1024のプリスケーラーを使用すると、次のようになります。
比較一致レジスタ= [16, 000, 000 /(1024 * 1)] -1
= 15, 624
256 <15, 624 <65, 536なので、この割り込みにはtimer1を使用する必要があります。

ステップ2:タイマー割り込みの構造化


タイマー設定コードは、Arduinoスケッチのsetup(){}関数内で実行されます。

タイマー割り込みのセットアップに関係するコードは、見るのが少し難しいですが、実際にはそれほど難しくありません。 ほぼ同じコードのメインチャンクをコピーし、プリスケーラと比較一致レジスタを変更して、正しい割り込み頻度を設定するだけです。

割り込みセットアップの主な構造は次のようになります。
 ////www.instructables.com/id/Arduino-Timer-Interrupts/ void setup()cli(); // stop interrupts // set timer0 interrupt at 2kHz TCCR0A = 0; // TCCR0Aレジスタ全体を0 TCCR0Bに設定= 0; // TCCR0Bで同じTCNT0 = 0; //カウンタ値を0に初期化する//比較一致レジスタを2khzインクリメントに設定するOCR0A = 124; // =(16 * 10 ^ 6)/(2000 * 64)-1 (<256でなければなりません)// CTCモードTCCR0Aをオンにします//セットアップを終了します 
これらのタイマー設定ごとにOCR#Aの値(比較一致値)がどのように変化するかに注意してください。 最後のステップで説明したように、これは次の式に従って計算されました。

コンペアマッチレジスタ= [16, 000, 000Hz /(プリスケーラ*希望の割り込み周波数)]-1
タイマー0と2を使用する場合、この数値は256未満、timer1の場合は65536未満でなければならないことに注意してください。

また、3つのタイマー間のセットアップが、CTCモードをオンにする行でわずかに異なることに注意してください。
TCCR0A | =(1 << WGM01); // timer0の場合
TCCR1B | =(1 << WGM12); // timer1の場合
TCCR2A | =(1 << WGM21); // timer2の場合
これはATMEL 328/168のデータシートから直接続きます。

最後に、プリスケーラのセットアップが最後のステップのテーブルに従う方法に注意してください(タイマー0のテーブルは上記で繰り返されます)。
TCCR2B | =(1 << CS22); //タイマー2の64プリスケーラーにCS#2ビットを設定します
TCCR1B | =(1 << CS11); //タイマー1の8プリスケーラーにCS#1ビットを設定します
TCCR0B | =(1 << CS02)| (1 << CS00); //タイマー0の1024プリスケーラーにCS#2およびCS#0ビットを設定します

最後のステップで、タイマーごとに異なる事前スケーリングオプションがあることに注意してください。 たとえば、timer2には1024プリスケーラのオプションはありません。

これらのタイマー割り込み中に実行するコマンドは、以下にカプセル化されたArduinoスケッチにあります。
ISR(TIMER0_COMPA_vect){// timer1の0から1、timer2の2に変更
//ここでコマンドを中断します
}
このコードは、setup()およびloop()関数の外側に配置する必要があります。 また、特に頻繁に割り込みを行う場合は、割り込みルーチンをできるだけ短くするようにしてください。 digitalWrite()およびdigitalRead()関数を使用する代わりに、ATMELチップのポート/ピンを直接アドレス指定する価値さえあります。 詳細については、こちらをご覧ください。

例-次のスケッチは、3つのタイマー割り込みを設定して実行します。

 //タイマー割り込み// Amanda Ghassaei // 2012年6月////www.instructables.com/id/Arduino-Timer-Interrupts/ / * *このプログラムはフリーソフトウェアです。 あなたはそれを再配布および/または修正することができます。* Free Software Foundationによって公開されているGNU General Public Licenseの条件の下で。 ライセンスのバージョン3、または*(オプション)以降のバージョン。 * * / // timer0、timer1、およびtimer2のタイマー設定。 // arduino unoまたはATMEL 328/168を搭載したボードの場合。diecimila、duemilanove、lilypad、nano、mini ... //このコードは3つのarduinoタイマー割り込みをすべて有効にします。 // timer0は2kHzで割り込み// timer1は1Hzで割り込み// timer2は8kHzで割り込み//ストレージ変数boolean toggle0 = 0; boolean toggle1 = 0; boolean toggle2 = 0; void setup()//ピンを出力として設定pinMode(8、OUTPUT); pinMode(9、OUTPUT); pinMode(13、OUTPUT); cli(); //割り込みを停止// timer0割り込みを2kHzに設定TCCR0A = 0; // TCCR2Aレジスタ全体を0に設定TCCR0B = 0; // TCCR2Bでも同じTCNT0 = 0; //カウンタ値を0に初期化//設定2khz増分の比較一致レジスタOCR0A = 124; // =(16 * 10 ^ 6)/(2000 * 64)-1(<256でなければなりません)// CTCモードをオンにしますTCCR0A //セットアップを終了ISR(TIMER0_COMPA_vect){ // timer0割り込み2kHzはピン8を切り替えます//周波数2kHz / 2 = 1kHzのパルス波を生成します(全波の切り替えに2サイクルかかり、次に切り替えて低くなります)if(toggle0){digitalWrite(8、HIGH); toggle0 = 0; } else {digitalWrite(8、LOW); toggle0 = 1; }} ISR(TIMER1_COMPA_vect){// timer1 interrupt 1Hz toggles pin 13(LED)//周波数1Hz / 2 = 0.5kHzのパルス波を生成します(全波で2サイクルかかり、次にトグルを下げます)if(toggle1){ digitalWrite(13、HIGH); toggle1 = 0; } else {digitalWrite(13、LOW); toggle1 = 1; }} ISR(TIMER2_COMPA_vect){// timer1割り込み8kHzはピン9をトグルします//周波数8kHz / 2 = 4kHzのパルス波を生成します(全波に2サイクルかかり、次にトグルローになります)if(toggle2){digitalWrite(9、高い); toggle2 = 0; } else {digitalWrite(9、LOW); toggle2 = 1; }} void loop(){//他のことをここで行う} 

上の画像は、これらのタイマー割り込みからの出力を示しています。 図1は1kHzで0と5Vの間で振動する方形波を示し(timer0割り込み)、図2はピン13に取り付けられたLEDが1秒間オンになり、その後1秒間オフになる(timer1割り込み)、図3はパルス波の発振を示します4kHzの周波数で0〜5V(timer2割り込み)。

ステップ3:例1:自転車速度計

この例では、arduinoを搭載した自転車の速度計を作成しました。 磁石をホイールに取り付け、フレームに取り付けられた磁気スイッチを通過するのにかかる時間を測定します。これは、ホイールが完全に1回転する時間です。

タイマー1を設定して、ms(周波数1kHz)ごとに割り込み、磁気スイッチを測定します。 磁石がスイッチを通過している場合、スイッチからの信号は高く、変数「時間」はゼロに設定されます。 磁石がスイッチの近くにない場合、「時間」は1ずつ増加します。このように、「時間」は、磁石が最後に磁気スイッチを通過してから経過したミリ秒単位の時間の測定値です。 この情報は、後のコードで自転車のrpmとmphを計算するために使用されます。

1kHzの割り込み用にtimer1を設定するコードの一部を次に示します

cli(); //割り込みの停止
// 1kHzでtimer1割り込みを設定します
TCCR1A = 0; // TCCR1Aレジスタ全体を0に設定
TCCR1B = 0; // TCCR1Bでも同じ
TCNT1 = 0; //カウンター値を0に初期化
// 1khz刻みのタイマーカウントを設定します
OCR1A = 1999; // =(16 * 10 ^ 6)/(1000 * 8)-1
//このbc 1999> 255には16ビットtimer1を使用する必要がありましたが、より大きなプリスケーラーでタイマー0または2に切り替えることができました
// CTCモードをオンにします
TCCR1B | =(1 << WGM12);
// 8プリスケーラのCS11ビットを設定します
TCCR1B | =(1 << CS11);
//タイマー比較割り込みを有効にします
TIMSK1 | =(1 << OCIE1A);
sei(); //割り込みを許可

ご覧になりたい場合の完全なコードは次のとおりです。
 //自転車スピードメーター// Amanda Ghassaei 2012によって////www.instructables.com/id/Arduino-Timer-Interrupts/ ////www.instructables.com/id/Arduino-Timer-Interrupts/ / * *これプログラムはフリーソフトウェアです。 あなたはそれを再配布および/または修正することができます。* Free Software Foundationによって公開されているGNU General Public Licenseの条件の下で。 ライセンスのバージョン3、または*(オプション)以降のバージョン。 * * / //計算の計算//タイヤ半径〜13.5インチ//円周= pi * 2 * r =〜85インチ//最大速度35mph =〜616inches /秒//最大rps =〜7.25 #define reed A0 /読み取りスイッチに接続された/ピン//ストレージ変数float radius = 13.5; //タイヤ半径(インチ単位)-自分の自転車の場合はこれを変更int reedVal; 長い時間= 0; // 1回の完全な回転間の時間(ミリ秒)float mph = 0.00; フロート円周; ブールバックライト; int maxReedCounter = 100; // min回転(デバウンス用)の1回転の時間(ミリ秒単位)int reedCounter; void setup(){reedCounter = maxReedCounter; 円周= 2 * 3.14 *半径; pinMode(1、OUTPUT); // tx pinMode(2、OUTPUT); //バックライトスイッチpinMode(reed、INPUT); // reddスイッチcheckBacklight(); Serial.write(12); // clear //タイマー設定-タイマー割り込みにより、リードスイッチの正確な時間測定が可能// arduinoタイマーの設定に関する詳細情報については、// arduino.cc/playground/Code/Timer1 cli( ); //割り込みの停止// timer1割り込みを1kHzに設定TCCR1A = 0; // TCCR1Aレジスタ全体を0に設定TCCR1B = 0; // TCCR1Bについても同じTCNT1 = 0; //カウンタ値を0に初期化; // 1kHz刻みのタイマーカウントを設定OCR1A = 1999; // =(16 * 10 ^ 6)/(1000 * 8)-1 // CTCモードをオンにするTCCR1B | =(1 << WGM12); // 8プリスケーラTCCR1BのCS11ビットを設定| =(1 << CS11); //タイマー比較割り込みを有効にしますTIMSK1 | =(1 <0){// reedCounterに負の値を設定しないreedCounter-= 1; // decrement reedCounter}}} else {// ifスイッチが開いている場合(reedCounter> 0 ){// reedCounterを負にさせないreedCounter-= 1; // decrement reedCounter}} if(time> 2000){mph = 0; //リードスイッチタイヤからの新しいパルスがまだない場合、mphを設定する0} else {time + = 1; // increment timer}} void displayMPH(){Serial.write(12); // clear Serial.write( "Speed ="); Serial.write(13); //新しい行を開始Serial.print(mph); Serial.write( "MPH"); //Serial.write("0.00 MPH "); } void loop(){// mphを1秒に1回表示displayMPH(); delay(1000); checkBacklight(); } 

ステップ4:例2:シリアル通信

このプロジェクトは、4x4バックライトボタンパッドです。 プロジェクトはusbを介してコンピューターに接続し、ボタンに関する情報をコンピューターに送信し、LEDの点灯方法に関する情報を受信します。 ビデオは次のとおりです。



このプロジェクトでは、timer2割り込みを使用して、着信シリアルデータがあるかどうかを定期的にチェックし、読み取り、マトリックス「ledData []」に格納しました。 コードを見ると、スケッチのメインループが実際にledDataの情報を使用して正しいLEDを点灯し、ボタンのステータスを確認する役割を果たしていることがわかります(「shift( ) ")。 割り込みルーチンはできるだけ短く、着信バイトをチェックして適切に保存するだけです。

timer2のセットアップは次のとおりです。

cli(); //割り込みの停止
// 128usごとにtimer2割り込みを設定します
TCCR2A = 0; // TCCR2Aレジスタ全体を0に設定
TCCR2B = 0; // TCCR2Bでも同じ
TCNT2 = 0; //カウンター値を0に初期化
// 7.8khzインクリメントの比較一致レジスタを設定します
OCR2A = 255; // =(16 * 10 ^ 6)/(7812.5 * 8)-1(<256でなければなりません)
// CTCモードをオンにします
TCCR2A | =(1 << WGM21);
// 8プリスケーラのCS21ビットを設定します
TCCR2B | =(1 << CS21);
//タイマー比較割り込みを有効にします
TIMSK2 | =(1 << OCIE2A);
sei(); //割り込みを許可

完全なArduinoスケッチを以下に示します。
 // 74HC595および74HC165およびシリアル通信によるボタンテスト// Amanda Ghassaeiによる// 2012年6月////www.instructables.com/id/Arduino-Timer-Interrupts/ / * *このプログラムはフリーソフトウェアです。 あなたはそれを再配布および/または修正することができます。* Free Software Foundationによって公開されているGNU General Public Licenseの条件の下で。 ライセンスのバージョン2、または*(オプション)以降のバージョン。 * * / //このファームウェアは、maxmspパッチ「ビートスライサー」でデータを送受信します//ピン接続#define ledLatchPin A1 #define ledClockPin A0 #define ledDataPin A2 #define buttonLatchPin 9 #define buttonClockPin 10 #define buttonDataPin A3 / /ループ変数バイトi; バイトj; バイトk; バイトledByte; // led状態のストレージ、4バイトバイトledData [] = {0、0、0、0}; //ボタン用のストレージ、4バイトバイトbuttonCurrent [] = {0, 0, 0, 0}; byte buttonLast [] = {0, 0, 0, 0}; byte buttonEvent [] = {0, 0, 0, 0}; byte buttonState [] = {0, 0, 0, 0}; //ボタンデバウンスカウンター-16バイトバイトbuttonDebounceCounter [4] [4]; void setup()=(1 << WGM21); // 8プリスケーラTCCR2BのCS21ビットを設定します// buttonCheck-指定されたボタンの状態をチェックします。 //このボタンチェック機能は、ブライアン・クラブツリーとジョー・レイクによってmonome 40hファームウェアから大部分コピーされますvoid buttonCheck(byte row、byte index){if(((buttonCurrent [row] ^ buttonLast [row])&(1 << index) )&& //現在の物理ボタン状態が((buttonCurrent [row] ^ buttonState [row])&(1 << index)))と異なる場合{//最後の物理ボタン状態と現在のデバウンス状態if(buttonCurrent [row]&(1 << index))//現在の物理的なボタンの状態が押されている場合buttonEvent [row] = 1 << index; //新しいボタンイベントをすぐにキューに入れるbuttonState [row] else {buttonDebounceCounter [row] [index] = 12; } //それ以外の場合、ボタンは以前に押されていたが//現在リリースされているため、デバウンスカウンターを設定する } else if(((buttonCurrent [row] ^ buttonLast [row])&(1 << index))== 0 && //現在の物理的なボタンの状態が(buttonCurrent [row] ^ buttonState [row]と同じ場合)&(1 <0 && --buttonDebounceCounter [row] [index] == 0){//デバウンスカウンターが0にデクリメントされた場合//(つまり、ボタンが// kButtonUpDefaultDebounceCountの間アップしたことを意味します/ / iterations /// buttonEvent [row] = 1 << index; //ボタン状態変更イベントをキューイングif(buttonCurrent [row]&(1 << index))=(1 << index); else {buttonState [ row]&=〜(1 << index);}}}} void shift(){for(i = 0; i <4; i ++){buttonLast [i] = buttonCurrent [i]; byte dataToSend =(1 < > 3; // latchpin low digitalWrite(buttonLatchPin、LOW); for(k = 0; k <4; k ++){buttonCheck(i、k); if(buttonEvent [i] <  1)&3; バイトledx =(ledByte >> 3)&3; if(ledstate)= 8 >> ledx; else {ledData [ledy]&=〜(8 >> ledx); }} // endシリアルが利用可能な場合} // end do while(Serial.available()> 8); } void loop(){shift(); // LEDを更新し、ボタンからデータを受け取る} 

以下のMaxMSPパッチをダウンロードします(Max Runtimeでも実行されます)。

添付ファイル

  • beat slicer.zipダウンロード

ステップ5:例3:DAC

このプロジェクトでは、タイマー割り込みを使用して、Arduinoから特定の周波数の正弦波を出力しました。 シンプルな8ビットR2R DACをデジタルピン0〜7にはんだ付けしました。 このDACは、マルチレベルの分圧器に配置された10kおよび20kの抵抗器で構成されていました。 DACの構築については、別の説明可能な記事で詳しく説明します。今のところ、上記の写真を含めました。
DACからの出力をオシロスコープに接続しました。 オシロスコープの使用/読み取り方法の理解が必要な場合は、このチュートリアルをご覧ください。 次のコードをArduinoにロードしました。
 // 63Hz正弦波//作成者:Amanda Ghassaei 2012 ////www.instructables.com/id/Arduino-Timer-Interrupts/ / * *このプログラムはフリーソフトウェアです。 あなたはそれを再配布および/または修正することができます。* Free Software Foundationによって公開されているGNU General Public Licenseの条件の下で。 ライセンスのバージョン3、または*(オプション)以降のバージョン。 * * / // 63Hz正弦波をarduino PORTD DAC float t = 0に送信します。 void setup()=(1 << CS21); //タイマー比較割り込みを有効にしますTIMSK2 ISR(TIMER2_COMPA_vect){//インクリメントt t + = 1; if(t == 628){// 40kHz / 628 =〜63Hz t = 0; }} void loop(){//周波数〜63Hzの正弦波//正弦値を0〜255の範囲でPORTDに送信PORTD = byte(127 + 127 * sin(t / 100)); } 
40kHzの周波数で変数tをインクリメントするタイマー割り込みを設定します。 tが627に達すると、ゼロにリセットされます(40, 000 / 628 = 63Hzの周波数で発生します)。 一方、メインループでは、Arduinoは0(バイナリで00000000)から255(バイナリで11111111)の値をデジタルピン0〜7(PORTD)に送信します。 次の式を使用してこの値を計算します。

PORTD = byte(127 + 127 * sin(t / 100));

したがって、tが0から627に増加すると、正弦関数は1つの完全なサイクルを移動します。 PORTDに送信される値は、周波数63Hz、振幅127の正弦波で、127前後で発振します。これが8ビット抵抗ラダーDACを介して送信されると、振幅2.5V、周波数63Hzで2.5V前後の発振信号を出力します。

正弦波の周波数は、(t / 100)項に2を掛けることで2倍にでき、4に掛けることで4倍になります...
また、プリスケーラまたはOCR2Aを減らしてタイマー割り込みの周波数を上げすぎると、正弦波が正しく出力されないことに注意してください。 これは、sin()関数は計算コストが高く、高い割り込み頻度では実行するのに十分な時間がないためです。 割り込みルーチン中に計算を実行する代わりに、高頻度の割り込みを使用している場合、配列に値を保存し、何らかのインデックスを使用してこれらの値を呼び出すことを検討してください。 私のarduino波形発生器でその例を見つけることができます-配列に20, 000の値のsinを保存することにより、100kHzのサンプリング周波数で正弦波を出力することができました。

ステップ6:タイマーとArduino関数

最後に注意すべき点があります。特定のタイマー設定は、実際にはArduinoライブラリ関数の一部を無効にします。 timer0は、関数millis()およびdelay()によって使用されます。timer0を手動で設定した場合、これらの関数は正しく機能しません。
さらに、3つのタイマーすべてが関数analogWrite()を引き受けます。 タイマーを手動で設定すると、analogWrite()が機能しなくなります。

コードに割り込みたくない部分がある場合は、cli()およびsei()を使用して割り込みをグローバルに無効および有効にすることを検討してください。

詳細については、ArduinoのWebサイトをご覧ください。

関連記事