mirror of
https://github.com/espressif/esptool.git
synced 2025-10-19 20:13:00 +08:00
feat(watchdog_reset): Add a new watchdog_reset option working even in USB modes
This commit is contained in:
@@ -35,6 +35,7 @@ The ``--after`` argument allows you to specify whether the chip should be reset
|
||||
:esp8266: * ``--after soft_reset`` runs the user firmware, but any subsequent reset will return to the serial bootloader. This was the reset behaviour in esptool v1.x.
|
||||
* ``--after no_reset`` leaves the chip in the serial bootloader, no reset is performed.
|
||||
* ``--after no_reset_stub`` leaves the chip in the stub bootloader, no reset is performed.
|
||||
:not esp8266 and not esp32 and not esp32h2 and not esp32c6: * ``--after watchdog_reset`` hard-resets the chip by triggering an internal watchdog reset. This is useful when the RTS control line is not available, especially in the USB-OTG and USB-Serial/JTAG modes. Use this if a chip is getting stuck in download mode when using the default reset method in USB-Serial/JTAG mode. Using this may cause the port to re-enumerate on Linux (e.g. ``/dev/ttyACM0`` -> ``/dev/ttyACM1``).
|
||||
|
||||
|
||||
Connect Loop
|
||||
|
@@ -151,7 +151,13 @@ def main(argv=None, esp=None):
|
||||
"--after",
|
||||
"-a",
|
||||
help="What to do after esptool.py is finished",
|
||||
choices=["hard_reset", "soft_reset", "no_reset", "no_reset_stub"],
|
||||
choices=[
|
||||
"hard_reset",
|
||||
"soft_reset",
|
||||
"no_reset",
|
||||
"no_reset_stub",
|
||||
"watchdog_reset",
|
||||
],
|
||||
default=os.environ.get("ESPTOOL_AFTER", "hard_reset"),
|
||||
)
|
||||
|
||||
@@ -1065,6 +1071,15 @@ def main(argv=None, esp=None):
|
||||
esp.soft_reset(False)
|
||||
elif args.after == "no_reset_stub":
|
||||
print("Staying in flasher stub.")
|
||||
elif args.after == "watchdog_reset":
|
||||
if esp.secure_download_mode:
|
||||
print(
|
||||
"WARNING: Watchdog hard reset is not supported in Secure Download "
|
||||
"Mode, attempting classic hard reset instead."
|
||||
)
|
||||
esp.hard_reset()
|
||||
else:
|
||||
esp.watchdog_reset()
|
||||
else: # args.after == 'no_reset'
|
||||
print("Staying in bootloader.")
|
||||
if esp.IS_STUB:
|
||||
|
@@ -1581,6 +1581,13 @@ class ESPLoader(object):
|
||||
# in the stub loader
|
||||
self.command(self.ESP_RUN_USER_CODE, wait_response=False)
|
||||
|
||||
def watchdog_reset(self):
|
||||
print(
|
||||
f"WARNING: Watchdog hard reset is not supported on {self.CHIP_NAME}, "
|
||||
"attempting classic hard reset instead."
|
||||
)
|
||||
self.hard_reset()
|
||||
|
||||
|
||||
def slip_reader(port, trace_function):
|
||||
"""Generator to read SLIP packets from a serial port.
|
||||
|
@@ -133,9 +133,6 @@ class ESP32C2ROM(ESP32C3ROM):
|
||||
self.stub_is_disabled = True
|
||||
self.IS_STUB = False
|
||||
|
||||
def hard_reset(self):
|
||||
ESPLoader.hard_reset(self)
|
||||
|
||||
""" Try to read (encryption key) and check if it is valid """
|
||||
|
||||
def is_flash_encryption_key_valid(self):
|
||||
|
@@ -253,21 +253,15 @@ class ESP32C3ROM(ESP32ROM):
|
||||
if not self.sync_stub_detected: # Don't run if stub is reused
|
||||
self.disable_watchdogs()
|
||||
|
||||
def hard_reset(self):
|
||||
if self.uses_usb_jtag_serial():
|
||||
self.rtc_wdt_reset()
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
else:
|
||||
ESPLoader.hard_reset(self)
|
||||
|
||||
def rtc_wdt_reset(self):
|
||||
print("Hard resetting with RTC WDT...")
|
||||
def watchdog_reset(self):
|
||||
print("Hard resetting with a watchdog...")
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 2000) # set WDT timeout
|
||||
self.write_reg(
|
||||
self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2
|
||||
) # enable WDT
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
if not set(spi_connection).issubset(set(range(0, 22))):
|
||||
|
@@ -6,6 +6,7 @@ import struct
|
||||
import time
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError
|
||||
@@ -162,6 +163,10 @@ class ESP32C5ROM(ESP32C6ROM):
|
||||
"consider using other pins for SPI flash connection."
|
||||
)
|
||||
|
||||
def watchdog_reset(self):
|
||||
# Watchdog reset disabled in parent (ESP32-C6) ROM, re-enable it
|
||||
ESP32C3ROM.watchdog_reset(self)
|
||||
|
||||
|
||||
class ESP32C5StubLoader(ESP32C5ROM):
|
||||
"""Access class for ESP32C5 stub loader, runs on top of ROM.
|
||||
|
@@ -192,10 +192,10 @@ class ESP32C6ROM(ESP32C3ROM):
|
||||
"consider using other pins for SPI flash connection."
|
||||
)
|
||||
|
||||
def hard_reset(self):
|
||||
def watchdog_reset(self):
|
||||
# Bug in the USB-Serial/JTAG controller can cause the port to disappear
|
||||
# if the chip is reset with RTC WDT, do a classic reset
|
||||
ESPLoader.hard_reset(self)
|
||||
# if watchdog reset happens, disable it on ESP32-C6
|
||||
ESPLoader.watchdog_reset(self)
|
||||
|
||||
|
||||
class ESP32C6StubLoader(ESP32C6ROM):
|
||||
|
@@ -5,6 +5,7 @@
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from .esp32c3 import ESP32C3ROM
|
||||
from .esp32c6 import ESP32C6ROM
|
||||
|
||||
|
||||
@@ -118,6 +119,10 @@ class ESP32C61ROM(ESP32C6ROM):
|
||||
}
|
||||
return macs.get(mac_type, None)
|
||||
|
||||
def watchdog_reset(self):
|
||||
# Watchdog reset disabled in parent (ESP32-C6) ROM, re-enable it
|
||||
ESP32C3ROM.watchdog_reset(self)
|
||||
|
||||
|
||||
class ESP32C61StubLoader(ESP32C61ROM):
|
||||
"""Access class for ESP32C61 stub loader, runs on top of ROM.
|
||||
|
@@ -76,9 +76,9 @@ class ESP32H2ROM(ESP32C6ROM):
|
||||
# ESP32H2 XTAL is fixed to 32MHz
|
||||
return 32
|
||||
|
||||
def hard_reset(self):
|
||||
# RTC WDT reset not available, do a classic reset
|
||||
ESPLoader.hard_reset(self)
|
||||
# Watchdog reset is not supported on ESP32-H2
|
||||
def watchdog_reset(self):
|
||||
ESPLoader.watchdog_reset(self)
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
if not set(spi_connection).issubset(set(range(0, 28))):
|
||||
|
@@ -5,7 +5,6 @@
|
||||
|
||||
|
||||
from .esp32h2 import ESP32H2ROM
|
||||
from ..loader import ESPLoader
|
||||
from ..util import FatalError
|
||||
|
||||
|
||||
@@ -46,10 +45,6 @@ class ESP32H21ROM(ESP32H2ROM):
|
||||
# ESP32H21 XTAL is fixed to 32MHz
|
||||
return 32
|
||||
|
||||
def hard_reset(self):
|
||||
# RTC WDT reset not available, do a classic reset
|
||||
ESPLoader.hard_reset(self)
|
||||
|
||||
def check_spi_connection(self, spi_connection):
|
||||
if not set(spi_connection).issubset(set(range(0, 28))):
|
||||
raise FatalError("SPI Pin numbers must be in the range 0-27.")
|
||||
|
@@ -262,19 +262,19 @@ class ESP32P4ROM(ESP32ROM):
|
||||
"consider using other pins for SPI flash connection."
|
||||
)
|
||||
|
||||
def rtc_wdt_reset(self):
|
||||
print("Hard resetting with RTC WDT...")
|
||||
def watchdog_reset(self):
|
||||
print("Hard resetting with a watchdog...")
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 2000) # set WDT timeout
|
||||
self.write_reg(
|
||||
self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2
|
||||
) # enable WDT
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
|
||||
def hard_reset(self):
|
||||
if self.uses_usb_jtag_serial() or self.uses_usb_otg():
|
||||
self.rtc_wdt_reset()
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
if self.uses_usb_otg():
|
||||
self.watchdog_reset()
|
||||
else:
|
||||
ESPLoader.hard_reset(self)
|
||||
|
||||
|
@@ -288,27 +288,27 @@ class ESP32S2ROM(ESP32ROM):
|
||||
if self.uses_usb_otg():
|
||||
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
|
||||
|
||||
def rtc_wdt_reset(self):
|
||||
print("Hard resetting with RTC WDT...")
|
||||
def watchdog_reset(self):
|
||||
print("Hard resetting with a watchdog...")
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 2000) # set WDT timeout
|
||||
self.write_reg(
|
||||
self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2
|
||||
) # enable WDT
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
|
||||
def hard_reset(self):
|
||||
uses_usb_otg = self.uses_usb_otg()
|
||||
if uses_usb_otg:
|
||||
# Check the strapping register to see if we can perform RTC WDT reset
|
||||
# Check the strapping register to see if we can perform a watchdog reset
|
||||
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
|
||||
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
|
||||
if (
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 # GPIO0 low
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
self.rtc_wdt_reset()
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
self.watchdog_reset()
|
||||
return
|
||||
|
||||
ESPLoader.hard_reset(self, uses_usb_otg)
|
||||
|
@@ -352,14 +352,15 @@ class ESP32S3ROM(ESP32ROM):
|
||||
if not self.sync_stub_detected: # Don't run if stub is reused
|
||||
self.disable_watchdogs()
|
||||
|
||||
def rtc_wdt_reset(self):
|
||||
print("Hard resetting with RTC WDT...")
|
||||
def watchdog_reset(self):
|
||||
print("Hard resetting with a watchdog...")
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY) # unlock
|
||||
self.write_reg(self.RTC_CNTL_WDTCONFIG1_REG, 2000) # set WDT timeout
|
||||
self.write_reg(
|
||||
self.RTC_CNTL_WDTCONFIG0_REG, (1 << 31) | (5 << 28) | (1 << 8) | 2
|
||||
) # enable WDT
|
||||
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0) # lock
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
|
||||
def hard_reset(self):
|
||||
try:
|
||||
@@ -372,16 +373,15 @@ class ESP32S3ROM(ESP32ROM):
|
||||
# Skip if response was not valid and proceed to reset; e.g. when monitoring while resetting
|
||||
pass
|
||||
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
|
||||
if uses_usb_otg:
|
||||
# Check the strapping register to see if we can perform a watchdog reset
|
||||
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
|
||||
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
|
||||
if (
|
||||
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 # GPIO0 low
|
||||
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
|
||||
):
|
||||
self.rtc_wdt_reset()
|
||||
sleep(0.5) # wait for reset to take effect
|
||||
self.watchdog_reset()
|
||||
return
|
||||
|
||||
ESPLoader.hard_reset(self, uses_usb_otg)
|
||||
|
@@ -1534,29 +1534,24 @@ class TestMakeImage(EsptoolTestCase):
|
||||
os.remove("test0x00000.bin")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
arg_chip in ["esp8266", "esp32", "esp32h2"], reason="Not supported on this chip"
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
"ESPTOOL_TEST_USB_OTG" in os.environ or arg_preload_port is not False,
|
||||
reason="Boot mode strapping pin pulled constantly low, can't reset out of bootloader",
|
||||
)
|
||||
class TestReset(EsptoolTestCase):
|
||||
def test_rtc_wdt_reset(self):
|
||||
# Erase the bootloader to get "invalid header" output + test RTC WDT reset
|
||||
res = self.run_esptool("--after no_reset erase_region 0x0 0x4000")
|
||||
assert "Erase completed" in res
|
||||
try:
|
||||
esp = esptool.get_default_connected_device(
|
||||
[arg_port], arg_port, 10, 115200, arg_chip
|
||||
def test_watchdog_reset(self):
|
||||
# Erase the bootloader to get "invalid header" output + test watchdog reset
|
||||
res = self.run_esptool("--after watchdog_reset erase_region 0x0 0x4000")
|
||||
if arg_chip in ["esp8266", "esp32", "esp32h2", "esp32c6"]:
|
||||
assert "Watchdog hard reset is not supported" in res
|
||||
assert "Hard resetting via RTS pin..." in res
|
||||
else:
|
||||
assert "Hard resetting with a watchdog..." in res
|
||||
# If there is no output, the chip did not reset
|
||||
# Mangled bytes are for C2 26 MHz when the baudrate doesn't match
|
||||
self.verify_output(
|
||||
[b"invalid header", b"\x02b\xe2n\x9e\xe0p\x12n\x9c\x0cn"]
|
||||
)
|
||||
esp.rtc_wdt_reset()
|
||||
finally:
|
||||
esp._port.close()
|
||||
sleep(0.2) # Give the chip time to reset
|
||||
# If there is no output, the chip did not reset
|
||||
# Mangled bytes are for C2 26 MHz when the baudrate doesn't match
|
||||
self.verify_output([b"invalid header", b"\x02b\xe2n\x9e\xe0p\x12n\x9c\x0cn"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(arg_chip != "esp32", reason="Don't need to test multiple times")
|
||||
|
Reference in New Issue
Block a user