ESP32-S3 - Device reset loop and USB re-enumeration when trying to run esptool commands over USB serial in own app
Posted: Sat Jan 24, 2026 10:14 am
Hi.
I'm building a secure mobile Android firmware updater app that programs ESP32-S3 devices over USB. The idea is to use this in manufacturing of my complete device for fast and secure handling and flashing of own firmware.
I'm using out of the box modules with the native USB-Serial-JTAG peripheral (no external USB-UART bridge), SLIP protocol over USB Serial and esptool-js v0.5.7 (JavaScript port of esptool.py).
It seems like I have to put the device into bootloader mode for the communications to work with my app. This is not needed when I use esptool, which makes the process more convenient and faster for flashing multiple devices.
Is there a way to put the device into bootloader mode automatically, or how does the esptool do it?
Which USB/serial state is the esp32-s3 device in when it's not in bootloader mode?
Which commands can it respond to then?
It would be great if you please can share some technical help here so I can get a more smooth connection process with the esp device.
>> Our connection approach and issue when not in bootloader mode
Our Android app connects to ESP32-S3 via USB-Serial-JTAG using:
USB Access: Android UsbManager API → USB device enumeration and permission
Serial Driver: usb-serial-for-android library → CDC-ACM interface handling
Protocol: SLIP-encoded packets over USB Bulk transfers
Commands: esptool.js SYNC command (opcode 0x08) to detect bootloader
The Problem: When device is NOT in bootloader mode, sending the SYNC packet causes immediate USB re-enumeration (device disconnects and reconnects with new ID), creating a permission loop. Resets?
Serial Port Configuration:
SET_LINE_CODING control transfer (USB request 0x20):
Baud: 115200, Data bits: 8, Stop bits: 1, Parity: None
SET_CONTROL_LINE_STATE control transfer (USB request 0x22):
DTR: false, RTS: false (strategy prevents reset triggers)
SYNC Command Transmission:
Opcode: 0x08 (ESP_SYNC)
Payload: 36 bytes (0x07 0x07 0x12 0x20 + 32 bytes of 0x55)
SLIP Encoding: Wrapped in 0xC0 delimiters, special chars escaped
USB Transfer: bulkTransfer() sends ~48-byte SLIP packet via Bulk OUT endpoint
Issue:
Re-enumeration Event (Scenario 1: Not in Bootloader)
50-200ms after SYNC packet: Device electrically disconnects from USB
Result:
UsbDeviceConnection invalidated
Device re-appears with new ID (deviceId: 43)
Permission lost (Android treats as new device)
Loop repeats: permission → connect → SYNC → re-enumerate → new ID...
No SLIP response received
--> Successful Connection (when in bootloader Mode)
Device held in bootloader (BOOT button during USB plug-in)
Same USB descriptors (VID/PID unchanged)
SYNC packet sent → No re-enumeration
ESP32 responds with 8 SLIP packets (~150ms total)
Chip ID read succeeds, flash operations work perfectly
Many thanks,
Daniel
I'm building a secure mobile Android firmware updater app that programs ESP32-S3 devices over USB. The idea is to use this in manufacturing of my complete device for fast and secure handling and flashing of own firmware.
I'm using out of the box modules with the native USB-Serial-JTAG peripheral (no external USB-UART bridge), SLIP protocol over USB Serial and esptool-js v0.5.7 (JavaScript port of esptool.py).
It seems like I have to put the device into bootloader mode for the communications to work with my app. This is not needed when I use esptool, which makes the process more convenient and faster for flashing multiple devices.
Is there a way to put the device into bootloader mode automatically, or how does the esptool do it?
Which USB/serial state is the esp32-s3 device in when it's not in bootloader mode?
Which commands can it respond to then?
It would be great if you please can share some technical help here so I can get a more smooth connection process with the esp device.
>> Our connection approach and issue when not in bootloader mode
Our Android app connects to ESP32-S3 via USB-Serial-JTAG using:
USB Access: Android UsbManager API → USB device enumeration and permission
Serial Driver: usb-serial-for-android library → CDC-ACM interface handling
Protocol: SLIP-encoded packets over USB Bulk transfers
Commands: esptool.js SYNC command (opcode 0x08) to detect bootloader
The Problem: When device is NOT in bootloader mode, sending the SYNC packet causes immediate USB re-enumeration (device disconnects and reconnects with new ID), creating a permission loop. Resets?
Serial Port Configuration:
SET_LINE_CODING control transfer (USB request 0x20):
Baud: 115200, Data bits: 8, Stop bits: 1, Parity: None
SET_CONTROL_LINE_STATE control transfer (USB request 0x22):
DTR: false, RTS: false (strategy prevents reset triggers)
SYNC Command Transmission:
Opcode: 0x08 (ESP_SYNC)
Payload: 36 bytes (0x07 0x07 0x12 0x20 + 32 bytes of 0x55)
SLIP Encoding: Wrapped in 0xC0 delimiters, special chars escaped
USB Transfer: bulkTransfer() sends ~48-byte SLIP packet via Bulk OUT endpoint
Issue:
Re-enumeration Event (Scenario 1: Not in Bootloader)
50-200ms after SYNC packet: Device electrically disconnects from USB
Result:
UsbDeviceConnection invalidated
Device re-appears with new ID (deviceId: 43)
Permission lost (Android treats as new device)
Loop repeats: permission → connect → SYNC → re-enumerate → new ID...
No SLIP response received
--> Successful Connection (when in bootloader Mode)
Device held in bootloader (BOOT button during USB plug-in)
Same USB descriptors (VID/PID unchanged)
SYNC packet sent → No re-enumeration
ESP32 responds with 8 SLIP packets (~150ms total)
Chip ID read succeeds, flash operations work perfectly
Many thanks,
Daniel