Arduinoの関数の作り方――loop()が肥大化する前に、自作関数で整理する。

プログラミング・電子工作






Arduinoの関数の作り方――loop()が肥大化する前に、自作関数で整理する。|YOFUKASI AI




Arduino / 電子工作

Arduinoの関数の作り方――loop()が肥大化する前に、自作関数で整理する。

同じ処理を何度もコピペしていませんか。関数化の基本構文から、スコープ・引数設計、実践的な5ステップまで、体系的に解説します。

YOFUKASI AI編集部 | 更新日:2026年8月18日 | 読了目安:約12分

Chapter 01 — Introduction

01なぜ今「関数化」が必要なのか

結論から言えば、Arduinoのコードが読みにくくなる最大の原因は、loop()関数の中に処理をすべて書き込んでしまうことにある。センサーの読み取り、しきい値判定、アクチュエータの制御――これらを1つのloop()に詰め込むと、行数が増えるほど「どこで何をしているか」を追うのが難しくなる。関数(function)として処理を分割することで、コードの見通しと再利用性は大きく改善する。

実例として、家庭菜園向けの自動水やりシステムをArduino IDEとESP8266で構築した事例がある。この事例では、土壌センサーの値を取得する処理と、AWS IoT Coreへの送信処理、しきい値判定によるポンプ制御処理が、それぞれ役割ごとに整理されている。土壌データをAWS IoT Coreに連携する自動水やり機の開発事例では、閾値600を超えた場合にポンプを作動させるロジックが明確に切り分けられており、後から閾値だけを調整する際の見通しの良さにつながっている。

編集部見解:趣味の電子工作であっても、処理が20〜30行を超えたあたりから「関数化した方が結局早い」と感じる場面が増える。特に配線変更やセンサーの追加・交換を伴うプロジェクトでは、処理単位で切り分けておくことが、後々の修正コストを左右する。
― YOFUKASI AI編集部

本記事では、Arduinoにおける関数の基本構文から、スコープ・引数の設計、実践的な整理手順までを、初心者からある程度コードを書き慣れたエンジニアまでを対象に体系的に解説する。



Chapter 02 — Background

02Arduinoにおける関数の基礎知識

関数(function)とは、一連の処理をひとつのまとまりとして名前を付けたもので、必要な場所から何度でも呼び出す(コールする)ことができる。Arduino公式のドキュメントでも、Arduinoのプログラミング言語は大きく「関数(functions)」「値(variables and constants)」「構造(structure)」の3要素に分けられると説明されている。関数を使う処理単位のことをまとめてモジュール化と呼ぶこともある。

Arduinoの言語仕様の一次情報は、Arduino公式 Language Referenceにまとまっている。公式リファレンスではArduinoのプログラミング言語が関数、値(変数と定数)、構造の3つの主要な部分に分けられると説明されている。関数は「マイコンの制御や計算処理を担う」要素として位置づけられている。

関数を自作するメリットについて、初心者向けに解説した日本語記事でも同様の整理がされている。Arduino IDEの使い方(function・関数の自作編)という記事では、関数を使うことでプログラマは秩序立った状態を保てる、同じ処理を同じ場所に集約することで繰り返しデバッグする必要がなくなる、モジュール化によってコードを他のプログラムで再利用しやすくなる、といった利点が挙げられている。

用語の整理

  • 戻り値(return value):関数が処理結果として呼び出し元に返す値。void型の場合は戻り値を返さない。
  • 引数(argument / parameter):関数を呼び出す際に渡す値。関数の挙動を外部からコントロールするために使う。
  • スコープ(scope):変数が有効な範囲。関数の中だけで使えるローカル変数と、プログラム全体で使えるグローバル変数がある。

これらの用語は、公式の関数リファレンス一覧を体系的にまとめた日本語サイトでも整理されている。Arduino-言語・関数リファレンス一覧(Spiceman)では、Digital I/O関数やAnalog I/O関数など、Arduino標準関数がカテゴリごとに解説されており、自作関数を設計する際の命名や引数設計の参考になる。

Arduinoの基礎文法そのものをまだ体系的に学んでいない場合は、先に基礎構文を押さえておくとこの先の理解がスムーズになる。Arduinoのコードの書き方の基礎はこちらの記事で詳しく解説している

Chapter 03 — Mechanism

03関数の仕組みと技術的背景

Arduinoの関数は、基本的に次の構文で定義する。

戻り値の型 関数名(引数の型 引数名) {
  // 処理
  return 値; // 戻り値がある場合のみ
}

戻り値が不要な場合はvoidを指定する。たとえばLEDを点滅させるだけの処理であれば戻り値は不要だが、センサーの値を読み取って呼び出し元に渡したい場合はintfloatなどの型を指定する。

// 戻り値のない関数(void型)
void blinkLED(int pin, int duration) {
  digitalWrite(pin, HIGH);
  delay(duration);
  digitalWrite(pin, LOW);
  delay(duration);
}

// 戻り値のある関数
int readSensorAverage(int pin, int samples) {
  long total = 0;
  for (int i = 0; i < samples; i++) {
    total += analogRead(pin);
    delay(5);
  }
  return total / samples;
}

スコープの考え方

関数の中で宣言した変数(ローカル変数)は、その関数の外からは参照できない。一方、関数の外(プログラムの先頭付近)で宣言した変数(グローバル変数)は、すべての関数から参照・変更できる。グローバル変数は便利だが、多用すると「どの関数がいつその値を書き換えたのか」を追いにくくなる副作用が生じやすい。

  • ローカル変数:関数内で完結する。他の処理に影響を与えないため安全性が高い。
  • グローバル変数:複数の関数間で共有したい状態(例:現在のモード、直近のセンサー値など)に限定して使うのが望ましい。

この設計思想は、Arduinoに限らず組込みソフトウェア開発全般で重視されている。独立行政法人情報処理推進機構(IPA)が公開している組込みソフトウェアの品質向上に関する報告書では、モジュール(機能単位)に内在する不備が不具合の原因になりやすいことが指摘されており、機能を適切な単位に分割し、それぞれの責務を明確にすることの重要性が繰り返し述べられている。IPA「組込みソフトウェア開発における品質向上の勧め[バグ管理手法編]」を参照。

編集部想定シナリオ
複数のセンサーとアクチュエータを扱うプロジェクトで、すべての判定ロジックをグローバル変数の書き換えだけで実現しようとすると、あるセンサーの読み取り関数を修正した際に、意図せず別のアクチュエータの制御に影響が出るといったトラブルが起こりやすい。引数と戻り値を介したデータのやり取りに切り替えることで、こうした「見えない依存関係」を減らせる。

圧力センサーや筋電センサーなど、複数のセンサー値を関数単位で処理する実例は、以下の記事でも詳しく扱っている。関数化の考え方を、実際のセンサー活用と合わせて確認したい場合に参考になる。



Chapter 04 — Comparison

04関数の書き方・開発環境の比較と選択肢

関数を「どこに書くか」という点では、選択肢が複数ある。プロジェクトの規模や目的に応じて、以下のように使い分けるとよい。

項目 A. 1ファイル(.ino)に全関数を記述 B. タブ機能で.inoファイルを分割 C. .h/.cppに分割(PlatformIO想定)
向いている規模 小規模・学習用途 中規模プロジェクト 再利用前提の中〜大規模プロジェクト
可読性 行数が増えると低下しやすい 機能ごとに視認しやすい ヘッダ分離により責務が明確
再利用性 低い 中程度 他プロジェクトへの移植が容易
導入の手軽さ Arduino IDEのみで完結 Arduino IDEのみで完結 PlatformIOなど別環境の導入が必要

※C案の開発環境としてはPlatformIO公式サイトを参照。Arduino IDEのファイル結合が単純な連結であるのに対し、PlatformIOでは一般的なC/C++のリンク処理が使われるため、includeやスコープをより厳密に制御できる点が実務上の違いとして紹介されている。比較評価の一部は編集部見解を含む。

初心者のうちはArduino IDEのタブ機能(B案)で十分だが、複数のプロジェクトで同じセンサー処理を使い回したい段階になったら、C案への移行を検討する価値がある。Arduino IDE自体のインストール手順は、以下の記事で最新版に対応した内容を確認できる。

Arduino IDEのダウンロード・インストール方法はこちらの記事で解説している

Chapter 05 — Practice

05実践:自作関数でコードを整理する5ステップ

ここでは「温湿度センサーの値を読み取り、一定条件でファンを制御する」という編集部想定シナリオを題材に、既存のloop()べた書きコードを関数化する具体的な手順を示す。

  1. STEP1. loop()内の処理を機能単位で洗い出す

    現在のloop()を読み、「センサー読み取り」「判定」「制御」のようにひとまとまりの処理を目視で区切る。目安として、5行以上まとまって同じ目的を果たしている箇所は関数化の候補にする。

    実例:温湿度制御コードでは「DHTセンサーから値を取得する処理(約6行)」「28℃を超えたらファンをONにする判定処理(約4行)」の2箇所を関数候補として洗い出した。
  2. STEP2. 戻り値の要否を決めて関数名を付ける

    値を返す必要があるかどうかでvoidか具体的な型かを決める。関数名は「動詞+対象」の形(例:readTemperature()controlFan())にすると、呼び出し箇所を読むだけで処理内容が推測できる。

    実例:float readTemperature()(戻り値あり)とvoid controlFan(bool shouldRun)(戻り値なし)の2関数に命名した。
  3. STEP3. 引数を設計し、グローバル変数への依存を減らす

    関数の中でしか使わない値はローカル変数にし、外部から制御したい値は引数として渡す。しきい値のような「後から変更したくなる値」は、関数内に直接書き込まず、定数(const)または引数として切り出す。

    実例:ファン起動のしきい値28.0℃をconst float FAN_THRESHOLD = 28.0;として定義し、controlFan()内で参照する形にした。しきい値変更時の修正箇所が1箇所で済むようになった。
  4. STEP4. loop()を関数呼び出しだけの構成に書き換える

    loop()の中身を、定義した関数の呼び出し行だけに置き換える。これによりloop()自体は数行〜10行程度に収まり、全体の処理フローが一目で把握できるようになる。

    実例:書き換え後のloop()float temp = readTemperature(); controlFan(temp > FAN_THRESHOLD); delay(1000);の3行のみとなり、行数は元の約18行から3行に削減された(編集部想定シナリオに基づく試算)。
  5. STEP5. 動作確認とデバッグ用シリアル出力を関数単位で追加する

    関数ごとにSerial.print()でデバッグ出力を仕込み、想定どおりの値が返っているかを1関数ずつ確認する。関数化されていると、どの関数の出力がおかしいかを個別に切り分けられるため、原因特定にかかる時間を短縮できる。

    実例:readTemperature()の戻り値をシリアルモニタで確認したところ、配線ミスにより常に0.0が返っていたことが即座に判明した。関数化前は判定ロジックとセンサー読み取りが同じブロックにあり、原因箇所の特定により多くの手順を要していた(編集部想定シナリオ)。

より複雑なセンサー連携・アクチュエータ制御を扱う場合の関数設計の実例は、握力計測ゲームやジャンプゲームなど、Arduino×Pythonの連携記事でも確認できる。

リアルタイム握力計測ゲームのコード解説はこちらの記事で読める。センサー読み取り関数と表示制御関数を分離する構成の参考になる。

Chapter 06 — Pitfalls

06注意点・よくある誤解

!誤解1. 関数化すればするほど良いコードになる

関数を細かく分割しすぎると、1つの処理を追うために何個もの関数を行き来する必要が生じ、かえって可読性が下がることがある。「ひとつの関数はひとつの役割」を目安にしつつ、粒度を細かくしすぎないことが重要。

!誤解2. グローバル変数を使えば関数間の連携が楽になる

グローバル変数は一見手軽だが、多用すると「どの関数がいつ値を変更したか」を追跡しづらくなる。可能な限り引数と戻り値でデータをやり取りし、グローバル変数は複数関数で共有が必須な状態管理に限定するのが望ましい。

!誤解3. 戻り値と副作用(side effect)を混同してしまう

関数が値をreturnする処理と、関数内でdigitalWrite()などを呼んでピンの状態を変える処理(副作用)は別物である。1つの関数に両方を詰め込むと、テストや再利用がしにくくなるため、「値を計算する関数」と「ハードウェアを操作する関数」は分けて設計するのが基本。



Chapter 07 — FAQ

07FAQ

Q.Arduinoの関数とは何ですか。変数とはどう違いますか。
A.関数(function)は、一連の処理をひとつのまとまりとして名前を付けたものです。値を保持する変数とは異なり、関数は「処理そのもの」をまとめる点が違います。同じ処理を何度も書く代わりに、関数を1回定義して何度も呼び出すことができます。

Q.戻り値がない関数(void型)と戻り値がある関数の違いは何ですか。
A.void型の関数は処理を実行するだけで、呼び出し元に値を返しません。一方、int型やfloat型などを指定した関数は、処理結果を呼び出し元に返すことができます。センサー値の取得や判定結果の返却には、戻り値のある関数が適しています。

Q.関数の引数はいくつまで指定できますか。
A.Arduino言語(C++ベース)では引数の数に文法上の明確な上限はありませんが、可読性の観点からは3〜4個程度までに抑えるのが実務上の目安です(編集部見解)。引数が増えすぎる場合は、構造体にまとめることも検討してください。

Q.グローバル変数を使わずに関数間でデータをやり取りするにはどうすればよいですか。
A.引数として値を渡し、戻り値として結果を受け取る設計にすることで、グローバル変数への依存を減らせます。これにより、どの関数がどのデータに影響するかが追いやすくなり、デバッグの負担も軽減されます。

Q.関数を別ファイル(.h/.cpp)に分割するメリットは何ですか。
A.コードの見通しが良くなるほか、同じ関数を別のプロジェクトで再利用しやすくなります。PlatformIOのような開発環境では、この分割が一般的なC/C++の作法に沿って行えるため、規模の大きいプロジェクトに向いています。

Q.関数化しすぎるとかえって読みにくくなりませんか。
A.1つの関数が担う役割を細分化しすぎると、処理の流れを追うために多数の関数を行き来する必要が生じます。「ひとつの関数はひとつの役割」を目安にしつつ、粒度はチームやプロジェクトの規模に応じて調整するのが編集部の見解です。

参考文献

関連記事(内部リンク)


関連記事

特集記事

コメント

この記事へのコメントはありません。

TOP
CLOSE