Arduinoのシリアルモニタ:ボーレート、データ送信、シリアルプロッタ
Arduinoのシリアルモニタの使い方。Serial.beginとボーレート、文字化けの直し方、改行コード、parseInt、シリアルプロッタ、ESP32とPicoの例を解説します。
Arduinoのシリアルモニタは、Arduino IDEの中にあるウィンドウで、ボードとパソコンがUSBケーブルを通じて文字をやり取りできるようにするものです。スケッチにあるSerial.println()の出力はすべてここで読み、ボードへ送り返すコマンドもここで入力します。センサーが今どんな値を測っているか、プログラムがどこまで進んだか、ボタンが本当に押されているかを確かめるいちばん手軽な方法で、Arduinoの世界ではデバッグ道具のナンバーワンです。使うのに必要なのは、コードに書いたSerial.begin(9600)、ボードセレクタで選んだ正しいポート、そしてコードと同じ速度に設定したモニタだけです。
このガイドでは、まずシリアルモニタを開き、ボーレートが実際に何を意味するのか、printとprintlnで数値を10進数・16進数・2進数で表示する方法、小数点以下の桁数の指定、snprintfで整った行を作る方法を見ていきます。実際にコンパイルできるスケッチ例を使って、文字化けのあらゆる原因、改行コードの設定がコードに与える影響、パソコンからボードへの数値やテキストコマンドの送信(Serial.read、parseInt、readStringUntil)、シリアルプロッタでのリアルタイムグラフ、作業を楽にしてくれるデバッグのテクニックを解説します。最後の章では、ESP32とRaspberry Pi Pico(ArduinoとMicroPythonの両方)でシリアル出力がどう違うか、モニタに何も表示されないといったよくあるトラブルの直し方を紹介します。

Arduinoのシリアルモニタとは?その仕組み
Arduino Unoの頭脳であるATmega328Pは、UARTと呼ばれるシリアル通信ユニットで外の世界とやり取りします。1本の線(TX)でデータを1ビットずつ送り、もう1本の線(RX)で受け取ります。今のパソコンにはこの種のシリアルポートがもうないため、ボード上の2つ目のチップ(純正品のUnoではATmega16U2、互換品では多くの場合CH340)が信号をUSBに変換します。パソコンからはボードが仮想シリアルポートとして見えます。WindowsではCOM3のような名前、Linuxでは/dev/ttyACM0や/dev/ttyUSB0、macOSでは/dev/cu.usbmodemや/dev/cu.usbserialで始まる名前です。シリアルモニタはこのポートを開き、届いたバイトを文字として表示し、入力した内容を同じ経路でボードへ送ります。Arduinoは標準で8N1形式を使います。1バイトごとにスタートビット1個、データビット8個、パリティビットなし、ストップビット1個という構成です。つまり1文字は線上で10ビットを占めます。この数字はあとで速度の計算に使います。
| 項目 | Arduino Uno | ESP32 DevKit | Raspberry Pi Pico |
|---|---|---|---|
| USB接続 | ATmega16U2またはCH340のブリッジチップ | CP2102またはCH340のブリッジチップ | RP2040自身のUSB(USB CDC) |
| シリアルピン | D0(RX)、D1(TX) | GPIO 3(RX)、GPIO 1(TX) | ピンなし、USBで直接 |
| 追加のハードウェアシリアルポート | なし(SoftwareSerialでソフトウェア的に実現) | Serial1とSerial2(Serial2はGPIO 16 RX、GPIO 17 TXでよく使われる) | Serial1(GP0 TX、GP1 RX)、Serial2(GP8 TX、GP9 RX) |
| 一般的な速度 | 9600ボー | 115200ボー | 関係なし、USBの速度で動く |
| モニタを開くとリセット | される(DTR線) | ボードとプログラムによる | されない |
| Serial.printf() | 使えない(snprintfを使う) | 使える | 使える |

シリアルモニタの開き方と使い方
- スケッチのsetup()部分にSerial.begin(9600);の行を追加して書き込みます。この行がないと、ボードはシリアルポートに何も出力しません。
- 上部バーのボードセレクタ、または「ツール」>「ポート」メニューで、ボードがつながっているポートを選びます。ポートが選ばれていないと、シリアルモニタに「接続されていません」という警告(英語表示ではNot connected. Select a board and a port to connect automatically)が表示されます。
- 右上の虫めがねアイコンをクリックするか、「ツール」>「シリアルモニタ」メニューを使うか、Ctrl+Shift+M(macOSではCmd+Shift+M)を押します。IDE 2では、モニタは下部パネルの「出力」の隣に開きます。
- パネル右側の速度メニューで、コードと同じボーレートを選びます。この例では9600です。速度が違うと、画面に文字化けした文字が次々と流れます。
- ボードにデータを送るには、パネル上部のメッセージ欄に入力してEnterを押します。そのすぐ隣のメニューで改行コードを選びます。この設定がなぜ大事なのかは後ほど説明します。
- 右上のボタンで、自動スクロールを止めたり(古い行を読むときに便利)、各行の先頭にパソコンの時刻を付けるタイムスタンプをオンにしたり、出力を消去したりできます。
古いArduino IDE 1.8では、シリアルモニタは別ウィンドウで開きます。操作できる内容は同じで、場所が違うだけです。自動スクロールとタイムスタンプのチェックボックスは左下、改行コードと速度のメニューは右下にあります。IDEを使わずにシリアルポートを見ることもできます。WindowsならPuTTYやCoolTerm、LinuxとmacOSならscreen /dev/ttyUSB0 115200コマンド、どのOSでもarduino-cli monitor -p COM3 -c baudrate=115200で同じことができます。ひとつ覚えておきたいのは、シリアルポートは同時に1つのプログラムしか開けないということです。PythonスクリプトやPuTTYがポートをつかんでいる間は、IDEからコードを書き込めません。IDEのインストールと画面のそのほかの部分については、Arduino IDEのインストール方法のガイドで解説しています。
Serial.begin()とボーレート:9600と115200のどちらを使う?
Serial.begin(速度)は、指定した速度でシリアルポートを開始する関数で、ほぼ必ずsetup()の中で1回だけ呼び出します。速度の単位はボー(baud)で、1秒間に線上を運ばれるビット数を表します。1文字が10ビットを占めるので、9600ボーは1秒あたり最大960文字ということになり、1文字を送るのに約1.04ミリ秒かかります。30文字の行ならこの速度で31 msかかるため、1秒に何百回も測定値を表示しようとすると、すぐにこの限界にぶつかります。115200ボーなら同じ行がわずか2.6 msで済みます。スケッチの値とシリアルモニタの値は必ず一致させてください。共通のクロック信号がなく、受信側はタイミングだけを頼りにビットを読み取るからです。
| ボーレート | 1秒あたりの文字数 | 1文字の時間 | 主な用途 |
|---|---|---|---|
| 9600 | 960 | 1.04 ms | Arduinoのスケッチ例、HC-05やNEO-6M GPSなどのモジュールの初期設定 |
| 57600 | 5760 | 174 µs | 古いブートローダーを積んだNanoの書き込み速度、一部のモジュール |
| 74880 | 7488 | 134 µs | ESP8266の起動メッセージ |
| 115200 | 11520 | 87 µs | ESP32、Pico、高速なデバッグ。新しいUnoブートローダーの書き込み速度 |
| 250000 | 25000 | 40 µs | 高速なデータ記録。Unoの16 MHzクロックで誤差なく作れる |
| 1000000 | 100000 | 10 µs | 非常に高速なデータ転送。短いケーブルが必要 |
Unoの16 MHzクロックでは、すべての速度を正確に作れるわけではありません。115200ボーは実際には約2%ずれた117647ボーで動いています。この誤差はシリアルモニタでは問題になりませんが、長いケーブルで別のマイコンと通信するときにはエラーの原因になることがあります。そのような場合は、57600ボーや、誤差なく作れる250000ボーのほうが安全です。始めたばかりなら、UnoとNanoには9600、ESP32とPicoには115200を使いましょう。2枚のボードをTX-RXの線でつなぐ場合は、両側の速度を同じにし、GNDを共通にしたうえで、5 VのTX線は分圧回路で電圧を下げてから3.3 VのRX入力(ESP32、Pico)につなぐ必要があります。詳しくはUARTとは?の記事で解説しています。
Serial.print()とSerial.println():表示と書式設定
Serial.print()は渡した値を書き出し、カーソルを同じ行に残します。Serial.println()は、そのあとに改行文字(\r\n)も追加するので、次の出力は新しい行から始まります。1行に複数の値を表示するには、最初の部分をprintで、最後の部分をprintlnで書きます。これらの関数はint、long、float、char、文字列、Stringなど、ほぼどんな型でも受け付け、省略できる2番目の引数で書式を指定します。整数ではDEC、HEX、BIN、OCTを選べ、浮動小数点数では2番目の引数が小数点以下の桁数になります。floatは標準で小数点以下2桁に四捨五入して表示されます。下のコードでは、各行の出力をコメントとして書いています。
1// Biçimlendirme örnekleri: çıktılar yorumlarda / Formatting examples: output in the comments
2void setup() {
3 Serial.begin(9600);
4 int sayi = 78;
5 float pi = 3.14159;
6
7 Serial.println(sayi); // 78
8 Serial.println(sayi, HEX); // 4E
9 Serial.println(sayi, BIN); // 1001110
10 Serial.println(sayi, OCT); // 116
11 Serial.println(pi); // 3.14 (varsayılan 2 basamak / 2 decimals by default)
12 Serial.println(pi, 4); // 3.1416
13 Serial.write(65); // A (ham bayt / raw byte)
14 Serial.println(); // yalnız satır sonu / line break only
15
16 Serial.print("Sicaklik:\t"); // \t = sekme / \t = tab
17 Serial.print(23.46, 1); // 23.5
18 Serial.println(" C"); // Sicaklik: 23.5 C
19
20 char satir[32];
21 int saat = 9, dakika = 5;
22 snprintf(satir, sizeof(satir), "Saat %02d:%02d", saat, dakika);
23 Serial.println(satir); // Saat 09:05
24
25 char metin[10];
26 dtostrf(pi, 6, 2, metin); // Uno'da %f yerine / instead of %f on the Uno
27 Serial.println(metin); // " 3.14"
28}
29
30void loop() {
31}| コード | シリアルモニタの表示 | メモ |
|---|---|---|
| Serial.print(78); | 78 | 標準は10進数(DEC) |
| Serial.print(78, HEX); | 4E | 16進数。アドレス、色、バイト値に |
| Serial.print(78, BIN); | 1001110 | 2進数。先頭のゼロは表示されない |
| Serial.print(3.14159); | 3.14 | floatは標準で小数点以下2桁 |
| Serial.print(3.14159, 4); | 3.1416 | 小数点以下4桁、四捨五入 |
| Serial.write(65); | A | 数値ではなく、65番のASCII文字を送る |
| Serial.print('A'); | A | char型は文字として表示される |
| Serial.print(5e9); | ovf | Unoでは約42.9億を超えるfloatは表示できない |
長い行を少しずつ表示する代わりに、snprintf()で1つの文字列にまとめることもできます。%dは整数、%02dは先頭をゼロで埋めた2桁の整数、%sは文字列、%luはmillis()のようなunsigned longです。メモリを節約するため、UnoのCライブラリは%fに対応していません。floatを表示しようとすると、その場所に?が出ます。そのため、floatは先にdtostrf(数値, 幅, 桁数, バッファ)で文字列に変換します。ESP32とPicoではSerial.printf()をそのまま使えます。UnoのRAMはわずか2 KBです。Serial.println(F("Hello"))のようにF()マクロで固定の文字列を書くと、RAMではなくフラッシュメモリに置かれ、大きなプロジェクトでのメモリ不足の問題を防げます。
シリアルモニタの文字化けとその直し方
⸮⸮⸮や�、あるいはでたらめな文字や記号の山が表示される場合、データはパソコンに届いているものの、正しく解読されていません。シリアル通信には共通のクロック信号がないため、受信側はスタートビットを検出した瞬間を起点に、自分の速度から各ビットの中央を計算します。速度が間違っていたり、ボードのクロックが本来の値と違っていたりすると、ビットを読み取るタイミングがずれ、すべてのバイトが別の文字に化けてしまいます。原因はたいてい1つの設定です。下の表に、よくあるケースを症状別にまとめました。Pro Miniのようにクロックの選択肢が複数あるボードについては、Arduino Pro Miniの記事もあわせてご覧ください。
| 症状 | 原因 | 対処法 |
|---|---|---|
| 最初から最後まで文字化けする | シリアルモニタの速度がSerial.begin()の値と違う | 右側のメニューでコードと同じ速度を選ぶ |
| 起動時の1行だけが化けて、残りは正常 | ESP8266は74880ボー、ESP32は115200ボーで自分の起動メッセージを出力している | 正常な動作。読みたい場合はモニタをその速度に合わせる |
| Pro Miniの出力がいつも化ける | 3.3 V / 8 MHzのボードなのに5 V / 16 MHzが選ばれていて、実際の速度が半分になっている | 「ツール」>「プロセッサ」メニューでボードの実際のクロックを選ぶ |
| 正常な文字の間にでたらめな文字が混じる | D0/D1ピンにつないだモジュールも線上で送信している | モジュールはSoftwareSerialでほかのピンにつなぐ |
| 数字の代わりに妙な記号が出る | Serial.write(数値)が数値を生のバイトとして送っている | 数値を文字として表示するにはSerial.print()を使う |
| 2枚のボード間のデータが壊れる | GNDが共通でない、ケーブルが長い、または速度が違う | GNDをつなぎ、ケーブルを短くし、速度を下げる |
| 同じ化けた行が何度も出て、ボードが再起動する | 電源が足りず、ボードがリセットを繰り返している | 電源とケーブルを確認し、負荷には別の電源から給電する |
改行コードの設定:改行なし、LFのみ、CRのみ、CRおよびLF
メッセージ欄に入力してEnterを押すと、シリアルモニタは入力した文字だけでなく、選んだ改行コードに応じて目に見えない文字を1つか2つ追加で送ります。コードがそれを想定していない場合、たとえば届いたバイトをすべて1つずつコマンドとして処理している場合は、'\n'(値10)と'\r'(13)もコマンドとして読まれてしまいます。逆に、コードが改行を待っているのに設定が「改行なし」だと、行はいつまでも終わらず、readStringUntil()はタイムアウトになるまで待ち続けます。下の表は、42と入力してEnterを押したときにボードへ送られるバイトです。
| 意味 | IDEでの名前 | 追加される文字 | 42と送ったときのバイト |
|---|---|---|---|
| 何も追加しない | 改行なし(No Line Ending) | なし | 52, 50 |
| 改行(LF) | LFのみ(New Line) | \n(LF、10) | 52, 50, 10 |
| 復帰(CR) | CRのみ(Carriage Return) | \r(CR、13) | 52, 50, 13 |
| 両方(CR + LF) | CRおよびLF(Both NL & CR) | \r\n(13、10) | 52, 50, 13, 10 |
実用的なルールはこうです。1行ずつ読むコードではLFのみを選び、読み取った文字列にtrim()をかけましょう。そうすれば、ユーザーがどの設定を選んでもコードは動きます。1、0、aのような1文字のコマンドには改行なしがいちばんすっきりした選択です。IDEは選んだ設定を覚えているので、別のプロジェクトに移ったときは確認を忘れないようにしましょう。
パソコンからArduinoへデータを送る:Serial.read()、parseInt()、readStringUntil()
シリアルモニタから送ったバイトは、ボードの受信バッファ(Unoでは64バイト)に順番に並びます。Serial.available()はそこに何バイト待っているかを返し、Serial.read()は次のバイトを取り出してバッファから削除し、データがなければ-1を返します。Serial.peek()は次のバイトを削除せずにのぞき見します。キーボードから届くものはすべてASCII文字であることを忘れないでください。7と入力すると、ボードに届くのは数値の7ではなく、文字'7'のコードである55です。1つのキーで動くシンプルなコマンドの例はArduinoの命令一覧の記事にあります。ここでは複数桁の数値と、単語でできたコマンドを扱います。バッファがいっぱいになると新しく届いたバイトは失われるため、loop()の中でdelay()を使って長く待つ代わりにmillis()でタイミングを取ると、シリアルの読み取りも安全になります。
Serial.parseInt()は、届いた文字から最初の整数を取り出します。先頭の文字や空白を読み飛ばし、数字をつなげ、数字でない最初の文字で止まります。マイナス記号も認識します。こうして文字列の「128」が数値の128になります。浮動小数点数にはSerial.parseFloat()が同じ働きをします。これらの関数には知っておくべきクセが1つあります。数値が届かないと、標準で1000 ms待ってから0を返すのです。よくあるバグはこうして起こります。設定が「LFのみ」のまま42を送ると、parseIntは42を読みますが、'\n'がバッファに残ります。次の周回でloop()はavailable()が0より大きいと判断して再びparseIntを呼び、こちらは1秒待ってから0を返します。その結果、数値のたびに0が続き、LEDが毎回1秒後に消える理由がわからなくなってしまいます。下のコードは、次の文字が数字でなければ捨てることで問題を根本から解決し、setTimeout()で待ち時間も短くしています。
1// Seri Port Ekranı'ndan sayı al: LED parlaklığı 0-255
2// Read a number from the Serial Monitor: LED brightness 0-255
3const int LED_PIN = 9; // ~9 PWM pini + 220 ohm direnç / ~9 PWM pin + 220 ohm resistor
4
5void setup() {
6 pinMode(LED_PIN, OUTPUT);
7 Serial.begin(9600);
8 Serial.setTimeout(100); // parseInt en fazla 100 ms bekler / parseInt waits at most 100 ms
9 Serial.println(F("0-255 arasi bir sayi yaz / Type a number from 0 to 255"));
10}
11
12void loop() {
13 if (Serial.available() > 0) {
14 char c = Serial.peek(); // sıradakine bak, silme / look at the next byte, keep it
15 if (isDigit(c) || c == '-') {
16 int deger = Serial.parseInt(); // "128" -> 128
17 deger = constrain(deger, 0, 255); // aralığın dışına çıkma / stay within range
18 analogWrite(LED_PIN, deger);
19 Serial.print(F("Parlaklik / Brightness: "));
20 Serial.println(deger);
21 } else {
22 Serial.read(); // '\r', '\n' ve harfleri at / drop '\r', '\n' and letters
23 }
24 }
25}単語でできたコマンドは、行全体をStringとして読むのがいちばん簡単です。Serial.readStringUntil('\n')は'\n'が届くまで読み、改行文字を捨てて残りを返します。設定が「CRおよびLF」だと文字列の末尾に'\r'が残り、「on」コマンドが「on\r」になって比較が一致しません。そのため、読み取った行にはすぐtrim()をかけましょう。先頭と末尾の空白に加えて、'\r'と'\n'の文字も取り除いてくれます。toLowerCase()を使えば、ON、On、onが同じものとして扱われます。下の例はon、off、blink 250、statusのコマンドを理解します。シリアルモニタは115200ボー、LFのみの設定で使ってください。delay()ではなくmillis()で点滅させているので、どの瞬間でも新しいコマンドにすぐ応答します。
1// Metin komutları: on, off, blink 250, status
2// Text commands: on, off, blink 250, status
3const int LED_PIN = LED_BUILTIN; // Uno'da pin 13 / pin 13 on the Uno
4bool ledAcik = false; // LED'in şu anki durumu / current LED state
5bool yanipSon = false; // yanıp sönme modu / blink mode
6unsigned long aralik = 500; // yanıp sönme aralığı (ms) / blink interval (ms)
7unsigned long sonDegisim = 0; // LED'in son değiştiği an / last time the LED changed
8
9void ledYaz(bool acik) {
10 ledAcik = acik;
11 digitalWrite(LED_PIN, acik ? HIGH : LOW);
12}
13
14void komutuIsle(String komut) {
15 if (komut == "on") {
16 yanipSon = false;
17 ledYaz(true);
18 Serial.println(F("LED: on"));
19 } else if (komut == "off") {
20 yanipSon = false;
21 ledYaz(false);
22 Serial.println(F("LED: off"));
23 } else if (komut.startsWith("blink")) {
24 long ms = komut.substring(5).toInt(); // "blink 250" -> 250
25 aralik = (ms >= 50) ? ms : 500; // çok küçükse 500 ms / fall back to 500 ms
26 yanipSon = true;
27 Serial.print(F("Blink (ms): "));
28 Serial.println(aralik);
29 } else if (komut == "status") {
30 Serial.print(F("LED: "));
31 Serial.print(ledAcik ? F("on") : F("off"));
32 Serial.print(F(", blink: "));
33 Serial.println(yanipSon ? F("yes") : F("no"));
34 } else {
35 Serial.print(F("Bilinmeyen komut / Unknown command: "));
36 Serial.println(komut);
37 }
38}
39
40void setup() {
41 pinMode(LED_PIN, OUTPUT);
42 Serial.begin(115200);
43 Serial.setTimeout(100); // satır sonu gelmezse 100 ms sonra bırak / give up after 100 ms without a line ending
44 Serial.println(F("Komutlar / Commands: on, off, blink 250, status"));
45}
46
47void loop() {
48 if (Serial.available() > 0) {
49 String komut = Serial.readStringUntil('\n'); // Enter'a kadar oku / read up to Enter
50 komut.trim(); // '\r' ve boşlukları sil / strip '\r' and spaces
51 komut.toLowerCase(); // ON ile on aynı olsun / treat ON like on
52 if (komut.length() > 0) {
53 komutuIsle(komut);
54 }
55 }
56
57 if (yanipSon && millis() - sonDegisim >= aralik) { // delay() olmadan yanıp sön / blink without delay()
58 sonDegisim = millis();
59 ledYaz(!ledAcik);
60 }
61}| 関数 | 戻り値 | 待ち時間 | 向いている用途 |
|---|---|---|---|
| Serial.read() | 1バイト(int)、データがなければ-1 | 待たない | 1文字のコマンド、自作のパーサー |
| Serial.peek() | 次のバイト(バッファから削除しない) | 待たない | 次に来るものを見てから判断する |
| Serial.parseInt() | long型の整数、タイムアウト時は0 | setTimeoutの時間(標準は1000 ms) | 数値の入力:角度、速度、明るさ |
| Serial.parseFloat() | float、タイムアウト時は0 | setTimeoutの時間 | 2.5のような小数の設定値 |
| Serial.readStringUntil(c) | 文字cまでの文字列(String) | cが届くか、時間切れになるまで | 単語や引数を含むコマンド |
| Serial.readBytesUntil(c, バッファ, n) | 読み取ったバイト数。文字列はchar配列に書き込む | cが届くか、nバイト読むか、時間切れになるまで | Stringを使わない、RAMにやさしい読み取り |
シリアルプロッタでリアルタイムグラフを描く
シリアルプロッタは、シリアルポートから届く数値をリアルタイムの折れ線グラフにしてくれます。IDE右上の波形アイコンか、「ツール」>「シリアルプロッタ」メニューから開きます。書式はとても簡単で、1行の値をカンマ、スペース、タブのいずれかで区切り、printlnで行を終えるだけです。1行がグラフ上の1ステップになり、行の中のそれぞれの値が別々の色の線になります。値の前にラベル:値の形で名前を付けると、線にその名前が付き、グラフ上部のチェックボックスで1本ずつ表示・非表示を切り替えられます。ラベルがなければ、線にはvalue 1、value 2のような名前が付きます。ここでも速度メニューはコードと一致させる必要があります。下のコードは、A0の生の測定値と、それを平滑化した値を同じグラフに描きます。
1// Seri Çizici: ham değer ve yumuşatılmış değer / Serial Plotter: raw and smoothed value
2const int SENSOR_PIN = A0; // potansiyometre ya da LDR bölücü çıkışı / potentiometer or LDR divider output
3float yumusak = 0; // üstel ortalama / exponential moving average
4
5void setup() {
6 Serial.begin(115200);
7}
8
9void loop() {
10 int ham = analogRead(SENSOR_PIN); // 0-1023
11 yumusak = yumusak * 0.9 + ham * 0.1; // yeni ölçümün payı %10 / the new reading weighs 10%
12
13 Serial.print("Min:0,Max:1023,"); // dikey ekseni sabitler / keeps the y-axis fixed
14 Serial.print("Ham:");
15 Serial.print(ham);
16 Serial.print(",Yumusak:");
17 Serial.println(yumusak); // satırı bitir: bir ölçüm / end the line: one sample
18 delay(20); // saniyede yaklaşık 50 satır / about 50 lines per second
19}- 目盛りを固定する:シリアルプロッタは届いた値に合わせて縦軸の目盛りを変え続けるので、小さな揺れが大きく見えてしまいます。各行にMin:0,Max:1023,のような2つの固定値を加えると、軸が固定されます。
- ラベルにスペースを使わない:スペースとカンマは区切りとして扱われるため、「Room Temp:24」は2つに分かれてしまいます。Room_Tempのようにアンダースコアを使った名前にしましょう。
- 行にはラベルと数値だけを出す:「Temp 24 C」のような単位や説明文はグラフを乱します。シリアルモニタ向けの説明は、別のコードで出力しましょう。
- 送信の間隔を調整する:delay(20)は1秒あたり約50行です。速すぎるとグラフが一瞬で流れ去り、遅すぎると線が階段状に見えます。
- フィルターの効果を自分の目で確かめる:生の値と平均値を同じグラフに描くと、フィルターでノイズがどれだけ減るかが一目でわかります。手法はセンサーデータのフィルタリングの記事で紹介しています。
- 記録には別のツールを使う:シリアルプロッタはデータをファイルに保存しません。長時間の測定では、値をCSVの行として出力し、Excelに取り込むか、Pythonで読み取るとよいでしょう。
シリアルモニタを使ったデバッグのテクニック
Arduino Unoには、コードを1ステップずつ実行できるデバッガーがありません。その役目を果たすのがシリアルモニタです。ただし、コードのあちこちに適当にprint文を散らすと、すぐに読めない文字の山になってしまいます。次の習慣を身につければ、バグをずっと早く見つけられます。
- まずsetup()からメッセージを出す:ボードが起動するたびに「Started」の行が1回表示されれば、ちゃんと動いています。この行が何度も繰り返される場合はボードがリセットを繰り返しており、問題はコードより電源にあります。Arduinoがリセットを繰り返すときのガイドを参照してください。
- 値は名前と一緒に出す:512とだけ表示するのではなく、temp=24.5 hum=40のように書きましょう。あとで、どの数値が何なのかを考えずに済みます。
- どこまで進んだか目印を付ける:プログラムがどこかで固まる場合は、重要な処理の前にA、B、Cのような短い目印を入れます。画面に最後に出た目印が、プログラムが止まった場所を示します。
- 毎回ではなく、変化したときに出す:loop()は1秒に何千回も回ります。毎回表示すると画面があふれ、プログラムも遅くなります。値は変化したときだけ、またはmillis()を使って1秒に1回だけ出しましょう。
- 時間を測る:ある処理にどれだけ時間がかかるかを知るには、その前後でmicros()の値を記録し、差を表示します。遅いセンサーの読み取りや長いループは、こうして見つけられます。
- 割り込みの中では表示しない:シリアル出力には時間がかかります。割り込み関数の中でSerial.printを使うと処理が無駄に長くなり、ほかのイベントを取りこぼす原因になります。割り込みではフラグ用の変数をセットするだけにして、表示はloop()の中で行いましょう。詳しくはArduinoの割り込みの記事で解説しています。
- デバッグ用の行は1つのスイッチでまとめる:下のマクロを使えば、DEBUGを0にするだけでデバッグ出力をコードから完全に取り除けます。完成したプロジェクトでは、フラッシュメモリと実行時間の両方を節約できます。
1// Hata ayıklama makroları: tek satırla aç ya da kapat
2// Debug macros: switch all debug output on or off with one line
3#define DEBUG 1 // 0 yaparsan debug satırları koddan çıkar / set to 0 to strip debug lines
4
5#if DEBUG
6 #define DBG(x) Serial.print(x)
7 #define DBGLN(x) Serial.println(x)
8#else
9 #define DBG(x)
10 #define DBGLN(x)
11#endif
12
13const int SENSOR_PIN = A0;
14const int ESIK = 800; // uyarı eşiği / warning threshold
15bool esikUstunde = false; // son durum / last state
16unsigned long sonRapor = 0;
17unsigned long turSayisi = 0; // saniyedeki loop() turu / loop() passes per second
18
19void setup() {
20 Serial.begin(115200);
21 DBGLN(F("Basladi / Started"));
22}
23
24void loop() {
25 turSayisi++;
26 int deger = analogRead(SENSOR_PIN);
27
28 bool simdi = deger > ESIK;
29 if (simdi != esikUstunde) { // yalnız değişince yaz / print only on change
30 esikUstunde = simdi;
31 DBG(F("[esik / threshold] "));
32 DBG(simdi ? F("ustunde / above") : F("altinda / below"));
33 DBG(F(", deger / value = "));
34 DBGLN(deger);
35 }
36
37 if (millis() - sonRapor >= 1000) { // saniyede bir özet / one summary per second
38 sonRapor = millis();
39 DBG(F("t = "));
40 DBG(sonRapor);
41 DBG(F(" ms, tur/s / loops/s = "));
42 DBGLN(turSayisi);
43 turSayisi = 0;
44 }
45}シリアル出力にはコストがあることを覚えておきましょう。Unoでは送信バッファも64バイトで、いっぱいになるとSerial.print()は空きができるまで待ち、loop()が遅くなります。これは上のコードのループカウンターで自分で測れます。毎回表示する25文字の行を加えると、9600ボーではプログラムが1秒あたり約40回まで落ちますが、変化したときだけ表示すれば何千回もの速さを保てます。ボードが固まったりリセットしたりする直前に何を出力したかを確実に知りたい場合は、重要な行のあとにSerial.flush()を呼び出します。バッファの中身がすべて送信されるまで待ってくれます。もっと複雑なプログラムでは、コードをArduinoのステートマシンの構造に書き換え、状態が変わったときだけ1行表示するようにすると、バグの追跡がずっと楽になります。
ESP32のシリアル出力
ESP32 DevKitボードでは、SerialはGPIO 1(TX)とGPIO 3(RX)にあるUART0で、ボード上のCP2102またはCH340チップを通じてUSBにつながっています。一般的な速度は115200ボーです。チップ内部のROMも、起動時にrst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)のような行をこの速度で出力するためで、モニタを115200にしておけばこれらの行も読めるようになります。ESP32のArduinoコアにはSerial.printf()があり、%fでのfloat表示にも対応しています。追加のシリアルポートSerial1とSerial2は好きなピンに割り当てられます。モジュールをつなぐときは、Serial2をGPIO 16と17で使うのが一般的です。ESP32-S3やESP32-C3のようにUSBを内蔵したボードでシリアルモニタに何も表示されない場合は、「ツール」>「USB CDC On Boot」をEnabledにします。コア自体の警告を見るには、「ツール」>「Core Debug Level」メニューでError、Warn、Info、Debug、Verboseのいずれかを選びます。自分のコードでは、log_e、log_w、log_iマクロでファイル名と行番号付きのログを出力できます。
1// ESP32: Serial (USB), Serial2 (GPIO16/17), printf ve log
2// ESP32: Serial (USB), Serial2 (GPIO16/17), printf and logging
3const int POT_PIN = 34; // ADC1, yalnız giriş pini / ADC1, input-only pin
4unsigned long sonYazdirma = 0;
5
6void setup() {
7 Serial.begin(115200); // açılış mesajlarıyla aynı hız / same rate as the boot messages
8 delay(500);
9 Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX = GPIO16, TX = GPIO17
10
11 Serial.printf("Model: %s, CPU: %lu MHz\n", ESP.getChipModel(), (unsigned long)ESP.getCpuFreqMHz());
12 Serial.printf("Bos RAM / Free heap: %lu bayt / bytes\n", (unsigned long)ESP.getFreeHeap());
13 log_i("Core Debug Level en az Info ise gorunur / shown when Core Debug Level is Info or higher");
14}
15
16void loop() {
17 while (Serial2.available() > 0) { // Serial2'den geleni USB'ye aktar / forward Serial2 data to USB
18 Serial.write(Serial2.read());
19 }
20
21 if (millis() - sonYazdirma >= 1000) { // delay() yok, tampon taşmaz / no delay(), no buffer overflow
22 sonYazdirma = millis();
23 float volt = analogReadMilliVolts(POT_PIN) / 1000.0; // kalibre gerilim / calibrated voltage
24 Serial.printf("t=%lu ms, GPIO34=%.2f V\n", millis(), volt); // ESP32'de %f çalışır / %f works on ESP32
25 }
26}Wi-Fiの接続トラブル、予期しないリセット、Guru Meditationエラーのときは、まずシリアルモニタを見ましょう。エラーの種類、リセットの原因、バックトレース(backtrace)の行がここに表示されます。これらのメッセージの読み方は、ESP32のブラウンアウトとGuru MeditationとESP32がWi-Fiにつながらないときのガイドで解説しています。ESP32を初めて使うなら、ESP32とは?の記事から始めましょう。
Raspberry Pi Picoのシリアル出力:ArduinoとMicroPython
Raspberry Pi PicoにはUSBシリアル変換チップがありません。RP2040が自分でUSBを処理し、パソコンからは直接シリアルポート(USB CDC)として見えます。Earle Philhower氏のArduino-PicoコアではSerialがこのUSBポートで、データはUSBの速度で流れるため、Serial.begin()に書いた速度の値は無視されます。これには2つの影響があります。モニタでどの速度を選んでも関係なくなる一方で、シリアルモニタを開いてもボードはUnoのようにリセットされません。setup()で最初に出力したメッセージは、モニタを開くころにはすでに送信されて消えてしまっています。そこで、下のようにパソコンがポートを開くのを少しだけ待ちます。Leonardo、Micro、Pro MicroのようなATmega32U4搭載ボードも同じです。PicoのGP0とGP1ピンにある本物のUARTは、Serial1という名前で使います。
1// Raspberry Pi Pico (Arduino-Pico çekirdeği): USB seri port
2// Raspberry Pi Pico (Arduino-Pico core): USB serial port
3void setup() {
4 Serial.begin(115200); // USB'de hız değeri önemsiz / the baud value is ignored over USB
5 unsigned long baslangic = millis();
6 while (!Serial && millis() - baslangic < 3000) {
7 delay(10); // port açılana kadar en çok 3 sn bekle / wait up to 3 s for the port
8 }
9 Serial1.begin(9600); // UART0: GP0 = TX, GP1 = RX
10 Serial.println("Pico hazir / Pico ready");
11}
12
13void loop() {
14 Serial.print("Sicaklik / Temperature (C): ");
15 Serial.println(analogReadTemp(), 1); // dahili sensör, yaklaşık / internal sensor, approximate
16 Serial1.println(millis()); // aynı anda UART0'a da yaz / also write to UART0
17 delay(1000);
18}MicroPythonではさらに簡単です。print()で出力したものはすべてUSB経由でパソコンに送られ、ThonnyのShell(シェル)に表示されます。設定するボーレートはありません。ThonnyでView > Plotter(「表示」>「プロッター」)メニューを開くと、行の中の数値がシリアルプロッタと同じようにリアルタイムのグラフになります。パソコンからデータを受け取るにはinput()を使えますが、input()は1行届くまでプログラムを止めてしまいます。測定を続けながらコマンドを待ち受けるには、下のようにselect.poll()でstdinにデータが届いているかを確認します。Shellにonかoffと入力してEnterを押すだけです。PicoとMicroPythonのインストールについてはRaspberry Pi Picoとは?の記事で解説しています。
1# Pico MicroPython: print() ile yazdır, programı durdurmadan komut al
2# Pico MicroPython: print with print(), read commands without blocking
3import sys
4import select
5import time
6from machine import ADC, Pin
7
8led = Pin(25, Pin.OUT) # Pico'da dahili LED; Pico W'de Pin("LED") / onboard LED; Pin("LED") on Pico W
9sensor = ADC(4) # dahili sıcaklık sensörü / internal temperature sensor
10poll = select.poll()
11poll.register(sys.stdin, select.POLLIN)
12
13while True:
14 volt = sensor.read_u16() * 3.3 / 65535
15 sicaklik = 27 - (volt - 0.706) / 0.001721
16 print("Sicaklik:{:.1f}".format(sicaklik)) # Thonny Plotter da çizer / Thonny's Plotter can graph it
17 if poll.poll(0): # bekleyen veri var mı? / is input waiting?
18 komut = sys.stdin.readline().strip() # satırı al, boşlukları sil / read the line, strip spaces
19 if komut == "on":
20 led.value(1)
21 elif komut == "off":
22 led.value(0)
23 print("Komut / Command:", komut)
24 time.sleep(1)シリアルモニタに何も出ない、ポートが使用中:よくあるエラーと対処法
| 症状 | 原因 | 対処法 |
|---|---|---|
| 画面に何も表示されない | Serial.begin()がない、または間違ったポートを選んでいる | setup()にSerial.begin()を追加し、ボードセレクタで正しいポートを選ぶ |
| 警告:Not connected. Select a board and a port to connect automatically(接続されていません) | IDEでポートが選ばれていない、またはボードが抜かれた | ボードを接続し、「ツール」>「ポート」メニューで選ぶ |
| エラー:Serial port busyまたはAccess is denied | ポートが別のIDEウィンドウ、PuTTY、Pythonスクリプトで開かれている | ポートをつかんでいるプログラムを閉じる。ポートは同時に1つのプログラムしか開けない |
| LeonardoやPicoで最初の数行が消える | USB内蔵のボードは、モニタを開いてもリセットされない | setup()でwhile (!Serial)を使って少し待つ |
| ESP32-S3/C3で何も表示されない | USB CDC On Bootが無効で、出力がUARTピンに出ている | 「ツール」>「USB CDC On Boot」:Enabled |
| 数値のたびに0が出る | parseIntが改行文字でタイムアウトしている | 数字以外の文字を読み飛ばすか、「改行なし」を選ぶ |
| コマンドの比較がいつも一致しない | 文字列の末尾に'\r'が残っている | 読み取ったあとにtrim()をかける |
| ポートが一覧にまったく出ない | 充電専用ケーブル、またはCH340/CP210xドライバがない | データ通信用ケーブルを使い、ドライバをインストールする |
シリアル通信を使ったプロジェクトのアイデア
- パソコンから操作するLEDとリレー:この記事のコマンドパーサーを拡張して、on、off、blinkのコマンドでリレーやモーターを制御しましょう。
- センサーデータロガー:温度と湿度をカンマ区切りのCSV行として出力し、パソコンで表やグラフにまとめます。最初のセンサーにはDHT11がおすすめです。
- 明るさのリアルタイムグラフ:LDRやポテンショメーターの値をシリアルプロッタで見ながら、フィルターの効果を同じグラフで確かめます。
- Pythonで作るデスクトップパネル:Arduinoが送ったデータをPythonプログラムで読み取り、ボタン付きの画面を作ります。詳しくはArduinoとPythonのシリアル通信の記事で解説しています。
- モジュールのテストステーション:GPS、Bluetooth、GSMモジュール(日本ではGSM網がないためLTEモジュール)もシリアルポートを使います。モジュールからの生データをシリアルモニタに転送して、動作しているか確かめましょう(HC-05、NEO-6M GPS)。
- ボードなしで試す:手元にボードがなくても、Tinkercadにもシリアルモニタがあります。この記事のコードはTinkercadのシミュレーションで動かせます。
シリアルモニタを使いこなせるようになると、Arduinoのバグ探しは当て推量ではなく、測って確かめる作業に変わります。次のステップとして、2枚のボードをTXとRXで通信させるにはUARTとは?の記事を、USBのないボードに書き込むにはUSB-TTL変換モジュールの記事を読んでみてください。BlueGraysアプリには、HC-SR04で測った距離をシリアルモニタに表示する、LDRで光の強さを読む、DHT11で温度と湿度を測る、2台のArduinoをシリアル線でつなぐといったプロジェクトが、回路図と完成済みのコード付きでステップごとに用意されています。
よくある質問
Arduinoのシリアルモニタはどうやって開きますか?
コードを書き込んだあと、Arduino IDE右上の虫めがねアイコンをクリックするか、「ツール」>「シリアルモニタ」メニューを使うか、Ctrl+Shift+Mを押します。IDE 2ではモニタは下部パネルに開きます。ボードセレクタで正しいポートが選ばれていること、右側の速度メニューがコードのSerial.begin()の値と一致していることが必要です。
シリアルモニタが文字化けするのはなぜですか?
いちばん多い原因は、シリアルモニタのボーレートがコードのSerial.begin()の値と違うことです。2つを一致させれば文字が読めるようになります。起動時の1行だけが化ける場合は、ESP8266やESP32自身の起動メッセージなので正常です。Pro Miniのようなボードで間違ったクロック周波数を選んだり、D0/D1ピンにモジュールをつないだりしても同じ症状が出ます。
Arduinoでは9600ボーと115200ボーのどちらを使うべきですか?
どちらでも動きます。大切なのは、コードとシリアルモニタで同じ速度を使うことです。9600ボーは1秒に約960文字を運び、Unoのシンプルなプロジェクトには十分です。大量のデータを表示する場合や、ESP32やPicoを使う場合は115200を選びましょう。12倍速く、表示によるプログラムの遅れも小さくなります。
Serial.printとSerial.printlnの違いは何ですか?
Serial.print()は値を書き出してカーソルを同じ行に残します。Serial.println()は書き出したあとに復帰と改行の文字(\r\n)を追加します。1行に複数の値を表示するには、先にprintを使い、最後にprintlnを使います。シリアルプロッタが各行を別々のサンプルとして数えられるように、行はprintlnで終える必要もあります。
シリアルモニタに何も表示されないのはなぜですか?
まず、setup()にSerial.begin()があるか、ボードセレクタで正しいポートが選ばれているかを確認します。Leonardo、Pico、ESP32-S3のようにUSBを内蔵したボードでは、setup()の最初のメッセージがモニタを開く前に送信されて消えることがあります。while (!Serial)で少し待つ処理を加えましょう。ESP32-S3とC3では、「ツール」メニューのUSB CDC On Boot設定もEnabledにする必要があります。
シリアルモニタを開くとArduinoがリセットされるのはなぜですか?
Uno、Nano、Megaでは、USBチップのDTR線がコンデンサを介してRESETピンにつながっています。ポートが開くとこの線に短いパルスが出て、ボードが再起動します。この仕組みは書き込みを楽にするためのもので、setup()のメッセージを見逃さずに済むという利点もあります。不要な場合はRESETとGNDの間に10 µFのコンデンサを付けられますが、書き込みの前には外す必要があります。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
HC-SR04で距離を測定(シリアルモニタ表示)
HC-SR04センサーは、コウモリが暗闇で道を見つけるのと同じ仕組みで動作します。人には聞こえない音を出し、その反響を捉えて前方の物体までの距離を測ります。Arduinoは0.5秒ごとに距離をセンチメートル単位でコンピューターの画面に表示し、3〜100センチメートルの測定値を示します。駐車センサーや障害物を避けるロボットなどのプロジェクトの基礎になります。

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

DHT11で温度と湿度を測定
部屋の温度は何度で、空気は湿っているでしょうか、それとも乾燥しているでしょうか?DHT11センサーが温度と湿度を測定し、Arduinoが2行のLCDディスプレイに値を表示して、数秒ごとに更新します。センサーに異常があればエラーメッセージが表示されます。自分だけの小さな気象観測装置を作る第一歩です。

ESP32とLDRモジュールで光の強さを読み取る
LDRモジュールの生のADC値と、暗い状態・明るい状態の基準値から計算した相対的な明るさの割合を、シリアルモニタでリアルタイムに確認します。この割合はルクスの測定値ではありません。モジュールの校正の仕組みを学べます。しきい値の決定に使うため、プロジェクト10の前に取り組むのが適しています。

IRリモコンのコードを読み取り
テレビのリモコンは、ボタンごとに目に見えない光で秘密のコードを送っています。赤外線受信機がその光を受け取り、Arduinoが押したボタンのコードをコンピューターの画面に表示します。コードが分かれば、引き出しに眠っている古いリモコンで自作のプロジェクトを操作できます。

RFIDの値の読み取り
建物のキーフォブや入館カードには、見えない識別番号が保存されています。このプロジェクトでは、その番号を読み取ります。カードをRFIDリーダーに近づけると、Arduinoがカードの番号をコンピューターの画面に表示します。カード式のドアや錠のプロジェクトで、どのカードに解錠を許可するかを決めるための第一歩です。

2台のArduinoで一方向通信
2台のArduinoを1本のワイヤーで通信させます。一方のボタンを押すともう一方のLEDが点灯し、もう一度押すと消灯します。送信側のボードがボタンの押下を短いメッセージで伝え、受信側のボードはそのメッセージを受け取るとLEDの点灯と消灯を切り替えます。機器同士が情報を伝える仕組みを、最もシンプルな形で確かめられます。

2台のArduinoで双方向通信
2台のArduinoをケーブルでつないで通信します。片方のボタンを押すともう片方のライトが点灯し、もう一度押すと消灯します。両方のボードで同じプログラムを動かすので、どちらからでもメッセージを送受信できます。ボード同士でメッセージをやり取りする仕組みを、シンプルな構成で学べます。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

