第40回 関数

今回は残り時間を表現する3個のLEDの制御方法を変えてみます。

残り時間LED制御

製作したキッチンタイマーには、残り時間を表現するために3個のLEDを接続しています。

残り時間表示用LED

この3個のLEDを使用して、残り時間を以下のように表現しています。

時間表示用LED緑ON

時間表示用LED黄ON

時間表示用LED赤ON

今まで記事では、1個のLEDを制御するには1つのdigitalWrite命令を使っていました。例えば緑色のLEDを消して黄色のLEDを点灯する場合は以下のように2つのdigitalWrite命令が必要です。

digitalWrite(LED_MIDORI, LOW); // 緑色LEDをOFF
digitalWrite(LED_KIIRO, HIGH); // 黄色LEDをON

この場合はdigitalWrite命令2つだけですのでそれほど手間ではありませんが、切り替えるたびにdigitalWrite命令を2行書くのは面倒です。

そこで、3個のLEDをまとめて制御できる命令があると便利だと思いませんか? イメージとしては、1つの命令で「緑ON、黄OFF、赤OFF」というように一気に3個のLEDを制御できると便利ですよね。

この「1つの命令」について、もう少し具体的なイメージを考えてみます。

例えば3個のLEDを「緑をON、黄をOFF、赤をOFF」のようにするには、今まで習得した方法ではスケッチに以下のように書く必要があります。

digitalWrite(LED_MIDORI, HIGH); // 緑色LEDをON
digitalWrite(LED_KIIRO, LOW);   // 黄色LEDをOFF
digitalWrite(LED_AKA, LOW);     // 赤色LEDをOFF

この3行命令を書くのではなく、1つの命令で書いてしまおう、というわけです。

例えば、時間(time)を表現するLED(led)を制御する(control)命令、ということで、こんな感じの命令があるとスケッチを書くのが楽になるのではないでしょうか。

controlTimeLed(HIGH, LOW, LOW); // 緑LED、黄LED、赤LEDの制御

勝手に作ったこの「controlTimeLed」という命令は、1番目のパラメータが緑色、続いて黄色、赤色のLEDのON/OFF(端子電圧のHIGH/LOW)を指定するようにしています。

この命令は、今勝手に作った命令ですので、当然ながらArduinoにはこのような命令は用意されていません。このような専用の命令が最初から用意されていないにしても、このように自分で考えた新しい命令を作れる仕組みがあれば便利ですよね。

実は、複数の命令をまとめて一つの新しい命令が作れる仕組みはどのようなプログラミング言語にも用意されているんです。そこで、この記事では、C/C++言語での複数の命令をまとめて、独自の新しい命令を作る仕組みを習得します。

 

これから作る命令

これから、C/C++言語で用意されている仕組みを使って、新しい命令を作ります。

新しく作る命令は、先ほどの例で説明した3個のLEDのON/OFFを制御する命令で、名前は「controlTimeLed」にします。なお、新しく作る命令の名前はなんでも構いません。例えば「ledSeigyo」(LED制御)や「jikanHyouji」(時間表示)というような名前でも構いません。英数字と一部の記号が使えますが、スペースを入れることはできません。

また、すでにある命令と同じ名前は使用できません。例えば「digitalWrite」という名前の命令はすでにありますので、この名前は使用できません。なお、名前のつけ方は自由ですが、ある程度のルール(慣習)がありますので、そのルールについては記事の最後に説明します。

それではこれから新しい「controlTimeLed」という名前の新しい命令を作っていきましょう!

これから作る新しい命令のできあがりのイメージは以下のようなものです。

controlTimeLed(HIGH, LOW, LOW);

この命令のパラメータは最初から順番に緑色LED、黄色LED、赤色LEDのOFF/ONを指定するパラメータにします。パラメータに指定する値はdigitalWriteと同じようにHIGH/LOWを使うことにします。

この1つの命令は、以下の3つの命令と同じ制御をすることになります。

digitalWrite(LED_MIDORI, HIGH); // 緑色LEDをON
digitalWrite(LED_KIIRO, LOW);   // 黄色LEDをOFF
digitalWrite(LED_AKA, LOW);     // 赤色LEDをOFF

これから、新しい命令の作り方を詳しく説明しますが、その前にひとつ重要な言葉を覚えましょう。

このシリーズでは今までずーっと「命令」という言葉を使ってきましたが、C/C++言語では命令のことを「関数」(かんすう)と呼んでいます。関数って中学校の数学で出てきましたよね。数学で出てきた「関数」とC/C++言語の「関数」は同じ意味なんです。

関数をまだ学んでいない方は「何?」って感じになってしまうと思いますので、関数について簡単に説明しておきます。

「関数」とは、何かを入力すると、その入力されたものに対して何らかの機能が働いて、何かが出力されるものです。

関数

さて、なんだか余計にわからなくなってしまいました。ということで関数の具体的な例を見てみましょう。以下の図では左側の数字が「?」の機能により右側の数字に変わります。この「?」はどのような機能かわかりますか?

関数の例

よくみてみると、左側の数字が「?」の部分を通ると2倍になっていますよね。つまりある数字を「?」に入力すると、出力が2倍の数字になっているわけです。

この「?」の部分を関数と呼び、上の関数は「入力の数字を2倍にして出力する」という機能を持っています。

このように関数は何かの入力に対して何らかの機能が働いてその結果を出力します。ちょっと補足ですが、「関数」は英語では「function」(ファンクション)と呼びます。「function」は「機能」という意味なんです。先ほどの上の図の「?」は入力の数字を2倍にして出力する「fuinction」、つまり「機能」といったほうがわかりやすいですよね。

ところで、スイッチ制御のところで出てきたdigitalReadの働きを「関数」という考え方の観点からもう一度見てみましょう。

digitalRead関数

この図の通り、digitalReadは以下のような関数になっています。

入力 機能 出力
端子番号 端子の電圧を読み取る 読み取った電圧の値

ここで、プログラミング言語での呼び方を覚えましょう。「入力」は「パラメータ」または「引数」と呼んでいます。また、「出力」は「返り値」と呼んでいます。

関数の呼び名

digitalRead関数で23番端子の電圧状態を読み取って、その結果を変数switch(uint8_t型)に代入する場合、以下のように書きます。

switch = digitalRead(23);

これを先ほどの関数の説明に当てはめると、以下のような関係になっています。

digitalRead関数のパラメータと返り値

それではこのように考えた場合、digitalWriteはどのような関数なのか考えてみましょう。

digitalWrite関数

digitalWrite関数はdigitalRead関数と違って出力(返り値)がありませんよね。数学ででてくる関数は出力がないということはありませんが、プログラミング言語の関数は出力がないことがある、という点に注意してください。

入力 機能 出力
端子番号 端子の電圧を制御する なし

ここまでの説明で、「関数」「パラメータ(引数)」「関数の機能」「返り値」という言葉が出てきました。これからこれらの言葉を使って関数を説明していきますので、わかりづらいところがあったら用意したノートに整理しておきましょう。

 

関数の定義

それでは、関数の作り方をみてみましょう。関数を作る、というのは一般的には関数を定義する、と呼んでいます。以下はC/C++言語の関数の定義方法です。

関数定義

なお、関数の返り値がない場合は、以下のように定義します。

関数定義(返り値なし)

関数名の前に書いてある「void」(ボイド)は日本語で「なし」という意味です。返り値はありません、という意味になっています。

また、このように定義した関数を使うことを、「関数を呼び出す」または「関数をコールする」と呼んでいます。コール(call)は日本語で「呼ぶ」という意味です。

ところで、、、説明を書いておきながらなんですが、この説明では関数の定義方法はさっぱりわかりませんよね。そこで、これから実際に定義した「controlTimeLed」関数を見ながら関数の定義方法を習得します。

 

LED制御関数

以下は、これから定義するcontrolTimeLed関数の機能とパラメータの図です。

controlTimeLed関数

controlTimeLedのパラメータは緑LED、黄LED、赤LEDを制御するための値です。また、返り値はありませんので関数定義するときは「void」で定義します。

それでは、最初にcontrolTimeLed関数の定義の例を見てみましょう。今回はcontrolTimeLed関数を以下のように定義してみました。

void controlTimeLed(uint8_t midori, uint8_t kiiro, uint8_t aka) {
digitalWrite(LED_MIDORI, midori);
digitalWrite(LED_KIIRO,  kiiro);
digitalWrite(LED_AKA,    aka);
}

この関数定義を見ると、関数名の「controlTimeLed」、返り値の「void」、関数の中身はわかると思いますが、パラメータの部分が???ではないでしょうか。

関数を定義する場合、パラメータは変数にしておきます。例えば上のように関数を定義して、この関数を呼び出すと、以下のように関数が処理されます。

controlTimeLedの処理

(1)定義したcontrolTimeLed関数をパラメータを指定して呼び出すと、(2)定義した関数のパラメータ変数にそれぞれ値が代入されて、(3)関数の中身が実行されます。

なお、controlTimeLed関数のパラメータはHIGHかLOWですが、Arduino IDEの裏ではそれぞれ数字の「1」と「0」に#define定義されていますので、パラメータの型は「uint8_t」型にしています。

ところで、この関数定義を書く場所ですが、Arduino IDEの場合はどこかに書いておけばOKです。ただし「どこか」と言っても「 { 」と「 } 」に囲まれたところには書けません。現在のスケッチでは以下の部分に関数定義を書くことはできません。この部分以外であればどこでもOKです。

関数定義の場所

なお、関数定義を書く場所は注意点があります。Arduino IDEの場合はこのように好きなところに書けますが、一般的なC/C++言語ではこのように自由な場所に書くことはできません。この注意点については補足記事で説明します。

 

実際のスケッチ

それでは現在のスケッチに関数定義を書いて、関数を呼び出してみます。関数定義の場所はヘッダ部の最後にしてみました。なおこの関数定義はスケッチの一番下でも構いません。

/*
 * キッチンタイマー
 * 
 * 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
 * 変更履歴:
 *   2019. 8.11: 新規作成
 *   2019. 8.15: スタートスイッチ処理を追加
 *   2019. 8.17: スイッチ関連の#define追加
 *   2019. 9. 8: 点滅回数カウント追加
 *               最終的に繰り返し処理をfor文で作成
 *   2019. 9.16: スケッチ動作開始時にLEDを点滅
 *   2019.10. 5: アラーム音追加
 *               動作開始時とタイマー時間の時に青色LEDを点灯するように変更
 *   2019.10.13: 残り時間LED制御追加
 *   2019.10.14: 残り時間が5秒以下になったらスピーカーを鳴らす動作を追加
 *   2019.11. 4: アラーム音をメロディーに変更
 *               音階定義をファイル化
 *   2019.11.11: メロディーを配列化
 *   2019.11.23: 残り時間表示LEDの関数化
 */

// 音階定義ファイルのインクルード
#include "onkai.h"

// 秒を表現するLED関連(青色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 23    // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON  0 // スイッチONの時のdigitalReadの値

// タイマー時間設定(単位:秒)
#define TIMER_JIKAN 10

// 残り時間を表現するLEDの制御時間
#define KIIRO_JIKAN TIMER_JIKAN / 3
#define AKA_JIKAN   TIMER_JIKAN / 3 * 2

// アラーム音関連
#define SPEAKER 18  // スピーカーの端子番号
#define ALARM   880 // 通常のアラーム音周波数

#define ALARM_ONKAISUU 13 // アラームメロディーの音の数
uint16_t alarm_melody[] = {DO_4, MI_4, SO_4, MI_4, DO_4, MI_4, SO_4, MI_4, DO_4, MI_4, SO_4, SI_4, DO_5};  // アラームメロディー


// 残り時間表示用LEDの制御関数
//   パラメータ: HIGHまたはLOW
//       midori: 緑色LEDのON/OFF
//       kiiro:  黄色LEDのON/OFF
//       aka:    赤色LEDのON/OFF
//   返り値:
//       なし
//
void controlTimeLed(uint8_t midori, uint8_t kiiro, uint8_t aka) {
  digitalWrite(LED_MIDORI, midori);
  digitalWrite(LED_KIIRO,  kiiro);
  digitalWrite(LED_AKA,    aka);
}


void setup() {
  // 端子の設定
  pinMode(BYOU_LED,   OUTPUT);   // 青色LED接続端子設定
  pinMode(LED_MIDORI, OUTPUT);   // 緑色LED接続端子設定
  pinMode(LED_KIIRO,  OUTPUT);   // 緑色LED接続端子設定
  pinMode(LED_AKA,    OUTPUT);   // 緑色LED接続端子設定
  pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定

  // 秒のLED(青色LED)を点灯する
  digitalWrite(BYOU_LED, HIGH);

  // スイッチが押されるまで待つ
  while(digitalRead(SWITCH) == SWITCH_OFF) {
  }

  // タイマー開始時に緑色LEDを点灯
  controlTimeLed(HIGH, LOW, LOW);

}


void loop() {
  
  uint8_t count;       // for文で回数を数えるために使用する変数
  uint8_t oto_bangou;  // for文でメロディーを鳴らすために使用する変数
  

  // TIMER_JIKAN分の回数を数える
  for( count=0; count<TIMER_JIKAN; count++) {

    // 残り時間表現用LEDの制御
    if( count == KIIRO_JIKAN ) {
      controlTimeLed(LOW, HIGH, LOW);
    }

    if( count == AKA_JIKAN ){
      controlTimeLed(LOW, LOW, HIGH);
    }

    // 残り時間に応じて処理を変える
    if( count >= (TIMER_JIKAN - 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( count=0; count<3; count++) {

    // メロディーを演奏する
    for( oto_bangou=0; oto_bangou<ALARM_ONKAISUU; oto_bangou++ ) {
      tone(SPEAKER, alarm_melody[oto_bangou]);
      delay(200);
    }
    
    // 音を消す
    noTone(SPEAKER);

    // 1.5秒あける
    delay(1500);

  }

  // 何もしないで待つ
  while( true ) {
  }

}

setupとloop

ところで、今回の記事を読んでお気づきかもしれませんが、Arduino IDEで新規ファイルを作成した時に最初に書かれているスケッチがありますよね。

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

このsetupとloopはそれぞれ「setup関数定義」と「loop関数定義」だったんです。setupとloop関数は返り値がないので「void」になっています。またパラメータ(引数)もありませんので何も書かれていません。

Arduino IDEのスケッチエディタでsetup関数とloop関数を定義するわけですが、スケッチをArduinoボードに送る時にArduino IDEは裏で、「最初にsetup関数を1回呼び出し、そのあとはloop関数を何度も繰り返し呼ぶ」というスケッチを追加しています。

ここで、Arduino IDEが裏で追加するスケッチを見てみましょう。

int main(void)
{

  setup();

  for (;;) {
    loop();
  }

  return 0;
}

私たちが書いたsetup関数とloop関数をArduino IDEは裏でこのように呼び出しています。このスケッチはsetup関数とloop関数の部分を抜き出したもので、裏で何をしているかの概要をわかるようにしています。

Arduinoボードは、電源を入れると必ず「main関数」を実行するようになっています。Arduino IDEは裏でmain関数をこのように用意しておき、私たちが書いたsetup関数とloop関数をmain関数からこのように呼び出していたわけです。

 

関数名の命名則

今回の記事で、何の説明もせずに関数名を「controlTimeLed」というように大文字と小文字を使ってこのようにつなげて書きました。関数名は半角英数字と一部の記号を使って自由に決めることができますが、慣習的なルールがあります。そこで、最後に関数名のルールを説明します。

Arduinoのスケッチで関数定義する場合、関数名は以下のようなルールが使われることが多いです。このため、このシリーズでは関数名は以下のルールを採用しています。

キャメルケース

なお、このようなルールで名前を書いた場合、大文字の部分がところどころに出てきて、これがラクダのコブに似ていることから「キャメルケース」と呼んでいます。「キャメル(Camel)」はラクダ、「ケース(Case)」は文字(もともとは活字の意味)です。

また他のルールとして、単語はすべて小文字で書いて、単語と単語を「 _ 」(アンダーバー/下線)でつなげるルールもあります。

control_time_led

これは先ほどのようにコブの部分がありません。細くて長く、ヘビに似ていることから「スネークケース」と呼んでいます。スネーク(snake)はヘビという意味です。

ところで、単語同士を「 – 」(ハイフン)でつなげるルールもあります。

control-time-led

C/C++言語ではこのような形式はほとんど見ません。ところでこの書き方は何ケースと呼ばれているかわかりますか? 何に似ているか、ちょっと想像してみてください。

なんとなく串に刺さっているように見えるので「焼き鳥ケース」と呼びそうな気もしますが、呼び方は世界共通ですので、日本独自のものではありません。

 

更新履歴

日付 内容
2019.11.23 新規投稿