第37回 #include

今回はスケッチを整理します。

目次

スケッチ、長くないですか?

前回の記事で、アラーム音をメロディーに変えたのはいいですが、スケッチがなんだかずいぶん長くなってしまいましたよね。

特に最初の#defineの部分が長くなっています。肝心のスケッチ本体であるsetuploopがずいぶんと下の方に行ってしまい、スケッチを開くとしばらく#defineの行が続いて、なんとも見づらいスケッチになってしまった、という状況です。

今回の記事では、長い#defineの部分をなんとかしてみましょう。

ヘッダ

前回の記事で作成したスケッチを見てみます。

スケッチの構成を見ると、最初にコメント部分、次に#define部分、そのあとやっとsetuploopがでてきます。

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のスケッチにはsetuploopの処理だけ書くようにする、という方法です。

このように、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もこのタイミングで処理されているので、プリプロセッサ指令だったんです。

更新履歴

日付内容
2019.11.4新規投稿
2021.8.27新サイトデザイン対応
2022.2.17複数ヘッダファイルの説明追加
2024.12.22Arduino IDE2対応
説明内容変更
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次