mirror of
https://github.com/espressif/esptool.git
synced 2025-10-19 20:13:00 +08:00
feat(bootloader reset): Allow custom reset strategy setting with a config file
This commit is contained in:
@@ -107,3 +107,42 @@ Complete list configurable options:
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| reset_delay | Time to wait before the boot pin is released after reset | 0.05 s |
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| custom_reset_sequence | Custom reset sequence for resetting into the bootloader | |
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
|
||||
Custom Reset Sequence
|
||||
---------------------
|
||||
|
||||
The ``custom_reset_sequence`` configuration option allows you to define a reset sequence which will get
|
||||
used when an :ref:`automatic reset into the serial bootloader <automatic-bootloader>` is performed.
|
||||
|
||||
The sequence is defined with a string in the following format:
|
||||
|
||||
- Consists of individual commands divided by ``|`` (e.g. ``R0|D1|W0.5``).
|
||||
- Commands (e.g. ``R0``) are defined by a code (``R``) and an argument (``0``).
|
||||
|
||||
+------+-----------------------------------------------------------+-----------------+
|
||||
| Code | Action | Argument |
|
||||
+======+===========================================================+=================+
|
||||
| D | Set DTR control line | ``1``/``0`` |
|
||||
+------+-----------------------------------------------------------+-----------------+
|
||||
| R | Set RTS control line | ``1``/``0`` |
|
||||
+------+-----------------------------------------------------------+-----------------+
|
||||
| U | Set DTR and RTS control lines at the same time | ``0,0``/``0,1`` |
|
||||
| | (Unix-like systems only) | ``1,0``/``1,1`` |
|
||||
+------+-----------------------------------------------------------+-----------------+
|
||||
| W | Wait for ``N`` seconds (where ``N`` is a float) | ``N`` |
|
||||
+------+-----------------------------------------------------------+-----------------+
|
||||
|
||||
|
||||
For example: ``D0|R1|W0.1|D1|R0|W0.5|D0`` represents the following classic reset sequence:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
_setDTR(False) # IO0=HIGH
|
||||
_setRTS(True) # EN=LOW, chip in reset
|
||||
time.sleep(0.1)
|
||||
_setDTR(True) # IO0=LOW
|
||||
_setRTS(False) # EN=HIGH, chip out of reset
|
||||
time.sleep(0.05)
|
||||
_setDTR(False) # IO0=HIGH, done
|
||||
|
@@ -19,6 +19,7 @@ CONFIG_OPTIONS = [
|
||||
"connect_attempts",
|
||||
"write_block_attempts",
|
||||
"reset_delay",
|
||||
"custom_reset_sequence",
|
||||
]
|
||||
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import time
|
||||
from .config import load_config_file
|
||||
from .reset import (
|
||||
ClassicReset,
|
||||
CustomReset,
|
||||
DEFAULT_RESET_DELAY,
|
||||
HardReset,
|
||||
USBJTAGSerialReset,
|
||||
@@ -570,7 +571,9 @@ class ESPLoader(object):
|
||||
used ESP chip, external settings, and environment variables.
|
||||
Returns a tuple of one or more reset strategies to be tried sequentially.
|
||||
"""
|
||||
# TODO: If config file defines custom reset sequence, parse it and return it
|
||||
cfg_custom_reset_sequence = cfg.get("custom_reset_sequence")
|
||||
if cfg_custom_reset_sequence is not None:
|
||||
return (CustomReset(self._port, cfg_custom_reset_sequence),)
|
||||
|
||||
cfg_reset_delay = cfg.getfloat("reset_delay")
|
||||
if cfg_reset_delay is not None:
|
||||
|
@@ -7,6 +7,8 @@ import os
|
||||
import struct
|
||||
import time
|
||||
|
||||
from .util import FatalError
|
||||
|
||||
# Used for resetting into bootloader on Unix-like systems
|
||||
if os.name != "nt":
|
||||
import fcntl
|
||||
@@ -130,3 +132,47 @@ class HardReset(ResetStrategy):
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
self._setRTS(False)
|
||||
|
||||
|
||||
class CustomReset(ResetStrategy):
|
||||
"""
|
||||
Custom reset strategy defined with a string.
|
||||
|
||||
CustomReset object is created as "rst = CustomReset(port, seq_str)"
|
||||
and can be later executed simply with "rst()"
|
||||
|
||||
The seq_str input string consists of individual commands divided by "|".
|
||||
Commands (e.g. R0) are defined by a code (R) and an argument (0).
|
||||
|
||||
The commands are:
|
||||
D: setDTR - 1=True / 0=False
|
||||
R: setRTS - 1=True / 0=False
|
||||
U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1
|
||||
W: Wait (time delay) - positive float number
|
||||
|
||||
e.g.
|
||||
"D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy
|
||||
"U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy
|
||||
"""
|
||||
|
||||
format_dict = {
|
||||
"D": "self.port.setDTR({})",
|
||||
"R": "self.port.setRTS({})",
|
||||
"W": "time.sleep({})",
|
||||
"U": "self._setDTRandRTS({})",
|
||||
}
|
||||
|
||||
def __call__(self):
|
||||
exec(self.constructed_strategy)
|
||||
|
||||
def __init__(self, port, seq_str):
|
||||
super().__init__(port)
|
||||
self.constructed_strategy = self._parse_string_to_seq(seq_str)
|
||||
|
||||
def _parse_string_to_seq(self, seq_str):
|
||||
try:
|
||||
cmds = seq_str.split("|")
|
||||
fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
|
||||
except Exception as e:
|
||||
raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}')
|
||||
return "\n".join(fn_calls_list)
|
||||
|
@@ -1124,3 +1124,28 @@ class TestConfigFile(EsptoolTestCase):
|
||||
os.environ["ESPTOOL_CFGFILE"] = tmp
|
||||
else:
|
||||
os.environ.pop("ESPTOOL_CFGFILE", None)
|
||||
|
||||
def test_custom_reset_sequence(self):
|
||||
# This reset sequence will fail to reset the chip to bootloader,
|
||||
# the flash_id operation should therefore fail.
|
||||
# Also tests the number of connection attempts.
|
||||
reset_seq_config = (
|
||||
"[esptool]\n"
|
||||
"custom_reset_sequence = D0|W0.1|R1|R0|W0.1|R1|R0\n"
|
||||
"connect_attempts = 1\n"
|
||||
)
|
||||
config_file_path = os.path.join(os.getcwd(), "esptool.cfg")
|
||||
with self.ConfigFile(config_file_path, reset_seq_config):
|
||||
output = self.run_esptool_error("flash_id")
|
||||
assert f"Loaded custom configuration from {config_file_path}" in output
|
||||
assert "A fatal error occurred: Failed to connect to" in output
|
||||
# Connection attempts are represented with dots,
|
||||
# there are enough dots for two attempts here, but only one is executed
|
||||
assert "Connecting............." not in output
|
||||
|
||||
# Test invalid custom_reset_sequence format is not accepted
|
||||
invalid_reset_seq_config = "[esptool]\n" "custom_reset_sequence = F0|R1|C0|A5\n"
|
||||
with self.ConfigFile(config_file_path, invalid_reset_seq_config):
|
||||
output = self.run_esptool_error("flash_id")
|
||||
assert f"Loaded custom configuration from {config_file_path}" in output
|
||||
assert 'Invalid "custom_reset_sequence" option format:' in output
|
||||
|
Reference in New Issue
Block a user