ESP32-devKitのUART0/UART1でhardwareSerial(シリアル通信)をテストする

ESP32のUART(hardwareSerial)通信をテストします。
初めてESP32を触る方にもわかりやすいよう、なるべく簡単・詳細に説明します。

シリアル通信とは

シリアル通信は、超簡単に言ってしまうと、1bitずつデータを伝送する方式。と言えます。1bitずつ送信するため、データバスは最低1本〜数本でよいです。複数データを同時に送るパラレル通信に比べて、バスの本数が少なくて済むのが特徴と言えます。

Wikipediaによると、下記の記述となっています。

シリアル通信(シリアルつうしん、英: Serial communication)は、電気通信において伝送路上を一度に1ビットずつ、逐次的にデータを送ることをいう。また、コンピュータにおいては、バス上を一度に1ビットずつ、逐次的にデータを送ることをいう。対照的にパラレル通信では、何らかの文字や記号を表すビット群が一度に送られる。シリアル通信は長距離の通信やコンピュータネットワークで使われる。これは、電線の本数を減らすためであり、同時にパラレル通信を長距離で使うと同期が困難になるためである。

https://ja.wikipedia.org/wiki/シリアル通信

ESP32で使えるシリアル通信の種類は?

ESP32ではUART、I2C、SPIの他、bluetoothでシリアル通信を行うためのプロファイルなどもサポートしています。
今回は、一番単純なUARTを試します。

ESP32で使えるUARTポートとピン割り当て

ESP32にはUARTのポートが3つあります。それぞれ、UART0、UART1、UART2と呼びます。

下表はESP32-WROOM-32D & ESP32-WROOM-32U Datasheet v2.3 8~9ページからの抜粋転載です。
太字のところがUARTとして使えるピンですが、UART1のみデフォルトでは使用できません。(TX/RXとも内蔵フラッシュと繋がっているバスと共用されているため)このため、UART1は別のピンにアサインして使用する必要があります(後述のコードで記載します)

NameNo.TypeFunction
SHD/SD2*17I/OGPIO9, SD_DATA2, SPIHD, HS1_DATA2, U1RXD
SWP/SD3*18I/OGPIO10, SD_DATA3, SPIWP, HS1_DATA3, U1TXD
SCS/CMD*19I/OGPIO11, SD_CMD, SPICS0, HS1_CMD, U1RTS
SCK/CLK*20I/OGPIO6, SD_CLK, SPICLK, HS1_CLK, U1CTS
SDO/SD0*21I/OGPIO7, SD_DATA0, SPIQ, HS1_DATA0, U2RTS
SDI/SD1*22I/OGPIO8, SD_DATA1, SPID, HS1_DATA1, U2CTS
IO1627I/OGPIO16, HS1_DATA4, U2RXD, EMAC_CLK_OUT
IO1728I/OGPIO17, HS1_DATA5, U2TXD, EMAC_CLK_OUT_180
IO1931I/OGPIO19, VSPIQ, U0CTS, EMAC_TXD0
RXD034I/OGPIO3, U0RXD, CLK_OUT2
TXD035I/OGPIO1, U0TXD, CLK_OUT3, EMAC_RXD2
IO2236I/OGPIO22, VSPIWP, U0RTS, EMAC_TXD1
ESP32-WROOM-32D & ESP32-WROOM-32U Datasheet v2.3より抜粋転載

* Pins SCK/CLK, SDO/SD0, SDI/SD1, SHD/SD2, SWP/SD3 and SCS/CMD, namely, GPIO6 to GPIO11 are connected to the integrated SPI flash integrated on the module and are not recommended for other uses.
(概略:* のついているピンは内蔵フラッシュに繋がっているため、その他の利用を推奨しません)

シリアル通信でよく使うAPI

ESP32のシリアル通信でよく使うAPIを表にまとめておきます。
下記サンプルコードを適宜書き換えるなどして試してみてください。

API説明使用方法
Serial.begin()シリアルデータ伝送のデータレートをビット/秒(ボー)で設定します。
HardwareSerialでは、IO指定ができるように拡張されています。
void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL, uint8_t rxfifo_full_thrhd = 112);
mySerial1.begin(115200,SERIAL_8N1,32,33);
Serial.available()シリアルポートから読み取るために使用できるバイト数(文字)を取得します。これは、すでに到着し、シリアル受信バッファに保存されているデータです。while (Serial.available()
Serial.availableForWrite()書き込み操作をブロックすることなく、シリアルバッファに書き込むために使用できるバイト数(文字)を取得します。Serial.availableForWrite()
Serial.read()受信したシリアルデータを読み取ります。Serial.read()
Serial.readStringUntil()シリアルバッファから文字列に文字を読み込みます。Serial.readStringUntil(terminator)
terminator: 検索する文字。許可されたデータ型:char
Serial.write()バイナリデータをシリアルポートに書き込みます。このデータはバイトまたは一連のバイトで送信されます。数値の数字を表す文字を送信するには、代わりにprint()関数を使用します。Serial.write(val)
Serial.write(str)
Serial.write(buf, len)
ただし、
val: 1バイトで送信する値。
str: 一連のバイトとして送信する文字列。
buf: 一連のバイトとして送信する配列。
len: 配列から送信されるバイト数。
Serial.print()データを人間が読めるASCIIテキストとしてシリアルポートに出力します。このコマンドは多くの形式をとることができます。数字は数字ごとにASCII文字で印刷されます。浮動小数点数はASCII桁と同様に印刷され、デフォルトは小数点以下2桁です。バイトは1文字で送信されます。文字と文字列はそのまま送信されます。「」内は左で指定された場合の出力値
Serial.print(78)「78」
Serial.print(78, BIN)「1001110」
Serial.print(78, OCT)「116」
Serial.print(78, DEC)「78」
Serial.print(78, HEX)「4E」
Serial.print(1.23456, 0)「1」
Serial.print(1.23456, 2)「1.23」
Serial.print(1.23456, 4)「1.2346」
Serial.println()データを人間が読めるASCIIテキストとしてシリアルポートに印刷し、その後にキャリッジリターン文字(ASCII 13または’\r’)と改行文字(ASCII 10または’\n’)が続きます。このコマンドは、Serial.print()と同じ形式をとります。Serial.println(val)
Serial.println(val, format)
主にhttps://www.arduino.cc/reference/en/language/functions/communication/serial/などを参照し記載

シリアル通信の実装

それでは本題のUART0/UART1を使ったESP32とPCのシリアルモニタを用いたシリアル通信について、やり方を解説していきます。

URAT0を使った双方向通信

UART0を使ってパソコンのシリアルモニタから文字を送信し、同じ文字がESP32から返ってくることを確認します。

結線方法

今回はESP32-devkitを使ってテストしますので、追加の結線は不要です。
ESP32-devkit上にUART-USB接続用のブリッジモジュール(CP2102など)がついていますので、USBケーブルを接続するのみで評価できます。

もし、その他のESP32モジュールを使っている場合でも、通常は書き込み用の接続がそのままUART0に繋がっていますので、書き込み用の結線以外の接続は不要です。

UART0評価用結線
UART0評価用結線

ArduinoIDEでのコード記述と評価

ArduinoIDEでのコード記述は下記のようになります。

// ESP32のハードウェアシリアルの評価用コード1
// UART0で受け取った文字列をUART0に表示する

HardwareSerial mySerial(0);

void setup()
{
  mySerial.begin(115200);
}

void loop()
{
  while (mySerial.available())
  {
    String incomingStream = mySerial.readStringUntil('\n');
    mySerial.println(incomingStream);
  }
}

コードが書けたらビルドとESP32-devkitへの書き込みを行います。

続けて、ArduinoIDEのシリアルモニタを立ち上げます。

シリアルモニタの入力窓で入力した文字列を「送信」もしくはエンター押下した際に、下の出力窓に表示されれば成功です。

PlatformIOでのコード記述と評価

PlatformIOでの記述は下記になります。ヘッダが追加されている以外はArduinoIDEと同じです。

#include <Arduino.h>

// ESP32のハードウェアシリアルの評価用コード1
// UART0で受け取った文字列をUART0に表示する

HardwareSerial mySerial(0);

void setup()
{
  mySerial.begin(115200);
}

void loop()
{
  while (mySerial.available())
  {
    String incomingStream = mySerial.readStringUntil('\n');
    mySerial.println(incomingStream);
  }
}

そのほか、platformio.iniにmonitor_portとupload_portの設定を追加しておきます。platformio.iniの場所と追加する内容は下記の通りです。

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

monitor_speed = 115200
monitor_port = /dev/cu.usbserial-0001

upload_speed = 1500000
upload_port = /dev/cu.usbserial-0001

準備ができたら、ビルドとESP32-devkitへの書き込みを行いましょう。

書き込みができたらPlatformIOでシリアルモニタを立ち上げます。
PlatformIOのシリアルモニタの立ち上げ方法は下記です。

シリアルモニタ上で入力した文字が、エンター押下と同時に表示されれば成功です。

UART0/UART1を使った双方向・複数通信

今度はUART0/UART1を同時に使い、UART0のシリアルモニタに入力した文字をUART1のシリアルモニタに返し、逆にUART1のシリアルモニタに入力した文字をUART0に返す、という動作を行います。

結線方法

結線は先ほどのUSBケーブルを挿しただけのESP32-devkitに4本だけ追加します。追加するのは3.3v、GND線とUART1のRx、Tx線です。
RxとTxはコード上で32、33ピンに指定しますので、USB-UARTブリッジのRxがESP32のTxに、USB-UARTブリッジのTxがESP32のRxにつながるように結線します。

UART0/UART1評価用結線

ArduinoIDEでのコード記述と評価

ArduinoIDEでの評価用コードは下記となります。

//ESP32のハードウェアシリアルの評価用コード2
//UART0で受け取った文字列をUART1に表示し、
//UART1で受け取った文字列をUART0に表示する。

HardwareSerial mySerial(0);
HardwareSerial mySerial1(1);

void setup() {
  mySerial.begin(115200);
  mySerial1.begin(115200,SERIAL_8N1,32,33);//RX=32, TX=33
}

void loop() {
  while (mySerial.available())
  {
    String incomingStream = mySerial.readStringUntil('\n');
    mySerial1.println(incomingStream);
  }
  while (mySerial1.available())
  {
    String incomingStream = mySerial1.readStringUntil('\n');
    mySerial.println(incomingStream);
  }
}

5~6行目でHardwareSerialを定義し、9~10行目で設定しています。設定方法はarduino-esp32coreのヘッダの中で下記のように記されているので、これに従います。

 void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL, uint8_t rxfifo_full_thrhd = 112);

その他の記述はほとんどUART0だけの時と同じです。できたらビルドと書き込みをしておきます。

シリアルモニタはArduinoIDEのものだけだと足りないので、WindowsならTeraTerm、MacならMinicomなどを追加します。

MacでMinicomのインストール〜使い方についてはこちらをご参照ください。

上記準備ができたら、ArduinoIDE、 Minicomなど双方のシリアルモニタを起動しましょう。

Minicomの起動はターミナルからminicomを入力です。
ArduinoIDEのシリアルモニタは下記メニューを押下です。

双方のシリアルモニタに文字を入力して、互いのシリアルモニタから文字が出力されれば成功です!

PlatformIOでのコード記述と評価

PlatformIOでのコードはほとんどArduinoIDEと同じです。ヘッダがあるか、ないかだけですね。

#include <Arduino.h>

// ESP32のハードウェアシリアルの評価用コード2
// UART0で受け取った文字列をUART1に表示し、
// UART1で受け取った文字列をUART0に表示する。

HardwareSerial mySerial(0);
HardwareSerial mySerial1(1);

void setup()
{
  mySerial.begin(115200);
  mySerial1.begin(115200, SERIAL_8N1, 32, 33); // RX=32, TX=33
}

void loop()
{
  while (mySerial.available())
  {
    String incomingStream = mySerial.readStringUntil('\n');
    mySerial1.println(incomingStream);
  }
  while (mySerial1.available())
  {
    String incomingStream = mySerial1.readStringUntil('\n');
    mySerial.println(incomingStream);
  }
}

そのほか、platformio.iniにmonitor_portとupload_portの設定を追加しておきます。
platformio.iniファイルの場所と記載内容は下記の通りです。

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

monitor_speed = 115200
monitor_port = /dev/cu.usbserial-0001

upload_speed = 1500000
upload_port = /dev/cu.usbserial-0001

準備ができたら、ビルドとESP32-devkitへの書き込みを行いましょう。

書き込みができたらPlatformioでシリアルモニタを立ち上げます。

シリアルモニタはPlatformIOのものだけだと足りないので、WindowsならTeraTerm、MacならMinicomなどを追加します。

MacでMinicomのインストール〜使い方についてはこちらをご参照ください。

上記準備ができたら、PlatformIOのシリアルモニタ Minicom双方のシリアルモニタを起動しましょう。
PlatformIOのシリアルモニタの立ち上げ方法は下記です。

双方のシリアルモニタに文字を入力して、互いのシリアルモニタから文字が出力されれば成功です!

hardwareSerialで転送可能な最大サイズは?

ArduinoのリファレンスによるとArduinoのソフトウェアシリアルのバッファは64バイトしかありません。では今回使ったESP32のHardwareSerialのバッファはどこまであるのでしょうか。

実は、いろいろ調べた(ココとか、ココとか)のですが、明確にDefineされている場所はなく、HWからの返事によるようです。

で、わかりませんでした。では意味が無いので、実験しました。
PlatformIO上で大量の文字列をUART0に送信し、受信できたところがどこまでかを調べました。

結果、65519バイトまでは応答できたことを共有しておきます。

この値が、ESP32の限界値なのか、PlatformIO側の限界値なのか、はたまたそれ以外の理由なのかは判別つきませんが、少なくとも日常使用するのに十分なバッファがあることは確認できました。

まとめ

以上、ESP32のUART0とUART1をHardwareSerialで利用する方法を順を追って解説しました。

同じような解説記事を見ると、ESP32を使っているのにSoftwareSerialを使っている例も多々あったりして、少々もったいないなと思っていました。

なるべく、正規のリファレンスをもとに記載しておりますが、お気づきの点やコメントあれば、お気軽にコメント欄にてお知らせください。

それでは〜。

ぼのかば

最初にプログラムしたのは相撲ロボット用のZ80アセンブリ(Aki-80)。
以後,PICアセンブリ>PIC C と渡り歩く。

好きなICのタイプは8pinDIP。

一応、電気科卒
*ブログ内の情報はMacOSX上での操作に基づきます。

ESP32
PICを少々。