はじめに (対象読者・この記事でわかること)

この記事は、Arduinoを使った電子工作やプログラミングに興味を持ち始めた方、あるいはArduinoの基本的な使い方に慣れてきたが、より深くその仕組みを理解したいと考えている方を対象としています。特に、プログラムの実行時間計測や定期的な処理の実現に欠かせない micros() 関数や millis() 関数について、「一体この関数は、Arduino IDEのどこに書かれているのだろう?」と疑問に思ったことがある方には、きっと役立つ内容となっています。

この記事を読むことで、micros()millis() がArduinoの内部でどのように実装されているのか、そのコード本体がどこにあるのかを具体的に知ることができます。これにより、Arduinoのタイムスタンプ機能の仕組みを理解し、より高度で正確なプログラム開発への第一歩を踏み出すことができるでしょう。また、Arduino IDEのディレクトリ構成や、C++言語による組み込み開発の雰囲気も垣間見ることができます。

Arduinoのタイムスタンプ機能:micros() と millis() の役割

Arduinoプログラミングにおいて、プログラムの実行時間や経過時間を計測することは非常に重要です。例えば、「LEDを1秒おきに点滅させたい」「ある処理が完了するまで何ミリ秒かかったかを把握したい」といったニーズは日常茶飯事です。このような要求に応えるのが、millis() 関数と micros() 関数です。

millis() 関数は、Arduinoボードのリセットまたは電源投入からの経過時間をミリ秒単位で返します。一方、micros() 関数は、同じく経過時間をマイクロ秒単位で返します。これらの関数は、Arduino IDEでスケッチを作成する際に特別な宣言なしに直接呼び出すことができます。これは、これらの関数がArduinoのコアライブラリとして提供されているためです。

millis() 関数は、主に非同期処理やタイミング制御に用いられます。例えば、delay() 関数はプログラムの実行を一時停止させるため、その間は他の処理を行うことができません。しかし、millis() を利用することで、delay() を使わずに、一定時間経過したかどうかを判断し、必要に応じて処理を実行するといった、より効率的なプログラミング(ノンブロッキング処理)が可能になります。

micros() 関数は、より高精度な時間計測が必要な場合に利用されます。例えば、高速な信号処理や、非常に短い間隔でのイベント検出などに役立ちます。ただし、micros() は内部的にオーバーフロー(値が最大値を超えて0に戻る現象)が発生するまでの時間が millis() よりも短いため、注意深い扱いが必要です。

内部構造を探る:micros() と millis() のコード本体はどこにある?

では、これらの便利な micros() 関数や millis() 関数は、具体的にArduino IDEのどこに、どのようなコードで実装されているのでしょうか。Arduino IDEは、私たちが書くスケッチをコンパイルし、Arduinoボードに書き込むための統合開発環境ですが、その内部にはArduinoボード上で動作するプログラムの基盤となる「コアライブラリ」が含まれています。micros()millis() の実装も、このコアライブラリの一部として存在しています。

Arduino IDEのインストールディレクトリとコアライブラリ

Arduino IDEをインストールすると、そのインストールディレクトリ内に様々なファイルやフォルダが生成されます。これらのうち、hardware というフォルダが、Arduinoボードのハードウェア固有の機能やライブラリを格納する場所となります。

さらに hardware フォルダ内には、各Arduinoボードのアーキテクチャ(AVR、ESP32、SAMDなど)に対応したフォルダが存在します。例えば、多くのArduinoボード(Uno、Nano、Megaなど)で採用されているAVRアーキテクチャの場合、hardware/arduino/avr のようなパスになります。

この avr フォルダの中には、cores というフォルダがあり、さらにその中に arduino というフォルダがあります。この arduino フォルダこそが、AVRベースのArduinoボードで共通して使われるC++ソースコード(コアライブラリ)が格納されている場所です。

micros() と millis() の実装コード

micros() および millis() 関数のコード本体は、この hardware/arduino/avr/cores/arduino/ ディレクトリ内にある wiring.cpp (または WInterrupts.c など、バージョンやボードによっては少し異なるファイル名の場合もあります)といったファイル内に記述されています。

これらのファイルを開いてみると、驚くほどシンプル、かつ巧妙に実装されていることがわかります。

millis() の実装例(概念):

millis() 関数は、内部で micros() 関数を呼び出し、その結果を1000で割った値を返している場合が多いです。これは、 micros() の方がより基本的なタイマーとして実装されており、 millis() はその上位関数として提供されているためです。

millis() の基盤となるのは、Arduinoボードのハードウェアタイマーです。AVRマイクロコントローラには、内部にタイマー/カウンターという機能が搭載されており、これを使って正確な時間計測を行います。millis() は、このハードタイマーのカウンタ値を定期的に読み取り、それがオーバーフローするまでの時間(通常は1ミリ秒ごと)をカウントアップしていくことで、経過時間をミリ秒単位で保持しています。

micros() の実装例(概念):

micros() 関数は、より直接的にハードウェアタイマーのカウンタ値を読み取ります。AVRマイコンのタイマーは、クロック信号に同期してカウントアップするため、このカウンタ値を直接読み取ることで、マイクロ秒単位での経過時間を取得できます。

しかし、マイクロ秒単位の計測は非常に高速であるため、カウンタ値の読み取りや計算処理に時間がかかると、そのオーバーヘッド自体が計測精度に影響を与えてしまう可能性があります。そのため、micros() の実装では、できるだけ高速かつ効率的にカウンタ値を読み取るための工夫が凝らされています。

注意点:オーバーフロー

millis() は約50日で、micros() は約70分でオーバーフロー(値が最大値を超えて0に戻る)します。これは、使用しているタイマーのビット数(16ビットまたは32ビット)とクロック周波数に依存します。このオーバーフローを考慮せずに単純な比較を行うと、予期せぬバグの原因となるため、millis() を使ったタイミング処理では、millis() - previousMillis のような差分計算や、オーバーフローを考慮した比較方法を用いることが一般的です。

コードを直接確認する方法

  1. Arduino IDEをインストールします。
  2. Arduino IDEのメニューから「ファイル」→「環境設定」を開きます。
  3. 「追加のボードマネージャURL」に、使用しているボード(例:ESP32、SAMDなど)のURLを追加し、「ボード」メニューから該当のボードをインストールします。 (AVRボードは標準で含まれています。)
  4. 「ツール」→「ボード」から、対象のArduinoボードを選択します。
  5. Arduino IDEのインストールディレクトリを開きます。 (Windowsなら通常 C:\Program Files (x86)\ArduinoC:\Users\[ユーザー名]\AppData\Local\Arduino15 など、macOSなら /Applications/Arduino.app 内、Linuxなら /usr/share/arduino などにあります。)
  6. hardware フォルダを探し、選択したボードのアーキテクチャ(例:avr)→ coresarduino と進んでいきます。
  7. そのディレクトリ内にある wiring.cpp などのソースファイルを確認します。

IDEのバージョンやボードによってパスやファイル名が若干異なることがありますが、これらの手順で目的のコードにたどり着くことができるはずです。

まとめ

本記事では、Arduinoプログラミングで頻繁に利用される micros() 関数と millis() 関数のコード本体が、Arduino IDEのどこに格納されているのか、その内部実装の概要について解説しました。

  • micros()millis() は、Arduinoのコアライブラリとして提供されている。
  • これらの関数のコードは、Arduino IDEのインストールディレクトリ内の hardware フォルダ配下に格納されている。
  • 具体的には、hardware/[アーキテクチャ]/cores/arduino/ ディレクトリにある wiring.cpp などのファイルに実装されている。
  • 実装の基盤は、Arduinoボードのハードウェアタイマーであり、micros() はタイマーカウンタを直接読み取り、millis() はそれを基にミリ秒単位の経過時間を計算している。

この記事を通して、Arduinoのタイムスタンプ機能の背後にある仕組みを理解し、より効果的なプログラミングを行うための一助となれば幸いです。今後は、これらの関数を使った具体的なノンブロッキング処理の実装方法や、オーバーフロー対策について、より詳細な記事も作成する予定です。

参考資料