タイマーとは?delay・millisとの違いとマイコンのタイマー割り込みを解説

組み込み基礎

マイコンでは、「1秒ごとにLEDを点滅させる」「一定周期でセンサーを読み取る」「決められた時間が経過したら処理を実行する」といった時間を使った処理がよく登場します。

簡単なプログラムならdelay()でも実現できますが、処理を止めずに時間を管理したい場合や、正確な周期で処理を実行したい場合にはタイマーが重要になります。

この記事では、マイコンのタイマーとは何かという基本から、delay()やmillis()との違い、ハードウェアタイマー、タイマー割り込みの仕組みまで順番に解説します。

マイコンのタイマーとは?

タイマー(Timer)とは、マイコン内部で時間を計測したり、一定のタイミングでイベントを発生させたりするための機能です。

マイコン内部のハードウェアタイマーでは、クロックをもとにカウンタを増減させることで時間を管理します。

基本的なイメージは次のようになります。

  • タイマーへクロックが入力される
  • クロックをもとにカウンタが進む
  • 設定した条件に到達する
  • イベントや割り込みが発生する
  • 必要な処理を実行する

この仕組みを利用することで、CPUがほかの処理をしている間にも時間を計測できます。

マイコンのタイマーの仕組みとdelay・millisの違い、タイマー割り込み、KUMITATE-C3でLEDを点滅させる例を示した図
マイコンのタイマーの基本と、delay・millis・タイマー割り込みの違い

上図①のように、ハードウェアタイマーはクロックをもとにカウントを進め、設定した条件に達したときにイベントを発生させます。図②ではdelay()との違い、図③ではタイマー割り込みの動作を示しています。

delay()はタイマーと何が違う?

Arduinoを使い始めたとき、時間を扱う方法として最初に登場することが多いのがdelay()です。

digitalWrite(LED_PIN, HIGH);
delay(1000);

digitalWrite(LED_PIN, LOW);
delay(1000);

このプログラムでは、LEDを1秒点灯し、その後1秒消灯します。

delay()は非常に簡単ですが、待っている間はその処理の流れを先へ進めることができません。

たとえば1秒待っている間に、別のボタン入力を確認したり、ほかの周期処理を行ったりしたい場合には不便になることがあります。

millis()を使えば処理を止めずに時間を測れる

Arduinoではmillis()を使って、プログラム開始後からの経過時間を取得できます。

これを利用すると、delay()のように処理を止めずに一定時間が経過したかを判断できます。

const int LED_PIN = 8;

unsigned long previousMillis = 0;
const unsigned long interval = 1000;

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {

    previousMillis = currentMillis;

    digitalWrite(
      LED_PIN,
      !digitalRead(LED_PIN)
    );
  }

  // ここで他の処理も実行できる
}

この方法なら、LEDの点滅周期を確認しながら、同じloop()の中でボタン入力や通信などの別の処理も実行できます。

millis()とハードウェアタイマーは同じもの?

millis()を使った時間管理と、ハードウェアタイマーを直接利用する方法は、プログラム上の使い方が異なります。

millis()では、通常処理の中で現在時刻を取得し、「前回から一定時間が経過したか」をプログラム側で判断します。

一方、ハードウェアタイマーでは、マイコン内部のタイマー周辺回路がカウントを行い、設定した条件になったときにイベントや割り込みを発生させることができます。

特徴
delay()指定時間だけ処理の流れを待たせる
millis()経過時間を取得し、通常処理側で時間を判断する
ハードウェアタイマータイマー周辺回路でカウントし、イベントなどを発生させる

ハードウェアタイマーの仕組み

ハードウェアタイマーは、マイコン内部のクロックをもとにカウントを進めます。

ただし、CPUのクロックをそのまま1回ずつ数えるとは限りません。タイマーによってはプリスケーラ(分周器)を使ってクロック周波数を下げてからカウントします。

たとえば、タイマーへ入力されるクロックを1MHzにすると、1カウントは次のようになります。

1 ÷ 1,000,000 = 1μs

この場合、1000カウントすれば約1ms、1,000,000カウントすれば約1秒というように時間を管理できます。

実際のクロック源、分周方法、カウンタ幅などはマイコンやタイマーによって異なります。

タイマー割り込みとは?

タイマーと組み合わせてよく使われるのがタイマー割り込みです。

タイマーが設定した条件に到達すると割り込みを発生させ、ISR(割り込みサービスルーチン)を実行します。

たとえば1msごとにタイマー割り込みを発生させれば、一定周期で処理を行うための基準として利用できます。

通常処理
   ↓
通常処理
   ↓
タイマー割り込み
   ↓
ISR
   ↓
通常処理へ戻る
   ↓
通常処理
   ↓
タイマー割り込み
   ↓
ISR

割り込みについては「割り込み処理とは?ポーリングとの違いと仕組みを初心者向けに解説」でも詳しく解説しています。

タイマー割り込みのISRは短くする

タイマー割り込みでも、GPIO割り込みと同じようにISRの処理はできるだけ短くすることが基本です。

ISR内で長時間処理すると、通常処理やほかの割り込みへ影響する可能性があります。

そのため、ISRではフラグやカウンタを更新し、時間のかかる処理は通常処理側で行う方法がよく使われます。

volatile bool timerFlag = false;

void timerISR() {
  timerFlag = true;
}

通常処理側でフラグを確認します。

void loop() {

  if (timerFlag) {

    timerFlag = false;

    // 時間のかかる処理はこちらで実行
  }
}

タイマーは何に使われる?

タイマーは組み込み開発のさまざまな場所で利用されています。

  • 一定周期でLEDを点滅させる
  • 一定周期でセンサーを読み取る
  • 経過時間を測定する
  • タイムアウトを判定する
  • 一定時間後に処理を実行する
  • PWM信号を生成する
  • 通信のタイミングを管理する
  • 周期的な制御処理を実行する

特に「一定周期で何かを実行する」という処理では、タイマーが重要な役割を持っています。

PWMとタイマーの関係

以前解説したPWMも、マイコン内部ではタイマーなどの周辺回路と深く関係しています。

一定周期の中でHIGHとLOWの時間を切り替えることで、PWM信号を生成できます。

ただし、実際にどのタイマーや専用PWM周辺回路を使用するかはマイコンによって異なります。

PWMについては「PWMとは?デューティ比・周波数と仕組みを初心者向けに解説」で詳しく解説しています。

KUMITATE-C3で時間を使ったLED点滅を試す

KUMITATE-C3に搭載されているESP32-C3でも、時間を使った処理やハードウェアタイマーを利用できます。

まずはタイマーの考え方を理解するため、millis()を使って処理を止めずにLEDを点滅させてみましょう。

const int LED_PIN = 0;

unsigned long previousMillis = 0;
const unsigned long interval = 500;

bool ledState = false;

void setup() {

  pinMode(LED_PIN, OUTPUT);
}

void loop() {

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {

    previousMillis = currentMillis;

    ledState = !ledState;

    digitalWrite(LED_PIN, ledState);
  }

  // ここでボタン入力など
  // 別の処理も実行できる
}

このプログラムではdelay()を使用していないため、LEDの点滅を行いながら、loop()内で別の処理を実行できます。

ESP32-C3のハードウェアタイマー

ESP32-C3には、ソフトウェアから利用できるハードウェアタイマーがあります。

Arduino-ESP32ではタイマー用APIが用意されており、一定周期で割り込みを発生させる処理などを実装できます。

ただし、Arduino-ESP32のタイマーAPIはCoreのバージョンによって書き方が変更されているため、実際にプログラムを作成するときは使用しているArduino-ESP32のバージョンに対応したドキュメントを確認してください。

タイマーの学習では、まずdelay()とmillis()の違いを理解し、その後ハードウェアタイマーとタイマー割り込みへ進むと理解しやすくなります。

delay・millis・タイマーはどう使い分ける?

方法向いている用途
delay()簡単な待ち時間、学習用の単純な処理
millis()処理を止めずに複数の処理を動かしたい場合
ハードウェアタイマー周辺回路による時間計測や一定周期のイベントが必要な場合
タイマー割り込み一定周期で割り込み処理を発生させたい場合

単純なプログラムだからといってdelay()を使ってはいけないわけではありません。必要な処理やタイミング精度、プログラム全体の構成に応じて適切な方法を選びます。

タイマーを使うときの注意点

タイマーを利用するときには、いくつか注意点があります。

  • タイマーの数には限りがある
  • タイマーの仕様はマイコンによって異なる
  • 使用できるクロック源や分周方法を確認する
  • カウンタの最大値を確認する
  • タイマー割り込みのISRを長くしない
  • ほかのライブラリが同じタイマー資源を利用している場合がある

特にPWMや通信ライブラリなどが内部でハードウェア資源を使用する場合があるため、複雑なプログラムでは周辺回路の割り当ても意識する必要があります。

まとめ

タイマーは、マイコン内部で時間を計測したり、一定のタイミングでイベントを発生させたりするための重要な機能です。

  • ハードウェアタイマーはクロックをもとに時間を計測する
  • delay()は簡単だが、待っている間は処理の流れが止まる
  • millis()を使えば処理を止めずに経過時間を確認できる
  • タイマーはカウンタを使って時間を管理する
  • プリスケーラを使ってタイマーのクロックを調整する場合がある
  • 設定した条件でタイマー割り込みを発生させることができる
  • ISRはできるだけ短くする
  • タイマーはLED点滅、センサー取得、PWM、タイムアウトなど幅広く使われる

タイマーを理解すると、「プログラムを止めて待つ」のではなく、複数の処理を動かしながら時間を管理する考え方が身につきます。

KUMITATE

読むだけでなく、
実際に動かして学びませんか?

クミタテは、組み込み開発や電子工作を 実践しながら学べる学習プラットフォームです。 ESP32を使ったプログラミングから、電子回路、センサー、 通信、基板設計まで、自分のペースで学習できます。

  • 無料で学べる実践的な教材を掲載
  • すぐに試せるサンプルプログラム付き
  • Googleアカウントですぐに登録可能
クミタテに無料登録する
組み込み基礎
スポンサーリンク