Skip to content

Commit

Permalink
Merge pull request #526 from Josverl:flash-stm32-ancient-dragon
Browse files Browse the repository at this point in the history
Add support for flashing STM32 devices using STM32CubeProgrammer CLI on Linux
  • Loading branch information
Josverl authored Mar 12, 2024
2 parents 766a37c + 82daa5d commit 010cb75
Show file tree
Hide file tree
Showing 27 changed files with 3,588 additions and 802 deletions.
9 changes: 5 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,19 @@
"justMyCode": false
},
{
"name": "mptool: Module",
"name": "CLI: mpflash",
"type": "python",
"request": "launch",
"module": "mptool.cli_main",
"module": "mpflash.cli_main",
"justMyCode": true,
// "sudo": true,
"cwd": "${workspaceFolder}",
"args": [
// "-V",
"--ignore",
"/dev/ttyAMA0",
// "--ignore",
// "/dev/ttyAMA0",
"list",
// "--no-erase",
// "--version",
// "v1.22.2",
// "--serial",
Expand Down
22 changes: 1 addition & 21 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,7 @@
],
"terminal.integrated.persistentSessionReviveProcess": "never",
"workbench.colorCustomizations": {
"activityBar.activeBorder": "#0bb45d",
"activityBar.activeBackground": "#914b09",
"activityBar.background": "#914b09",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#0bb45d",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"editorGroup.border": "#914b09",
"panel.border": "#914b09",
"sash.hoverBorder": "#914b09",
"statusBar.background": "#613206",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#914b09",
"statusBarItem.remoteBackground": "#613206",
"statusBarItem.remoteForeground": "#e7e7e7",
"tab.activeBorder": "#914b09",
"titleBar.activeBackground": "#613206",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#61320699",
"titleBar.inactiveForeground": "#e7e7e799"
"activityBar.activeBorder": "#0bb45d"
},
"python.terminal.activateEnvInCurrentTerminal": true,
"todo-tree.filtering.ignoreGitSubmodules": true,
Expand Down
18 changes: 17 additions & 1 deletion src/mpflash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,23 @@ mpflash has been tested on Windows x64, Linux X64 and ARM64, but not (yet) macOS
## Installation
To install mpflash, you can use pip: `pip install mpflash`

## How to use
## Basic usage
You can use mpflash to perform various operations on your MicroPython boards. Here is an example of basic usage:

| Command | Description |
|---------|-------------|
| `mpflash list` | List the connected board(s) including their firmware details |
| `mpflash download` | Download the MicroPython firmware(s) for the connected board(s) |
| `mpflash flash` | Flash the latest stable firmware to the connected board(s) |


## Linux permissions for usb devices
In order to flash the firmware to the board, you need to have the correct permissions to access the USB devices.
On Windows this will not be an issue, but on Linux you can use udev rules to give non-root users access to the USB devices.
[See thestm32_permissions documentation](./stm32_udev_rules.md) for more information.


## Advanced use
You can list the connected boards using the following command:
```bash
$ mpflash list
Expand Down
41 changes: 41 additions & 0 deletions src/mpflash/mpflash/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from .vendored import pydfu as pydfu
from argparse import Namespace
from pathlib import Path


def main():
print("Hello, DFU!")
dfu_file = Path("/home/jos/projects/micropython/ports/stm32/build-PYBV11/firmware.dfu")

print("List ANY DFU devices...")
try:
pydfu.list_dfu_devices()
except ValueError as e:
print(f"Insuffient permissions to access usb DFU devices: {e}")
exit(1)

kwargs = {"idVendor": 0x0483, "idProduct": 0xDF11}
print("List SPECIFIED DFU devices...")
pydfu.list_dfu_devices(**kwargs)

# Needs to be a list of serial ports
print("Inititialize pydfu...")
pydfu.init(**kwargs)

# print("Mass erase...")
# pydfu.mass_erase()

print("Read DFU file...")
elements = pydfu.read_dfu_file(dfu_file)
if not elements:
print("No data in dfu file")
return
print("Writing memory...")
pydfu.write_elements(elements, False, progress=pydfu.cli_progress)

print("Exiting DFU...")
pydfu.exit_dfu()


if __name__ == "__main__":
main()
31 changes: 17 additions & 14 deletions src/mpflash/mpflash/common.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
from pathlib import Path
from typing import Dict, Union
from typing import TypedDict

import platformdirs
from github import Github
from loguru import logger as log
from packaging.version import parse


PORT_FWTYPES = {
"stm32": ".hex",
"esp32": ".bin",
"esp8266": ".bin",
"rp2": ".uf2",
"samd": ".uf2",
"mimxrt": ".hex",
"nrf": ".uf2",
"renesas-ra": ".hex",
"stm32": [".hex", ".dfu"], # .hex for cube cli, but need .dfu for pydfu.py
"esp32": [".bin"],
"esp8266": [".bin"],
"rp2": [".uf2"],
"samd": [".uf2"],
"mimxrt": [".hex"],
"nrf": [".uf2"],
"renesas-ra": [".hex"],
}

DEFAULT_FW_PATH = platformdirs.user_downloads_path() / "firmware"
# DEFAULT_FW_PATH = Path.home() / "mp_firmware"

FWInfo = Dict[str, Union[str, bool]]
class FWInfo(TypedDict):
filename: str
port:str
board:str
variant:str
preview: bool
version:str

#############################################################
# Version handling copied from stubber/utils/versions.py
Expand Down
4 changes: 4 additions & 0 deletions src/mpflash/mpflash/config.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
"""centralized configuration for mpflash"""

import platformdirs

from typing import List
from pathlib import Path


class MPtoolConfig:
"""Centralized configuration for mpflash"""

quiet: bool = False
ignore_ports: List[str] = []
firmware_folder: Path = platformdirs.user_downloads_path() / "firmware"


config = MPtoolConfig()
38 changes: 14 additions & 24 deletions src/mpflash/mpflash/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
from mpflash.list import list_mcus

from .cli_group import cli
from .common import DEFAULT_FW_PATH, PORT_FWTYPES, clean_version
from .common import PORT_FWTYPES, clean_version
from .config import config

MICROPYTHON_ORG_URL = "https://micropython.org/"

Expand All @@ -30,21 +31,6 @@
# regex to extract the version from the firmware filename
RE_VERSION_PREVIEW = r"(\d+\.\d+(\.\d+)?(-\w+.\d+)?)"

# # boards we are interested in ( this avoids getting a lot of boards that are not relevant)
# DEFAULT_BOARDS = [
# "PYBV11",
# "ESP8266_GENERIC",
# "ESP32_GENERIC",
# "ESP32_GENERIC_S3",
# "RPI_PICO",
# "RPI_PICO_W",
# "ADAFRUIT_QTPY_RP2040",
# "ARDUINO_NANO_RP2040_CONNECT",
# "PIMORONI_PICOLIPO_16MB",
# "SEEED_WIO_TERMINAL",
# "PARTICLE_XENON",
# ]


# use functools.lru_cache to avoid needing to download pages multiple times
@functools.lru_cache(maxsize=500)
Expand Down Expand Up @@ -108,7 +94,10 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar

for board in track(_urls, description=f"Checking {port} download pages", transient=True):
# add a board to the list for each firmware found
firmwares = firmware_list(board["url"], MICROPYTHON_ORG_URL, PORT_FWTYPES[port])
firmwares = []
for ext in PORT_FWTYPES[port]:
firmwares += firmware_list(board["url"], MICROPYTHON_ORG_URL, ext)

for _url in firmwares:
board["firmware"] = _url
board["preview"] = "preview" in _url # type: ignore
Expand All @@ -127,19 +116,20 @@ def get_boards(ports: List[str], boards: List[str], clean: bool) -> List[Firmwar
# remove hash from firmware name
fname = re.sub(RE_HASH, ".", fname)
board["filename"] = fname
board["ext"] = Path(board["firmware"]).suffix
board["variant"] = board["filename"].split("-v")[0] if "-v" in board["filename"] else ""
board_urls.append(board.copy())
return board_urls


def key_fw_variant_ver(x: FirmwareInfo):
def key_fw_ver_pre_ext_bld(x: FirmwareInfo):
"sorting key for the retrieved board urls"
return x["variant"], x["version"], x["preview"], x["build"]
return x["variant"], x["version"], x["preview"], x["ext"], x["build"]


def key_fw_variant(x: FirmwareInfo):
def key_fw_var_pre_ext(x: FirmwareInfo):
"Grouping key for the retrieved board urls"
return x["variant"], x["preview"]
return x["variant"], x["preview"], x["ext"]


def download_firmwares(
Expand Down Expand Up @@ -193,7 +183,7 @@ def download_firmwares(
def get_firmware_list(ports: List[str], boards: List[str], versions: List[str], preview: bool, clean: bool):
log.trace("Checking MicroPython download pages")

board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_variant_ver)
board_urls = sorted(get_boards(ports, boards, clean), key=key_fw_ver_pre_ext_bld)

log.debug(f"Total {len(board_urls)} firmwares")
relevant = [
Expand All @@ -205,7 +195,7 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
log.debug(f"Matching firmwares: {len(relevant)}")
# select the unique boards
unique_boards: List[FirmwareInfo] = []
for _, g in itertools.groupby(relevant, key=key_fw_variant):
for _, g in itertools.groupby(relevant, key=key_fw_var_pre_ext):
# list is aleady sorted by build so we can just get the last item
sub_list = list(g)
unique_boards.append(sub_list[-1])
Expand All @@ -221,7 +211,7 @@ def get_firmware_list(ports: List[str], boards: List[str], versions: List[str],
"--destination",
"-d",
type=click.Path(file_okay=False, dir_okay=True, path_type=Path),
default=DEFAULT_FW_PATH,
default=config.firmware_folder,
show_default=True,
help="The folder to download the firmware to.",
)
Expand Down
2 changes: 1 addition & 1 deletion src/mpflash/mpflash/flash_esp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import esptool
from loguru import logger as log

from stubber.bulk.mpremoteboard import MPRemoteBoard
from .mpremoteboard.mpremoteboard import MPRemoteBoard


def flash_esp(
Expand Down
Loading

0 comments on commit 010cb75

Please sign in to comment.