Firmware programming using native USB port on ESP32-S3 does not work
I tried to upload my firmware using the native USB port of some ESP32-S3 based devices (Epressif ESP32-S3-DevKitC-1, WT32-SC01 Plus as well as one of my own designs). Unfortunatelly the upload (programming) fails with the message "Installation failed, The device has been lost."
It looks like the programming works fine, but towards the end the transfer speeds up like crazy and then fails...
However programming works just just fine, when using an USB to UART bridge (same firmware, same manifest). Interestingly I can program the devices with esptool-js using the native USB port without any issues (using the same firmware).

Just to be sure that this is not related to my system, I've tried the same thing on three different Windows 10 systems and on one system running Windows 11 (not sure if the problem is present on Apple systems as well). I've experienced the same behavior on Microsoft Edge as well as on Google Chrome.
Do you have any idea how this could be fixed?
Has been fixed upstream but has not been released yet. We'll update when available.
Hi Paulus, unfortunately the problem persists after the fix in esptool-js. This kept bugging me, so I invested a few hours yesterday to do some extensive testing. I found the following behavior, which might help you identify the root cause:
- When programming an ESP32-S3 using an USB to UART bridge it always works (using esptool.js, esp-web-tools, PlatformIO or the Arduino IDE).
- When using the native USB port, programming works using esptool.js, PlatformIO or the Arduino IDE. For esp-web-tools it works only (once) if the device was previously erased or was programmed with a really small firmware (something like a simple "Hello World").
- As soon as a “larger” firmware (around 1.3 Mbytes in my case) has been written to a ESP32-S3 device, using native USB and esp-web-tools, any subsequent attempt to write a larger firmware to the device will cause it to fail with a "the device has been lost" error message.
- From this point on, the device seems to be corrupted in such a way, that no larger firmware can be programmed over native USB (also not using PlatformIO or the Arduino IDE or even Espressif's Flash dowload tool).
- The device can be "healed" by uploading a small firmware (either over native USB or an USB to UART bridge) using esptool.js, esp-web-tools, PlatformIO or the Arduino IDE.
- Any attempt to erase the device, after an upload of a larger firmware has been aborted with the "the device has been lost" error message, will further "corrupt" the device by causing a boot loop.
- Unfortunately I can not simply automatically erase the device before every upload (the obvious workaround), as attempting to erase a device containing a larger firmware with esp-web-tools (even after a successful upload of a larger firmware) will corrupt the device with a boot loop. Erasing the device with esptools.js however will not corrupt the device.
- No device corruption occurs (irrespective of the firmware size) using esptool.js to program or erase the device.
Feel free to reach out to me, if you need more information or further testing...
Weird.
When we erase the flash, we call erase_flash()
https://github.com/esphome/esp-web-tools/blob/ccf9c3be2f98a6c82990dea0f75753520dbf7121/src/flash.ts#L163
Esptool-js supports flashing on write too, but that calls the same logic:
https://github.com/espressif/esptool-js/blob/e8d66b62bfec8c416e65fca614e06965b8bf78fa/src/esploader.ts#L900
Could be the old stub loader code used in esptool.js. There where some mandatory bug fixes for the S3 in native USB mode in esptool.py stub loaders.
Yes, there where quite a few fixes in the stub, but actually I tried the stub from esptool.py 4.5.1 and experience the same behavior. The only difference being, that the upload failed a bit earlier (at around 30% instead of at around 80%). I assume (kind of thinking loud) that this has to be kind of a timeout/watchdog issue in the stub that is not triggered by smaller firmware as the upload process is shorter. But this does not explain, why things work using plain esptool.js. I even "hacked" together a firmware upload facility using esptool.js (with the old stubs) and everything works as expected. So I agree with Paulus: this is weird...
One difference I was able to spot is how flash size, frequency, etc are handled, when calling write_flash()
https://github.com/esphome/esp-web-tools/blob/0e0bc1d65dcf9aaf3cf199fcbff97c6318b7e58b/src/flash.ts#L184-L190
vs
https://github.com/espressif/esptool-js/blob/e8d66b62bfec8c416e65fca614e06965b8bf78fa/index.js#L292-L298
not sure if this could be related..?
Have you tried when changing this values?
Yes, I've tried changing these values, but as expected, it did not make any difference. I would have been surprised, if this was the cause, since flashing over an USB to UART bridge works, using exactly the same call...
Can anybody replicate the problem or am I the only one facing this issue?
I have the same issue when I try to configure the WiFi via the ESP Web Tools interface. It says disconnected, but wifi is successfully configured after that error message.
The S3 native USB has 3 modes: (1) CDC/JTAG || (2) OTG - TinyUSB driver || (3) disabled Each of these 3 modes are activated by the running firmware of the S3, right after booting.
USB Web tools and other uploading modes work best with the (1) CDC/JTAG. It is possible to activate this mode by uploading an empty (or small) firmware/sketch that sets the CDC/JTAG mode. But if the next uploaded firmware is created/programmed to use (2) or (3), the following uploads using USB will fail.
Therefore, S3 demands that CDC/JTAG is activated by the running firmware and that all next firmwares keep this mode always active.
@SuGlider That's also what I noticed. But as a side-question, how to do this with the ESP32-S2? As far as I know, it doesn't support HWCDC. (and if it does, I would really like to know about it)
To workaround this S3 bug esptool.py and esptool.js needs to be enhanced to do this (set up S3 to work correctly with cdc/jtag) without any user interaction.
To workaround this S3 bug esptool.py and esptool.js needs to be enhanced to do this (set up S3 to work correctly with cdc/jtag) without any user interaction.
This is not a esptool.py bug, because it works fine with ESPTOOL.py. This is a limitation of WEB-USB-tools.
The work around for S3/S2 USB is to set USB DFU mode manually (ROM) by keeping BOOT pressed and pulsing RESET/EN. It needs user interaction... It seems to fail with WEB-Tools.
https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/dfu.html
Check this video: https://www.youtube.com/watch?v=Zqqj8GC0Q9Q BOOT + RESET activates S2 TinyUSB ROM layer and then the computer is able to enumerate it.
Can the web flasher toggle the CTS/RTS pins? In the USBCDC code, there is some code to detect this and issue a reboot.
As I do use CDC/JTAG for the USB mode on my S3 devices, this can not be the issue. By now I am suspecting, that the issue is somehow related to Windows not playing nicely with something in esp-web-tools: A few weeks ago, I switched my main workstation to Linux and noticed, that the issue went away (uploading using Chromium). When going back to Windows the problem reappears and the device gets corrupted, after the upload process aborts with the "The device has been lost" message. Uploading a tiny firmware (also configured to use CDC/JTAG) makes the device programmable again. I've build my own "web-upload-facility" based on esptool-js and this works on Windows and Linux with the exactly same firmware files that cause the issue when attempting an upload with esp-web-tools. I would rather use esp-web-tools, which is very pretty and refined, instead of investing the time to make my own solution look good...
A side note: I do not use a merged firmware file, I use a manifest with four parts.
@dkalliv See also my comment here: https://github.com/espressif/esptool-js/issues/97#issuecomment-1572728755