◆unit MIDIを入手したのでM5StampS3を使ってUSB MIDI I/Fのキーボードデータを
DIN-MIDI OUT/INへの変換アダプタを作ってみました。
USBのキーボードから変換器でDINにしてunit MIDIのDIN入力に繋いでいます。
1.開発環境 2024/11/28時点
2.変換アダプタ回路図

3.USB-MIDIをESP32S3で読み込むための準備
①スケッチ例を下記よりダウンロードします。
esp32-usb-host-demos
解凍後、「esp32-usb-host-demos-main\examples\usbhmidi\usbhmidi.ino」
を別フォルダにコピペして使っています。
そこに、「esp32-usb-host-demos-main」のフォルダにある二つのファイル
「show_desc.hpp」usbhhelp.hpp」をコピペします。
*ESP32S3Debkit用

*M5Stamps3用

①Windows11 X64 Pro 24H2
②Arduino IDE 2.3.2(2.3.3はフリーズするのでバージョンを落として使っています)
➂ESP32ボードバージョンV3.0.7(esp32s3dev)
④M5Stack Arduino ボードバージョンV2.08(M5Stamp-S3)
④M5Stack Arduino ボードバージョンV2.08(M5Stamp-S3)
2.変換アダプタ回路図

3.USB-MIDIをESP32S3で読み込むための準備
①スケッチ例を下記よりダウンロードします。
esp32-usb-host-demos
解凍後、「esp32-usb-host-demos-main\examples\usbhmidi\usbhmidi.ino」
を別フォルダにコピペして使っています。
そこに、「esp32-usb-host-demos-main」のフォルダにある二つのファイル
「show_desc.hpp」usbhhelp.hpp」をコピペします。
*ESP32S3Debkit用

*M5Stamps3用

②USB-DIN変換用のスケッチです。先人のスケッチを修正しています。
*ESP32S3用とM5STAMPS3用とは//コメントで変更しています。
*他のESP32S3ボードを使用する場合、ここのDIN用シリアルポート番号を
変更すれば、いけると思います。
◆当然ながらスケッチは、保障無しの自己責任で参考程度にお願いします。
4.実行例
最初は、二つ変換アダプタを作り、相互接続して実験しました。
下記ブログ内記事を参照してみて下さい。
◆トラブル
①DINコネクタの4,5番ピンの単純な裏表の勘違いで動作せず分かるまで
大変でした。
②その後、データのサイズが、USBから入出力データが、4バイトで
DINへのデータが3バイトと違っていたので同じく分かるまで大変でした。
◆最後、両方向の変換がうまくいき、音が出た時は感激しました。
カシオのLK-223を繋ぐと鍵盤からの入力と音源への出力と分離して
入出力出来ました。
下図は、デバッグ用に付けたシリアル出力です。

4.DIN MIDIで流れるデータの方式について
①詳しくは、下記を参照して下さい。
M I D I 1.0 規格書
②概略を下記に示します。(ChatGPTより)
### **2.3 ピッチベンド**
### **2.4 プログラムチェンジ**
*ESP32S3用とM5STAMPS3用とは//コメントで変更しています。
*他のESP32S3ボードを使用する場合、ここのDIN用シリアルポート番号を
変更すれば、いけると思います。
◆当然ながらスケッチは、保障無しの自己責任で参考程度にお願いします。
************************************************************************************************
161: //Serial2.begin(31250,SERIAL_8N1,18,17); //RXD=18 TXD=17 ESP32S3 Devkit
161: //Serial2.begin(31250,SERIAL_8N1,18,17); //RXD=18 TXD=17 ESP32S3 Devkit
162: Serial2.begin(31250,SERIAL_8N1,15,13);//RXD:15 TXD:13 M5STAMPS3
163: delay(500);
164; usbh_setup(show_config_desc_full);
165: //Serial.println("ESP32S3 MIDI USB-IN -> DIN-OUT & DIN-IN -> USB-OUT");
166: Serial.println("M5STAMP MIDI USB-IN -> DIN-OUT & DIN-IN -> USB-OUT");
*************************************************************************************************/*m5stamp_midi.ino
* MIT License
*Copyright (c) 2021 touchgadgetdev@gmail.com
*2024/11/24 modify By JK1VCK
*/
#include <usb/usb_host.h>
#include "show_desc.hpp"
#include "usbhhelp.hpp"
bool isMIDI = false;
bool isMIDIReady = false;
const size_t MIDI_IN_BUFFERS = 8;
const size_t MIDI_OUT_BUFFERS = 8;
usb_transfer_t *MIDIOut = NULL;
usb_transfer_t *MIDIIn[MIDI_IN_BUFFERS] = {NULL};
// USB MIDI Event Packet Format (always 4 bytes)
//
// Byte 0 |Byte 1 |Byte 2 |Byte 3
// -------|-------|-------|------
// CN+CIN |MIDI_0 |MIDI_1 |MIDI_2
//
// CN = Cable Number (0x0..0xf) specifies virtual MIDI jack/cable
// CIN = Code Index Number (0x0..0xf) classifies the 3 MIDI bytes.
// See Table 4-1 in the MIDI 1.0 spec at usb.org.
//
static void midi_transfer_cb(usb_transfer_t *transfer) {
ESP_LOGI("", "midi_transfer_cb context: %d", transfer->context);
if (Device_Handle == transfer->device_handle) {
int in_xfer = transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK;
if ((transfer->status == 0) && in_xfer) {
uint8_t *const p = transfer->data_buffer;
for (int i = 0; i < transfer->actual_num_bytes; i += 4) {
if ((p[i] + p[i+1] + p[i+2] + p[i+3]) == 0) break;
ESP_LOGI("", "midi: %02x %02x %02x %02x",p[i], p[i+1], p[i+2], p[i+3]);
Serial.printf("USB MIDI-IN: %02X %02X %02X %02X ",p[i], p[i+1], p[i+2], p[i+3]);
Serial.printf("DIN MIDI-OUT: %02X %02X %02X\n", p[i+1], p[i+2], p[i+3]);
Serial2.write(p[i+1]);
Serial2.write(p[i+2]);
Serial2.write(p[i+3]);
}
esp_err_t err = usb_host_transfer_submit(transfer);
if (err != ESP_OK) {
ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
}
} else {
ESP_LOGI("", "transfer->status %d", transfer->status);
}
}
}
void check_interface_desc_MIDI(const void *p) {
const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p;
// USB MIDI
if ((intf->bInterfaceClass == USB_CLASS_AUDIO) &&
(intf->bInterfaceSubClass == 3) &&
(intf->bInterfaceProtocol == 0))
{
isMIDI = true;
ESP_LOGI("", "Claiming a MIDI device!");
esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle,
intf->bInterfaceNumber, intf->bAlternateSetting);
if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err);
}
}
void prepare_endpoints(const void *p) {
const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p;
esp_err_t err;
// must be bulk for MIDI
if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_BULK) {
ESP_LOGI("", "Not bulk endpoint: 0x%02x", endpoint->bmAttributes);
return;
}
if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
for (int i = 0; i < MIDI_IN_BUFFERS; i++) {
err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIIn[i]);
if (err != ESP_OK) {
MIDIIn[i] = NULL;
ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err);
} else {
MIDIIn[i]->device_handle = Device_Handle;
MIDIIn[i]->bEndpointAddress = endpoint->bEndpointAddress;
MIDIIn[i]->callback = midi_transfer_cb;
MIDIIn[i]->context = (void *)i;
MIDIIn[i]->num_bytes = endpoint->wMaxPacketSize;
esp_err_t err = usb_host_transfer_submit(MIDIIn[i]);
if (err != ESP_OK) {
ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
}
}
}
} else {
err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIOut);
if (err != ESP_OK) {
MIDIOut = NULL;
ESP_LOGI("", "usb_host_transfer_alloc Out fail: %x", err);
return;
}
ESP_LOGI("", "Out data_buffer_size: %d", MIDIOut->data_buffer_size);
MIDIOut->device_handle = Device_Handle;
MIDIOut->bEndpointAddress = endpoint->bEndpointAddress;
MIDIOut->callback = midi_transfer_cb;
MIDIOut->context = NULL;
// MIDIOut->flags |= USB_TRANSFER_FLAG_ZERO_PACK;
}
isMIDIReady = ((MIDIOut != NULL) && (MIDIIn[0] != NULL));
}
void show_config_desc_full(const usb_config_desc_t *config_desc) {
// Full decode of config desc.
const uint8_t *p = &config_desc->val[0];
uint8_t bLength;
for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) {
bLength = *p;
if ((i + bLength) <= config_desc->wTotalLength) {
const uint8_t bDescriptorType = *(p + 1);
switch (bDescriptorType) {
case USB_B_DESCRIPTOR_TYPE_DEVICE:
ESP_LOGI("", "USB Device Descriptor should not appear in config");
break;
case USB_B_DESCRIPTOR_TYPE_CONFIGURATION:
show_config_desc(p);
break;
case USB_B_DESCRIPTOR_TYPE_STRING:
ESP_LOGI("", "USB string desc TBD");
break;
case USB_B_DESCRIPTOR_TYPE_INTERFACE:
show_interface_desc(p);
if (!isMIDI) check_interface_desc_MIDI(p);
break;
case USB_B_DESCRIPTOR_TYPE_ENDPOINT:
show_endpoint_desc(p);
if (isMIDI && !isMIDIReady) {
prepare_endpoints(p);
}
break;
case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER:
ESP_LOGI("", "USB device qual desc TBD");// Should not be in config?
break;
case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION:
ESP_LOGI("", "USB Other Speed TBD");// Should not be in config?
break;
case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER:
ESP_LOGI("", "USB Interface Power TBD");// Should not be in config?
break;
default:
ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", *p);
break;
}
}
else {
ESP_LOGI("", "USB Descriptor invalid");
return;
}
}
}
void setup() {
Serial.begin(115200);
//Serial2.begin(31250,SERIAL_8N1,18,17); //RXD=18 TXD=17 ESP32S3 Devkit
Serial2.begin(31250,SERIAL_8N1,15,13);//RXD:15 TXD:13 M5STAMPS3
delay(500);
usbh_setup(show_config_desc_full);
//Serial.println("ESP32S3 MIDI USB-IN -> DIN-OUT & DIN-IN -> USB-OUT");
Serial.println("M5STAMP MIDI USB-IN -> DIN-OUT & DIN-IN -> USB-OUT");
}
int ct=0;
uint8_t data[5]={1,2,3,4,5};
void loop() {
uint8_t wk;
usbh_task();
if (Serial2.available()) {// DIN MIDIからメッセージを取得
wk=Serial2.read(); //Serial.printf("ct:%d %02x ",ct,wk);
if ((wk & 0x80)) ct=0;
data[ct]=wk;
ct++;
if (ct>2) {
ct=0;
Serial.print("DIN MIDI-IN: ");
for(int i=0;i<3;i++) {
Serial.printf("%02X ",data[i]);
}
if (isMIDIReady) {
ESP_LOGI("", "MIDI send 4 bytes");
MIDIOut->num_bytes = 4;
uint8_t *const p = MIDIOut->data_buffer;
p[0]=(data[0]&0xF0)>>4;
for(int i=0;i<3;i++) {
p[i+1]=data[i];
}
Serial.print(" USB MIDI-OUT: ");
for(int i=0;i<4;i++) {
Serial.printf("%02X ",p[i]);
}
esp_err_t err = usb_host_transfer_submit(MIDIOut);
if (err != ESP_OK) {
ESP_LOGI("", "usb_host_transfer_submit Out fail: %x", err);
} else {
Serial.printf(" USB OUT OK\n");
}
} else {
Serial.println();
}
}
}
}
4.実行例
最初は、二つ変換アダプタを作り、相互接続して実験しました。
下記ブログ内記事を参照してみて下さい。
◆トラブル
①DINコネクタの4,5番ピンの単純な裏表の勘違いで動作せず分かるまで
大変でした。
②その後、データのサイズが、USBから入出力データが、4バイトで
DINへのデータが3バイトと違っていたので同じく分かるまで大変でした。
◆最後、両方向の変換がうまくいき、音が出た時は感激しました。
カシオのLK-223を繋ぐと鍵盤からの入力と音源への出力と分離して
入出力出来ました。
下図は、デバッグ用に付けたシリアル出力です。

①詳しくは、下記を参照して下さい。
M I D I 1.0 規格書
②概略を下記に示します。(ChatGPTより)
## **1. データの基本構造**
MIDIデータは**1バイト(8ビット)単位**で送信されます。
1バイトは以下の形式で構成されています:
1バイトは以下の形式で構成されています:
### **1.1 ステータスバイト**
**最上位ビット (MSB)**: 常に `1`。
**下位7ビット**: メッセージの種類やチャネル番号を指定。
| ステータスバイト | 機能
|--------------------------|-----------------------------------------------
| `1000xxxx` | ノートオフメッセージ
| `1001xxxx` | ノートオンメッセージ
| `1010xxxx` | ポリフォニック・アフタータッチ
| `1011xxxx` | コントロールチェンジ
| `1100xxxx` | プログラムチェンジ
| `1101xxxx` | チャネル・アフタータッチ
| `1110xxxx` | ピッチベンド
| `1111xxxx` | システムメッセージ
|--------------------------|-----------------------------------------------
`xxxx` はチャネル番号(0~15、16チャネル対応)。
### **1.2 データバイト**
**最上位ビット (MSB)**: 常に `0`。
**下位7ビット**: 各メッセージに必要な追加データ。
例:
* ノートオンメッセージには「音のピッチ」と「ベロシティ」が含まれる。
* コントロールチェンジには「コントローラ番号」と「値」が含まれる。
## **2. 主なメッセージの種類**
以下は、DIN MIDIでやり取りされる代表的なメッセージです。
### **2.1 ノートオン/ノートオフ**
**目的**: ノート(音符)を演奏開始・停止。
**フォーマット**:
*ノートオン: `1001cccc`(チャネル番号を含む) + `0kkkkkkk`(ノート番号)
+ `0vvvvvvv`(ベロシティ)
+ `0vvvvvvv`(ベロシティ)
*ノートオフ: `1000cccc`(チャネル番号を含む) + `0kkkkkkk`(ノート番号)
+ `0vvvvvvv`(ベロシティ)
+ `0vvvvvvv`(ベロシティ)
| 項目 | 内容
|--------------------|------------------------------------------
| ノート番号 | 0~127(中央Cは60)
| ベロシティ | 0~127(0はノートオフ扱い)
|--------------------|------------------------------------------ ### **2.2 コントロールチェンジ**
**目的**: 音量、パンニング、モジュレーションなどのパラメータを変更。
**フォーマット**:
`1011cccc`(チャネル番号を含む) + `0ccccccc`(コントローラ番号) + `0vvvvvvv`(値)
| コントローラ番号 | 機能例
|--------------------------|--------------------------
| 1 | モジュレーション
| 1 | モジュレーション
| 7 | メイン音量
| 10 | パンニング
| 64 | ダンパーペダル
|--------------------------|--------------------------### **2.3 ピッチベンド**
**目的**: 音程を滑らかに変更。
**フォーマット**:
`1110cccc`(チャネル番号を含む) + `0vvvvvvv`(LSB) + `0vvvvvvv`(MSB)
| 項目 | 内容
|---------------------|--------------------------
| 範囲 | -8192~8191
| デフォルト値 | 0(中央値)
|---------------------|--------------------------### **2.4 プログラムチェンジ**
**目的**: 音色(プログラム)の切り替え。
**フォーマット**:
`1100cccc`(チャネル番号を含む) + `0ppppppp`(プログラム番号)
| 項目 | 内容
|-----------------------|------------------------------
| プログラム番号 | 0~127(音色番号)
|-----------------------|------------------------------ ### **2.5 システムメッセージ**
**目的**: グローバルな操作(全チャネルに影響)。
**例**:
**システムリアルタイムメッセージ**: MIDIクロック(テンポ同期)やスタート/ストップ。
**システムエクスクルーシブ (SysEx)**: メーカー固有の機能。
## **3. 伝送方式**
DIN MIDIでは、以下の方式でデータが送信されます。
### **3.1 物理規格**
**ケーブル**: 5ピンDINケーブル(通常、ピン2がGND、ピン4とピン5がデータ用)。
**伝送速度**: 31.25 kbps (非同期)。
**信号レベル**: 電圧範囲は約5V。
### **3.2 データ伝送**
*MIDIデータはシリアル通信方式で1バイトずつ送信。
*スタートビット、8ビットデータ(LSB先行)、ストップビットで構成されます。
| 項目 | 内容
|----------------------|-----------------------
| スタートビット | 1ビット (0固定)
| データビット | 8ビット
| ストップビット | 1ビット (1固定)
|----------------------|----------------------- ### **3.3 データの順序**
送信されるデータの順序は以下の通り:
1. ステータスバイト(メッセージタイプとチャネル)
2. データバイト(必要に応じて1~2バイト)
例:
ノートオン (チャネル1、ノート60、ベロシティ127):
`10010000`(ステータスバイト)
`00111100`(ノート番号60)
`01111111`(ベロシティ127)
## **4. まとめ**
DIN MIDIはシンプルかつ堅牢なプロトコルで、音楽デバイス間のコミュニケーションを
標準化しています。シリアル通信を利用し、音符の演奏、音色の変更、コントローラの
操作、テンポ同期など、幅広い音楽情報をリアルタイムで伝送可能です。
標準化しています。シリアル通信を利用し、音符の演奏、音色の変更、コントローラの
操作、テンポ同期など、幅広い音楽情報をリアルタイムで伝送可能です。




































