第40回 関数

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

目次

残り時間LED制御方法について、少し深掘りしてみる

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

残り時間表示用LED

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

当たり前でしょ?という感じですが、今までスケッチでは、1個のLEDを制御するには1つのdigitalWriteを使っていました。

例えば緑色のLEDを消して黄色のLEDを点灯する場合は次のように2つのdigitalWriteが必要です。

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

この場合はdigitalWriteは2行書くだけですのでそれほど手間ではありませんが、残り時間LEDの点灯状態を切り替えるたびに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つの指示で1行で書けるようにしてしまおう!、というわけです。

例えば、「時間を表現するLEDを制御する」という意味合いで、次のような感じでcontrolTimeLedという指示で書けると楽になるのではないでしょうか?

controlTimeLed(HIGH, LOW, LOW); // 引数はそれぞれ緑LED、黄LED、赤LEDの制御

control(制御する)、Time(時間の)、Led(LED)、という意味合いでこのような指示名を考えてみました。引数は、1番目が緑色、続いて黄色、赤色のLEDのON/OFF(HIGH/LOW)を指定するようにしています。

このように勝手に作ったので、当然ながらArduinoにはこのような指示はありませんよね。

このようなキッチンタイマー専用の指示が最初から用意されていないにしても、自分で考えた新しい指示を作れる仕組みがあれば便利ですよね。

実は、複数の指示をまとめて一つの新しい指示が作れる仕組みはどのようなプログラミング言語にも用意されているんです。

そこで、この記事では、C++言語での独自の新しい指示を作る仕組みを習得します。

これから作る指示

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

今回の記事では、先ほどの例で説明したcontrolTimeLedという名前で、3個のLEDのON/OFFをまとめて制御する指示を作成してみます。

この名前はどのようなものでも構いません。例えばledSeigyo(LED制御)やjikanHyouji(時間表示)というような名前でも構いません。

ただし、いくつか制約があります。

  • 英数字と一部の記号が使えますが、スペースを入れることはできません
  • すでにある命令と同じ名前は使用できません。
    例えばdigitalWriteという名前の命令はすでにありますので、この名前は使用できません。

この制約のもとで名前のつけ方は自由ですが、ゆるい?慣習がありますので、その内容については記事の最後に説明します。


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

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

controlTimeLed(HIGH, LOW, LOW)

1番目の引数から順番に、緑色LED、黄色LED、赤色LEDのOFF/ONを指定できるようにします。

この1つの指示によって、処理内容としては次の内容と同じにしよう、というわけです。

digitalWrite(LED_MIDORI, HIGH);
digitalWrite(LED_KIIRO, LOW);
digitalWrite(LED_AKA, LOW);

それでは実際の作り方に進みたいところですが、その前に大切な用語を確認しておきましょう!

「関数」

このシリーズでは今までずーっと「指示」や「命令」という言葉を使ってきましたが、C++言語ではこれらを「関数」(かんすう)と呼んでいます

関数って確か中学校の数学あたりで出てきましたよね。数学で使う「関数」とC++言語の「関数」は同じ意味なんです。

そこで、最初に「関数」とはどのようなものを指しているのか、確認しておきましょう。

「関数」とは?

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

関数

さて、、、

説明しておきながらこんなこというのもなんですが、なんだか余計にわからなくなってしまいました。

そこで、スケッチで利用するdigitalReadは何をしているのか、「関数」という観点から具体的に確認してみましょう!

このイラストの通り「digitalRead」は「電圧値を読み取る」と言う「機能」を持っています。

また、どの端子名の電圧を読み取るか、という情報を引数に書きますが、この情報は「機能」に対する「入力」と捉えることができます。

さらに、digitalReadは読み取った電圧の値を教えてくれます。この情報は「機能」の「出力」と捉えることができます。

このように考えると、digitalReadは「関数」の特徴を持っていることがわかります。


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

ここで補足ですが、「関数」は英語では「function」(ファンクション)と呼ばれています。「function」は日本語で「機能」という意味なんです。個人的には、日本語の「関数」より「function」の方がしっくりくる気がしています。

「関数」の用語

ここで、プログラミング言語での関数に関する用語を確認しておきましょう。

例えば、digitalRead関数でA5端子の電圧状態を読み取って、その結果をuint8_t型の変数switchに代入する場合、次のように書きますよね。

uint8_t switch;

switch = digitalRead(A5);

このとき、「digitalRead」を「関数名」、A5を「引数」、読み取った電圧値(変数switchに代入される値)を「戻り値」「返り値」と呼んでいます。

なお、関数の中には戻り値がないものや引数がないものもあります。

例えば、digitalWriteはどのような関数なのか考えてみましょう。

digitalWrite関数はdigitalRead関数と違って、戻り値がありませんよね。また、引数は2つあることも特徴です。

ここまで、関数の基本的な考え方と用語について確認しました。

次は実際にオリジナルの関数を作成していきましょう!

関数の定義

これからcontrolTimeLedという関数を作成します。

この「関数を作成」することを「関数を定義する」と呼んでいます。なんだかちょっと仰々しいですね。

それではさっそくC++言語の関数定義方法を確認しましょう。

ここで、一つ新しく「仮引数」という用語が出てきました。この用語については、このあと実際にcontrolTimeLedという関数を定義するときに詳しく説明します。

ところで、これから作成する関数controlTimeLedは3個のLEDを制御するだけてのすで、戻り値はありませんよね。

でも上の定義方法の場合、関数の戻り値は関数名の前に指定することになっています。

このように関数の戻り値がない場合は、次のように定義します。

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

また、このように定義した関数を使うことを、「関数を呼び出す」または「関数をコールする」と呼んでいます。コール(call)は日本語で「呼ぶ」という意味です。(今までのスケッチでいうと、digitalWrite関数を呼び出しす、という言い方もできますね)


関数の作成方法を一般的に説明しましたが、結局この説明では何をどうしたら良いのかさっぱりわかりませんよね。

そこで、これから実際にcontrolTimeLed関数をどのように定義すれば良いか、具体的にみていきます。

controlTimeLed関数

最初に、controlTimeLed関数をどのように呼び出したいか、もう一度確認しておきましょう。

スケッチにこのように3つの引数にHIGHかLOWか指定して、3色のLEDを制御しよう、というわけです。

これは「関数を呼び出す側」の話です。

これに対応させる形で「関数を定義する側」(呼び出される側)はどのようになるかみていきます。

関数定義側を一つ一つ確認していきましょう。

このcontrolTimeLedという関数は3色のLEDを制御するだけですので、戻り値はありません。そのため最初にvoidを書いています。

次に関数名を書きます。

関数定義側の引数ですが、関数呼び出し側からは具体的な値(HIGHかLOW)が指定されますが、その値は何になるかはわかりません。

そこで、関数定義側では引数として変数を書いておき、この変数に値が代入されるようにします。例えば上の関数呼び出しの例では、関数定義側の最初の引数greenにはHIGHが代入されることになります。

この関数定義側の引数は、実際の引数(値)ではなく、仮の引数として変数を書いているので、これを「仮引数」と呼んでいます

また、仮引数は変数として扱いますので、変数型を指定する必要があります。このcontrolTimeLed関数では仮引数の変数型は全てuint8_t型にしています。

このように関数呼び出し側から受け取った引数の値は、それぞれmidori、kiiro、akaという変数に代入されますので、この変数を使用して関数の処理内容を書くことになります。

例えば緑色LEDを制御するにはdigitalWrite(LED_MIDORI, midori);と書くことになります。

最後に、controlTimeLed関数の定義まとめます。

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

setup関数とloop関数

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

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

}

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

}

このsetuploopはそれぞれ「setup関数定義」と「loop関数定義」だったんです。

これらの関数は戻り値がないので、戻り値の型の部分はvoidになっています。

引数もありませんので、仮引数の部分には何も書かれていません。

私たちはずっと、setup関数とloop関数の処理の中身を書いていたんですね。

関数定義の場所

ところで、この関数定義はスケッチのどこに書けばよいのでしょうか?

実はArduino IDEの場合、スケッチのどこかに書いておけばOKです。

ただし「どこか」と言っても、他の関数定義の中に書くことはできません。

現在のスケッチでは、すでにsetup関数とloop関数定義がありますので、その定義部分には関数定義を書くことはできません。

この部分以外であれば、関数定義はどこに書いてもOKです。

関数を書く場所

Arduino IDEでは、プログラミングに慣れていない方でもなるべく簡単にスケッチが書けるように、関数定義の場所はそれほど意識する必要はないようになっています。

ただ実際のC++言語では、関数定義は関数を使う前に書く必要があります。Arduinoを使っている限りそれほど意識する必要はありませんが、PICマイコンなどのプログラミングでは重要なポイントになります。

実際のスケッチ

それでは現在のスケッチに関数定義を書いて、関数を呼び出してみます。

関数定義の場所はヘッダ部の最後にしてみました。なおこの関数定義はスケッチの一番下でも構いません。

/*
  キッチンタイマー
  
  内容: スイッチ、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: 周波数定義部分をファイル化
    2024.12.23: メロディーデータを配列化
    2024.12.26: 残り時間LED制御の関数化
*/

// 各音程の周波数定義読み込み
#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 // アラーム音の音程

#define MELODY_LENGTH 13 // アラームメロディーの音の数
uint16_t 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
//   返り値: なし
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() {
  // 端子の設定
  // スピーカー接続端子は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 ) {
      controlTimeLed(LOW, HIGH, LOW);
    }

    // 残り時間が3分の1のとき、黄色をOFF、赤色をONにする
    if( count == AKA_JIKAN ){
      controlTimeLed(LOW, LOW, 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++) {
  
    // 1回分のメロディー演奏
    for(uint8_t i=0; i<MELODY_LENGTH; i++ ) {
      tone(SPEAKER, melody[i]);
      delay(200);
    }   
    // 音を消す
    noTone(SPEAKER);

    // メロディーを区切るために少し時間待ちをする
    delay(500);
  }

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

}

【補足】setup関数とloop関数は誰が呼び出しているか?

私たちは、Arduino IDEでsetup関数とloop関数を定義するわけですが、この関数は誰が呼び出しているのでしょうか?

Arduinoボードにスケッチを送るとき、Arduino IDEは裏でいくつかC++のプログラムを追加しています。

その追加するプログラムがこれらの関数を呼び出しているんです。

実際に、Arduino IDEが裏で追加するC++のプログラムを見てみましょう。(説明のため、実際のプログラムから余計な部分は削除しています)

int main(void)
{

  setup();

  for (;;) {
    loop();
  }

  return 0;
}

C++言語では、プログラムは必ずmain関数から始まることになっています。

上のプログラムの1行目にmain関数がありますので、Arduinoボードが動作を開始するとここからスタートします。

動作開始後にsetup関数を呼び出していますよね。これは動作開始後1回だけ呼ばれます。

次にfor(;;)にあるようにloop関数をずっと繰り返し処理しています。

Arduino IDEは裏でmain関数をこのように用意しておき、私たちが書いたsetup関数とloop関数をこのように呼び出していたわけです。

【補足】関数名の命名則

今回の記事で、何の説明もせずに関数名をcontrolTimeLedというように適当に?大文字と小文字を混ぜて書きました。

関数名は半角英数字と一部の記号を使って自由に決めることができますが、慣習的な緩いルールがありますので、最後に補足します。


Arduinoのスケッチで関数定義する場合、関数名は次のようなルールが使われることが多いです。

キャメルケース

複数の単語をスペースなしで繋げると区切りがわかりづらくなってしまいますが、このルールでは単語の先頭を大文字にすることにより区切りをなんとか判別できるようにしています。

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

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

control_time_led

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

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

control-time-led

これは単語を串刺し?にしているのように見えますよね。日本でしたら「団子ケース」や「焼き鳥ケース」にしたいところですが、世界的に「ケバブケース」よ呼ばれることがあるようです。

関数名の書き方ですが、プログラミング言語ごとに異なっていることもあります。どれが正解というわけではありませんが、他の人が書いたスケッチやプログラムを見たときに、ちょっと注意してみると面白いと思います。

更新履歴

日付内容
2019.11.23新規投稿
2021.8.27新サイトデザイン対応
2024.12.26説明内容簡略化
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次