This is a [USB DFU](https://www.usb.org/document-library/device-firmware-upgrade-11-new-version-31-aug-2004) (DFU mode) implementation for [ESP32-S2](https://www.espressif.com/en/products/socs/esp32-s2) based on [tinyUSB](https://docs.tinyusb.org/en/latest/index.html). It allows flashing the firmware using [dfu-util](http://dfu-util.sourceforge.net/). background ========== ESP-ROM ------- The ESP-S2 comes with a ROM bootloader that already allows you to flash over USB using the serial CDC ACM profile. But this method does not let you restart into the main firmware. The ROM bootloader USB stack even offers DFU capability, and you can flash using dfu-util. But this one also does not let you restart into the main firmware. To flash using USB serial (can't restart the device): ~~~ idf.py -p /dev/ttyACM0 flash ... WARNING: ESP32-S2 chip was placed into download mode using GPIO0. esptool.py can not exit the download mode over USB. To run the app, reset the chip manually. To suppress this note, set --after option to 'no_reset'. ~~~ And to [flash using DFU](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/dfu.html): ~~~ # generate the DFU binary (not the same as the usual flash binary) idf.py dfu dfu-util --device 303a:0002 --download build/dfu.bin # or idf.py dfu-flash ~~~ Note: after detaching, the bootloader claims for be in runtime mode, but this is still the bootloader, and not the flashed firmware. mode switch ----------- To switch from the ESP32-S2 ROM bootloader back to the firmware flashed over USB, you need to press the reset button. This is cumbersome when developing firmware, and sometimes impossible if the board is encased or installed remotely. This USB DFU implementation allows to switch back to runtime mode without pressing a button. The runtime firmware can also reboot into the DFU mode without pressing a button. ESP USB CDC ----------- You can change the console output using `idf.py menuconfig` under `Component config → ESP System Settings → Channel for console output` from the default UART0 to USB CDC. After re-powering and re-plugging USB, the device appears as USB CDC ACM device. Not only it the console over this serial port, but you can even use it to flash the device without pressing any button. ~~~ idf.py --port=/dev/ttyACM0 flash ~~~ The USB device also comes with DFU capabilities, and can be flashed using DFU, also without pressing on buttons: Using the IDF: ~~~ idf.py dfu dfu-flash ~~~ Or using `dfu-util` directly: ~~~ idf.py dfu dfu-util --device 303a:0002 --download build/dfu.bin ~~~ This can only be used with the USB CDC ACM profile, Espressif USB VID/PID. It makes it simpler and is sufficient for most projects. Our DFU implementation can be use with and USB configuration, allows custom USB VID/PID, and is faster. It makes it a bit more complex, and needs more space, but allows using custom USB configuration. install ======= compile ------- To compile the firmware, you need the [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html#installation). Here I compile it for the [WEMOS S2 mini](https://www.wemos.cc/en/latest/s2/s2_mini.html) board (other board definitions are available in `hw/bsp/esp32s2/boards/`). ~~~ cd examples/device/dfu_freertos/ make BOARD=wemos_s2_mini all ~~~ This is equivalent to: ~~~ idf.py -B_build/wemos_s2_mini -DFAMILY=esp32s2 -DBOARD=wemos_s2_mini -DIDF_TARGET=esp32s2 build ~~~ flash ----- To flash the compiled firmware: ~~~ idf.py -B_build/wemos_s2_mini -DFAMILY=esp32s2 -DBOARD=wemos_s2_mini -DIDF_TARGET=esp32s2 -p /dev/ttyACM0 flash ~~~ or to use esptool directly over the ROM USB DFU bootloader, without ESP-IDF: ~~~ esptool.py -p /dev/ttyACM0 --before no_reset --after no_reset --chip esp32s2 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x1000 _build/wemos_s2_mini/bootloader/bootloader.bin 0x8000 _build/wemos_s2_mini/partition_table/partition-table.bin 0xd000 _build/wemos_s2_mini/ota_data_initial.bin 0x10000 _build/wemos_s2_mini/dfu_freertos.bin ~~~ This will flash the DFU firmware to the `factory` partition. The DFU firmware will in turn flash the downloaded image onto the [OTA0](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/ota.html) partition. For that, a custom [partition table](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/partition-tables.html) is used. config ------ To further configure the build: ~~~ idf.py -B_build/wemos_s2_mini -DFAMILY=esp32s2 -DBOARD=wemos_s2_mini -DIDF_TARGET=esp32s2 menuconfig ~~~ runtime ------- This firmware is just for the DFU mode. The main application needs to implement the runtime mode separately. To switch from runtime to DFU mode during detach, set the boot partition in `otadata` to `factory`, and restart the ESP. force DFU --------- The USB DFU firmware does not act as bootloader. Meaning, the ESP bootloader does not start the USB DFU firmware, which in turn starts the main firmware. The main firmware in the OTA0 partition is directly loaded by the ESP bootloader. Thus is it up to the main firmware to start the DFU mode, as described in `runtime`. In case the main firmware is defective, and does not allow to switch back to DFU mode, you can still force booting the USB DFU firmware. For that, boot the ESP ROM bootloader (i.e. download mode), and invalidate the OTA data partition: ~~~ otatool.py --esptool-args after=no_reset_stub --port /dev/ttyACM0 erase_otadata ~~~ When restating the ESP by pressing the reset button, the ESP bootloader will start the firmware which is in the factory partition, which should be the DFU firmware previously flashed. design choice ============= ESP-IDF ------- The [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html) uses [tinyUSB](https://github.com/espressif/tinyusb), but only offers [few profiles](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html) (e.g. CDC, MSC). They are defined in 'esp-idf/components/tinyusb/additions', and re-use the tinyUSB device implementations. But there is no DFU implementation (runtime or DFU mode). TinyUSB ------- [TinyUSB](https://github.com/hathach/tinyusb) is a generic USB stack for micro-controllers. It [supports](https://docs.tinyusb.org/en/latest/reference/supported.html) ESP32-S2. DFU is [implemented](https://github.com/hathach/tinyusb/tree/master/src/class/dfu) and there is an [example](https://github.com/hathach/tinyusb/tree/master/examples/device/dfu). This example can't be used for ESP32-S2 though. I've added freeRTOS to it so it can be used on the ESP32-S2. bootloader ---------- The ESP-IDF allows you to have a [custom](https://github.com/espressif/esp-idf/tree/master/examples/custom_bootloader) [bootloader](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/bootloader.html) (2nd stage), but I was not able to add freeRTOS to it, required by the tinyUSB implementation. factory ------- This USB DFU implementation is flashed as a factory application image. It will then flash the main firmware image into the [OTA0 partition](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/ota.html). Thus it requires a custom [partition table](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/partition-tables.html), with a "small" factory partition, just one OTA application partition, and one OTA data partition. The factory partition should be 200 KB. This is super large for just a "bootloader" to flash firmware images, but since ESP32 often use large external flash memory, this is not too much of an issue. Feel free to reuse the 'partitions.csv' file as example. alternatives ============ TinyUF2 ------- [TinyUF2](https://github.com/adafruit/tinyuf2) is a UF2 bootloader by Adafruit. It is based on TinyUSB, and supports ESP32-S2. It uses the DFU name (Device Firmware Upgrade), but it's not the DFU specified by USB. Instead it provides a MSC interface where you can copy the firmware binary file to.