feat(core): 更新py-esptool (version: 4.8.1)

This commit is contained in:
王立帮
2025-04-07 23:51:29 +08:00
parent 6b4ca0a883
commit 937ecf44f4
120 changed files with 1871 additions and 8899 deletions

View File

@@ -2,7 +2,7 @@
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
# PYTHON_ARGCOMPLETE_OK
__all__ = [
"chip_id",
"detect_chip",
@@ -28,7 +28,7 @@ __all__ = [
"write_mem",
]
__version__ = "4.7.0"
__version__ = "4.8.1"
import argparse
import inspect
@@ -38,8 +38,8 @@ import sys
import time
import traceback
from bin_image import intel_hex_to_bin
from cmds import (
from esptool.bin_image import intel_hex_to_bin
from esptool.cmds import (
DETECTED_FLASH_SIZES,
chip_id,
detect_chip,
@@ -49,6 +49,7 @@ from cmds import (
erase_flash,
erase_region,
flash_id,
read_flash_sfdp,
get_security_info,
image_info,
load_ram,
@@ -65,15 +66,22 @@ from cmds import (
write_flash_status,
write_mem,
)
from config import load_config_file
from loader import DEFAULT_CONNECT_ATTEMPTS, ESPLoader, list_ports
from targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
from util import (
from esptool.config import load_config_file
from esptool.loader import (
DEFAULT_CONNECT_ATTEMPTS,
DEFAULT_OPEN_PORT_ATTEMPTS,
StubFlasher,
ESPLoader,
list_ports,
)
from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
from esptool.util import (
FatalError,
NotImplementedInROMError,
flash_size_bytes,
strip_chip_name,
)
from itertools import chain, cycle, repeat
import serial
@@ -123,6 +131,14 @@ def main(argv=None, esp=None):
default=os.environ.get("ESPTOOL_BAUD", ESPLoader.ESP_ROM_BAUD),
)
parser.add_argument(
"--port-filter",
action="append",
help="Serial port device filter, can be vid=NUMBER, pid=NUMBER, name=SUBSTRING",
type=str,
default=[],
)
parser.add_argument(
"--before",
help="What to do before connecting to the chip",
@@ -145,6 +161,15 @@ def main(argv=None, esp=None):
action="store_true",
)
# --stub-version can be set with --no-stub so the tests wouldn't fail if this option is implied globally
parser.add_argument(
"--stub-version",
default=os.environ.get("ESPTOOL_STUB_VERSION", StubFlasher.STUB_SUBDIRS[0]),
choices=StubFlasher.STUB_SUBDIRS,
# not a public option and is not subject to the semantic versioning policy
help=argparse.SUPPRESS,
)
parser.add_argument(
"--trace",
"-t",
@@ -217,7 +242,12 @@ def main(argv=None, esp=None):
default="0xFFFFFFFF",
)
def add_spi_flash_subparsers(parent, allow_keep, auto_detect):
def add_spi_flash_subparsers(
parent: argparse.ArgumentParser,
allow_keep: bool,
auto_detect: bool,
size_only: bool = False,
):
"""Add common parser arguments for SPI flash properties"""
extra_keep_args = ["keep"] if allow_keep else []
@@ -234,33 +264,35 @@ def main(argv=None, esp=None):
extra_fs_message = ""
flash_sizes = []
parent.add_argument(
"--flash_freq",
"-ff",
help="SPI Flash frequency",
choices=extra_keep_args
+ [
"80m",
"60m",
"48m",
"40m",
"30m",
"26m",
"24m",
"20m",
"16m",
"15m",
"12m",
],
default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else None),
)
parent.add_argument(
"--flash_mode",
"-fm",
help="SPI Flash mode",
choices=extra_keep_args + ["qio", "qout", "dio", "dout"],
default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"),
)
if not size_only:
parent.add_argument(
"--flash_freq",
"-ff",
help="SPI Flash frequency",
choices=extra_keep_args
+ [
"80m",
"60m",
"48m",
"40m",
"30m",
"26m",
"24m",
"20m",
"16m",
"15m",
"12m",
],
default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else None),
)
parent.add_argument(
"--flash_mode",
"-fm",
help="SPI Flash mode",
choices=extra_keep_args + ["qio", "qout", "dio", "dout"],
default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"),
)
parent.add_argument(
"--flash_size",
"-fs",
@@ -468,7 +500,7 @@ def main(argv=None, esp=None):
parser_elf2image.add_argument(
"--use_segments",
help="If set, ELF segments will be used instead of ELF sections "
"to genereate the image.",
"to generate the image.",
action="store_true",
)
parser_elf2image.add_argument(
@@ -489,7 +521,7 @@ def main(argv=None, esp=None):
"quantity. This will make the other segments invisible to the ROM "
"loader. Use this argument with care because the ROM loader will load "
"only the RAM segments although the other segments being present in "
"the output.",
"the output. Implies --dont-append-digest",
action="store_true",
default=None,
)
@@ -540,7 +572,9 @@ def main(argv=None, esp=None):
parser_read_flash = subparsers.add_parser(
"read_flash", help="Read SPI flash content"
)
add_spi_connection_arg(parser_read_flash)
add_spi_flash_subparsers(
parser_read_flash, allow_keep=True, auto_detect=True, size_only=True
)
parser_read_flash.add_argument("address", help="Start address", type=arg_auto_int)
parser_read_flash.add_argument(
"size",
@@ -594,6 +628,14 @@ def main(argv=None, esp=None):
type=arg_auto_size,
)
parser_read_flash_sfdp = subparsers.add_parser(
"read_flash_sfdp",
help="Read SPI flash SFDP (Serial Flash Discoverable Parameters)",
)
add_spi_flash_subparsers(parser_read_flash_sfdp, allow_keep=True, auto_detect=True)
parser_read_flash_sfdp.add_argument("addr", type=arg_auto_int)
parser_read_flash_sfdp.add_argument("bytes", type=int)
parser_merge_bin = subparsers.add_parser(
"merge_bin",
help="Merge multiple raw binary files into a single file for later flashing",
@@ -664,12 +706,40 @@ def main(argv=None, esp=None):
for operation in subparsers.choices.keys():
assert operation in globals(), "%s should be a module function" % operation
# Enable argcomplete only on Unix-like systems
if sys.platform != "win32":
try:
import argcomplete
argcomplete.autocomplete(parser)
except ImportError:
pass
argv = expand_file_arguments(argv or sys.argv[1:])
args = parser.parse_args(argv)
print("esptool.py v%s" % __version__)
load_config_file(verbose=True)
StubFlasher.set_preferred_stub_subdir(args.stub_version)
# Parse filter arguments into separate lists
args.filterVids = []
args.filterPids = []
args.filterNames = []
for f in args.port_filter:
kvp = f.split("=")
if len(kvp) != 2:
raise FatalError("Option --port-filter argument must consist of key=value")
if kvp[0] == "vid":
args.filterVids.append(arg_auto_int(kvp[1]))
elif kvp[0] == "pid":
args.filterPids.append(arg_auto_int(kvp[1]))
elif kvp[0] == "name":
args.filterNames.append(kvp[1])
else:
raise FatalError("Option --port-filter argument key not recognized")
# operation function can take 1 arg (args), 2 args (esp, arg)
# or be a member function of the ESPLoader class.
@@ -705,10 +775,31 @@ def main(argv=None, esp=None):
initial_baud = args.baud
if args.port is None:
ser_list = get_port_list()
ser_list = get_port_list(args.filterVids, args.filterPids, args.filterNames)
print("Found %d serial ports" % len(ser_list))
else:
ser_list = [args.port]
open_port_attempts = os.environ.get(
"ESPTOOL_OPEN_PORT_ATTEMPTS", DEFAULT_OPEN_PORT_ATTEMPTS
)
try:
open_port_attempts = int(open_port_attempts)
except ValueError:
raise SystemExit("Invalid value for ESPTOOL_OPEN_PORT_ATTEMPTS")
if open_port_attempts != 1:
if args.port is None or args.chip == "auto":
print(
"WARNING: The ESPTOOL_OPEN_PORT_ATTEMPTS (open_port_attempts) option can only be used with --port and --chip arguments."
)
else:
esp = esp or connect_loop(
args.port,
initial_baud,
args.chip,
open_port_attempts,
args.trace,
args.before,
)
esp = esp or get_default_connected_device(
ser_list,
port=args.port,
@@ -770,6 +861,13 @@ def main(argv=None, esp=None):
"Keeping initial baud rate %d" % initial_baud
)
def _define_spi_conn(spi_connection):
"""Prepare SPI configuration string and value for flash_spi_attach()"""
clk, q, d, hd, cs = spi_connection
spi_config_txt = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
return spi_config_txt, value
# Override the common SPI flash parameter stuff if configured to do so
if hasattr(args, "spi_connection") and args.spi_connection is not None:
spi_config = args.spi_connection
@@ -781,15 +879,26 @@ def main(argv=None, esp=None):
esp.check_spi_connection(args.spi_connection)
# Encode the pin numbers as a 32-bit integer with packed 6-bit values,
# the same way the ESP ROM takes them
clk, q, d, hd, cs = args.spi_connection
spi_config = f"CLK:{clk}, Q:{q}, D:{d}, HD:{hd}, CS:{cs}"
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
spi_config, value = _define_spi_conn(args.spi_connection)
print(f"Configuring SPI flash mode ({spi_config})...")
esp.flash_spi_attach(value)
elif args.no_stub:
print("Enabling default SPI flash mode...")
# ROM loader doesn't enable flash unless we explicitly do it
esp.flash_spi_attach(0)
if esp.CHIP_NAME != "ESP32" or esp.secure_download_mode:
print("Enabling default SPI flash mode...")
# ROM loader doesn't enable flash unless we explicitly do it
esp.flash_spi_attach(0)
else:
# ROM doesn't attach in-package flash chips
spi_chip_pads = esp.get_chip_spi_pads()
spi_config_txt, value = _define_spi_conn(spi_chip_pads)
if spi_chip_pads != (0, 0, 0, 0, 0):
print(
"Attaching flash from eFuses' SPI pads configuration"
f"({spi_config_txt})..."
)
else:
print("Enabling default SPI flash mode...")
esp.flash_spi_attach(value)
# XMC chip startup sequence
XMC_VENDOR_ID = 0x20
@@ -879,6 +988,10 @@ def main(argv=None, esp=None):
flash_size = detect_flash_size(esp, args)
elif args.flash_size == "keep":
flash_size = detect_flash_size(esp, args=None)
if not esp.IS_STUB:
print(
"WARNING: In case of failure, please set a specific --flash_size."
)
else:
flash_size = args.flash_size
@@ -968,14 +1081,29 @@ def arg_auto_chunk_size(string: str) -> int:
return num
def get_port_list():
def get_port_list(vids=[], pids=[], names=[]):
if list_ports is None:
raise FatalError(
"Listing all serial ports is currently not available. "
"Please try to specify the port when running esptool.py or update "
"the pyserial package to the latest version"
)
return sorted(ports.device for ports in list_ports.comports())
ports = []
for port in list_ports.comports():
if sys.platform == "darwin" and port.device.endswith(
("Bluetooth-Incoming-Port", "wlan-debug")
):
continue
if vids and (port.vid is None or port.vid not in vids):
continue
if pids and (port.pid is None or port.pid not in pids):
continue
if names and (
port.name is None or all(name not in port.name for name in names)
):
continue
ports.append(port.device)
return sorted(ports)
def expand_file_arguments(argv):
@@ -1001,6 +1129,53 @@ def expand_file_arguments(argv):
return argv
def connect_loop(
port: str,
initial_baud: int,
chip: str,
max_retries: int,
trace: bool = False,
before: str = "default_reset",
):
chip_class = CHIP_DEFS[chip]
esp = None
print(f"Serial port {port}")
first = True
ten_cycle = cycle(chain(repeat(False, 9), (True,)))
retry_loop = chain(
repeat(False, max_retries - 1), (True,) if max_retries else cycle((False,))
)
for last, every_tenth in zip(retry_loop, ten_cycle):
try:
esp = chip_class(port, initial_baud, trace)
if not first:
# break the retrying line
print("")
esp.connect(before)
return esp
except (
FatalError,
serial.serialutil.SerialException,
IOError,
OSError,
) as err:
if esp and esp._port:
esp._port.close()
esp = None
if first:
print(err)
print("Retrying failed connection", end="", flush=True)
first = False
if last:
raise err
if every_tenth:
# print a dot every second
print(".", end="", flush=True)
time.sleep(0.1)
def get_default_connected_device(
serial_list,
port,