基礎 · 読了まで13分

Arduinoのmillis()とdelay():コードを止めずに時間を計る

Arduinoのmillis()を学びましょう。delay()でスケッチが止まる理由、delayを使わない点滅、複数のタスクの同時実行、49日のオーバーフローに強い時間管理、micros()、ESP32とPicoを解説します。

Arduinoのmillis()関数は、ボードが動き始めてからの時間をミリ秒で返します。delay()を使わずに時間を計る標準的な方法です。delay(1000)と書くと、Arduinoはまる1秒間ほかに何もしません。ボタンを押しても気づかれず、センサーも読まれず、ディスプレイも更新されません。millis()では代わりに「前の処理からどれだけ時間がたったか」を尋ねます。時間が来た処理は実行し、まだの処理は飛ばし、loop()は止まりません。こうして1台のArduinoで、LEDの点滅、ボタンの読み取り、センサーの測定、シリアルポートへの報告を同時にこなせます。

このガイドでは、delay()がプログラムを固まらせる理由、有名なdelayを使わない点滅の例、複数のタスクを同時に動かす方法、49.7日後に起こるオーバーフローを安全に乗り切る方法、マイクロ秒を数えるmicros()関数、再利用できるタイマーのパターンを学びます。最後の章では、同じ考え方をESP32とRaspberry Pi Pico(MicroPython)でどう書くかを見ていきます。

Arduino Unoボードと内蔵のL LED
Arduino Uno:13番ピンの内蔵「L」LEDのおかげで、追加の部品なしでmillis()を試せます。

delay()とは?なぜプログラムが止まるのか

delay(ms)は、指定した時間がたつまでプロセッサをループの中で空回りさせます。その間、loop()の次の行には進みません。これをブロッキング待機と呼びます。LEDを1個点滅させるだけなら問題ありませんが、プロジェクトにボタンを加えた瞬間に問題が始まります。次のコードは、LEDを1秒ごとに点滅させながら、ボタンが押されたかも確認します。

C++ (Arduino IDE)
1const int LED_PIN = 13;                   // dahili LED / built-in LED
2const int BUTON_PIN = 2;                  // buton, diğer ucu GND / button, other leg to GND
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6  pinMode(BUTON_PIN, INPUT_PULLUP);
7  Serial.begin(9600);
8}
9
10void loop() {
11  digitalWrite(LED_PIN, HIGH);
12  delay(1000);                            // 1 sn bekle, buton okunmaz / wait 1 s, button is not read
13  digitalWrite(LED_PIN, LOW);
14  delay(1000);
15  if (digitalRead(BUTON_PIN) == LOW) {    // 2 saniyede bir bakılır / checked once every 2 s
16    Serial.println(F("Butona basildi / Button pressed"));
17  }
18}

このコードでは、ボタンに気づくのは2秒に1回だけで、しかもちょうどその瞬間に押していた場合だけです。短く押すと、2つのdelay()の間で取りこぼされます。delay()の間も割り込みは動き続け、millis()は数え続け、シリアルポートに届いたバイトは64バイトのバッファにたまります。しかし、コードは待っています。ディスプレイ、センサー、ブザーを加えていくと、delay()のたびにほかのすべての処理が止められ、機器の反応が鈍く感じられます。解決策は、待つのをやめて時間を計ることです。

millis()とは?そのしくみ

millis()は、ボードの電源が入るかリセットされてからのミリ秒数をunsigned long(32ビット、符号なし)で返します。Arduino Unoでは、このカウンターはハードウェアタイマーTimer0のオーバーフロー割り込みによってバックグラウンドで更新されます。16 MHzではTimer0は1.024 msごとにオーバーフローし、コアのコードは余分な0.024 msを積み上げて、約43 msごとにカウンターを1ではなく2進めます。つまり、millis()はいくつかの値を完全に飛ばすので、millis() == 5000のような等号での判定は当てになりません。必ず>=を使ってください。また、割り込みが無効の間(たとえば割り込みルーチンの中)はmillis()が進まないことにも注意しましょう。

関数働き分解能(Uno、16 MHz)オーバーフローまでプログラムを止める?
delay(ms)指定したミリ秒だけ待つ1 ms-はい
delayMicroseconds(us)短い待機(3〜16383 µsで正確)約1 µs-はい
millis()起動からのミリ秒1 ms(ときどき2ずつ進む)約49.7日いいえ
micros()起動からのマイクロ秒4 µs約71.6分いいえ

delayを使わない点滅:millis()でLEDを点滅させる

  1. 処理を最後に行った時刻を覚えておくunsigned longの変数(oncekiZaman)を作ります。
  2. loop()を通るたびに、millis()で現在の時刻を読みます。
  3. simdi - oncekiZaman >= ARALIKという条件で、十分な時間がたったかを確認します。
  4. 時間が来ていれば処理を行い(LEDを切り替え)、oncekiZamanを更新します。
  5. 時間が来ていなければ何もせず、loop()を先に進めます。ほかのタスクが動くのはまさにこのときです。
C++ (Arduino IDE)
1const int LED_PIN = LED_BUILTIN;          // Uno'da 13 / pin 13 on the Uno
2const unsigned long ARALIK = 500;         // yanıp sönme aralığı (ms) / blink interval (ms)
3
4unsigned long oncekiZaman = 0;            // son değişim anı / time of the last toggle
5bool ledDurumu = false;
6
7void setup() {
8  pinMode(LED_PIN, OUTPUT);
9}
10
11void loop() {
12  unsigned long simdi = millis();         // şu anki zaman / current time
13  if (simdi - oncekiZaman >= ARALIK) {    // taşmaya karşı güvenli / overflow-safe
14    oncekiZaman += ARALIK;                // kaymayı önler / prevents drift
15    ledDurumu = !ledDurumu;
16    digitalWrite(LED_PIN, ledDurumu);
17  }
18  // burada başka işler beklemeden çalışır / other work runs here without waiting
19}

コードではoncekiZaman = simdiではなくoncekiZaman += ARALIKと書きました。違いは小さいものの重要です。loop()がときどき数ミリ秒遅れると、前者ではその遅れが毎回加わってLEDが何時間もかけてずれていきますが、後者は目標の時刻を一定の幅で進めるので、平均の周期がぴったり500 msに保たれます。時計、メトロノーム、定期的な測定には+=を使いましょう。プログラムが長時間止まる可能性がある場合(たとえばWi-Fiを待っている間)、+=は取り逃した分を次々に取り戻そうとするので、その場合は= simdiのほうが安全です。コードを止めずにLEDの明るさも変えたいなら、PWMとanalogWriteの記事を見てみてください。

複数のことを同時に:millis()によるマルチタスク

Arduino Unoのプロセッサはシングルコアで、本当の意味で2つのことを同時にはできません。しかし、loop()は1秒間に何万回も回れるので、順番が来たタスクに数マイクロ秒ずつ与えれば、外からは「同時」に見えます。これを協調的マルチタスクと呼びます。ルールは簡単で、どのタスクも自分の時間変数と間隔を持ち、どのタスクも長く待たないことです。次の例では4つのタスクが並んで動きます。

C++ (Arduino IDE)
1const int KIRMIZI_LED = 8;                // 300 ms'de bir yanıp söner / blinks every 300 ms
2const int YESIL_LED = 9;                  // 1000 ms'de bir yanıp söner / blinks every 1000 ms
3const int SARI_LED = 10;                  // butonla açılıp kapanır / toggled by the button
4const int BUTON = 2;                      // diğer ucu GND / other leg to GND
5
6unsigned long kirmiziZaman = 0, yesilZaman = 0, butonZaman = 0, seriZaman = 0;
7bool kirmizi = false, yesil = false, sari = false;
8int sonOkuma = HIGH;                      // son ham okuma / last raw reading
9int kararliDurum = HIGH;                  // titreşimsiz durum / debounced state
10
11void setup() {
12  pinMode(KIRMIZI_LED, OUTPUT);
13  pinMode(YESIL_LED, OUTPUT);
14  pinMode(SARI_LED, OUTPUT);
15  pinMode(BUTON, INPUT_PULLUP);
16  Serial.begin(9600);
17}
18
19void loop() {
20  unsigned long simdi = millis();
21
22  // Görev 1: kırmızı LED / Task 1: red LED
23  if (simdi - kirmiziZaman >= 300) {
24    kirmiziZaman = simdi;
25    kirmizi = !kirmizi;
26    digitalWrite(KIRMIZI_LED, kirmizi);
27  }
28
29  // Görev 2: yeşil LED / Task 2: green LED
30  if (simdi - yesilZaman >= 1000) {
31    yesilZaman = simdi;
32    yesil = !yesil;
33    digitalWrite(YESIL_LED, yesil);
34  }
35
36  // Görev 3: 50 ms debounce ile buton / Task 3: button with 50 ms debounce
37  int okuma = digitalRead(BUTON);
38  if (okuma != sonOkuma) {                // değişti, sayacı yeniden başlat / changed, restart the timer
39    butonZaman = simdi;
40    sonOkuma = okuma;
41  }
42  if (simdi - butonZaman >= 50 && okuma != kararliDurum) {
43    kararliDurum = okuma;
44    if (kararliDurum == LOW) {            // basıldı / pressed
45      sari = !sari;
46      digitalWrite(SARI_LED, sari);
47    }
48  }
49
50  // Görev 4: saniyede bir seri rapor / Task 4: serial report once per second
51  if (simdi - seriZaman >= 1000) {
52    seriZaman = simdi;
53    Serial.print(F("Calisma suresi (s) / Uptime (s): "));
54    Serial.println(simdi / 1000);
55  }
56}
タスクピン間隔働き
赤LED8300 ms速く点滅する
緑LED91000 msゆっくり点滅する
ボタン+黄LED2と1050 msのチャタリング対策押すたびに黄LEDを切り替える
シリアルへの報告-1000 ms稼働時間を秒で表示する

ボタンの処理もmillis()を使っています。生の読み取り値が変わるたびにタイマーをやり直し、読み取り値が50 ms安定していたら本当の押下として受け付けます。こうすれば、ボタンの機械的なチャタリングで1回の押下が何十回にもなることがなく、コードも待ちません。ボタンの配線と内部プルアップ抵抗のくわしい説明はArduinoに押しボタンをつなぐ方法を参照してください。LEDには220 Ωの抵抗で十分です。ほかの色や電流についてはLED抵抗計算ツールを使ってください。

49.7日のオーバーフローと、オーバーフローに強い比較

unsigned longが保持できるのは最大4,294,967,295です。millis()がこの数に達すると、約49日17時間2分後に、再び0から数え始めます。ボードを何週間もつけっぱなしにするなら(温室の制御、水槽、警報システム)、その瞬間は必ず来ます。うれしいことに、simdi - onceki >= aralikの形で引き算を使って書いたコードは、オーバーフローの影響を受けません。符号なしの引き算は2の32乗を法として計算されるからです。たとえば、onceki = 4,294,967,000で、オーバーフロー後にsimdi = 200なら、simdi - onceki は負の数にはならず、正しい結果の496になります。つまり、待つ間隔が49.7日より短い限り、比較は常に正しく動きます。

コード結果理由
if (millis() - onceki >= aralik)(引き算の形)正しい符号なしの引き算は、オーバーフローをまたいでも正しい差を返す
if (millis() >= onceki + aralik)(足し算の形)間違いonceki + aralikがオーバーフローすると小さな数になり、条件が早すぎるタイミングで成り立つ
if (millis() == hedef)(等号での判定)間違いmillis()はときどき2ずつ進むので、その値がちょうど現れないことがある
int onceki = millis();(int型)間違いUnoではintは16ビットで、32.7秒を過ぎると間違った値になる
long onceki = millis();(long型)危険符号付きの型は24.8日を過ぎると負になる。混乱を避けるためunsigned longを使う
時間の変数をintで宣言することは絶対に避けてください。Arduino Unoではintは16ビットで、最大32,767までしか保持できないため、33秒目からプログラムがおかしな動きを始めます。millis()やmicros()を扱う変数はすべてunsigned long(またはuint32_t)にしましょう。大きな間隔を書くときも60000ULの形を使ってください。Unoでは60 * 1000のような積はintの上限を超え、負の数になってしまいます。

micros()でマイクロ秒を測る

micros()も同じしくみですが、起動からのマイクロ秒を返します。Arduino UnoやNanoのような16 MHzのボードでは分解能は4 µs(8 MHzのボードでは8 µs)で、約71.6分ごとにオーバーフローします。ここでも引き算のルールが当てはまります。コードの一部にかかる時間の測定、短いパルスの計時、音の生成など、精密な処理に使われます。次の例は、analogRead()の呼び出しにかかる時間を1秒に2回測ります。Unoでは約110〜120 µsと表示されます。

C++ (Arduino IDE)
1unsigned long raporZaman = 0;
2
3void setup() {
4  Serial.begin(9600);
5}
6
7void loop() {
8  if (millis() - raporZaman >= 500) {         // yarım saniyede bir ölç / measure every half second
9    raporZaman = millis();
10    unsigned long basla = micros();           // başlangıç (µs) / start (µs)
11    int deger = analogRead(A0);
12    unsigned long sure = micros() - basla;    // geçen süre (µs) / elapsed time (µs)
13    Serial.print(F("A0 = "));
14    Serial.print(deger);
15    Serial.print(F("  sure / time: "));
16    Serial.print(sure);
17    Serial.println(F(" us"));
18  }
19}

長い間隔にはmicros()よりmillis()を使いましょう。71分というオーバーフローの周期は、長い待ち時間には短すぎます。delayMicroseconds()は、HC-SR04距離センサーのTRIGピンに10 µsのトリガーパルスを送るときのような、数マイクロ秒の避けられない短い待ち時間専用です。その程度の短い待ちなら、ほかのタスクが目に見えて遅れることはありません。

タイマーのパターン:周期、ワンショット、ステートマシン

  • 周期タスク:LEDの点滅、2秒ごとのセンサーの読み取り、1秒ごとのディスプレイの更新のように、決まった間隔で繰り返す処理です。パターンはすでに上で見ました。
  • ワンショットタイマー(タイムアウト):あるイベントから決まった時間のあとに1回だけ動きます。階段の照明がよい例です。動きがあったら照明を点けて時刻を記録し、30秒間新しい動きがなければ消します。新しい動きのたびに時刻を更新すれば、点灯時間が延びます。
  • タイムアウト付きの待機:返事や信号を待つときは上限を設け、いつまでも待ち続けないようにします。たとえば、2秒以内にデータが届かなければエラーを表示します。
  • ステートマシン:機器の現在のステップを変数に持ち、各ステップの時間が来たら次のステップに進みます。信号機、洗濯機のプログラム、ゲームのステージはこの方法で書かれます。
  • 長押しの検出:ボタンが押されている時間をmillis()で測れば、短押しと長押しに別々の役割を与えられます。
C++ (Arduino IDE)
1// Yeniden kullanılabilir zamanlayıcı / Reusable timer
2struct Zamanlayici {
3  unsigned long baslangic = 0;
4  unsigned long sure = 0;
5
6  void kur(unsigned long ms) {              // süreyi şimdiden başlat / start the period from now
7    baslangic = millis();
8    sure = ms;
9  }
10  bool doldu() const {                      // süre doldu mu? / has it expired?
11    return millis() - baslangic >= sure;
12  }
13};
14
15const int KIRMIZI = 11, SARI = 12, YESIL = 13;
16enum Durum { DUR, HAZIRLAN, GEC, YAVASLA }; // trafik lambası adımları / traffic light states
17Durum durum = DUR;
18Zamanlayici isik;
19
20void lambalar(bool k, bool s, bool y) {
21  digitalWrite(KIRMIZI, k);
22  digitalWrite(SARI, s);
23  digitalWrite(YESIL, y);
24}
25
26void setup() {
27  pinMode(KIRMIZI, OUTPUT);
28  pinMode(SARI, OUTPUT);
29  pinMode(YESIL, OUTPUT);
30  lambalar(true, false, false);
31  isik.kur(5000);                           // kırmızı 5 sn / red for 5 s
32}
33
34void loop() {
35  if (isik.doldu()) {
36    switch (durum) {
37      case DUR:      durum = HAZIRLAN; lambalar(true, true, false);  isik.kur(1500); break;
38      case HAZIRLAN: durum = GEC;      lambalar(false, false, true); isik.kur(5000); break;
39      case GEC:      durum = YAVASLA;  lambalar(false, true, false); isik.kur(2000); break;
40      case YAVASLA:  durum = DUR;      lambalar(true, false, false); isik.kur(5000); break;
41    }
42  }
43  // loop serbest: buton, sensör, ekran... / loop stays free: buttons, sensors, display...
44}

このコードでは、Zamanlayiciという小さな構造体(struct)が、周期の開始と、時間が来たかの確認をそれぞれ1行にまとめています。信号機は4つのステップのステートマシンで、各ステップでランプを設定し、次の切り替えまでの時間を開始します。loop()は決して待たないので、同じボードに歩行者用ボタン、夜間モード用のLDR光センサー、残り時間を表示するディスプレイを簡単に加えられます。多くのタイマーが必要なら、ライブラリマネージャにある既製のタイマーライブラリも同じ原理で動いています。

ESP32のmillis()、delay()、Ticker

ESP32のArduinoコアでは、millis()とmicros()は同じ名前で、同じ32ビットのunsigned longの型です。値は64ビットのesp_timer_get_time()カウンターから計算されます。つまり、49.7日と71.6分のオーバーフロー周期はESP32でも同じで、同じ引き算のルールを使います。大きな違いはdelay()にあります。ESP32のdelay()はFreeRTOSのvTaskDelay()を呼びます。プロセッサは空回りせず、待っている間もWi-FiやBluetoothなどのほかのタスクが動きます。ただし、loop()は待ったままなので、Webサーバー、MQTT、ボタンの読み取りなどの処理は遅れます。周期的な処理には、ESP32コアに付属するTickerライブラリも便利です。

C++ (Arduino IDE)
1#include <Ticker.h>
2
3const int LED1 = 2;                         // çoğu DevKit'te dahili LED / built-in LED on most DevKits
4const int LED2 = 4;
5
6Ticker ticker;
7bool led1Durum = false;
8bool led2Durum = false;
9unsigned long oncekiZaman = 0;
10
11void led1Degistir() {                       // kısa tut, uzun iş yapma / keep it short, no long work
12  led1Durum = !led1Durum;
13  digitalWrite(LED1, led1Durum);
14}
15
16void setup() {
17  Serial.begin(115200);
18  pinMode(LED1, OUTPUT);
19  pinMode(LED2, OUTPUT);
20  ticker.attach_ms(250, led1Degistir);      // her 250 ms'de çağır / call every 250 ms
21}
22
23void loop() {
24  unsigned long simdi = millis();
25  if (simdi - oncekiZaman >= 1000) {        // Arduino ile aynı desen / same pattern as on Arduino
26    oncekiZaman = simdi;
27    led2Durum = !led2Durum;
28    digitalWrite(LED2, led2Durum);
29    int64_t us = esp_timer_get_time();      // 64 bit, pratikte taşmaz / 64-bit, practically never overflows
30    Serial.printf("millis=%lu  esp_timer=%lld us\n", simdi, us);
31  }
32}

Tickerに渡した関数は、バックグラウンドのタイマータスクから呼ばれます。その中で時間のかかること(HTTPリクエスト、長いシリアル出力、delay)はせず、フラグを立てるかピンを切り替えるだけにしてください。64ビットのesp_timer_get_time()は何十万年もオーバーフローしないので、何か月もつけっぱなしの機器でマイクロ秒の精度が必要な測定に使えます。内蔵LEDのピンはボードによって異なることがあります。ボードのピン配置はピン配置ツールで確認できます。ESP32が初めてなら、セットアップとピンの選び方にはESP32とは?の記事から始めるのがおすすめです。

Raspberry Pi Pico(MicroPython):ticks_ms()とticks_diff()

MicroPythonでは、delay()の代わりにtime.sleep()、time.sleep_ms()、time.sleep_us()を、millis()の代わりにtime.ticks_ms()を使います。ただし注意が必要です。ticksの値はArduinoのように2の32乗で一周するのではなく、ポートによって異なるもっと小さな上限で一周します。Picoではその上限は2の30乗ms、約12.4日です(ボードでtime.ticks_add(0, -1)を実行すれば確認できます)。そのため、2つのticksの値を直接引いたり比べたりせず、差はtime.ticks_diff(new, old)で求め、未来の時刻の計算にはtime.ticks_add()を使ってください。次のコードは、GP14のボタンでGP15のLEDを切り替えながら、Picoのボード上のLEDをブロッキングなしで点滅させます。

MicroPython
1from machine import Pin
2import time
3
4led = Pin("LED", Pin.OUT)                       # dahili LED (Pico ve Pico W) / onboard LED (Pico and Pico W)
5dis_led = Pin(15, Pin.OUT)                      # GP15'e harici LED / external LED on GP15
6buton = Pin(14, Pin.IN, Pin.PULL_UP)            # GP14 buton, diğer ucu GND / GP14 button, other leg to GND
7
8ARALIK = 500                                    # ms
9onceki = time.ticks_ms()
10son_okuma = 1                                   # son ham okuma / last raw reading
11kararli = 1                                     # titreşimsiz durum / debounced state
12degisim = time.ticks_ms()                       # son değişim anı / time of the last change
13
14while True:
15    simdi = time.ticks_ms()
16    if time.ticks_diff(simdi, onceki) >= ARALIK:   # taşmaya karşı güvenli / wrap-safe
17        onceki = time.ticks_add(onceki, ARALIK)    # kaymasız / drift-free
18        led.toggle()
19    okuma = buton.value()
20    if okuma != son_okuma:                      # değişti, sayacı yeniden başlat / changed, restart the timer
21        degisim = simdi
22        son_okuma = okuma
23    if time.ticks_diff(simdi, degisim) >= 50 and okuma != kararli:   # 50 ms debounce
24        kararli = okuma
25        if kararli == 0:                        # basıldı / pressed
26            dis_led.toggle()
27

周期的な処理のために、MicroPythonにはmachine.Timerもあります。Timer(period=500, mode=Timer.PERIODIC, callback=関数)の行で、500 msごとに関数が呼ばれます。Tickerと同じく、これらのコールバックは短くしてください。Arduino IDEで(arduino-picoコアを使って)Picoをプログラムするなら、millis()とmicros()はArduinoとまったく同じように動きます。PicoにMicroPythonをインストールし、ピンを知るにはRaspberry Pi Picoとは?の記事を参照してください。

millis()を使ったプロジェクトアイデア

  • 反応速度テスト:ランダムな瞬間にLEDを点け、ボタンが押されるまでの時間をmillis()で測り、結果をミリ秒で表示します。
  • 階段の照明:PIRセンサーが動きを検知したら照明を点け、最後の動きから30秒後に消します。その間もコードはほかの処理を続けます。
  • だんだん速くなる駐車センサー:HC-SR04で測った距離が短くなるほど、ブザーの鳴る間隔を短くします。
  • 止まらないメロディー:delay()ではなくmillis()でブザーの音を順番に鳴らし、曲の演奏中もボタンが使えるようにします。
  • センサーとディスプレイを同時に:DHT11センサーは2秒ごとに読み、ディスプレイは250 msごとに更新します。
  • ストップウォッチ:スタート、ストップ、リセットのボタンを備え、7セグメントディスプレイに時間を表示するストップウォッチを作ります。

よくある間違いと対処法

間違い症状対処法
時間の変数をintで宣言した約33秒後にプログラムがおかしくなる時間の変数はすべてunsigned longにする
millis() >= onceki + aralikと書いた49.7日後に時間管理が狂うmillis() - onceki >= aralikの形に変える
millis() == hedefで判定したイベントがときどき起きない等号の代わりに>=を使う
oncekiの更新を忘れた時間が来たあと、処理が毎回繰り返される条件の中でonceki = simdiかonceki += aralikと書く
タイマーのコードの中にdelay()が残っているほかのタスクがやはり遅れる長い待ち時間はステートマシンに分ける
割り込みの中でmillis()を使って待ったボードが固まるISRの中では決して待たず、フラグを立てるだけにする
60 * 1000のようなintの積間隔が負になるか、短すぎる60000ULか60UL * 1000と書く
コードが本当にブロッキングなしで動いているかを確かめる簡単な方法は、loop()が1秒間に何回回るかを数えることです。毎回カウンターを1増やし、1秒ごとにシリアルポートに表示してリセットします。数が何千から急に1桁に落ちたら、どこかで何かが待っています(delay、pulseIn、長いwhileループ)。

millis()を使いこなせるようになると、プロジェクトががらりと変わります。LED、ボタン、センサー、ディスプレイが、互いを待つことなく一緒に動くようになります。次のステップとして、Arduinoのプロジェクトアイデアの一覧から時間管理が必要なプロジェクトを選んでもよいでしょう。BlueGraysアプリでは、信号機、パトランプ、スマート階段・廊下照明、音で知らせる反応速度テストなどのプロジェクトを、回路図と既製のコード付きでステップごとに学び、自分のボードで試せます。

よくある質問

Arduinoのmillis()とは?

millis()は、Arduinoの電源が入るかリセットされてからの時間をミリ秒で返す関数です。値はunsigned longで、約49.7日後に再び0から始まります。プログラムを止めずに時間を計るのに使います。

millis()とdelay()の違いは?

delay()は指定した時間だけプログラムを止めるので、その間はボタンの読み取りやディスプレイの更新など、ほかのことが何もできません。millis()はどれだけ時間がたったかを教えるだけで、決して待ちません。経過時間を確認して時間が来た処理を実行すれば、複数のタスクを同時に動かせます。

49日後にmillis()がオーバーフローするとプログラムは壊れますか?

millis() - onceki >= aralikの形で引き算を使って時間を確認していれば壊れません。符号なしの引き算は、オーバーフローをまたいでも正しい差を返します。問題が出るのは、millis() >= onceki + aralikのように足し算で書いた比較と、intの変数を使った場合だけです。

Arduinoでmillis()をリセットできますか?

millis()をリセットする公式の関数はなく、コアの内部変数を書き換えるのもおすすめできません。その必要もありません。始めたい瞬間を変数に保存し、そこから経過時間を計算すればよいのです。ボードをリセットすれば、millis()は当然0からやり直しになります。

Arduinoは2つのことを同時にできますか?

Unoはシングルコアなので、物理的に2つの命令を同時に実行することはできません。しかし、millis()で各処理を短いステップに分ければ、loop()は1秒間に何千回も回り、どの処理にも順番が回ってきます。外からは、LED、ボタン、センサー、ディスプレイがすべて同時に動いているように見えます。ESP32にはさらに2つのコアとFreeRTOSのタスクがあります。

millis()は時計を作れるほど正確ですか?

millis()はボードの発振子に依存しており、Arduino Unoではセラミック発振子のため、1日に数分までずれることがあります。短い間隔を測るには十分ですが、何日も正確であるべき時計には、DS1307やDS3231のようなRTCモジュールか、ESP32でNTPから時刻を取得する方法のほうが正確です。

全プロジェクトを見る
LEDの点滅
#1初級

LEDの点滅

電子工作の第一歩は、1個のLEDを点けたり消したりすることです。Arduinoで短い間隔で点灯と消灯を繰り返すと、LEDが素早く点滅します。ボードが光を制御する仕組みを最もシンプルな形で確かめ、これから取り組むプロジェクトの基礎を身につけます。

4.6(188)
1127
信号機
#3初級

信号機

毎日道路で見かける信号機の小さな模型です。赤が2秒点灯し、黄が一瞬点灯した後、緑が2秒点灯します。その後、再び黄を経て赤に戻り、この動作を繰り返します。3個のLEDを順番に点けたり消したりしながら、タイミングの組み立て方を学びます。

4.8(131)
397
LEDの警察車両風点滅
#4初級

LEDの警察車両風点滅

警察車両の警光灯のように、赤いLEDが素早く3回点滅し、続いて青いLEDが3回点滅します。このパターンを途切れることなく繰り返します。Arduinoに2個のLEDと2個の抵抗をつなぐだけで、数分で組み立てられる、目を引く入門プロジェクトです。

4.7(103)
258
速度調整できる流れるLED
#7中級

速度調整できる流れるLED

6個のLEDが順番に点滅し、光が端から端へ走るように見えます。ポテンショメーターのつまみを回すと、その速さがすぐに変わります。一方に回すと光が素早く進み、反対に回すと各LEDが1秒近く点灯します。つまみで動きの速さを調整する仕組みを目で確かめられます。

4.6(72)
146
ボタンでLEDの点灯・消灯を切り替え
#10初級

ボタンでLEDの点灯・消灯を切り替え

部屋の照明スイッチのように、ボタンを1回押すと点灯し、もう1回押すと消灯します。Arduinoはボタンが押されるたびにLEDの状態を反転させます。ボタン内部の小さな接点の振動で誤動作しないよう、短い待ち時間も設けます。1つのボタンでオン・オフを切り替える、シンプルな仕組みです。

4.8(100)
167
階段・廊下のスマート照明
#82中級
プレミアム

階段・廊下のスマート照明

階段や廊下に足を踏み入れると、進む方向に沿って照明が1段ずつ点灯します。両端に距離センサーがあり、どちらの前を通っても、その端から8個のLEDが0.5秒間隔で順番に点灯し、その後、同じ方向に順番に消灯します。夜の暗がりで足元を照らす、おしゃれなホームオートメーションのアイデアです。

5.0(35)
51
Arduino 音で知らせる反応速度テスト:反射神経をミリ秒で測ろう(LCD+ブザー+LED 3個)
#97中級
プレミアム

Arduino 音で知らせる反応速度テスト:反射神経をミリ秒で測ろう(LCD+ブザー+LED 3個)

反射神経はどれくらい速いでしょうか。ボタンを押すと、赤LEDが位置について、黄LEDが用意を知らせ、1.5〜3.5秒のランダムな待ち時間が始まります。緑LEDが光ってブザーが鳴った瞬間にもう一度押すと、反応時間がI2C LCDにミリ秒で表示され、下の行にその回の最高記録と平均が出ます。緑より先に押すと画面にTOO EARLY!と表示され、残念な音が鳴ります。ボタンを長押しすると新しいプレイヤーになり、全員が同じ待ち時間の順番で勝負できます。

4.6(33)
50
4桁の7セグメントディスプレイで作るデジタル時計
#48中級
プレミアム

4桁の7セグメントディスプレイで作るデジタル時計

キッチンのオーブンの時計のように、4桁の数字が光るデジタル時計です。DS1302時計モジュールが時刻を保持し、Arduinoが毎秒、時と分を読み取って4桁のディスプレイに表示します。時と分の間には小さな点が点灯します。時刻を保持するモジュールとディスプレイを組み合わせる方法を学びます。

4.8(56)
73