基礎 · 読了まで13分

UARTとは?TX/RXの配線、ボーレート、Arduinoのシリアル通信ガイド

UARTとは何か、どう動くのかを解説します。TX/RXのクロス接続、ボーレート、スタート・ストップビット、2台のArduinoの接続、SoftwareSerial、5V/3.3Vのレベル、ESP32のSerial2、PicoのUARTを紹介します。

UARTとは何か、簡単に説明します。UART(Universal Asynchronous Receiver-Transmitter)は、2本のデータ線(TXとRX)と共通GNDだけで、2台の機器が1バイトずつデータをやり取りするためのシリアル通信ハードウェアです。Arduinoをパソコンにつないだときにシリアルモニタに表示される各行は、UARTを通って届きます。GPS受信機、HC-05 Bluetoothモジュール、ESP-01 Wi-Fiモジュール、SIM800L GSMモジュール、指紋リーダー、MP3プレーヤーなど、数多くのモジュールもUARTでArduinoと通信します。

この記事では、UARTにクロック線が不要な理由、ボーレートとは何か、スタートビット・データビット・ストップビットで1バイトを囲む仕組み、TXとRXを交差させる理由、2台のArduinoで一方向通信から双方向通信へ進む方法、SoftwareSerialの制約、5 Vと3.3 Vのボードを安全につなぐ方法、ESP32の3つのUART、Raspberry Pi PicoのUARTピンを順に解説します。最後に、データが混ざらないよう開始文字と終了文字でパケットを組み立てる方法と、よくある間違いを紹介します。

CP2102 USB-TTL変換モジュール
CP2102 USB-TTL変換モジュール:パソコンのUSBポートを、TX・RX・GNDピンを持つUARTに変換します。Arduino UnoのUSBチップも同じ役割を果たします。

UARTとは?どのように動く?

UARTの名前にある「非同期」がポイントです。I2CやSPIではデータと一緒にクロック信号を送りますが、UARTにはクロック線がありません。その代わり、通信する両側でボーレートという速度を事前に決めます。待機中、信号線はHIGH(1)です。1バイトを送るとき、送信機は1ビット分の時間だけ信号線をLOWにします。これがスタートビットで、受信機に「今から数え始める」ことを知らせます。続いて8ビットのデータを最下位ビット(LSB)から順に送り、設定によってはパリティビットが続きます。最後に信号線を少なくとも1ビット分の時間HIGHに保ちます。これがストップビットです。Arduinoの既定の設定は8N1で、データ8ビット、パリティなし、ストップ1ビットを意味します。

ビットスタートD0D1D2D3D4D5D6D7ストップ
文字「A」(0x41 = 0100 0001)0100000101
信号線のレベルLOWHIGHLOWLOWLOWLOWLOWHIGHLOWHIGH

各ビットの長さは1 / ボーレート秒です。9600ボーでは1ビットが104.2マイクロ秒です。8N1フレームは1バイトあたり10ビット(スタート + データ8ビット + ストップ)なので、1秒間に最大960バイト送れます。受信機はスタートビットの立ち下がりエッジを検出すると、各ビットの中央で信号線をサンプリングします。両側のクロックが少し違っても、10ビットの間に積み重なる誤差が半ビット未満なら正しく読み取れます。そのため、ボーレートの合計誤差はおおむね2〜3%未満に抑える必要があります。Arduino Unoの16 MHz水晶では115200ボーを正確に作れず、実際の速度には約2.1%のずれがありますが、許容範囲内なので動作します。

ボーレート1ビットの時間毎秒の最大バイト数(8N1)使用例
9600104.2 µs960NEO-6M GPS、HC-05のデータモード、DFPlayer Mini、シリアルモニタの定番設定
1920052.1 µs1920一部のセンサーや古いモデム
3840026.0 µs3840HC-05のATコマンドモード
5760017.4 µs5760古いArduinoブートローダー、SoftwareSerialの上限
1152008.68 µs11520ESP32とESP-01、高速なデバッグ出力
1000000以上1 µs以下100,000以上短距離のボード間通信、高速なデータ記録

UART・I2C・SPIの比較

Arduinoで最もよく使われるシリアル通信方式はUART、I2C、SPIの3つです。UARTは2台の機器間の1対1通信専用で、両側が好きなときに送信できます(全二重)。I2Cはアドレスを使い、同じ2本の線に数十台の機器をまとめます。SPIは4本の線でより高速に通信しますが、機器ごとに専用の選択ピンが必要です。詳しくはI2Cとは?とSPIとは?の記事をご覧ください。

項目UARTI2CSPI
配線数2本(TX、RX)+ GND2本(SDA、SCL)+ GND4本(SCK、MOSI、MISO、CS)+ GND
クロック線なし(非同期)ありあり
機器の数2台(1対1)アドレスで最大127台CSピンの数だけ
一般的な速度9600〜115200ボー100〜400 kHz1〜80 MHz
同時双方向通信可能(全二重)不可可能
代表的なモジュールGPS、Bluetooth、GSM、ESP-01、指紋認証OLED、LCD、RTC、BME280SDカード、RFID、TFT、LoRa

TXとRXのクロス接続と共通GND

UART配線の基本は、一方の機器のTX(送信)ピンを、もう一方のRX(受信)ピンにつなぎ、線を交差させることです。口と耳の関係を思い浮かべてください。一方が話すところで、もう一方は聞く必要があります。TX同士をつなぐと2つの出力を結ぶことになり、データは通りません。3本目の線であるGNDは、両方のボードに同じ電圧基準を与えます。これがないと「HIGH」と「LOW」が意味を持たなくなり、受信データが文字化けします。ボードを別々の電源で動かす場合も、GND同士をつなぐ必要があります。

Arduino A配線Arduino B補足
TX(D11、SoftwareSerial)→RX(D10、SoftwareSerial)Aの送信データがBへ届く
RX(D10、SoftwareSerial)←TX(D11、SoftwareSerial)Bの応答がAへ届く
GND—GND共通グラウンド。必須
5V任意5VUSBから給電するボードが片方だけの場合のみ
Arduino UnoとNanoでは、ハードウェアUARTのD0(RX)とD1(TX)ピンはUSBチップにもつながっています。このピンにモジュールを接続したままコードを書き込もうとすると、「not in sync」エラーで失敗し、シリアルモニタではモジュールのデータとパソコンのデータが混ざります。書き込み中はD0/D1の線を外すか、この記事の例のようにSoftwareSerialでほかのピンを使ってください。

2台のArduinoを通信させる:一方向通信

Arduino Uno
Arduino Uno:唯一のハードウェアUART(D0/D1)はUSB用に使うため、2台のUnoをつなぐにはD10/D11でSoftwareSerialを使うのが最も簡単です。

最初の例では、ボードAがA0のポテンショメーターの値を1秒に10回ボードBへ送り、ボードBはその値に合わせてD9のLEDの明るさを調整します。両方のボードでD10(RX)とD11(TX)にSoftwareSerialを使うため、USB接続とシリアルモニタを自由に使えます。値はprintln()で送るので、各数値の末尾に「\r\n」(改行)の文字が付き、受信側はそれを見て数値の終わりを判断します。配線はAのD11からBのD10、AのD10からBのD11をつなぎ、GND同士もつなぎます。

C++ (Arduino IDE)
1#include <SoftwareSerial.h>
2
3SoftwareSerial link(10, 11);    // RX = 10, TX = 11 (diğer karta / to the other board)
4
5void setup() {
6  Serial.begin(9600);           // USB: bilgisayar / computer
7  link.begin(9600);             // iki tarafta aynı baud / same baud on both sides
8}
9
10void loop() {
11  int deger = analogRead(A0);   // potansiyometre 0-1023 / potentiometer 0-1023
12  link.println(deger);          // ör. "512\r\n" / e.g. "512\r\n"
13  Serial.println(deger);
14  delay(100);                   // saniyede 10 ölçüm / 10 readings per second
15}
16
C++ (Arduino IDE)
1#include <SoftwareSerial.h>
2
3SoftwareSerial link(10, 11);    // RX = 10, TX = 11
4const int LED_PIN = 9;          // PWM pini / PWM pin
5
6char tampon[8];                 // en fazla 7 karakter + '\0' / up to 7 chars + '\0'
7byte konum = 0;
8
9void setup() {
10  Serial.begin(9600);
11  link.begin(9600);
12  pinMode(LED_PIN, OUTPUT);
13}
14
15void loop() {
16  while (link.available()) {               // gelen her baytı oku / read every incoming byte
17    char c = link.read();
18    if (c == '\n') {                       // satır bitti / line complete
19      tampon[konum] = '\0';
20      int deger = atoi(tampon);
21      analogWrite(LED_PIN, map(deger, 0, 1023, 0, 255));
22      Serial.println(deger);
23      konum = 0;
24    } else if (c != '\r' && konum < sizeof(tampon) - 1) {
25      tampon[konum++] = c;                 // satıra ekle / append to the line
26    }
27  }
28}
29

受信コードには注目すべき点が2つあります。まず、データは「ひとまとまり」では届きません。「512」は3つの別々のバイトとして、約1ミリ秒間隔で届きます。そのため、バイトをバッファにため、改行が届いた時点で数値に変換します。次に、コードは待機しません。readString()やparseInt()のような関数は、データが来ないと既定で1秒待つため、プログラムが遅くなります。届いたバイトから順に読む方法が、最も確実で高速です。また、破損したデータを配列の末尾より先に書き込まないよう、バッファのサイズにも制限を設けています。

双方向通信:コマンドと応答

実際のプロジェクトでは、一方のボードがコマンドを送り、もう一方が実行して応答する双方向のやり取りがよく必要になります。以下の例ではボードAがブリッジとして動作し、シリアルモニタで入力したすべての文字をBへ転送し、Bから届いたすべての文字を表示します。ボードBは「LED ON」「LED OFF」「DURUM?」(状態)のコマンドを理解してそれぞれに応答し、D2のボタンが押されたときにも自発的に通知します。シリアルモニタの改行コードをLFのみに設定するのを忘れないでください。Bはその文字でコマンドの終わりを判断します。

C++ (Arduino IDE)
1#include <SoftwareSerial.h>
2
3SoftwareSerial link(10, 11);    // RX = 10, TX = 11
4
5void setup() {
6  Serial.begin(9600);
7  link.begin(9600);
8  Serial.println("Komut yaz / type a command: LED ON, LED OFF, DURUM?");
9}
10
11void loop() {
12  if (Serial.available()) link.write(Serial.read());   // bilgisayar -> B kartı / computer -> board B
13  if (link.available()) Serial.write(link.read());     // B kartı -> bilgisayar / board B -> computer
14}
15
C++ (Arduino IDE)
1#include <SoftwareSerial.h>
2
3SoftwareSerial link(10, 11);    // RX = 10, TX = 11
4const int LED_PIN = 13;         // kart üstündeki LED / on-board LED
5const int BUTON_PIN = 2;        // buton GND'ye / button to GND
6
7String satir = "";
8int oncekiButon = HIGH;
9
10void komutCalistir(String k) {
11  k.trim();                                  // \r ve boşlukları at / strip \r and spaces
12  if (k == "LED ON") {
13    digitalWrite(LED_PIN, HIGH);
14    link.println("OK LED=1");
15  } else if (k == "LED OFF") {
16    digitalWrite(LED_PIN, LOW);
17    link.println("OK LED=0");
18  } else if (k == "DURUM?") {                // durum sorgusu / status query
19    link.print("LED=");
20    link.print(digitalRead(LED_PIN));
21    link.print(" BUTON=");
22    link.println(digitalRead(BUTON_PIN) == LOW ? 1 : 0);
23  } else {
24    link.print("HATA / unknown: ");
25    link.println(k);
26  }
27}
28
29void setup() {
30  link.begin(9600);
31  pinMode(LED_PIN, OUTPUT);
32  pinMode(BUTON_PIN, INPUT_PULLUP);
33  satir.reserve(32);
34}
35
36void loop() {
37  while (link.available()) {
38    char c = link.read();
39    if (c == '\n') {
40      komutCalistir(satir);
41      satir = "";
42    } else if (satir.length() < 31) {
43      satir += c;
44    }
45  }
46
47  int buton = digitalRead(BUTON_PIN);        // değişince kendiliğinden bildir / report changes unprompted
48  if (buton != oncekiButon) {
49    oncekiButon = buton;
50    link.println(buton == LOW ? "BUTON BASILDI / pressed" : "BUTON BIRAKILDI / released");
51    delay(20);                               // basit debounce / simple debounce
52  }
53}
54

ボードBのtrim()呼び出しは、シリアルモニタが「CRおよびLF」に設定されているときに届く\r文字と余分な空白を除去します。そのため、どちらの設定でもコマンドを認識できます。Stringオブジェクトにreserve(32)を使うと、文字を追加するたびにメモリが再割り当てされるのを防ぎ、Unoの2 KBのRAMの断片化を抑えられます。この構成は、パソコンのPythonプログラムからArduinoと通信するための基本にもなります。詳しくはArduinoとPythonのシリアル通信の記事をご覧ください。

SoftwareSerialの制約

SoftwareSerialはハードウェアではなくソフトウェアでUARTを再現し、プロセッサ自身が各ビットのタイミングを管理します。とても便利ですが、代償もあります。送信中は1バイト全体にわたって割り込みが無効になります。9600ボーでは1バイトあたり約1ミリ秒で、その間にmillis()がずれたり、サーボのパルスが揺らいだり、別のシリアルポートに届いたデータを失ったりする可能性があります。同時に受信待機できるSoftwareSerialポートは1つだけです。2つ目を開いた場合は、listen()で受信待機するポートを選ぶ必要があります。MegaやLeonardoなどのボードでは、割り込みに対応した特定のピンしかRXに使えません。

項目ハードウェアUART(Serial)SoftwareSerialAltSoftSerial
UnoのピンD0(RX)、D1(TX)、USBと共用ほぼ任意のピン(RXにはPCINTが必要)固定:D8(RX)、D9(TX)
安定して使える最高速度1〜2 Mbaud57600ボー(115200は限界に近い)中程度の速度(9600〜38400)で安定
CPU負荷非常に低い(ハードウェアバッファ)高い。送信中は割り込み無効中程度。Timer1を使用
同時送受信可能不可可能
複数ポートMegaでは4つ同時に受信待機できるのは1つ1ポートのみ
競合する機能コードの書き込みとシリアルモニタServo、NeoPixelなどタイミングに厳しいライブラリD10のPWMとServo(Timer1)
複数のUARTモジュール(GPS + Bluetoothなど)を使う場合は、4つのハードウェアUART(Serialに加えてSerial1〜Serial3)を持つArduino Megaか、3つを持つESP32を選んでください。LeonardoとPro MicroではUSBが独立しているため、D0/D1のSerial1を完全に自由に使えます。

5Vと3.3Vのレベルを合わせる

UARTの「HIGH」のレベルはボードの動作電圧です。Arduino Unoは5 V、ESP32、Raspberry Pi Pico、ESP-01、多くの新しいモジュールは3.3 Vです。5 VのTX出力を3.3 VのRX入力に直接つなぐと、入力に過電圧が加わり、時間の経過とともに、または直ちにピンが壊れます。ESP32とRP2040を搭載したPicoは5 Vトレラントではありません。逆方向は通常問題ありません。Unoは3.0 V以上をHIGHと判断するため、3.3 VのTXを読み取れます。ただし、しきい値に近いので、長い配線ではレベル変換モジュールを使うほうが確実です。

接続必要なもの例
Uno TX(5 V)→ ESP32 / Pico RX(3.3 V)分圧回路かレベル変換モジュール直列に1 kΩ + GNDへ2 kΩ:5 × 2/3 ≈ 3.3 V
ESP32 / Pico TX(3.3 V)→ Uno RX(5 V)通常は直接接続Unoは3.0 Vを超える電圧をHIGHと判断
Uno TX → HC-05 RX分圧回路を推奨HC-05のRXは3.3 Vレベルの入力
Uno TX → SIM800L RX分圧回路SIM800Lの信号レベルは約2.8 V
両方とも5 V、または両方とも3.3 V直接接続Uno ↔ Nano、ESP32 ↔ Pico
分圧回路が適しているのは、一方向の低〜中速信号(例えば115200ボーまで)だけです。双方向や非常に高速な信号線には、BSS138を使った双方向レベル変換モジュールを使ってください。分圧用の抵抗値は分圧計算ツールで計算できます。レベル変換モジュールはレベル変換モジュールの記事で詳しく解説しています。

ESP32のUART:Serial、Serial1、Serial2

ESP32には3つのハードウェアUARTがあります。特に便利なのは、GPIOマトリクスによってUARTをほぼ任意のピンに割り当てられることです。UART0(Serial)はUSBシリアル変換チップにつながっており、シリアルモニタに使います。UART2(Serial2)の既定のピンはGPIO 16(RX)と17(TX)です。UART1の従来の既定ピン(GPIO 9と10)はボードのフラッシュメモリにつながっていて直接使えないので、Serial1は必ずbegin(baud, SERIAL_8N1, RX, TX)のようにピンを指定して開始してください。以下のコードは、シリアルモニタとUART2の機器(GPS、別のArduino、センサー)の間に双方向ブリッジを作ります。GPSモジュールのNMEAセンテンスを確認するのに最適です。

UARTArduinoでの名前既定のRX / TX補足
UART0SerialGPIO 3 / GPIO 1USBシリアル変換チップに接続。コードの書き込みとシリアルモニタに使用
UART1Serial1GPIO 9 / GPIO 10(フラッシュ用、使用不可)begin()でほかのピンを指定。例:25 / 26
UART2Serial2GPIO 16 / GPIO 17WROVERボードでは16/17をPSRAMが使うため、ほかのピンを選ぶ
C++ (Arduino IDE)
1// ESP32: UART0 = Serial (USB), UART2 = Serial2 (GPIO 16/17)
2const int RXD2 = 16;            // karşı cihazın TX'i buraya / the other device's TX goes here
3const int TXD2 = 17;            // karşı cihazın RX'ine / to the other device's RX
4
5void setup() {
6  Serial.begin(115200);                          // USB Seri Monitör / USB Serial Monitor
7  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);   // baud, çerçeve, RX, TX / baud, frame, RX, TX
8  // UART1'i de istediğin pinlere taşıyabilirsin / UART1 can be moved to any free pins:
9  // Serial1.begin(115200, SERIAL_8N1, 25, 26);
10  Serial.println("UART2 hazir / ready");
11}
12
13void loop() {
14  while (Serial2.available()) Serial.write(Serial2.read());   // UART2 -> USB
15  while (Serial.available()) Serial2.write(Serial.read());    // USB -> UART2
16}
17

ESP32のUARTは最大5 Mbaudに対応し、それぞれにハードウェアバッファがあります。既定の受信バッファは256バイトで、begin()の前にSerial2.setRxBufferSize(1024)のように呼び出すと拡大できます。これは1秒間に数十のセンテンスを送るGPSモジュールや、長い応答を返すGSMモジュールで重要です。ESP32のピンは3.3 Vなので、5 VのArduinoにつなぐときは前の節の分圧回路を忘れないでください。ESP32のピン配置はピン配置ツールで確認できます。

Raspberry Pi PicoのUARTピンとMicroPython

Raspberry Pi Picoには2つのハードウェアUART(uart0とuart1)があり、それぞれを複数のピンの組み合わせに割り当てられます。MicroPythonではREPLとprint()の出力にUSB上の別の仮想シリアルポートを使うため、UART0は完全に自由に使えます。Arduinoのようにコードの書き込み時に線を外す必要はありません。Picoのピンは3.3 Vで、RP2040を搭載したPicoでは5 Vトレラントではありません。以下のコードは、ArduinoのボードBと同じ処理をPicoで行います。UART0から行を読み取り、「LED ON」と「LED OFF」のコマンドで内蔵LEDを点灯・消灯し、応答を返します。Unoのブリッジスケッチと通信させる場合は、UnoのTX線に分圧回路を入れるのを忘れないでください。

UARTTXピンRXピンMicroPythonの例
uart0GP0, GP12, GP16GP1, GP13, GP17UART(0, 9600, tx=Pin(0), rx=Pin(1))
uart1GP4, GP8GP5, GP9UART(1, 9600, tx=Pin(4), rx=Pin(5))
MicroPython
1from machine import UART, Pin
2import time
3
4uart = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))   # UART0: GP0 = TX, GP1 = RX
5led = Pin("LED", Pin.OUT)                             # Pico ve Pico W dahili LED / on-board LED
6
7tampon = b""
8while True:
9    if uart.any():
10        tampon += uart.read()                         # gelen baytlar / incoming bytes
11        while b"\n" in tampon:                        # her tam satırı işle / handle each full line
12            satir, tampon = tampon.split(b"\n", 1)
13            try:
14                komut = satir.decode().strip()
15            except UnicodeError:
16                continue                              # bozuk bayt: atla / corrupt byte: skip
17            if komut == "LED ON":
18                led.on()
19                uart.write("OK LED=1\n")
20            elif komut == "LED OFF":
21                led.off()
22                uart.write("OK LED=0\n")
23            else:
24                uart.write("HATA / unknown: " + komut + "\n")
25    if len(tampon) > 64:                              # satır sonu hiç gelmezse taşmayı önle / avoid overflow
26        tampon = b""
27    time.sleep_ms(5)
28

パケットの区切り:開始文字と終了文字

UARTはバイトの流れであり、データの始まりや終わりを認識しません。送信側が「23.5,48,812」と送っても、受信側が途中から受信待機を始めていたり、ノイズで1バイト失われたり、2回分の測定値がつながってしまったりすることがあります。解決策はデータをパケットにすることです。各パケットを「<」で始め、「>」で終えると、受信側は最初の「<」までを無視し、「>」が届いたらパケットを処理して、途中に入り込んだ不完全なパケットを捨てられます。送信側で必要なのは、Serial.print('<')とSerial.print('>')の間に値をカンマ区切りで出力することだけです。シリアルモニタに<23.5,48,812>と入力すれば、以下の受信コードをすぐに試せます。

C++ (Arduino IDE)
1// Paket biçimi / packet format: <sicaklik,nem,isik>  ör. / e.g. <23.5,48,812>
2const byte MAKS = 32;
3char tampon[MAKS];
4byte konum = 0;
5bool paketIcinde = false;                    // '<' görüldü mü? / have we seen '<'?
6
7void paketiIsle(char* p) {
8  char* parca = strtok(p, ",");              // ilk alan / first field
9  if (!parca) return;
10  float sicaklik = atof(parca);
11  parca = strtok(NULL, ",");
12  if (!parca) return;                        // eksik alan: paketi at / missing field: drop
13  int nem = atoi(parca);
14  parca = strtok(NULL, ",");
15  if (!parca) return;
16  int isik = atoi(parca);
17
18  Serial.print("Sicaklik / temp: ");
19  Serial.print(sicaklik, 1);
20  Serial.print("  Nem / humidity: ");
21  Serial.print(nem);
22  Serial.print("  Isik / light: ");
23  Serial.println(isik);
24}
25
26void setup() {
27  Serial.begin(9600);                        // Seri Monitör'den <23.5,48,812> yazarak dene / test from the Serial Monitor
28}
29
30void loop() {
31  while (Serial.available()) {
32    char c = Serial.read();
33    if (c == '<') {                          // yeni paket başladı / a new packet starts
34      paketIcinde = true;
35      konum = 0;
36    } else if (c == '>' && paketIcinde) {    // paket bitti / packet complete
37      tampon[konum] = '\0';
38      paketIcinde = false;
39      paketiIsle(tampon);
40    } else if (paketIcinde) {
41      if (konum < MAKS - 1) tampon[konum++] = c;
42      else paketIcinde = false;              // çok uzun, bozuk: at / too long, corrupt: drop
43    }                                        // paket dışındaki baytlar yok sayılır / bytes outside packets are ignored
44  }
45}
46
方法例長所短所
改行で区切るテキスト512\n最も簡単。シリアルモニタで読める行の途中では先頭が分からない
開始文字と終了文字<23.5,48,812>同期を失っても自動で復帰データ内に<と>を使えない
チェックサム付きテキスト(NMEAなど)$GPGGA,...*47破損したパケットを検出できる計算とコードが少し増える
バイナリパケット0xAA、長さ、データ、CRC最短かつ最速読みにくく、デバッグしにくい

ノイズの多い環境や長い配線では、さらに一歩進めてパケットにチェックサムを加えられます。GPSモジュールが使うNMEA形式がよい例です。「$」と「*」の間にあるすべての文字のXORを取り、結果を2桁の16進数で末尾に書きます。受信側も同じ計算を行い、一致しなければパケットを捨てます。これにより、「812」が破損して「872」として処理されるような、気付きにくい誤りを防げます。

送信側の速度は、受信側の処理能力を超えてはいけません。Arduino Unoのハードウェア受信バッファはわずか64バイトです。loop()内で長いdelay()を使うと、バッファがいっぱいになった時点で届いたバイトを失います。受信側ではdelay()の代わりにmillis()を使い、ループのたびにSerial.available()を確認してください。

UARTのプロジェクトアイデア

  • スマホから操作:HC-05 Bluetoothモジュールは、実質的には無線のUARTです。HC-05でスマホからLED、リレー、ロボットカーを操作します。
  • GPS位置の記録:NEO-6M GPSモジュールがUARTで送るNMEAセンテンスを解析し、位置をSDカードに書き込みます。
  • 2枚のボードのシステム:センサーを読み取るUnoと、Wi-Fiでデータをインターネットに送るESP32をUARTで接続します。
  • SMSアラーム:UARTでSIM800LモジュールにATコマンドを送り、ドアが開いたら自分宛てにSMSを送信します。
  • パソコンの操作パネル:Pythonで作ったデスクトップアプリからArduinoにコマンドを送り、センサーデータをグラフにします。
  • MP3プレーヤー:DFPlayer Miniを使い、UARTで曲を選び、音量を調整し、音声の警告を再生します。

よくある間違いと対処法

症状考えられる原因対処法
シリアルモニタで文字化けするボーレートが一致していないコードとシリアルモニタで同じボーレートを使う
データがまったく届かないTX同士、RX同士をつないでいるTX → RX、RX → TXと交差させる
不規則に文字が化けるGNDが未接続両方のボードのGNDをつなぐ
書き込みに失敗する(not in sync)D0/D1にモジュールが接続されている書き込み中は線を外すか、SoftwareSerialを使う
コマンドを認識しない改行コードが\r\n、または改行なしシリアルモニタを「LFのみ」にし、trim()で受信テキストを整える
長いメッセージの末尾が欠ける64バイトのバッファがあふれたdelay()を避け、届いたデータをすぐ読み取り、速度を下げる
ESP32のRXピンが動かない、または壊れた5 VのTXを直接接続した分圧回路かレベル変換モジュールを追加
SoftwareSerialが115200で失敗するソフトウェアのタイミング処理が追い付かない57600以下にするか、ハードウェアUARTに切り替える
UARTモジュールが応答しない場合は、まずUSB-TTL変換モジュールでパソコンに直接つなぎ、シリアル端末からコマンドを送ってください。そこで動くなら、問題はArduinoのコードか配線にあります。そこでも動かない場合は、ボーレート、電源、モジュール自体を確認してください。

UARTは電子工作で最初に学びたい通信方式です。2本の線、共通GND、同じボーレートだけで通信でき、正しく設定すれば何年も問題なく動作します。クロス接続、レベル合わせ、パケットの組み立てを理解すれば、GPSからGSMまで数多くのモジュールを手軽に使えます。BlueGraysアプリには、2台のArduino間の一方向・双方向通信、BluetoothでスマホからLEDを点灯するプロジェクト、Bluetoothで操作するロボットカーのプロジェクトが、ステップごとの回路図とコード付きで用意されています。

よくある質問

UARTは何の略で、何に使いますか?

UARTはUniversal Asynchronous Receiver-Transmitterの略です。クロック線を使わず、TXとRXの線と事前に決めたボーレートだけで、2台の機器が1バイトずつデータをやり取りできます。Arduinoのシリアルモニタ、GPS、Bluetooth、GSMモジュールはいずれもUARTを使います。

TXとRXはどうつなぎますか?

一方のTXピンをもう一方のRXピンへ、RXピンをもう一方のTXピンへと交差させて接続します。両方の機器のGNDピンも必ずつないでください。TX同士をつなぐと、データはまったく通りません。

ボーレートとは何ですか?どれを選べばよいですか?

ボーレートは1秒間に送るビット数で、両側で同じ値にする必要があります。Arduinoで使うモジュールでは9600と115200が最も一般的です。多くのGPSやBluetoothモジュールの既定値は9600で、ESP32や高速な出力には115200がよく使われます。まずはモジュールの資料に記載された既定の速度で始めてください。

UARTとシリアルポートは同じですか?

普段は同じ意味で使われることがよくあります。UARTはデータを直列化するハードウェアユニットで、シリアルポートはパソコンのCOMや/dev/ttyの接続です。RS-232とRS-485は、異なる電圧レベルを使ってUART信号をより遠くまで送るための規格です。

Arduinoで複数のUARTを使うにはどうすればよいですか?

UnoのハードウェアUARTは1つで、USBと共用しています。ポートを増やすにはSoftwareSerialを使いますが、同時に受信待機できるのは1つだけです。Arduino MegaにはSerial1、Serial2、Serial3という3つの追加ハードウェアUARTがあり、ESP32には合計3つ、Raspberry Pi Picoには2つあります。

UARTケーブルは何メートルまで使えますか?

3.3 Vまたは5 VのTTLレベルUARTは、低いボーレートなら数メートルまで安定して動作しますが、速度を上げると距離は短くなります。さらに遠くへ送る場合は、MAX485などのRS-485トランシーバーで数百メートル、RS-232で約15メートルまで対応できます。

全プロジェクトを見る
2台のArduinoで一方向通信
#57中級
プレミアム

2台のArduinoで一方向通信

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

4.4(37)
50
2台のArduinoで双方向通信
#58中級
プレミアム

2台のArduinoで双方向通信

2台のArduinoをケーブルでつないで通信します。片方のボタンを押すともう片方のライトが点灯し、もう一度押すと消灯します。両方のボードで同じプログラムを動かすので、どちらからでもメッセージを送受信できます。ボード同士でメッセージをやり取りする仕組みを、シンプルな構成で学べます。

4.5(20)
26
BluetoothでスマートフォンからLEDを操作
#34初級
プレミアム

BluetoothでスマートフォンからLEDを操作

スマートフォンで部屋の照明をつけたり消したりするための、小さな練習です。Bluetoothモジュールがスマートフォンから送られた文字をArduinoに伝えます。ある文字で赤いLEDが、別の文字で緑のLEDが点灯し、同じ文字を小文字で送ると消灯します。無線操作の仕組みを、ごくシンプルな形で学べます。

4.8(68)
89
L298Nで作るBluetooth操作の車
#80上級
プレミアム

L298Nで作るBluetooth操作の車

スマートフォンをリモコンにして、自作のロボットカーを操作できます。ArduinoはBluetoothモジュールを通じてスマートフォンのリモコンアプリから指令を受け取り、モータードライバーで車輪を回します。前進、後退、左右や斜めへの移動ができ、速度もスマートフォンから調整できます。停止ボタンを押すと、短いブレーキ動作でモーターがすぐに止まります。

5.0(38)
65
Python開発者向けキーボード
#66中級
プレミアム

Python開発者向けキーボード

コーディング中に同じパターンを何度も入力する手間を省く、小さなキーボードです。Arduino Leonardoをコンピューターにキーボードとして接続します。4x4キーパッドの各キーを1回押すだけで、条件分岐、ループ、画面への出力、ファイルの読み込みなどのPythonのひな形を入力し、書き足す場所へカーソルを移動します。F5を押して、書いたプログラムを実行するキーもあります。

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

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

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

4.6(46)
71
DHT11で温度と湿度を測定
#55中級

DHT11で温度と湿度を測定

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

4.7(47)
73
ESP32とLDRモジュールで光の強さを読み取る
#113初級

ESP32とLDRモジュールで光の強さを読み取る

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

4.6(36)
55