From d742c2ff136462c90e8c671673bc638c8d2a712f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Wed, 3 Feb 2021 08:00:08 +0100 Subject: [PATCH] Bump versions --- .DS_Store | Bin 0 -> 6148 bytes About.py | 5 +- Main.py | 19 +- build-on-mac.spec | 2 +- build-on-win.spec | 2 +- build.bat | 1 + build.sh | 7 +- esptool-py-why-here.txt | 12 - esptool.py | 2959 --------------------------------------- requirements.txt | 8 +- 10 files changed, 26 insertions(+), 2989 deletions(-) create mode 100644 .DS_Store delete mode 100644 esptool-py-why-here.txt delete mode 100755 esptool.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..792a8cd9d661a39554f6a13352f0fc40a110c87e GIT binary patch literal 6148 zcmeHKOKQVF43(Nt4B2Fvs*@T%eycA<*E3>E5fJt4Hh8hd^+=33L;lKzcJ8 zy)k|Z%MuZ7zxP{_wTLuuL;0~VH9I$-*i&W{2*(|tG9329$9vy@O{#AvjQcDnIm??I z@8I9Qm02o41*iZOpaN82P66w^ux1s=NCl_>6}Tv1--iM>tchcwe>xC+1OP6PcEj3d z31G1VuqKXy$iOtHz@Tc57#eisOV-uIF)-+&Iech7S#v^Be>(0jUM^Y#8L0pjxK>~h z+qL!o7x>Kle@)_!3Q&QEQb3p8u50m1*;^+sXT7$-U*T5s3pc~sDG1(a>v39)i cqNppj#(7N~1D%e%(}DaMFkNU=;J+341326jdH?_b literal 0 HcmV?d00001 diff --git a/About.py b/About.py index 2572082..02bcaae 100644 --- a/About.py +++ b/About.py @@ -2,6 +2,7 @@ # coding=utf-8 import sys +import datetime import os import wx import wx.html @@ -36,7 +37,7 @@ class AboutDlg(wx.Dialog):

-

© 2020 Marcel Stör. Licensed under MIT.

+

© {2} Marcel Stör. Licensed under MIT.

@@ -54,7 +55,7 @@ class AboutDlg(wx.Dialog): html = HtmlWindow(self, wx.ID_ANY, size=(420, -1)) if "gtk2" in wx.PlatformInfo or "gtk3" in wx.PlatformInfo: html.SetStandardFonts() - txt = self.text.format(self._get_bundle_dir(), __version__) + txt = self.text.format(self._get_bundle_dir(), __version__, datetime.datetime.now().year) html.SetPage(txt) ir = html.GetInternalRepresentation() html.SetSize((ir.GetWidth() + 25, ir.GetHeight() + 25)) diff --git a/Main.py b/Main.py index 5a6552c..8c1cbb6 100644 --- a/Main.py +++ b/Main.py @@ -13,12 +13,8 @@ import json import images as images from serial import SerialException from serial.tools import list_ports -from esptool import ESPLoader -from esptool import NotImplementedInROMError -from esptool import FatalError -from argparse import Namespace -__version__ = "4.0" +__version__ = "5.0.0" __flash_help__ = '''

This setting is highly dependent on your device!

@@ -61,6 +57,11 @@ class RedirectText: # noinspection PyStatementEffect None + # esptool >=3 handles output differently of the output stream is not a TTY + # noinspection PyMethodMayBeStatic + def isatty(self): + return True + # --------------------------------------------------------------------------- @@ -83,6 +84,8 @@ class FlashingThread(threading.Thread): command.extend(["--baud", str(self._config.baud), "--after", "no_reset", "write_flash", + # https://github.com/espressif/esptool/issues/599 + "--flash_size", "detect", "--flash_mode", self._config.mode, "0x00000", self._config.firmware_path]) @@ -209,7 +212,7 @@ class NodeMcuFlasher(wx.Frame): self._select_configured_port() bmp = images.Reload.GetBitmap() reload_button = wx.BitmapButton(panel, id=wx.ID_ANY, bitmap=bmp, - size=(bmp.GetWidth() + 7, bmp.GetHeight() + 7)) + size=(bmp.GetWidth() + 2, bmp.GetHeight() + 2)) reload_button.Bind(wx.EVT_BUTTON, on_reload) reload_button.SetToolTip("Reload serial device list") @@ -219,7 +222,7 @@ class NodeMcuFlasher(wx.Frame): serial_boxsizer = wx.BoxSizer(wx.HORIZONTAL) serial_boxsizer.Add(self.choice, 1, wx.EXPAND) serial_boxsizer.AddStretchSpacer(0) - serial_boxsizer.Add(reload_button, 0, wx.ALIGN_RIGHT, 20) + serial_boxsizer.Add(reload_button) baud_boxsizer = wx.BoxSizer(wx.HORIZONTAL) @@ -298,7 +301,7 @@ class NodeMcuFlasher(wx.Frame): flashmode_label_boxsizer = wx.BoxSizer(wx.HORIZONTAL) flashmode_label_boxsizer.Add(flashmode_label, 1, wx.EXPAND) flashmode_label_boxsizer.AddStretchSpacer(0) - flashmode_label_boxsizer.Add(icon, 0, wx.ALIGN_RIGHT, 20) + flashmode_label_boxsizer.Add(icon) erase_label = wx.StaticText(panel, label="Erase flash") console_label = wx.StaticText(panel, label="Console") diff --git a/build-on-mac.spec b/build-on-mac.spec index 209af1d..0b59bf0 100644 --- a/build-on-mac.spec +++ b/build-on-mac.spec @@ -25,6 +25,6 @@ exe = EXE(pyz, upx=True, console=False , icon='images/icon-256.icns') app = BUNDLE(exe, - name='NodeMCU-PyFlasher-4.0.app', + name='NodeMCU-PyFlasher-5.0.0.app', icon='./images/icon-256.icns', bundle_identifier='com.frightanic.nodemcu-pyflasher') diff --git a/build-on-win.spec b/build-on-win.spec index 272710d..c6c9b33 100644 --- a/build-on-win.spec +++ b/build-on-win.spec @@ -19,7 +19,7 @@ exe = EXE(pyz, a.binaries, a.zipfiles, a.datas, - name='NodeMCU-PyFlasher-4.0', + name='NodeMCU-PyFlasher-5.0.0', debug=False, strip=False, upx=True, diff --git a/build.bat b/build.bat index 8749fba..04fc46f 100755 --- a/build.bat +++ b/build.bat @@ -1,3 +1,4 @@ pyinstaller --log-level=DEBUG ^ --noconfirm ^ + --windowed ^ build-on-win.spec diff --git a/build.sh b/build.sh index 9fe9994..726dca4 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,13 @@ #!/usr/bin/env bash -#rm -fr build dist -VERSION=4.0 +# rm -fr build dist +VERSION=5.0.0 NAME=NodeMCU-PyFlasher pyinstaller --log-level=DEBUG \ --noconfirm \ + --windowed \ build-on-mac.spec -#https://github.com/sindresorhus/create-dmg +# https://github.com/sindresorhus/create-dmg create-dmg dist/$NAME-$VERSION.app mv "$NAME-$VERSION 0.0.0.dmg" dist/$NAME-$VERSION.dmg diff --git a/esptool-py-why-here.txt b/esptool-py-why-here.txt deleted file mode 100644 index 4ff2b2d..0000000 --- a/esptool-py-why-here.txt +++ /dev/null @@ -1,12 +0,0 @@ -If esptool.py is installed using python setup.py` from a checked out version it creates something like the following -in the Python environment - -esptool.py file containing - -#!/usr/local/opt/python/bin/python2.7 -# EASY-INSTALL-SCRIPT: 'esptool==1.3.dev0','esptool.py' -__requires__ = 'esptool==1.3.dev0' -__import__('pkg_resources').run_script('esptool==1.3.dev0', 'esptool.py') - -PyInstaller (and cx_Freeze) doesn't support pkg_resources and complains about 'ImportError: "No module named -pkg_resources"'. This can be avoided if the application maintains a local copy of esptool.py. \ No newline at end of file diff --git a/esptool.py b/esptool.py deleted file mode 100755 index 7561c4b..0000000 --- a/esptool.py +++ /dev/null @@ -1,2959 +0,0 @@ -#!/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 - -try: - import serial -except ImportError: - print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable)) - raise - -# 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 - -try: - import serial.tools.list_ports as list_ports -except ImportError: - print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). " - "Check the README for installation instructions." % (sys.VERSION, sys.executable)) - raise - -__version__ = "2.6" - -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 -DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write - - -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 - # set write timeout, to prevent esptool blocked at write forever. - try: - self._port.write_timeout = DEFAULT_SERIAL_WRITE_TIMEOUT - except NotImplementedError: - # no write timeout for RFC2217 ports - # need to set the property back to None or it will continue to fail - self._port.write_timeout = None - - 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) - try: - 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, end='') - return inst - finally: - print('') # end line - 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) - - 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"] - - blk3_part_res = word3 >> 14 & 0x1 - if blk3_part_res: - features += ["BLK3 partially reserved"] - - word6 = self.read_efuse(6) - coding_scheme = word6 & 0x3 - features += ["Coding Scheme %s" % { - 0: "None", - 1: "3/4", - 2: "Repeat (UNSUPPORTED)", - 3: "Invalid"}[coding_scheme]] - - 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 - if self.addr != 0: - self.pad_to_alignment(4) # pad all "real" ImageSegments 4 byte aligned 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 - SHA256_DIGEST_LEN = 32 - - """ Base class with common firmware image functions """ - def __init__(self): - self.segments = [] - self.entrypoint = 0 - self.elf_sha256 = None - self.elf_sha256_offset = 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 segment count %d (max 16). Usually this indicates a linker script problem.' % len(self.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 maybe_patch_segment_data(self, f, segment_data): - """If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data.""" - segment_len = len(segment_data) - file_pos = f.tell() - if self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len: - # SHA256 digest needs to be patched into this segment, - # calculate offset of the digest inside the segment. - patch_offset = self.elf_sha256_offset - file_pos - # Sanity checks - if patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len: - raise FatalError('Can not place SHA256 digest on segment boundary' + - '(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)' % - (self.elf_sha256_offset, file_pos, segment_len)) - assert(len(self.elf_sha256) == self.SHA256_DIGEST_LEN) - # offset relative to the data part - patch_offset -= self.SEG_HEADER_LEN - segment_data = segment_data[0:patch_offset] + self.elf_sha256 + \ - segment_data[patch_offset + self.SHA256_DIGEST_LEN:] - return segment_data - - def save_segment(self, f, segment, checksum=None): - """ Save the next segment to the image file, return next checksum value if provided """ - segment_data = self.maybe_patch_segment_data(f, segment.data) - 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) - - self.verify() - - 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) - - self.verify() - - 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 - for segment in flash_segments[1:]: - if segment.addr // self.IROM_ALIGN == last_addr // self.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 % self.IROM_ALIGN) - self.SEG_HEADER_LEN - pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past - if pad_len == 0 or pad_len == self.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 += self.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) % self.IROM_ALIGN == segment.addr % self.IROM_ALIGN - checksum = self.save_flash_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 - - if self.secure_pad: - # pad the image so that after signing it will end on a a 64KB boundary. - # This ensures all mapped flash content will be verified. - if not self.append_digest: - raise FatalError("secure_pad only applies if a SHA-256 digest is also appended to the image") - align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN - # 16 byte aligned checksum (force the alignment to simplify calculations) - checksum_space = 16 - # after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment - space_after_checksum = 32 + 4 + 64 + 12 - pad_len = (self.IROM_ALIGN - align_past - checksum_space - space_after_checksum) % self.IROM_ALIGN - pad_segment = ImageSegment(0, b'\x00' * pad_len, f.tell()) - - checksum = self.save_segment(f, pad_segment, checksum) - total_segments += 1 - - # done writing segments - self.append_checksum(f, checksum) - image_length = f.tell() - - if self.secure_pad: - assert ((image_length + space_after_checksum) % self.IROM_ALIGN) == 0 - - # kinda hacky: go back to the initial header and write the new segment count - # that includes padding segments. This header is not checksummed - 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 save_flash_segment(self, f, segment, checksum=None): - """ Save the next segment to the image file, return next checksum value if provided """ - segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN - segment_len_remainder = segment_end_pos % self.IROM_ALIGN - if segment_len_remainder < 0x24: - # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the - # last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary. - segment.data += b'\x00' * (0x24 - segment_len_remainder) - return self.save_segment(f, segment, checksum) - - 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(" 0] - self.sections = prog_sections - - def sha256(self): - # return SHA256 hash of the input ELF file - sha256 = hashlib.sha256() - with open(self.name, 'rb') as f: - sha256.update(f.read()) - return sha256.digest() - - -def slip_reader(port, trace_function): - """Generator to read SLIP packets from a serial port. - Yields one full SLIP packet at a time, raises exception on timeout or invalid data. - - Designed to avoid too many calls to serial.read(1), which can bog - down on slow systems. - """ - partial_packet = None - in_escape = False - while True: - waiting = port.inWaiting() - read_bytes = port.read(1 if waiting == 0 else waiting) - if read_bytes == b'': - waiting_for = "header" if partial_packet is None else "content" - trace_function("Timed out waiting for packet %s", waiting_for) - raise FatalError("Timed out waiting for packet %s" % waiting_for) - trace_function("Read %d bytes: %s", len(read_bytes), HexFormatter(read_bytes)) - for b in read_bytes: - if type(b) is int: - b = bytes([b]) # python 2/3 compat - - if partial_packet is None: # waiting for packet header - if b == b'\xc0': - partial_packet = b"" - else: - trace_function("Read invalid data: %s", HexFormatter(read_bytes)) - trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) - raise FatalError('Invalid head of packet (0x%s)' % hexify(b)) - elif in_escape: # part-way through escape sequence - in_escape = False - if b == b'\xdc': - partial_packet += b'\xc0' - elif b == b'\xdd': - partial_packet += b'\xdb' - else: - trace_function("Read invalid data: %s", HexFormatter(read_bytes)) - trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) - raise FatalError('Invalid SLIP escape (0xdb, 0x%s)' % (hexify(b))) - elif b == b'\xdb': # start of escape sequence - in_escape = True - elif b == b'\xc0': # end of packet - trace_function("Received full packet: %s", HexFormatter(partial_packet)) - yield partial_packet - partial_packet = None - else: # normal byte in packet - partial_packet += b - - -def arg_auto_int(x): - return int(x, 0) - - -def div_roundup(a, b): - """ Return a/b rounded up to nearest integer, - equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only - without possible floating point accuracy errors. - """ - return (int(a) + int(b) - 1) // int(b) - - -def align_file_position(f, size): - """ Align the position in the file to the next block of specified size """ - align = (size - 1) - (f.tell() % size) - f.seek(align, 1) - - -def flash_size_bytes(size): - """ Given a flash size of the type passed in args.flash_size - (ie 512KB or 1MB) then return the size in bytes. - """ - if "MB" in size: - return int(size[:size.index("MB")]) * 1024 * 1024 - elif "KB" in size: - return int(size[:size.index("KB")]) * 1024 - else: - raise FatalError("Unknown size %s" % size) - - -def hexify(s, uppercase=True): - format_str = '%02X' if uppercase else '%02x' - if not PYTHON2: - return ''.join(format_str % c for c in s) - else: - return ''.join(format_str % ord(c) for c in s) - - -class HexFormatter(object): - """ - Wrapper class which takes binary data in its constructor - and returns a hex string as it's __str__ method. - - This is intended for "lazy formatting" of trace() output - in hex format. Avoids overhead (significant on slow computers) - of generating long hex strings even if tracing is disabled. - - Note that this doesn't save any overhead if passed as an - argument to "%", only when passed to trace() - - If auto_split is set (default), any long line (> 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): - with open(args.filename, 'wb') as f: - 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) - - if args.erase_all: - erase_flash(esp, args) - - 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 * 2) - 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): - with open(seg, 'rb') as f: - data = f.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() - image.secure_pad = args.secure_pad - elif args.version == '1': # ESP8266 - image = ESP8266ROMFirmwareImage() - else: - image = ESP8266V2FirmwareImage() - 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.elf_sha256_offset: - image.elf_sha256 = e.sha256() - image.elf_sha256_offset = args.elf_sha256_offset - - image.verify() - - 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)) - with open(args.filename, 'wb') as f: - f.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(custom_commandline=None): - """ - Main function for esptool - - custom_commandline - Optional override for default arguments parsing (that uses sys.argv), can be a list of custom arguments - as strings. - """ - 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) - parser_write_flash.add_argument('--erase-all', '-e', - help='Erase all regions of flash (not just write areas) before programming', - action="store_true") - - 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'], default='1') - parser_elf2image.add_argument('--secure-pad', action='store_true', help='Pad image so once signed it will end on a 64KB boundary. For ESP32 images only.') - parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.', - type=arg_auto_int, default=None) - - 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(custom_commandline) - - 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 - - if args.port is None: - ser_list = sorted(ports.device for ports in list_ports.comports()) - print("Found %d serial ports" % len(ser_list)) - else: - ser_list = [args.port] - esp = None - for each_port in reversed(ser_list): - print("Serial port %s" % each_port) - try: - if args.chip == 'auto': - esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace) - else: - chip_class = { - 'esp8266': ESP8266ROM, - 'esp32': ESP32ROM, - }[args.chip] - esp = chip_class(each_port, initial_baud, args.trace) - esp.connect(args.before) - break - except (FatalError, OSError) as err: - if args.port is not None: - raise - print("%s failed to connect: %s" % (each_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)) - - try: - operation_func(esp, args) - finally: - try: # Clean up AddrFilenamePairAction files - for address, argfile in args.addr_filename: - argfile.close() - except AttributeError: - pass - - # 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 relase, 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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\ -"""))) -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() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e8dcea0..7283483 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -esptool==2.6 -wxPython==4.0.4 -PyInstaller==3.6 +esptool>=3.0 +pyserial~=3.5 +wxPython~=4.1.1 +PyInstaller~=4.2 +httplib2>=0.18.1