こんにちは、「メカのりまき」です。
この記事では、タイマ割り込みについて説明した後、「8分タイマ」の制作について説明したいと思います。
この記事では、関数と割り込み処理についての基礎知識が必要になります。
関数と割り込み処理が良く分かっていないという方は、以下のリンク先の記事で説明をしていますので、先にご覧になることをお勧めします。
目次
本記事で使用するもの
記事前半のタイマ割り込みの説明では以下のものを使用しています。
- 「Arduino IDE2.0.4」をインストールしたPC(Windows11)
- USBケーブル
- Arduino Uno R3(ELEGOO UNO R3でも可)
また、後半の「8分タイマ」の制作では、上記のものに加え、以下のものを使用しています。
- ブレッドボード
- LED
- 抵抗器
- パッシブブザー
- タクトスイッチ
- ジャンパーワイヤー
「Arduino Uno R3」の互換ボードであれば、「ELEGOO UNO R3」以外のボードでも、本記事と同様な手順で動作可能だと思われますが、動作検証はしていないので、その点はご了承ください。
以下のリンクのスターターキットを購入した場合、上記で述べた使用するものは、PC以外すべて入手することができます。
Arduinoをまだ購入していないという方やArduinoで動かすことができるたくさんのパーツが欲しいという方は、購入を検討してみてください。
タイマ割り込みの説明
「タイマ割り込み」は、指定した時間が過ぎるごとに、現在の処理を中断して、指定した関数を実行するという機能です。
時間の経過は、「クロック」というものと「タイマ」というものを利用して行います。
「クロック」とは処理のタイミングを合わせるために利用されるパルス信号であり、一定の周期で「HIGH」になったり「LOW」になったりを繰り返します。
そして、「タイマ」はそのクロックのパルス信号に応じて、カウントをしています。
カウントの仕方はマイコンや設定により異なっていて、例えばクロックの立ち上がりが起きるたびに、カウント上げるような設定だとすると、以下のようにカウントが上がっていくということになります。
ここで、クロックは一定の周期で、「HIGH」と「LOW」を繰り返しているため、「タイマ」のカウント数から時間が分かるということになります。
ただし、「タイマ」のカウントには上限があり、計れる時間には限りがあります。
よって、カウントが上限に達したときは、カウントが「0」に戻るような処理やカウントを下げていくような処理が行われます。
また、上限に達する前に「0」に戻るようにすることも設定によっては可能になります。
ここで、改めてカウンタの波形を見てみると、設定が常に同じ場合、一定の波形を繰り返しているのが分かると思います。
「タイマ割り込み」は、その性質を利用し、カウント数が上限に達したときや指定した値になったとき、割り込みを発生させ、関数の実行を行っています。
Arduinoとタイマ割り込み
「Arduino Uno R3」には「Timer0」、「Timer1」、「Timer2」の3つのタイマがあり、これらを設定することにより、「タイマ割り込み」を行うことができます。
しかし、「Arduino IDE」では、「タイマ」の設定を行うための関数は用意されておらず、「タイマ割り込み」を行えるようにするには、「レジスタ」というものを操作する記述をする必要があります。
以下のスケッチは「レジスタ」の操作で「タイマ」を設定し、4番ピンの「HIGH」と「LOW」を切り替えるものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
void setup() { //4番ピンを出力ピンに設定 pinMode(4,OUTPUT); //念のためレジスタを初期化 TCCR1A=0; TCCR1B=0; //CTCモードに設定 TCCR1B|=(1<<WGM12); //クロックの立ち上がりが1024回起きるごとに1カウントを増やすように設定 TCCR1B|=(1<<CS12)|(1<<CS10); //15625カウントしたら0に戻るように設定+割り込みをするように設定 OCR1A=15625-1; //6250カウントしたら割り込みをするように設定 OCR1B=6250-1; //割り込みを許可 TIMSK1|=(1<<OCIE1A)|(1<<OCIE1B); } void loop() { } //15625カウントしたときに行う割り込み処理 ISR(TIMER1_COMPA_vect){ digitalWrite(4,LOW); } //6250カウントしたときに行う割り込み処理 ISR(TIMER1_COMPB_vect){ digitalWrite(4,HIGH); } |
「レジスタ」で設定する方法を理解していれば、自由自在に割り込み処理の設定を行うことができるので、一番良いのですが、「レジスタ」の対応はマイコンの種類により異なるため、扱う際はマイコンについてよく調べる必要があります。
さらに、処理をするための文を書くために、「ビット演算」というものを理解する必要があるため初心者にとっては少しハードルが高いです。
そこで、今回の記事では、上記のように「レジスタ」の操作をして、タイマ割り込みを利用する方法ではなく、ライブラリを利用することで、簡単に「タイマ割り込み」が行える方法について、説明したいと思います。
MsTimer2の説明
タイマ割り込みが行えるライブラリはいくつかあるのですが、今回は「MsTimer2」というライブラリについて説明したいと思います。
「MsTimer2」ライブラリの「MsTimer2::set()」という関数は、ms単位の時間と割り込みで実行したい関数を引数にしており、指定した時間になったら、指定した関数を実行するというタイマ割り込みが行えるように設定をしてくれます。
ライブラリの中身については分からなくても、タイマ割り込みを行えるのですが、まったく分からないまま使用するのは嫌だという方がいらっしゃるかもしれないので、「MsTimer2::set()」の仕組みについて、もう少しだけ詳しく説明したいと思います。
まず、「MsTimer2::set()」という関数を使うと、レジスタを操作する処理を行い、1[ms]ごとに割り込み処理を行うように、「Timer2」の設定が行われます。
そして、割り込み処理が行われるたびに、1[ms]を表すためのカウント(タイマで使用しているのと別物)を増やしていき、ユーザが指定した時間([ms]単位)になったら、指定した関数を実行して、その後、再スタートするという仕組みになっています。
よって、純粋な「タイマ割り込み」とは少し異なりますが、指定した時間ごとに割り込みを行うという点は同じになるということになります。
MsTimer2のインストール
「MsTimer2」は、「Arduino IDE」に初めから付属しているライブラリではないため、利用するためには、インストールを行う必要があります。
インストールする方法は以下の通りです。
STEP1
ライブラリマネージャを開く。
以下の画像は「Arduino IDE2.0.4」のものです。
古いバージョンを使用している場合は、「ツール」タブをクリック後、「ライブラリの管理」をクリックしてください。
STEP2
「MsTimer2」で検索をして、「MsTimer2」を探す。
STEP3
「インストール」をクリックする。
MsTimer2の使用例
では、実際に「MsTimer2」を利用する方法について、説明をしたいと思います。
以下のスケッチを書き込んでシリアルモニタを確認してみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
//ライブラリの読みこみ #include <MsTimer2.h> //タイマー割り込みが発生するたびに1増える変数 int count=0; //countに応じてtrueになる変数(フラグ) volatile bool Count_Flag_1=false; volatile bool Count_Flag_2=false; volatile bool Count_Flag_3=false; volatile bool Count_Flag_4=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //改行してシリアルモニタに(-_-)を表示 Serial.print("\n(-_-)"); //1秒ごとにCount_Upが実行されるように、タイマー割り込みの設定をする MsTimer2::set(1000,Count_Up); //タイマー割り込みを開始する MsTimer2::start(); } void loop() { //各フラグを監視して、いずれかがtrueになっていたらフラグに応じた表示を行う if(Count_Flag_1){ Serial.print('z'); Count_Flag_1=false; }else if(Count_Flag_2){ Serial.print('z'); Count_Flag_2=false; }else if(Count_Flag_3){ Serial.print('Z'); Count_Flag_3=false; }else if(Count_Flag_4){ Serial.println('Z'); Count_Flag_4=false; } } //タイマー割り込み発生時に実行する関数 void Count_Up(){ //countを1増やす count++; //countに応じてフラグをtrueにする if(count==5){ Count_Flag_1=true; }else if(count==10){ Count_Flag_2=true; }else if(count==15){ Count_Flag_3=true; }else if(count==20){ Count_Flag_4=true; //タイマ割り込みを終了する MsTimer2::stop(); } } |
すると、始めに「(-_-)」と表示された後、ゆっくりと「z」が追加され最終的に「(-_-)zzZZ」の状態で表示が止まるのが確認できます。
最初にシリアルモニタを開いていない場合、「z」だけ出力されたり、何も表示されなかったりすることがあります。
その場合は、シリアルモニタを開いた状態で「Arduino Uno R3」のリセットボタンを押すと、再度スケッチの初めから処理が実行されるので、正しく表示されます。
それでも、表示が上手くいかない場合は、スケッチやシリアルモニタの設定を確認をしてみてください。
スケッチの解説
まずは、「MsTimer2」に関係する処理について説明していきたいと思います。
まず、スケッチの2行目で、以下のような文が記述されています。
2 |
#include <MsTimer2.h> |
上記の文では「MsTime2」ライブラリを読み込み、ライブラリに登録されている関数を利用できるようにしています。
次に、22行目では以下の文が記述されています。
22 |
MsTimer2::set(1000,Count_Up); |
上記の文では、1000[ms](1秒)ごとに、「Count_UP」という関数が実行されるように、タイマ割り込みの設定を行っています。
ここで指定する関数は、外部割り込みのときと同様、値を返さない「void型」で、さらに引数を持たないことが条件になります。
関数の中身についての注意点も外部割り込みのときと同様で、割り込み処理を利用した関数が利用できなかったり、「void loop()」と「割り込み処理」で同時にシリアルモニタへ表示させる処理が行われると表示がおかしくなったり、関数の中と外で共通の変数に対して「volatile」を付けないと意図しない処理になったりします。
タイマ割り込みを行う仕組みについては、前の節で説明した通りです。
次に、25行目では以下の文が記述されています。
25 |
MsTimer2::start(); |
実は、「MsTimer2::set()」は「Timer2」の設定を行っているだけであり、タイマ割り込みは開始されていません。
上記の「MsTimer2::start()」では、カウントを初期化し、タイマ割り込みの許可をすることで、タイマ割り込みを開始しています。
最後に63行目では以下の文が記述されています。
63 |
MsTimer2::stop(); |
上記の「MsTimer2::stop()」は、タイマ割り込みを終了させる文です。
これにより、「Timer2」のタイマ割り込みが禁止され、1[ms]ごとのカウントが止まります。
「MsTimer2」に関係する処理についての説明は以上になります。
スケッチ全体の処理をまとめると以下の図のようになります。
ここで、19行目では「Serial.print(“\n(-_-)”);」と書いているのに、「\n」が表示されないのは、この部分が改行コードを表しているからです。
これにより、改行してから、「(-_-)」の表示を行っています。
MsTimer2の注意点
「MsTimer2」は「Timer2」を利用して割り込み処理行っているため、同じ「Timer2」を使うような関数を同時に実行しようとすると、動作がおかしくなったり、どちらかが動作しなくなったりします。
例えば、「Timer2」を利用したものとして、「tone()関数」や「Arduino Uno R3」の3番ピンと11番ピンの「PWM制御(analogWrite())」などがあります。
これらの関数を扱う際、「Timer2」はそれぞれの関数に合わせて設定されるため、同時に扱うことはできなくなるということになります。
ただし、同時に使用しないように、うまく実行させれば、同じスケッチ内で同じ「タイマ」を使用した関数を複数使用することが可能な場合があります。
例えば、以下で説明する「8分タイマ」は、「MsTimer2」と「tone()関数」を同じスケッチ内で使用しています。
タイマ割り込みについての説明は以上となります。
8分タイマの制作
ここからは「8分タイマ」の制作について説明したいと思います。
回路は前回、前々回の記事で制作した「右往左往ゲーム」の回路をそのまま使用します。
「右往左往ゲーム」の詳しい説明については、以下のリンク先の記事をご覧になってください。
「8分タイマ」の制作では、「右往左往ゲーム」を作った際の知識も利用するため、先に「右往左往ゲーム」についての記事をご覧になることをお勧めします。
8分タイマの概要
「右往左往ゲーム」の回路をそのまま再利用して、8分までの時間を計測できる「8分タイマ」を制作したいと思います。
「8分タイマ」の内容は、左のスイッチを押すことで時間の設定、右のスイッチを押すことで計測の開始をし、時間が経過したらアラームを鳴らすというものです。
ここで、アラームはスイッチを押すまで、鳴り続けるものとします。
また、時間の設定や時間の経過は、LEDの点灯により行います。
時間の計測が開始された後に、計測をやめたいとき、「Arduino Uno R3」のリセットボタンを押してリセットすることも可能ですが、今回は回路上のスイッチを押すことにより、リセットが行えるようにしたいと思います。
スケッチの構造を考える
ここでは、上記の「8分タイマ」を作成するために、スケッチをどのような手順で考えたのかを説明したいと思います。
まず、「8分タイマ」の大まかな処理としては、「時間の設定」、「時間を計る」、「アラームを鳴らす」の3つに分けることができます。
そこで、今回はそれぞれを「mode=1」、「mode=2」、「mode=3」とし、スイッチが押されたときや時間が経過したときに「mode」を切り替え、それぞれの処理を行うようにしたいと考えました。
大まかな処理の流れは以下のようになります。
大まかな流れが決まったので、次に、それぞれの「mode」で行いたいことや「mode」の切り替わる条件をまとめました。
まとめた結果が以下のようになります。
最後に、上記で決めたことをするために、具体的にどのような処理をすればいいのかをまとめました。
まとめた結果が以下のようになります。
これで、スケッチの構造は決まったので、後はそれをスケッチにまとめていくだけです。
1分後にアラームを鳴らす
まずは、全体の構造を作りつつ、1分間だけカウントできるようなスケッチを作成します。
今回作成するのは、以下の図の赤枠で囲った部分になります。
以下のスケッチを書き込んでください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
//ライブラリの読み込み #include <MsTimer2.h> //タイマ割り込みが発生するたびに1増える変数 volatile int count=0; //現在のモードを表す変数 volatile int mode=1; //1分のカウントをする変数 volatile int count_minute=1; //右のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_R=false; //左のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_L=false; void setup() { //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //3番ピンをプルアップ付きの入力ピンとして設定する pinMode(3,INPUT_PULLUP); //3番ピンが押された瞬間に関数Pushed_L()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーに繋いだピン(12番ピン)を出力ピンとして設定する pinMode(12,OUTPUT); } void loop() { //各モードによって行う処理を変える(mode 1:時間の設定を行う 2:時間を計る 3:アラームを鳴らす) if(mode==1){ //右のスイッチが押されたらタイマ割り込みを開始してmode 2に移行 //左のスイッチが押されたらcount_minuteを増やす if(Pushed_Flag_R){ Start_Count(); Pushed_Flag_R=false; }else if(Pushed_Flag_L){ //後でcount_minuteを増やす関数を追加 Pushed_Flag_L=false; } }else if(mode==2){ //右か左のスイッチが押されたらcount_minuteのリセットをしてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ //後でcount_minuteをリセットする関数を追加 Pushed_Flag_R=false; Pushed_Flag_L=false; } }else if(mode==3){ //アラームを鳴らす Alarm(); //右か左のスイッチが押されたらアラームを止めてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Stop_Alarm(); Pushed_Flag_R=false; Pushed_Flag_L=false; } } } //**********タイマ割り込み発生時に実行する関数********** void Count_Up(){ //countを1増やす count++; //countが60以上になったらcount_minuteを減らしLEDを消灯、それ以外のときcountの偶数、奇数に合わせて点灯、消灯を行う if(count>=60){ //count_minuteに対応したLEDを消灯させる digitalWrite(count_minute+3,LOW); //count_minuteを減らす count_minute--; //countを0に戻して60以上になるのを待つ count=0; //count_minuteが0以下になったらタイマ割り込みを止めmodeを3にする if(count_minute<=0){ MsTimer2::stop(); mode=3; } }else if(count%2==0){ digitalWrite(count_minute+3,HIGH); }else{ digitalWrite(count_minute+3,LOW); } } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Pushed_Flag_RがfalseのときPushed_Flag_Rをtrueにする if(!Pushed_Flag_R){ Pushed_Flag_R=true; } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Pushed_Flag_LがfalseのときPushed_Flag_Lをtrueにする if(!Pushed_Flag_L){ Pushed_Flag_L=true; } } //**********カウントを始める関数********** void Start_Count(){ //modeを2する mode=2; //countを0にする count=0; //1秒ごとにCount_Upが実行されるように、タイマ割り込みの設定をする MsTimer2::set(1000,Count_Up); //タイマ割り込みを開始する MsTimer2::start(); //チャタリング防止のため待機 delay(250); } //**********アラームを鳴らす関数********** void Alarm(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //ブザーを鳴らす tone(12,3136,500); delay(1000); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを止める関数********** void Stop_Alarm(){ //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //チャタリング防止のため待機 delay(250); //modeを1する mode=1; } |
上記のスケッチを書き込むと、以下の動画のように、1分間だけ時間の計測が行えることが確認できます。
スケッチの解説
まずは、「void loop()」の部分から説明したいと思います。
「void loop()」では、各「mode」に応じた処理を行うために、if文を使い、各ブロックへ分岐をさせています。
大まかな処理の流れは以下のようになります。
そして、分岐したブロックの中で、左右のスイッチが押されているかどうかを、if文で判別し、さらに処理を分岐させる構造となっています。
スイッチが押されているかの判別は、変数「Pushed_Flag_R」、「Pushed_Flag_L」が「true」になっているかによって行われています。
上記の変数「Pushed_Flag_R」、「Pushed_Flag_L」は、スイッチが押されたときの外部割り込みにより「true」になり、実行させたい処理が終わった後に「false」に戻ります。
ここで、スケッチ全体の構図を見ると以下のようになっています。
次に、スイッチが押されたとき実行する関数やタイマ割り込みで実行される関数について説明したいと思います。
まず、時間の計測を開始する関数「Start_Count」です。
この関数の処理の流れは以下のようになります。
これにより、タイマ割り込みが開始され、「mode」は「2」に切り替わります。
最後の「delay」は、チャタリングにより、もう一度「Pushed_Flag_R」が「true」になってしまうことを防ぐために入れています。
次に、タイマ割り込みで実行する関数であり、時間の測定をする「Count_Up」についてです。
この関数の処理の流れは以下のようになります。
この関数は、タイマ割り込みにより、1秒ごとに実行されるため、1秒ごとに「count」が「1」増えていくということになります。
よって、「count」が「60」になったときが、1分の経過をあらわしているため、「count」が「60」以上になったときに、「count_minute」に対応したLEDを消灯させ、「count_minute」を減らしています。
ここで、さらに、「count_minute」が「0」になったときは、設定した時間が経過したことをあらわしているため、タイマ割り込みを終了させ、「mode」を「3」にしています。
また、「count」が「60」以上でないときは、1秒ごとにLEDの点灯、消灯を切り替えて時間の計測途中であることを知らせるようにしています。
if文の条件である「count%2==0」は「count」が偶数のとき「true」、奇数のとき「false」になります。
よって、「count」が偶数のときLEDが点灯、奇数のときLEDが消灯するということになります。
次に、アラームを鳴らす関数である「Alarm」についてです。
この関数の処理の流れは以下のようになります。
この関数はブザーを鳴らして、待機をするだけです。
割り込み処理によって、「tone()関数」の波形が乱れたとしても、音の変化を感じることはないとおもいますが、念のため、関数の最初と最後で外部割り込みの禁止を行っています。
この関数は、「mode」が切り替わるまで、「void loop()」により、繰り返されるため、スイッチが押されるまで、ブザーを鳴らし続けます。
最後に、アラームを止める関数である「Stop_Alarm」についてです。
この関数の処理の流れは以下のようになります。
これにより、LEDの状態は最初の状態に戻されて、「mode」が「1」になるため、アラームが止まり、最初の状態に戻ります。
時間の設定を行えるようにする
上記のスケッチが上手く動作していれば、後は関数を追加していくだけです。
まず、時間の設定を行う関数を追加します。
以下のスケッチを書き込んでください。
黄色でマークされている部分が変更または追加した部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
//ライブラリの読み込み #include <MsTimer2.h> //タイマ割り込みが発生するたびに1増える変数 volatile int count=0; //現在のモードを表す変数 volatile int mode=1; //1分のカウントをする変数 volatile int count_minute=1; //右のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_R=false; //左のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_L=false; void setup() { //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //3番ピンをプルアップ付きの入力ピンとして設定する pinMode(3,INPUT_PULLUP); //3番ピンが押された瞬間に関数Pushed_L()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーに繋いだピン(12番ピン)を出力ピンとして設定する pinMode(12,OUTPUT); } void loop() { //各モードによって行う処理を変える(mode 1:時間の設定を行う 2:時間を計る 3:アラームを鳴らす) if(mode==1){ //右のスイッチが押されたらタイマ割り込みを開始してmode 2に移行 //左のスイッチが押されたらcount_minuteを増やす if(Pushed_Flag_R){ Start_Count(); Pushed_Flag_R=false; }else if(Pushed_Flag_L){ Count_Plus_Minute(); Pushed_Flag_L=false; } }else if(mode==2){ //右か左のスイッチが押されたらcount_minuteのリセットをしてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ //後でcount_minuteをリセットする関数を追加 Pushed_Flag_R=false; Pushed_Flag_L=false; } }else if(mode==3){ //アラームを鳴らす Alarm(); //右か左のスイッチが押されたらアラームを止めてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Stop_Alarm(); Pushed_Flag_R=false; Pushed_Flag_L=false; } } } //**********タイマ割り込み発生時に実行する関数********** void Count_Up(){ //countを1増やす count++; //countが60以上になったらcount_minuteを減らしLEDを消灯、それ以外のときcountの偶数、奇数に合わせて点灯、消灯を行う if(count>=60){ //count_minuteに対応したLEDを消灯させる digitalWrite(count_minute+3,LOW); //count_minuteを減らす count_minute--; //countを0に戻して60以上になるのを待つ count=0; //count_minuteが0以下になったらタイマ割り込みを止めmodeを3にする if(count_minute<=0){ MsTimer2::stop(); mode=3; } }else if(count%2==0){ digitalWrite(count_minute+3,HIGH); }else{ digitalWrite(count_minute+3,LOW); } } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Pushed_Flag_RがfalseのときPushed_Flag_Rをtrueにする if(!Pushed_Flag_R){ Pushed_Flag_R=true; } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Pushed_Flag_LがfalseのときPushed_Flag_Lをtrueにする if(!Pushed_Flag_L){ Pushed_Flag_L=true; } } //**********カウントを始める関数********** void Start_Count(){ //modeを2する mode=2; //countを0にする count=0; //1秒ごとにCount_Upが実行されるように、タイマ割り込みの設定をする MsTimer2::set(1000,Count_Up); //タイマ割り込みを開始する MsTimer2::start(); //チャタリング防止のため待機 delay(250); } //**********count_minuteを増やす関数********** void Count_Plus_Minute(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //count_minuteを1増やす count_minute++; //count_minuteが8より大きくなったら if(count_minute>8){ //LEDを消灯させる for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } //count_minuteを1に戻す count_minute=1; } //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //ブザーを鳴らす tone(12,392,125); //ブザー音+チャタリング防止のため待機 delay(250); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを鳴らす関数********** void Alarm(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //ブザーを鳴らす tone(12,3136,500); delay(1000); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを止める関数********** void Stop_Alarm(){ //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //チャタリング防止のため待機 delay(250); //modeを1する mode=1; } |
上記のスケッチを書き込むと、以下の動画のように、左のスイッチで時間の設定が行えるようになるのが確認できます。
スケッチの解説
「mode=1」で、左のスイッチが押されたとき、新しく作成した「Count_Plus_Minute」という関数を実行させるように変更を加えています。
新しく作成した「Count_Plus_Minute」の処理の流れは以下のようになります。
これにより、左のスイッチが押されると、「count_minute」の値が増え、ブザー音と共に「count_minute」に対応したLEDが点灯します。
また、if文により、「count_minute」が「8」よりも大きい場合は、「count_minute」を「1」に戻すような処理も行っています。
リセットを行えるようにする
次は、時間のリセットを行う関数を追加します。
以下のスケッチを書き込んでください。
黄色でマークされている部分が変更または追加した部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
//ライブラリの読み込み #include <MsTimer2.h> //タイマ割り込みが発生するたびに1増える変数 volatile int count=0; //現在のモードを表す変数 volatile int mode=1; //1分のカウントをする変数 volatile int count_minute=1; //右のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_R=false; //左のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_L=false; void setup() { //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //3番ピンをプルアップ付きの入力ピンとして設定する pinMode(3,INPUT_PULLUP); //3番ピンが押された瞬間に関数Pushed_L()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーに繋いだピン(12番ピン)を出力ピンとして設定する pinMode(12,OUTPUT); } void loop() { //各モードによって行う処理を変える(mode 1:時間の設定を行う 2:時間を計る 3:アラームを鳴らす) if(mode==1){ //右のスイッチが押されたらタイマ割り込みを開始してmode 2に移行 //左のスイッチが押されたらcount_minuteを増やす if(Pushed_Flag_R){ Start_Count(); Pushed_Flag_R=false; }else if(Pushed_Flag_L){ Count_Plus_Minute(); Pushed_Flag_L=false; } }else if(mode==2){ //右か左のスイッチが押されたらcount_minuteのリセットをしてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Reset_Count(); Pushed_Flag_R=false; Pushed_Flag_L=false; } }else if(mode==3){ //アラームを鳴らす Alarm(); //右か左のスイッチが押されたらアラームを止めてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Stop_Alarm(); Pushed_Flag_R=false; Pushed_Flag_L=false; } } } //**********タイマ割り込み発生時に実行する関数********** void Count_Up(){ //countを1増やす count++; //countが60以上になったらcount_minuteを減らしLEDを消灯、それ以外のときcountの偶数、奇数に合わせて点灯、消灯を行う if(count>=60){ //count_minuteに対応したLEDを消灯させる digitalWrite(count_minute+3,LOW); //count_minuteを減らす count_minute--; //countを0に戻して60以上になるのを待つ count=0; //count_minuteが0以下になったらタイマ割り込みを止めmodeを3にする if(count_minute<=0){ MsTimer2::stop(); mode=3; } }else if(count%2==0){ digitalWrite(count_minute+3,HIGH); }else{ digitalWrite(count_minute+3,LOW); } } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Pushed_Flag_RがfalseのときPushed_Flag_Rをtrueにする if(!Pushed_Flag_R){ Pushed_Flag_R=true; } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Pushed_Flag_LがfalseのときPushed_Flag_Lをtrueにする if(!Pushed_Flag_L){ Pushed_Flag_L=true; } } //**********カウントを始める関数********** void Start_Count(){ //modeを2する mode=2; //countを0にする count=0; //1秒後とにCount_Upが実行されるように、タイマ割り込みの設定をする MsTimer2::set(1000,Count_Up); //タイマ割り込みを開始する MsTimer2::start(); //チャタリング防止のため待機 delay(250); } //**********count_minuteを増やす関数********** void Count_Plus_Minute(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //count_minuteを1増やす count_minute++; //count_minuteが8より大きくなったら if(count_minute>8){ //LEDを消灯させる for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } //count_minuteを1に戻す count_minute=1; } //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //ブザーを鳴らす tone(12,392,125); //ブザー音+チャタリング防止のため待機 delay(250); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********count_minuteをリセットする関数********** void Reset_Count(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //タイマ割り込みを禁止する MsTimer2::stop(); //LEDを消灯させる for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーを鳴らす tone(12,165,50); delay(100); tone(12,165,150); //ブザー音+チャタリング防止のため待機 delay(250); //modeを1する mode=1; attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを鳴らす関数********** void Alarm(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //ブザーを鳴らす tone(12,3136,500); delay(1000); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを止める関数********** void Stop_Alarm(){ //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //チャタリング防止のため待機 delay(250); //modeを1する mode=1; } |
上記のスケッチを書き込むと、以下の動画のように、時間を計測している途中でリセットが行えるようになるのが確認できます。
スケッチの解説
「mode=2」で、左右どちらかのスイッチが押されたとき、新しく作成した「Reset_Count」という関数を実行させるように変更を加えています。
新しく作成した「Reset_Count」の処理の流れは以下のようになります。
これにより、スイッチが押されると、ブザー音と共に、LEDが最初の状態にリセットされます。
アラームを自分なりにアレンジする
上記のスケッチで「8分タイマ」のシステムは完成ですが、アラームが少し寂しいので、最後にアラームを鳴らす演出を自分で考えて変更を加えてみてください。
以下のスケッチは、LEDを点灯させながら、音を鳴らす例です。
LEDを点灯させる場合は、最後に全て消灯させるように気を付けてください。
黄色でマークされている部分が変更または追加した部分です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
//ライブラリの読み込み #include <MsTimer2.h> //タイマ割り込みが発生するたびに1増える変数 volatile int count=0; //現在のモードを表す変数 volatile int mode=1; //1分のカウントをする変数 volatile int count_minute=1; //右のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_R=false; //左のスイッチが押されたかを確認する変数 volatile bool Pushed_Flag_L=false; void setup() { //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //3番ピンをプルアップ付きの入力ピンとして設定する pinMode(3,INPUT_PULLUP); //3番ピンが押された瞬間に関数Pushed_L()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーに繋いだピン(12番ピン)を出力ピンとして設定する pinMode(12,OUTPUT); } void loop() { //各モードによって行う処理を変える(mode 1:時間の設定を行う 2:時間を計る 3:アラームを鳴らす) if(mode==1){ //右のスイッチが押されたらタイマ割り込みを開始してmode 2に移行 //左のスイッチが押されたらcount_minuteを増やす if(Pushed_Flag_R){ Start_Count(); Pushed_Flag_R=false; }else if(Pushed_Flag_L){ Count_Plus_Minute(); Pushed_Flag_L=false; } }else if(mode==2){ //右か左のスイッチが押されたらcount_minuteのリセットをしてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Reset_Count(); Pushed_Flag_R=false; Pushed_Flag_L=false; } }else if(mode==3){ //アラームを鳴らす Alarm(); //右か左のスイッチが押されたらアラームを止めてmode 1に移行 if(Pushed_Flag_R||Pushed_Flag_L){ Stop_Alarm(); Pushed_Flag_R=false; Pushed_Flag_L=false; } } } //**********タイマ割り込み発生時に実行する関数********** void Count_Up(){ //countを1増やす count++; //countが60以上になったらcount_minuteを減らしLEDを消灯、それ以外のときcountの偶数、奇数に合わせて点灯、消灯を行う if(count>=60){ //count_minuteに対応したLEDを消灯させる digitalWrite(count_minute+3,LOW); //count_minuteを減らす count_minute--; //countを0に戻して60以上になるのを待つ count=0; //count_minuteが0以下になったらタイマ割り込みを止めmodeを3にする if(count_minute<=0){ MsTimer2::stop(); mode=3; } }else if(count%2==0){ digitalWrite(count_minute+3,HIGH); }else{ digitalWrite(count_minute+3,LOW); } } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Pushed_Flag_RがfalseのときPushed_Flag_Rをtrueにする if(!Pushed_Flag_R){ Pushed_Flag_R=true; } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Pushed_Flag_LがfalseのときPushed_Flag_Lをtrueにする if(!Pushed_Flag_L){ Pushed_Flag_L=true; } } //**********カウントを始める関数********** void Start_Count(){ //modeを2する mode=2; //countを0にする count=0; //1秒ごとにCount_Upが実行されるように、タイマ割り込みの設定をする MsTimer2::set(1000,Count_Up); //タイマ割り込みを開始する MsTimer2::start(); //チャタリング防止のため待機 delay(250); } //**********count_minuteを増やす関数********** void Count_Plus_Minute(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //count_minuteを1増やす count_minute++; //count_minuteが8より大きくなったら if(count_minute>8){ //LEDを消灯させる for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } //count_minuteを1に戻す count_minute=1; } //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //ブザーを鳴らす tone(12,392,125); //ブザー音+チャタリング防止のため待機 delay(250); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********count_minuteをリセットする関数********** void Reset_Count(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //タイマ割り込みを禁止する MsTimer2::stop(); //LEDを消灯させる for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる(初期1分:4番ピン) digitalWrite(count_minute+3,HIGH); //ブザーを鳴らす tone(12,165,50); delay(100); tone(12,165,150); //ブザー音+チャタリング防止のため待機 delay(250); //modeを1する mode=1; attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを鳴らす関数********** void Alarm(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //ブザーを鳴らしながらLEDを点灯させる digitalWrite(7,HIGH); digitalWrite(8,HIGH); tone(12,1047,125); delay(250); digitalWrite(6,HIGH); digitalWrite(9,HIGH); tone(12,1175,125); delay(250); digitalWrite(5,HIGH); digitalWrite(10,HIGH); tone(12,1319,125); delay(250); digitalWrite(4,HIGH); digitalWrite(11,HIGH); tone(12,1397,125); delay(250); for(int i=4;i<=11;i++){ digitalWrite(i,LOW); } tone(12,3136,500); delay(1000); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********アラームを止める関数********** void Stop_Alarm(){ //count_minuteを1に戻す count_minute=1; //count_minuteに応じたLEDを点灯させる digitalWrite(count_minute+3,HIGH); //チャタリング防止のため待機 delay(250); //modeを1する mode=1; } |
上記のスケッチを書き込んだ場合、以下の動画のように動作します。
「8分タイマ」の制作についての説明は以上です。
終わりに
今回は前回の記事の外部割り込みに引き続き、タイマ割り込みの使い方について説明しました。
割り込み処理はとても便利で使いこなせるようになると、作れるものの幅が広がります。
扱いが難しいため、スケッチのミスに気付かず、思ったように動作しないことも、多々あるとは思いますが、なぜ思ったように動作しないのかを、よく考えて直すことで、スケッチを書く技術が上がってくると思います。
「右往左往ゲーム」と「8分タイマ」を参考にたくさん挑戦してみてください。
次回は、「LCDモジュール(1602A)」に文字を出力させる方法について、説明したいと思います。
本記事はここまでです。ご清覧ありがとうございました。
広告
楽天モーションウィジェットとGoogleアドセンス広告です。
気になる商品がございましたら、チェックをしてみてください。