From fbbcbefccf8ee6ddd86356be80c5ade9965911de Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 12:09:10 +0200 Subject: [PATCH 1/9] support for ESP32-S31 --- esp_flasher/__main__.py | 1 + esp_flasher/common.py | 10 +- esp_flasher/own_esptool.py | 283 +++++++++++++++++++++++++++++++- esp_flasher/stubs/esp32s31.json | 8 + 4 files changed, 296 insertions(+), 6 deletions(-) create mode 100644 esp_flasher/stubs/esp32s31.json diff --git a/esp_flasher/__main__.py b/esp_flasher/__main__.py index 0246909..1a25c88 100644 --- a/esp_flasher/__main__.py +++ b/esp_flasher/__main__.py @@ -34,6 +34,7 @@ def parse_args(argv): group.add_argument("--esp32", action="store_true") group.add_argument("--esp32s2", action="store_true") group.add_argument("--esp32s3", action="store_true") + group.add_argument("--esp32s31", action="store_true") group.add_argument("--esp32c2", action="store_true") group.add_argument("--esp32c3", action="store_true") group.add_argument("--esp32c5", action="store_true") diff --git a/esp_flasher/common.py b/esp_flasher/common.py index 54a9566..5f14020 100644 --- a/esp_flasher/common.py +++ b/esp_flasher/common.py @@ -315,6 +315,10 @@ def configure_write_flash_args( # Fallback if revision cannot be parsed (assume RC1) model = "esp32p4" safeboot = "tasmota32p4-safeboot.bin" + elif "ESP32-S31" in info.model: + model = "esp32s31" + safeboot = "tasmota32s31-safeboot.bin" + ofs_bootloader = 0x2000 elif "ESP32-S3" in info.model: model = "esp32s3" safeboot = "tasmota32s3-safeboot.bin" @@ -392,8 +396,8 @@ def configure_write_flash_args( min_rev, min_rev_full, max_rev_full, elf_sha256_offset, use_segments, flash_mmu_page_size, pad_to_size, spi_connection, output) -def detect_chip(port, force_esp8266=False, force_esp32=False, force_esp32s2=False, force_esp32s3=False, force_esp32c2=False, force_esp32c3=False, force_esp32c5=False, force_esp32c6=False, force_esp32c61=False, force_esp32p4=False): - if force_esp8266 or force_esp32 or force_esp32s2 or force_esp32s3 or force_esp32c2 or force_esp32c3 or force_esp32c5 or force_esp32c6 or force_esp32c61 or force_esp32p4: +def detect_chip(port, force_esp8266=False, force_esp32=False, force_esp32s2=False, force_esp32s3=False, force_esp32s31=False, force_esp32c2=False, force_esp32c3=False, force_esp32c5=False, force_esp32c6=False, force_esp32c61=False, force_esp32p4=False): + if force_esp8266 or force_esp32 or force_esp32s2 or force_esp32s3 or force_esp32s31 or force_esp32c2 or force_esp32c3 or force_esp32c5 or force_esp32c6 or force_esp32c61 or force_esp32p4: if force_esp8266: klass = esptool.ESP8266ROM elif force_esp32: @@ -402,6 +406,8 @@ def detect_chip(port, force_esp8266=False, force_esp32=False, force_esp32s2=Fals klass = esptool.ESP32S2ROM elif force_esp32s3: klass = esptool.ESP32S3ROM + elif force_esp32s31: + klass = esptool.ESP32S31ROM elif force_esp32c2: klass = esptool.ESP32C2ROM elif force_esp32c3: diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index e80ed7b..a1a7bf0 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -93,7 +93,7 @@ def colorize(text, color): DEFAULT_CONNECT_ATTEMPTS = 7 # default number of times to try connection WRITE_BLOCK_ATTEMPTS = 3 # number of times to try writing a data block -SUPPORTED_CHIPS = ['esp8266', 'esp32', 'esp32s2', 'esp32s3', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'] +SUPPORTED_CHIPS = ['esp8266', 'esp32', 'esp32s2', 'esp32s3', 'esp32s31', 'esp32c2', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'] # Handle PyInstaller's temporary directory for frozen executables if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): @@ -172,6 +172,7 @@ def _chip_to_rom_loader(chip): 'esp32': ESP32ROM, 'esp32s2': ESP32S2ROM, 'esp32s3': ESP32S3ROM, + 'esp32s31': ESP32S31ROM, 'esp32p4': ESP32P4ROM, 'esp32c3': ESP32C3ROM, 'esp32c5': ESP32C5ROM, @@ -925,7 +926,7 @@ def flash_begin(self, size, offset, begin_rom_encrypted=False): params = struct.pack('> 6) & 0x03 + + def get_minor_chip_version(self): + num_word = 3 # EFUSE_RD_MAC_SYS3_REG + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 18) & 0x0F + + def get_major_chip_version(self): + num_word = 3 # EFUSE_RD_MAC_SYS3_REG + return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 22) & 0x03 + + def get_chip_description(self): + chip_name = { + 0: "ESP32-S31", + }.get(self.get_pkg_version(), "unknown ESP32-S31") + major_rev = self.get_major_chip_version() + minor_rev = self.get_minor_chip_version() + return f"{chip_name} (revision v{major_rev}.{minor_rev})" + + def get_chip_features(self): + return [ + "Wi-Fi 6", + "BT 5.4 (LE)", + "IEEE802.15.4", + "Dual Core + LP Core", + "300MHz", + ] + + def get_crystal_freq(self): + # ESP32-S31 XTAL is fixed to 40MHz + return 40 + + def get_flash_voltage(self): + pass # not supported on ESP32-S31 + + def override_vddsdio(self, new_voltage): + raise NotImplementedInROMError( + "VDD_SDIO overrides are not supported for ESP32-S31" + ) + + def read_mac(self, mac_type="BASE_MAC"): + """Read MAC from EFUSE region""" + if mac_type != "BASE_MAC": + return None + mac0 = self.read_reg(self.MAC_EFUSE_REG) + mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC + bitstring = struct.pack(">II", mac1, mac0)[2:] + return tuple(bitstring) + + def get_flash_crypt_config(self): + return None # doesn't exist on ESP32-S31 + + def get_secure_boot_enabled(self): + return ( + self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG) + & self.EFUSE_SECURE_BOOT_EN_MASK + ) + + def get_key_block_purpose(self, key_block): + if key_block < 0 or key_block > self.EFUSE_MAX_KEY: + raise FatalError( + f"Valid key block numbers must be in range 0-{self.EFUSE_MAX_KEY}" + ) + + reg, shift = [ + (self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT), + (self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT), + (self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT), + (self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT), + (self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT), + ][key_block] + return (self.read_reg(reg) >> shift) & 0x1F + + def uses_key_manager_for_flash_encryption(self): + return bool( + ( + self.read_reg(self.EFUSE_FORCE_USE_KEY_MANAGER_KEY_REG) + >> self.EFUSE_FORCE_USE_KEY_MANAGER_KEY_SHIFT + ) + & self.FORCE_USE_KEY_MANAGER_VAL_XTS_AES_KEY + ) + + def is_flash_encryption_key_valid(self): + # Need to see either an AES-128 key or two AES-256 keys + purposes = [ + self.get_key_block_purpose(b) for b in range(self.EFUSE_MAX_KEY + 1) + ] + + if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes): + return True + + if any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any( + p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes + ): + return True + + return self.uses_key_manager_for_flash_encryption() + + def change_baud(self, baud): + ESPLoader.change_baud(self, baud) + + def _post_connect(self): + if self.uses_usb(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + + def check_spi_connection(self, spi_connection): + if not set(spi_connection).issubset(set(range(0, 61))): + raise FatalError("SPI Pin numbers must be in the range 0-60.") + if any([v for v in spi_connection if v in [33, 34]]): + print( + "GPIO pins 33 and 34 are used by USB-Serial/JTAG, " + "consider using other pins for SPI flash connection." + ) + + def hard_reset(self): + if (not self.secure_download_mode) and self.uses_usb(): + self.watchdog_reset() + else: + ESPLoader.hard_reset(self) + + class ESP32P4ROM(ESP32ROM): CHIP_NAME = "ESP32-P4" IMAGE_CHIP_ID = 18 @@ -4002,6 +4232,27 @@ def __init__(self, rom_loader): ESP32S3ROM.STUB_CLASS = ESP32S3StubLoader +class ESP32S31StubLoader(ESP32S31ROM): + """Access class for ESP32-S31 stub loader, runs on top of ROM.""" + + FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c + STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM + IS_STUB = True + + def __init__(self, rom_loader): + self.secure_download_mode = rom_loader.secure_download_mode + self._port = rom_loader._port + self._trace_enabled = rom_loader._trace_enabled + self.flush_input() # resets _slip_reader + + if rom_loader.uses_usb(): + self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK + + +ESP32S31ROM.STUB_CLASS = ESP32S31StubLoader + + class ESP32C3StubLoader(ESP32C3ROM): """ Access class for ESP32C3 stub loader, runs on top of ROM. @@ -4207,6 +4458,8 @@ def LoadFirmwareImage(chip, filename): return ESP32S2FirmwareImage(f) elif chip == "esp32s3": return ESP32S3FirmwareImage(f) + elif chip == "esp32s31": + return ESP32S31FirmwareImage(f) elif chip == 'esp32c3': return ESP32C3FirmwareImage(f) elif chip == 'esp32c5': @@ -4997,6 +5250,23 @@ def set_mmu_page_size(self, size): ESP32C5ROM.BOOTLOADER_IMAGE = ESP32C5FirmwareImage +class ESP32S31FirmwareImage(ESP32FirmwareImage): + """ESP32-S31 Firmware Image almost exactly the same as ESP32FirmwareImage""" + + ROM_LOADER = ESP32S31ROM + + def set_mmu_page_size(self, size): + if size not in [8192, 16384, 32768, 65536]: + raise FatalError( + "{} bytes is not a valid ESP32-S31 page size, " + "select from 64KB, 32KB, 16KB, 8KB.".format(size) + ) + self.IROM_ALIGN = size + + +ESP32S31ROM.BOOTLOADER_IMAGE = ESP32S31FirmwareImage + + class ESP32C6FirmwareImage(ESP32FirmwareImage): """ESP32C6 Firmware Image almost exactly the same as ESP32FirmwareImage""" @@ -5833,6 +6103,10 @@ def elf2image(args): image = ESP32S3FirmwareImage() if args.secure_pad_v2: image.secure_pad = '2' + elif args.chip == 'esp32s31': + image = ESP32S31FirmwareImage() + if args.secure_pad_v2: + image.secure_pad = '2' elif args.chip == 'esp32c3': image = ESP32C3FirmwareImage() if args.secure_pad_v2: @@ -6711,6 +6985,7 @@ def __call__(self, parser, namespace, values, option_string=None): ESP32ROM.STUB_CODE = load_stub("esp32") ESP32S2ROM.STUB_CODE = load_stub("esp32s2") ESP32S3ROM.STUB_CODE = load_stub("esp32s3") +ESP32S31ROM.STUB_CODE = load_stub("esp32s31") ESP32C3ROM.STUB_CODE = load_stub("esp32c3") ESP32C5ROM.STUB_CODE = load_stub("esp32c5") ESP32C6ROM.STUB_CODE = load_stub("esp32c6") diff --git a/esp_flasher/stubs/esp32s31.json b/esp_flasher/stubs/esp32s31.json new file mode 100644 index 0000000..591dcfd --- /dev/null +++ b/esp_flasher/stubs/esp32s31.json @@ -0,0 +1,8 @@ +{ + "entry": 788533248, + "text": "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", + "text_start": 788533248, + "data": "ECIAL0wiAS+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+OEwAvjhMAL44TAC+qGgAvthoALwIbAC8uGwAvuBsAL2IbAC+MGgAv+hsAL3YcAC+cHAAvQhoAL4ocAC9CGgAvAh0ALxIdAC8WHQAvAhsAL24dAC+CHQAv", + "data_start": 788647348, + "bss_start": 788570112 +} \ No newline at end of file From 5b28406609968ac08a6b830cd522e3a77c6a3c85 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 12:43:33 +0200 Subject: [PATCH 2/9] review fixes --- esp_flasher/__main__.py | 30 ++++++++++++++++++++++++++++-- esp_flasher/own_esptool.py | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/esp_flasher/__main__.py b/esp_flasher/__main__.py index 1a25c88..56e3811 100644 --- a/esp_flasher/__main__.py +++ b/esp_flasher/__main__.py @@ -131,7 +131,20 @@ def run_esp_flasher(argv, skip_logs=False): firmware = open(args.binary, "rb") except IOError as err: raise Esp_flasherError(f"Error opening binary: {err}") from err - chip = detect_chip(port, args.esp8266, args.esp32) + chip = detect_chip( + port, + force_esp8266=args.esp8266, + force_esp32=args.esp32, + force_esp32s2=args.esp32s2, + force_esp32s3=args.esp32s3, + force_esp32s31=args.esp32s31, + force_esp32c2=args.esp32c2, + force_esp32c3=args.esp32c3, + force_esp32c5=args.esp32c5, + force_esp32c6=args.esp32c6, + force_esp32c61=args.esp32c61, + force_esp32p4=args.esp32p4, + ) info = read_chip_info(chip) print() @@ -172,7 +185,20 @@ def run_esp_flasher(argv, skip_logs=False): ) # pylint: disable=protected-access stub_chip._port.close() - chip = detect_chip(port, args.esp8266, args.esp32) + chip = detect_chip( + port, + force_esp8266=args.esp8266, + force_esp32=args.esp32, + force_esp32s2=args.esp32s2, + force_esp32s3=args.esp32s3, + force_esp32s31=args.esp32s31, + force_esp32c2=args.esp32c2, + force_esp32c3=args.esp32c3, + force_esp32c5=args.esp32c5, + force_esp32c6=args.esp32c6, + force_esp32c61=args.esp32c61, + force_esp32p4=args.esp32p4, + ) stub_chip = chip_run_stub(chip) if flash_size is None: diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index a1a7bf0..b588e66 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -3583,7 +3583,7 @@ def check_spi_connection(self, spi_connection): def hard_reset(self): if (not self.secure_download_mode) and self.uses_usb(): - self.watchdog_reset() + self.rtc_wdt_reset() else: ESPLoader.hard_reset(self) From 47755d0fb7829fc673d4820cf6bd3e4544b4044b Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 13:42:38 +0200 Subject: [PATCH 3/9] add reg addr --- esp_flasher/own_esptool.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index b588e66..767d7b3 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -3391,6 +3391,10 @@ class ESP32S31ROM(ESP32C5ROM): RTC_CNTL_WDTCONFIG1_REG = DR_REG_LP_WDT_BASE + 0x4 RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x18 RTC_CNTL_WDT_WKEY = 0x50D83AA1 + RTC_CNTL_SWD_CONF_REG = DR_REG_LP_WDT_BASE + 0x001C # LP_WDT_SWD_CONFIG_REG + RTC_CNTL_SWD_AUTO_FEED_EN = 1 << 18 + RTC_CNTL_SWD_WPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0020 # LP_WDT_SWD_WPROTECT_REG + RTC_CNTL_SWD_WKEY = 0x50D83AA1 EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # EFUSE_RD_REPEAT_DATA0_REG @@ -3425,6 +3429,10 @@ class ESP32S31ROM(ESP32C5ROM): FLASH_ENCRYPTED_WRITE_ALIGN = 16 + # USB port IDs sourced from esp_rom_caps.h (differ from ESP32C5ROM's values) + UARTDEV_BUF_NO_USB = 4 # ESP_ROM_USB_OTG_NUM + UARTDEV_BUF_NO_USB_JTAG_SERIAL = 5 # ESP_ROM_USB_SERIAL_DEVICE_NUM + MEMORY_MAP = [ [0x00000000, 0x00010000, "PADDING"], [0x40000000, 0x54000000, "DROM"], @@ -3568,7 +3576,22 @@ def is_flash_encryption_key_valid(self): def change_baud(self, baud): ESPLoader.change_baud(self, baud) + def disable_watchdogs(self): + # UARTDEV_BUF_NO address for ESP32-S31 is not confirmed; the inherited + # ESP32C5ROM address (0x4085F514) maps to unmapped memory on S31 and + # would cause a bus fault. Disable watchdogs unconditionally instead. + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) + self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, self.RTC_CNTL_SWD_WKEY) + self.write_reg( + self.RTC_CNTL_SWD_CONF_REG, + self.read_reg(self.RTC_CNTL_SWD_CONF_REG) | self.RTC_CNTL_SWD_AUTO_FEED_EN, + ) + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) + def _post_connect(self): + super()._post_connect() # runs disable_watchdogs() if not stub-detected if self.uses_usb(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK From 4f80634b8ad9f0007200e234b9b07bddc2e9b712 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 13:52:32 +0200 Subject: [PATCH 4/9] remove not existing wdt disable for s31 --- esp_flasher/own_esptool.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index 767d7b3..f0b8be1 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -3576,22 +3576,7 @@ def is_flash_encryption_key_valid(self): def change_baud(self, baud): ESPLoader.change_baud(self, baud) - def disable_watchdogs(self): - # UARTDEV_BUF_NO address for ESP32-S31 is not confirmed; the inherited - # ESP32C5ROM address (0x4085F514) maps to unmapped memory on S31 and - # would cause a bus fault. Disable watchdogs unconditionally instead. - self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) - self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) - self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) - self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, self.RTC_CNTL_SWD_WKEY) - self.write_reg( - self.RTC_CNTL_SWD_CONF_REG, - self.read_reg(self.RTC_CNTL_SWD_CONF_REG) | self.RTC_CNTL_SWD_AUTO_FEED_EN, - ) - self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) - def _post_connect(self): - super()._post_connect() # runs disable_watchdogs() if not stub-detected if self.uses_usb(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK From 3a90327febc29a79621ce9ad29b9e352a9203928 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 14:04:17 +0200 Subject: [PATCH 5/9] wire s31 hard reset --- esp_flasher/own_esptool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index f0b8be1..7dcea09 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -3593,7 +3593,7 @@ def hard_reset(self): if (not self.secure_download_mode) and self.uses_usb(): self.rtc_wdt_reset() else: - ESPLoader.hard_reset(self) + ESP32C5ROM.hard_reset(self) class ESP32P4ROM(ESP32ROM): From 61773bd8958ceaf54d0026840da605744f9d3dc4 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 14:42:38 +0200 Subject: [PATCH 6/9] fix post connect --- esp_flasher/own_esptool.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index 7dcea09..1603ff7 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -3576,7 +3576,22 @@ def is_flash_encryption_key_valid(self): def change_baud(self, baud): ESPLoader.change_baud(self, baud) + def disable_watchdogs(self): + # UARTDEV_BUF_NO address for ESP32-S31 is not confirmed; the inherited + # ESP32C5ROM address (0x4085F514) maps to unmapped memory on S31 and + # would cause a bus fault. Disable watchdogs unconditionally instead. + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) + self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) + self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, self.RTC_CNTL_SWD_WKEY) + self.write_reg( + self.RTC_CNTL_SWD_CONF_REG, + self.read_reg(self.RTC_CNTL_SWD_CONF_REG) | self.RTC_CNTL_SWD_AUTO_FEED_EN, + ) + self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) + def _post_connect(self): + super()._post_connect() # calls disable_watchdogs() above if not stub-detected if self.uses_usb(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK @@ -3954,11 +3969,7 @@ def power_on_flash(self): class ESP32P4RC1ROM(ESP32P4ROM): """ESP32-P4 RC1 ROM class for revisions < 3.0""" - - def _post_connect(self): - # Override parent's _post_connect to prevent switching stub code - # This class already uses the correct RC1 stub code - pass + pass class ESP32H2ROM(ESP32C6ROM): From 67e378b3b8b652aceedcbbf1bdc24dbab01dd710 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 15:14:27 +0200 Subject: [PATCH 7/9] use PID/VID to detect USB/OTG mode --- esp_flasher/own_esptool.py | 223 ++++++++----------------------------- 1 file changed, 48 insertions(+), 175 deletions(-) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index 1603ff7..1954571 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -397,7 +397,8 @@ class ESPLoader(object): # Response to ESP_SYNC might indicate that flasher stub is running instead of the ROM bootloader sync_stub_detected = False - # Device PIDs + # Device VIDs/PIDs + ESPRESSIF_VID = 0x303A USB_JTAG_SERIAL_PID = 0x1001 # Chip IDs that are no longer supported by esptool @@ -696,28 +697,47 @@ def _setRTS(self, state): # request is sent with the updated RTS state and the same DTR state self._port.setDTR(self._port.dtr) - def _get_pid(self): + def get_usb_vid_pid(self): + if getattr(self, '_usb_vid_pid_cache', None) is not None: + return self._usb_vid_pid_cache if list_ports is None: - print("\nListing all serial ports is currently not available. Can't get device PID.") - return + self._usb_vid_pid_cache = (None, None) + return self._usb_vid_pid_cache active_port = self._port.port # Pyserial only identifies regular ports, URL handlers are not supported if not active_port.lower().startswith(("com", "/dev/")): - print("\nDevice PID identification is only supported on COM and /dev/ serial ports.") - return + self._usb_vid_pid_cache = (None, None) + return self._usb_vid_pid_cache # Return the real path if the active port is a symlink if active_port.startswith("/dev/") and os.path.islink(active_port): active_port = os.path.realpath(active_port) # The "cu" (call-up) device has to be used for outgoing communication on MacOS if sys.platform == "darwin" and "tty" in active_port: - active_port = [active_port, active_port.replace("tty", "cu")] + active_ports = [active_port, active_port.replace("tty", "cu")] + else: + active_ports = [active_port] ports = list_ports.comports() for p in ports: - if p.device in active_port: - return p.pid - print("\nFailed to get PID of a device on {}, using standard reset sequence.".format(active_port)) + if p.device in active_ports: + self._usb_vid_pid_cache = (p.vid, p.pid) + return self._usb_vid_pid_cache + print("\nFailed to get VID/PID of a device on {}, using standard reset sequence.".format(active_port)) + self._usb_vid_pid_cache = (None, None) + return self._usb_vid_pid_cache + + def uses_usb_jtag_serial(self): + """True if the host sees this port as Espressif USB Serial/JTAG (VID/PID match).""" + if self.secure_download_mode: + return False + return self.get_usb_vid_pid() == (self.ESPRESSIF_VID, self.USB_JTAG_SERIAL_PID) + + def uses_usb_otg(self): + """True if the host sees this port as Espressif USB-OTG (VID/PID match).""" + if self.secure_download_mode: + return False + return self.get_usb_vid_pid() == (self.ESPRESSIF_VID, self.IMAGE_CHIP_ID) def bootloader_reset(self, usb_jtag_serial=False, extra_delay=False): """ Issue a reset-to-bootloader, with USB-JTAG-Serial custom reset sequence option @@ -815,7 +835,7 @@ def connect(self, mode='default_reset', attempts=DEFAULT_CONNECT_ATTEMPTS, detec sys.stdout.flush() last_error = None - usb_jtag_serial = (mode == 'usb_reset') or (self._get_pid() == self.USB_JTAG_SERIAL_PID) + usb_jtag_serial = (mode == 'usb_reset') or self.uses_usb_jtag_serial() try: for _, extra_delay in zip(range(attempts) if attempts > 0 else itertools.count(), itertools.cycle((False, True))): @@ -2017,9 +2037,6 @@ class ESP32S2ROM(ESP32ROM): PURPOSE_VAL_XTS_AES256_KEY_2 = 3 PURPOSE_VAL_XTS_AES128_KEY = 4 - UARTDEV_BUF_NO = 0x3FFFFD14 # Variable in ROM .bss which indicates the port in use - UARTDEV_BUF_NO_USB = 2 # Value of the above indicating that USB-OTG is in use - USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used GPIO_STRAP_REG = 0x3F404038 @@ -2192,16 +2209,8 @@ def is_flash_encryption_key_valid(self): p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes ) - def uses_usb(self, _cache=[]): - if self.secure_download_mode: - return False # can't detect native USB in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB) - return _cache[0] - def _post_connect(self): - if self.uses_usb(): + if self.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK def rtc_wdt_reset(self): @@ -2214,7 +2223,7 @@ def rtc_wdt_reset(self): self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock def hard_reset(self): - if self.uses_usb(): + if self.uses_usb_otg(): # Check the strapping register to see if we can perform RTC WDT reset strap_reg = self.read_reg(self.GPIO_STRAP_REG) force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) @@ -2227,7 +2236,7 @@ def hard_reset(self): print('Hard resetting via RTS pin...') self._setRTS(True) # EN->LOW - if self.uses_usb(): + if self.uses_usb_otg(): # Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions time.sleep(0.2) self._setRTS(False) @@ -2304,10 +2313,6 @@ class ESP32S3ROM(ESP32ROM): PURPOSE_VAL_XTS_AES256_KEY_2 = 3 PURPOSE_VAL_XTS_AES128_KEY = 4 - UARTDEV_BUF_NO = 0x3FCEF14C # Variable in ROM .bss which indicates the port in use - UARTDEV_BUF_NO_USB = 3 # The above var when USB-OTG is used - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4 # The above var when USB-JTAG/Serial is used - RTCCNTL_BASE_REG = 0x60008000 RTC_CNTL_SWD_CONF_REG = RTCCNTL_BASE_REG + 0x00B4 RTC_CNTL_SWD_AUTO_FEED_EN = 1 << 31 @@ -2487,27 +2492,8 @@ def read_mac(self): except TypeError: # Python 3, bitstring elements are already bytes return tuple(bitstring) - def uses_usb(self, _cache=[]): - if self.secure_download_mode: - return False # can't detect native USB in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB) - return _cache[0] - - def uses_usb_jtag_serial(self, _cache=[]): - """ - Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used - """ - if self.secure_download_mode: - return False # can't detect USB-JTAG/Serial in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL) - return _cache[0] - def _post_connect(self): - if self.uses_usb(): + if self.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK def rtc_wdt_reset(self): @@ -2529,7 +2515,7 @@ def hard_reset(self): except Exception: # Skip if response was not valid and proceed to reset; e.g. when monitoring while resetting pass - uses_usb_otg = self.uses_usb() + uses_usb_otg = self.uses_usb_otg() if uses_usb_otg or self.uses_usb_jtag_serial(): # Check the strapping register to see if we can perform RTC WDT reset strap_reg = self.read_reg(self.GPIO_STRAP_REG) @@ -2543,7 +2529,7 @@ def hard_reset(self): print('Hard resetting via RTS pin...') self._setRTS(True) # EN->LOW - if self.uses_usb(): + if self.uses_usb_otg(): # Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions time.sleep(0.2) self._setRTS(False) @@ -2616,9 +2602,6 @@ class ESP32C3ROM(ESP32ROM): FLASH_ENCRYPTED_WRITE_ALIGN = 16 - UARTDEV_BUF_NO = 0x3FCDF07C # Variable in ROM .bss which indicates the port in use - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used - RTCCNTL_BASE_REG = 0x60008000 RTC_CNTL_SWD_CONF_REG = RTCCNTL_BASE_REG + 0x00AC RTC_CNTL_SWD_AUTO_FEED_EN = 1 << 31 @@ -2767,17 +2750,6 @@ def is_flash_encryption_key_valid(self): return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes) - def uses_usb_jtag_serial(self, _cache=[]): - """ - Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used - """ - if self.secure_download_mode: - return False # can't detect USB-JTAG/Serial in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL) - return _cache[0] - def disable_watchdogs(self): # When USB-JTAG/Serial is used, the RTC WDT and SWD watchdog are not reset # and can then reset the board during flashing. Disable or autofeed them. @@ -2876,9 +2848,6 @@ class ESP32C6ROM(ESP32C3ROM): FLASH_ENCRYPTED_WRITE_ALIGN = 16 - UARTDEV_BUF_NO = 0x4087F580 # Variable in ROM .bss which indicates the port in use - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used - DR_REG_LP_WDT_BASE = 0x600B1C00 RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG RTC_CNTL_WDTCONFIG1_REG = DR_REG_LP_WDT_BASE + 0x0004 # LP_WDT_RWDT_CONFIG1_REG @@ -3071,19 +3040,6 @@ class ESP32C61ROM(ESP32C6ROM): EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x034 EFUSE_SECURE_BOOT_EN_MASK = 1 << 26 - # Variable in ROM .bss which indicates the port in use - @property - def UARTDEV_BUF_NO(self): - """Variable .bss.UartDev.buff_uart_no in ROM .bss - which indicates the port in use. - """ - return 0x4084F5EC if self.get_chip_revision() <= 2 else 0x4084F5E4 - - @property - def UARTDEV_BUF_NO_USB_JTAG_SERIAL(self): - """The above var when USB-JTAG/Serial is used.""" - return 3 if self.get_chip_revision() <= 2 else 4 - FLASH_FREQUENCY = { "80m": 0xF, "40m": 0x0, @@ -3203,10 +3159,6 @@ class ESP32C5ROM(ESP32C6ROM): PCR_SYSCLK_XTAL_FREQ_V = 0x7F << 24 PCR_SYSCLK_XTAL_FREQ_S = 24 - UARTDEV_BUF_NO = 0x4085F514 # Variable in ROM .bss which indicates the port in use - UARTDEV_BUF_NO_USB = 3 # The above var when USB-OTG is used - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4 # The above var when USB-JTAG/Serial is used - FLASH_FREQUENCY = { "80m": 0xF, "40m": 0x0, @@ -3284,25 +3236,6 @@ def get_crystal_freq_rom_expect(self): self.read_reg(self.PCR_SYSCLK_CONF_REG) & self.PCR_SYSCLK_XTAL_FREQ_V ) >> self.PCR_SYSCLK_XTAL_FREQ_S - def uses_usb(self, _cache=[]): - if self.secure_download_mode: - return False # can't detect native USB in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB) - return _cache[0] - - def uses_usb_jtag_serial(self, _cache=[]): - """ - Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used - """ - if self.secure_download_mode: - return False # can't detect USB-JTAG/Serial in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL) - return _cache[0] - def disable_watchdogs(self): # When USB-JTAG/Serial is used, the RTC WDT and SWD watchdog are not reset # and can then reset the board during flashing. Disable or autofeed them. @@ -3429,10 +3362,6 @@ class ESP32S31ROM(ESP32C5ROM): FLASH_ENCRYPTED_WRITE_ALIGN = 16 - # USB port IDs sourced from esp_rom_caps.h (differ from ESP32C5ROM's values) - UARTDEV_BUF_NO_USB = 4 # ESP_ROM_USB_OTG_NUM - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 5 # ESP_ROM_USB_SERIAL_DEVICE_NUM - MEMORY_MAP = [ [0x00000000, 0x00010000, "PADDING"], [0x40000000, 0x54000000, "DROM"], @@ -3576,23 +3505,9 @@ def is_flash_encryption_key_valid(self): def change_baud(self, baud): ESPLoader.change_baud(self, baud) - def disable_watchdogs(self): - # UARTDEV_BUF_NO address for ESP32-S31 is not confirmed; the inherited - # ESP32C5ROM address (0x4085F514) maps to unmapped memory on S31 and - # would cause a bus fault. Disable watchdogs unconditionally instead. - self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) - self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0) - self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) - self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, self.RTC_CNTL_SWD_WKEY) - self.write_reg( - self.RTC_CNTL_SWD_CONF_REG, - self.read_reg(self.RTC_CNTL_SWD_CONF_REG) | self.RTC_CNTL_SWD_AUTO_FEED_EN, - ) - self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) - def _post_connect(self): - super()._post_connect() # calls disable_watchdogs() above if not stub-detected - if self.uses_usb(): + super()._post_connect() # calls C5ROM's disable_watchdogs() via MRO if not stub-detected + if self.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK def check_spi_connection(self, spi_connection): @@ -3605,7 +3520,7 @@ def check_spi_connection(self, spi_connection): ) def hard_reset(self): - if (not self.secure_download_mode) and self.uses_usb(): + if (not self.secure_download_mode) and self.uses_usb_otg(): self.rtc_wdt_reset() else: ESP32C5ROM.hard_reset(self) @@ -3695,22 +3610,6 @@ class ESP32P4ROM(ESP32ROM): PMU_0P1A_FORCE_TIEH_SEL_0 = 1 << 7 PMU_DATE_REG = DR_REG_PMU_BASE + 0x3FC - @property - def UARTDEV_BUF_NO(self): - """Variable .bss.UartDev.buff_uart_no in ROM .bss - which indicates the port in use. - """ - BUF_UART_NO_OFFSET = 24 - - BSS_UART_DEV_ADDR = 0x4FF3FEB0 if self.get_chip_full_revision() < 300 else 0x4FFBFEB0 - return BSS_UART_DEV_ADDR + BUF_UART_NO_OFFSET - - # The value from UARTDEV_BUF_NO when USB-OTG is used - UARTDEV_BUF_NO_USB_OTG = 5 - - # The value from UARTDEV_BUF_NO when USB-JTAG/Serial is used - UARTDEV_BUF_NO_USB_JTAG_SERIAL = 6 - MEMORY_MAP = [ [0x00000000, 0x00010000, "PADDING"], [0x40000000, 0x4C000000, "DROM"], @@ -3786,18 +3685,9 @@ def get_chip_revision(self): def get_chip_full_revision(self): return self.get_major_chip_version() * 100 + self.get_minor_chip_version() - def uses_usb(self, _cache=[]): - """Check if USB-OTG or USB-JTAG/Serial is being used""" - if self.secure_download_mode: - return False # can't detect native USB in secure download mode - if not _cache: - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - _cache.append(buf_no in [self.UARTDEV_BUF_NO_USB_OTG, self.UARTDEV_BUF_NO_USB_JTAG_SERIAL]) - return _cache[0] - def _post_connect(self): - # Set USB RAM block if USB is being used - if self.uses_usb(): + # Set USB RAM block if USB-OTG is being used + if self.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK if not self.secure_download_mode: @@ -3825,13 +3715,6 @@ def disable_watchdogs(self): ) self.write_reg(self.RTC_CNTL_SWD_WPROTECT_REG, 0) - def uses_usb_jtag_serial(self): - """Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used""" - if self.secure_download_mode: - return False - buf_no = self.read_reg(self.UARTDEV_BUF_NO) & 0xff - return buf_no == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL - def get_crystal_freq(self): # ESP32P4 XTAL is fixed to 40MHz return 40 @@ -3909,7 +3792,7 @@ def rtc_wdt_reset(self): time.sleep(0.5) # wait for reset to take effect def hard_reset(self): - if self.uses_usb(): + if self.uses_usb_otg(): self.rtc_wdt_reset() else: print('Hard resetting via RTS pin...') @@ -4219,7 +4102,7 @@ def __init__(self, rom_loader): self._trace_enabled = rom_loader._trace_enabled self.flush_input() # resets _slip_reader - if rom_loader.uses_usb(): + if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK @@ -4243,7 +4126,7 @@ def __init__(self, rom_loader): self._trace_enabled = rom_loader._trace_enabled self.flush_input() # resets _slip_reader - if rom_loader.uses_usb(): + if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK @@ -4264,7 +4147,7 @@ def __init__(self, rom_loader): self._trace_enabled = rom_loader._trace_enabled self.flush_input() # resets _slip_reader - if rom_loader.uses_usb(): + if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK @@ -4372,13 +4255,8 @@ def __init__(self, rom_loader): self._trace_enabled = rom_loader._trace_enabled self.flush_input() # resets _slip_reader - # Cache USB status from ROM loader - self._uses_usb = rom_loader.uses_usb() - if self._uses_usb: + if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK - - def uses_usb(self): - return self._uses_usb ESP32P4ROM.STUB_CLASS = ESP32P4StubLoader @@ -4397,13 +4275,8 @@ def __init__(self, rom_loader): self._trace_enabled = rom_loader._trace_enabled self.flush_input() # resets _slip_reader - # Cache USB status from ROM loader - self._uses_usb = rom_loader.uses_usb() - if self._uses_usb: + if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK - - def uses_usb(self): - return self._uses_usb ESP32P4RC1ROM.STUB_CLASS = ESP32P4RC1StubLoader From 96deb07da7b37f9d778c77f1016897a1ebbb64d7 Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 15:46:03 +0200 Subject: [PATCH 8/9] Clamp P4 OTG stub flash writes to the USB block size --- esp_flasher/own_esptool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index 1954571..0ff75cb 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -4257,6 +4257,7 @@ def __init__(self, rom_loader): if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK ESP32P4ROM.STUB_CLASS = ESP32P4StubLoader @@ -4277,6 +4278,7 @@ def __init__(self, rom_loader): if rom_loader.uses_usb_otg(): self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK + self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK ESP32P4RC1ROM.STUB_CLASS = ESP32P4RC1StubLoader From f659cd22e6daeb5d9aca0ca23e381d801591c0bd Mon Sep 17 00:00:00 2001 From: Jason2866 Date: Wed, 20 May 2026 15:58:47 +0200 Subject: [PATCH 9/9] align not ssupported flash voltage --- esp_flasher/own_esptool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esp_flasher/own_esptool.py b/esp_flasher/own_esptool.py index 0ff75cb..03c6cae 100644 --- a/esp_flasher/own_esptool.py +++ b/esp_flasher/own_esptool.py @@ -2701,7 +2701,7 @@ def get_crystal_freq(self): return 40 def get_flash_voltage(self): - pass # not supported on ESP32-C3 + raise NotSupportedError(self, "Reading flash voltage") def override_vddsdio(self, new_voltage): raise NotImplementedInROMError( @@ -3437,7 +3437,7 @@ def get_crystal_freq(self): return 40 def get_flash_voltage(self): - pass # not supported on ESP32-S31 + raise NotSupportedError(self, "Reading flash voltage") def override_vddsdio(self, new_voltage): raise NotImplementedInROMError(