こんにちは、「メカのりまき」です。
この記事では、外部割り込みについて説明した後、前回の記事で途中まで制作したLEDを使ったゲームの説明の続きをしたいと思います。
この記事は前回の記事の続きとなっております。
関数が良く分からないという方や、後半説明するLEDを使ったゲームを制作したいという方は、前回の記事を先にご覧になることをお勧めします。
目次
本記事で使用するもの
本記事で私が使用した物は以下の通りです。
- 「Arduino IDE2.0.4」をインストールしたPC(Windows11)
- USBケーブル
- Arduino Uno R3(ELEGOO UNO R3でも可)
- ブレッドボード
- LED
- 抵抗器
- パッシブブザー
- タクトスイッチ
- ジャンパーワイヤー
「Arduino Uno R3」の互換ボードであれば、「ELEGOO UNO R3」以外のボードでも、本記事と同様な手順で動作可能だと思われますが、動作検証はしていないので、その点はご了承ください。
以下のリンクのスターターキットを購入した場合、上記で述べた使用するものは、PC以外すべて入手することができます。
Arduinoをまだ購入していないという方やArduinoで動かすことができるたくさんのパーツが欲しいという方は、購入を検討してみてください。
回路について
本記事では、前回の記事で制作した、以下のような回路を使用して説明を行います。
ゲームの製作は行わず、外部割込みの使用方法だけを知りたいという方は、以下のような回路でも問題ありません。
外部割込みの説明
外部割り込みは、ピンの電圧がある条件を満たしたときに、現在の処理を中断して、指定した関数を実行するという機能です。
外部割り込みに使用できるピンはボードごとに決められており、「Arduino Uno R3」では、2番ピンと3番ピンが対応しています。
この外部割り込みを利用すると、スイッチが押された瞬間に、指定した関数を実行するといったことができるようになります。
外部割り込みの例
では、早速ですが、以下のスケッチを書き込んでください。
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 |
//bool型の変数Pushed_Flagを用意 volatile bool Pushed_Flag=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //1秒置きにPushed_Flagの中身を切り替える Pushed_Flag=false; delay(1000); Pushed_Flag=true; delay(1000); } void Pushed(){ //Pushed_Flagの中身をシリアルモニタに表示(true:1,false:0) Serial.println(Pushed_Flag); } |
スケッチの書き込みが終わりましたら、次はシリアルモニタを開き、2番ピンに繋いだスイッチを押してみてください。
スケッチと回路が正しければ、スイッチが押し込まれた瞬間に「0」か「1」の数字が出力されるのが確認できます。
ここで、「0」か「1」の数字が出力されない場合は、スケッチと回路が誤っていないか確認をしてみてください。
スケッチの解説
本記事では、外部割り込みの部分についてのみ、説明したいと思います。
スイッチの使い方が分からないという方は、以下のリンク先の記事で説明していますので、是非、ご覧になってください。
では、スケッチの説明をしたいと思います。
まず、2行目の変数宣言を行う際に、「volatile」という新しい単語が出てきます。
2 |
volatile bool Pushed_Flag=false; |
「volatile」は、割り込み処理の中と外で共通の変数を使う場合に記述します。
「volatile」が何をしているかについては、次の節で説明したいと思います。
ひとまず、割り込み処理の中と外で共通の変数を使う場合に、必要なものと覚えておいてください。
次に、13行目の以下の文について説明します。
13 |
attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); |
「attachInterrupt()」は外部割り込みの設定を行うための関数で、引数の構成は以下のようになっています。
まず、上記の「割り込み番号」について説明したいと思います。
スケッチの13行目の文を見ると割り込み番号を指定をする部分に、「digitalPinToInterrupt()」という関数が使われています。
これは、ピン番号を割り込み番号に変更するための関数です。
実は割り込み番号と外部割り込みに利用するピンの番号は異なっており、「Arduino Uno R3」の場合、割り込み番号「0」が2番ピン、割り込み番号「1」が3番ピンに対応しています。
この番号の対応は、ボードの種類によって、異なるため、割り込み番号を直接記述する場合は、どのピンがどの割り込み番号に対応しているのかを確認する必要があります。
しかし、「digitalPinToInterrupt()」を利用すれば、ボードの種類に関係なく、ピン番号で指定することができるので、基本的には「digitalPinToInterrupt()」を使うことをおすすめします。
次に、「実行したい関数」についてです。
ここで指定する関数は、値を返さない「void型」で、さらに引数を持たないことが条件になります。
また、指定した関数の中に割り込み処理を利用するような関数を入れていた場合、うまく機能することができない場合があります。
例えば「delay()」は割り込み処理を利用した関数であるため、指定した関数の中に入れていても、うまく機能することができません。
また、今回「Serial.println()」を実行させるようにしていますが、「void loop()」内でシリアルモニタへ表示させる処理を行っていた場合、シリアルモニタへ表示させる処理が競合してしまい表示がうまくいかないことがあるので、「void loop()」と「割り込み処理」で同時に使用されないように注意が必要になります。
最後に「割り込みの発生条件」についてです。
「Arduino Uno R3」で利用できる発生条件は以下の4つです。
記述 | 条件 |
LOW | ピンの電圧がLOWのとき |
CHANGE | ピンの電圧が変化したとき |
RISING | ピンの電圧がLOWからHIGHに変化したとき |
FALLING | ピンの電圧がHIGHからLOWに変化したとき |
ここで指定した条件を、指定したピンが満たした場合、割り込み処理が実行されます。
上記の説明をまとめると、スケッチの13行目の文は、2番ピンの電圧が「HIGH」から「LOW」に変わった瞬間に、「Pushed()」という関数を実行するように設定しているということになります。
ここで、スケッチ全体の流れをまとめると以下のようになります。
「void loop()」では、「Pushed_Flag」の「true」と「false」を1秒ごとに切り替える処理が行われています。
ここで、スイッチが押された瞬間、2番ピンの電圧が「HIGH」から「LOW」に変わるので、「Pushed_Flag」の値を表示させる割り込み処理が発生します。
よって、スイッチが押された瞬間に「0」か「1」が表示されるという処理になっていたということになります。
少し話は変わりますが、上記のスケッチの動作確認で時々、1回しかスイッチを押していないのに、処理が何度も実行されることがあります。
これは、スイッチが押し込まれたときの振動でスイッチのONとOFFが切り替わる、チャタリングという現象が発生していることが原因です。
スイッチの判定を正確に行うには、別途対策を行う必要がありますが、今回の記事では、チャタリング対策なしで、割り込み処理の説明をさせていただきます。
volatileの説明
スケッチを「Arduino」に書き込む際には、スケッチを機械の言葉に翻訳する「コンパイル」というものが行われており、その「コンパイル」を行うプログラムのことを「コンパイラ」と呼びます。
「コンパイラ」は私たちが書いたスケッチを「コンパイル」する際に、無駄な処理を省いたり、順番を少し入れ替えたりして、より速く処理が行われるように最適化を行うことがあります。
最適化は、処理を速くしてくれるので、基本的には良いことなのですが、割り込み処理を利用する場合、最適化が悪影響を及ぼすことがあります。
先程のスケッチの2行目の「volatile」をなくした以下のようなスケッチを書き込んで、スイッチを何度か押してみてください。
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 |
//void loop()内で変化するbool型の変数Pushed_Flagを用意 bool Pushed_Flag=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //1秒置きにPushed_Flagの中身を切り替える Pushed_Flag=false; delay(1000); Pushed_Flag=true; delay(1000); } void Pushed(){ //Pushed_Flagの中身をシリアルモニタに表示(true:1,false:0) Serial.println(Pushed_Flag); } |
先程のスケッチと異なり、スイッチを何度押しても、「1」という数字のみが出力されてしまいます。
上記のように、割り込みで実行する関数の中と外で共通の変数(上記の場合「Pushed_Flag」)を使用していた場合、最適化により、変数が思っていたように変化しておらず、こちらが意図していないような処理が行われることがあります。
そのような場合は、共通で使用する変数の型の前に「volatile」と記述することにより、解決することができます。
「volatile」は型修飾子と呼ばれているものの1つです。
型修飾子は変数の扱い方を、「コンパイラ」に詳しく伝える役割を持っており、「volatile」は、変数の内容が複数の関数からアクセスされ、値が常に変化するような変数であるということを「コンパイラ」に伝えています。
すると、「コンパイラ」は、その変数に対しての最適化を抑制して、変化に応じた新しい値をそれぞれの関数で扱うことができるようにコンパイルを行います。
「volatile」を付けていなくても、こちらが意図したように割り込み処理を行うこともありますが、上記のように、コンパイルの最適化で、こちらが意図していないような割り込み処理になってしまうこともあるため、割り込みで実行する関数の中と外で共通の変数を使用する場合は、「volatile」を付けることをおすすめします。
volatileの注意点
「volatile」を付けた変数を利用すれば、変化に応じた新しい値を扱うことができるという説明をしましたが、その場合、変化の途中で割り込み処理が起こり、値がおかしくなる可能性があることに注意する必要があります。
以下のスケッチを書き込んで、スイッチを何度もすばやく連打してみてください。
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 |
//int型の変数valueを用意 volatile int value; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //valueに1833を入れる value=1833; //5[μs]待機 delayMicroseconds(5); //valueが1833でも4848でもないとき、シリアルモニタに値を表示させる if(value!=1833&&value!=4848){ Serial.println(value); } } void Pushed(){ //valueに4848を入れる value=4848; } |
すると、時々「4649」という数字が表示されるのが確認できると思います。
(ごく稀にですが、「4848」と表示される場合もあります。)
スケッチの解説
スケッチ全体の流れは以下のようになっています。
ここで、シリアルモニタに数字を表示させる条件を見ると、「value」の値が「1833」でも「4848」でもないときとなっています。
「value」にはループ処理内で「1833」、割り込み処理内で「4848」を入れていますが、それ以外の数字を入れるような処理は行っていません。
よって、シリアルモニタに表示させる処理は行われないと考えられますが、実際には「4649」という数字が出力されてしまっています。
その理由について、順を追って説明したいと思います。
まず、「1833」、「4848」、「4649」をそれぞれ2進数で表すと以下のようになります。
この2進数は、数値を「0」と「1」の2種類の数字で表したものです。
私たちが普段使うのは10進数と呼ばれるもので、「0」から「9」の10種類の数字を使って、数値を表し、「9」の次は桁が上がって「10」となります。
2進数の場合は、「1」の次に桁が上がり、「10」という表記になります。
ここで、10進数と2進数の関係を図にまとめると以下のようになります。
コンピュータは処理を行う際、2進数を使っていて、1桁分のデータを1ビット([bit])と呼びます。
1回の処理で扱えるビット数はマイコンごとに決まっており、「Arduino Uno R3」が1回の処理で扱えるのは8ビットとなっています。
そして、「Arduino Uno R3」の場合、「int型」のデータはで16ビットとして扱われます。
つまり、「int型」のデータは2回に分けて、読み書きがおこなわれているということになります。
ここで、運悪く「value=1833」の途中で、割り込み処理が発生した場合、以下のような流れで処理が行われます。
よって、「4649」という数字が出力されることがあるということになります。
ちなみに、ごく稀に「4848」が表示されるのは、「value=4649」で条件が満たされた後に、割り込み処理で「value=4848」になる可能性があるからです。
割り込み禁止の使い方
上記のように、割り込み処理で8ビット以上の変数(「int型」や「float型」など)を共有して扱う場合は、データを入れる前と後で、割り込み処理の禁止と許可をすると、安全にデータのやり取りを行うことができます。
また、「if文」の条件が正しいか判断しているときや、条件が満たされた後に、割り込みが発生する可能性はあるので必要に応じて割り込み禁止と許可をします。
以下のスケッチを書き込んでスイッチを何度か押してみてください。
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 |
//int型の変数valueを用意 volatile int value; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //割り込み処理を禁止する noInterrupts(); //valueに1833を入れる value=1833; //割り込み処理を許可する interrupts(); //5[μs]待機 delayMicroseconds(5); //割り込み処理を禁止する noInterrupts(); //valueが1833でも4848でもないとき、シリアルモニタに値を表示させる if(value!=1833&&value!=4848){ Serial.println(value); } //割り込み処理を許可する interrupts(); } void Pushed(){ //valueに4848を入れる value=4848; } |
今度は、スイッチを押しても何も表示されないのが確認できると思います。
スケッチの解説
今回追加したのは、19、39行目の「noInterrupts()」と25、31行目の「interrupts()」という関数です。
「noInterrupts()」を実行すると、すべての割り込み処理が禁止されます。
そして、「interrupts()」を実行すると、割り込み処理が再び行えるようになります。
上記のように、割り込み処理で8ビット以上の変数を扱う場合や、割り込みが入っては困るような大事な処理がある場合は、「noInterrupts()」と「interrupts()」を使うことをおすすめします。
上記のif文の場合、条件が正しいか判断しているときに値が変化したとしても、条件が満たされることは無いと思われますが、if文の「1833」と「4848」を入れ替えた場合は、条件が満たされる場合があるので、割り込み禁止と許可を追加しています。
割り込み禁止中の判定
割り込み禁止中も、外部割り込みの判定は行われているので、注意が必要です。
以下のスケッチを書き込んでください。
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 |
//bool型の変数Pushed_Flagを用意 volatile bool Pushed_Flag=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //割り込みを許可する interrupts(); //左にInterrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=1000;i++){ Serial.print("Interrupt: "); Serial.println(Pushed_Flag); } //割り込み処理を禁止する noInterrupts(); //左にNo_Interrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=1000;i++){ Serial.print("No_Interrupt: "); Serial.println(Pushed_Flag); } } void Pushed(){ //Pushed_Flagのtrueとfalseを反対にする Pushed_Flag=!Pushed_Flag; } |
シリアルモニタの左側に「Interrupt: 」と表示されている時に、スイッチを押すと、右側の数字が変化するのが確認できます。
一方、シリアルモニタの左側に「No_Interrupt: 」と表示されている時に、スイッチを押しても、右側の数字は変化しないことが確認できます。
スケッチの解説
上記のスケッチは、左側に割り込みが許可されているかを、右側にbool型の変数の値を表示させ続ける処理を行っており、スイッチを押して割り込み処理が行われると、変数の値が切り替わるという仕組みになっています。
スケッチ全体の流れは以下の図のようになります。
ここで、割り込みを禁止している際に、スイッチを押したときの処理に注目してください。
割り込み禁止の状態で、スイッチを押しても、数字の変化はありませんが、割り込みが許可された瞬間に遅れて数字が変化することが確認できます。
これは、割り込みを禁止にしていても、外部割り込みの判定が行われていて、割り込みが許可された瞬間に処理が実行されることを意味しています。
上記のように、割り込み禁止中に外部割り込みの条件を満たした場合、回数はカウントされませんが、割り込みを許可したタイミングで、処理が実行されるので注意が必要となります。
イメージとしては以下のような感じです。
外部割り込み禁止の使い方
「noInterrupts()」で、割り込み禁止を行った場合、すべての割り込み処理が行えなくなります。
以下のスケッチを書き込んでください。
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 |
//bool型の変数Pushed_Flagを用意 volatile bool Pushed_Flag=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //割り込みを許可する interrupts(); //左にInterrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=10;i++){ Serial.print("Interrupt: "); Serial.println(Pushed_Flag); //1秒間待機する処理を追加 delay(1000); } //割り込み処理を禁止する noInterrupts(); //左にNo_Interrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=10;i++){ Serial.print("No_Interrupt: "); Serial.println(Pushed_Flag); //1秒間待機する処理を追加 delay(1000); } } void Pushed(){ //Pushed_Flagのtrueとfalseを反対にする Pushed_Flag=!Pushed_Flag; } |
上記のスケッチは、シリアルモニタへの出力の後に、「delay()」を使っていますが、割り込み禁止中の「delay()」は機能しておらず、「No_Interrupt: ~」の部分だけ、一気に出力されるのが確認できます。
外部割込みの処理は禁止にしたいけれど、他の割り込み処理は許可したいという場合は、「noInterrupts()」の代わりに「detachInterrupt()」という関数を利用します。
以下のスケッチを書き込んでください。
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 |
//bool型の変数Pushed_Flagを用意 volatile bool Pushed_Flag=false; void setup() { //シリアル通信の初期化処理 Serial.begin(9600); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); } void loop() { //2番ピンの外部割り込みを許可する(外部割り込みの再設定) attachInterrupt(digitalPinToInterrupt(2),Pushed,FALLING); //左にInterrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=10;i++){ Serial.print("Interrupt: "); Serial.println(Pushed_Flag); //1秒間待機する処理を追加 delay(1000); } //2番ピンの外部割り込み処理を禁止する(割り込みが発生したときに実行する関数の登録を解除) detachInterrupt(digitalPinToInterrupt(2)); //左にNo_Interrupt: 右にPushed_Flagの状態を表示させる処理を1000回行う for(int i=1;i<=10;i++){ Serial.print("No_Interrupt: "); Serial.println(Pushed_Flag); //1秒間待機する処理を追加 delay(1000); } } void Pushed(){ //Pushed_Flagのtrueとfalseを反対にする Pushed_Flag=!Pushed_Flag; } |
先ほどのスケッチと異なり、「delay()」が正常に処理されているのが確認できると思います。
スケッチの解説
「noInterrupts()」の代わりに以下の文により、割り込みを禁止にしています。
31 |
detachInterrupt(digitalPinToInterrupt(2)); |
上記の「detachInterrupt()」は指定した割り込み番号に、対応する割り込み処理を禁止して、実行する関数の登録を解除するという文です。
指定するのは割り込み番号であるため、「attachInterrupt()」と同様に「digitalPinToInterrupt()」を使って、対応するピンの指定を行っています。
割り込みを許可するときは、実行する関数の登録を解除されてしまっているため、「attachInterrupt()」を使い、外部割り込みを再設定して割り込みを行えるようにします。
「noInterrupts()」ときと同様、割り込み禁止中も、外部割り込みの判定は行われているので注意が必要です。
外部割り込みについての説明は以上となります。
LEDを使ったゲームを作る(後編)
前回の記事に引き続き、LEDを使ったゲームの作り方を説明していきたいと思います。
ステージを作る
前回の記事では、8つのパターンで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 |
//点灯の長さを決める変数[ms] int Beat_time=125; //現在のステージを入れる変数 int Stage=1; //ステージの数を入れる変数 int Last_Stage=8; //クリアの判定を行う変数 bool Clear_Flag=false; void setup() { //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //ブザーに繋いだピン(12)を出力ピンとして設定する pinMode(12,OUTPUT); } void loop() { //関数を呼び出しLEDを点灯させる //※LED_Beat_Rの引数の合計は12以下にすること(引数の合計が12より大きい場合右端のLEDが点滅します) if(Stage==1){ LED_Beat_R(3,3,1); Clear_Flag=true; //Clear_Flagをtrueにして次のステージへ進ませる }else if(Stage==2){ LED_Beat_R(1,1,3); }else if(Stage==3){ LED_Beat_R(3,1,1); }else if(Stage==4){ LED_Beat_R(1,5,1); }else if(Stage==5){ LED_Beat_R(5,3,3); }else if(Stage==6){ LED_Beat_R(1,5,3); }else if(Stage==7){ LED_Beat_R(0,5,3); }else if(Stage==8){ LED_Beat_R(3,3,5); } //クリアの判定を行う Judge(); } //**********指定したピンのLEDをBeat_timeの時間点灯させる関数(ブザーで音を鳴らす処理あり)********** void LED_HIGH(int a){ digitalWrite(a,HIGH); tone(12,392,Beat_time); //ブザーで音を鳴らす処理を追加 delay(Beat_time); digitalWrite(a,LOW); } //**********11→8、7→4番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(右向き)********** void LED_Beat_R(int delay_1,int delay_2,int delay_3){ //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は右端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=11;j>=7;j=j-4){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-3); //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } }else{ //右端のLEDを点滅させる digitalWrite(4,HIGH); delay(500); digitalWrite(4,LOW); delay(500); } } //**********クリアの判定をする関数********** void Judge(){ //Clear_Flagがtrueならクリアの音を鳴らして次のステージへ、それ以外は失敗の音を鳴らす if(Clear_Flag){ tone(12,988,50); delay(100); tone(12,784,150); Stage++; //最後のステージをクリアしたら最初のステージに戻す if(Stage>Last_Stage){ Stage=1; } //Clear_Flagをfalseに戻す Clear_Flag=false; }else{ tone(12,165,50); delay(100); tone(12,165,150); } delay(900); } |
上記のスケッチを書き込むと、1回目のパターンでLEDが点灯した後、クリアしたことを知らせる音が鳴るのが確認できます。
また、2回目のパターンでLEDが点灯した後、失敗を知らせる音が鳴り、その後は同じパターンを繰り返し続けるのが確認できます。
スケッチの解説
今回のスケッチでは3つの変数を追加し、ステージとステージクリアの管理を行うようにしています。
まずは、「Stage」という変数について説明したいと思います。
この変数は、現在どのステージなのかを確認するために使用します。
「void loop()」ではこの変数と「if文」を組み合わせることにより、ステージごとに異なるパターンでLEDを点灯させています。
続いて、「Clear_Flag」という変数について説明します。
この「Clear_Flag」はステージをクリアにするかどうかを判断するために使用しており、クリアにしたいときは「true」それ以外のときは「false」になるようにしています。
新しく作成した「Judge()」という関数では、「Clear_Flag」が「true」の場合、クリアを知らせる音を鳴らした後「Stage」の値を「1」増やし、最後に「Clear_Flag」を「false」にします。
一方、「Clear_Flag」が「true」以外の場合、失敗を知らせる音を鳴らし、「Stage」の値は変更しません。
そうすることにより、ステージをクリアすると次のステージに進むような処理になります。
今回は、スイッチの判定を行う処理を追加していないため、30行目のように、「Clear_Flag=true」と記述し、強制的にステージをクリアさせています。
最後に、「Last_Stage」という関数についてです。
この変数は、現在のステージが最後のステージであるかを判別するために使用しており、ステージの数を最初に入れています。
なぜ、最後のステージであるかを判別しているかというと、最後のステージをクリアした場合、次のステージがないため、最初のステージに戻す必要があるからです。
よって、111~113行目で、最後のステージをクリアした場合、最初のステージに戻すような処理を行っています。
今回のスケッチの処理の流れをざっくりまとめると以下のようになります。
スイッチの判定を追加する
上記のスケッチでは、1番目のパターンでLEDを点灯させた後、「Clear_Flag」を「true」にするような文を書くことにより、次のステージへ進ませました。
今度は、右側のスイッチが押されることで、「Clear_Flag」を「true」にするようなスケッチに変更したいと思います。
以下のスケッチを書き込んでください。
黄色でマークされた部分が変更または追加した部分です。
先程のスケッチの30行目で記述していた「Clear_Flag=true;」は削除してください。
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 |
//点灯の長さを決める変数[ms] int Beat_time=125; //現在のステージを入れる変数 int Stage=1; //ステージの数を入れる変数 int Last_Stage=8; //クリアの判定を行う変数 volatile bool Clear_Flag=false; void setup() { //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //ブザーに繋いだピン(12)を出力ピンとして設定する pinMode(12,OUTPUT); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); } void loop() { //関数を呼び出しLEDを点灯させる //※LED_Beat_Rの引数の合計は12以下にすること(引数の合計が12より大きい場合右端のLEDが点滅します) if(Stage==1){ LED_Beat_R(3,3,1); }else if(Stage==2){ LED_Beat_R(1,1,3); }else if(Stage==3){ LED_Beat_R(3,1,1); }else if(Stage==4){ LED_Beat_R(1,5,1); }else if(Stage==5){ LED_Beat_R(5,3,3); }else if(Stage==6){ LED_Beat_R(1,5,3); }else if(Stage==7){ LED_Beat_R(0,5,3); }else if(Stage==8){ LED_Beat_R(3,3,5); } //クリアの判定を行う Judge(); } //**********指定したピンのLEDをBeat_timeの時間点灯させる関数(ブザーで音を鳴らす処理あり)********** void LED_HIGH(int a){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 digitalWrite(a,HIGH); tone(12,392,Beat_time); //ブザーで音を鳴らす処理を追加 delay(Beat_time); digitalWrite(a,LOW); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 } //**********11→8、7→4番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(右向き)********** void LED_Beat_R(int delay_1,int delay_2,int delay_3){ //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は右端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=11;j>=7;j=j-4){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-3); //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } }else{ //右端のLEDを点滅させる digitalWrite(4,HIGH); delay(500); digitalWrite(4,LOW); delay(500); } } //**********クリアの判定をする関数********** void Judge(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 //Clear_Flagがtrueならクリアの音を鳴らして次のステージへ、それ以外は失敗の音を鳴らす if(Clear_Flag){ tone(12,988,50); delay(100); tone(12,784,150); Stage++; //最後のステージをクリアしたら最初のステージに戻す if(Stage>Last_Stage){ Stage=1; } //Clear_Flagをfalseに戻す Clear_Flag=false; }else{ tone(12,165,50); delay(100); tone(12,165,150); } delay(900); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Clear_Flagをtrueにする Clear_Flag=true; } |
上記のスケッチを書き込むとスイッチが押されたときだけ、クリアした音が鳴り、次のステージへ進むのが確認できます。
先程のスケッチと異なり、最後のステージまで進めるようになったので、最後のステージをクリアしたときに、最初のステージに戻ることを確認してみてください。
スケッチの解説
今回追加したのは、2番ピンに対しての外部割り込みの処理です。
138行目から142行目で「Pushed_R」という関数を作成しており、「void setup()」の中で、2番ピンに対してプルアップ付きの入力ピンの設定と「Pushed_R」を実行する外部割り込みの設定を行っています。
「Pushed_R」の内容は、「Clear_Flag」を「true」にするという単純な処理のみです。
「Clear_Flag」は割り込みで実行する関数の中と外で、共通の変数として扱うため「volatile」を付けるように変更を加えています。
また、割り込み処理によって、「tone()関数」の波形が乱されるのを防ぐために、「LED_HIGH()」と「Judge()」が実行されている間の外部割り込みを禁止にしています。
今回のスケッチの処理の流れをざっくりまとめると以下のようになります。
判定を細かく設定する
上記のスケッチでは、スイッチを適当なタイミングで押しても、クリアになり、次のステージへ簡単に進ませることができました。
これでは、ゲームとして面白くないため、次は、最後の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 |
//点灯の長さを決める変数[ms] int Beat_time=125; //現在のステージを入れる変数 int Stage=1; //ステージの数を入れる変数 int Last_Stage=8; //クリアの判定を行う変数 volatile bool Clear_Flag=false; //スイッチが押されたかを確認する変数 volatile bool Pushed_Flag=false; //最後のLEDが点灯しているときだけtrueになる変数 volatile bool Last_LED_Flag=false; void setup() { //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //ブザーに繋いだピン(12)を出力ピンとして設定する pinMode(12,OUTPUT); //2番ピンをプルアップ付きの入力ピンとして設定する pinMode(2,INPUT_PULLUP); //2番ピンが押された瞬間に関数Pushed_R()が実行されるように設定する attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); } void loop() { //関数を呼び出しLEDを点灯させる //※LED_Beat_Rの引数の合計は12以下にすること(引数の合計が12より大きい場合右端のLEDが点滅します) if(Stage==1){ LED_Beat_R(3,3,1); }else if(Stage==2){ LED_Beat_R(1,1,3); }else if(Stage==3){ LED_Beat_R(3,1,1); }else if(Stage==4){ LED_Beat_R(1,5,1); }else if(Stage==5){ LED_Beat_R(5,3,3); }else if(Stage==6){ LED_Beat_R(1,5,3); }else if(Stage==7){ LED_Beat_R(0,5,3); }else if(Stage==8){ LED_Beat_R(3,3,5); } //クリアの判定を行う Judge(); } //**********指定したピンのLEDをBeat_timeの時間点灯させる関数(ブザーで音を鳴らす処理あり)********** void LED_HIGH(int a){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 digitalWrite(a,HIGH); tone(12,392,Beat_time); //ブザーで音を鳴らす処理を追加 delay(Beat_time); digitalWrite(a,LOW); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 } //**********11→8、7→4番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(右向き)********** void LED_Beat_R(int delay_1,int delay_2,int delay_3){ //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は右端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=11;j>=7;j=j-4){ //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //最後のLEDが点灯する直前にLast_LED_Flagをtrueにする if(j==7){ Last_LED_Flag=true; } //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-3); //最後のLEDが消灯したらLast_LED_Flagをfalseに戻す if(j==7){ Last_LED_Flag=false; } //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } } }else{ //右端のLEDを点滅させる digitalWrite(4,HIGH); delay(500); digitalWrite(4,LOW); delay(500); } } //**********クリアの判定をする関数********** void Judge(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 //Clear_Flagがtrueならクリアの音を鳴らして次のステージへ、それ以外は失敗の音を鳴らす if(Clear_Flag){ tone(12,988,50); delay(100); tone(12,784,150); Stage++; //最後のステージをクリアしたら最初のステージに戻す if(Stage>Last_Stage){ Stage=1; } //Clear_Flagをfalseに戻す Clear_Flag=false; }else{ tone(12,165,50); delay(100); tone(12,165,150); } delay(900); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 //Pushed_Flagをfalseに戻す Pushed_Flag=false; } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //一回押されたら次から処理を行わないようにする if(!(Pushed_Flag)){ Pushed_Flag=true; //Last_LED_Flagがtrueのときに押されていたらClear_Flagをtrueにする if(Last_LED_Flag){ Clear_Flag=true; } } } |
上記のスケッチを書き込むと、最後のLEDが点灯するタイミングでスイッチを押さないと次のステージへ進めなくなるのが確認できます。
また、スイッチが押されると、LEDの点灯はスキップされることが確認できます。
スケッチの解説
今回のスケッチでは、2つの「bool型」の変数を追加しています。
1つは「Pushed_Flag」という変数で、177行目の文により、スイッチが押されると「true」になり、169行目の文により、ステージクリアの判定が終わると「false」に戻ります。
この変数と「if文」を組み合わせることにより、すでにスイッチが押されている場合、LEDの点灯処理や外部割り込みの処理をスキップさせています。
もう1つは「Last_LED_Flag」という変数です。
この変数は、114~116行目の文により、最後のLEDが点灯する直前に「true」になり、122~124行目の文により、LEDが点灯し終わった後「false」になります。
つまり、「true」になっている期間は、最後のLEDが点灯している間だけとなります。
ここで、「Pushed_R」の中身を見ると、if文により、「Last_LED_Flag」が「true」になっているときだけ「Clear_Flag」を「true」にするような処理を行っています。
よって、最後の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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
//点灯の長さを決める変数[ms] int Beat_time=125; //現在のステージを入れる変数 int Stage=1; //ステージの数を入れる変数 int Last_Stage=8; //クリアの判定を行う変数 volatile bool Clear_Flag=false; //スイッチが押されたかを確認する変数 volatile bool Pushed_Flag=false; //最後のLEDが点灯しているときだけtrueになる変数 volatile bool Last_LED_Flag=false; //右に向かって点灯しているときtrueになる変数 volatile bool Right_Flag=false; //左に向かって点灯しているときtrueになる変数 volatile bool Left_Flag=false; void setup() { //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //ブザーに繋いだピン(12)を出力ピンとして設定する pinMode(12,OUTPUT); //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); } void loop() { //関数を呼び出しLEDを点灯させる //※LED_Beat_Rの引数の合計は12以下にすること(引数の合計が12より大きい場合右端のLEDが点滅します) //※LED_Beat_Lの引数の合計は12以下にすること(引数の合計が12より大きい場合左端のLEDが点滅します) if(Stage==1){ LED_Beat_R(3,3,1); }else if(Stage==2){ LED_Beat_L(1,1,3); }else if(Stage==3){ LED_Beat_R(3,1,1); }else if(Stage==4){ LED_Beat_L(1,5,1); }else if(Stage==5){ LED_Beat_R(5,3,3); }else if(Stage==6){ LED_Beat_L(1,5,3); }else if(Stage==7){ LED_Beat_R(0,5,3); }else if(Stage==8){ LED_Beat_L(3,3,5); } //クリアの判定を行う Judge(); } //**********指定したピンのLEDをBeat_timeの時間点灯させる関数(ブザーで音を鳴らす処理あり)********** void LED_HIGH(int a){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 digitalWrite(a,HIGH); tone(12,392,Beat_time); //ブザーで音を鳴らす処理を追加 delay(Beat_time); digitalWrite(a,LOW); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********11→8、7→4番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(右向き)********** void LED_Beat_R(int delay_1,int delay_2,int delay_3){ //Right_Flagをtrueにする Right_Flag=true; //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は右端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=11;j>=7;j=j-4){ //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //最後のLEDが点灯する直前にLast_LED_Flagをtrueにする if(j==7){ Last_LED_Flag=true; } //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-3); //最後のLEDが消灯したらLast_LED_Flagをfalseに戻す if(j==7){ Last_LED_Flag=false; } //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } } }else{ //右端のLEDを点滅させる digitalWrite(4,HIGH); delay(500); digitalWrite(4,LOW); delay(500); } //Right_Flagをfalseにする Right_Flag=false; } //**********4→7、8→11番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(左向き)********** void LED_Beat_L(int delay_1,int delay_2,int delay_3){ //Left_Flagをtrueにする Left_Flag=true; //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は左端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=4;j<=8;j=j+4){ //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //最後のLEDが点灯する直前にLast_LED_Flagをtrueにする if(j==8){ Last_LED_Flag=true; } //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+3); //最後のLEDが消灯したらLast_LED_Flagをfalseに戻す if(j==8){ Last_LED_Flag=false; } //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } } }else{ //左端のLEDを点滅させる digitalWrite(11,HIGH); delay(500); digitalWrite(11,LOW); delay(500); } //Left_Flagをfalseにする Left_Flag=false; } //**********クリアの判定をする関数********** void Judge(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //Clear_Flagがtrueならクリアの音を鳴らして次のステージへ、それ以外は失敗の音を鳴らす if(Clear_Flag){ tone(12,988,50); delay(100); tone(12,784,150); Stage++; //最後のステージをクリアしたら最初のステージに戻す if(Stage>Last_Stage){ Stage=1; } //Clear_Flagをfalseに戻す Clear_Flag=false; }else{ tone(12,165,50); delay(100); tone(12,165,150); } delay(900); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 //Pushed_Flagをfalseに戻す Pushed_Flag=false; } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Right_Flagがtrueのときだけ処理を実行 if(Right_Flag){ //一回押されたら次から処理を行わないようにする if(!(Pushed_Flag)){ Pushed_Flag=true; //Last_LED_Flagがtrueのときに押されていたらClear_Flagをtrueにする if(Last_LED_Flag){ Clear_Flag=true; } } } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Left_Flagがtrueのときだけ処理を実行 if(Left_Flag){ //一回押されたら次から処理を行わないようにする if(!(Pushed_Flag)){ Pushed_Flag=true; //Last_LED_Flagがtrueのときに押されていたらClear_Flagをtrueにする if(Last_LED_Flag){ Clear_Flag=true; } } } } |
上記のスケッチを書き込むと、左に向かって点灯するパターンが追加されたことが確認できます。
スケッチの解説
今回は左側のスイッチも利用するため、3番ピンに対する割り込みの設定や割り込み禁止の処理を追加しています。
設定のやり方については、2番ピンと同じですが、実行する関数は新たに作成する「Pushed_L()」となっています。
左に向かってLEDを点灯させる関数「LED_Beat_L()」や左のスイッチが押されたときに実行する関数「Pushed_L()」は、「LED_Beat_R()」と「Pushed_R()」をコピーしてから、中身を少し変更して作成しています。
「LED_Beat_L()」については、反対方向にLED点灯させていくため、変数「j」が関係している部分のほとんどが変更になるので注意してください。
「void loop()」では偶数のステージの「LED_Beat_R()」を「LED_Beat_L()」に変更して、ステージをクリアするたびに方向が変わるようにしています。
また、右に向かって点灯しているのか、それとも、左に向かって点灯しているかを判断するために、「Right_Flag」、「Left_Flag」という変数を追加しています。
「Right_Flag」は「LED_Beat_R()」が実行されている間「true」になるようにして、「Left_Flag」は「LED_Beat_L()」が実行されている間「true」になるようにしています。
さらに、「Pushed_R()」は「Right_Flag」が「true」のときのみ中身を実行するようにして、「Pushed_L()」は「Left_Flag」が「true」のときのみ中身を実行するようにしています。
これにより、点灯している方向とは逆のスイッチを押しても反応しないような仕組みとなっています。
ゲームをクリアした際の演出を追加する
上記のスケッチでは、最後のステージをクリアしても、最初のステージに戻るだけで、少し寂しいです。
よって、最後のステージをクリアしたときに、ちょっとした演出を行うように、変更を加えたいと思います。
以下のスケッチを書き込んでください。
黄色でマークされた部分が追加した部分です。
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 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
//点灯の長さを決める変数[ms] int Beat_time=125; //現在のステージを入れる変数 int Stage=1; //ステージの数を入れる変数 int Last_Stage=8; //クリアの判定を行う変数 volatile bool Clear_Flag=false; //スイッチが押されたかを確認する変数 volatile bool Pushed_Flag=false; //最後のLEDが点灯しているときだけtrueになる変数 volatile bool Last_LED_Flag=false; //右に向かって点灯しているときtrueになる変数 volatile bool Right_Flag=false; //左に向かって点灯しているときtrueになる変数 volatile bool Left_Flag=false; void setup() { //LEDに繋いだピン(4~11)を出力ピンとして設定する for(int i=4;i<=11;i++){ pinMode(i,OUTPUT); } //ブザーに繋いだピン(12)を出力ピンとして設定する pinMode(12,OUTPUT); //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); } void loop() { //関数を呼び出しLEDを点灯させる //※LED_Beat_Rの引数の合計は12以下にすること(引数の合計が12より大きい場合右端のLEDが点滅します) //※LED_Beat_Lの引数の合計は12以下にすること(引数の合計が12より大きい場合左端のLEDが点滅します) if(Stage==1){ LED_Beat_R(3,3,1); }else if(Stage==2){ LED_Beat_L(1,1,3); }else if(Stage==3){ LED_Beat_R(3,1,1); }else if(Stage==4){ LED_Beat_L(1,5,1); }else if(Stage==5){ LED_Beat_R(5,3,3); }else if(Stage==6){ LED_Beat_L(1,5,3); }else if(Stage==7){ LED_Beat_R(0,5,3); }else if(Stage==8){ LED_Beat_L(3,3,5); } //クリアの判定を行う Judge(); } //**********指定したピンのLEDをBeat_timeの時間点灯させる関数(ブザーで音を鳴らす処理あり)********** void LED_HIGH(int a){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 digitalWrite(a,HIGH); tone(12,392,Beat_time); //ブザーで音を鳴らす処理を追加 delay(Beat_time); digitalWrite(a,LOW); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 } //**********11→8、7→4番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(右向き)********** void LED_Beat_R(int delay_1,int delay_2,int delay_3){ //Right_Flagをtrueにする Right_Flag=true; //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は右端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=11;j>=7;j=j-4){ //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //最後のLEDが点灯する直前にLast_LED_Flagをtrueにする if(j==7){ Last_LED_Flag=true; } //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j-3); //最後のLEDが消灯したらLast_LED_Flagをfalseに戻す if(j==7){ Last_LED_Flag=false; } //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } } }else{ //右端のLEDを点滅させる digitalWrite(4,HIGH); delay(500); digitalWrite(4,LOW); delay(500); } //Right_Flagをfalseにする Right_Flag=false; } //**********4→7、8→11番ピンに繋いだLEDを同じリズムで順番に点灯させる関数(左向き)********** void LED_Beat_L(int delay_1,int delay_2,int delay_3){ //Left_Flagをtrueにする Left_Flag=true; //引数の合計が12以下の場合、リズムに合わせてLEDを点灯,それ以外は左端のLEDが点滅 if(delay_1+delay_2+delay_3<=12){ for(int j=4;j<=8;j=j+4){ //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j); //点灯の長さ×delay_1 待機 delay(Beat_time*delay_1); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+1); //点灯の長さ×delay_2 待機 delay(Beat_time*delay_2); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+2); //点灯の長さ×delay_3 待機 delay(Beat_time*delay_3); } //スイッチがすでに押されていた場合、処理をスキップする if(!Pushed_Flag){ //最後のLEDが点灯する直前にLast_LED_Flagをtrueにする if(j==8){ Last_LED_Flag=true; } //指定したピンのLEDをBeat_timeの時間点灯させる関数を呼び出す LED_HIGH(j+3); //最後のLEDが消灯したらLast_LED_Flagをfalseに戻す if(j==8){ Last_LED_Flag=false; } //点灯の長さ×(12-(delay_1+delay_2+delay_3)) 待機 delay(Beat_time*(12-(delay_1+delay_2+delay_3))); } } }else{ //左端のLEDを点滅させる digitalWrite(11,HIGH); delay(500); digitalWrite(11,LOW); delay(500); } //Left_Flagをfalseにする Left_Flag=false; } //**********クリアの判定をする関数********** void Judge(){ detachInterrupt(digitalPinToInterrupt(2)); //2番ピンの外部割り込みを禁止 detachInterrupt(digitalPinToInterrupt(3)); //3番ピンの外部割り込みを禁止 //Clear_Flagがtrueならクリアの音を鳴らして次のステージへ、それ以外は失敗の音を鳴らす if(Clear_Flag){ tone(12,988,50); delay(100); tone(12,784,150); Stage++; //最後のステージをクリアしたらゲームクリアの演出をして最初のステージに戻す if(Stage>Last_Stage){ Game_Clear(); //ゲームクリアの演出 Stage=1; } //Clear_Flagをfalseに戻す Clear_Flag=false; }else{ tone(12,165,50); delay(100); tone(12,165,150); } delay(900); attachInterrupt(digitalPinToInterrupt(2),Pushed_R,FALLING); //2番ピンの外部割り込みを許可 attachInterrupt(digitalPinToInterrupt(3),Pushed_L,FALLING); //3番ピンの外部割り込みを許可 //Pushed_Flagをfalseに戻す Pushed_Flag=false; } //**********右のスイッチが押されたときに実行する関数********** void Pushed_R(){ //Right_Flagがtrueのときだけ処理を実行 if(Right_Flag){ //一回押されたら次から処理を行わないようにする if(!(Pushed_Flag)){ Pushed_Flag=true; //Last_LED_Flagがtrueのときに押されていたらClear_Flagをtrueにする if(Last_LED_Flag){ Clear_Flag=true; } } } } //**********左のスイッチが押されたときに実行する関数********** void Pushed_L(){ //Left_Flagがtrueのときだけ処理を実行 if(Left_Flag){ //一回押されたら次から処理を行わないようにする if(!(Pushed_Flag)){ Pushed_Flag=true; //Last_LED_Flagがtrueのときに押されていたらClear_Flagをtrueにする if(Last_LED_Flag){ Clear_Flag=true; } } } } //**********ゲームクリアの演出をする関数********** void Game_Clear(){ //1秒待機 delay(1000); //クリアしたっぽい音を鳴らす tone(12,415,125); delay(375); tone(12,415,125); delay(375); tone(12,415,250); delay(375); tone(12,466,125); delay(375); tone(12,415,125); delay(375); tone(12,466,125); delay(375); tone(12,523,1750); //偶数番のLEDと奇数番のLEDを交互に5回点滅させる for(int j=1;j<=5;j++){ //偶数番のLEDを点灯 for(int i=4;i<=11;i=i+2){ digitalWrite(i,HIGH); } //奇数番のLEDを消灯 for(int i=5;i<=11;i=i+2){ digitalWrite(i,LOW); } //0.25秒待機 delay(250); //偶数番のLEDを消灯 for(int i=4;i<=11;i=i+2){ digitalWrite(i,LOW); } //奇数番のLEDを点灯 for(int i=5;i<=11;i=i+2){ digitalWrite(i,HIGH); } //0.25秒待機 delay(250); } //奇数番のLEDを消灯 for(int i=5;i<=11;i=i+2){ digitalWrite(i,LOW); } } |
上記のスケッチを書き込んで、最後のステージをクリアするとゲームをクリアしたことを伝える演出が行われることが確認できます。
スケッチの解説
「Game_Clear()」という関数にゲームクリアを知らせる演出についての処理をまとめておき、250行目で関数を呼び出しています。
これにより、最後のステージがクリアされたときに、演出を行ってから、最初のステージに戻る処理が行われています。
ゲームクリアを知らせる演出は、ブザーでそれらしい音を鳴らした後に、偶数番のLEDと奇数番のLEDを交互に点滅させたものとなっています。
余裕のある方は、自分なりの演出を考えてみてください。
ゲームクリアの演出をすぐに確認したい場合は、「Stage」の変数宣言を行っている5行目の文の「1」という数字を、「Last_Stage」の値(上記のスケッチの場合「8」)と同じものに変更すると、最後の1回をクリアするだけで、演出を確認できるようになります。
終わりに
2回の記事に分けて説明した「右往左往ゲーム」の製作はいかがだったでしょうか。
楽しく関数や割り込みの使い方を学んで貰えていれば幸いです。
今回制作したゲームで何か物足りないと感じた方は、スケッチに変更を加えて、色々と改造をしてみてください。
そうすることで、もっと楽しく学ぶことができるはずです。
次回の記事では、タイマー割り込みについて説明した後、今回作ったゲームの回路を再利用した「8分タイマー」の制作について説明したいと思います。
本記事はここまでです。ご清覧ありがとうございました。
広告
楽天モーションウィジェットとGoogleアドセンス広告です。
気になる商品がございましたら、チェックをしてみてください。