第37回 #include
第37回 #include 2025 6/20 Arduino入門(基礎1)今回はスケッチを整理します。
目次スケッチ、長くないですか?
前回の記事で、アラーム音をメロディーに変えたのはいいですが、スケッチがなんだかずいぶん長くなってしまいましたよね。
特に最初の#defineの部分が長くなっています。肝心のスケッチ本体であるsetupやloopがずいぶんと下の方に行ってしまい、スケッチを開くとしばらく#defineの行が続いて、なんとも見づらいスケッチになってしまった、という状況です。
今回の記事では、長い#defineの部分をなんとかしてみましょう。
ヘッダ
前回の記事で作成したスケッチを見てみます。
スケッチの構成を見ると、最初にコメント部分、次に#define部分、そのあとやっとsetupとloopがでてきます。
Arduinoのスケッチに限らず、プログラム本体が始まるまでの部分を「ヘッダ」と呼ぶことがあります。次のスケッチではsetupが始まるまでの部分がヘッダになります。
それにしても、前回追加した各音程の周波数定義をする#define部分はなんだか無駄に長いですよね。
よくよく考えてみると、この周波数定義の部分は、ちょっと特殊な感じがします。
具体的には、
- 音程によって周波数が決まっているわけだから、値を変更することはないし、
- この先、他にもメロディーを演奏するスケッチを作った時に、この部分は流用できるし、
- 正直なところ邪魔なので、どこか目につかないところで定義してほしい
って感じです。
例えば、ヘッダのはじめの方にある#define BYOU_LED 12は、青色LEDの接続端子番号を定義しています。将来、青色LEDの接続端子を変更した場合、この定義を変更すればスケッチ側の対応はすぐに終わります。
#define TIMER_JIKAN 30はタイマー時間を定義しています。この数値を変更すれば、簡単にタイマー時間を変更できるので便利です。
このように定義している#defineは参照したり変更したりする可能性が高いので、スケッチのヘッダ部分に書いてある方が便利です。
でも各音程の周波数を定義する#defineは変更することはないですよね。さらに、他のスケッチでも使うかもしれません。
C++言語では、このような時のために便利な機能があります。最初に概要を説明します。
ヘッダを別ファイルにする
C++言語では、「プログラムの一部分を別ファイルにする」という仕組みがあります。
言葉ではわかりづらいので、スケッチの例で確認しましょう。
前回作成したスケッチの「各音程の周波数定義部分」を、いったん次のようにスケッチとは別のファイルとして作成しておきます。
元のスケッチでは、「各音程の周波数定義部分」がなくなりますが、次のように元のスケッチに「別ファイルの内容を読み込んでください」とい指示を書きます。
このようにすると、あたかも別ファイルの内容が元のスケッチに書いてあったように扱ってくれる仕組みがあるんです。
それでは、具体的にスケッチの書き方を確認しましょう!
#include
先ほどのようにヘッダの部分をファイル化したものを「ヘッダファイル」と呼ぶことがあります。
スケッチでヘッダファイルを取り込んでもらう場合、次のように#include(インクルード)を使います。
「include」は日本語で、「含める」「取り込む」などの意味ですので、イメージはつきやすいかな、と思います。
#includeの使い方について、「ファイル名の付け方」と「❶と❷の違い」について説明します。
ファイル名の付け方ファイル名の付け方は絶対的なルールはありませんが、慣習があります。
#includeで読み込みファイルは「ヘッダファイル」(header file)と呼ばれることがあるので、ファイル名の拡張子はheaderを意味する「h」を使います。
例えば、各音程の周波数を定義する#define部分をファイルにした場合、「onkai.h」などのように、拡張子を「h」にしたファイル名にします。
❶と❷の違いちょっとややこしいのですが、ファイル名の指定は2通りの書き方があります。
❶ “ファイル名”ファイル名を"で囲んだ場合、Arduino IDEはそのファイルを、スケッチと同じフォルダから探しにいきます。
もしスケッチと同じフォルダで見つからない場合、Arduino IDEのシステムのフォルダを探しにいきます。
なお、ヘッダファイルがスケッチとは違うフォルダにある場合、フォルダパスも含めて指定することができます。
❷ <ファイル名>Arduino IDEは最初からシステムのフォルダを探しにいきます。
この説明でちょっと?のところがありますよね。
探す場所として、「スケッチと同じフォルダ」は良いとして「Arduino IDEのシステムのフォルダ」というのはちょっと謎です。これについて具体例で補足しておきます。
この入門シリーズで使用しているArduino MicroはUSBキーボードとして動作するようにスケッチを作成することができます。
USBキーボードのスケッチを作成するときに、スケッチが簡単に書けるように、Arduino IDEでは、「USBキーボードのスケッチ作成用のヘッダファイル」をシステム側で用意してくれているんです。
この「USBキーボードのスケッチ作成用のヘッダファイル」は、Arduino IDE側のシステムのフォルダに保存されています。
「Arduino IDEのシステムのフォルダ」には、他にもいろいろなヘッダファイルが保存されている場所です。
ややこしいですが、❶と❷の違いは「スケッチと同じフォルダ」を探しに行くかどうか、という違いだけです。
自分で作成したヘッダファイルをスケッチと同じフォルダに保存して読み込む場合、「❶ “ファイル名」の書き方にする、という点がポイントです。
「❷ <ファイル名>」の場合、スケッチと同じフォルダは探してくれませんので、自分で作成したファイルを見つけてくれません。(スケッチのすぐそばにあるのに…)
でも、どちらの書き方でも「Arduino IDEのシステムのフォルダ」にあるフォルダは探しに行くわけなので、全部「❶ “ファイル名”」の書き方でもいいですよね。
ただ慣習としては、自分で作成したヘッダファイルは「❶ “ファイル名”」、Arduino IDEのシステムのフォルダのヘッダファイルは「❷ <ファイル名>」の書き方がされています。
それでは、スケッチの各音程の周波数定義の部分を別ファイルにして#includeでスケッチに読み込むように変更てみましょう!
周波数定義部分の別ファイル化
それでは各音程の周波数定義部分を別ファイル化してスケッチに読み込むようにしてみます。
現在のスケッチに、新しくファイルを追加する場合は、次のようにウインドウ右上の「・・・」をクリックします。
次のようにメニューが表示されますので、一番上の「新しいタブ」を選択します。
ファイル名を入力するダイアログが表示されますので、新規作成するファイル名「onkai.h」を入力、OKボタンをクリックします。
このタブ名のところに「onkai.h」と入力してOKボタンをクリックすると、新しくonkai.hというファイルが作成されます。
ウィンドウが真っ白になってしまいますので、「スケッチが消えたか?」と思ってしまうかもしれませんね。
Arduino IDEでは複数ファイルがある場合、次のようにタブをクリックして表示するファイルを選択するようになっています。次のウィンドウは新規作成した「onkai.h」を表示している状態です。
なお、ファイル名を間違ってしまった場合は、先ほどの「・・・」メニューの中に「名前の変更…」という項目がありますので、それでファイル名を修正します。
これで新規ファイルが作成できましたので、定義部分を新しいファイルに移動しましょう!
元の「kitchen_timer.ino」のスケッチから、各音程の周波数定義部分をカットして、新しく作成した「onkai.h」にコピーしましょう。(元の「kitchen_timer」にある音階定義の部分は必要ありませんので、「コピペ」ではなく「カット➡︎ペースト」するようにしてください)
この状態ですとまだ内容が保存されていませんので、ファイルメニューから「save」(保存)を選択して保存します。
これで周波数定義部分を別ファイル化できました。
次は、元のkitchen_timerのスケッチで、onkai.hを#includeで読み込む必要があります。
#includeで読み込む場合、スケッチの元の場所に書きたくなりますが、一般的にはスケッチの先頭で書く慣習がありますので、それに合わせたいと思います。(#defineは使用前であればどこで定義しても同じですので、先頭で#includeしても問題ありません)
これでスケッチ完成です!
完成したスケッチは次の2つになります。
「kitchen_timer.ino」のスケッチ /* キッチンタイマー 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー 変更履歴: 2024.11.25: 新規作成 2024.12.01: スイッチが押されたらLED点滅開始 2024.12.02: スイッチ関連の#define追加 2024.12.05: 点滅回数カウント追加 2024.12.06: 繰り返し処理をforに変更 2024.12.13: アラーム音追加・動作開始時とタイマー時間の時に青色LEDを点灯 2024.12.15: 残り時間のLED制御を追加 2024.12.17: 残り時間が少なくなったら音も鳴らす制御を追加 2024.12.17: アラーム音をメロディーに変更 2024.12.22: 周波数定義部分をファイル化 */ // 各音程の周波数定義読み込み #include "onkai.h" // 秒を表現するLED関連の定義 #define BYOU_LED 12 // 秒を表現するLEDの接続端子 #define BYOU_ON 50 // 秒を表現するLEDを点灯している時間(単位:ミリ秒) #define BYOU_OFF (1000 - BYOU_ON) // 秒を表現するLEDを消している時間(単位:ミリ秒) // 残り時間を表現するLED関連(緑色、黄色、赤色LED) #define LED_MIDORI 8 // 緑色LEDの端子番号 #define LED_KIIRO 6 // 黄色LEDの端子番号 #define LED_AKA 4 // 赤色LEDの端子番号 // タイマースタートスイッチ関連の定義 #define SWITCH A5 // スイッチを接続している端子名 #define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値 #define SWITCH_ON 0 // スイッチONの時のdigitalReadの値 // タイマー時間設定(LEDの点滅回数) #define TIMER_JIKAN 30 // 残り時間を表現するLEDの制御時間 #define KIIRO_JIKAN 10 // 残り時間3分の2 #define AKA_JIKAN 20 // 残り時間3分の1 // アラーム音関連 #define SPEAKER A0 // スピーカーの端子番号 #define ALARM 880 // アラーム音の音程 void setup() { // 端子の設定 // スピーカー接続端子はtoneの指示で自動的に出力設定になるので設定していません pinMode(BYOU_LED, OUTPUT); // 秒表現のLED接続端子の出力設定 pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子をプルアップ設定 pinMode(LED_MIDORI, OUTPUT); // 緑色LED接続端子設定 pinMode(LED_KIIRO, OUTPUT); // 緑色LED接続端子設定 pinMode(LED_AKA, OUTPUT); // 緑色LED接続端子設定 // 秒のLED(青色LED)を点灯する digitalWrite(BYOU_LED, HIGH); // スイッチが押されるまで何もしないで待つ while( digitalRead(SWITCH) == SWITCH_OFF ) { } // タイマー開始時に緑色LEDを点灯 digitalWrite(LED_MIDORI, HIGH); } void loop() { // TIMER_JIKAN分の回数を数える for(uint8_t count=0; count<TIMER_JIKAN; count++) { // 残り時間が3分の2のとき、緑色をOFF、黄色をONにする if( count == KIIRO_JIKAN ) { digitalWrite(LED_MIDORI, LOW); digitalWrite(LED_KIIRO, HIGH); } // 残り時間が3分の1のとき、黄色をOFF、赤色をONにする if( count == AKA_JIKAN ){ digitalWrite(LED_KIIRO, LOW); digitalWrite(LED_AKA, HIGH); } // 残り時間が5秒以下かそうでないかで処理を変える if( (TIMER_JIKAN-count) <= 5 ) { // 残り時間が5秒以下のときは、1秒に1回青色LEDを点滅すると同時に音を鳴らす digitalWrite(BYOU_LED, HIGH); tone(SPEAKER, ALARM); delay(BYOU_ON); digitalWrite(BYOU_LED, LOW); noTone(SPEAKER); delay(BYOU_OFF); } else { // そうでなければ、1秒に1回青色LEDを点滅する(音は鳴らさない) digitalWrite(BYOU_LED, HIGH); delay(BYOU_ON); digitalWrite(BYOU_LED, LOW); delay(BYOU_OFF); } } // 時間になったので秒のLED(青色LED)を点灯する digitalWrite(BYOU_LED, HIGH); // メロディーを3回演奏する for(uint8_t count=0; count<3; count++) { // ド tone(SPEAKER, DO_4); delay(200); // ミ tone(SPEAKER, MI_4); delay(200); // ソ tone(SPEAKER, SO_4); delay(200); // ミ tone(SPEAKER, MI_4); delay(200); // ド tone(SPEAKER, DO_4); delay(200); // ミ tone(SPEAKER, MI_4); delay(200); // ソ tone(SPEAKER, SO_4); delay(200); // ミ tone(SPEAKER, MI_4); delay(200); // ド tone(SPEAKER, DO_4); delay(200); // ミ tone(SPEAKER, MI_4); delay(200); // ソ tone(SPEAKER, SO_4); delay(200); // シ tone(SPEAKER, SI_4); delay(200); // 1オクターブ高いド tone(SPEAKER, DO_5); delay(200); // 音を消す noTone(SPEAKER); // メロディーを区切るために少し時間待ちをする delay(500); } // 何もしないで待つ while( true ) { } } 「onkai.h」のスケッチ // メロディー演奏用の音の周波数 #define DO_3 131 #define DOS_3 139 #define RE_3 147 #define RES_3 156 #define MI_3 165 #define FA_3 175 #define FAS_3 185 #define SO_3 196 #define SOS_3 208 #define RA_3 220 #define RAS_3 233 #define SI_3 247 #define DO_4 262 #define DOS_4 277 #define RE_4 294 #define RES_4 311 #define MI_4 330 #define FA_4 349 #define FAS_4 370 #define SO_4 392 #define SOS_4 415 #define RA_4 440 #define RAS_4 466 #define SI_4 494 #define DO_5 523 #define DOS_5 554 #define RE_5 587 #define RES_5 622 #define MI_5 659 #define FA_5 698 #define FAS_5 740 #define SO_5 784 #define SOS_5 831 #define RA_5 880 #define RAS_5 932 #define SI_5 988スケッチができたらArduinoボードに送って動作を確認してみましょう!
ここから先は補足になりますので、余力があればお読みください!
【補足】スケッチをフォルダに入れる理由
Arduino IDEでスケッチ保続すると、スケッチファイルが1つだけなのに、わざわざそれをフォルダに入れていますよね。なんとも無駄なことをしているように見えています。
今回の記事で作成したスケッチを開いた状態で、「スケッチ」メニューから「スケッチのフォルダを表示」を選択してみてください。
スケッチが保存されているフォルダが表示されます。
このフォルダの中身を見ると、「kitchen_timer.ino」というスケッチ本体と一緒に、今回の記事で作成したヘッダファイル「onkai.h」が保存されていますよね。
ということで、スケッチをフォルダで保存するのは、スケッチ本体以外にも関連するファイルが複数出てくるためなんです。
今回は「onkai.h」というヘッダファイル1つを追加しただけですが、スケッチの規模が大きくなってくると、スケッチ自体を複数のファイルに分けたり、ヘッダファイルを複数使用したりというケースが出てきます。
そのような時のためにファイルがバラバラにならないようにスケッチは始めからフォルダで管理されています。
【補足】スケッチのヘッダファイル名
今回は周波数定義の部分を「onkai.h」という別ファイルにして、kitchen_timer.inoのスケッチで読み込みました。#defineが少なくなってスッキリしましたよね。
でも、「kitchen_timer」のスケッチにはまだ#defineがたくさん残っていますよね。
kitchen_timerに残っている#defineはこの先変更することがあるのでこのままにしますが、一般的には#defineなどは別ファイルにしてしまうこともあります。
例えば今回のkitchen_timer.inoのスケッチで、残りの#defineの部分を「onkai.h」とは別のファイルに持っていき、kitchen_timerのスケッチにはsetupとloopの処理だけ書くようにする、という方法です。
このように、kitchen_timer.inoの残りの#define部分を別ファイルにする場合、kitchen_teimer.inoと密接に関連するファイルになりますので、ファイル名は「kitchen_timer.h」というように、スケッチ本体と名前部分が同じで拡張子が「h」のファイルを作成するケースが多いです。
今回のキッチンタイマーのスケッチの場合、以下のようなファイル構成にすることもできます。
ファイル内容onkai.h各音程の周波数を定義したヘッダファイルkitchen_timer.hキッチンタイマーのLED接続端子番号などの#defineが書かれたヘッダファイルkitchen_timer.inoキッチンタイマーのスケッチ本体。このスケッチには#defineは書かれておらず、上の2つのヘッダファイルを#includeにより読み込むプリプロセッサ指令
#includeは「プリプロセッサ指令」と呼ばれています。急に難しい用語が出てきましたが、スケッチがArduinoボードに送られる過程をみながら、#includeについて理解を深めたいと思います。
キッチンタイマーのスケッチは、Arduino IDEがすぐにArduinoボードように変換して送るわけではなく、その前に「前処理」をしているんです。この前処理を英語で「プリプロセッサ」と呼んでいます。(「プリ」は「前に」や「事前に」の意味です。「プロセッサ」は「処理」の意味です)
ここで今回のスケッチがどのように処理されてArduinoボードに送られているか、少し詳しくみてみます。
Arduino IDEはスケッチをArduinoボードに送るとき、最終的にはスケッチをArduinoボード用に変換しますが、その前にスケッチの中に#includeがあればその部分を#includeで指定されたファイルの中身に置き換えます。
#includeはこのようにプリプロセッサに対して指令を行っているので「プリプロセッサ指令」と呼ばれています。
実は#defineもこのタイミングで処理されているので、プリプロセッサ指令だったんです。
目次に戻る