diff --git a/cpu/esp8266/Makefile.include b/cpu/esp8266/Makefile.include
index 2b194a9ad7..bc319e3044 100644
--- a/cpu/esp8266/Makefile.include
+++ b/cpu/esp8266/Makefile.include
@@ -175,7 +175,7 @@ FLASH_FREQ = 26m # FIX configuration, DO NOT CHANGE
FLASH_SIZE ?= 1MB
FLASHDEPS += preflash
-PREFLASHER ?= $(ESP8266_RTOS_SDK_DIR)/components/esptool_py/esptool/esptool.py
+PREFLASHER ?= $(RIOTBASE)/dist/tools/esptool/esptool.py
PREFFLAGS = --chip esp8266 elf2image
PREFFLAGS += --flash_mode $(FLASH_MODE) --flash_size $(FLASH_SIZE)
PREFFLAGS += --flash_freq $(FLASH_FREQ) --version 3
@@ -208,8 +208,8 @@ ifneq (, $(filter esp_qemu, $(USEMODULE)))
FFLAGS += head -c $$((0x100000)) > $(BINDIR)/esp8266flash.bin && rm tmp.bin
else
export PROGRAMMER_SPEED ?= 460800
- FLASHER = esptool.py
- FFLAGS += --chip esp8266 --port $(PORT) --baud $(PROGRAMMER_SPEED)
+ FLASHER = $(RIOTBASE)/dist/tools/esptool/esptool.py
+ FFLAGS += --chip esp8266 --port $(PROG_DEV) --baud $(PROGRAMMER_SPEED)
FFLAGS += --before default_reset --after hard_reset write_flash -z
FFLAGS += --flash_size detect
FFLAGS += --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ)
diff --git a/cpu/esp8266/doc.txt b/cpu/esp8266/doc.txt
index 0c56dc4060..2b17cce82e 100644
--- a/cpu/esp8266/doc.txt
+++ b/cpu/esp8266/doc.txt
@@ -148,24 +148,25 @@ described below.
The following software components are required for compilation:
- Xtensa GCC compiler suite for ESP8266
-- Modified ESP8266 RTOS SDK which includes all SOC definitions,
- the hardware abstraction library `libhal.a` and some other binary libraries
-- ESP flash programmer tool `esptool.py`
+- Modified ESP8266 RTOS SDK which includes all SOC definitions and
+ some binary libraries
+- Modified version of ESP flash programmer tool `esptool.py`
There are two options to install the toolchain:
-- using a riotdocker image, see section [RIOT Docker Toolchain (riotdocker)](#esp8266_riot_docker_toolchain)
+- using RIOT Docker (riotdocker), see section
+ [RIOT Docker Toolchain (riotdocker)](#esp8266_riot_docker_toolchain)
- manual installation, see section [Manual Toolchain Installation](#esp8266_manual_toolchain_installation)
-In both cases, the ESP flash programmer tool `esptool.py` has to be installed,
+In both cases, the ESP flash programmer tool `esptool.py` is required,
see section [Installation of `esptool.py`](#esp8266_installation_of_esptool).
## RIOT Docker Toolchain (riotdocker) [[TOC](#esp8266_toc)]
-The easiest way to install the toolchain is to use the RIOT Docker image
-`riotdocker`. The compilation process using Docker consists of two steps
+The easiest way to install the toolchain is to use RIOT Docker
+`riotdocker`. The compilation process using RIOT Docker consists of two steps
-1. making the RIOT application in Docker with command `make BOARD=...`
+1. making the RIOT application in RIOT Docker with command `make BOARD=...`
2. flashing the RIOT application on the host computer with command
`make flash-only BOARD=...`
@@ -174,7 +175,7 @@ installed. Both steps can also be performed with a single command on the host
system using the `BUILD_IN_DOCKER` variable:
```
-`BUILD_IN_DOCKER=1 make BOARD=... flash
+BUILD_IN_DOCKER=1 make BOARD=... flash
```
### Preparing the Environment [[TOC](#esp8266_toc)]
@@ -182,7 +183,7 @@ system using the `BUILD_IN_DOCKER` variable:
Using RIOT Docker requires at least the following software components:
- Docker container virtualization software
-- RIOT Docker (`riotdocker`) image
+- RIOT Docker image (`riotdocker`)
- ESP flash programmer tool `esptool.py`
For information about installing Docker on your host, refer to the appropriate
@@ -196,45 +197,10 @@ sudo apt-get install docker.io
For information on how to install `esptool.py`, see section
[Installation of `esptool.py`](#esp8266_installation_of_esptool).
-### Generating a riotdocker Image [[TOC](#esp8266_toc)]
+### Using an Existing RIOT Docker Image [[TOC](#esp8266_toc)]
-A `riotdocker` fork that only installs the toolchain for ESP8266 RTOS SDK is
-available at [GitHub](https://github.com/gschorcht/riotdocker-Xtensa-ESP.git).
-After cloning this git repository, checkout branch `esp8266_only_rtos_sdk` to
-generate a Docker image with a size of "only" 890 MByte:
-
-```
-cd $HOME/esp
-git clone https://github.com/gschorcht/riotdocker-Xtensa-ESP.git
-cd riotdocker-Xtensa-ESP
-git checkout esp8266_only_rtos_sdk
-docker build -t riotbuild .
-```
-
-A `riotdocker` version that contains toolchains for all platforms supported
-by RIOT can be found at [GitHub](https://github.com/RIOT-OS/riotdocker).
-However, the Docker image generated from the this Docker file has a size of
-about 1.5 GByte.
-
-Once a Docker image has been created, it can be started with the following
-commands while in the RIOT root directory:
-
-```
-cd /path/to/RIOT
-docker run -i -t -u $UID -v $(pwd):/data/riotbuild riotbuild
-```
-
-@note RIOT's root directory `/path/to/RIOT` becomes visible as the home
-directory of the `riotbuild` user in the Docker. That is, the output
-of compilations performed in RIOT Docker are also accessible on the host system.
-
-Please refer the [RIOT wiki](https://github.com/RIOT-OS/RIOT/wiki/Use-Docker-to-build-RIOT)
-on how to use the Docker image to compile RIOT OS.
-
-### Using an Existing riotdocker Image [[TOC](#esp8266_toc)]
-
-Alternatively, an existing Docker image from Docker Hub can be used. You can
-either pull and start the
+The easiest way to use RIOT Docker is to use an existing `riotdocker` image.
+You can either pull and start the
[schorcht/riotbuild_esp8266_rtos](https://hub.docker.com/r/schorcht/riotbuild_esp8266_rtos)
Docker image which only contains the toolchain for ESP8266 RTOS SDK using
@@ -251,27 +217,63 @@ cd /path/to/RIOT
docker run -i -t -u $UID -v $(pwd):/data/riotbuild riot/riotbuild
```
-### Make Process with Docker Image [[TOC](#esp8266_toc)]
+### Generating a riotdocker Image [[TOC](#esp8266_toc)]
-Using Docker, the make process consists of the following two steps:
+Alternatively, you can generate the `riotdocker` image by yourself.
+A `riotdocker` fork that only installs the toolchain for ESP8266 RTOS SDK is
+available at [GitHub](https://github.com/gschorcht/riotdocker-Xtensa-ESP.git).
+After cloning this repository, checkout branch `esp8266_only_rtos_sdk` to
+generate a Docker image with a size of "only" 890 MByte:
-1. **making** the RIOT binary **within a RIOT Docker image**
+```
+cd $HOME/esp
+git clone https://github.com/gschorcht/riotdocker-Xtensa-ESP.git
+cd riotdocker-Xtensa-ESP
+git checkout esp8266_only_rtos_sdk
+docker build -t riotbuild .
+```
+
+A `riotdocker` version that contains toolchains for all platforms supported
+by RIOT can be found at [GitHub](https://github.com/RIOT-OS/riotdocker).
+However, the Docker image generated from this Docker file has a size of
+about 1.5 GByte.
+
+Once the Docker image has been created, it can be started with the following
+commands while in the RIOT root directory:
+
+```
+cd /path/to/RIOT
+docker run -i -t -u $UID -v $(pwd):/data/riotbuild riotbuild
+```
+
+@note RIOT's root directory `/path/to/RIOT` becomes visible as the home
+directory of the `riotbuild` user in the Docker. That is, the output
+of compilations in RIOT Docker are also accessible on the host system.
+
+Please refer the [RIOT wiki](https://github.com/RIOT-OS/RIOT/wiki/Use-Docker-to-build-RIOT)
+on how to use the Docker image to compile RIOT OS.
+
+### Make Process with RIOT Docker [[TOC](#esp8266_toc)]
+
+Using RIOT Docker, the make process consists of the following two steps:
+
+1. **making** the RIOT binary **within RIOT Docker**
2. **flashing** the RIOT binary using a flasher program **on the host system**
-Once the RIOT Docker image has been started from RIOT's root directory, a RIOT
-application can be compiled inside the Docker using the make command as usual,
-for example:
+Once the according RIOT Docker image has been started from RIOT's root
+directory, a RIOT application can be compiled inside RIOT Docker using the
+make command as usual, for example:
```
make BOARD=esp8266-esp-12x -C tests/shell ...
```
This will generate a RIOT binary in ELF format.
-@note You can't use the `flash` target inside the Docker image.
+@note You can't use the `flash` target inside RIOT Docker.
-The RIOT binary has to be flash outside docker on the host system. Since the
-Docker image was started while in RIOT's root directory, the output of the
-compilations is also accessible on the host system. On the host system,
+The RIOT binary has to be flash outside RIOT Docker on the host system. Since
+the RIOT Docker image was started while in RIOT's root directory, the output
+of the compilations is also accessible on the host system. On the host system,
the `flash-only` target can then be used to flash the binary.
```
@@ -343,34 +345,27 @@ export ESP8266_RTOS_SDK_DIR=$HOME/esp/ESP8266_RTOS_SDK
### Installation of `esptool.py` (ESP flash programmer tool) [[TOC](#esp8266_toc)]
-The RIOT OS port does not work with the `esptool.py` ESP flasher program
-available on GitHub [GitHub](https://github.com/espressif/esptool).
-Instead, the modified version from the ESP8266 RTOS SDK has to be used.
+The RIOT port does not work with the `esptool.py` ESP flasher program
+available on [GitHub](https://github.com/espressif/esptool) or
+as package for your OS.
+Instead, a modified version from the ESP8266 RTOS SDK is required.
-To avoid the installation of the ESP8266 RTOS SDK, for example because
-`riotdocker` is used for compilation, `esptool.py` has been extracted
-from the ESP8266 RTOS SDK and is available as standalone copy from
-[GitHub](https://github.com/gschorcht/esptool-esp8266-rtos-sdk-v3).
+To avoid the installation of the complete ESP8266 RTOS SDK, for example
+because RIOT Docker `riotdocker` is used for compilation, `esptool.py`
+has been extracted from the ESP8266 RTOS SDK and placed in RIOT's
+directory `dist/tools/esptool`.
+For convenience, the build system uses always the version from this directory.
-```
-cd $HOME/esp
-git clone https://github.com/gschorcht/esptool-esp8266-rtos-sdk-v3
-chmod +x $HOME/esp/esptool-esp8266-rtos-sdk-v3/esptool.py
-```
-You have to expand your `PATH` variable to use it.
-
-```
-export PATH=$HOME/esp/esptool-esp8266-rtos-sdk-v3:$PATH
-```
-
-`esptool.py` depends on `pySerial` which can be installed either using `pip`
+Therefore, it is **not necessary to install** `esptool.py` explicitly. However
+`esptool.py` depends on `pySerial` which can be installed either
+using `pip`
```
sudo pip install pyserial
```
or the package manager of your OS, for example on Debian/Ubuntu systems:
```
-apt-get install pyserial
+apt install python-pyserial
```
For more information on `esptool.py`, please refer the
[git repository](https://github.com/espressif/esptool).
@@ -385,14 +380,13 @@ directories.
```
/path/to/esp/xtensa-esp8266-elf
/path/to/esp/ESP8266_RTOS_SDK
-/path/to/esp/esptool-esp8266-rtos-sdk-v3
```
-To use the toolchain, please check that your environment variables are set
+To use the toolchain, please ensure that your environment variables are set
correctly to
```
-export PATH=/path/to/esp/esptool-esp8266-rtos-sdk-v3:/path/to/esp/xtensa-esp8266-elf/bin:$PATH
+export PATH=/path/to/esp/xtensa-esp8266-elf/bin:$PATH
export ESP8266_RTOS_SDK_DIR=/path/to/esp/ESP8266_RTOS_SDK
```
@@ -404,8 +398,7 @@ The compilation process can be controlled by following make variables:
Option | Values | Default | Description
-------|--------|---------|------------
-FLASH_MODE | dout, dio, qout, qio | dout | Set the flash mode, please take
-care with your module, see section [Flash Modes](#esp8266_flash_modes)
+FLASH_MODE | dout, dio, qout, qio | dout | Set the flash mode, please take care with your module, see section [Flash Modes](#esp8266_flash_modes)
PORT | /dev/<port> | /dev/USB0 | Set the USB port for flashing the firmware
diff --git a/dist/tools/esptool/esptool.py b/dist/tools/esptool/esptool.py
new file mode 100755
index 0000000000..cd70056e95
--- /dev/null
+++ b/dist/tools/esptool/esptool.py
@@ -0,0 +1,3023 @@
+#!/usr/bin/env python
+#
+# ESP8266 & ESP32 ROM Bootloader Utility
+# Copyright (C) 2014-2016 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) PTE LTD, other contributors as noted.
+# https://github.com/espressif/esptool
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import division, print_function
+
+import argparse
+import base64
+import binascii
+import copy
+import hashlib
+import inspect
+import io
+import os
+import shlex
+import struct
+import sys
+import time
+import zlib
+import string
+import serial.tools.list_ports as list_ports
+
+import serial
+
+# check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269
+try:
+ if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__:
+ raise ImportError("""
+esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'.
+
+You may be able to work around this by 'pip uninstall serial; pip install pyserial' \
+but this may break other installed Python software that depends on 'serial'.
+
+There is no good fix for this right now, apart from configuring virtualenvs. \
+See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""")
+except TypeError:
+ pass # __doc__ returns None for pyserial
+
+
+__version__ = "2.4.0"
+
+MAX_UINT32 = 0xffffffff
+MAX_UINT24 = 0xffffff
+
+DEFAULT_TIMEOUT = 3 # timeout for most flash operations
+START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase)
+CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase
+MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run
+SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader
+MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum
+ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region
+MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond
+
+
+def timeout_per_mb(seconds_per_mb, size_bytes):
+ """ Scales timeouts which are size-specific """
+ result = seconds_per_mb * (size_bytes / 1e6)
+ if result < DEFAULT_TIMEOUT:
+ return DEFAULT_TIMEOUT
+ return result
+
+
+DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB',
+ 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', 0x18: '16MB'}
+
+
+def check_supported_function(func, check_func):
+ """
+ Decorator implementation that wraps a check around an ESPLoader
+ bootloader function to check if it's supported.
+
+ This is used to capture the multidimensional differences in
+ functionality between the ESP8266 & ESP32 ROM loaders, and the
+ software stub that runs on both. Not possible to do this cleanly
+ via inheritance alone.
+ """
+ def inner(*args, **kwargs):
+ obj = args[0]
+ if check_func(obj):
+ return func(*args, **kwargs)
+ else:
+ raise NotImplementedInROMError(obj, func)
+ return inner
+
+
+def stub_function_only(func):
+ """ Attribute for a function only supported in the software stub loader """
+ return check_supported_function(func, lambda o: o.IS_STUB)
+
+
+def stub_and_esp32_function_only(func):
+ """ Attribute for a function only supported by software stubs or ESP32 ROM """
+ return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME == "ESP32")
+
+
+PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3
+
+# Function to return nth byte of a bitstring
+# Different behaviour on Python 2 vs 3
+if PYTHON2:
+ def byte(bitstr, index):
+ return ord(bitstr[index])
+else:
+ def byte(bitstr, index):
+ return bitstr[index]
+
+# Provide a 'basestring' class on Python 3
+try:
+ basestring
+except NameError:
+ basestring = str
+
+
+def esp8266_function_only(func):
+ """ Attribute for a function only supported on ESP8266 """
+ return check_supported_function(func, lambda o: o.CHIP_NAME == "ESP8266")
+
+
+class ESPLoader(object):
+ """ Base class providing access to ESP ROM & software stub bootloaders.
+ Subclasses provide ESP8266 & ESP32 specific functionality.
+
+ Don't instantiate this base class directly, either instantiate a subclass or
+ call ESPLoader.detect_chip() which will interrogate the chip and return the
+ appropriate subclass instance.
+
+ """
+ CHIP_NAME = "Espressif device"
+ IS_STUB = False
+
+ DEFAULT_PORT = "/dev/ttyUSB0"
+
+ # Commands supported by ESP8266 ROM bootloader
+ ESP_FLASH_BEGIN = 0x02
+ ESP_FLASH_DATA = 0x03
+ ESP_FLASH_END = 0x04
+ ESP_MEM_BEGIN = 0x05
+ ESP_MEM_END = 0x06
+ ESP_MEM_DATA = 0x07
+ ESP_SYNC = 0x08
+ ESP_WRITE_REG = 0x09
+ ESP_READ_REG = 0x0a
+
+ # Some comands supported by ESP32 ROM bootloader (or -8266 w/ stub)
+ ESP_SPI_SET_PARAMS = 0x0B
+ ESP_SPI_ATTACH = 0x0D
+ ESP_CHANGE_BAUDRATE = 0x0F
+ ESP_FLASH_DEFL_BEGIN = 0x10
+ ESP_FLASH_DEFL_DATA = 0x11
+ ESP_FLASH_DEFL_END = 0x12
+ ESP_SPI_FLASH_MD5 = 0x13
+
+ # Some commands supported by stub only
+ ESP_ERASE_FLASH = 0xD0
+ ESP_ERASE_REGION = 0xD1
+ ESP_READ_FLASH = 0xD2
+ ESP_RUN_USER_CODE = 0xD3
+
+ # Maximum block sized for RAM and Flash writes, respectively.
+ ESP_RAM_BLOCK = 0x1800
+
+ FLASH_WRITE_SIZE = 0x400
+
+ # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want.
+ ESP_ROM_BAUD = 115200
+
+ # First byte of the application image
+ ESP_IMAGE_MAGIC = 0xe9
+
+ # Initial state for the checksum routine
+ ESP_CHECKSUM_MAGIC = 0xef
+
+ # Flash sector size, minimum unit of erase.
+ FLASH_SECTOR_SIZE = 0x1000
+
+ UART_DATA_REG_ADDR = 0x60000078
+
+ # Memory addresses
+ IROM_MAP_START = 0x40200000
+ IROM_MAP_END = 0x40300000
+
+ # The number of bytes in the UART response that signify command status
+ STATUS_BYTES_LENGTH = 2
+
+ def __init__(self, port=DEFAULT_PORT, baud=ESP_ROM_BAUD, trace_enabled=False):
+ """Base constructor for ESPLoader bootloader interaction
+
+ Don't call this constructor, either instantiate ESP8266ROM
+ or ESP32ROM, or use ESPLoader.detect_chip().
+
+ This base class has all of the instance methods for bootloader
+ functionality supported across various chips & stub
+ loaders. Subclasses replace the functions they don't support
+ with ones which throw NotImplementedInROMError().
+
+ """
+ if isinstance(port, basestring):
+ self._port = serial.serial_for_url(port)
+ else:
+ self._port = port
+ self._slip_reader = slip_reader(self._port, self.trace)
+ # setting baud rate in a separate step is a workaround for
+ # CH341 driver on some Linux versions (this opens at 9600 then
+ # sets), shouldn't matter for other platforms/drivers. See
+ # https://github.com/espressif/esptool/issues/44#issuecomment-107094446
+ self._set_port_baudrate(baud)
+ self._trace_enabled = trace_enabled
+
+ def _set_port_baudrate(self, baud):
+ try:
+ self._port.baudrate = baud
+ except IOError:
+ raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud)
+
+ @staticmethod
+ def detect_chip(port=DEFAULT_PORT, baud=ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False):
+ """ Use serial access to detect the chip type.
+
+ We use the UART's datecode register for this, it's mapped at
+ the same address on ESP8266 & ESP32 so we can use one
+ memory read and compare to the datecode register for each chip
+ type.
+
+ This routine automatically performs ESPLoader.connect() (passing
+ connect_mode parameter) as part of querying the chip.
+ """
+ detect_port = ESPLoader(port, baud, trace_enabled=trace_enabled)
+ detect_port.connect(connect_mode)
+ print('Detecting chip type...', end='')
+ sys.stdout.flush()
+ date_reg = detect_port.read_reg(ESPLoader.UART_DATA_REG_ADDR)
+
+ for cls in [ESP8266ROM, ESP32ROM]:
+ if date_reg == cls.DATE_REG_VALUE:
+ # don't connect a second time
+ inst = cls(detect_port._port, baud, trace_enabled=trace_enabled)
+ print(' %s' % inst.CHIP_NAME)
+ return inst
+ print('')
+ raise FatalError("Unexpected UART datecode value 0x%08x. Failed to autodetect chip type." % date_reg)
+
+ """ Read a SLIP packet from the serial port """
+ def read(self):
+ return next(self._slip_reader)
+
+ """ Write bytes to the serial port while performing SLIP escaping """
+ def write(self, packet):
+ buf = b'\xc0' \
+ + (packet.replace(b'\xdb',b'\xdb\xdd').replace(b'\xc0',b'\xdb\xdc')) \
+ + b'\xc0'
+ self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf))
+ self._port.write(buf)
+
+ def trace(self, message, *format_args):
+ if self._trace_enabled:
+ now = time.time()
+ try:
+
+ delta = now - self._last_trace
+ except AttributeError:
+ delta = 0.0
+ self._last_trace = now
+ prefix = "TRACE +%.3f " % delta
+ print(prefix + (message % format_args))
+
+ """ Calculate checksum of a blob, as it is defined by the ROM """
+ @staticmethod
+ def checksum(data, state=ESP_CHECKSUM_MAGIC):
+ for b in data:
+ if type(b) is int: # python 2/3 compat
+ state ^= b
+ else:
+ state ^= ord(b)
+
+ return state
+
+ """ Send a request and read the response """
+ def command(self, op=None, data=b"", chk=0, wait_response=True, timeout=DEFAULT_TIMEOUT):
+ saved_timeout = self._port.timeout
+ new_timeout = min(timeout, MAX_TIMEOUT)
+ if new_timeout != saved_timeout:
+ self._port.timeout = new_timeout
+
+ try:
+ if op is not None:
+ self.trace("command op=0x%02x data len=%s wait_response=%d timeout=%.3f data=%s",
+ op, len(data), 1 if wait_response else 0, timeout, HexFormatter(data))
+ pkt = struct.pack(b' self.STATUS_BYTES_LENGTH:
+ return data[:-self.STATUS_BYTES_LENGTH]
+ else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg)
+ return val
+
+ def flush_input(self):
+ self._port.flushInput()
+ self._slip_reader = slip_reader(self._port, self.trace)
+
+ def sync(self):
+ self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55',
+ timeout=SYNC_TIMEOUT)
+ for i in range(7):
+ self.command()
+
+ def _setDTR(self, state):
+ self._port.setDTR(state)
+
+ def _setRTS(self, state):
+ self._port.setRTS(state)
+ # Work-around for adapters on Windows using the usbser.sys driver:
+ # generate a dummy change to DTR so that the set-control-line-state
+ # request is sent with the updated RTS state and the same DTR state
+ self._port.setDTR(self._port.dtr)
+
+ def _connect_attempt(self, mode='default_reset', esp32r0_delay=False):
+ """ A single connection attempt, with esp32r0 workaround options """
+ # esp32r0_delay is a workaround for bugs with the most common auto reset
+ # circuit and Windows, if the EN pin on the dev board does not have
+ # enough capacitance.
+ #
+ # Newer dev boards shouldn't have this problem (higher value capacitor
+ # on the EN pin), and ESP32 revision 1 can't use this workaround as it
+ # relies on a silicon bug.
+ #
+ # Details: https://github.com/espressif/esptool/issues/136
+ last_error = None
+
+ # If we're doing no_sync, we're likely communicating as a pass through
+ # with an intermediate device to the ESP32
+ if mode == "no_reset_no_sync":
+ return last_error
+
+ # issue reset-to-bootloader:
+ # RTS = either CH_PD/EN or nRESET (both active low = chip in reset
+ # DTR = GPIO0 (active low = boot to flasher)
+ #
+ # DTR & RTS are active low signals,
+ # ie True = pin @ 0V, False = pin @ VCC.
+ if mode != 'no_reset':
+ self._setDTR(False) # IO0=HIGH
+ self._setRTS(True) # EN=LOW, chip in reset
+ time.sleep(0.1)
+ if esp32r0_delay:
+ # Some chips are more likely to trigger the esp32r0
+ # watchdog reset silicon bug if they're held with EN=LOW
+ # for a longer period
+ time.sleep(1.2)
+ self._setDTR(True) # IO0=LOW
+ self._setRTS(False) # EN=HIGH, chip out of reset
+ if esp32r0_delay:
+ # Sleep longer after reset.
+ # This workaround only works on revision 0 ESP32 chips,
+ # it exploits a silicon bug spurious watchdog reset.
+ time.sleep(0.4) # allow watchdog reset to occur
+ time.sleep(0.05)
+ self._setDTR(False) # IO0=HIGH, done
+
+ for _ in range(5):
+ try:
+ self.flush_input()
+ self._port.flushOutput()
+ self.sync()
+ return None
+ except FatalError as e:
+ if esp32r0_delay:
+ print('_', end='')
+ else:
+ print('.', end='')
+ sys.stdout.flush()
+ time.sleep(0.05)
+ last_error = e
+ return last_error
+
+ def connect(self, mode='default_reset'):
+ """ Try connecting repeatedly until successful, or giving up """
+ print('Connecting...', end='')
+ sys.stdout.flush()
+ last_error = None
+
+ try:
+ for _ in range(7):
+ last_error = self._connect_attempt(mode=mode, esp32r0_delay=False)
+ if last_error is None:
+ return
+ last_error = self._connect_attempt(mode=mode, esp32r0_delay=True)
+ if last_error is None:
+ return
+ finally:
+ print('') # end 'Connecting...' line
+ raise FatalError('Failed to connect to %s: %s' % (self.CHIP_NAME, last_error))
+
+ """ Read memory address in target """
+ def read_reg(self, addr):
+ # we don't call check_command here because read_reg() function is called
+ # when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different
+ # for different chip types (!)
+ val, data = self.command(self.ESP_READ_REG, struct.pack(' start:
+ raise FatalError(("Software loader is resident at 0x%08x-0x%08x. " +
+ "Can't load binary at overlapping address range 0x%08x-0x%08x. " +
+ "Either change binary loading address, or use the --no-stub " +
+ "option to disable the software loader.") % (start, end, load_start, load_end))
+
+ return self.check_command("enter RAM download mode", self.ESP_MEM_BEGIN,
+ struct.pack(' length:
+ raise FatalError('Read more than expected')
+ digest_frame = self.read()
+ if len(digest_frame) != 16:
+ raise FatalError('Expected digest, got: %s' % hexify(digest_frame))
+ expected_digest = hexify(digest_frame).upper()
+ digest = hashlib.md5(data).hexdigest().upper()
+ if digest != expected_digest:
+ raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest))
+ return data
+
+ def flash_spi_attach(self, hspi_arg):
+ """Send SPI attach command to enable the SPI flash pins
+
+ ESP8266 ROM does this when you send flash_begin, ESP32 ROM
+ has it as a SPI command.
+ """
+ # last 3 bytes in ESP_SPI_ATTACH argument are reserved values
+ arg = struct.pack(' 0:
+ self.write_reg(SPI_MOSI_DLEN_REG, mosi_bits - 1)
+ if miso_bits > 0:
+ self.write_reg(SPI_MISO_DLEN_REG, miso_bits - 1)
+ else:
+
+ def set_data_lengths(mosi_bits, miso_bits):
+ SPI_DATA_LEN_REG = SPI_USR1_REG
+ SPI_MOSI_BITLEN_S = 17
+ SPI_MISO_BITLEN_S = 8
+ mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1)
+ miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1)
+ self.write_reg(SPI_DATA_LEN_REG,
+ (miso_mask << SPI_MISO_BITLEN_S) | (
+ mosi_mask << SPI_MOSI_BITLEN_S))
+
+ # SPI peripheral "command" bitmasks for SPI_CMD_REG
+ SPI_CMD_USR = (1 << 18)
+
+ # shift values
+ SPI_USR2_DLEN_SHIFT = 28
+
+ if read_bits > 32:
+ raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported")
+ if len(data) > 64:
+ raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported")
+
+ data_bits = len(data) * 8
+ old_spi_usr = self.read_reg(SPI_USR_REG)
+ old_spi_usr2 = self.read_reg(SPI_USR2_REG)
+ flags = SPI_USR_COMMAND
+ if read_bits > 0:
+ flags |= SPI_USR_MISO
+ if data_bits > 0:
+ flags |= SPI_USR_MOSI
+ set_data_lengths(data_bits, read_bits)
+ self.write_reg(SPI_USR_REG, flags)
+ self.write_reg(SPI_USR2_REG,
+ (7 << SPI_USR2_DLEN_SHIFT) | spiflash_command)
+ if data_bits == 0:
+ self.write_reg(SPI_W0_REG, 0) # clear data register before we read it
+ else:
+ data = pad_to(data, 4, b'\00') # pad to 32-bit multiple
+ words = struct.unpack("I" * (len(data) // 4), data)
+ next_reg = SPI_W0_REG
+ for word in words:
+ self.write_reg(next_reg, word)
+ next_reg += 4
+ self.write_reg(SPI_CMD_REG, SPI_CMD_USR)
+
+ def wait_done():
+ for _ in range(10):
+ if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0:
+ return
+ raise FatalError("SPI command did not complete in time")
+ wait_done()
+
+ status = self.read_reg(SPI_W0_REG)
+ # restore some SPI controller registers
+ self.write_reg(SPI_USR_REG, old_spi_usr)
+ self.write_reg(SPI_USR2_REG, old_spi_usr2)
+ return status
+
+ def read_status(self, num_bytes=2):
+ """Read up to 24 bits (num_bytes) of SPI flash status register contents
+ via RDSR, RDSR2, RDSR3 commands
+
+ Not all SPI flash supports all three commands. The upper 1 or 2
+ bytes may be 0xFF.
+ """
+ SPIFLASH_RDSR = 0x05
+ SPIFLASH_RDSR2 = 0x35
+ SPIFLASH_RDSR3 = 0x15
+
+ status = 0
+ shift = 0
+ for cmd in [SPIFLASH_RDSR, SPIFLASH_RDSR2, SPIFLASH_RDSR3][0:num_bytes]:
+ status += self.run_spiflash_command(cmd, read_bits=8) << shift
+ shift += 8
+ return status
+
+ def write_status(self, new_status, num_bytes=2, set_non_volatile=False):
+ """Write up to 24 bits (num_bytes) of new status register
+
+ num_bytes can be 1, 2 or 3.
+
+ Not all flash supports the additional commands to write the
+ second and third byte of the status register. When writing 2
+ bytes, esptool also sends a 16-byte WRSR command (as some
+ flash types use this instead of WRSR2.)
+
+ If the set_non_volatile flag is set, non-volatile bits will
+ be set as well as volatile ones (WREN used instead of WEVSR).
+
+ """
+ SPIFLASH_WRSR = 0x01
+ SPIFLASH_WRSR2 = 0x31
+ SPIFLASH_WRSR3 = 0x11
+ SPIFLASH_WEVSR = 0x50
+ SPIFLASH_WREN = 0x06
+ SPIFLASH_WRDI = 0x04
+
+ enable_cmd = SPIFLASH_WREN if set_non_volatile else SPIFLASH_WEVSR
+
+ # try using a 16-bit WRSR (not supported by all chips)
+ # this may be redundant, but shouldn't hurt
+ if num_bytes == 2:
+ self.run_spiflash_command(enable_cmd)
+ self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(">= 8
+
+ self.run_spiflash_command(SPIFLASH_WRDI)
+
+ def hard_reset(self):
+ self._setRTS(True) # EN->LOW
+ time.sleep(0.1)
+ self._setRTS(False)
+ time.sleep(1)
+
+ def soft_reset(self, stay_in_bootloader):
+ if not self.IS_STUB:
+ if stay_in_bootloader:
+ return # ROM bootloader is already in bootloader!
+ else:
+ # 'run user code' is as close to a soft reset as we can do
+ self.flash_begin(0, 0)
+ self.flash_finish(False)
+ else:
+ if stay_in_bootloader:
+ # soft resetting from the stub loader
+ # will re-load the ROM bootloader
+ self.flash_begin(0, 0)
+ self.flash_finish(True)
+ elif self.CHIP_NAME != "ESP8266":
+ raise FatalError("Soft resetting is currently only supported on ESP8266")
+ else:
+ # running user code from stub loader requires some hacks
+ # in the stub loader
+ self.command(self.ESP_RUN_USER_CODE, wait_response=False)
+
+
+class ESP8266ROM(ESPLoader):
+ """ Access class for ESP8266 ROM bootloader
+ """
+ CHIP_NAME = "ESP8266"
+ IS_STUB = False
+
+ DATE_REG_VALUE = 0x00062000
+
+ # OTP ROM addresses
+ ESP_OTP_MAC0 = 0x3ff00050
+ ESP_OTP_MAC1 = 0x3ff00054
+ ESP_OTP_MAC3 = 0x3ff0005c
+
+ SPI_REG_BASE = 0x60000200
+ SPI_W0_OFFS = 0x40
+ SPI_HAS_MOSI_DLEN_REG = False
+
+ FLASH_SIZES = {
+ '512KB':0x00,
+ '256KB':0x10,
+ '1MB':0x20,
+ '2MB':0x30,
+ '4MB':0x40,
+ '2MB-c1': 0x50,
+ '4MB-c1':0x60,
+ '8MB':0x80,
+ '16MB':0x90,
+ }
+
+ BOOTLOADER_FLASH_OFFSET = 0
+
+ def get_efuses(self):
+ # Return the 128 bits of ESP8266 efuse as a single Python integer
+ return (self.read_reg(0x3ff0005c) << 96 |
+ self.read_reg(0x3ff00058) << 64 |
+ self.read_reg(0x3ff00054) << 32 |
+ self.read_reg(0x3ff00050))
+
+ def get_chip_description(self):
+ efuses = self.get_efuses()
+ is_8285 = (efuses & ((1 << 4) | 1 << 80)) != 0 # One or the other efuse bit is set for ESP8285
+ return "ESP8285" if is_8285 else "ESP8266EX"
+
+ def get_chip_features(self):
+ features = ["WiFi"]
+ if self.get_chip_description() == "ESP8285":
+ features += ["Embedded Flash"]
+ return features
+
+ def flash_spi_attach(self, hspi_arg):
+ if self.IS_STUB:
+ super(ESP8266ROM, self).flash_spi_attach(hspi_arg)
+ else:
+ # ESP8266 ROM has no flash_spi_attach command in serial protocol,
+ # but flash_begin will do it
+ self.flash_begin(0, 0)
+
+ def flash_set_parameters(self, size):
+ # not implemented in ROM, but OK to silently skip for ROM
+ if self.IS_STUB:
+ super(ESP8266ROM, self).flash_set_parameters(size)
+
+ def chip_id(self):
+ """ Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function """
+ id0 = self.read_reg(self.ESP_OTP_MAC0)
+ id1 = self.read_reg(self.ESP_OTP_MAC1)
+ return (id0 >> 24) | ((id1 & MAX_UINT24) << 8)
+
+ def read_mac(self):
+ """ Read MAC from OTP ROM """
+ mac0 = self.read_reg(self.ESP_OTP_MAC0)
+ mac1 = self.read_reg(self.ESP_OTP_MAC1)
+ mac3 = self.read_reg(self.ESP_OTP_MAC3)
+ if (mac3 != 0):
+ oui = ((mac3 >> 16) & 0xff, (mac3 >> 8) & 0xff, mac3 & 0xff)
+ elif ((mac1 >> 16) & 0xff) == 0:
+ oui = (0x18, 0xfe, 0x34)
+ elif ((mac1 >> 16) & 0xff) == 1:
+ oui = (0xac, 0xd0, 0x74)
+ else:
+ raise FatalError("Unknown OUI")
+ return oui + ((mac1 >> 8) & 0xff, mac1 & 0xff, (mac0 >> 24) & 0xff)
+
+ def get_erase_size(self, offset, size):
+ """ Calculate an erase size given a specific size in bytes.
+
+ Provides a workaround for the bootloader erase bug."""
+
+ sectors_per_block = 16
+ sector_size = self.FLASH_SECTOR_SIZE
+ num_sectors = (size + sector_size - 1) // sector_size
+ start_sector = offset // sector_size
+
+ head_sectors = sectors_per_block - (start_sector % sectors_per_block)
+ if num_sectors < head_sectors:
+ head_sectors = num_sectors
+
+ if num_sectors < 2 * head_sectors:
+ return (num_sectors + 1) // 2 * sector_size
+ else:
+ return (num_sectors - head_sectors) * sector_size
+
+ def override_vddsdio(self, new_voltage):
+ raise NotImplementedInROMError("Overriding VDDSDIO setting only applies to ESP32")
+
+
+class ESP8266StubLoader(ESP8266ROM):
+ """ Access class for ESP8266 stub loader, runs on top of ROM.
+ """
+ FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
+ IS_STUB = True
+
+ def __init__(self, rom_loader):
+ self._port = rom_loader._port
+ self._trace_enabled = rom_loader._trace_enabled
+ self.flush_input() # resets _slip_reader
+
+ def get_erase_size(self, offset, size):
+ return size # stub doesn't have same size bug as ROM loader
+
+
+ESP8266ROM.STUB_CLASS = ESP8266StubLoader
+
+
+class ESP32ROM(ESPLoader):
+ """Access class for ESP32 ROM bootloader
+
+ """
+ CHIP_NAME = "ESP32"
+ IS_STUB = False
+
+ DATE_REG_VALUE = 0x15122500
+
+ IROM_MAP_START = 0x400d0000
+ IROM_MAP_END = 0x40400000
+ DROM_MAP_START = 0x3F400000
+ DROM_MAP_END = 0x3F800000
+
+ # ESP32 uses a 4 byte status reply
+ STATUS_BYTES_LENGTH = 4
+
+ SPI_REG_BASE = 0x60002000
+ EFUSE_REG_BASE = 0x6001a000
+
+ SPI_W0_OFFS = 0x80
+ SPI_HAS_MOSI_DLEN_REG = True
+
+ FLASH_SIZES = {
+ '1MB':0x00,
+ '2MB':0x10,
+ '4MB':0x20,
+ '8MB':0x30,
+ '16MB':0x40
+ }
+
+ BOOTLOADER_FLASH_OFFSET = 0x1000
+
+ OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"]
+
+ def get_chip_description(self):
+ word3 = self.read_efuse(3)
+ chip_ver_rev1 = (word3 >> 15) & 0x1
+ pkg_version = (word3 >> 9) & 0x07
+
+ chip_name = {
+ 0: "ESP32D0WDQ6",
+ 1: "ESP32D0WDQ5",
+ 2: "ESP32D2WDQ5",
+ 5: "ESP32-PICO-D4",
+ }.get(pkg_version, "unknown ESP32")
+
+ return "%s (revision %d)" % (chip_name, chip_ver_rev1)
+
+ def get_chip_features(self):
+ features = ["WiFi"]
+ word3 = self.read_efuse(3)
+
+ # names of variables in this section are lowercase
+ # versions of EFUSE names as documented in TRM and
+ # ESP-IDF efuse_reg.h
+
+ chip_ver_dis_bt = word3 & (1 << 1)
+ if chip_ver_dis_bt == 0:
+ features += ["BT"]
+
+ chip_ver_dis_app_cpu = word3 & (1 << 0)
+ if chip_ver_dis_app_cpu:
+ features += ["Single Core"]
+ else:
+ features += ["Dual Core"]
+
+ chip_cpu_freq_rated = word3 & (1 << 13)
+ if chip_cpu_freq_rated:
+ chip_cpu_freq_low = word3 & (1 << 12)
+ if chip_cpu_freq_low:
+ features += ["160MHz"]
+ else:
+ features += ["240MHz"]
+
+ pkg_version = (word3 >> 9) & 0x07
+ if pkg_version in [2, 4, 5]:
+ features += ["Embedded Flash"]
+
+ word4 = self.read_efuse(4)
+ adc_vref = (word4 >> 8) & 0x1F
+ if adc_vref:
+ features += ["VRef calibration in efuse"]
+
+ return features
+
+ def read_efuse(self, n):
+ """ Read the nth word of the ESP3x EFUSE region. """
+ return self.read_reg(self.EFUSE_REG_BASE + (4 * n))
+
+ def chip_id(self):
+ raise NotSupportedError(self, "chip_id")
+
+ def read_mac(self):
+ """ Read MAC from EFUSE region """
+ words = [self.read_efuse(2), self.read_efuse(1)]
+ bitstring = struct.pack(">II", *words)
+ bitstring = bitstring[2:8] # trim the 2 byte CRC
+ try:
+ return tuple(ord(b) for b in bitstring)
+ except TypeError: # Python 3, bitstring elements are already bytes
+ return tuple(bitstring)
+
+ def get_erase_size(self, offset, size):
+ return size
+
+ def override_vddsdio(self, new_voltage):
+ new_voltage = new_voltage.upper()
+ if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:
+ raise FatalError("The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'")
+ RTC_CNTL_SDIO_CONF_REG = 0x3ff48074
+ RTC_CNTL_XPD_SDIO_REG = (1 << 31)
+ RTC_CNTL_DREFH_SDIO_M = (3 << 29)
+ RTC_CNTL_DREFM_SDIO_M = (3 << 27)
+ RTC_CNTL_DREFL_SDIO_M = (3 << 25)
+ # RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do
+ RTC_CNTL_SDIO_FORCE = (1 << 22)
+ RTC_CNTL_SDIO_PD_EN = (1 << 21)
+
+ reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
+ reg_val |= RTC_CNTL_SDIO_PD_EN
+ if new_voltage != "OFF":
+ reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO
+ if new_voltage == "1.9V":
+ reg_val |= (RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M) # boost voltage
+ self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)
+ print("VDDSDIO regulator set to %s" % new_voltage)
+
+
+class ESP32StubLoader(ESP32ROM):
+ """ Access class for ESP32 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._port = rom_loader._port
+ self._trace_enabled = rom_loader._trace_enabled
+ self.flush_input() # resets _slip_reader
+
+
+ESP32ROM.STUB_CLASS = ESP32StubLoader
+
+
+class ESPBOOTLOADER(object):
+ """ These are constants related to software ESP bootloader, working with 'v2' image files """
+
+ # First byte of the "v2" application image
+ IMAGE_V2_MAGIC = 0xea
+
+ # First 'segment' value in a "v2" application image, appears to be a constant version value?
+ IMAGE_V2_SEGMENT = 4
+
+
+def LoadFirmwareImage(chip, filename):
+ """ Load a firmware image. Can be for ESP8266 or ESP32. ESP8266 images will be examined to determine if they are
+ original ROM firmware images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images.
+
+ Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) or ESP8266V2FirmwareImage (v2).
+ """
+ with open(filename, 'rb') as f:
+ if chip.lower() == 'esp32':
+ return ESP32FirmwareImage(f)
+ else: # Otherwise, ESP8266 so look at magic to determine the image type
+ magic = ord(f.read(1))
+ f.seek(0)
+ if magic == ESPLoader.ESP_IMAGE_MAGIC:
+ return ESP8266ROMFirmwareImage(f)
+ elif magic == ESPBOOTLOADER.IMAGE_V2_MAGIC:
+ return ESP8266V2FirmwareImage(f)
+ else:
+ raise FatalError("Invalid image magic number: %d" % magic)
+
+
+class ImageSegment(object):
+ """ Wrapper class for a segment in an ESP image
+ (very similar to a section in an ELFImage also) """
+ def __init__(self, addr, data, file_offs=None):
+ self.addr = addr
+ self.data = data
+ self.file_offs = file_offs
+ self.include_in_checksum = True
+ self.pad_to_alignment(4) # pad all ImageSegments to at least 4 bytes length
+
+ def copy_with_new_addr(self, new_addr):
+ """ Return a new ImageSegment with same data, but mapped at
+ a new address. """
+ return ImageSegment(new_addr, self.data, 0)
+
+ def split_image(self, split_len):
+ """ Return a new ImageSegment which splits "split_len" bytes
+ from the beginning of the data. Remaining bytes are kept in
+ this segment object (and the start address is adjusted to match.) """
+ result = copy.copy(self)
+ result.data = self.data[:split_len]
+ self.data = self.data[split_len:]
+ self.addr += split_len
+ self.file_offs = None
+ result.file_offs = None
+ return result
+
+ def __repr__(self):
+ r = "len 0x%05x load 0x%08x" % (len(self.data), self.addr)
+ if self.file_offs is not None:
+ r += " file_offs 0x%08x" % (self.file_offs)
+ return r
+
+ def pad_to_alignment(self, alignment):
+ self.data = pad_to(self.data, alignment, b'\x00')
+
+
+class ELFSection(ImageSegment):
+ """ Wrapper class for a section in an ELF image, has a section
+ name as well as the common properties of an ImageSegment. """
+ def __init__(self, name, addr, data):
+ super(ELFSection, self).__init__(addr, data)
+ self.name = name.decode("utf-8")
+
+ def __repr__(self):
+ return "%s %s" % (self.name, super(ELFSection, self).__repr__())
+
+
+class BaseFirmwareImage(object):
+ SEG_HEADER_LEN = 8
+
+ """ Base class with common firmware image functions """
+ def __init__(self):
+ self.segments = []
+ self.entrypoint = 0
+
+ def load_common_header(self, load_file, expected_magic):
+ (magic, segments, self.flash_mode, self.flash_size_freq, self.entrypoint) = struct.unpack(' 16:
+ raise FatalError('Invalid firmware image magic=%d segments=%d' % (magic, segments))
+ return segments
+
+ def load_segment(self, f, is_irom_segment=False):
+ """ Load the next segment from the image file """
+ file_offs = f.tell()
+ (offset, size) = struct.unpack(' 0x40200000 or offset < 0x3ffe0000 or size > 65536:
+ print('WARNING: Suspicious segment 0x%x, length %d' % (offset, size))
+
+ def save_segment(self, f, segment, checksum=None):
+ """ Save the next segment to the image file, return next checksum value if provided """
+ f.write(struct.pack(' 0:
+ if len(irom_segments) != 1:
+ raise FatalError('Found %d segments that could be irom0. Bad ELF file?' % len(irom_segments))
+ return irom_segments[0]
+ return None
+
+ def get_non_irom_segments(self):
+ irom_segment = self.get_irom_segment()
+ return [s for s in self.segments if s != irom_segment]
+
+
+class ESP8266ROMFirmwareImage(BaseFirmwareImage):
+ """ 'Version 1' firmware image, segments loaded directly by the ROM bootloader. """
+
+ ROM_LOADER = ESP8266ROM
+
+ def __init__(self, load_file=None):
+ super(ESP8266ROMFirmwareImage, self).__init__()
+ self.flash_mode = 0
+ self.flash_size_freq = 0
+ self.version = 1
+
+ if load_file is not None:
+ segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
+
+ for _ in range(segments):
+ self.load_segment(load_file)
+ self.checksum = self.read_checksum(load_file)
+
+ def default_output_name(self, input_file):
+ """ Derive a default output name from the ELF name. """
+ return input_file + '-'
+
+ def save(self, basename):
+ """ Save a set of V1 images for flashing. Parameter is a base filename. """
+ # IROM data goes in its own plain binary file
+ irom_segment = self.get_irom_segment()
+ if irom_segment is not None:
+ with open("%s0x%05x.bin" % (basename, irom_segment.addr - ESP8266ROM.IROM_MAP_START), "wb") as f:
+ f.write(irom_segment.data)
+
+ # everything but IROM goes at 0x00000 in an image file
+ normal_segments = self.get_non_irom_segments()
+ with open("%s0x00000.bin" % basename, 'wb') as f:
+ self.write_common_header(f, normal_segments)
+ checksum = ESPLoader.ESP_CHECKSUM_MAGIC
+ for segment in normal_segments:
+ checksum = self.save_segment(f, segment, checksum)
+ self.append_checksum(f, checksum)
+
+
+class ESP8266V2FirmwareImage(BaseFirmwareImage):
+ """ 'Version 2' firmware image, segments loaded by software bootloader stub
+ (ie Espressif bootloader or rboot)
+ """
+
+ ROM_LOADER = ESP8266ROM
+
+ def __init__(self, load_file=None):
+ super(ESP8266V2FirmwareImage, self).__init__()
+ self.version = 2
+ if load_file is not None:
+ segments = self.load_common_header(load_file, ESPBOOTLOADER.IMAGE_V2_MAGIC)
+ if segments != ESPBOOTLOADER.IMAGE_V2_SEGMENT:
+ # segment count is not really segment count here, but we expect to see '4'
+ print('Warning: V2 header has unexpected "segment" count %d (usually 4)' % segments)
+
+ # irom segment comes before the second header
+ #
+ # the file is saved in the image with a zero load address
+ # in the header, so we need to calculate a load address
+ irom_segment = self.load_segment(load_file, True)
+ irom_segment.addr = 0 # for actual mapped addr, add ESP8266ROM.IROM_MAP_START + flashing_addr + 8
+ irom_segment.include_in_checksum = False
+
+ first_flash_mode = self.flash_mode
+ first_flash_size_freq = self.flash_size_freq
+ first_entrypoint = self.entrypoint
+ # load the second header
+
+ segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
+
+ if first_flash_mode != self.flash_mode:
+ print('WARNING: Flash mode value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
+ % (first_flash_mode, self.flash_mode))
+ if first_flash_size_freq != self.flash_size_freq:
+ print('WARNING: Flash size/freq value in first header (0x%02x) disagrees with second (0x%02x). Using second value.'
+ % (first_flash_size_freq, self.flash_size_freq))
+ if first_entrypoint != self.entrypoint:
+ print('WARNING: Entrypoint address in first header (0x%08x) disagrees with second header (0x%08x). Using second value.'
+ % (first_entrypoint, self.entrypoint))
+
+ # load all the usual segments
+ for _ in range(segments):
+ self.load_segment(load_file)
+ self.checksum = self.read_checksum(load_file)
+
+ def default_output_name(self, input_file):
+ """ Derive a default output name from the ELF name. """
+ irom_segment = self.get_irom_segment()
+ if irom_segment is not None:
+ irom_offs = irom_segment.addr - ESP8266ROM.IROM_MAP_START
+ else:
+ irom_offs = 0
+ return "%s-0x%05x.bin" % (os.path.splitext(input_file)[0],
+ irom_offs & ~(ESPLoader.FLASH_SECTOR_SIZE - 1))
+
+ def save(self, filename):
+ with open(filename, 'wb') as f:
+ # Save first header for irom0 segment
+ f.write(struct.pack(b' 0:
+ last_addr = flash_segments[0].addr
+ #print('%x' % last_addr)
+ for segment in flash_segments[1:]:
+ if segment.addr // IROM_ALIGN == last_addr // IROM_ALIGN:
+ raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " +
+ "Can't generate binary. Suggest changing linker script or ELF to merge sections.") %
+ (segment.addr, last_addr))
+ last_addr = segment.addr
+ print('%x' % last_addr)
+
+ def get_alignment_data_needed(segment):
+ # Actual alignment (in data bytes) required for a segment header: positioned so that
+ # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN
+ #
+ # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned
+ # IROM_ALIGN+0x18 to account for the binary file header
+ align_past = (segment.addr % IROM_ALIGN) - self.SEG_HEADER_LEN
+ pad_len = (IROM_ALIGN - (f.tell() % IROM_ALIGN)) + align_past
+ if pad_len == 0 or pad_len == IROM_ALIGN:
+ return 0 # already aligned
+
+ # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well
+ pad_len -= self.SEG_HEADER_LEN
+ if pad_len < 0:
+ pad_len += IROM_ALIGN
+ return pad_len
+
+ # try to fit each flash segment on a 64kB aligned boundary
+ # by padding with parts of the non-flash segments...
+ while len(flash_segments) > 0:
+ segment = flash_segments[0]
+ pad_len = 0
+ #get_alignment_data_needed(segment)
+ if pad_len > 0: # need to pad
+ #if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN:
+ # pad_segment = ram_segments[0].split_image(pad_len)
+ # if len(ram_segments[0].data) == 0:
+ # ram_segments.pop(0)
+ #else:
+ pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell())
+ print('qqq %x' % pad_segment.addr)
+ checksum = self.save_segment(f, pad_segment, checksum)
+ total_segments += 1
+ else:
+ # write the flash segment
+ #assert (f.tell() + 8) % IROM_ALIGN == segment.addr % IROM_ALIGN
+ checksum = self.save_segment(f, segment, checksum)
+ flash_segments.pop(0)
+ total_segments += 1
+
+ # flash segments all written, so write any remaining RAM segments
+ for segment in ram_segments:
+ checksum = self.save_segment(f, segment, checksum)
+ total_segments += 1
+
+ # done writing segments
+ self.append_checksum(f, checksum)
+ # kinda hacky: go back to the initial header and write the new segment count
+ # that includes padding segments. This header is not checksummed
+ image_length = f.tell()
+ f.seek(1)
+ try:
+ f.write(chr(total_segments))
+ except TypeError: # Python 3
+ f.write(bytes([total_segments]))
+
+ if self.append_digest:
+ # calculate the SHA256 of the whole file and append it
+ f.seek(0)
+ digest = hashlib.sha256()
+ digest.update(f.read(image_length))
+ f.write(digest.digest())
+
+ with open(filename, 'wb') as real_file:
+ real_file.write(f.getvalue())
+
+ def load_extended_header(self, load_file):
+ def split_byte(n):
+ return (n & 0x0F, (n >> 4) & 0x0F)
+
+ fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)))
+
+ self.wp_pin = fields[0]
+
+ # SPI pin drive stengths are two per byte
+ self.clk_drv, self.q_drv = split_byte(fields[1])
+ self.d_drv, self.cs_drv = split_byte(fields[2])
+ self.hd_drv, self.wp_drv = split_byte(fields[3])
+
+ if fields[15] in [0, 1]:
+ self.append_digest = (fields[15] == 1)
+ else:
+ raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", fields[15])
+
+ # remaining fields in the middle should all be zero
+ if any(f for f in fields[4:15] if f != 0):
+ print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?")
+
+ def save_extended_header(self, save_file):
+ def join_byte(ln,hn):
+ return (ln & 0x0F) + ((hn & 0x0F) << 4)
+
+ append_digest = 1 if self.append_digest else 0
+
+ fields = [self.wp_pin,
+ join_byte(self.clk_drv, self.q_drv),
+ join_byte(self.d_drv, self.cs_drv),
+ join_byte(self.hd_drv, self.wp_drv)]
+ fields += [0] * 11
+ fields += [append_digest]
+
+ packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields)
+ save_file.write(packed)
+
+# Backwards compatibility for previous API, remove in esptool.py V3
+ESPFirmwareImage = ESP8266ROMFirmwareImage
+OTAFirmwareImage = ESP8266V2FirmwareImage
+
+
+def esp8266_crc32(data):
+ """
+ CRC32 algorithm used by 8266 SDK bootloader (and gen_appbin.py).
+ """
+ crc = binascii.crc32(data, 0) & 0xFFFFFFFF
+ if crc & 0x80000000:
+ return crc ^ 0xFFFFFFFF
+ else:
+ return crc + 1
+
+
+class ESP32FirmwareImage(BaseFirmwareImage):
+ """ ESP32 firmware image is very similar to V1 ESP8266 image,
+ except with an additional 16 byte reserved header at top of image,
+ and because of new flash mapping capabilities the flash-mapped regions
+ can be placed in the normal image (just @ 64kB padded offsets).
+ """
+
+ ROM_LOADER = ESP32ROM
+
+ # ROM bootloader will read the wp_pin field if SPI flash
+ # pins are remapped via flash. IDF actually enables QIO only
+ # from software bootloader, so this can be ignored. But needs
+ # to be set to this value so ROM bootloader will skip it.
+ WP_PIN_DISABLED = 0xEE
+
+ EXTENDED_HEADER_STRUCT_FMT = "B" * 16
+
+ def __init__(self, load_file=None):
+ super(ESP32FirmwareImage, self).__init__()
+ self.flash_mode = 0
+ self.flash_size_freq = 0
+ self.version = 1
+ self.wp_pin = self.WP_PIN_DISABLED
+ # SPI pin drive levels
+ self.clk_drv = 0
+ self.q_drv = 0
+ self.d_drv = 0
+ self.cs_drv = 0
+ self.hd_drv = 0
+ self.wp_drv = 0
+
+ self.append_digest = True
+
+ if load_file is not None:
+ start = load_file.tell()
+
+ segments = self.load_common_header(load_file, ESPLoader.ESP_IMAGE_MAGIC)
+ self.load_extended_header(load_file)
+
+ for _ in range(segments):
+ self.load_segment(load_file)
+ self.checksum = self.read_checksum(load_file)
+
+ if self.append_digest:
+ end = load_file.tell()
+ self.stored_digest = load_file.read(32)
+ load_file.seek(start)
+ calc_digest = hashlib.sha256()
+ calc_digest.update(load_file.read(end - start))
+ self.calc_digest = calc_digest.digest() # TODO: decide what to do here?
+
+ def is_flash_addr(self, addr):
+ return (ESP32ROM.IROM_MAP_START <= addr < ESP32ROM.IROM_MAP_END) \
+ or (ESP32ROM.DROM_MAP_START <= addr < ESP32ROM.DROM_MAP_END)
+
+ def default_output_name(self, input_file):
+ """ Derive a default output name from the ELF name. """
+ return "%s.bin" % (os.path.splitext(input_file)[0])
+
+ def warn_if_unusual_segment(self, offset, size, is_irom_segment):
+ pass # TODO: add warnings for ESP32 segment offset/size combinations that are wrong
+
+ def save(self, filename):
+ total_segments = 0
+ with io.BytesIO() as f: # write file to memory first
+ self.write_common_header(f, self.segments)
+
+ # first 4 bytes of header are read by ROM bootloader for SPI
+ # config, but currently unused
+ self.save_extended_header(f)
+
+ checksum = ESPLoader.ESP_CHECKSUM_MAGIC
+
+ # split segments into flash-mapped vs ram-loaded, and take copies so we can mutate them
+ flash_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s:s.addr) if self.is_flash_addr(s.addr)]
+ ram_segments = [copy.deepcopy(s) for s in sorted(self.segments, key=lambda s:s.addr) if not self.is_flash_addr(s.addr)]
+
+ IROM_ALIGN = 65536
+
+ # check for multiple ELF sections that are mapped in the same flash mapping region.
+ # this is usually a sign of a broken linker script, but if you have a legitimate
+ # use case then let us know (we can merge segments here, but as a rule you probably
+ # want to merge them in your linker script.)
+ if len(flash_segments) > 0:
+ last_addr = flash_segments[0].addr
+ for segment in flash_segments[1:]:
+ if segment.addr // IROM_ALIGN == last_addr // IROM_ALIGN:
+ raise FatalError(("Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " +
+ "Can't generate binary. Suggest changing linker script or ELF to merge sections.") %
+ (segment.addr, last_addr))
+ last_addr = segment.addr
+
+ def get_alignment_data_needed(segment):
+ # Actual alignment (in data bytes) required for a segment header: positioned so that
+ # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN
+ #
+ # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned
+ # IROM_ALIGN+0x18 to account for the binary file header
+ align_past = (segment.addr % IROM_ALIGN) - self.SEG_HEADER_LEN
+ pad_len = (IROM_ALIGN - (f.tell() % IROM_ALIGN)) + align_past
+ if pad_len == 0 or pad_len == IROM_ALIGN:
+ return 0 # already aligned
+
+ # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well
+ pad_len -= self.SEG_HEADER_LEN
+ if pad_len < 0:
+ pad_len += IROM_ALIGN
+ return pad_len
+
+ # try to fit each flash segment on a 64kB aligned boundary
+ # by padding with parts of the non-flash segments...
+ while len(flash_segments) > 0:
+ segment = flash_segments[0]
+ pad_len = get_alignment_data_needed(segment)
+ if pad_len > 0: # need to pad
+ if len(ram_segments) > 0 and pad_len > self.SEG_HEADER_LEN:
+ pad_segment = ram_segments[0].split_image(pad_len)
+ if len(ram_segments[0].data) == 0:
+ ram_segments.pop(0)
+ else:
+ pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell())
+ checksum = self.save_segment(f, pad_segment, checksum)
+ total_segments += 1
+ else:
+ # write the flash segment
+ assert (f.tell() + 8) % IROM_ALIGN == segment.addr % IROM_ALIGN
+ checksum = self.save_segment(f, segment, checksum)
+ flash_segments.pop(0)
+ total_segments += 1
+
+ # flash segments all written, so write any remaining RAM segments
+ for segment in ram_segments:
+ checksum = self.save_segment(f, segment, checksum)
+ total_segments += 1
+
+ # done writing segments
+ self.append_checksum(f, checksum)
+ # kinda hacky: go back to the initial header and write the new segment count
+ # that includes padding segments. This header is not checksummed
+ image_length = f.tell()
+ f.seek(1)
+ try:
+ f.write(chr(total_segments))
+ except TypeError: # Python 3
+ f.write(bytes([total_segments]))
+
+ if self.append_digest:
+ # calculate the SHA256 of the whole file and append it
+ f.seek(0)
+ digest = hashlib.sha256()
+ digest.update(f.read(image_length))
+ f.write(digest.digest())
+
+ with open(filename, 'wb') as real_file:
+ real_file.write(f.getvalue())
+
+ def load_extended_header(self, load_file):
+ def split_byte(n):
+ return (n & 0x0F, (n >> 4) & 0x0F)
+
+ fields = list(struct.unpack(self.EXTENDED_HEADER_STRUCT_FMT, load_file.read(16)))
+
+ self.wp_pin = fields[0]
+
+ # SPI pin drive stengths are two per byte
+ self.clk_drv, self.q_drv = split_byte(fields[1])
+ self.d_drv, self.cs_drv = split_byte(fields[2])
+ self.hd_drv, self.wp_drv = split_byte(fields[3])
+
+ if fields[15] in [0, 1]:
+ self.append_digest = (fields[15] == 1)
+ else:
+ raise RuntimeError("Invalid value for append_digest field (0x%02x). Should be 0 or 1.", fields[15])
+
+ # remaining fields in the middle should all be zero
+ if any(f for f in fields[4:15] if f != 0):
+ print("Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?")
+
+ def save_extended_header(self, save_file):
+ def join_byte(ln,hn):
+ return (ln & 0x0F) + ((hn & 0x0F) << 4)
+
+ append_digest = 1 if self.append_digest else 0
+
+ fields = [self.wp_pin,
+ join_byte(self.clk_drv, self.q_drv),
+ join_byte(self.d_drv, self.cs_drv),
+ join_byte(self.hd_drv, self.wp_drv)]
+ fields += [0] * 11
+ fields += [append_digest]
+
+ packed = struct.pack(self.EXTENDED_HEADER_STRUCT_FMT, *fields)
+ save_file.write(packed)
+
+
+class ELFFile(object):
+ SEC_TYPE_PROGBITS = 0x01
+ SEC_TYPE_STRTAB = 0x03
+
+ LEN_SEC_HEADER = 0x28
+
+ def __init__(self, name):
+ # Load sections from the ELF file
+ self.name = name
+ with open(self.name, 'rb') as f:
+ self._read_elf_file(f)
+
+ def get_section(self, section_name):
+ for s in self.sections:
+ if s.name == section_name:
+ return s
+ raise ValueError("No section %s in ELF file" % section_name)
+
+ def _read_elf_file(self, f):
+ # read the ELF file header
+ LEN_FILE_HEADER = 0x34
+ try:
+ (ident,_type,machine,_version,
+ self.entrypoint,_phoff,shoff,_flags,
+ _ehsize, _phentsize,_phnum, shentsize,
+ shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
+ except struct.error as e:
+ raise FatalError("Failed to read a valid ELF header from %s: %s" % (self.name, e))
+
+ if byte(ident, 0) != 0x7f or ident[1:4] != b'ELF':
+ raise FatalError("%s has invalid ELF magic header" % self.name)
+ if machine != 0x5e:
+ raise FatalError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine))
+ if shentsize != self.LEN_SEC_HEADER:
+ raise FatalError("%s has unexpected section header entry size 0x%x (not 0x28)" % (self.name, shentsize, self.LEN_SEC_HEADER))
+ if shnum == 0:
+ raise FatalError("%s has 0 section headers" % (self.name))
+ self._read_sections(f, shoff, shnum, shstrndx)
+
+ def _read_sections(self, f, section_header_offs, section_header_count, shstrndx):
+ f.seek(section_header_offs)
+ len_bytes = section_header_count * self.LEN_SEC_HEADER
+ section_header = f.read(len_bytes)
+ if len(section_header) == 0:
+ raise FatalError("No section header found at offset %04x in ELF file." % section_header_offs)
+ if len(section_header) != (len_bytes):
+ raise FatalError("Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" % (len(section_header), len_bytes))
+
+ # walk through the section header and extract all sections
+ section_header_offsets = range(0, len(section_header), self.LEN_SEC_HEADER)
+
+ def read_section_header(offs):
+ name_offs,sec_type,_flags,lma,sec_offs,size = struct.unpack_from(" 16 bytes) will be
+ printed as separately indented lines, with ASCII decoding at the end
+ of each line.
+ """
+ def __init__(self, binary_string, auto_split=True):
+ self._s = binary_string
+ self._auto_split = auto_split
+
+ def __str__(self):
+ if self._auto_split and len(self._s) > 16:
+ result = ""
+ s = self._s
+ while len(s) > 0:
+ line = s[:16]
+ ascii_line = "".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace))
+ else '.' for c in line.decode('ascii', 'replace'))
+ s = s[16:]
+ result += "\n %-16s %-16s | %s" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line)
+ return result
+ else:
+ return hexify(self._s, False)
+
+
+def pad_to(data, alignment, pad_character=b'\xFF'):
+ """ Pad to the next alignment boundary """
+ pad_mod = len(data) % alignment
+ if pad_mod != 0:
+ data += pad_character * (alignment - pad_mod)
+ return data
+
+
+class FatalError(RuntimeError):
+ """
+ Wrapper class for runtime errors that aren't caused by internal bugs, but by
+ ESP8266 responses or input content.
+ """
+ def __init__(self, message):
+ RuntimeError.__init__(self, message)
+
+ @staticmethod
+ def WithResult(message, result):
+ """
+ Return a fatal error object that appends the hex values of
+ 'result' as a string formatted argument.
+ """
+ message += " (result was %s)" % hexify(result)
+ return FatalError(message)
+
+
+class NotImplementedInROMError(FatalError):
+ """
+ Wrapper class for the error thrown when a particular ESP bootloader function
+ is not implemented in the ROM bootloader.
+ """
+ def __init__(self, bootloader, func):
+ FatalError.__init__(self, "%s ROM does not support function %s." % (bootloader.CHIP_NAME, func.__name__))
+
+
+class NotSupportedError(FatalError):
+ def __init__(self, esp, function_name):
+ FatalError.__init__(self, "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME))
+
+# "Operation" commands, executable at command line. One function each
+#
+# Each function takes either two args (, ) or a single
+# argument.
+
+
+def load_ram(esp, args):
+ image = LoadFirmwareImage(esp.CHIP_NAME, args.filename)
+
+ print('RAM boot...')
+ for seg in image.segments:
+ size = len(seg.data)
+ print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ')
+ sys.stdout.flush()
+ esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr)
+
+ seq = 0
+ while len(seg.data) > 0:
+ esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq)
+ seg.data = seg.data[esp.ESP_RAM_BLOCK:]
+ seq += 1
+ print('done!')
+
+ print('All segments done, executing at %08x' % image.entrypoint)
+ esp.mem_finish(image.entrypoint)
+
+
+def read_mem(esp, args):
+ print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address)))
+
+
+def write_mem(esp, args):
+ esp.write_reg(args.address, args.value, args.mask, 0)
+ print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address))
+
+
+def dump_mem(esp, args):
+ f = open(args.filename, 'wb')
+ for i in range(args.size // 4):
+ d = esp.read_reg(args.address + (i * 4))
+ f.write(struct.pack(b'> 16
+ args.flash_size = DETECTED_FLASH_SIZES.get(size_id)
+ if args.flash_size is None:
+ print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id))
+ args.flash_size = '4MB'
+ else:
+ print('Auto-detected Flash size:', args.flash_size)
+
+
+def _update_image_flash_params(esp, address, args, image):
+ """ Modify the flash mode & size bytes if this looks like an executable bootloader image """
+ if len(image) < 8:
+ return image # not long enough to be a bootloader image
+
+ # unpack the (potential) image header
+ magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4])
+ if address != esp.BOOTLOADER_FLASH_OFFSET or magic != esp.ESP_IMAGE_MAGIC:
+ return image # not flashing a bootloader, so don't modify this
+
+ if args.flash_mode != 'keep':
+ flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
+
+ flash_freq = flash_size_freq & 0x0F
+ if args.flash_freq != 'keep':
+ flash_freq = {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
+
+ flash_size = flash_size_freq & 0xF0
+ if args.flash_size != 'keep':
+ flash_size = esp.parse_flash_size_arg(args.flash_size)
+
+ flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq)
+ if flash_params != image[2:4]:
+ print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params))
+ image = image[0:2] + flash_params + image[4:]
+ return image
+
+
+def write_flash(esp, args):
+ # set args.compress based on default behaviour:
+ # -> if either --compress or --no-compress is set, honour that
+ # -> otherwise, set --compress unless --no-stub is set
+ if args.compress is None and not args.no_compress:
+ args.compress = not args.no_stub
+
+ # verify file sizes fit in flash
+ flash_end = flash_size_bytes(args.flash_size)
+ for address, argfile in args.addr_filename:
+ argfile.seek(0,2) # seek to end
+ if address + argfile.tell() > flash_end:
+ raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " +
+ "Use --flash-size argument, or change flashing address.")
+ % (argfile.name, argfile.tell(), address, flash_end))
+ argfile.seek(0)
+
+ for address, argfile in args.addr_filename:
+ if args.no_stub:
+ print('Erasing flash...')
+ image = pad_to(argfile.read(), 4)
+ if len(image) == 0:
+ print('WARNING: File %s is empty' % argfile.name)
+ continue
+ image = _update_image_flash_params(esp, address, args, image)
+ calcmd5 = hashlib.md5(image).hexdigest()
+ uncsize = len(image)
+ if args.compress:
+ uncimage = image
+ image = zlib.compress(uncimage, 9)
+ ratio = uncsize / len(image)
+ blocks = esp.flash_defl_begin(uncsize, len(image), address)
+ else:
+ ratio = 1.0
+ blocks = esp.flash_begin(uncsize, address)
+ argfile.seek(0) # in case we need it again
+ seq = 0
+ written = 0
+ t = time.time()
+ while len(image) > 0:
+ print('\rWriting at 0x%08x... (%d %%)' % (address + seq * esp.FLASH_WRITE_SIZE, 100 * (seq + 1) // blocks), end='')
+ sys.stdout.flush()
+ block = image[0:esp.FLASH_WRITE_SIZE]
+ if args.compress:
+ esp.flash_defl_block(block, seq, timeout=DEFAULT_TIMEOUT * ratio)
+ else:
+ # Pad the last block
+ block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block))
+ esp.flash_block(block, seq)
+ image = image[esp.FLASH_WRITE_SIZE:]
+ seq += 1
+ written += len(block)
+ t = time.time() - t
+ speed_msg = ""
+ if args.compress:
+ if t > 0.0:
+ speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000)
+ print('\rWrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, written, address, t, speed_msg))
+ else:
+ if t > 0.0:
+ speed_msg = " (%.1f kbit/s)" % (written / t * 8 / 1000)
+ print('\rWrote %d bytes at 0x%08x in %.1f seconds%s...' % (written, address, t, speed_msg))
+ try:
+ res = esp.flash_md5sum(address, uncsize)
+ if res != calcmd5:
+ print('File md5: %s' % calcmd5)
+ print('Flash md5: %s' % res)
+ print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest()))
+ raise FatalError("MD5 of file does not match data in flash!")
+ else:
+ print('Hash of data verified.')
+ except NotImplementedInROMError:
+ pass
+
+ print('\nLeaving...')
+
+ if esp.IS_STUB:
+ # skip sending flash_finish to ROM loader here,
+ # as it causes the loader to exit and run user code
+ esp.flash_begin(0, 0)
+ if args.compress:
+ esp.flash_defl_finish(False)
+ else:
+ esp.flash_finish(False)
+
+ if args.verify:
+ print('Verifying just-written flash...')
+ print('(This option is deprecated, flash contents are now always read back after flashing.)')
+ verify_flash(esp, args)
+
+
+def image_info(args):
+ image = LoadFirmwareImage(args.chip, args.filename)
+ print('Image version: %d' % image.version)
+ print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set')
+ print('%d segments' % len(image.segments))
+ print
+ idx = 0
+ for seg in image.segments:
+ idx += 1
+ print('Segment %d: %r' % (idx, seg))
+ calc_checksum = image.calculate_checksum()
+ print('Checksum: %02x (%s)' % (image.checksum,
+ 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum))
+ try:
+ digest_msg = 'Not appended'
+ if image.append_digest:
+ is_valid = image.stored_digest == image.calc_digest
+ digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(),
+ "valid" if is_valid else "invalid")
+ print('Validation Hash: %s' % digest_msg)
+ except AttributeError:
+ pass # ESP8266 image has no append_digest field
+
+
+def make_image(args):
+ image = ESP8266ROMFirmwareImage()
+ if len(args.segfile) == 0:
+ raise FatalError('No segments specified')
+ if len(args.segfile) != len(args.segaddr):
+ raise FatalError('Number of specified files does not match number of specified addresses')
+ for (seg, addr) in zip(args.segfile, args.segaddr):
+ data = open(seg, 'rb').read()
+ image.segments.append(ImageSegment(addr, data))
+ image.entrypoint = args.entrypoint
+ image.save(args.output)
+
+
+def elf2image(args):
+ e = ELFFile(args.input)
+ if args.chip == 'auto': # Default to ESP8266 for backwards compatibility
+ print("Creating image for ESP8266...")
+ args.chip = 'esp8266'
+
+ if args.chip == 'esp32':
+ image = ESP32FirmwareImage()
+ elif args.version == '1': # ESP8266
+ image = ESP8266ROMFirmwareImage()
+ elif args.version == '2': # ESP8266
+ image = ESP8266V2FirmwareImage()
+ else:
+ image = ESP8266V3FirmwareImage()
+ image.entrypoint = e.entrypoint
+ image.segments = e.sections # ELFSection is a subclass of ImageSegment
+ image.flash_mode = {'qio':0, 'qout':1, 'dio':2, 'dout': 3}[args.flash_mode]
+ image.flash_size_freq = image.ROM_LOADER.FLASH_SIZES[args.flash_size]
+ image.flash_size_freq += {'40m':0, '26m':1, '20m':2, '80m': 0xf}[args.flash_freq]
+
+ if args.output is None:
+ args.output = image.default_output_name(args.input)
+ image.save(args.output)
+
+
+def read_mac(esp, args):
+ mac = esp.read_mac()
+
+ def print_mac(label, mac):
+ print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac))))
+ print_mac("MAC", mac)
+
+
+def chip_id(esp, args):
+ try:
+ chipid = esp.chip_id()
+ print('Chip ID: 0x%08x' % chipid)
+ except NotSupportedError:
+ print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME)
+ read_mac(esp, args)
+
+
+def erase_flash(esp, args):
+ print('Erasing flash (this may take a while)...')
+ t = time.time()
+ esp.erase_flash()
+ print('Chip erase completed successfully in %.1fs' % (time.time() - t))
+
+
+def erase_region(esp, args):
+ print('Erasing region (may be slow depending on size)...')
+ t = time.time()
+ esp.erase_region(args.address, args.size)
+ print('Erase completed successfully in %.1f seconds.' % (time.time() - t))
+
+
+def run(esp, args):
+ esp.run()
+
+
+def flash_id(esp, args):
+ flash_id = esp.flash_id()
+ print('Manufacturer: %02x' % (flash_id & 0xff))
+ flid_lowbyte = (flash_id >> 16) & 0xFF
+ print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte))
+ print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown")))
+
+
+def read_flash(esp, args):
+ if args.no_progress:
+ flash_progress = None
+ else:
+ def flash_progress(progress, length):
+ msg = '%d (%d %%)' % (progress, progress * 100.0 / length)
+ padding = '\b' * len(msg)
+ if progress == length:
+ padding = '\n'
+ sys.stdout.write(msg + padding)
+ sys.stdout.flush()
+ t = time.time()
+ data = esp.read_flash(args.address, args.size, flash_progress)
+ t = time.time() - t
+ print('\rRead %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...'
+ % (len(data), args.address, t, len(data) / t * 8 / 1000))
+ open(args.filename, 'wb').write(data)
+
+
+def verify_flash(esp, args):
+ differences = False
+
+ for address, argfile in args.addr_filename:
+ image = pad_to(argfile.read(), 4)
+ argfile.seek(0) # rewind in case we need it again
+
+ image = _update_image_flash_params(esp, address, args, image)
+
+ image_size = len(image)
+ print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name))
+ # Try digest first, only read if there are differences.
+ digest = esp.flash_md5sum(address, image_size)
+ expected_digest = hashlib.md5(image).hexdigest()
+ if digest == expected_digest:
+ print('-- verify OK (digest matched)')
+ continue
+ else:
+ differences = True
+ if getattr(args, 'diff', 'no') != 'yes':
+ print('-- verify FAILED (digest mismatch)')
+ continue
+
+ flash = esp.read_flash(address, image_size)
+ assert flash != image
+ diff = [i for i in range(image_size) if flash[i] != image[i]]
+ print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0]))
+ for d in diff:
+ flash_byte = flash[d]
+ image_byte = image[d]
+ if PYTHON2:
+ flash_byte = ord(flash_byte)
+ image_byte = ord(image_byte)
+ print(' %08x %02x %02x' % (address + d, flash_byte, image_byte))
+ if differences:
+ raise FatalError("Verify failed.")
+
+
+def read_flash_status(esp, args):
+ print('Status value: 0x%04x' % esp.read_status(args.bytes))
+
+
+def write_flash_status(esp, args):
+ fmt = "0x%%0%dx" % (args.bytes * 2)
+ args.value = args.value & ((1 << (args.bytes * 8)) - 1)
+ print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes))
+ print(('Setting flash status: ' + fmt) % args.value)
+ esp.write_status(args.value, args.bytes, args.non_volatile)
+ print(('After flash status: ' + fmt) % esp.read_status(args.bytes))
+
+
+def version(args):
+ print(__version__)
+
+#
+# End of operations functions
+#
+
+
+def main():
+ parser = argparse.ArgumentParser(description='esptool.py v%s - ESP8266 ROM Bootloader Utility' % __version__, prog='esptool')
+
+ parser.add_argument('--chip', '-c',
+ help='Target chip type',
+ choices=['auto', 'esp8266', 'esp32'],
+ default=os.environ.get('ESPTOOL_CHIP', 'auto'))
+
+ parser.add_argument(
+ '--port', '-p',
+ help='Serial port device',
+ default=os.environ.get('ESPTOOL_PORT', None))
+
+ parser.add_argument(
+ '--baud', '-b',
+ help='Serial port baud rate used when flashing/reading',
+ type=arg_auto_int,
+ default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD))
+
+ parser.add_argument(
+ '--before',
+ help='What to do before connecting to the chip',
+ choices=['default_reset', 'no_reset', 'no_reset_no_sync'],
+ default=os.environ.get('ESPTOOL_BEFORE', 'default_reset'))
+
+ parser.add_argument(
+ '--after', '-a',
+ help='What to do after esptool.py is finished',
+ choices=['hard_reset', 'soft_reset', 'no_reset'],
+ default=os.environ.get('ESPTOOL_AFTER', 'hard_reset'))
+
+ parser.add_argument(
+ '--no-stub',
+ help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.",
+ action='store_true')
+
+ parser.add_argument(
+ '--trace', '-t',
+ help="Enable trace-level output of esptool.py interactions.",
+ action='store_true')
+
+ parser.add_argument(
+ '--override-vddsdio',
+ help="Override ESP32 VDDSDIO internal voltage regulator (use with care)",
+ choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES,
+ nargs='?')
+
+ subparsers = parser.add_subparsers(
+ dest='operation',
+ help='Run esptool {command} -h for additional help')
+
+ def add_spi_connection_arg(parent):
+ parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. 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).',
+ action=SpiConnectionAction)
+
+ parser_load_ram = subparsers.add_parser(
+ 'load_ram',
+ help='Download an image to RAM and execute')
+ parser_load_ram.add_argument('filename', help='Firmware image')
+
+ parser_dump_mem = subparsers.add_parser(
+ 'dump_mem',
+ help='Dump arbitrary memory to disk')
+ parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int)
+ parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int)
+ parser_dump_mem.add_argument('filename', help='Name of binary dump')
+
+ parser_read_mem = subparsers.add_parser(
+ 'read_mem',
+ help='Read arbitrary memory location')
+ parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int)
+
+ parser_write_mem = subparsers.add_parser(
+ 'write_mem',
+ help='Read-modify-write to arbitrary memory location')
+ parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int)
+ parser_write_mem.add_argument('value', help='Value', type=arg_auto_int)
+ parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int)
+
+ def add_spi_flash_subparsers(parent, is_elf2image):
+ """ Add common parser arguments for SPI flash properties """
+ extra_keep_args = [] if is_elf2image else ['keep']
+ auto_detect = not is_elf2image
+
+ parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency',
+ choices=extra_keep_args + ['40m', '26m', '20m', '80m'],
+ default=os.environ.get('ESPTOOL_FF', '40m' if is_elf2image else 'keep'))
+ parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode',
+ choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'],
+ default=os.environ.get('ESPTOOL_FM', 'qio' if is_elf2image else 'keep'))
+ parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16M)'
+ ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)',
+ action=FlashSizeAction, auto_detect=auto_detect,
+ default=os.environ.get('ESPTOOL_FS', 'detect' if auto_detect else '1MB'))
+ add_spi_connection_arg(parent)
+
+ parser_write_flash = subparsers.add_parser(
+ 'write_flash',
+ help='Write a binary blob to flash')
+ parser_write_flash.add_argument('addr_filename', metavar=' ', help='Address followed by binary filename, separated by space',
+ action=AddrFilenamePairAction)
+ add_spi_flash_subparsers(parser_write_flash, is_elf2image=False)
+ parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
+ parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' +
+ '(mostly superfluous, data is read back during flashing)', action='store_true')
+ compress_args = parser_write_flash.add_mutually_exclusive_group(required=False)
+ compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)',action="store_true", default=None)
+ compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)',action="store_true")
+
+ subparsers.add_parser(
+ 'run',
+ help='Run application code in flash')
+
+ parser_image_info = subparsers.add_parser(
+ 'image_info',
+ help='Dump headers from an application image')
+ parser_image_info.add_argument('filename', help='Image file to parse')
+
+ parser_make_image = subparsers.add_parser(
+ 'make_image',
+ help='Create an application image from binary files')
+ parser_make_image.add_argument('output', help='Output image file')
+ parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file')
+ parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int)
+ parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0)
+
+ parser_elf2image = subparsers.add_parser(
+ 'elf2image',
+ help='Create an application image from ELF file')
+ parser_elf2image.add_argument('input', help='Input ELF file')
+ parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str)
+ parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1','2','3'], default='1')
+
+ add_spi_flash_subparsers(parser_elf2image, is_elf2image=True)
+
+ subparsers.add_parser(
+ 'read_mac',
+ help='Read MAC address from OTP ROM')
+
+ subparsers.add_parser(
+ 'chip_id',
+ help='Read Chip ID from OTP ROM')
+
+ parser_flash_id = subparsers.add_parser(
+ 'flash_id',
+ help='Read SPI flash manufacturer and device ID')
+ add_spi_connection_arg(parser_flash_id)
+
+ parser_read_status = subparsers.add_parser(
+ 'read_flash_status',
+ help='Read SPI flash status register')
+
+ add_spi_connection_arg(parser_read_status)
+ parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1,2,3], default=2)
+
+ parser_write_status = subparsers.add_parser(
+ 'write_flash_status',
+ help='Write SPI flash status register')
+
+ add_spi_connection_arg(parser_write_status)
+ parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true')
+ parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1,2,3], default=2)
+ parser_write_status.add_argument('value', help='New value', type=arg_auto_int)
+
+ parser_read_flash = subparsers.add_parser(
+ 'read_flash',
+ help='Read SPI flash content')
+ add_spi_connection_arg(parser_read_flash)
+ parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int)
+ parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int)
+ parser_read_flash.add_argument('filename', help='Name of binary dump')
+ parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true")
+
+ parser_verify_flash = subparsers.add_parser(
+ 'verify_flash',
+ help='Verify a binary blob against flash')
+ parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space',
+ action=AddrFilenamePairAction)
+ parser_verify_flash.add_argument('--diff', '-d', help='Show differences',
+ choices=['no', 'yes'], default='no')
+ add_spi_flash_subparsers(parser_verify_flash, is_elf2image=False)
+
+ parser_erase_flash = subparsers.add_parser(
+ 'erase_flash',
+ help='Perform Chip Erase on SPI flash')
+ add_spi_connection_arg(parser_erase_flash)
+
+ parser_erase_region = subparsers.add_parser(
+ 'erase_region',
+ help='Erase a region of the flash')
+ add_spi_connection_arg(parser_erase_region)
+ parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int)
+ parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int)
+
+ subparsers.add_parser(
+ 'version', help='Print esptool version')
+
+ # internal sanity check - every operation matches a module function of the same name
+ for operation in subparsers.choices.keys():
+ assert operation in globals(), "%s should be a module function" % operation
+
+ expand_file_arguments()
+
+ args = parser.parse_args()
+
+ print('esptool.py v%s' % __version__)
+
+ # operation function can take 1 arg (args), 2 args (esp, arg)
+ # or be a member function of the ESPLoader class.
+
+ if args.operation is None:
+ parser.print_help()
+ sys.exit(1)
+
+ operation_func = globals()[args.operation]
+
+ if PYTHON2:
+ # This function is depreciated in Python3
+ operation_args = inspect.getargspec(operation_func).args
+ else:
+ operation_args = inspect.getfullargspec(operation_func).args
+
+ if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object
+ if args.before != "no_reset_no_sync":
+ initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate
+ else:
+ initial_baud = args.baud
+
+ ser_list = sorted(ports.device for ports in list_ports.comports())
+ if args.port is None:
+ raise FatalError('Cannot find target port named \'%s\'.' % args.port)
+
+ try:
+ if args.chip == 'auto':
+ esp = ESPLoader.detect_chip(args.port, initial_baud, args.before, args.trace)
+ else:
+ chip_class = {
+ 'esp8266': ESP8266ROM,
+ 'esp32': ESP32ROM,
+ }[args.chip]
+ esp = chip_class(args.port, initial_baud, args.trace)
+ esp.connect(args.before)
+ except FatalError as err:
+ if args.port is not None:
+ raise
+ print("%s failed to connect: %s" % (args.port, err))
+ esp = None
+ if esp is None:
+ raise FatalError("All of the %d available serial ports could not connect to a Espressif device." % len(ser_list))
+
+ print("Chip is %s" % (esp.get_chip_description()))
+
+ print("Features: %s" % ", ".join(esp.get_chip_features()))
+
+ read_mac(esp, args)
+
+ if not args.no_stub:
+ esp = esp.run_stub()
+
+ if args.override_vddsdio:
+ esp.override_vddsdio(args.override_vddsdio)
+
+ if args.baud > initial_baud:
+ try:
+ esp.change_baud(args.baud)
+ except NotImplementedInROMError:
+ print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud)
+
+ # override 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)
+ 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 hasattr(args, "flash_size"):
+ print("Configuring flash size...")
+ detect_flash_size(esp, args)
+ esp.flash_set_parameters(flash_size_bytes(args.flash_size))
+
+ operation_func(esp, args)
+
+ # Handle post-operation behaviour (reset or other)
+ if operation_func == load_ram:
+ # the ESP is now running the loaded image, so let it run
+ print('Exiting immediately.')
+ elif args.after == 'hard_reset':
+ print('Hard resetting via RTS pin...')
+ esp.hard_reset()
+ elif args.after == 'soft_reset':
+ print('Soft resetting...')
+ # flash_finish will trigger a soft reset
+ esp.soft_reset(False)
+ else:
+ print('Staying in bootloader.')
+ if esp.IS_STUB:
+ esp.soft_reset(True) # exit stub back to ROM loader
+
+ esp._port.close()
+
+ else:
+ operation_func(args)
+
+
+def expand_file_arguments():
+ """ Any argument starting with "@" gets replaced with all values read from a text file.
+ Text file arguments can be split by newline or by space.
+ Values are added "as-is", as if they were specified in this order on the command line.
+ """
+ new_args = []
+ expanded = False
+ for arg in sys.argv:
+ if arg.startswith("@"):
+ expanded = True
+ with open(arg[1:],"r") as f:
+ for line in f.readlines():
+ new_args += shlex.split(line)
+ else:
+ new_args.append(arg)
+ if expanded:
+ print("esptool.py %s" % (" ".join(new_args[1:])))
+ sys.argv = new_args
+
+
+class FlashSizeAction(argparse.Action):
+ """ Custom flash size parser class to support backwards compatibility with megabit size arguments.
+
+ (At next major release, remove deprecated sizes and this can become a 'normal' choices= argument again.)
+ """
+ def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs):
+ super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs)
+ self._auto_detect = auto_detect
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ try:
+ value = {
+ '2m': '256KB',
+ '4m': '512KB',
+ '8m': '1MB',
+ '16m': '2MB',
+ '32m': '4MB',
+ '16m-c1': '2MB-c1',
+ '32m-c1': '4MB-c1',
+ }[values[0]]
+ print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0]))
+ print("Please use the equivalent size '%s'." % (value))
+ print("Megabit arguments may be removed in a future release.")
+ except KeyError:
+ value = values[0]
+
+ known_sizes = dict(ESP8266ROM.FLASH_SIZES)
+ known_sizes.update(ESP32ROM.FLASH_SIZES)
+ if self._auto_detect:
+ known_sizes['detect'] = 'detect'
+ if value not in known_sizes:
+ raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys())))
+ setattr(namespace, self.dest, value)
+
+
+class SpiConnectionAction(argparse.Action):
+ """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas.
+ """
+ def __call__(self, parser, namespace, value, option_string=None):
+ if value.upper() == "SPI":
+ value = 0
+ elif value.upper() == "HSPI":
+ value = 1
+ 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)
+ 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.')
+ # 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)
+ setattr(namespace, self.dest, value)
+
+
+class AddrFilenamePairAction(argparse.Action):
+ """ Custom parser class for the address/filename pairs passed as arguments """
+ def __init__(self, option_strings, dest, nargs='+', **kwargs):
+ super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ # validate pair arguments
+ pairs = []
+ for i in range(0,len(values),2):
+ try:
+ address = int(values[i],0)
+ except ValueError:
+ raise argparse.ArgumentError(self,'Address "%s" must be a number' % values[i])
+ try:
+ argfile = open(values[i + 1], 'rb')
+ except IOError as e:
+ raise argparse.ArgumentError(self, e)
+ except IndexError:
+ raise argparse.ArgumentError(self,'Must be pairs of an address and the binary filename to write there')
+ pairs.append((address, argfile))
+
+ # Sort the addresses and check for overlapping
+ end = 0
+ for address, argfile in sorted(pairs):
+ argfile.seek(0,2) # seek to end
+ size = argfile.tell()
+ argfile.seek(0)
+ sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)
+ sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1
+ if sector_start < end:
+ message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name)
+ raise argparse.ArgumentError(self, message)
+ end = sector_end
+ setattr(namespace, self.dest, pairs)
+
+
+# Binary stub code (see flasher_stub dir for source & details)
+ESP8266ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b"""
+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\
+nORxF3HqX/8XohBRvg==\
+""")))
+ESP32ROM.STUB_CODE = eval(zlib.decompress(base64.b64decode(b"""
+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\
+""")))
+
+
+def _main():
+ try:
+ main()
+ except FatalError as e:
+ print('\nA fatal error occurred: %s' % e)
+ sys.exit(2)
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/dist/tools/flake8/check.sh b/dist/tools/flake8/check.sh
index f8705ffe15..1595fd7931 100755
--- a/dist/tools/flake8/check.sh
+++ b/dist/tools/flake8/check.sh
@@ -28,7 +28,8 @@ EXCLUDE="^(.+/vendor/\
|dist/tools/mcuboot\
|dist/tools/uhcpd\
|dist/tools/stm32loader\
-|dist/tools/suit_v4/suit_manifest_encoder_04)"
+|dist/tools/suit_v4/suit_manifest_encoder_04)\
+|dist/tools/esptool"
FILEREGEX='(\.py$|pyterm$)'
FILES=$(FILEREGEX=${FILEREGEX} EXCLUDE=${EXCLUDE} changed_files)