M5Stack Tab5でPCとUSB高速通信

M5 Japan Tour 2025 Autumnで置いていたやつのUSB通信部分の解説です。
M5Stack Tab5に搭載されているESP32-P4は USB 2.0 High Speed (480Mbps) 対応のPHYを持っていて、Tab5のUSB Type-Aのポートで使うことができます。
Type-Aポートなので物理的にはUSBデバイスしか接続できないですが、SoC的にはHost/Device両対応しているので、TypeAオス-TypeAオスみたいな規格違反ケーブルを使うことで、Tab5側をUSBデバイスとして認識させてPCと通信させることができます。
USBケーブルの用意
ESP-IDFのサンプルコードのREADMEに必要な配線が書いてあります。
ESP BOARD USB CONNECTOR (type A)
--
| || VBUS (5V)
[USB_DM] ------> | || D-
[USB_DP] ------> | || D+
| || GND
--Tab5の場合は ESP BOARD 側がTab5のUSB TypeAポートなので、両方の D+同士・D-同士が繋がったTypeAオス-TypeAオスのケーブルが必要ということになります。そして、VBUSとGNDは接続してしまうとPCとTab5が両者とも相手側に電源供給しようとしてしまって回路が壊れる可能性があるので、ここは繋がないようにする必要があります。
とはいえ、ケーブルを自作するのは面倒なので、普通に販売されているTypeAオス-TypeAオスの規格違反ケーブルを購入して、ケーブルのUSB端子の両側のピンにテープを貼って電流が流れないようにして使うのがいいと思います。
ちなみに、僕は手元にTypeAオス-TypeAオスのケーブルの在庫がなかったため、通常のTypeA-Cケーブル(TypeAオス-TypeCオス)に規格違反TypeCアダプタ(TypeCメス-TypeAオス)を組み合わせて使っています。

(細かいことを言うと、Tab5側のTypeA端子には逆流保護があるので、後述の手順でTab5のTypeAから5V出力をOffにすれば接続してしまっても問題はありませんが、繋がないようにしておくに越したことはないでしょう。)
Tab5側のセットアップ
IDFでTab5を扱える環境を用意してください。自分はv5.5.1で確認しています。
TinyUSB導入
ESP Registry から `espressif/esp_tinyusb` を追加すると、TinyUSB本体とTinyUSBをESP-IDF上で動かすための追加のコードが取得できます。

Detail of component espressif/esp_tinyusb - 2.0.1~1
そして、menuconfigからVendor Specific Interface (TINYUSB_VENDOR_COUNT) の数を1にします。

VBUS Off
念の為にTab5のUSB TypeAポートからの5V出力はOffにしておくのがいいでしょう。アドレスが0x44になっている方のPI4IOのPIN3でOn/Offが切り替えできます。
M5GFX(M5Unified)やM5Tab5-UserDemoの初期化コードだとここの初期値Highになっており、後からLowに切り替えても起動時に一瞬5V出力が出ることになるため、直接ドライバの初期値を書き換えるのがいいでしょう。
ここの 0b00001000 のビットを0にしておきましょう。(上記リンクのcommitだと 0b10001001 になっているため、0b10000001 に変更する感じ)
自分の場合はこの辺りのドライバを自前で用意しているので、こんな感じになってます。
USB通信
#include "tinyusb.h"
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN)
#define EPNUM_VENDOR_OUT (0x01)
#define EPNUM_VENDOR_IN (0x81)
uint8_t const tusb_configuration_descriptor[] = {
// Config Header
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, 0x00, 100),
// Vendor Interface
TUD_VENDOR_DESCRIPTOR(0, 4, EPNUM_VENDOR_OUT, EPNUM_VENDOR_IN, 512)
};
tinyusb_config_t tusb_install_cfg = {
.fs_configuration_descriptor = tusb_configuration_descriptor,
.hs_configuration_descriptor = tusb_configuration_descriptor,
};USBディスクリプタを定義します。EPNUM_VENDOR_OUT EPNUM_VENDOR_IN は通信する時にPCから指定するエンドポイント番号です。PC -> Tab5の通信に0x01、Tab5 -> PCの通信に0x81を使うようになってます。
あとは tinyusb_driver_install(&tusb_install_cfg) を実行するとTinyUSBの初期化とタスク実行をやってくれるので、 tud_mounted() でPCと接続されているかどうかを確認して、接続されていれば通信処理を行います。
esp_err_t err = tinyusb_driver_install(&tusb_install_cfg);
if (err) {
M5.Display.print("TinyUSB Driver Install Failed: ");
M5.Display.println(esp_err_to_name(err));
}
M5.Display.println("TinyUSB Driver Installed.");
bool mounted = false;
while (true) {
bool device_mounted = tud_mounted();
if (device_mounted && mounted != device_mounted) {
M5.Display.println("Device connected.");
}
mounted = device_mounted;
if (!device_mounted) {
vTaskDelay(100 / portTICK_PERIOD_MS);
continue;
}
uint32_t data_available = tud_vendor_available();
if (!data_available) {
vTaskDelay(100 / portTICK_PERIOD_MS);
continue;
}
static uint8_t data_buffer[256];
while (data_available > 0) {
uint32_t read_size = data_available;
if (read_size > sizeof(data_buffer) - 1) read_size = sizeof(data_buffer) - 1;
read_size = tud_vendor_read(data_buffer, read_size);
data_available -= read_size;
M5.Display.print((char*)data_buffer);
}
M5.Display.println("");
}上記のコードでは tud_vendor_available で受信可能なデータサイズをとって tud_vendor_read で受信という形になってます。
確認はしてないけど tud_vendor_write_available で送信可能なデータサイズをとって tud_vendor_write で書き込んで tud_vendor_write_flush すればTab5 -> PCのデータ通信もできると思います。
PC側アプリ
libusbを使ってバルク伝送するのが簡単です。
pyusb
サンプルとしてpyusbを使ったデータ送信のコードを置いておきます。
import usb.core, usb.util, time, struct
VID, PID = 0x303a, 0x4020
dev = usb.core.find(idVendor=VID, idProduct=PID)
assert dev is not None
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
dev.set_configuration()
cfg = dev.get_active_configuration()
intf = cfg[(0,0)]
ep_out = usb.util.find_descriptor(intf, bEndpointAddress=0x01)
data = bytearray("Hello from PC", "utf-8")
ep_out.write(data, timeout=1000)ドライバ
OSによってはUSBデバイスを使えるようにするためのセットアップが必要です
macOS
そのまま使えます。
Windows
Zadigを使ってドライバをセットアップする必要があります。
Linux
そのままだとデバイスへのアクセス権限が制限されていて、sudoじゃないと通信できない状態になります。それだと面倒なので、以下のようにudevルールを追加しておくのがいいと思います。
echo 'SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="4020", MODE="0666"' | sudo tee /etc/udev/rules.d/99-m5tab5-udev.rules
