Skip to content

Commit

Permalink
feat(spi_connection): Support --spi-connection on all chips
Browse files Browse the repository at this point in the history
Closes #916
  • Loading branch information
radimkarnis committed Nov 10, 2023
1 parent a91eee1 commit 1a38293
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 66 deletions.
4 changes: 4 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ target_esp32c3:
extends: .target_esptool_test
tags:
- esptool_esp32c3_target
variables:
ESPTOOL_TEST_SPI_CONN: "6,2,7,4,10"
script:
- coverage run --parallel-mode -m pytest ${CI_PROJECT_DIR}/test/test_esptool.py --port /dev/serial_ports/ESP32C3 --chip esp32c3 --baud 115200

Expand Down Expand Up @@ -329,6 +331,8 @@ target_esp32s3_jtag_serial:
extends: .target_esptool_test
tags:
- esptool_esp32s3_jtag_serial_target
variables:
ESPTOOL_TEST_SPI_CONN: "12,13,11,9,10"
script:
- coverage run --parallel-mode -m pytest ${CI_PROJECT_DIR}/test/test_esptool.py --port /dev/serial_ports/ESP32S3_JTAG_SERIAL --preload-port /dev/serial_ports/ESP32S3_PRELOAD --chip esp32s3 --baud 115200

Expand Down
10 changes: 6 additions & 4 deletions docs/en/advanced-topics/serial-protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ The SPI _ATTACH command enables the SPI flash interface. It takes a 32-bit data

.. only:: not esp8266

On the {IDF_TARGET_NAME} stub loader, it is required to send this command before interacting with SPI flash.
On the {IDF_TARGET_NAME} stub loader sending this command before interacting with SPI flash is optional. On {IDF_TARGET_NAME} ROM loader, it is required to send this command before interacting with SPI flash.

+------------------+----------------------------------------------------------------------------------------------------------------------------------+
| Value | Meaning |
Expand All @@ -400,9 +400,11 @@ The SPI _ATTACH command enables the SPI flash interface. It takes a 32-bit data

When writing the values of each pin as 6-bit numbers packed into the data word, each 6-bit value uses the following representation:

* Pin numbers 0 through 30 are represented as themselves.
* Pin numbers 32 & 33 are represented as values 30 & 31.
* It is not possible to represent pins 30 & 31 or pins higher than 33. This is the same 6-bit representation used by the ``SPI_PAD_CONFIG_xxx`` efuses.
.. only:: esp32

* Pin numbers 0 through 30 are represented as themselves.
* Pin numbers 32 & 33 are represented as values 30 & 31.
* It is not possible to represent pins 30 & 31 or pins higher than 33. This is the same 6-bit representation used by the ``SPI_PAD_CONFIG_xxx`` efuses.

On {IDF_TARGET_NAME} ROM loader only, there is an additional 4 bytes in the data payload of this command. These bytes should all be set to zero.

Expand Down
52 changes: 30 additions & 22 deletions docs/en/esptool/advanced-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The ``--no-stub`` option disables uploading of a software "stub loader" that man

Passing ``--no-stub`` will disable certain options, as not all options are implemented in every chip's ROM loader.

.. only:: esp32
.. only:: not esp8266

Overriding SPI Flash Connections
--------------------------------
Expand All @@ -61,42 +61,50 @@ Passing ``--no-stub`` will disable certain options, as not all options are imple

The only exception to this is if the ``--no-stub`` option is also provided. In this case, efuse values are ignored and ``--spi-connection`` will default to ``--spi-connection SPI`` unless set to a different value.

SPI Mode
^^^^^^^^
.. only:: esp32

``--spi-connection SPI`` uses the default SPI pins:
SPI Mode
^^^^^^^^

* CLK = GPIO 6
* Q = GPIO 7
* D = GPIO 8
* HD = GPIO 9
* CS = GPIO 11
``--spi-connection SPI`` uses the default SPI pins:

During normal booting, this configuration is selected if all SPI pin efuses are unset and GPIO1 (U0TXD) is not pulled low (default).
* CLK = GPIO 6
* Q = GPIO 7
* D = GPIO 8
* HD = GPIO 9
* CS = GPIO 11

This is the normal pin configuration for ESP32 chips that do not contain embedded flash.
During normal booting, this configuration is selected if all SPI pin efuses are unset and GPIO1 (U0TXD) is not pulled low (default).

HSPI Mode
^^^^^^^^^
This is the normal pin configuration for ESP32 chips that do not contain embedded flash.

``--spi-connection HSPI`` uses the HSPI peripheral instead of the SPI peripheral for SPI flash communications, via the following HSPI pins:
HSPI Mode
^^^^^^^^^

* CLK = GPIO 14
* Q = GPIO 12
* D = GPIO 13
* HD = GPIO 4
* CS = GPIO 15
``--spi-connection HSPI`` uses the HSPI peripheral instead of the SPI peripheral for SPI flash communications, via the following HSPI pins:

During normal booting, this configuration is selected if all SPI pin efuses are unset and GPIO1 (U0TXD) is pulled low on reset.
* CLK = GPIO 14
* Q = GPIO 12
* D = GPIO 13
* HD = GPIO 4
* CS = GPIO 15

During normal booting, this configuration is selected if all SPI pin efuses are unset and GPIO1 (U0TXD) is pulled low on reset.

Custom SPI Pin Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

``--spi-connection <CLK>,<Q>,<D>,<HD>,<CS>`` allows a custom list of pins to be configured for the SPI flash connection. This can be used to emulate the flash configuration equivalent to a particular set of SPI pin efuses being burned. The values supplied are GPIO numbers.

For example, ``--spi-connection 6,17,8,11,16`` sets an identical configuration to the factory efuse configuration for ESP32s with embedded flash.
.. only:: esp32

For example, ``--spi-connection 6,17,8,11,16`` sets an identical configuration to the factory efuse configuration for ESP32s with embedded flash.

When setting a custom pin configuration, the SPI peripheral (not HSPI) will be used unless the ``CLK`` pin value is set to 14 (HSPI CLK), in which case the HSPI peripheral will be used.

.. note::

When setting a custom pin configuration, the SPI peripheral (not HSPI) will be used unless the ``CLK`` pin value is set to 14 (HSPI CLK), in which case the HSPI peripheral will be used.
Some GPIO pins might be shared with other peripherals. Therefore, some SPI pad pin configurations might not work reliably or at all. Use a different combination of pins if you encounter issues.

Specifying Arguments via File
-----------------------------
Expand Down
65 changes: 35 additions & 30 deletions esptool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ def add_spi_connection_arg(parent):
parent.add_argument(
"--spi-connection",
"-sc",
help="ESP32-only argument. Override default SPI Flash connection. "
help="Override default SPI Flash connection. "
"Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers "
"to use for SPI flash (CLK,Q,D,HD,CS).",
"to use for SPI flash (CLK,Q,D,HD,CS). Not supported with ESP8266.",
action=SpiConnectionAction,
)

Expand Down Expand Up @@ -770,14 +770,22 @@ def add_spi_flash_subparsers(parent, allow_keep, auto_detect):
"Keeping initial baud rate %d" % initial_baud
)

# override common SPI flash parameter stuff if configured to do so
# Override the common SPI flash parameter stuff if configured to do so
if hasattr(args, "spi_connection") and args.spi_connection is not None:
if esp.CHIP_NAME != "ESP32":
raise FatalError(
"Chip %s does not support --spi-connection option." % esp.CHIP_NAME
)
print("Configuring SPI flash mode...")
esp.flash_spi_attach(args.spi_connection)
spi_config = args.spi_connection
if args.spi_connection == "SPI":
value = 0
elif args.spi_connection == "HSPI":
value = 1
else:
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
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
Expand Down Expand Up @@ -846,6 +854,15 @@ def flash_xmc_startup():
"Try checking the chip connections or removing "
"any other hardware connected to IOs."
)
if (
hasattr(args, "spi_connection")
and args.spi_connection is not None
):
print(
"Some GPIO pins might be used by other peripherals, "
"try using another --spi-connection combination."
)

except FatalError as e:
raise FatalError(f"Unable to verify flash chip connection ({e}).")

Expand Down Expand Up @@ -1023,43 +1040,31 @@ class SpiConnectionAction(argparse.Action):
"""

def __call__(self, parser, namespace, value, option_string=None):
if value.upper() == "SPI":
value = 0
elif value.upper() == "HSPI":
value = 1
if value.upper() in ["SPI", "HSPI"]:
values = value.upper()
elif "," in value:
values = value.split(",")
if len(values) != 5:
raise argparse.ArgumentError(
self,
"%s is not a valid list of comma-separate pin numbers. "
"Must be 5 numbers - CLK,Q,D,HD,CS." % value,
f"{value} is not a valid list of comma-separate pin numbers. "
"Must be 5 numbers - CLK,Q,D,HD,CS.",
)
try:
values = tuple(int(v, 0) for v in values)
except ValueError:
raise argparse.ArgumentError(
self,
"%s is not a valid argument. All pins must be numeric values"
% values,
)
if any([v for v in values if v > 33 or v < 0]):
raise argparse.ArgumentError(
self, "Pin numbers must be in the range 0-33."
f"{values} is not a valid argument. "
"All pins must be numeric values",
)
# encode the pin numbers as a 32-bit integer with packed 6-bit values,
# the same way ESP32 ROM takes them
# TODO: make this less ESP32 ROM specific somehow...
clk, q, d, hd, cs = values
value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk
else:
raise argparse.ArgumentError(
self,
"%s is not a valid spi-connection value. "
"Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS)."
% value,
f"{value} is not a valid spi-connection value. "
"Values are SPI, HSPI, or a sequence of 5 pin numbers - CLK,Q,D,HD,CS.",
)
setattr(namespace, self.dest, value)
setattr(namespace, self.dest, values)


class AutoHex2BinAction(argparse.Action):
Expand Down
7 changes: 6 additions & 1 deletion esptool/targets/esp32.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def read_efuse(self, n):
return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n))

def chip_id(self):
raise NotSupportedError(self, "chip_id")
raise NotSupportedError(self, "Function chip_id")

def read_mac(self, mac_type="BASE_MAC"):
"""Read MAC from EFUSE region"""
Expand Down Expand Up @@ -374,6 +374,11 @@ def change_baud(self, baud):
time.sleep(0.05) # get rid of garbage sent during baud rate change
self.flush_input()

def check_spi_connection(self, spi_connection):
# Pins 30, 31 do not exist
if not set(spi_connection).issubset(set(range(0, 30)) | set((32, 33))):
raise FatalError("SPI Pin numbers must be in the range 0-29, 32, or 33.")


class ESP32StubLoader(ESP32ROM):
"""Access class for ESP32 stub loader, runs on top of ROM."""
Expand Down
5 changes: 5 additions & 0 deletions esptool/targets/esp32c2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .esp32c3 import ESP32C3ROM
from ..loader import ESPLoader
from ..util import FatalError


class ESP32C2ROM(ESP32C3ROM):
Expand Down Expand Up @@ -144,6 +145,10 @@ def is_flash_encryption_key_valid(self):
return True
return False

def check_spi_connection(self, spi_connection):
if not set(spi_connection).issubset(set(range(0, 21))):
raise FatalError("SPI Pin numbers must be in the range 0-20.")


class ESP32C2StubLoader(ESP32C2ROM):
"""Access class for ESP32C2 stub loader, runs on top of ROM.
Expand Down
9 changes: 9 additions & 0 deletions esptool/targets/esp32c3.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,15 @@ def _post_connect(self):
if not self.sync_stub_detected: # Don't run if stub is reused
self.disable_watchdogs()

def check_spi_connection(self, spi_connection):
if not set(spi_connection).issubset(set(range(0, 22))):
raise FatalError("SPI Pin numbers must be in the range 0-21.")
if any([v for v in spi_connection if v in [18, 19]]):
print(
"WARNING: GPIO pins 18 and 19 are used by USB-Serial/JTAG, "
"consider using other pins for SPI flash connection."
)


class ESP32C3StubLoader(ESP32C3ROM):
"""Access class for ESP32C3 stub loader, runs on top of ROM.
Expand Down
9 changes: 9 additions & 0 deletions esptool/targets/esp32c6.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ def is_flash_encryption_key_valid(self):

return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)

def check_spi_connection(self, spi_connection):
if not set(spi_connection).issubset(set(range(0, 31))):
raise FatalError("SPI Pin numbers must be in the range 0-30.")
if any([v for v in spi_connection if v in [12, 13]]):
print(
"WARNING: GPIO pins 12 and 13 are used by USB-Serial/JTAG, "
"consider using other pins for SPI flash connection."
)


class ESP32C6StubLoader(ESP32C6ROM):
"""Access class for ESP32C6 stub loader, runs on top of ROM.
Expand Down
10 changes: 10 additions & 0 deletions esptool/targets/esp32h2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later

from .esp32c6 import ESP32C6ROM
from ..util import FatalError


class ESP32H2ROM(ESP32C6ROM):
Expand Down Expand Up @@ -58,6 +59,15 @@ def get_crystal_freq(self):
# ESP32H2 XTAL is fixed to 32MHz
return 32

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.")
if any([v for v in spi_connection if v in [26, 27]]):
print(
"WARNING: GPIO pins 26 and 27 are used by USB-Serial/JTAG, "
"consider using other pins for SPI flash connection."
)


class ESP32H2StubLoader(ESP32H2ROM):
"""Access class for ESP32H2 stub loader, runs on top of ROM.
Expand Down
3 changes: 3 additions & 0 deletions esptool/targets/esp32p4.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ def _post_connect(self):
# if not self.sync_stub_detected: # Don't run if stub is reused
# self.disable_watchdogs()

def check_spi_connection(self, spi_connection):
pass # TODO: Define GPIOs for --spi-connection


class ESP32P4StubLoader(ESP32P4ROM):
"""Access class for ESP32P4 stub loader, runs on top of ROM.
Expand Down
9 changes: 9 additions & 0 deletions esptool/targets/esp32s2.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ def hard_reset(self):
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)

def check_spi_connection(self, spi_connection):
if not set(spi_connection).issubset(set(range(0, 22)) | set(range(26, 47))):
raise FatalError("SPI Pin numbers must be in the range 0-21, or 26-46.")
if any([v for v in spi_connection if v in [19, 20]]):
print(
"WARNING: GPIO pins 19 and 20 are used by USB-OTG, "
"consider using other pins for SPI flash connection."
)


class ESP32S2StubLoader(ESP32S2ROM):
"""Access class for ESP32-S2 stub loader, runs on top of ROM.
Expand Down
11 changes: 11 additions & 0 deletions esptool/targets/esp32s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,17 @@ def hard_reset(self):
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)

def check_spi_connection(self, spi_connection):
if not set(spi_connection).issubset(set(range(0, 22)) | set(range(26, 49))):
raise FatalError("SPI Pin numbers must be in the range 0-21, or 26-48.")
if spi_connection[3] > 46: # hd_gpio_num must be <= SPI_GPIO_NUM_LIMIT (46)
raise FatalError("SPI HD Pin number must be <= 46.")
if any([v for v in spi_connection if v in [19, 20]]):
print(
"WARNING: GPIO pins 19 and 20 are used by USB-Serial/JTAG and USB-OTG, "
"consider using other pins for SPI flash connection."
)


class ESP32S3StubLoader(ESP32S3ROM):
"""Access class for ESP32S3 stub loader, runs on top of ROM.
Expand Down
9 changes: 5 additions & 4 deletions esptool/targets/esp8266.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later

from ..loader import ESPLoader
from ..util import FatalError, NotImplementedInROMError
from ..util import FatalError, NotSupportedError


class ESP8266ROM(ESPLoader):
Expand Down Expand Up @@ -170,9 +170,10 @@ def get_erase_size(self, offset, size):
return (num_sectors - head_sectors) * sector_size

def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"Overriding VDDSDIO setting only applies to ESP32"
)
raise NotSupportedError(self, "Overriding VDDSDIO")

def check_spi_connection(self, spi_connection):
raise NotSupportedError(self, "Setting --spi-connection")


class ESP8266StubLoader(ESP8266ROM):
Expand Down
Loading

0 comments on commit 1a38293

Please sign in to comment.