YouTube動画
動画内で作成したスケッチ
動画では、一部の処理を関数化して、if文をswitch文に変更しました。これらの変更を加えた最終版のスケッチが以下になります。
ヘッダファイル
メロディー演奏用に、音程を定義した以下のファイルを使用しています。
/*
* 音程ごとの周波数
*/
#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
#define DO_6 1047
スケッチ本体
/*
Arduino入門 第9回 スケッチ仕上げ
残り時間LED制御用に関数を定義
if文処理をswitch文に変更
*/
#include "pitch.h" // 音程周波数定義ファイル
#define TIMER 10 // タイマー時間(秒)
#define HALF_TIMER 5 // 「残り時間が半分」の時間(秒)
#define SOON_TIMER 3 // 「もうすぐ」の時間(秒)
#define SW_PIN A5 // スイッチ接続端子
#define SW_OFF 1 // スイッチOFFのdigitalReadの値
#define LED_PIN 12 // 1秒ごとに点滅するLEDの接続端子
#define LED_ON_INTERVAL 50 // LEDを点灯する時間
#define LED_OFF_INTERVAL 950 // LEDを消灯する時間
#define FULL_PIN 8 // 「残り時間十分」LEDの接続端子
#define HALF_PIN 6 // 「残り時間半分」LEDの接続端子
#define SOON_PIN 4 // 「もうすぐ」LEDの接続端子
#define SPEAKER_PIN A0 // スピーカー接続端子
#define ALARM_TONE 1760 // アラーム音の周波数
// setNokoriLed関数定義
// positionで指定した位置の残り時間LEDのみをONにする
void setNokoriLed(int position) {
// 最初に全部のLEDをOFFにしておく
digitalWrite(FULL_PIN, LOW);
digitalWrite(HALF_PIN, LOW);
digitalWrite(SOON_PIN, LOW);
// positionの値に応じて対応するLEDをONにする
switch(position) {
case 1: digitalWrite(FULL_PIN, HIGH); break;
case 2: digitalWrite(HALF_PIN, HIGH); break;
case 3: digitalWrite(SOON_PIN, HIGH); break;
}
}
void setup() {
// LED接続端子を出力制御にする
pinMode(LED_PIN, OUTPUT);
pinMode(FULL_PIN, OUTPUT);
pinMode(HALF_PIN, OUTPUT);
pinMode(SOON_PIN, OUTPUT);
// スイッチ接続端子をプルアップ機能有効にする
pinMode(SW_PIN, INPUT_PULLUP);
}
void loop() {
// スイッチが押されるまで待つ
while( digitalRead(SW_PIN) == SW_OFF ) {
}
// 「残り時間十分」LED(1番目)をON
setNokoriLed(1);
// 指定した時間、LEDを点滅する
// 処理中は残り時間に応じてLEDの点灯状態を変える
for(int i=0; i<TIMER; i++) {
// 残り時間を計算
int nokori = TIMER - i;
// 残り時間が半分の時のLED(2番目)をON
if(nokori == HALF_TIMER){
setNokoriLed(2);
}
// もうすぐの時のLED(3番目)をON
if(nokori == SOON_TIMER) {
setNokoriLed(3);
}
// LEDをピカッと1秒間で点滅
digitalWrite(LED_PIN, HIGH);
delay(LED_ON_INTERVAL);
digitalWrite(LED_PIN, LOW);
delay(LED_OFF_INTERVAL);
}
// メロディーを鳴らす
// メロディーの配列
int melody[]={DO_4, RE_4, MI_4, RE_4, DO_4};
// メロディー再生
for(int i=0; i<5; i++) {
tone(SPEAKER_PIN, melody[i]);
delay(500);
}
// 音を止める
noTone(SPEAKER_PIN);
// LEDを消灯
digitalWrite(SOON_PIN, LOW);
}
動画の要点
関数
関数とは?
スケッチでは希望の動作を実現するために、いろいろな命令を組み合わせて実現してます。そのとき、同じような命令の組み合わせがよく出てくることがあります。
そのようなとき、「関数」という仕組みを利用すると、一連の処理をまとめて新しい命令を作成することができます。
「関数」を作成することを「関数を定義する」と呼びます。関数の定義は次の書式になっています。
実は、今まで「命令」と呼んできたものは「関数」はだったんです。Arduinoのスケッチ(C++のプログラム)は関数から構成されています。
setupとloop
今まで、スケッチを書くときはsetupとloopに関数(命令)を書いてきました。実はこのsetupとloopの部分では関数定義をしていたんです。setupとloopが関数ということは、誰かがこの関数を呼び出しているはずです。
誰が呼び出しているのか解明する前に、Arduino IDEとArduinoボードの関係を確認しておきましょう。
ArduinoボードはC++のプログラムで動作します。一方Arduino IDEではArduino言語を使い、C++の難しい部分は隠しています。そのため、Ardino IDEがArduinoボードにスケッチを書き込むとき、途中でC++のプログラムに変換しています。この変換のときに、Arduino IDEは裏でC++のプログラムをいくつか追加しています。
ここで、Arduino IDEが裏で追加しているC++プログラムのうち、重要なファイルの中身を覗いてみたいと思います。
/*
main.cpp - Main loop for Arduino sketches
Copyright (c) 2005-2013 Arduino Team. All right reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02112-1301 USA
*/
#include <Arduino.h>
// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }
// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }
void setupUSB() __attribute__((weak));
void setupUSB() { }
int main(void)
{
init();
initVariant();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
ちょっと難し内容ですが、要点のみ見ていきましょう。
C++プログラムでは、かならず「main関数」から実行するようになっています。つまり、どのようなC++のプログラムでも「main関数」が必ずあります。
上のプログラムでは33行目からmain関数が書かれています。main関数を見ると、setup関数とloop関数がありますよね。main関数の実行が開始されると、いくつかのシステム初期化の処理のあとに43行目でsetup関数が呼ばれています。
また、45行目でfor文がありますが、このfor文は処理をずっと繰り返す、という意味になります。for文の中にあるloop関数は、ずっと繰り返される、ということになります。
このようにsetupとloop関数が呼び出されてArduinoボードが動作しています。
switch文
プログラミングの世界では、特定の条件が成立したときに特定の処理を行う、ということがあります。例えば、一定の気温と湿度を超えた場合、熱中症を注意するためにアラームを鳴らす、などです。
このような動作はif文で実現できますが、場合分け処理のような場合は「switch文」が便利です。switch文は次のような書式になっています。
switch文は条件に応じた場合分け処理を行うことができますが、次の注意点があります。
- 処理を切り分ける「条件」と、caseの値は「整数値」(小数値は指定できない)
- caseは整数値のみで、不等号を使って比較することはできない(case >=3など)
- breakがないと、次のcaseの処理に進む
メモリと変数
スケッチを書くとき、変数は必ずといっていいほど使います。この変数はArduinoボード内部に保存されます。
Arduinoボード内部には「フラッシュメモリ」と「RAM」という2種類のメモリが存在します。
フラッシュメモリは一般的なPCのSSDと同じようなもので、電源を切っても内容は消えません。Arduinoボードではスケッチの保存に使われます。
Arduino IDEの検証結果には、フラッシュメモリに保存されるスケッチについて、サイズの情報が表示されます。
RAMは一般的なPCのメモリと同じく、電源を切ると内容は消えてしまいます。Arduinoボードでは変数や配列の保存場所に利用されます。
Arrduino IDEの検証結果を見ると、RAMについては「グローバル変数」と「ローカル変数」の情報が書かれています。
これらの違いは、その変数がどこで使えるか、という観点で異なります。使える範囲のことを「変数の有効範囲」や「変数のスコープ」と呼ばれています。
特定の関数内で変数宣言した場合、所属する波括弧の最後まで使えます。このような変数は「ローカル変数」と呼ばれています。ローカルは「局所的」という意味です。
一方で、ヘッダ部で宣言した場合はそれ以降のどの関数内でも利用できます。このような変数は「グローバル変数」と呼ばれています。グローバルは「全体的」という意味です。
この2種類の変数は次のような特徴があります。
- ローカル変数は、宣言されたときに初めてRAMに保存領域が確保される。不要になったらRAMの保存領域は開放される
- グローバル変数は、Arduinoボードが動作している間、ずっとRAMに保存領域が確保される。その保存領域が開放されることはない
このように、グローバル変数はスケッチ作成時点でRAMの保存領域が確定できますので、Arduino IDEの検証結果には具体的なサイズがかかれています。一方でローカル変数はArduinoボード動作中は必要サイズが変動するため、どのぐらい使えるか、という情報がかかれています。
なお、変数型別のRAM使用サイズは次のようになっています。
定数定義
今までのスケッチでは、定数を定義するとき、スケッチのヘッダー部分で次のように#defineを使用していました。
#define TIMER 10 // タイマー時間(秒)
Arduino IDEのスケッチ例や他の人が書いたスケッチでは、これとは違った書き方がされていることもあります。
constによる定数定義
次のような変数宣言をした場合、メモリに変数の保存領域が確保されます。
int pushButton = 12; // スイッチ接続端子
でもこの変数宣言の前に「const」をつけると、変数ではなく、定数として扱われます。constはconstantの略で「定数」という意味です。
const int pushButton = 2; // スイッチ接続端子
これは、#defineを使って次のように定義したときと同じ働きをします。
#define pushButton 2 // スイッチ接続端子
このように同じ働きをする書き方が2種類ありますが、constを使ったほうが値の型(整数なのか小数なのか)がわかるので、C++ではconstを使うことが推奨されています。
constなしでも定数?
Arduino IDEアプリのファイルメニュー▶スケッチ例▶Basicに「DigitalReadSerial」というスケッチ例があります。
/*
DigitalReadSerial
Reads a digital input on pin 2, prints the result to the Serial Monitor
This example code is in the public domain.
https://www.arduino.cc/en/Tutorial/BuiltInExamples/DigitalReadSerial
*/
// digital pin 2 has a pushbutton attached to it. Give it a name:
int pushButton = 2;
// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
// make the pushbutton's pin an input:
pinMode(pushButton, INPUT);
}
// the loop routine runs over and over again forever:
void loop() {
// read the input pin:
int buttonState = digitalRead(pushButton);
// print out the state of the button:
Serial.println(buttonState);
delay(1); // delay in between reads for stability
}
このスケッチの12行目でグローバル変数宣言をしています。
スケッチ上はグローバル変数宣言ですが、実はこのpushButtonは定数として扱われます。
Arduino IDEはスケッチを解釈するときに、宣言された変数が本当に変数として扱われているか(RAMの保存領域を必要とするか)を判定します。
上のスケッチでは、setup関数とloop関数の両方でpushButtonが使われていますが、いずれも引数で値をしていするだけの目的で使用されています。つまり変数として操作していません。
このように判定できた場合は、Arduino IDEはpushButtonは変数とはせずに、constがつけられたものとして処理します。