ESP32-S3のSerialの謎を解き明かそう!

2024/05/09に公開

文:BLACK NANANA

みんな、はろー!ブラックなななだよ。ボクは主にブルスカで活動しているからぜひフォローしてね!ボクはなななくん(@nananauno)よりもより専門的でマニアックな内容を扱っているからよろしくね!今日はESP32-S3を使っているみんながいつも疑問に思っているSerialの謎を解明していくよ!

もし、この記事がみんなのお役に立てた場合は、「バッジを送る」からバッジを送ってもらえるとボクのモチベがどんどん上がっていくからよろしくね!

M5Stack coreシリーズ (ESP32) だとPCのシリアルモニターからログが見れるのに、同じコードをESP32-S3を使用したStampS3とかATOM S3に書き込むとシリアルモニターからログが見れないってことに遭遇した経験はあるかな?ボクは毎回遭遇しているよ!これはESP32とESP32-S3でUSB周りの機能に差があるからなんだけど、みんなよく理解せずにS3だからUSB CDCをとりあえず有効にしておこうとかやってない?どうしてそうなるかを正しく理解することで、ESP32をもっと知ることができるし、今後も同じ状況に遭遇した時に適切に処理できるようになるよ。

ここでは、ESP32-S3のお話を取り上げているけど、ESP32-C3でも同じだよ。

やりたいこと

  • ESP32-S3のSerialでログが出力されない原因を正しく理解する。

対象読者

  • ESP32-S3でログが出なくて困っている、困ったことがある
  • M5StackやESP32系のマイコンを使っている
  • ESP32についてより深く知りたい

環境

実際の動作を見るためにボクは以下の環境を使ったよ。実際の動作を見なくても良いよって場合は何も無くても大丈夫だよ。

  • Arduino IDE (arduino-esp32 2.0.11)
  • M5Stack BASIC/core2
  • M5Stack StampS3
  • USB-Serial変換ボード (例:WCH-LinkE)

M5Stackの代わりにESP32とESP32-S3を搭載した別のマイコンボードでもOKだよ。ただ、ESP32-S3の場合は43番、44番ピンにアクセスできる方が良いよ!

ESP32-S3のSerialの謎

ESP32-S3を使ったマイコンボードでSerial.printlnを呼び出してもログに何も表示されない!これは、みんなが一度は経験したことある現象じゃないかな?この記事ではどうしてそのようなことが起こっているのかを解き明かしていくよ。

まずは、実際の動作を見てみるよ。

もし手元にM5Stack BASIC/core2と、StampS3があったら以下のコードをそれぞれに書き込んで動作を比べてみてね。StampS3にファームを書き込むとき、Arduino IDEのTools内の設定は以下のようにしていてね。

  • USB CDC On Boot: Disabled
  • USB Mode: Hardware CDC and JTAG
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("Hello!");
  delay(1000);
}

BASIC/core2ではArduino IDEのシリアルモニターからログが出力されているけど、StampS3ではArduino IDEのシリアルモニターからログが出力されていないという結果になるはずだよ。

次に、ESP32-S3用のコードを以下のように変更してStampS3にファームを書き込んでみてね。今度はArduino IDEのシリアルモニターからログが出力されていると思うよ。

void setup() {
  // put your setup code here, to run once:
  USBSerial.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  USBSerial.println("Hello!");
  delay(1000);
}

最後にもう1回、最初のサンプルコードをStampS3に書き込んで欲しいんだけど、書き込む前にArduino IDEのTools内の設定を以下のように変更してから書き込んでね。

  • USB CDC On Boot: Enabled
  • USB Mode: Hardware CDC and JTAG

一番最初のサンプルコードはStampS3でログが出力されなかったけど、今回はシリアルモニターからログが表示されたと思うよ。

じゃあ、一番最初にStampS3で試した時、ログはいったいどこに出力されていたのかな?

疑問を解決するためには、もし手元にUSB-Serial変換ボードがあったら、それをStampS3の44番ピン(RXD)、43番ピン(TXD)に繋いでPCからログを見てみてね。Serialで出力したログがUSB-Serial変換ボード側で見ることができたかな?ボクはWCH-LinkEっていうCH32V用のデバッガのTXとRXをそれぞれStampS3の44番ピン、43番ピンに接続して確認してみたけど、確かにログが表示されていたよ。

この実験で分かったことは、ESP32-S3を使ったマイコンボード(ここではStampS3)でSerialを使用すると、Serialの入出力先はGPIOの44番ピン、43番ピンになっていて、PC側でログを見るためには、44/43番ピンにUSB-Serial変換ボードを接続するか、USBSerialインスタンスを使用するか、Arduino IDEでUSB CDC On Bootという設定を有効にする必要があるってことが分かったよね。

StampS3でシリアル出力するために使用したインスタンスと設定、実際の出力先を以下にまとめたよ:

シリアルのインスタンス USB CDC On Boot USB Mode 実際の出力先
Serial Disabled Hardware CDC and JTAG 44/43
Serial Enabled Hardware CDC and JTAG USBシリアル
USBSerial Disabled Hardware CDC and JTAG USBシリアル

ESP32とESP32-S3は何が違うの?

どうしてESP32とESP32-S3で動作が違うの!!って思うよね。その謎を解くためには、ESP32とESP32-S3のUSB周りの違いを知っておく必要があるよ。ESP32とESP32-S3のUSB周りでは以下のような違いがあるよ。

ESP32

  • マイコン内蔵のUSB-Serial変換機能無し
    → USB-Serial変換を外付けする必要がある。

ESP32-S3

  • マイコン内蔵のUSB-Serial変換機能有り
    → USB-Serial変換を外付けする必要がない。

ArduinoやM5Stackのようなマイコンボードって誰でも簡単に使えるようにしてくれているから、当たり前のようにArduino IDEとかに接続して簡単にファームウェアが書き込めるようになっているけど、Arduino UNO R3までで使われていたATMega328PとかESP32って実はUSB-Serialの変換機能がマイコンに内蔵されていなくて、外付けのUSB-Serial変換ICを通じてPCに接続されているよ。

下の回路図はM5Stack core2 v1.1のUSB周りの回路図だよ。USB端子のデータラインはマイコンに直接接続されていなくて、CH9102というICが間に挟まっているよね。このICがUSB-Serial変換ICで、CH9102はESP32のUART TX/RXに接続されているよ。


Complete schematic of M5Stack core2 v1.1

https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/core/Core2 v1.1/Sch_Core2_v1.1_2024-02-22.pdf

下の回路図はESP32-S3を使ったSeeed XIAO ESP32S3という開発ボードのUSB周りの回路図だよ。USB端子のデータラインが直接ESP32-S3のUSB D+/D-に接続されていてUSB-Serial変換ICが無いことが分かるよね!


Seeed Studio XIAO ESP32S3 Schematic

https://files.seeedstudio.com/wiki/SeeedStudio-XIAO-ESP32S3/res/XIAO_ESP32S3_SCH_v1.1.pdf

ESP32-S3では44/43番ピンがUART0のデフォルトの入出力先になっていて、ESP32の場合は3/1番ピンがUART0のデフォルトの入出力先になっているよ。そして、ピンを指定せずにSerial.begin()を呼び出すと、どちらもデフォルトのピンを使用してUART0を初期化しているよ。ということはESP32もESP32-S3もSerialの出力先はUART0のGPIOになっていて動作としては同じになっているよ。EspressifさんがESP32とESP32-S3でSerialの動作を意図的に変えている訳ではなく、動作としてはESP32もESP32-S3も同じで、違うのはUSB周りの回路ということだね。


ESP32もESP32-S3もデフォルトではUART0で通信しているよ

EspressifさんのReference Manualによると、ESP32-S3/C3はよりピン数の少ない省スペースなハードを想定しているから、USBに外付けでUSB-Serial変換は付けたくないよね?だからUSB-Serial機能を内蔵しているよって書いてあるよ。

USBSerialって何? USB CDCを有効にすると何が起こっているの?

StampS3でUSBSerialインスタンスを使用してログを出力した時、期待通りにArduino IDEのシリアルモニターからログが出力されていたけど、USBSerialというのは何者なのかな?arduino-esp32のソースコードを見てみると、HWCDC.hの一番下でインスタンスが定義されていて、HWCDCというクラスのインスタンスであることが分かるよ。

https://github.com/espressif/arduino-esp32/blob/2.0.11/cores/esp32/HWCDC.h

HWCDC.h
#if ARDUINO_USB_MODE
#if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC
extern HWCDC Serial;
#else
extern HWCDC USBSerial;
#endif
#endif

そして、Arduino IDEのTools内のUSB CDC On BootとUSB Modeはそれぞれ以下のような対応になっているよ。

USB CDC On Boot ARDUINO_USB_CDC_ON_BOOT
Enabled 1
Disabled 0
USB Mode ARDUINO_USB_MODE
Hardware CDC and JTAG 1
USB-OTG 0

これを元にもう一度上記のHWCDC.hを見てみると、USB CDC On Bootが有効でUSB ModeがHardware CDC and JTAGになっていると、HWCDCクラスを使用してSerialというインスタンスが、USB CDC On Bootが無効の状態だとHWCDCを使用してUSBSerialというインスタンスが生成されているよ。最初の実験でどうしてそういう結果になったのかが分かってきたかな?

HWCDCというのはESP32-S3でUSB機能を使ってシリアルコンソールを扱うためのクラスなんだけど、実はESP32にはHardwareSerial, HWCDC, USBCDCという3種類のシリアルコンソールが準備されているよ。それぞれ以下のような役割だよ。

HardwareSerial
GPIOに接続されたUARTでコンソールを扱うためのクラス
これは一番分かりやすくて、ESP32-S3だと44/43番ピンといったGPIOを使ってUARTの通信を行うために使われるクラスだよ。ESP32でもESP32-S3でもGPIOでUARTの入出力を行う時にこのクラスのインスタンスが使用されているよ。

HWCDC
ESP32-S3に内蔵されたHardware Serial/JTAGでコンソールを扱うためのクラス

USBCDC
USB-OTGでコンソールを扱うためのクラス

Hardware Serial/JTAG?USB-OTG何それ?って感じだよね。HWCDCとUSBCDCはちょっとややこしいから、👇にESP32-S3に搭載された2つのUSB機能をまとめてみたから読んでみてね。

USB_SERIAL_JTAGとUSB_OTG

ESP32-S3のUSBには2つの機能があって、1つはUSB_SERIAL_JTAG、もう1つがUSB_OTGだよ。ESP32-S3にUSBのデータラインD+/D-のピンはそれぞれ1つずつしか無いから、どちらかの機能が内蔵のPHY(PHYはアナログ信号をデジタル信号にまたその逆の変換をする物理層のことだよ)に接続された状態になっているよ。デフォルトはUSB_SERIAL_JTAGのモジュールが接続されていて、内蔵のPHYに接続されていない機能は外付けのUSB PHYを使用することで使用できるようになるよ。ESP32-S3ではソフト的にどちらの機能を内蔵PHYに接続するかを切り替えることもできるし、eFuseを書き換えることでHW的に機能を切り替えることもできるよ。

ESP32-S3のUSB周りの機能はこんな感じになっているよ。

ESP32-S3 USBの周辺機能イメージ

USB_SERIAL_JTAGはPCからUSB経由でマイコンのログを取得したりJTAGでデバッグしたり、ファームを書き換えたりするための機能で、USB_SERIAL_JTAGにはこの機能しか無いよ。上記のHWCDCクラスは、このUSB_SERIAL_JTAGの機能でコンソールを扱うためのクラスになっているよ。

一方でUSB_OTGはESP32-S3がUSBのホストになったりデバイスになったりと色々なデバイスになることができる機能があるよ。例えばESP32-S3をUSBキーボードにしたい時はUSB_OTGを使用する必要があるよ。上記のUSBCDCはこのUSB_OTGでコンソールを扱うためのクラスになっているよ。

ESP32-S3のUSB機能は2種類あって、それぞれにシリアル通信用のコンソール機能があるということだよ。そして、それのどちらを使うかがHWCDCとUSBCDCで区別されているよ。HWCDCとUSBCDCはちょっとややこしいけど、それぞれの役割を知っていたら、どういうケースで使用するかというのは分かりやすいよね。

arduino-esp32 3.0.0変更点

ここまではarduino-esp32 2.0.11でのお話だったんだけど、ESP32-C6に対応した最新のarduino-esp32 3.0.0ではシリアル周りの定義が変更されていて、実験で使ったサンプルコードはそのままだと正しくビルドできなくなっているよ。

具体的な変更点をざっくりと下にまとめてみたよ。

  • HWCDCのインスタンスがHWCDCSerialになっている。
  • USBCDCのインスタンスがUSBSerialになっている。
  • 設定を変えないとインスタンス化されない

USB CDC On BootとUSB Modeの設定に応じてSerialが指し示すものが変化するようになっているよ。

ARDUINO_USB_MODE ARDUINO_USB_CDC_ON_BOOT Serial
0 0 Serial0
1 1 HWCDCSerial
0 1 USBSerial

2.0.11ではUSB CDC On BootがDisabledの状態でもUSBSerial(HWCDCのインスタンス)が使えたけど、3.0.0ではUSB CDC On BootをEnabledにしておかないとHWCDCのインスタンスが使えなくなっているし、インスタンスの名前もUSBSerialじゃなくてHWCDCSerialに変わっているよ。

M5.Logを使おう

M5StackでM5Unifiedを使っている場合限定のお話になっちゃうけど、M5UnifiedにM5.Logという機能があって、このメソッドはSerialがどのインスタンスなのかを気にせずに使用することができるようになっているよ。つまり、M5Stack core2でもStampS3でも同じようにM5.Log()でログ出力するとシリアルモニターからログを見ることができるよ。M5Stackを使用していて、Serialの使い分けが面倒だっていう場合は、M5UnifiedのLog機能の使用がオススメだよ!

らびやん(@lovyan03)さんのM5Unifiedハンズオンのサンプルコードを参考にしてみてね。
https://github.com/lovyan03/M5Unified_HandsOn/blob/main/sample_code/Log1.cpp

まとめ

今回はESP32-S3でみんなが経験したことのありそうなSerialの謎に迫ってみたよ。ESP32とESP32-S3で動作が違うように見えるけど、実際の動作はどちらも同じで、変わっていたのはUSB周りの機能だということが分かってもらえたかな。また、ESP32-S3では2つのUSB機能があって、両方ともシリアルコンソールの機能があって、それぞれに専用のクラス(インスタンス)があることも説明したよ。

ちょっと踏み込んだ内容で分かりづらいことがあったかもしれないけど、疑問に思っていることは放置せずにより深く知ることでESP32をもっと使いこなせるようになると思うよ!分からないことは、自分で調べるかなななくん(@nananauno)に質問してね。ボクに聞かれても答えないよ。

じゃあ、またね!

参考文献

https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-guides/usb-otg-console.html

https://docs.espressif.com/projects/esp-idf/en/stable/esp32s3/api-guides/usb-serial-jtag-console.html

https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf

https://lang-ship.com/blog/work/m5stack-atoms3-2/

https://hollyhockberry.hatenablog.com/entry/2022/01/20/185004

Discussion