基礎 · 読了まで18分

Arduinoの命令一覧:書き方、例、よくある間違い

Arduinoの命令を解説します。pinMode、digitalWrite、analogRead、analogWrite、millis、Serial、map、tone、pulseIn、attachInterruptを、例と落とし穴とあわせて紹介します。

Arduinoの命令は、ボードに何をするかを伝える既製の関数です。pinMode()はピンを出力にし、digitalWrite()はLEDを点け、analogRead()はセンサーを読み、Serial.println()はパソコンにメッセージを送ります。Arduinoのスケッチのほとんどは、実は20個ほどの基本の命令をいろいろに組み合わせたものにすぎません。これらの命令とその意味がわかれば、LEDの点滅からロボットカーまで、目にするどんなコードも読めるようになり、自分のプロジェクトを一から書けるようになります。

このガイドでは、よく使うArduinoの命令を入出力、時間、算術、通信の4つに分類しました。命令ごとに、書き方、パラメーターの意味、Arduino Unoでコンパイルして動く短い例、初心者が最もよくする間違いを紹介します。最後の章では、同じ命令がESP32ではどう違う動きをするか、Raspberry Pi PicoのMicroPythonでは何に相当するかも見ていきます。

デジタルピンとアナログピンのあるArduino Unoボード
Arduino Uno:デジタルピン0〜13、アナログ入力A0〜A5、~の付いたPWMピンがあれば、この記事のすべての命令を試せます。

Arduinoの命令とは?スケッチはどう動く?

ArduinoのコードはC/C++の上に作られていて、「命令」と呼ぶもののほとんどは、実はArduinoのコアが用意してくれている関数です。関数はかっこの中にパラメーター(たとえばピン番号と値)を受け取り、結果を返すものもあります。digitalRead()はピンの状態を、millis()は経過時間を返します。どのArduinoのスケッチにも、必須の関数が2つあります。setup()はボードの電源が入ったときかリセットされたときに1回だけ動き、そのあとloop()がずっと繰り返されます。ピンの設定やSerial.begin()などの準備はsetup()に、ずっと続けて行う仕事はloop()に書きます。

C++ (Arduino IDE)
1// Her Arduino programı bu iki fonksiyondan oluşur / Every Arduino sketch has these two functions
2void setup() {
3  // Açılışta ya da resetten sonra bir kez çalışır / Runs once at power-up or after reset
4  pinMode(13, OUTPUT);      // dahili LED pini çıkış / built-in LED pin as output
5}
6
7void loop() {
8  // Sonsuza kadar tekrar eder / Repeats forever
9  digitalWrite(13, HIGH);   // LED'i yak / turn the LED on
10  delay(500);               // 500 ms bekle / wait 500 ms
11  digitalWrite(13, LOW);    // LED'i söndür / turn the LED off
12  delay(500);
13}

最初から3つのルールを覚えておくと、多くのコンパイルエラーを防げます。命令は大文字と小文字を区別します(digitalwriteと書くと「'digitalwrite' was not declared in this scope」(宣言されていない)というエラーになります)。どの文もセミコロンで終わります。文字列はダブルクォートで、1文字はシングルクォートで囲みます。変数の型やifやforなどの構造については、Arduinoのプログラミングの基本を見てみましょう。

Arduinoの命令一覧:分類別の表

下の4つの表で、ふだんのプロジェクトで使うほぼすべての命令をまとめています。1つ目のグループは入出力の命令で、ピンの役割を決めたり、電圧をかけたり、値を読んだりします。ボードのピン番号はピン配置ツールで確認できます。

命令書き方働き例
pinModepinMode(pin, mode)ピンを入力か出力にするpinMode(13, OUTPUT);
digitalWritedigitalWrite(pin, value)ピンをHIGH(5 V)かLOW(0 V)にするdigitalWrite(13, HIGH);
digitalReaddigitalRead(pin)ピンがHIGHかLOWかを読むint b = digitalRead(2);
analogReadanalogRead(pin)0〜5 Vの電圧を0〜1023の数値に変えるint v = analogRead(A0);
analogWriteanalogWrite(pin, value)0〜255のPWM信号を作るanalogWrite(9, 128);
analogReferenceanalogReference(type)analogReadの基準電圧を選ぶanalogReference(INTERNAL);
tone / noTonetone(pin, 周波数, 時間)方形波で音を出す。noToneで止めるtone(8, 440, 500);
pulseInpulseIn(pin, 値, タイムアウト)パルスの長さをµsで測るpulseIn(10, HIGH, 30000);
shiftOutshiftOut(dataPin, clockPin, 順番, バイト)1バイトを1ビットずつ送り出す(例:74HC595へ)shiftOut(11, 12, MSBFIRST, 255);
attachInterruptattachInterrupt(割り込み番号, 関数, モード)ピンが変わった瞬間に関数を呼ぶattachInterrupt(digitalPinToInterrupt(2), say, FALLING);

2つ目のグループは時間の命令です。待つdelay()と、時間を測るmillis()やmicros()の違いを理解することが、なめらかに動くスケッチを書く鍵です。

命令書き方働き注意点
delaydelay(ms)指定したミリ秒だけ待つその間プログラムは止まる
delayMicrosecondsdelayMicroseconds(us)マイクロ秒単位で待つ3〜16383 µsの間で正確
millismillis()起動からのミリ秒を返すunsigned long。約49.7日で一周して戻る
microsmicros()起動からのマイクロ秒を返すUnoでは4 µs刻み、約70分で一周して戻る

3つ目のグループは算術の命令で、センサーの値をほかの範囲に変換したり、制限したり、乱数を作ったりするのに使います。4つ目のグループの通信の命令で、ボードはパソコンやほかの機器とデータをやり取りできます。

命令書き方働き結果の例
mapmap(x, fromLow, fromHigh, toLow, toHigh)ある範囲を別の範囲に換算するmap(512, 0, 1023, 0, 255) → 127
constrainconstrain(x, 下限, 上限)値を2つの限界の間に収めるconstrain(300, 0, 255) → 255
min / maxmin(a, b) / max(a, b)小さいほう、または大きいほうの値を返すmin(7, 3) → 3
absabs(x)絶対値abs(-12) → 12
pow / sqrt / sqpow(底, 指数)、sqrt(x)、sq(x)累乗、平方根、2乗pow(2, 10) → 1024.0
randomrandom(最小, 最大)ランダムな整数(最大は含まない)random(1, 7) → 1〜6
randomSeedrandomSeed(seed)乱数の並びの出発点を変えるrandomSeed(analogRead(A3));
命令働き例
Serial.begin(speed)指定したボーレートでシリアルポートを開くSerial.begin(9600);
Serial.print(data)データを表示し、同じ行にとどまるSerial.print(temperature);
Serial.println(data)データを表示し、改行するSerial.println("Hello");
Serial.available()読み取りを待っているバイト数を返すif (Serial.available() > 0)
Serial.read()1バイト読む。データがなければ-1を返すchar c = Serial.read();
Serial.readStringUntil(c)指定した文字までの文字列を読むString s = Serial.readStringUntil('\n');
Serial.parseInt()送られてきた文字列から整数を取り出すint n = Serial.parseInt();
Serial.write(byte)生のバイトを送るSerial.write(65); // 'A'
Wire.begin()I2Cバスを始める(Wire.h)Wire.begin();
SPI.begin()SPIバスを始める(SPI.h)SPI.begin();

pinMode():ピンの役割を決める

pinMode(pin, mode)は、デジタルピンが入力か出力かをボードに伝える命令で、ふつうsetup()に1回書きます。モードは3つあります。OUTPUTはピンを出力にし(LED、ブザー、リレーを動かすため)、INPUTはハイインピーダンスの入力にし(外付けのプルアップ抵抗かプルダウン抵抗のある回路用)、INPUT_PULLUPは入力にして、約20〜50 kΩの内部抵抗を5 Vにつなぎます。アナログピン(A0〜A5)もpinModeでデジタルピンとして使えます。たとえばpinMode(A0, OUTPUT)は正しい命令です。

pinMode(pin, OUTPUT)を忘れてdigitalWrite(pin, HIGH)を使うと、ピンは出力になりません。Unoではこの命令は内部プルアップ抵抗をオンにするだけです。LEDは点きますが、電流が20〜50 kΩの抵抗を通るので、とても暗く見えます。LEDが暗ければ、まずsetup()のpinModeの行を確認しましょう。

digitalWrite()とdigitalRead():デジタル出力と入力

digitalWrite(pin, HIGH)は、Unoでは出力ピンを5 Vに、digitalWrite(pin, LOW)は0 Vにします。ピンから20 mAまでは安全に取り出せます(絶対最大定格は40 mA)。LEDは必ず抵抗を付けてつなぎましょう。適切な値はLEDの抵抗の計算ツールで求められます。digitalRead(pin)はピンの電圧を見てHIGHかLOWを返します。Unoでは、おおよそ3 Vより上がHIGH、1.5 Vより下がLOWです。下の例では、D2につないだボタンを押すと内蔵LEDが点きます。

C++ (Arduino IDE)
1const int BUTON_PIN = 2;   // buton: bir ucu D2, diğer ucu GND / button: one leg to D2, the other to GND
2const int LED_PIN = 13;    // dahili LED / built-in LED
3
4void setup() {
5  pinMode(BUTON_PIN, INPUT_PULLUP);   // dahili pull-up direnci açık / internal pull-up resistor enabled
6  pinMode(LED_PIN, OUTPUT);
7}
8
9void loop() {
10  int durum = digitalRead(BUTON_PIN); // HIGH ya da LOW döner / returns HIGH or LOW
11  if (durum == LOW) {                 // pull-up ile basılı = LOW / with pull-up, pressed = LOW
12    digitalWrite(LED_PIN, HIGH);
13  } else {
14    digitalWrite(LED_PIN, LOW);
15  }
16}

INPUT_PULLUPではロジックが逆になります。ボタンを離しているときは内部抵抗のおかげでピンはHIGHになり、押すとGNDにつながるのでLOWになります。INPUTモードで抵抗なしにボタンをつなぐと、ピンが浮いてdigitalRead()はでたらめな値を返し、手を近づけるだけでも結果が変わることがあります。ボタンのチャタリングと正しいつなぎ方についてはArduinoに押しボタンをつなぐ方法をご覧ください。

analogRead()とanalogWrite():アナログ入力とPWM

analogRead(pin)は10ビットの変換器(ADC)を使い、A0〜A5のピンの0〜5 Vの電圧を0〜1023の数値に変えます。1回の読み取りに約100 µsかかるので、1秒に約10,000回読めます。数値を電圧に直すには値 × 5.0 / 1023を使います。たとえば512なら約2.5 Vです。基準電圧はanalogReference()で変えられます。INTERNALを選ぶとUnoの1.1 Vの内部基準電圧を使い、小さな電圧の分解能が上がります。分解能と基準電圧について詳しくはADCとアナログ・デジタル変換にあります。

名前とは違い、analogWrite(pin, value)は本物のアナログ電圧を作りません。ピンをとても速くオン・オフしてPWM信号を作ります。0はずっとオフ、255はずっとオン、127はおよそ半分オンです。その平均のため、私たちの目にはLEDが半分の明るさに見え、モーターはゆっくり回ります。Unoでは、ボード上で~の付いた3、5、6、9、10、11番のピンだけがPWMを出せます。周波数はほとんどのピンで490 Hz、5番と6番で980 Hzです。analogWrite()はピンを自分で出力に設定するので、pinModeは必須ではありません。下のコードはポテンショメーターでLEDの明るさを決め、読み取った値と電圧をシリアルモニタに表示します。デューティ比と周波数について詳しくはPWMとは?をご覧ください。

C++ (Arduino IDE)
1const int POT_PIN = A0;   // potansiyometrenin orta bacağı / potentiometer wiper
2const int LED_PIN = 9;    // ~9 PWM pini + 220 ohm direnç / ~9 PWM pin + 220 ohm resistor
3
4void setup() {
5  Serial.begin(9600);
6}
7
8void loop() {
9  int ham = analogRead(POT_PIN);         // 0-1023 arası / between 0 and 1023
10  float volt = ham * (5.0 / 1023.0);     // sayıyı gerilime çevir / convert the reading to volts
11  analogWrite(LED_PIN, ham / 4);         // 0-1023 -> 0-255 PWM parlaklığı / 0-1023 -> 0-255 PWM brightness
12
13  Serial.print(F("Ham / raw: "));
14  Serial.print(ham);
15  Serial.print(F("  Gerilim / voltage: "));
16  Serial.print(volt, 2);                 // 2 ondalık basamak / 2 decimal places
17  Serial.println(F(" V"));
18  delay(200);
19}
PWMのないピンでは、analogWrite()は中間の値を作れず、128未満の値ではピンをLOWに、128以上ではHIGHにします。UnoのA0〜A5のピンもPWMを出せず、従来のUno R3には本物のアナログ出力(DAC)もありません。analogWrite(A0, 100)と書いても2 Vにはなりません。本物のアナログ電圧が必要なら、DACのあるボード(A0にDACがあるUno R4など)を使うか、PWM出力にRCフィルタを付けましょう。

delay()、millis()、micros():時間の命令

delay(ms)は指定したミリ秒だけ待ち、その間loop()のほかの行は動きません。delay(1000)はまるまる1秒の停止です。マイクロ秒単位の短い待ち時間にはdelayMicroseconds()があり、3〜16383 µsの間で正確です。一方、millis()は待たずに、ボードが起動してからの時間をミリ秒で返します。結果は必ずunsigned longの変数に入れましょう。intを使うと、Unoでは32,767を超えたとたん、約33秒後に値があふれ、タイミングが壊れます。millis()は約49.7日ごとにゼロに戻りますが、下の引き算の方法なら、その一周をまたいでも正しく動き続けます。

C++ (Arduino IDE)
1const int LED_PIN = 13;
2const unsigned long ARALIK = 500;   // yanıp sönme aralığı (ms) / blink interval (ms)
3unsigned long oncekiZaman = 0;      // son değişim anı / time of the last toggle
4
5void setup() {
6  pinMode(LED_PIN, OUTPUT);
7}
8
9void loop() {
10  unsigned long simdi = millis();
11  if (simdi - oncekiZaman >= ARALIK) {               // taşmada da doğru çalışır / safe across overflow
12    oncekiZaman = simdi;
13    digitalWrite(LED_PIN, !digitalRead(LED_PIN));    // LED'i tersine çevir / toggle the LED
14  }
15  // Burada buton, sensör ve ekran işleri de yapılabilir / button, sensor and display tasks can run here
16}

この形ならloop()は決して止まりません。LEDが点滅し続けている間も、同じループでボタンを読み、センサーを測り、ディスプレイを更新できます。micros()もマイクロ秒単位で同じように動き、Unoでは4 µs刻みで進み、約70分で一周して戻ります。複数の仕事を同時に動かす方法はArduinoのmillis()とdelay()で詳しく解説しています。

Serial.begin()、Serial.print()、Serial.read():シリアル通信

Serialの命令を使うと、ArduinoはUSBケーブルでパソコンと通信でき、これが最も大切なデバッグの道具になります。Serial.begin(9600)は1秒あたり9600ビットでシリアルポートを開きます。シリアルモニタの速度が合っていないと、画面に文字化けした文字が表示されます。Serial.print()はデータを表示して同じ行にとどまり、Serial.println()は表示したあと改行します。2つ目のパラメーターで形式を選べます。Serial.print(78, HEX)は「4E」、Serial.print(78, BIN)は「1001110」を表示し、Serial.print(3.14159, 3)は「3.142」を表示します(小数は初期設定で小数点以下2桁)。固定の文字列をF("...")の中に入れると、Unoの2 KBのRAMを節約できます。

データを読むには、まずSerial.available()でバッファにバイトが待っているかを確認します。Unoの受信バッファは64バイトです。Serial.read()はそのバッファから1バイトを取り出し、読むものがなければ-1を返します。下のコードは、シリアルモニタから送った1と0の文字でLEDをオン・オフします。

C++ (Arduino IDE)
1const int LED_PIN = 13;
2
3void setup() {
4  Serial.begin(9600);                    // Seri Monitör de 9600 olmalı / Serial Monitor must be 9600 too
5  pinMode(LED_PIN, OUTPUT);
6  Serial.println(F("1: LED açık / on, 0: LED kapalı / off"));
7}
8
9void loop() {
10  if (Serial.available() > 0) {          // tamponda bayt var mı? / any bytes waiting?
11    char c = Serial.read();              // ilk baytı al / read the first byte
12    if (c == '1') {                      // '1' karakteri, 1 sayısı değil / the character '1', not the number 1
13      digitalWrite(LED_PIN, HIGH);
14      Serial.println(F("LED açık / on"));
15    } else if (c == '0') {
16      digitalWrite(LED_PIN, LOW);
17      Serial.println(F("LED kapalı / off"));
18    }
19    // '\n' ve '\r' gibi diğer karakterler atlanır / other characters such as '\n' and '\r' are ignored
20  }
21}

比較を'1'(シングルクォート、つまり文字)で行っている点に注目してください。キーボードで入力した1はASCIIの表で数値49に相当するので、c == 1という条件は決して成り立ちません。数桁の数値にはSerial.parseInt()が、1行まるごと取り出すにはSerial.readStringUntil('\n')が使えます。シリアルモニタの改行の設定、ボーレート、プロッタについてはArduinoのシリアルモニタの使い方をご覧ください。

map()とconstrain():値の換算と制限

map(value, fromLow, fromHigh, toLow, toHigh)は、数値をある範囲から別の範囲へ比例で移します。裏にある式は(x − fromLow) × (toHigh − toLow) / (fromHigh − fromLow) + toLowです。たとえばmap(512, 0, 1023, 0, 255)は127.6ではなく127を返します。map()は整数だけで計算し、小数を切り捨てるからです。行き先の範囲を入れ替えると、逆の関係を作れます:map(x, 0, 1023, 255, 0)。map()は結果を制限しないので、入力が指定した範囲を外れると、出力も外れます。そのため、ふつうはconstrain(x, low, high)と組み合わせて使います。

C++ (Arduino IDE)
1const int LDR_PIN = A0;   // LDR + 10k gerilim bölücü / LDR + 10k voltage divider
2const int LED_PIN = 9;    // ~9 PWM pini / ~9 PWM pin
3
4void setup() {
5  pinMode(LED_PIN, OUTPUT);
6}
7
8void loop() {
9  int isik = analogRead(LDR_PIN);           // odada ölçülen aralık ~200-800 / measured range in the room ~200-800
10  int pwm = map(isik, 200, 800, 255, 0);    // karanlıkta parlak, aydınlıkta sönük / bright in the dark, dim in the light
11  pwm = constrain(pwm, 0, 255);             // aralık dışı değerleri kes / clip out-of-range values
12  analogWrite(LED_PIN, pwm);
13  delay(20);
14}

この例では、LDRは部屋の中で200〜800の値を出します。夜に値が100まで下がると、map()は297を計算します。constrain()がないと、analogWrite()は0〜255の範囲外のこの値を受け取ってしまいます。Unoではこのような値はピンによって違う、予想外の結果になります。8ビットのタイマーを使う3、5、6、11番のピンでは297があふれて41のように振る舞うので、真っ暗なのにLEDは明るく光るどころか、ほとんど消えてしまいます。Unoではconstrain()、min()、max()、abs()がマクロとして定義されているので、かっこの中にx++やほかの関数呼び出しを入れないでください。式が2回以上評価され、予想外の結果になることがあります。LDRの配線はLDR光センサーをご覧ください。

tone()とnoTone():ブザーで音を出す

tone(pin, frequency, duration)は、ピンにデューティ比50%の方形波を作り、パッシブブザーや小さなスピーカーをその周波数で鳴らします。時間はミリ秒単位で、省略できます。省略すると、noTone(pin)を呼ぶまで音が続きます。大切な点として、tone()は待ちません。音を鳴らし始めると、すぐ次の行に進みます。そのため、音符を続けて鳴らすときは、各tone()のあとに少なくとも音符と同じ長さのdelay()が必要です。そうしないと最後の音符しか聞こえません。

C++ (Arduino IDE)
1const int BUZZER_PIN = 8;   // pasif buzzer'ın + ucu / passive buzzer + leg
2
3void setup() {
4  int notalar[] = {262, 294, 330, 349, 392};   // Do Re Mi Fa Sol (Hz) / C D E F G (Hz)
5  for (int i = 0; i < 5; i++) {
6    tone(BUZZER_PIN, notalar[i], 200);         // 200 ms çal / play for 200 ms
7    delay(250);                                // tone() beklemez, sen bekle / tone() does not wait, so you do
8  }
9  noTone(BUZZER_PIN);                          // sesi kapat / stop the sound
10}
11
12void loop() {
13}
  • 周波数の限界:Unoでは31 Hz未満の音は作れず、上限は65,535 Hzです。ブザーで実用的な範囲はおよそ100〜5000 Hzです。
  • 音は一度に1つ:一度に音を出せるのは1本のピンだけです。別のピンで音を出すには、まずnoTone()で前の音を止める必要があります。
  • PWMとの競合:Unoではtone()がTimer2を使うので、音が鳴っている間は3番と11番のピンのanalogWrite()が乱れます。
  • アクティブブザー:自分で発振器を持っているので、tone()で周波数を変えられません。アクティブブザーはdigitalWrite()でオン・オフするだけにしましょう。違いについてはブザーとは?をご覧ください。

pulseIn():パルスの長さを測る

pulseIn(pin, HIGH)は、ピンがHIGHになるのを待ち、HIGHのままでいる時間を測り、結果をマイクロ秒で返します。10 µsから3分までの長さのパルスを測れます。最もよく知られた使い方はHC-SR04超音波距離センサーです。センサーは、音が行って戻ってくるのにかかる時間だけの長さのパルスを出します。音は空気中を約343 m/s、つまり0.0343 cm/µsで進み、往復なので、距離 = 時間 × 0.0343 / 2です。

C++ (Arduino IDE)
1const int TRIG_PIN = 9;
2const int ECHO_PIN = 10;
3
4void setup() {
5  Serial.begin(9600);
6  pinMode(TRIG_PIN, OUTPUT);
7  pinMode(ECHO_PIN, INPUT);
8}
9
10void loop() {
11  digitalWrite(TRIG_PIN, LOW);
12  delayMicroseconds(2);
13  digitalWrite(TRIG_PIN, HIGH);            // 10 µs tetik darbesi / 10 µs trigger pulse
14  delayMicroseconds(10);
15  digitalWrite(TRIG_PIN, LOW);
16
17  unsigned long sure = pulseIn(ECHO_PIN, HIGH, 30000UL);   // en fazla 30 ms bekle / wait at most 30 ms
18  if (sure == 0) {
19    Serial.println(F("Menzil dışı / out of range"));
20  } else {
21    float mesafe = sure * 0.0343 / 2.0;    // ses hızı 343 m/s, gidiş-dönüş / speed of sound 343 m/s, round trip
22    Serial.print(mesafe, 1);
23    Serial.println(F(" cm"));
24  }
25  delay(100);
26}

3つ目のパラメーターのタイムアウトはとても大切です。これがないと、pulseIn()はパルスが来ないときに1秒待って0を返します。センサーの線が外れると、ループのたびにプログラム全体が1秒止まってしまいます。30000 µsのタイムアウトは約5メートルに相当し、HC-SR04の4メートルの範囲には十分です。センサーの配線と精度についてはHC-SR04距離センサーをご覧ください。

attachInterrupt():割り込みですぐに反応する

attachInterrupt(digitalPinToInterrupt(pin), function, mode)は、loop()がそのとき何をしていても、ピンが変わった瞬間に指定した関数を実行します。この関数を割り込みサービスルーチン(ISR)と呼びます。モードには4つの値があります。RISINGはLOWからHIGHへの変化で、FALLINGはHIGHからLOWへの変化で、CHANGEはすべての変化で、LOWはピンがLOWの間ずっとトリガーします。UnoとNanoでこの方法で割り込みを起こせるのはD2とD3のピンだけです。Megaでは2、3、18、19、20、21番のピンが使えます。速く回るエンコーダー、風速計のパルス、ごく短いボタンの押下を見逃さないのに最適です。

C++ (Arduino IDE)
1const int BUTON_PIN = 2;            // Uno'da kesme pinleri: 2 ve 3 / interrupt pins on the Uno: 2 and 3
2volatile unsigned long sayac = 0;   // ISR'de değişen değişken volatile olmalı / variables changed in an ISR must be volatile
3
4void say() {                        // kesme fonksiyonu (ISR): kısa tut / interrupt service routine: keep it short
5  sayac++;
6}
7
8void setup() {
9  Serial.begin(9600);
10  pinMode(BUTON_PIN, INPUT_PULLUP);
11  attachInterrupt(digitalPinToInterrupt(BUTON_PIN), say, FALLING);   // HIGH'dan LOW'a geçişte / on a HIGH-to-LOW edge
12}
13
14void loop() {
15  noInterrupts();                   // 4 baytlık değeri güvenle kopyala / copy the 4-byte value safely
16  unsigned long kopya = sayac;
17  interrupts();
18
19  Serial.print(F("Basma sayısı / presses: "));
20  Serial.println(kopya);
21  delay(500);
22}
ISRの中で変える変数はすべてvolatileで宣言し、関数はできるだけ短くしましょう。ISRの中ではdelay()は動かず、millis()は進まず、Serial.print()はプログラムを止めてしまうことがあります。フラグかカウンターを更新するだけにして、本当の仕事はloop()で行いましょう。機械式のボタンはチャタリングするので、1回の押下でカウンターが何回も増えることがあります。チャタリングの対策と割り込みの詳しいことはArduinoの割り込みにあります。

random()とrandomSeed():乱数を作る

random(max)は0からmax−1までの、random(min, max)はminからmax−1までのランダムな整数を返します。上限は結果に含まれません。そのため、1〜6のサイコロにはrandom(1, 7)と書きます。Arduinoは本物のランダムさを作るのではなく、決まった式で数の並びを計算するので、randomSeed()を呼ばないと、ボードが起動するたびに同じ数が同じ順番で出てきます。何もつないでいないアナログピンのノイズを種(シード)に使えば、実際にはこれを解決できます。

C++ (Arduino IDE)
1void setup() {
2  Serial.begin(9600);
3  randomSeed(analogRead(A3));     // boştaki A3 pininin gürültüsü / noise from the unconnected A3 pin
4}
5
6void loop() {
7  int zar = random(1, 7);         // 1-6 arası; üst sınır dahil değil / 1 to 6; the upper bound is exclusive
8  Serial.print(F("Zar / dice: "));
9  Serial.println(zar);
10  delay(1000);
11}

ESP32でのArduinoの命令:注意すべき点

ESP32もArduino IDEで同じ命令を使ってプログラムでき、この記事のコードのほとんどはピン番号を変えればそのまま動きます。ただし、ハードウェアが違うので、一部の命令は違う動きをします。最も大きな違いは電圧です。ESP32のピンは3.3 Vで動き、digitalWrite(pin, HIGH)は3.3 Vを出力し、5 Vの信号をピンに直接つないではいけません。ボードについてもっと知るにはESP32とは?をご覧ください。

命令Arduino UnoESP32
digitalWrite(pin, HIGH)5 V3.3 V
analogRead()10ビット、0〜1023、A0〜A512ビット、0〜4095。Wi-Fiがオンの間はADC1のピン(GPIO 32〜39)のみ
analogWrite()~のピン(3、5、6、9、10、11)のみ、490/980 Hz出力できるほぼすべてのGPIO。周波数はledcAttach(pin, freq, resolution)で設定
attachInterrupt()D2とD3のみすべてのGPIO。ISRにIRAM_ATTRを付ける
tone()Timer2を使い、3番と11番のPWMを乱すLEDCのハードウェアで動く
Serial.begin()ふつうは9600ふつうは115200(起動時のメッセージもこの速度)
delay()プロセッサを待つことだけに完全に使う時間をFreeRTOSに渡す。Wi-Fiは裏で動き続ける
C++ (Arduino IDE)
1const int POT_PIN = 34;     // ADC1 pini, Wi-Fi açıkken de okur / ADC1 pin, works with Wi-Fi on
2const int LED_PIN = 2;      // çoğu DevKit'te dahili LED / onboard LED on most DevKits
3const int BUTON_PIN = 4;    // butonun diğer ucu GND / other button leg to GND
4
5volatile bool basildi = false;
6
7void IRAM_ATTR butonISR() { // ESP32'de ISR IRAM'e yerleşmeli / on the ESP32 the ISR should live in IRAM
8  basildi = true;
9}
10
11void setup() {
12  Serial.begin(115200);
13  pinMode(BUTON_PIN, INPUT_PULLUP);
14  attachInterrupt(digitalPinToInterrupt(BUTON_PIN), butonISR, FALLING);   // her GPIO kesme destekler / every GPIO supports interrupts
15}
16
17void loop() {
18  int ham = analogRead(POT_PIN);                     // 0-4095 (12 bit)
19  analogWrite(LED_PIN, map(ham, 0, 4095, 0, 255));   // 8 bit PWM / 8-bit PWM
20  if (basildi) {
21    basildi = false;
22    Serial.println("Butona basıldı / button pressed");
23  }
24  Serial.println(ham);
25  delay(200);
26}

ESP32ではanalogRead()が0〜4095を返すので、map()の範囲もそれに合わせて変わります。ISRの前のIRAM_ATTRは、コードを高速な内部メモリに置きます。これがないと、ボードが状況によって「Guru Meditation Error」で再起動することがあります。ESP32のアナログの値は、範囲の両端付近(0 Vと3.3 Vのあたり)では直線的ではありません。より正確に測りたいなら、analogReadMilliVolts()関数がキャリブレーション済みの値をミリボルトで返します。

Raspberry Pi Pico:Arduinoの命令のMicroPythonでの対応

Raspberry Pi PicoをArduino IDE(arduino-picoコア)でプログラムすれば、この記事のすべての命令が同じ名前で動きます。analogRead()は初期設定で10ビットを返し、analogWrite()は1 kHzのPWMを作ります。ただし、ほとんどの人はPicoをMicroPythonで使います。そこでは命令をmachineとtimeのモジュールのオブジェクトで書きます。考え方は同じで、書き方が変わるだけです。Picoのピンも3.3 Vで、アナログ入力はGP26、GP27、GP28のピンです。始めるにはRaspberry Pi Picoとは?をご覧ください。

Arduinoの命令MicroPythonでの対応メモ
pinMode(15, OUTPUT)led = Pin(15, Pin.OUT)Pinオブジェクトは1回だけ作る
pinMode(14, INPUT_PULLUP)btn = Pin(14, Pin.IN, Pin.PULL_UP)内部プルアップ抵抗
digitalWrite(15, HIGH)led.value(1)led.on()でもよい
digitalRead(14)btn.value()0か1を返す
analogRead(A0)ADC(26).read_u16()0〜65535を返す
analogWrite(15, 128)PWM(Pin(15)).duty_u16(32768)デューティは0〜65535
delay(500)time.sleep_ms(500)time.sleep(0.5)でもよい
millis()time.ticks_ms()差にはtime.ticks_diff()を使う
Serial.println(x)print(x)Thonnyのシェルに表示される
tone(pin, 440)pwm.freq(440)とpwm.duty_u16(32768)duty_u16(0)で音を止める
pulseIn(pin, HIGH, 30000)machine.time_pulse_us(pin, 1, 30000)タイムアウトのときはマイナスの値を返す
attachInterrupt(...)btn.irq(trigger=Pin.IRQ_FALLING, handler=f)(割り込みの設定)fはパラメーターとしてピンのオブジェクトを受け取る
random(1, 7)random.randint(1, 6)上限を含む
map() / constrain()組み込みではないので自分で書くconstrainなら:min(max(x, 0), 255)
MicroPython
1from machine import Pin, ADC, PWM
2import time
3
4led = PWM(Pin(15))                       # pinMode + analogWrite karşılığı / pinMode + analogWrite equivalent
5led.freq(1000)                           # 1 kHz PWM
6buton = Pin(14, Pin.IN, Pin.PULL_UP)     # INPUT_PULLUP karşılığı / INPUT_PULLUP equivalent
7pot = ADC(26)                            # GP26 = ADC0
8
9def map_deger(x, giris_min, giris_max, cikis_min, cikis_max):   # Arduino map() karşılığı / Arduino map() equivalent
10    return (x - giris_min) * (cikis_max - cikis_min) // (giris_max - giris_min) + cikis_min
11
12onceki = time.ticks_ms()                 # millis() karşılığı / millis() equivalent
13while True:
14    ham = pot.read_u16()                 # 0-65535 (analogRead)
15    led.duty_u16(ham)                    # parlaklık / brightness (analogWrite)
16    if buton.value() == 0:               # digitalRead; basılı = 0 / pressed = 0
17        print("Butona basıldı / button pressed")
18    if time.ticks_diff(time.ticks_ms(), onceki) >= 1000:   # saniyede bir / once a second
19        onceki = time.ticks_ms()
20        print("Yüzde / percent:", map_deger(ham, 0, 65535, 0, 100))
21    time.sleep_ms(50)                    # delay(50)

表のとおり、MicroPythonのrandom.randint(1, 6)は上限を含み、Arduinoのrandom(1, 7)と同じ結果になります。time.ticks_diff()は、millis()の引き算と同じく一周への安全性を持っています。この小さな違いが、2つのプラットフォームを行き来するときに最も混乱しやすい点です。MicroPythonのほかの基本の命令については、MicroPythonの基本を見てみましょう。

これらの命令で作れるプロジェクトのアイデア

  • 信号機:pinMode()、digitalWrite()、delay()で3つのLEDを順番に点けます。Arduinoで信号機でステップごとに解説しています。
  • デジタルサイコロ:random()で1〜6の数を作り、ボタンを押したら結果をLEDか7セグメントディスプレイに表示します。
  • 駐車センサー:pulseIn()で距離を測り、map()でビープの間隔に変え、tone()で警告音を鳴らします。
  • 自動ナイトライト:analogRead()で明るさを読み、map()とconstrain()でanalogWrite()の明るさを決めます。
  • シリアルポートのリモコン:Serial.read()を使い、パソコンからの命令でLED、リレー、サーボモーターを制御します。
  • 反応テスト:random()でランダムなタイミングにLEDを点け、attachInterrupt()とmillis()でボタンを押すまでの反応時間をミリ秒単位で測ります。

よくある間違いと対処法

間違い症状対処法
pinMode()を忘れたLEDがとても暗い、またはまったく点かないsetup()にpinMode(pin, OUTPUT)を書く
INPUTモードで抵抗なしのボタンdigitalRead()がでたらめに0と1を返すINPUT_PULLUPを使う。押すとLOWになる
命令の名前の打ち間違い'digitalwrite' was not declared in this scope(宣言されていない)大文字と小文字に注意:digitalWrite
ボーレートの不一致シリアルモニタに文字化けした文字シリアルモニタの速度をSerial.begin()に合わせる
PWMのないピンでanalogWrite()LEDが完全に点くか消えるかだけ~の付いたピン(3、5、6、9、10、11)を使う
millis()をintに入れた約33秒後にタイミングが壊れるunsigned longを使う
random(1, 6)でサイコロ6が決して出ないrandom(1, 7)と書く。上限は含まれない
map()に小数を期待した結果がいつも整数で、小さな変化が失われる必要なら自分で小数の式を書く
タイムアウトなしのpulseIn()センサーがないとループのたびに1秒止まる3つ目のパラメーターに30000を渡す
ISRの中でdelay()やSerialプログラムが止まる、またはおかしな動きをするISRではvolatileのフラグを変えるだけにする
命令が正確に何をするのかわからないときは、小さなテスト用のスケッチを書き、Serial.println()で結果を表示してみましょう。Arduino IDE 2では、命令の名前を右クリックして「Go to Definition」(定義に移動)を選ぶと、ソースコードも見られます。コードがコンパイルできないときは、エラーメッセージの最初の行がたいてい問題を指しています。よくあるメッセージとその直し方はArduinoのコンパイルエラーにあります。

この記事の命令は、Arduinoで作るほとんどのプロジェクトの部品です。それ以外は、たいていライブラリが用意する追加の関数です。それぞれの命令を専用の小さな回路で一度試し、結果をシリアルモニタで見ると、暗記するよりずっとよく身につきます。BlueGraysアプリでは、LEDの点灯、ボタンでのLEDの制御、LDRでの明るさの測定、ブザーの周波数の設定、HC-SR04での距離の測定、サイコロのプロジェクトなど、これらの命令を使うプロジェクトを、回路図とコメント付きのコードでステップごとに学べます。

よくある質問

最もよく使うArduinoの命令は?

最もよく使うのは、pinMode()、digitalWrite()、digitalRead()、analogRead()、analogWrite()、delay()、millis()、Serial.begin()、Serial.println()です。値の換算にmap()、音にtone()、距離センサーにpulseIn()、割り込みにattachInterrupt()を加えれば、初心者向けのプロジェクトはほぼすべて書けます。

Arduinoの命令は何の言語で書かれていますか?

ArduinoのコードはCとC++の上に作られていて、Arduinoのコアが言語にpinMode()やdigitalWrite()などの既製の関数を追加しています。命令は英語で、大文字と小文字を区別し、どの文もセミコロンで終わります。ESP32とRaspberry Pi Picoは、好みに応じてMicroPythonでもプログラムできます。

digitalWriteとanalogWriteの違いは何ですか?

digitalWrite()はピンをHIGH(Unoでは5 V)かLOW(0 V)の2つの状態のどちらかにするだけです。analogWrite()はピンをとても速くオン・オフして0〜255のPWM信号を作るので、LEDの明るさやモーターの速さを調整できます。Unoでは、analogWrite()は~の付いたピンでしか動きません。

void setupとvoid loopの意味は?

setup()は、ボードの電源が入ったときかリセットされたときに1回動く関数で、pinMode()やSerial.begin()などの準備をここに書きます。loop()は、setup()が終わったあとずっと繰り返される関数です。前に付いているvoidは、これらの関数が値を返さないという意味です。

Serial.printとSerial.printlnの違いは何ですか?

Serial.print()はデータを表示してカーソルを同じ行に残すので、次のデータはそのすぐ後ろに続きます。Serial.println()は表示したあとに改行の文字(\r\n)を送るので、次のデータは新しい行から始まります。1行に複数の値を並べて表示するには、まずprint()を使い、最後にprintln()を使いましょう。

Arduinoのmap関数は何をしますか?

map()は、数値をある範囲から別の範囲へ比例で変換します。たとえば、analogRead()の0〜1023の値をanalogWrite()用の0〜255の範囲や、サーボ用の0〜180度に変えます。整数だけで計算し、結果を制限しないので、範囲外の入力に備えてconstrain()と組み合わせることがすすめられています。

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

LEDの点滅

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

4.6(188)
1127
ボタンでLEDを点灯(押している間のみ)
#9初級

ボタンでLEDを点灯(押している間のみ)

呼び鈴のように、押している間は点灯し、離すと消灯します。Arduinoはボタンの状態を常に読み取り、押されている間はLEDを点灯させ、指を離した瞬間に消灯させます。このシンプルな回路で、ボードが外部からの操作を検知する仕組みを学べます。

4.8(113)
229
LDRで光の強さを測定
#11初級

LDRで光の強さを測定

部屋の明るさを数値で見てみませんか?光に反応する小さな部品、LDRで周囲の光を測定し、Arduinoが0.5秒ごとにその値をコンピューターの画面に表示します。手で覆うと数値が変わる様子を確認できます。自動で点灯する常夜灯などのプロジェクトへの第一歩です。

4.6(81)
139
ブザーの周波数を調整
#14中級

ブザーの周波数を調整

4つのボタンでブザーの音を操作します。1つは音を鳴らし、1つは音を止め、残りの2つは音を高くしたり低くしたりします。音を高くするボタンや低くするボタンを押し続けると、音程が段階的に変化し、サイレンのように聞こえます。音の高低が生まれる理由を、自分の耳で確かめられます。

4.6(69)
118
HC-SR04で距離を測定(シリアルモニタ表示)
#27初級

HC-SR04で距離を測定(シリアルモニタ表示)

HC-SR04センサーは、コウモリが暗闇で道を見つけるのと同じ仕組みで動作します。人には聞こえない音を出し、その反響を捉えて前方の物体までの距離を測ります。Arduinoは0.5秒ごとに距離をセンチメートル単位でコンピューターの画面に表示し、3〜100センチメートルの測定値を示します。駐車センサーや障害物を避けるロボットなどのプロジェクトの基礎になります。

4.6(46)
71
速度調整できるKnight Rider風LED
#8中級

速度調整できるKnight Rider風LED

6個のLEDの上を光が端から端へ走り、折り返して戻ってきます。ポテンショメーターのつまみを回すと、その速さをすぐに調整できます。つまみを一方の端にすると光が飛ぶように進み、反対の端にするとゆっくり進みます。1つのつまみで動作中のアニメーションを操る楽しさを体験できます。

4.5(60)
120
ポテンショメーターでサーボモーターを操作
#36初級
プレミアム

ポテンショメーターでサーボモーターを操作

ポテンショメーターのつまみを回すと、サーボモーターのアームもそれに合わせて回ります。つまみの一方の端が0度、もう一方の端が半回転に対応します。Arduinoがつまみの位置を常に読み取り、サーボモーターが動きにすぐ反応します。ロボットアームや回転式カメラ台などのプロジェクトへの第一歩です。

4.6(37)
52
サイコロのプロジェクト
#79中級
プレミアム

サイコロのプロジェクト

ボードゲームでサイコロをなくす心配がなくなります。ボタンを押すたびに、7セグメントディスプレイに1から6までのランダムな数字が表示されます。1回押すと結果が1つだけ出て、コンピューターの画面にも表示されます。友達とのゲームで使える、手作りのデジタルサイコロです。

5.0(48)
67