Arduinoプログラミングの基本:変数・ループ・関数
Arduinoプログラミングをゼロから学びます。setupとloop、int・long・byte、オーバーフロー、const、#define、if-else、switch-case、ループ、配列、関数をLEDの例で解説します。
Arduinoプログラミングとは、Arduinoのボードに何をさせるかをC/C++の言語で伝えることです。どのLEDをいつ点灯させるか、ボタンが押されたら何をするか、センサーから届いた数値をどう解釈するかをコードに書くと、ボードはその命令を1秒間に何千回も繰り返します。うれしいことに、LEDの点滅からロボットカーまで、目にするスケッチのほとんどは同じ10個ほどの部品の組み合わせでできています。それがsetup()とloop()、変数、定数、演算子、if-else、switch-case、forループとwhileループ、配列、関数です。
このガイドでは、これらの部品を一つずつ、それぞれ実際のLEDやボタンの例とともに学びます。すべての例は同じシンプルな回路で動き、Arduino Uno向けにコンパイルして動作を確認しています。初心者が特につまずきやすいポイント、つまり変数のオーバーフロー、=と==の取り違え、breakの書き忘れ、毎回リセットされてしまうカウンターについては別に説明します。最後のセクションでは、同じ考え方がESP32でどう動くか、Raspberry Pi PicoのMicroPythonではどう書くかも紹介します。

Arduinoプログラミングとは?使う言語は?
ArduinoのコードはC++をベースにしていて、独立した「Arduino言語」というものはありません。ArduinoのコアがC++にpinMode()、digitalWrite()、delay()といった便利な関数を追加しています。書いたファイルはスケッチと呼ばれ、拡張子は.inoです。「検証」をクリックすると、Arduino IDEがファイルの先頭にArduino.hヘッダーを追加し、関数の宣言を自動で作成します。そしてコンパイラがコードをプロセッサが理解できる機械語に変換します。「書き込み」ボタンを押すと、その結果がUSB経由でボードのブートローダーに送られます。まだIDEをインストールしていない場合は、Arduino IDEのインストールのガイドから始めてください。
コードを書くときに守るべき基本ルールがいくつかあります。最初に覚えておけば、始めたばかりのころに出るコンパイルエラーの半分はなくなります。
| ルール | 正しい書き方 | 守らなかった場合 |
|---|---|---|
| 文の終わりには必ずセミコロンを付ける | digitalWrite(13, HIGH); | エラー:expected ';' before '}' token |
| 大文字と小文字は区別される | pinMode(13, OUTPUT); | エラー:'pinmode' was not declared in this scope |
| コードのブロックは波かっこで囲む | void loop() { ... } | { には必ず対応する } が必要 |
| 1行コメント | // この行は実行されない | コメントはコンパイルされず、説明のためのもの |
| 複数行コメント | /* ... */ | 閉じ忘れると、その下がすべてコメントになる |
| 文字列はダブルクォート、1文字はシングルクォート | "Hello"と'A' | クォートの種類を間違えると、値がおかしくなるかエラーになる |
例で使う回路
すべての例は1つの回路で動きます。D8〜D13ピンにLEDを6個、それぞれ220 Ωの抵抗を通してつなぎ、各LEDの短い足(カソード)をGNDにつなぎます。手持ちのLEDに合う抵抗値はLED抵抗計算ツールで確認できます。ボタンは片方の足をD2に、対角にある足をGNDにつなぎます。コードでINPUT_PULLUPを使うので外付けの抵抗は不要で、ボタンを離しているときはピンがHIGH、押すとLOWになります。詳しくはArduinoに押しボタンをつなぐ方法とLEDとは?を参照してください。
| 部品 | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| LED 6個(それぞれ220 Ω、カソードはGNDへ) | D8, D9, D10, D11, D12, D13 | GPIO 18, 19, 21, 22, 23, 25 | GP14, GP13, GP12, GP11, GP10, GP9 |
| ボタン(もう片方の足はGNDへ) | D2 | GPIO 4 | GP2 |
| ボード上のLED | D13(Lと書かれたLED) | GPIO 2(多くのDevKit) | GP25(Pico Wでは"LED") |
| シリアル通信速度 | 9600 baud | 115200 baud | ThonnyのShell |
setup()とloop():すべてのスケッチの骨組み
Arduinoのスケッチには必ず2つの関数があります。setup()はボードの電源が入ったときかリセットボタンが押されたときに一度だけ実行され、ピンの設定やSerial.begin()などの準備をここに書きます。loop()はsetup()が終わったあと、ずっと繰り返し実行されます。裏側では、Arduinoのコアに見えないmain()関数があり、まずハードウェアを準備してからsetup()を一度呼び出し、終わりのないループの中でloop()を何度も呼び出しています。この2つの関数の外側、ファイルの先頭部分はグローバル領域で、どこからでも使える必要がある定数や変数はここで定義します。
1// 1) Global alan: sabitler ve değişkenler / Global area: constants and variables
2const int LED_PIN = 13; // dahili LED / built-in LED
3unsigned long passCount = 0; // loop() kaç kez döndü / how many times loop() has run
4
5// 2) setup(): açılışta bir kez çalışır / runs once at power-up or reset
6void setup() {
7 pinMode(LED_PIN, OUTPUT);
8 Serial.begin(9600);
9 Serial.println("setup() bitti / setup() done");
10}
11
12// 3) loop(): sonsuza kadar tekrar eder / repeats forever
13void loop() {
14 passCount++; // her turda 1 artır / add 1 on every pass
15 Serial.print("Tur / Pass: ");
16 Serial.println(passCount);
17
18 digitalWrite(LED_PIN, HIGH); // LED'i yak / LED on
19 delay(500); // 500 ms bekle / wait 500 ms
20 digitalWrite(LED_PIN, LOW); // LED'i söndür / LED off
21 delay(500);
22}コードを書き込んで、シリアルモニタを9600 baudで開いてください。「setup() done」が一度表示され、続いて1秒ごとに増えていく回数が表示されます。同じリズムで内蔵LEDも点滅します。loop()の中にある2つのdelay(500)が1回の周回をおよそ1秒に延ばしています。待ち時間もシリアル出力もないloop()なら、Unoでも1秒間に数十万回まわることがあります。シリアルモニタを使いこなすには、Arduinoのシリアルモニタのガイドも見てみてください。
変数とデータ型:int、long、float、bool、byte
変数とは、値を保存しておくメモリ上の名前付きの箱です。定義するときは、まず型を書き、次に名前、必要なら初期値を書きます。たとえばint counter = 0;のようにします。型によって、箱が何バイトを使うか、どの範囲の数値を入れられるかが決まります。Arduino UnoのRAMはわずか2 KBなので、適切な型を選ぶことが大切です。変数名は英字かアンダースコアで始め、スペースや、é、ü、çのようなアクセント付きの文字(もちろん日本語も)は使えません。
| 型 | Unoでのサイズ | 範囲 | 主な用途 |
|---|---|---|---|
| bool | 1バイト | trueまたはfalse | LEDが点いているか、ボタンが押されたか |
| byte | 1バイト | 0 – 255 | ピン番号、PWMの明るさ |
| char | 1バイト | -128 – 127 | 1文字:'A' |
| int | 2バイト | -32,768 – 32,767 | カウンター、analogRead()の結果 |
| unsigned int | 2バイト | 0 – 65,535 | 正の数だけ |
| long | 4バイト | -2,147,483,648 – 2,147,483,647 | 大きな整数 |
| unsigned long | 4バイト | 0 – 4,294,967,295 | millis()による時間の計測 |
| float | 4バイト | 約±3.4 × 10^38、精度は6〜7桁 | 温度、電圧、平均値 |
| double | 4バイト(Unoではfloatと同じ) | floatと同じ | ESP32では8バイト |
いくつかの実践的なルールを知っておくと、多くのトラブルを避けられます。analogRead()は0〜1023の数値を返すので、intで十分です。一方、millis()はunsigned longを返します。時間をint変数に入れておくと、32.7秒で値がオーバーフローしてタイミングが狂います。Unoではfloatの計算はソフトウェアで処理されるので整数の計算より遅く、6〜7桁を超える部分は信頼できません。そのため、2つのfloatを==で比較してはいけません。どのボードでもサイズが変わらない型を使いたいときは、uint8_t、int16_t、uint32_tのように名前にサイズが入った型を使います。
オーバーフロー:変数が上限を超えるとどうなる?
型に入りきらない大きな数を入れようとしても、Arduinoはエラーを出しません。数値は車の走行距離計のように一周して戻ります。Unoのintは最大32,767で、1を足すと-32,768になります。byteは255から0に戻ります。もっと気づきにくい落とし穴は定数どうしの計算です。60 * 1000のような式は、結果をlong変数に入れる場合でも、まずintとして計算されてオーバーフローします。次のスケッチはこれらのケースをすべてシリアルモニタに表示し、loop()ではbyteのオーバーフローをLEDで見せます。
1const int LED_PIN = 9; // PWM destekli pin (~9) / PWM-capable pin (~9)
2byte brightness = 0; // byte: 0-255
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 Serial.begin(9600);
7
8 int a = 32767; // Uno'da int'in en büyük değeri / largest int on an Uno
9 a = a + 1; // taşma: -32768 olur / overflow: becomes -32768
10 Serial.println(a);
11
12 long wrong = 60 * 1000; // int ile hesaplanır, taşar: -5536 / computed as int, overflows: -5536
13 long right = 60 * 1000L; // L eki hesabı long yapar: 60000 / the L suffix makes it long math: 60000
14 Serial.println(wrong);
15 Serial.println(right);
16
17 float half = 5 / 2; // tam sayı bölmesi: 2.00 / integer division: 2.00
18 float half2 = 5 / 2.0; // ondalıklı bölme: 2.50 / floating-point division: 2.50
19 Serial.println(half);
20 Serial.println(half2);
21}
22
23void loop() {
24 analogWrite(LED_PIN, brightness); // parlaklığı uygula / apply the brightness
25 brightness++; // 255'ten sonra 0'a döner / wraps to 0 after 255
26 delay(10);
27}| コードの行 | Unoでの出力 | ESP32とPicoでの出力 | 理由 |
|---|---|---|---|
| a = 32767 + 1 | -32768 | 32768 | intはUnoでは2バイト、ほかのボードでは4バイト |
| 60 * 1000 | -5536 | 60000 | 定数はまずintとして計算される |
| 60 * 1000L | 60000 | 60000 | 接尾辞Lを付けるとlongで計算される |
| 5 / 2 | 2.00 | 2.00 | 整数どうしの割り算の結果は整数になる |
| 5 / 2.0 | 2.50 | 2.50 | 片方に小数点があれば、結果も小数になる |
D9のLEDを見てください。明るさが0から255へゆっくり上がったあと、brightness++の行で値が256ではなく0になり、LEDが急に消えます。オーバーフローを自分の目で確かめるいちばん簡単な方法です。D9はUnoの~印が付いたPWMピンの一つなので、analogWrite()が使えます。PWMについてはPWMとは?で詳しく説明しています。millis()がおよそ49.7日で一周するのもオーバーフローですが、正しく書かれたコードなら問題は起きません。書き方はmillis()とdelay()の違いのガイドで紹介しています。
constと#define:変わらない値
ピン番号やしきい値のように、プログラムの実行中に変わらない数値に名前を付ける方法は2つあります。constで定義した定数には型があり、スコープのルールにも従います。うっかり変更しようとすると、コンパイラがassignment of read-only variable 'LED_PIN'(読み取り専用変数への代入)で止めてくれます。一方、#defineはプリプロセッサの命令で、コンパイルが始まる前にコード中のBUTTON_PINをすべて2に置き換えます。型はなく、行末にセミコロンも等号も付けません。単純な数値なら、どちらもRAMを使いません。コンパイラが値を直接コードに埋め込むからです。現在はconstが好まれ、定数名を大文字(UPPER_CASE)で書くのが一般的な習慣です。次の例では両方を使っています。ボタンを押している間、LEDが点灯します。
1#define BUTTON_PIN 2 // önişlemci: derlemeden önce metin değişimi / preprocessor: text swap before compiling
2const int LED_PIN = 13; // tipi olan, salt okunur sabit / typed, read-only constant
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTTON_PIN, INPUT_PULLUP); // dahili pull-up: basılınca LOW / internal pull-up: LOW when pressed
7}
8
9void loop() {
10 if (digitalRead(BUTTON_PIN) == LOW) { // buton basılı mı? / is the button pressed?
11 digitalWrite(LED_PIN, HIGH); // evet: LED yanar / yes: LED on
12 } else {
13 digitalWrite(LED_PIN, LOW); // hayır: LED söner / no: LED off
14 }
15}演算子:算術、比較、論理
演算子を使うと、変数で計算したり、比較したり、条件を組み合わせたりできます。普段のプロジェクトで使う演算子は、ほぼすべてこの表にあります。
| 種類 | 演算子 | 意味 | 例 |
|---|---|---|---|
| 算術 | + - * / | 四則演算 | x = a * 2; |
| 剰余 | % | 割り算の余り(整数のみ) | 7 % 3は1 |
| 代入 | = += -= *= /= | 代入、加算して代入、減算して代入… | total += 5; |
| インクリメント、デクリメント | ++ -- | 1を足す、または1を引く | i++; |
| 比較 | == != < > <= >= | 結果はtrueかfalse | if (x >= 10) |
| 論理 | && || ! | AND(かつ)、OR(または)、NOT(否定) | if (a && !b) |
| 条件 | ? : | 短いif-else | x > 0 ? HIGH : LOW |
| ビット演算 | & | ^ ~ << >> | 数値のビットに対して働く | 1 << 3は8 |
特に取り違えやすいのが=と==、&と&&の2組です。=が1つなら値を代入し、2つなら比較します。&が1つなら数値のビットに対して働き、&&は両方の条件が真かどうかを調べます。AND、OR、NOTの真理値表は論理ゲートツールで試せます。ビット演算子については数の表し方とビット演算のガイドで詳しく説明しています。次の例は、ボタンが押された回数を数え、押した回数が奇数のときにLEDを点灯させます。
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4int presses = 0; // toplam basış sayısı / total number of presses
5bool lastPressed = false; // önceki turdaki durum / state on the previous pass
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10 Serial.begin(9600);
11}
12
13void loop() {
14 bool pressed = (digitalRead(BUTTON_PIN) == LOW); // karşılaştırma: true ya da false / comparison: true or false
15
16 if (pressed && !lastPressed) { // şimdi basılı VE önceden değildi: yeni basış / pressed now AND not before: new press
17 presses++; // 1 artır / increment by 1
18 Serial.print("Basış / Presses: ");
19 Serial.println(presses);
20 delay(50); // basit debounce / simple debounce
21 }
22 lastPressed = pressed;
23
24 // Kalan (%) ile tek mi çift mi? / odd or even using the remainder (%)
25 digitalWrite(LED_PIN, (presses % 2 == 1) ? HIGH : LOW);
26}ポイントはpressed && !lastPressedという条件です。これは「今押されている、かつ、前回の周回では押されていなかった」という意味です。こうすると、ボタンを押し続けても数が増え続けることはなく、1回押すごとに1回だけ数えられます。presses % 2はその数を2で割った余りで、奇数なら1、偶数なら0になります。最後の行にある「? :」演算子が、その結果に応じてHIGHかLOWを選びます。
if、else if、else:条件で処理を分ける
ifは、かっこの中の条件が真のとき、波かっこの中のコードを実行します。条件が偽のときは、elseブロックがあればそれが実行されます。選択肢がもっと必要なときは、間にelse ifを追加します。条件は上から順に調べられ、最初に真になったブロックが実行されると、残りは飛ばされます。C++では0以外の値はすべて真として扱われるので、if (counter)は「カウンターが0でなければ」という意味になります。シンプルなif-elseの例はconstのセクションですでに見ました。今度は短押しと長押しを区別する例を書いてみましょう。
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4void setup() {
5 pinMode(LED_PIN, OUTPUT);
6 pinMode(BUTTON_PIN, INPUT_PULLUP);
7 Serial.begin(9600);
8}
9
10void loop() {
11 if (digitalRead(BUTTON_PIN) == LOW) { // basıldı / pressed
12 unsigned long start = millis();
13 while (digitalRead(BUTTON_PIN) == LOW) { // bırakılana kadar bekle / wait until released
14 }
15 unsigned long held = millis() - start; // basılı kalma süresi (ms) / hold time (ms)
16
17 if (held < 50) {
18 return; // 50 ms'den kısa: parazit, yok say / under 50 ms: bounce, ignore
19 } else if (held < 1000) {
20 digitalWrite(LED_PIN, HIGH); // kısa basış: LED yak / short press: LED on
21 Serial.println("Kısa basış / Short press");
22 } else {
23 digitalWrite(LED_PIN, LOW); // uzun basış: LED söndür / long press: LED off
24 Serial.println("Uzun basış / Long press");
25 }
26 }
27}ボタンが押された瞬間に、コードはmillis()で時刻を記録し、ボタンが離されるまでwhileループで待ってから、その間の時間を計算します。50 msより短い押下は、ボタンの機械的なチャタリングによるノイズとみなし、returnでloop()のその周回を早めに終わらせます。1秒より短い押下ならLEDを点灯し、それより長ければ消灯します。条件の順番が重要です。もしheld < 1000のチェックを先に書いていたら、30 msのノイズも「短押し」として数えられてしまいます。
switch-case:多くの場合から選ぶ
変数の値に応じてさまざまな処理をしたいときは、長いelse ifの連続よりswitch-caseのほうが読みやすくなります。switchはかっこの中の値を各caseラベルと比べ、一致したところから実行を始めます。各caseはbreakで終わらせる必要があります。そうしないと、プログラムは次のcaseのコードへそのまま進んでしまいます。どのcaseにも一致しなければ、defaultブロックが実行されます。switchはint、byte、char、enumのような整数の型でしか使えません。String、float、または「10から20の間」のような範囲にはifが必要です。次の例では、ボタンを押すたびにランプが次のモードに切り替わります。消灯、点灯、ゆっくり点滅、速い点滅の順です。
1const int LED_PIN = 13;
2const int BUTTON_PIN = 2;
3
4byte mode = 0; // 0 kapalı, 1 açık, 2 yavaş, 3 hızlı / 0 off, 1 on, 2 slow, 3 fast
5bool lastPressed = false;
6
7void setup() {
8 pinMode(LED_PIN, OUTPUT);
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
14 if (pressed && !lastPressed) {
15 mode = (mode + 1) % 4; // 0 → 1 → 2 → 3 → 0
16 delay(50); // basit debounce / simple debounce
17 }
18 lastPressed = pressed;
19
20 switch (mode) {
21 case 0: // kapalı / off
22 digitalWrite(LED_PIN, LOW);
23 break;
24 case 1: // sürekli açık / always on
25 digitalWrite(LED_PIN, HIGH);
26 break;
27 case 2: // 500 ms'de bir değiş / toggle every 500 ms
28 digitalWrite(LED_PIN, (millis() / 500) % 2 ? HIGH : LOW);
29 break;
30 case 3: // 100 ms'de bir değiş / toggle every 100 ms
31 digitalWrite(LED_PIN, (millis() / 100) % 2 ? HIGH : LOW);
32 break;
33 default: // beklenmeyen değer: başa dön / unexpected value: reset
34 mode = 0;
35 break;
36 }
37}mode = (mode + 1) % 4の行は、モードを0、1、2、3と進め、4になると%演算子のおかげで0に戻します。点滅にdelay()を使っていない点に注目してください。(millis() / 500) % 2という式は500 msごとに0と1を切り替えるので、LEDが点滅している間もボタンにすぐ反応します。この「モード」の考え方を発展させたものはステートマシン(状態遷移)と呼ばれます。信号機やメニューのようなプロジェクトでの使い方は、Arduinoのステートマシンのガイドで紹介しています。
for、while、do-whileループ
ループは、コードのブロックを何度も繰り返し実行します。forループのかっこには3つの部分があります。開始(int i = 0)、続ける条件(i < 6)、各周回のあとの処理(i++)です。forは繰り返す回数がわかっているときに最適です。whileは条件が真である限り続き、各周回の前に条件を調べるので、どれくらい続くか事前にわからない待ち時間に向いています。do-whileは最後に条件を調べるので、必ず1回は実行されます。ループの中では、breakでただちにループを抜け、continueで現在の周回の残りを飛ばして次の周回に進みます。loop()はもともと終わりのないループなので、その中にwhile (true)を書く必要は普通ありません。
| ループ | 条件を調べるタイミング | 使いどころ | 例 |
|---|---|---|---|
| for | 各周回の前 | 繰り返す回数がわかっているとき | for (int i = 0; i < 6; i++) |
| while | 各周回の前 | 何かが起きるのを待つとき | while (digitalRead(2) == HIGH) |
| do-while | 各周回のあと | 少なくとも1回は実行したいとき | do { ... } while (x < 10); |
次の例は、6個のLEDでナイトライダー効果を作ります。setup()のforループで6本のピンを1行で出力に設定し、loop()のwhileループでボタンが押されるまで待ちます。ボタンを押している間、光がD8からD13へ走り、また戻ってきます。
1const int FIRST_LED = 8; // ilk LED pini / first LED pin
2const int LAST_LED = 13; // son LED pini / last LED pin
3const int BUTTON_PIN = 2;
4
5void setup() {
6 for (int pin = FIRST_LED; pin <= LAST_LED; pin++) {
7 pinMode(pin, OUTPUT); // 6 pini tek döngüyle çıkış yap / make 6 pins outputs with one loop
8 }
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10}
11
12void loop() {
13 while (digitalRead(BUTTON_PIN) == HIGH) {
14 // butona basılana kadar burada bekle / wait here until the button is pressed
15 }
16
17 for (int pin = FIRST_LED; pin <= LAST_LED; pin++) { // ileri: 8 → 13 / forward: 8 → 13
18 digitalWrite(pin, HIGH);
19 delay(80);
20 digitalWrite(pin, LOW);
21 }
22 for (int pin = LAST_LED - 1; pin > FIRST_LED; pin--) { // geri: 12 → 9 / back: 12 → 9
23 digitalWrite(pin, HIGH);
24 delay(80);
25 digitalWrite(pin, LOW);
26 }
27}戻りのループをLAST_LED - 1から始め、ピンがFIRST_LEDより大きい間だけ回しているのは意図的です。両端のLEDが2回続けて点灯しないので、動きがなめらかに見えます。速度の調整、光が満ちていく効果やほかのパターンについては、Arduinoのナイトライダーと流れるLEDのガイドを参照してください。
配列:多くの値を1つの名前でまとめる
配列は、同じ型の値を複数、順番に1つの名前でまとめて持ちます。const byte LEDS[] = {8, 9, 10, 11, 12, 13};の行は、要素が6個の配列を作ります。要素には角かっこでアクセスし、番号は0から始まります。LEDS[0]は8、LEDS[5]は13です。配列の長さは定義したあとで変えられません。要素の数を手で書く代わりにsizeof(LEDS) / sizeof(LEDS[0])で計算させれば、配列に新しいピンを追加したときにループが自動で対応します。LEDS[6]のように配列の範囲外の番号を使ってもエラーにはならず、メモリ上の別の変数を読んだり壊したりしてしまいます。そのため、番号が0から「要素数より1小さい数」までに収まるように注意する必要があります。次の例は、6個のLEDを電子サイコロにします。
1const byte LEDS[] = {8, 9, 10, 11, 12, 13}; // LED pinleri / LED pins
2const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]); // eleman sayısı: 6 / number of elements: 6
3const byte BUTTON_PIN = 2;
4
5void setup() {
6 for (byte i = 0; i < LED_COUNT; i++) { // indeks 0'dan başlar / index starts at 0
7 pinMode(LEDS[i], OUTPUT);
8 }
9 pinMode(BUTTON_PIN, INPUT_PULLUP);
10 randomSeed(analogRead(A0)); // boştaki A0'ın gürültüsüyle tohumla / seed from noise on the unconnected A0
11}
12
13void loop() {
14 if (digitalRead(BUTTON_PIN) == LOW) {
15 byte dice = random(1, 7); // 1 ile 6 arası / between 1 and 6
16 for (byte i = 0; i < LED_COUNT; i++) {
17 digitalWrite(LEDS[i], (i < dice) ? HIGH : LOW); // ilk 'dice' kadar LED yanar / the first 'dice' LEDs light up
18 }
19 delay(300); // basılı tutarsan zar dönmeye devam eder / keeps rolling while held
20 }
21}random(1, 7)は1から6までの数を作ります。上限の値は結果に含まれないからです。その数だけLEDが点灯します。サイコロの目が4なら、LEDS[0]からLEDS[3]までの4個のLEDが点きます。randomSeed(analogRead(A0))の行は、何もつながっていないA0ピンの電気的なノイズを初期値として使います。これがないと、ボードは電源を入れるたびに同じ数の並びを作ってしまいます。配列のもう一つの良いところは、LEDをほかのピンに移しても、最初の1行を変えるだけで済むことです。
関数を書く:コードを部品に分ける
プログラムの何か所かで同じ処理をしているなら、その処理を関数にしましょう。関数は、戻り値の型、名前、かっこの中の引数、波かっこの中の本体からできています。値を返さない関数の型はvoidで、値を返す関数はreturnで結果を返します。引数はコピーとして渡されるので、関数の中で引数を変えても、呼び出した側の変数は変わりません。Arduino IDEは.inoファイル内の関数の宣言を自動で作るので、関数をloop()の下に書くこともできます。ただし、.cppファイルやPlatformIOでは、関数を使う前に宣言しなければなりません。関数にはblinkLedやisPressedのように、何をするのかがわかる名前を付けましょう。
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4// Değer döndürmeyen (void) fonksiyon / function that returns nothing (void)
5void blinkLed(byte pin, byte times, int ms) {
6 for (byte i = 0; i < times; i++) {
7 digitalWrite(pin, HIGH);
8 delay(ms);
9 digitalWrite(pin, LOW);
10 delay(ms);
11 }
12}
13
14// bool döndüren fonksiyon / function that returns a bool
15bool isPressed(byte pin) {
16 return digitalRead(pin) == LOW; // INPUT_PULLUP: basılı = LOW / pressed = LOW
17}
18
19void setup() {
20 pinMode(LED_PIN, OUTPUT);
21 pinMode(BUTTON_PIN, INPUT_PULLUP);
22 blinkLed(LED_PIN, 3, 100); // açılışta 3 hızlı yanıp sönme / 3 quick blinks at start-up
23}
24
25void loop() {
26 if (isPressed(BUTTON_PIN)) {
27 blinkLed(LED_PIN, 2, 250); // basınca 2 yavaş yanıp sönme / 2 slow blinks per press
28 }
29}blinkLed()はピン、点滅の回数、速さを引数として受け取るので、同じ関数を別のLEDや別の速さにも使い回せます。isPressed()はboolを返し、INPUT_PULLUPの「押された=LOW」という考え方を1か所にまとめています。いつかボタンの配線を逆にしても、この行を変えるだけで済みます。ただし、関数の中のdelay()はやはりプログラムを止めます。複数のことを同時にこなす関数には、millis()を使ったタイミング処理が必要です。
グローバル変数、ローカル変数、static変数
変数がどこから見えるかをスコープと呼びます。関数の外で定義したグローバル変数は、プログラムが動いている間ずっと存在し、どこからでも読んだり変えたりできます。関数や波かっこのブロックの中で定義したローカル変数は、そのブロックが実行されるたびに新しく作られ、終わると消えます。初心者がいちばんよく口にする「カウンターがずっと1のまま」という悩みは、ここから生まれます。ローカル変数の前にstaticを付けると、初期値が入るのは最初の1回だけになり、周回をまたいで値が保たれます。ただし、見えるのはやはりその関数の中だけです。
1const byte LED_PIN = 13;
2const byte BUTTON_PIN = 2;
3
4int globalCount = 0; // global: her yerden görünür, değerini korur / global: visible everywhere, keeps its value
5
6void setup() {
7 pinMode(LED_PIN, OUTPUT);
8 pinMode(BUTTON_PIN, INPUT_PULLUP);
9 Serial.begin(9600);
10}
11
12void loop() {
13 int localCount = 0; // yerel: her turda yeniden 0 olur / local: back to 0 on every pass
14 static int staticCount = 0; // static yerel: değerini korur, yalnız burada görünür / static local: keeps its value, visible only here
15
16 if (digitalRead(BUTTON_PIN) == LOW) {
17 globalCount++;
18 localCount++;
19 staticCount++;
20 Serial.print(globalCount);
21 Serial.print(' ');
22 Serial.print(localCount);
23 Serial.print(' ');
24 Serial.println(staticCount); // 3. basışta: 3 1 3 / on the 3rd press: 3 1 3
25 digitalWrite(LED_PIN, (staticCount % 2) ? HIGH : LOW);
26 delay(300);
27 }
28}ボタンを3回押すと、シリアルモニタには1 1 1、2 1 2、3 1 3という行が表示されます。グローバルとstaticのカウンターは増え続けますが、ローカルのカウンターはloop()の周回ごとに0からやり直すので、いつも1のままです。グローバル変数と同じ名前のローカル変数を定義すると、ローカル変数がグローバル変数を隠してしまい、その関数の中ではグローバル変数にまったく触れなくなります。割り込み関数(ISR)で変更されるグローバル変数は、さらにvolatileとして宣言する必要があります。詳しくはArduinoの割り込みのガイドで説明しています。
ESP32でのArduinoプログラミング:同じ言語、違うサイズ
Arduino IDEでESP32をプログラミングするときも、同じC++の言語と同じ命令を使いますが、いくつか違いがあります。ESP32は32ビットのプロセッサなのでintは4バイトになり、-2,147,483,648から2,147,483,647までの値を入れられます。そのため、オーバーフローの例の32767 + 1は32768になります。doubleは8バイト、analogRead()は0〜4095の値を返し、ピンは3.3 Vで動作します。Serial.printf()で書式付きの表示もできます。random()は標準でハードウェアの乱数生成器を使うので、randomSeed()の行は必要ありません。次のプログラムは、この記事で学んだことをすべて組み合わせています。配列、関数、switch-case、そしてモードを切り替えるボタンです。ボードの全体的な特徴についてはESP32とは?を参照してください。
1// ESP32: aynı dil, farklı pinler / ESP32: same language, different pins
2const byte LEDS[] = {18, 19, 21, 22, 23, 25}; // güvenli GPIO'lar / safe GPIOs
3const byte LED_COUNT = sizeof(LEDS) / sizeof(LEDS[0]);
4const byte BUTTON_PIN = 4; // diğer bacağı GND'ye / other leg to GND
5
6byte mode = 0; // 0 kapalı, 1 yürüyen ışık, 2 hepsi yanıp söner / 0 off, 1 chaser, 2 all blink
7bool lastPressed = false;
8
9void setAll(bool on) {
10 for (byte i = 0; i < LED_COUNT; i++) {
11 digitalWrite(LEDS[i], on ? HIGH : LOW);
12 }
13}
14
15void setup() {
16 Serial.begin(115200);
17 for (byte i = 0; i < LED_COUNT; i++) {
18 pinMode(LEDS[i], OUTPUT);
19 }
20 pinMode(BUTTON_PIN, INPUT_PULLUP);
21 Serial.printf("int = %d bayt / bytes\n", (int)sizeof(int)); // ESP32'de 4 / 4 on the ESP32
22
23 int a = 32767;
24 a = a + 1;
25 Serial.println(a); // ESP32'de 32768, taşma yok / 32768 on the ESP32, no overflow
26}
27
28void loop() {
29 bool pressed = (digitalRead(BUTTON_PIN) == LOW);
30 if (pressed && !lastPressed) {
31 mode = (mode + 1) % 3; // 0 → 1 → 2 → 0
32 delay(50);
33 }
34 lastPressed = pressed;
35
36 switch (mode) {
37 case 0:
38 setAll(false);
39 break;
40 case 1: { // case içinde değişken için süslü parantez / braces for a variable inside a case
41 byte step = (millis() / 120) % LED_COUNT; // sıradaki LED / LED whose turn it is
42 for (byte i = 0; i < LED_COUNT; i++) {
43 digitalWrite(LEDS[i], (i == step) ? HIGH : LOW);
44 }
45 break;
46 }
47 case 2:
48 setAll((millis() / 300) % 2); // 300 ms'de bir hepsi / all of them every 300 ms
49 break;
50 }
51}このプログラムをUnoで動かすには、LEDS配列を{8, 9, 10, 11, 12, 13}に変え、BUTTON_PINを2にして、Unoが対応していないSerial.printf()の行をSerial.print()に置き換えるだけです。ピン番号を先頭で定数や配列として定義しておく利点は、まさにここにあります。また、case 1のブロックが波かっこで囲まれていることにも注目してください。中でstep変数を定義しているので、ESP32ではこの波かっこが必須です。
Raspberry Pi Pico:同じ考え方をMicroPythonで
Raspberry Pi Picoは、Arduino IDEにRP2040のボードパッケージを追加すればC++でプログラミングできます。その場合、この記事のコードはピン番号を変えるだけで動き、intはやはり4バイトです。ただし、Picoのいちばん一般的な使い方はMicroPythonです。Pythonでは変数の型を書かず、ブロックは波かっこではなくインデントで表し、行末にセミコロンは付けません。また、整数はオーバーフローせず、必要なだけ大きくなります。次のコードは、電子サイコロの例のMicroPython版です。セットアップについてはRaspberry Pi Picoとは?を、言語の詳細についてはMicroPythonの基本を参照してください。
1from machine import Pin
2import time, random
3
4leds = [Pin(n, Pin.OUT) for n in (14, 13, 12, 11, 10, 9)] # liste = dizi / list = array
5button = Pin(2, Pin.IN, Pin.PULL_UP) # basılınca 0 / reads 0 when pressed
6
7def show(count): # fonksiyon / function
8 for i, led in enumerate(leds): # for döngüsü / for loop
9 led.value(1 if i < count else 0)
10
11show(0) # setup() karşılığı: bir kez çalışır / like setup(): runs once
12while True: # loop() karşılığı / like loop()
13 if button.value() == 0: # buton basılı mı? / is the button pressed?
14 show(random.randint(1, 6)) # 1-6 arası zar at / roll a number from 1 to 6
15 time.sleep_ms(300)| 概念 | Arduino(C++) | MicroPython |
|---|---|---|
| 一度だけ実行される部分 | setup() | while True:の行より前のコード |
| ずっと繰り返される部分 | loop() | while True: |
| 変数 | int x = 5; | x = 5 |
| 定数 | const int PIN = 2; | PIN = const(2)(from micropython import constが必要) |
| 条件分岐 | if / else if / else | if / elif / else |
| 多分岐 | switch-case | if / elifの連続 |
| 回数を数えるループ | for (int i = 0; i < 6; i++) | for i in range(6): |
| 配列 | byte a[] = {1, 2, 3}; | a = [1, 2, 3] |
| 関数 | void blink(int pin) { ... }(関数の定義) | def blink(pin):(本体はインデントで書く) |
| 論理 | && || ! | and or not |
| コメント | // コメント | # コメント |
この基本で作れるプロジェクトのアイデア
- 信号機:const値、delay()、ifを使って、歩行者用ボタン付きの横断歩道を作ります。手順はArduinoで信号機で紹介しています。
- ナイトライダーと流れるLED:forループと配列で、何十種類もの光のパターンを作ります。
- 電子サイコロ:配列、random()、関数を使って、LED 6個のサイコロを7セグメントディスプレイに移します。詳しくは7セグメントディスプレイのガイドを参照してください。
- 反応速度テスト:ランダムなタイミングでLEDを点け、ボタンを押すまでの速さをunsigned longとmillis()で計ります。
- マルチモードランプ:switch-caseを使い、1つのボタンで消灯、点灯、呼吸(ゆっくり明滅)、フラッシュのモードを切り替えます。カラー版を作るならRGB LEDの使い方を見てみてください。
- 暗証番号ロック:押されたキーを配列に集め、保存してある暗証番号と比べます。詳しくはキーパッドのガイドで説明しています。
よくある間違いと直し方
| 間違い | 症状 | 直し方 |
|---|---|---|
| 比較に=を使う | ifのブロックが常に実行される | 比較には==を使う |
| #defineの行に;や=を付ける | エラー:expected ')' before ';' token | #define LED_PIN 13と書く |
| 命令の大文字・小文字を間違える | エラー:'pinmode' was not declared in this scope | pinModeのように大文字と小文字に注意する |
| 変数名にアクセント付きの文字や日本語を使う | エラー:stray '\303' in program | 名前には半角英字、数字、_だけを使う |
| 60 * 1000をintで計算する | 結果が-5536になる | 60 * 1000Lか60000ULと書く |
| millis()をintに入れる | 32秒後にタイミングが狂う | unsigned longを使う |
| 整数の割り算 | 5 / 2が2になる | 5 / 2.0と書くか、floatに変換する |
| caseの最後のbreakを忘れる | 次のcaseまで実行される | caseはすべてbreakで終える |
| 配列の範囲外に書き込む | おかしな値が出る、フリーズする | 番号を0から「要素数 - 1」までに収める |
| カウンターをローカル変数にする | カウンターがずっと1のまま | グローバル変数かstatic変数にする |
これらの部品を覚えたら、次はよく使う命令を知るステップです。Arduinoの命令一覧では、pinMode()からattachInterrupt()まで例付きで紹介しています。BlueGraysアプリには、LEDの点滅、ボタンでLEDを操作、ナイトライダー、信号機、サイコロといったプロジェクトが、回路図とすぐに使えるコード付きでステップごとに用意されているので、ここで学んだ構文をすべて実際の回路で試せます。
よくある質問
Arduinoで使うプログラミング言語は?
ArduinoのコードはC++をベースにしています。ArduinoのコアがpinMode()、digitalWrite()、delay()といった便利な関数を言語に追加しており、コードは拡張子.inoのファイルに書きます。ESP32やRaspberry Pi Picoのようなボードは、好みに応じてMicroPythonでもプログラミングできます。
Arduinoプログラミングは何から学べばいい?
まずArduino IDEをインストールし、内蔵LEDを点滅させるBlinkの例を書き込みます。次にsetup()とloop()、変数、if-else、ループ、配列、関数の順に、それぞれLEDやボタンの回路で試してみてください。新しいテーマを小さなプロジェクトで使うと、読むだけよりずっとしっかり身につきます。
Arduinoのintとlongの違いは?
Arduino Unoでは、intは2バイトで-32,768から32,767までの数を入れられ、longは4バイトで約±21億まで扱えます。この上限を超えた数はエラーなしで一周してしまうので、millis()のような大きな値にはlongかunsigned longを使います。ESP32とPicoでは、intも4バイトです。
Arduinoのconstと#defineの違いは?
constで定義した定数には型があり、スコープのルールに従い、変更しようとするとコンパイラがエラーを報告します。#defineはコンパイル前に行われる文字の置き換えで、型がなく、末尾にセミコロンを付けると思わぬエラーの原因になります。単純な数値ならどちらもRAMを使いませんが、より安全なので普通はconstが好まれます。
Arduinoのforループとwhileループの違いは?
forループは開始、条件、処理の更新を1行にまとめたもので、6個のLEDを順番に点灯させるときのように、繰り返す回数がわかっている場合に使います。whileループは条件だけを調べて、それが真である限り動き続けるので、ボタンが押されるのを待つような長さのわからない場面に向いています。
ArduinoはPythonでプログラミングできる?
Arduino Unoのような8ビットのボードではPythonを動かせないので、C++でプログラミングします。一方、ESP32やRaspberry Pi PicoはMicroPythonでプログラミングできます。また、パソコン上で動くPythonのプログラムが、シリアルポート経由でUnoとやり取りして間接的に制御することもできます。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
LEDの点滅
電子工作の第一歩は、1個のLEDを点けたり消したりすることです。Arduinoで短い間隔で点灯と消灯を繰り返すと、LEDが素早く点滅します。ボードが光を制御する仕組みを最もシンプルな形で確かめ、これから取り組むプロジェクトの基礎を身につけます。

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

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

Knight Rider風LED
Knight Riderでおなじみの、光が左右に往復する動きです。6個のLEDの上を光の点が端から端へ進み、折り返します。Arduinoが各LEDを10分の1秒ずつ点灯させ、端に着くたびに向きを変えて往復し続けます。装飾用の照明や警告灯作りの第一歩にぴったりです。

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

ESP32でボタンによるLED制御(押している間だけ点灯)
ボタンを押している間はLEDが点灯し、離すと消灯します。ESP32のデジタル入力ピンと内蔵プルアップ抵抗の使い方を学びます。INPUT_PULLUPモードを使うため、外付け抵抗は不要です。

Raspberry Pi PicoでボタンによるLEDの点灯・消灯切り替え
ボタンとLEDを使って、さらに一歩進みます!基本のボタンプロジェクトと違い、ボタンを押し続ける必要はありません。1回押して離すとLEDが点灯し、もう1回押して離すと消灯します(トグル動作)。回路は同じ(LEDはGP14、ボタンはGP2)ですが、コードは大きく変わります。エッジ検出(1→0に変わる瞬間の検出)、状態の記録、チャタリング対策(機械的な接点の振動を除去)を学びます。MicroPythonのled.toggle()なら1行で状態を切り替えられます。モード切り替え、IRQ、ダブルクリックなど、ボタン操作を使うあらゆるプロジェクトの基礎になります。

サイコロのプロジェクト
ボードゲームでサイコロをなくす心配がなくなります。ボタンを押すたびに、7セグメントディスプレイに1から6までのランダムな数字が表示されます。1回押すと結果が1つだけ出て、コンピューターの画面にも表示されます。友達とのゲームで使える、手作りのデジタルサイコロです。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

