ESP32マルチ書き込みボードの改造（ネイティブUSBによる書き込み）

# 0. はじめに

先日、『[**ESP32マルチ書き込みボード**](https://qiita.com/nak435/items/500c877bcaa3d38b939a)』を紹介させていただきました。その後、**ESP32-S3のネイティブUSBによる書き込み**にも対応したので、今回はそれを紹介いたします。


https://qiita.com/nak435/items/500c877bcaa3d38b939a

# 1. 改造した書き込みボード

新しい書き込みボードは↓こんな感じです。<i>左側のUSBアダプタも作成しました。3.3V降圧モジュールを仕込んであります。</i>

<img width=300 alt="IMG_0876.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/27eaf6c6-1f61-044c-0932-567b1e3f5dbd.png">


下の写真は改造前のボードで、比較しながら改造点を説明します。
<img width=200 alt="IMG_0802.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/7c9932f9-124c-a48a-5a51-acc6c0ba0cdf.png">

<b>(1)</b> ESP32-S3のネイティブUSBを使用するため、USBシリアル変換モジュールは使用しません。代わりUSBのD-/D+信号線をESP32-S3のIO19/IO20に接続するため、2Pピンソケットを追加しました。

<b>(2)</b> 手動書き込みに対応するため、IO0をGNDに落とすタクトスイッチを追加しました。ネイティブUSBで書き込む場合『自動書き込み』ができず、後から急遽追加しました。そのためタクトスイッチも不揃いで<b>取って付けた</b>感ありありです。</i>

書き込みボード自体の改造は以上の2点だけです。

次回、書き込みボード自体を新規作成する時は、ネイティブUSB用の4Pヘッダ（3.3v/GND/D-/D+）を独立して準備しようと思います。そうすればシリアル接続と共存ができますので。


# 2. ネイティブUSBを使用する場合の気づき

自分に経験がないため、実際の書き込みは、一筋縄ではいきませんでした。
色々試行錯誤して、今は普通に書き込めるようになりましたので、そこで得た内容を気づき事項としてまとめておきます。

## (1) 自動書き込みができない

通常のシリアルインタフェースによる自動書き込みは、RTS/DTRピンによりEN/IO0を制御することでブートモードを制御していますが、ネイティブUSBの場合はそれができません。よって、手動書き込みのため、再起動時にIO0をGNDに落として<b>Download Boot</b>にする必要がありました。

同様に、書き込み後の自動リセットも効かないため、手動でリセット（再起動）する必要があります。


:::note warn
『<b>自動書き込みができない</b>』という認識は<b>誤り</b>(?)であったことが、後で判明しました。詳しくは後述します。
:::



## (2) シリアルポートが変化する

[**CircuitPython**](https://circuitpython.org/board/espressif_esp32s3_devkitc_1_n8r8/)が書き込んであったESP32-S3をネイティブUSB接続したときのシリアルポートは`/dev/cu.usbmodemC7FD1A7E6ACC1`でした。

USBデバイスツリーには、`ESP32-S3-DevKitC-1-N8R8`と認識され、
![circuitpython.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/415b1a05-84c8-6b42-2a87-6625b1196c90.png)
`CIRCUITPY`というフラッシュドライブも現れます。また、
`screen /dev/cu.usbmodemC7FD1A7E6ACC1 115200`でREPLも機能します。

これを、<b>Download Boot</b>で起動すると、シリアルポートは`/dev/cu.usbmodem144201`に変化し、USBデバイスツリーには、`USB JTAG/serial debug unit`と認識されます。
![USB_JTAG.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/088acea2-532e-9561-fe59-fc5b3c215d87.png)

ここで、Arduino IDEからESP32のスケッチ例`KeyboardMessage`を書き込んで再起動すると、今度は、シリアルポートは`/dev/cu.usbmodem144202`に変化し、USBデバイスツリーには、`ESP32S3_DEV`と認識されます。
![ESP32S3_DEV.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/48d1f524-3a9d-0a91-1147-fffe67806c08.png)


以上のように、シリアルインタフェースでは固定であったシリアルポートが、ネイティブUSBだと状況により変化するため、**esptool**や**Arduino IDE**から書き込みをする場合に、シリアルポートを確認する必要があります。

## (3) Arduino IDEでのボードの指定方法

よく分からず戸惑ったのが、Arduino IDEでのボードの指定方法です。ボードそのものは、`ESP32S3 Dev Module`でよいのですが、`USB Mode`と`Upload Mode`で何を指定すべきか分からず、何パターンも試して分かった事があります。

### ・Upload Mode

これは、現在のESP32-S3のネイティブUSBの認識状態により、指定値の振る舞いが違ってきます。<i>下表は『USB認識状態』を現在のシリアルポートで代用します。</i>

| USB認識状態<br>（シリアルポート） | UART0 / Hardware CDC |USB-OTG CDC (TinyUSB)|
|:-:|:-:|:-:|
| /dev/cu.usbmodemC7FD1A7E6ACC1 | 書き込み不可(*1) |書き込み不可(*2)|
| /dev/cu.usbmodem144201 | 書き込み可  |書き込み可 |
| /dev/cu.usbmodem144202 | 書き込み不可(*1)  |書き込み可 |

(*1)書き込み不可理由：Connecting...........スケッチの書き込み中にエラーが発生しました.
(*2)書き込み不可理由：シリアルポート「・・・」が選択されていますが、そのポートは存在しないか、ボードが接続されていません。

:::note alert
はじめて今回の書き込みボードでネイティブUSB接続によりアップロードを試みた時は、`CircuitPython`が書き込んであるESP32-S3を使用し、シリアルポートは`/dev/cu.usbmodemC7FD1A7E6ACC1`でした。上の表に示す通り、この状態では、`Upload Mode`に何を指定してもアップロードすることができなかったことと、その後、（IO0をGNDに落として）<b>Download Boot</b>したところ、シリアルポートが`/dev/cu.usbmodem144201`に変化し、アップロードできたことで、前項の『ネイティブUSBの場合は自動書き込みはできない』という誤認識につながりました。
:::

興味ある動作として、`USB-OTG CDC (TinyUSB)`を指定した場合は、書き込みの最初に現在のシリアルポートを一度閉じて、複数のシリアルポートを試してアップロードするシリアルポートを決定する動作が確認できました。

```log:Arduino IDEのログから抜粋
シリアルポート「/dev/cu.usbmodem144202」を1200bpsで開いて閉じる事によって、リセットを行っています。
PORTS {/dev/cu.BLTH, /dev/cu.Bluetooth-Incoming-Port, /dev/cu.usbmodem144202, /dev/cu.usbmodemHIDLD1, /dev/tty.BLTH, /dev/tty.Bluetooth-Incoming-Port, /dev/tty.usbmodem144202, /dev/tty.usbmodemHIDLD1, } / {/dev/cu.BLTH, /dev/cu.Bluetooth-Incoming-Port, /dev/cu.usbmodem144201, /dev/cu.usbmodemHIDLD1, /dev/tty.BLTH, /dev/tty.Bluetooth-Incoming-Port, /dev/tty.usbmodem144201, /dev/tty.usbmodemHIDLD1, } => {/dev/cu.usbmodem144201, /dev/tty.usbmodem144201, }
Found upload port: /dev/cu.usbmodem144201
```

この動作により、シリアルポートが`/dev/cu.usbmodem144202`のときも`/dev/cu.usbmodem144201`に変化し、書き込み可となり、この動作は『自動書き込み（<b>Download Boot</b>への移行）』の動作だと理解しました。（<i>←この理解も正しく無い気がしています。</i>）


### ・USB Mode

アップロード後のESP32-S3のUSBのモードを指定するものと思われます。


| USB Mode | 目的 | 備考 |
|:-:|:-:|:-:|
| Hardware CDC and JTAG | Serial利用  |   |
| USB-OTG (TinyUSB) | USB-HID実装 |   |


単なるLチカのスケッチならどちらの指定でもちゃんと動作しますが、シリアルモニタに表示したい場合は`Hardware CDC and JTAG`を指定しないと表示しませんでした。逆に、スケッチ例`KeyboardMessage`では、`USB-OTG (TinyUSB)`を指定しないと機能しませんでした。
`KeyboardMessage`のソースコードを見ると、コードの先頭に`USBモード`の判定が入っています。

```c++:KeyboardMessage.ino
#if ARDUINO_USB_MODE
#warning This sketch should be used when USB is in OTG mode
void setup(){}
void loop(){}
#else
: : :
```
`Hardware CDC and JTAG`を指定してコンパイルすると、**warningメッセージ**「_This sketch should be used when USB is in OTG mode_」が出ていたのですが、処理が進んでアップロードができてしまうので、気づきませんでした。`#warning`を`#error`に変えてコンパイルエラーにすべきだと思います。

# 3. 書き込み性能

気持ちネイティブUSB接続の方が早く書き込みができるように感じたので、ESP32ダウンローダによるシリアル接続と、今回のネイティブUSB接続での、書き込み性能を比較してみました。

> **esptool**を使い、ファイル[`adafruit-circuitpython-espressif_esp32s3_devkitc_1_n8r8-ja-7.3.3.bin`](https://circuitpython.org/board/espressif_esp32s3_devkitc_1_n8r8/)（約1.5MB）を書き込む速度を**time**コマンドで比較。通信以外の時間も含む。


| 書き込み  | ボーレート | 速度 [秒] |
|:-:|:-:|:-:|
|ネイティブUSB接続 | 指定なし(115,200bps)   |23.672| 
|ネイティブUSB接続 | 12,000,000bps   |23.675| 
|シリアル接続   | 指定なし(115,200bps)  | 94.538 |
| シリアル接続  | 921,600bps  | 28.440|
| シリアル接続  | 1,500,000bps  | 25.127|

数秒ですがシリアル接続より早いです。


>『[ESP32­S3 Technical Reference Manualの「33.2 Features」](https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf#usbserialjtag)』に「USBシリアル/JTAGデバイスは USB2.0デバイスですが、USB2.0規格で導入された高速スピード (480Mbps) モードではなく、フルスピード (12Mbps) のみをサポートする」と注記がありました。

ネイティブUSB接続の場合はボーレートの指定は意味が無い(?)ようです。





# 4. あらためて

この記事をまとめるにあたり、あらためて『[ESP32-S3 Series Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf)』を読むと、「3.5.12 USB Serial/JTAG Controller」に、「CDC-ACM supports host controllable <b>chip reset and entry into download mode</b>.」という記載がありました。また、『[ESP32­S3 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf)』の「Table 33­-1. Standard CDC­-ACM Control Requests」と「Table 33-­2. CDC­-ACM Settings with RTS and DTR」から、『[SET_CONTROL_LINE_STATE](https://www.keil.com/pack/doc/mw/USB/html/group__usbh__cdcacm_functions__api.html#gaa0487b3b54588e9c313d6246ccc52a57)』apiによって、RTS/DTRを制御していることが読み取れます。そして、RTS=1/DTR=0でESP32-S3をResetできるようです。

USB-CDCでシリアル通信をエミュレーションしていることが理解できて少しスッキリしました。 


しかし、`CircuitPython`が書き込んである状態のシリアルポート`/dev/cu.usbmodemC7FD1A7E6ACC1`では書き込み出来ないことが腑に落ちません。また、どのモードでも書き込み後に`リセット`が効かず手動リセットが必要で、これも納得できません。これについては、似たような指摘を『[esp32-s3 does not reset after upload #6762](https://github.com/espressif/arduino-esp32/issues/6762)』で発見しましたが、結論がよく分かりませんでした。

`MicroPython`を書き込んで試してみましたが、状況は同じでした。ただし、シリアルポートは`/dev/cu.usbmodem1234561`、USBデバイスツリーには`Espressif Device`と認識され、`CircuitPython`の時のようなフラッシュドライブは現れません。
![micropython.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283293/073d4024-5a57-cba7-bc79-ca9f57b24d36.png)

**esptool**コマンドの`--before`引数を`default_reset`から`usb_reset`に変えてみても、状況は変わらず。

この状態で、`ESPアップローダ`でシリアル接続からアップロードすると、これは普通に問題なく可能で、シリアルポートは`/dev/cu.wchusbserial53250003491`。
シリアル接続であれば、どの状態でも自動書き込みが可能です。

<hr>


ここで、**eFuse**の設定値を確認しました。
ダウンロードに関係していそうなeFuseの値は以下の通りです。

```log:eFuse summary情報からの抜粋
DIS_FORCE_DOWNLOAD (BLOCK0)                        Disables forcing chip into Download mode           = False R/W (0b0)
DIS_USB_SERIAL_JTAG_DOWNLOAD_MODE (BLOCK0)         Disables USB-Serial-JTAG download feature in UART  = False R/W (0b0)
                                                   download boot mode    
DIS_USB_OTG_DOWNLOAD_MODE (BLOCK0)                 Disables USB-OTG download feature in UART download = True R/W (0b1)
                                                    boot mode                                        
DIS_DOWNLOAD_MODE (BLOCK0)                         Disables all Download boot modes                   = False R/W (0b0)
ENABLE_SECURITY_DOWNLOAD (BLOCK0)                  Enables secure UART download mode (read/write flas = False R/W (0b0)
                                                   h only)                                           
```

`DIS_USB_OTG_DOWNLOAD_MODE`が**True**になっているのが気になります。


## 4.1 eFuseを設定する


**espefuse**コマンドで`DIS_USB_OTG_DOWNLOAD_MODE`を**False**に設定します。

```console
$ espefuse.py --chip esp32s3 --port /dev/cu.wchusbserial53250003491 burn_efuse DIS_USB_OTG_DOWNLOAD_MODE 0

A fatal error occurred: New value is not accepted for efuse 'DIS_USB_OTG_DOWNLOAD_MODE' (will always burn 0->1), given value=0
$
```

しかし、エラーになりました。
0を1にする変更しか出来ない? 常にDisableでEnable出来ない???
シリアル接続でしたので、ネイティブUSB接続にて再度実行してみる。

```console
$ espefuse.py --chip esp32s3 --port /dev/cu.usbmodem144201 burn_efuse DIS_USB_OTG_DOWNLOAD_MODE 0

A fatal error occurred: New value is not accepted for efuse 'DIS_USB_OTG_DOWNLOAD_MODE' (will always burn 0->1), given value=0
$
```

ネイティブUSB接続でも結果は同じ。

ESP32-S3の今のUSBモードが`Hardware CDC`のはずなので、これを`USB-OTG`に設定して、再度実行するも、結果は同じ。

```console
$ espefuse.py --chip esp32s3 --port /dev/cu.usbmodem144201 burn_efuse DIS_USB_OTG_DOWNLOAD_MODE 0

A fatal error occurred: New value is not accepted for efuse 'DIS_USB_OTG_DOWNLOAD_MODE' (will always burn 0->1), given value=0
$
```

他、USBモードと接続形態（シリアル/ネイティブUSB）の組み合わせを試しましたが、すべて同じエラーでした。手詰まり、お手上げです。

<i><b>ネイティブUSB接続の場合は自動書き込みはできず、DOWNLOAD BOOTで起動してから書き込むのが、唯一の手順なのでしょうか?</b></i>

ESP32ネイティブUSB接続用のVCPドライバが必要な気がします・・・・・。

<i>**ネイティブUSB接続時の自動書き込みをご存知の方、ぜひ教えてください。**</i>



# 5. おわり

「自動書き込みはできない、書き込み後のリセットも効かない。」まったくスッキリしていませんが、ネイティブUSBによる書き込みができることは確認できたので、今回の記事は以上とします。  



# おまけ

ネイティブUSB接続ができると「[Digispark ATTiny85](http://digistump.com/products/1)」や「[Beetle ATMega32u4](https://www.diymore.cc/products/diymore-beetle-badusb-micro-atmega32u4-au-development-expansion-module-board-for-arduino-leonardo-r3-5v-i2c)」を、超高機能なESP32-S3に置き換えて、**WiFi付き**/**Bluetooth付き**にすることができます。実現したいことの内容とマイコンの単価を鑑みて、使い分けていこうと思います。
<a href="http://digistump.com/products/1"><img width=150 src="https://s3.amazonaws.com/digistump-resources/images/l/2520dcf84a0a3bf2257c2559d7a187db.jpg"></a> <a href="https://www.diymore.cc/products/diymore-beetle-badusb-micro-atmega32u4-au-development-expansion-module-board-for-arduino-leonardo-r3-5v-i2c"><img width="100" src="https://www.amazon.co.jp/images/I/610SxrdhPAL._AC_SL1000_.jpg"></a>



<table style="text-align: center;">
  <caption>マイコン単体（秋月電子）価格</caption>
  <thead style="text-align: center;">
    <tr>
      <th colspan=2>マイコン</th><th>サイズ(W x H)</th><th>税込価格</th>
    </tr>
  </thead>
<tbody style="text-align: center;">
  <tr style="text-align: center;">
    <td><a href="https://akizukidenshi.com/catalog/g/gI-09574/"><img width=110 src="https://akizukidenshi.com/img/goods/L/109574.jpg"></a></td>
    <td> <a href="https://akizukidenshi.com/catalog/g/gI-09574/">ATTiny85-20SUR</a> </td> 
    <td>約5mm x 7mm</td>
<td>￥150</td>
  </tr>
  <tr style="text-align: center;">
    <td><a href="https://akizukidenshi.com/catalog/g/gI-09835/"><img width=100 src="https://akizukidenshi.com/img/goods/L/109835.jpg"></a></td>
    <td> <a href="https://akizukidenshi.com/catalog/g/gI-09835/">ATMega32u4</a>  </td> 
    <td>約12mm x 12mm</td>
<td>￥640</td>
  </tr>
  <tr style="text-align: center;">
    <td><a href="https://akizukidenshi.com/catalog/g/gM-17256/"><img width=120 src="https://akizukidenshi.com/img/goods/L/117256.jpg"></a> </td></td>
    <td> <a href="https://akizukidenshi.com/catalog/g/gM-17256/">ESP32-S3-WROOM-1-N16R8</a> </td> 
    <td>約14mm x 26mm<br>（アンテナ部を含む）</td>
<td>￥530</td>
  </tr>
</tbody>
</table>

ESP32-S3のコスパの良さが際立ちます。チップサイズに制約が無いならATMega32u4の出番はもう無いかも・・・。