pyflashgui 0.1.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyflashgui-0.2.1/PKG-INFO +127 -0
- pyflashgui-0.2.1/PYPI.md +87 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/README.md +19 -10
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/__init__.py +1 -1
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/__main__.py +14 -2
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/backends/base.py +69 -5
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/backends/jlink_backend.py +81 -9
- pyflashgui-0.2.1/pyflashgui/backends/pyocd_backend.py +646 -0
- pyflashgui-0.2.1/pyflashgui/backends/pyocd_packs.py +397 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/backends/simulated.py +37 -5
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/excepthook.py +6 -7
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/flasher.py +243 -47
- pyflashgui-0.2.1/pyflashgui/core/library_log.py +96 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/memory_map.py +36 -2
- pyflashgui-0.2.1/pyflashgui/core/pack_manager.py +174 -0
- pyflashgui-0.2.1/pyflashgui/core/shortcut.py +259 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/worker.py +1 -2
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/__init__.py +1 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/about.md +26 -2
- pyflashgui-0.2.1/pyflashgui/help/changelog.md +182 -0
- pyflashgui-0.2.1/pyflashgui/help/console.md +70 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/files.md +8 -10
- pyflashgui-0.2.1/pyflashgui/help/main-window.png +0 -0
- pyflashgui-0.2.1/pyflashgui/help/manual.md +58 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/memory-map.md +2 -1
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/memory.md +15 -5
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/help/probe.md +31 -9
- pyflashgui-0.2.1/pyflashgui/resources/tick.svg +7 -0
- pyflashgui-0.2.1/pyflashgui/ui/buttons.py +40 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/confirm.py +8 -0
- pyflashgui-0.2.1/pyflashgui/ui/console_view.py +349 -0
- pyflashgui-0.2.1/pyflashgui/ui/crop_dialog.py +171 -0
- pyflashgui-0.2.1/pyflashgui/ui/diagnostics.py +264 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/files_view.py +192 -71
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/folders.py +2 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/fonts.py +1 -1
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/help_menu.py +33 -13
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/main_window.py +351 -34
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/memory_view.py +73 -33
- pyflashgui-0.2.1/pyflashgui/ui/packs_dialog.py +445 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/panes.py +17 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/probe_bar.py +109 -14
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/read_dialog.py +4 -3
- pyflashgui-0.2.1/pyflashgui/ui/scripting.py +350 -0
- pyflashgui-0.2.1/pyflashgui/ui/scripts_menu.py +138 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/session.py +7 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/theme.py +68 -4
- pyflashgui-0.2.1/pyflashgui/ui/workspace_menu.py +254 -0
- pyflashgui-0.2.1/pyflashgui.egg-info/PKG-INFO +127 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/SOURCES.txt +17 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/requires.txt +1 -1
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyproject.toml +5 -2
- pyflashgui-0.2.1/tests/test_backends.py +718 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_compare.py +16 -4
- pyflashgui-0.2.1/tests/test_console.py +283 -0
- pyflashgui-0.2.1/tests/test_diagnostics.py +174 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_flash_panes.py +230 -15
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_flasher.py +54 -2
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_jlink_backend.py +38 -1
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_launcher.py +124 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_memory_map.py +22 -3
- pyflashgui-0.2.1/tests/test_packs.py +529 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_read_back.py +163 -32
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_readme.py +3 -2
- pyflashgui-0.2.1/tests/test_scripts_menu.py +100 -0
- pyflashgui-0.2.1/tests/test_shortcut.py +251 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_theme.py +65 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_workspaces.py +208 -2
- pyflashgui-0.1.0/PKG-INFO +0 -80
- pyflashgui-0.1.0/PYPI.md +0 -40
- pyflashgui-0.1.0/pyflashgui/backends/pyocd_backend.py +0 -271
- pyflashgui-0.1.0/pyflashgui/core/shortcut.py +0 -154
- pyflashgui-0.1.0/pyflashgui/help/changelog.md +0 -46
- pyflashgui-0.1.0/pyflashgui/help/main-window.png +0 -0
- pyflashgui-0.1.0/pyflashgui/help/manual.md +0 -44
- pyflashgui-0.1.0/pyflashgui.egg-info/PKG-INFO +0 -80
- pyflashgui-0.1.0/tests/test_backends.py +0 -251
- pyflashgui-0.1.0/tests/test_shortcut.py +0 -108
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/LICENSE +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/NOTICE +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/backends/__init__.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/__init__.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/checkout.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/compare.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/context.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/events.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/images.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/installation.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/layout.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/packages.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/paths.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/settings.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/slow_start.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/timing.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/updates.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/core/workspaces.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/resources/__init__.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.ico +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.png +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui.svg +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/resources/pyflashgui_small.svg +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/__init__.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/detached.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/erase_dialog.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/event_colours.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/map_view.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/messages.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/pane_bar.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui/ui/persist.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/dependency_links.txt +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/entry_points.txt +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/pyflashgui.egg-info/top_level.txt +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/setup.cfg +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_build_version.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_changelog.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_checkout.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_erase.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_events.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_excepthook.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_folders.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_fonts.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_headers.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_help.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_icon.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_images.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_installation.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_messages.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_notice.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_packages.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_packaging.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_persist.py +0 -0
- {pyflashgui-0.1.0 → pyflashgui-0.2.1}/tests/test_slow_start.py +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyflashgui
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Graphical flash programming tool: Arm Cortex-M with any probe pyOCD supports, and more through J-Link Commander
|
|
5
|
+
Author: davhodg
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/davhodg/pyflashgui
|
|
8
|
+
Project-URL: Documentation, https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md
|
|
9
|
+
Project-URL: Repository, https://github.com/davhodg/pyflashgui
|
|
10
|
+
Project-URL: Issues, https://github.com/davhodg/pyflashgui/issues
|
|
11
|
+
Project-URL: Discussions, https://github.com/davhodg/pyflashgui/discussions
|
|
12
|
+
Project-URL: Releases, https://github.com/davhodg/pyflashgui/releases
|
|
13
|
+
Project-URL: Changelog, https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/changelog.md
|
|
14
|
+
Keywords: flash,programmer,debug probe,SWD,CMSIS-DAP,ST-Link,J-Link,pyOCD,Cortex-M,microcontroller,embedded,firmware,Intel HEX,S-record,ELF,Qt
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
26
|
+
Classifier: Topic :: System :: Hardware
|
|
27
|
+
Requires-Python: >=3.12
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
License-File: NOTICE
|
|
31
|
+
Requires-Dist: PySide6-Essentials>=6.8
|
|
32
|
+
Requires-Dist: pyocd>=0.46
|
|
33
|
+
Requires-Dist: bincopy>=20.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff==0.16.*; extra == "dev"
|
|
38
|
+
Requires-Dist: trove-classifiers; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<img src="https://raw.githubusercontent.com/davhodg/pyflashgui/master/pyflashgui/resources/pyflashgui.png" alt="pyflashgui icon: a chip with pins on every side and a flash across it" width="128">
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
# pyflashgui
|
|
46
|
+
|
|
47
|
+
[](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
|
|
48
|
+
[](https://github.com/davhodg/pyflashgui/releases)
|
|
49
|
+
[](https://pypi.org/project/pyflashgui/)
|
|
50
|
+

|
|
51
|
+

|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
A graphical flash programming tool. It works with any debug probe [pyOCD](https://pyocd.io) supports, for Arm Cortex-M targets: CMSIS-DAP probes such as the Raspberry Pi Debug Probe, DAPLink, NXP MCU-Link and Atmel-ICE, TI XDS110 and the probes built into many development boards, as well as ST-Link and J-Link. With a J-Link it can also use SEGGER's J-Link Commander, for the many other devices that supports.
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
## Install and run
|
|
59
|
+
|
|
60
|
+
To install it:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
pip install pyflashgui
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
To run it (`python -m pyflashgui` does the same):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
pyflashgui
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
To update it:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
pip install --upgrade pyflashgui
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Python 3.15 on Windows is not supported yet:** libraries pyOCD needs there have not been released for 3.15. Use 3.14, 3.13 or 3.12 until they have.
|
|
79
|
+
|
|
80
|
+
On Linux, Qt needs a system library to draw the window, and Debian, Ubuntu, Linux Mint and the like often leave it out:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
sudo apt install libxcb-cursor0
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A probe is usable without root on Linux once a udev rule allows it; pyOCD publishes [a set of them](https://github.com/pyocd/pyOCD/tree/main/udev).
|
|
87
|
+
|
|
88
|
+
## What it does
|
|
89
|
+
|
|
90
|
+
- **Probe and target** in a bar across the top: the probe by its serial number, the target by name, the debug clock, and Connect -- through pyOCD, stopping the target or leaving it running.
|
|
91
|
+
- **Files**: ELF, Intel HEX, Motorola S-record and raw binary, in one list with what has been read from a target. A file is read again whenever a build writes it.
|
|
92
|
+
- **Device packs**: every Cortex-M device there is a CMSIS-Pack for, found by part number and installed from inside pyflashgui, with the address each pack comes from -- or added from a `.pack` file, for a computer with no internet.
|
|
93
|
+
- **Memory map**: where everything ticked will land on the chosen target, how full each flash region is and how much is erased, checked before anything is sent. A file built for another part, or two that write the same addresses, are found here.
|
|
94
|
+
- **Program and verify** in one press, with each half on its own beside it and **Program without erasing** for flash erased already, **Erase** of all the flash, some regions or a range, and **Reset**, by the reset pin or another way where a device needs it.
|
|
95
|
+
- **Read** any range of a target's memory back, shown as addresses, bytes and text, with a search for bytes or text and a way to an address. A read is saved as raw binary, Intel HEX or S-record, can be cropped to what is in use or to a range, and can be programmed like a file.
|
|
96
|
+
- **Compare** any two of them -- what is in the target against the file that should be, or one read against another -- with each difference marked.
|
|
97
|
+
- **Workspaces**: one for each board or project, holding its probe, target, files and layout.
|
|
98
|
+
- **A Python console** with the probe and the target as `flash`: read, write RAM, erase, program, verify and reset from a prompt or a script, each call waiting for its answer. *File > Run script...* runs one, and keeps the last five a click away.
|
|
99
|
+
- **A simulated probe and target**, to try all of it with no hardware.
|
|
100
|
+
- **Help > Diagnostics**: how the connection was made, the last operations in order and what pyOCD reported, ready to paste into a bug report.
|
|
101
|
+
|
|
102
|
+
The [manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md) covers each of these, and is also under **Help > Documentation** in the application.
|
|
103
|
+
|
|
104
|
+
## Probes and targets
|
|
105
|
+
|
|
106
|
+
Through [pyOCD](https://pyocd.io), any probe it supports, for Arm Cortex-M targets: CMSIS-DAP probes, ST-Link, and J-Link. The targets are those pyOCD has built in and those in CMSIS-Packs, which **Tools > pyOCD device packs** finds and installs.
|
|
107
|
+
|
|
108
|
+
Through SEGGER's J-Link Commander, a J-Link reaches the devices beyond Cortex-M that SEGGER's software supports -- Cortex-A and Cortex-R, RISC-V and others. SEGGER's software is not included: install it, and pyflashgui finds it.
|
|
109
|
+
|
|
110
|
+
The [README](https://github.com/davhodg/pyflashgui#probes) says which probes need which drivers.
|
|
111
|
+
|
|
112
|
+
## More
|
|
113
|
+
|
|
114
|
+
- [Source, issues and releases](https://github.com/davhodg/pyflashgui)
|
|
115
|
+
- [Standalone Windows installer](https://github.com/davhodg/pyflashgui/releases)
|
|
116
|
+
- [Questions and ideas](https://github.com/davhodg/pyflashgui/discussions)
|
|
117
|
+
- [Manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md)
|
|
118
|
+
|
|
119
|
+
## Safety notice and licence
|
|
120
|
+
|
|
121
|
+
pyflashgui talks to real equipment. It is intended only for people trained and experienced in working with microcontrollers, debug probes and the equipment they are part of. It is a tool for development and the bench, and is not meant for production programming.
|
|
122
|
+
|
|
123
|
+
Erasing and programming replace what a device holds, and cannot be undone. A device given the wrong file, or one that changes its protection or configuration, may not run, and may no longer be reachable through its debug port. Connecting, resetting and reading can stop or restart a device that is running, and with it whatever that device controls.
|
|
124
|
+
|
|
125
|
+
Like any software, pyflashgui can have faults, so do not rely on it alone to say that a device holds what was meant. Where a mistake could hurt someone or damage equipment, keep a way to stop that equipment within reach.
|
|
126
|
+
|
|
127
|
+
pyflashgui is provided under the Apache License 2.0, without warranty of any kind.
|
pyflashgui-0.2.1/PYPI.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/davhodg/pyflashgui/master/pyflashgui/resources/pyflashgui.png" alt="pyflashgui icon: a chip with pins on every side and a flash across it" width="128">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# pyflashgui
|
|
6
|
+
|
|
7
|
+
[](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
|
|
8
|
+
[](https://github.com/davhodg/pyflashgui/releases)
|
|
9
|
+
[](https://pypi.org/project/pyflashgui/)
|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
A graphical flash programming tool. It works with any debug probe [pyOCD](https://pyocd.io) supports, for Arm Cortex-M targets: CMSIS-DAP probes such as the Raspberry Pi Debug Probe, DAPLink, NXP MCU-Link and Atmel-ICE, TI XDS110 and the probes built into many development boards, as well as ST-Link and J-Link. With a J-Link it can also use SEGGER's J-Link Commander, for the many other devices that supports.
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
## Install and run
|
|
19
|
+
|
|
20
|
+
To install it:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
pip install pyflashgui
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To run it (`python -m pyflashgui` does the same):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pyflashgui
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To update it:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
pip install --upgrade pyflashgui
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Python 3.15 on Windows is not supported yet:** libraries pyOCD needs there have not been released for 3.15. Use 3.14, 3.13 or 3.12 until they have.
|
|
39
|
+
|
|
40
|
+
On Linux, Qt needs a system library to draw the window, and Debian, Ubuntu, Linux Mint and the like often leave it out:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
sudo apt install libxcb-cursor0
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
A probe is usable without root on Linux once a udev rule allows it; pyOCD publishes [a set of them](https://github.com/pyocd/pyOCD/tree/main/udev).
|
|
47
|
+
|
|
48
|
+
## What it does
|
|
49
|
+
|
|
50
|
+
- **Probe and target** in a bar across the top: the probe by its serial number, the target by name, the debug clock, and Connect -- through pyOCD, stopping the target or leaving it running.
|
|
51
|
+
- **Files**: ELF, Intel HEX, Motorola S-record and raw binary, in one list with what has been read from a target. A file is read again whenever a build writes it.
|
|
52
|
+
- **Device packs**: every Cortex-M device there is a CMSIS-Pack for, found by part number and installed from inside pyflashgui, with the address each pack comes from -- or added from a `.pack` file, for a computer with no internet.
|
|
53
|
+
- **Memory map**: where everything ticked will land on the chosen target, how full each flash region is and how much is erased, checked before anything is sent. A file built for another part, or two that write the same addresses, are found here.
|
|
54
|
+
- **Program and verify** in one press, with each half on its own beside it and **Program without erasing** for flash erased already, **Erase** of all the flash, some regions or a range, and **Reset**, by the reset pin or another way where a device needs it.
|
|
55
|
+
- **Read** any range of a target's memory back, shown as addresses, bytes and text, with a search for bytes or text and a way to an address. A read is saved as raw binary, Intel HEX or S-record, can be cropped to what is in use or to a range, and can be programmed like a file.
|
|
56
|
+
- **Compare** any two of them -- what is in the target against the file that should be, or one read against another -- with each difference marked.
|
|
57
|
+
- **Workspaces**: one for each board or project, holding its probe, target, files and layout.
|
|
58
|
+
- **A Python console** with the probe and the target as `flash`: read, write RAM, erase, program, verify and reset from a prompt or a script, each call waiting for its answer. *File > Run script...* runs one, and keeps the last five a click away.
|
|
59
|
+
- **A simulated probe and target**, to try all of it with no hardware.
|
|
60
|
+
- **Help > Diagnostics**: how the connection was made, the last operations in order and what pyOCD reported, ready to paste into a bug report.
|
|
61
|
+
|
|
62
|
+
The [manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md) covers each of these, and is also under **Help > Documentation** in the application.
|
|
63
|
+
|
|
64
|
+
## Probes and targets
|
|
65
|
+
|
|
66
|
+
Through [pyOCD](https://pyocd.io), any probe it supports, for Arm Cortex-M targets: CMSIS-DAP probes, ST-Link, and J-Link. The targets are those pyOCD has built in and those in CMSIS-Packs, which **Tools > pyOCD device packs** finds and installs.
|
|
67
|
+
|
|
68
|
+
Through SEGGER's J-Link Commander, a J-Link reaches the devices beyond Cortex-M that SEGGER's software supports -- Cortex-A and Cortex-R, RISC-V and others. SEGGER's software is not included: install it, and pyflashgui finds it.
|
|
69
|
+
|
|
70
|
+
The [README](https://github.com/davhodg/pyflashgui#probes) says which probes need which drivers.
|
|
71
|
+
|
|
72
|
+
## More
|
|
73
|
+
|
|
74
|
+
- [Source, issues and releases](https://github.com/davhodg/pyflashgui)
|
|
75
|
+
- [Standalone Windows installer](https://github.com/davhodg/pyflashgui/releases)
|
|
76
|
+
- [Questions and ideas](https://github.com/davhodg/pyflashgui/discussions)
|
|
77
|
+
- [Manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md)
|
|
78
|
+
|
|
79
|
+
## Safety notice and licence
|
|
80
|
+
|
|
81
|
+
pyflashgui talks to real equipment. It is intended only for people trained and experienced in working with microcontrollers, debug probes and the equipment they are part of. It is a tool for development and the bench, and is not meant for production programming.
|
|
82
|
+
|
|
83
|
+
Erasing and programming replace what a device holds, and cannot be undone. A device given the wrong file, or one that changes its protection or configuration, may not run, and may no longer be reachable through its debug port. Connecting, resetting and reading can stop or restart a device that is running, and with it whatever that device controls.
|
|
84
|
+
|
|
85
|
+
Like any software, pyflashgui can have faults, so do not rely on it alone to say that a device holds what was meant. Where a mistake could hurt someone or damage equipment, keep a way to stop that equipment within reach.
|
|
86
|
+
|
|
87
|
+
pyflashgui is provided under the Apache License 2.0, without warranty of any kind.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# pyflashgui
|
|
6
6
|
|
|
7
7
|
[](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
|
|
8
|
-
[](https://github.com/davhodg/pyflashgui/releases)
|
|
9
9
|
[](https://pypi.org/project/pyflashgui/)
|
|
10
10
|

|
|
11
11
|

|
|
@@ -21,7 +21,9 @@ A graphical flash programming tool. It works with any debug probe [pyOCD](https:
|
|
|
21
21
|
|
|
22
22
|
- **Any probe.** A low-cost CMSIS-DAP probe or the ST-Link on a development board does the job a dedicated programmer does.
|
|
23
23
|
- **Workspaces.** The target, the probe, its speed and the files to program are kept together, one workspace per product, so programming a board is one click.
|
|
24
|
-
- **Files.** ELF, Intel HEX, Motorola S-record and raw binary, with a map of what will be written where.
|
|
24
|
+
- **Files.** ELF, Intel HEX, Motorola S-record and raw binary, with a map of what will be written where, and what is read back from a target in the same list, to compare and save.
|
|
25
|
+
- **Device packs.** Every Cortex-M device there is a CMSIS-Pack for, found by part number and installed from inside pyflashgui -- or from a file, for a computer with no internet.
|
|
26
|
+
- **Python.** A console with the probe and the target to hand, and *File > Run script...*, for what the buttons do not do: watching a value in RAM, writing a test pattern, a loop that programs and reads back.
|
|
25
27
|
|
|
26
28
|
## Installing
|
|
27
29
|
|
|
@@ -29,6 +31,8 @@ There are three options:
|
|
|
29
31
|
|
|
30
32
|
1. **With pip** -- from [PyPI](https://pypi.org/project/pyflashgui/), into whichever Python environment you choose. Python 3.12 or newer is needed.
|
|
31
33
|
|
|
34
|
+
**Python 3.15 on Windows is not supported yet:** libraries pyOCD needs there have not been released for 3.15. Use 3.14, 3.13 or 3.12 until they have.
|
|
35
|
+
|
|
32
36
|
To install it:
|
|
33
37
|
|
|
34
38
|
```
|
|
@@ -55,10 +59,14 @@ There are three options:
|
|
|
55
59
|
|
|
56
60
|
It asks first who it is for: **everybody who uses the computer**, which needs administrator rights, or **only you**, which does not.
|
|
57
61
|
|
|
58
|
-
3. **From a Git clone** -- clone the repository, then double-click `pyflashgui.cmd` (Windows) or run `./pyflashgui.sh` (Linux). This is the way to have the latest changes before they are released, or to contribute. Python 3.12 or newer
|
|
62
|
+
3. **From a Git clone** -- clone the repository, then double-click `pyflashgui.cmd` (Windows) or run `./pyflashgui.sh` (Linux). This is the way to have the latest changes before they are released, or to contribute. Python 3.12 or newer is needed: on Windows 3.14, 3.13 or 3.12, as below.
|
|
59
63
|
|
|
60
64
|
The first run sets itself up: it creates a *virtual environment* -- a folder called `.venv` holding its own copy of Python and only the libraries pyflashgui needs, so nothing else on the machine is touched -- and downloads a couple of hundred megabytes into it. That takes a few minutes once; every later start is immediate, and deleting `.venv` undoes the whole thing. If `uv` is installed it is used instead of pip, which makes rebuilding that folder a matter of seconds.
|
|
61
65
|
|
|
66
|
+
**On Windows** the launcher makes `.venv` from Python 3.14, 3.13 or 3.12 when the `py` launcher has one of them, newest first, even if a newer Python is the one on the PATH: those are the versions every library pyflashgui runs on has a ready-made download for. 3.15 is not one of them yet; if it is the only Python installed, `py install 3.14` adds one the launcher will choose.
|
|
67
|
+
|
|
68
|
+
**If the Python `.venv` was made with is uninstalled** -- or replaced by a newer version -- the launcher says so, and offers to delete `.venv` and set it up again with the Python installed now. Workspaces and settings are not in `.venv`, and are kept.
|
|
69
|
+
|
|
62
70
|
**On Linux**, run it from a terminal, so its messages can be read:
|
|
63
71
|
|
|
64
72
|
```
|
|
@@ -73,7 +81,7 @@ There are three options:
|
|
|
73
81
|
|
|
74
82
|
Double-clicking `pyflashgui.sh` in a file manager works too: choose *Run in Terminal* when it asks, for the same reason.
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
For a shortcut, *Tools > Settings > Add shortcut to Start menu* (the applications menu on Linux) and, on Windows, *Add shortcut to desktop*. The entry starts the launcher, so a start from it after a pull still picks up new libraries.
|
|
77
85
|
|
|
78
86
|
The launcher keeps its own `.venv` on purpose and will not use an environment you already have. That is the point of it: it is the way in for somebody who does not want to think about Python environments, and one that sometimes used yours and sometimes did not would be worse than one that never does. To run a checkout in an environment of your own instead, `pip install -e .` installs it there, reading the source.
|
|
79
87
|
|
|
@@ -110,11 +118,11 @@ CMSIS-DAP is a protocol and not a product, so a great many probes speak it. Amon
|
|
|
110
118
|
|
|
111
119
|
**With pyOCD, the target has to be an Arm Cortex-M, whichever probe it is.** The probe carries the debug port; what is on the far side of it is pyOCD's to understand, and Cortex-M is what it understands. A probe that can reach other kinds of processor with its maker's own software -- a Cortex-A or Cortex-R, a RISC-V core -- still reaches only Cortex-M here.
|
|
112
120
|
|
|
113
|
-
|
|
121
|
+
**Which Cortex-M devices** is a matter of CMSIS-Packs. pyOCD has a few dozen devices built in; every other is described by a pack its maker publishes. **Tools > pyOCD device packs** finds a device by its part number and downloads the pack it is in, however pyflashgui was installed.
|
|
114
122
|
|
|
115
123
|
### Beyond Cortex-M, with a J-Link
|
|
116
124
|
|
|
117
|
-
pyflashgui can instead drive a J-Link through SEGGER's own J-Link Commander, which comes with SEGGER's J-Link software. That reaches every device SEGGER's software knows -- Cortex-A and Cortex-R, RISC-V and others -- and works with a J-Link and nothing else. SEGGER's software is yours to install, under SEGGER's licence: it is for genuine SEGGER probes, and SEGGER describes J-Link Commander as a tool for development, not for production programming.
|
|
125
|
+
pyflashgui can instead drive a J-Link through SEGGER's own J-Link Commander, which comes with SEGGER's J-Link software. That reaches every device SEGGER's software knows -- Cortex-A and Cortex-R, RISC-V and others -- and works with a J-Link and nothing else. SEGGER's software is yours to install, under SEGGER's licence: it is for genuine SEGGER probes, and SEGGER describes J-Link Commander as a tool for development, not for production programming.
|
|
118
126
|
|
|
119
127
|
On Linux a probe is usable without root once a udev rule allows it; pyOCD publishes [a set of them](https://github.com/pyocd/pyOCD/tree/main/udev). Vendor software is never bundled with pyflashgui: install the vendor's own, and pyflashgui finds it.
|
|
120
128
|
|
|
@@ -130,10 +138,11 @@ On Linux a probe is usable without root once a udev rule allows it; pyOCD publis
|
|
|
130
138
|
|
|
131
139
|
- **The Windows installer:** *Settings > Apps* (or *Add or remove programs*), or *Uninstall
|
|
132
140
|
pyflashgui* in its Start menu folder. It takes away its shortcuts too.
|
|
133
|
-
- **pip:** `pip uninstall pyflashgui
|
|
134
|
-
- **A Git clone:** delete the folder. If
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
- **pip:** `pip uninstall pyflashgui`, and what *Tools > Settings* added, as for a clone.
|
|
142
|
+
- **A Git clone:** delete the folder. If *Tools > Settings* was used to add a shortcut, take
|
|
143
|
+
that away as well: the shortcuts are
|
|
144
|
+
`%APPDATA%\Microsoft\Windows\Start Menu\Programs\pyflashgui.lnk` and `pyflashgui.lnk` on
|
|
145
|
+
the desktop (on Linux, `~/.local/share/applications/pyflashgui.desktop`).
|
|
137
146
|
|
|
138
147
|
Your workspaces and settings are in `%APPDATA%\pyflashgui` (`~/.config/pyflashgui` on Linux),
|
|
139
148
|
and no way of uninstalling touches them; delete that folder to remove them too.
|
|
@@ -29,6 +29,7 @@ SELFTEST_MODULES = (
|
|
|
29
29
|
"pyflashgui.core.images",
|
|
30
30
|
"pyflashgui.core.memory_map",
|
|
31
31
|
"pyflashgui.backends.pyocd_backend",
|
|
32
|
+
"pyflashgui.backends.pyocd_packs",
|
|
32
33
|
"pyflashgui.backends.jlink_backend",
|
|
33
34
|
"pyflashgui.backends.simulated",
|
|
34
35
|
"pyflashgui.ui.main_window",
|
|
@@ -76,9 +77,19 @@ def selftest() -> int:
|
|
|
76
77
|
except Exception as exc:
|
|
77
78
|
failures.append(f"pyocd: {exc}")
|
|
78
79
|
|
|
80
|
+
# Tools > pyOCD device packs is the only way an installed copy has of getting a
|
|
81
|
+
# target pyOCD has not built in, and what does it is a native library.
|
|
82
|
+
# Opening the store of packs loads it; nothing is downloaded.
|
|
83
|
+
try:
|
|
84
|
+
from pyflashgui.backends import pyocd_packs
|
|
85
|
+
|
|
86
|
+
pyocd_packs.installed()
|
|
87
|
+
except Exception as exc:
|
|
88
|
+
failures.append(f"device packs: {exc}")
|
|
89
|
+
|
|
79
90
|
# libusb is a native library carried inside a Python package, which is
|
|
80
91
|
# the kind of file a build leaves behind. Without it pyOCD lists no
|
|
81
|
-
# CMSIS-DAP v2 probe and no ST-Link,
|
|
92
|
+
# CMSIS-DAP v2 probe and no ST-Link, so the self-test checks for it.
|
|
82
93
|
try:
|
|
83
94
|
import libusb_package
|
|
84
95
|
|
|
@@ -149,7 +160,8 @@ def set_icon(app) -> None:
|
|
|
149
160
|
|
|
150
161
|
#: The libraries Qt's X11 plugin needs that Debian, Ubuntu and Linux Mint can
|
|
151
162
|
#: leave out, each with the package it comes in. Without one, Qt stops with
|
|
152
|
-
#: "could not load the Qt platform plugin xcb"
|
|
163
|
+
#: "could not load the Qt platform plugin xcb", so they are looked for first
|
|
164
|
+
#: and the package to install is named.
|
|
153
165
|
XCB_LIBRARIES = (
|
|
154
166
|
("libxcb-cursor.so.0", "libxcb-cursor0"),
|
|
155
167
|
("libxcb-icccm.so.4", "libxcb-icccm4"),
|
|
@@ -28,8 +28,10 @@ from dataclasses import dataclass
|
|
|
28
28
|
from pyflashgui.core.images import Segment
|
|
29
29
|
from pyflashgui.core.memory_map import Region
|
|
30
30
|
|
|
31
|
-
#: Called with how far along something is, from 0.0 to 1.0
|
|
32
|
-
|
|
31
|
+
#: Called with how far along something is, from 0.0 to 1.0, and -- where
|
|
32
|
+
#: one piece of work is done in stages -- what is being done now, when that
|
|
33
|
+
#: changes: "Erasing", then "Programming".
|
|
34
|
+
Progress = Callable[..., None]
|
|
33
35
|
#: Asked between steps whether to stop. True means somebody pressed Cancel.
|
|
34
36
|
Cancelled = Callable[[], bool]
|
|
35
37
|
|
|
@@ -37,6 +39,24 @@ Cancelled = Callable[[], bool]
|
|
|
37
39
|
ERASE_SECTORS = "sectors"
|
|
38
40
|
#: Erase all of the flash first, so nothing of what was there is left.
|
|
39
41
|
ERASE_CHIP = "chip"
|
|
42
|
+
#: Erase nothing: the flash written to was erased beforehand. Flash can only
|
|
43
|
+
#: be written where it is blank; written over anything else, it holds
|
|
44
|
+
#: neither what was there nor what was written.
|
|
45
|
+
ERASE_NONE = "none"
|
|
46
|
+
|
|
47
|
+
#: Ways of restarting a target. Not every device restarts properly every
|
|
48
|
+
#: way: an XMC4500 asked to reset itself through pyOCD stayed stopped, and
|
|
49
|
+
#: started from its reset pin, as from its reset button.
|
|
50
|
+
#: The backend's own choice for the target: for most, the next one.
|
|
51
|
+
RESET_DEFAULT = "default"
|
|
52
|
+
#: The processor asked to reset the whole device (SYSRESETREQ).
|
|
53
|
+
RESET_SYSTEM = "system"
|
|
54
|
+
#: The device's reset line pulled by the probe, as its reset button does.
|
|
55
|
+
#: Only where the probe is wired to it: some, the Raspberry Pi Debug Probe
|
|
56
|
+
#: among them, are not.
|
|
57
|
+
RESET_PIN = "pin"
|
|
58
|
+
#: The processor alone, leaving the rest of the device as it is.
|
|
59
|
+
RESET_CORE = "core"
|
|
40
60
|
|
|
41
61
|
#: What verifying reads at a time. Small enough that progress moves and a
|
|
42
62
|
#: cancel is noticed; large enough that the probe is not asked a thousand
|
|
@@ -114,6 +134,12 @@ class Connection(ABC):
|
|
|
114
134
|
a probe left attached is one nothing else can use.
|
|
115
135
|
"""
|
|
116
136
|
|
|
137
|
+
#: The debug clock it runs at, said in the Event Log on connecting:
|
|
138
|
+
#: "4 MHz", or how the backend chooses it. Empty where it cannot say.
|
|
139
|
+
speed: str = ""
|
|
140
|
+
#: Anything else worth saying on connecting, a sentence each.
|
|
141
|
+
notes: tuple[str, ...] = ()
|
|
142
|
+
|
|
117
143
|
@abstractmethod
|
|
118
144
|
def regions(self) -> list[Region]:
|
|
119
145
|
"""The target's memory, as it turned out to be once connected."""
|
|
@@ -136,9 +162,12 @@ class Connection(ABC):
|
|
|
136
162
|
segments: Sequence[Segment],
|
|
137
163
|
erase: str = ERASE_SECTORS,
|
|
138
164
|
progress: Progress = _nothing,
|
|
139
|
-
) -> None:
|
|
165
|
+
) -> str | None:
|
|
140
166
|
"""Erase what has to be erased and write these bytes.
|
|
141
167
|
|
|
168
|
+
Returns anything about how it went worth saying in the Event Log, a
|
|
169
|
+
sentence, or None.
|
|
170
|
+
|
|
142
171
|
Not cancellable. Stopping half way through leaves a target that is
|
|
143
172
|
neither what it was nor what it was going to be, which is worse than
|
|
144
173
|
either; a board can always be programmed again afterwards.
|
|
@@ -148,14 +177,36 @@ class Connection(ABC):
|
|
|
148
177
|
def read(self, address: int, length: int) -> bytes:
|
|
149
178
|
"""This many bytes of the target's memory, from this address."""
|
|
150
179
|
|
|
180
|
+
def write(self, address: int, data: bytes) -> None:
|
|
181
|
+
"""Write these bytes to RAM, or to a peripheral's registers, as they are.
|
|
182
|
+
|
|
183
|
+
Not for flash, which has to be erased before it can be written and
|
|
184
|
+
is written by ``program``. A backend that cannot says so.
|
|
185
|
+
"""
|
|
186
|
+
raise BackendError("This way of reaching the probe cannot write to memory.")
|
|
187
|
+
|
|
151
188
|
@abstractmethod
|
|
152
|
-
def reset(self, halt: bool = False) -> None:
|
|
153
|
-
"""Restart the target, and leave it running unless ``halt``.
|
|
189
|
+
def reset(self, halt: bool = False, kind: str = RESET_DEFAULT) -> str | None:
|
|
190
|
+
"""Restart the target, and leave it running unless ``halt``.
|
|
191
|
+
|
|
192
|
+
``kind`` is one of the backend's ``reset_kinds``. Returns anything
|
|
193
|
+
about it worth saying in the Event Log, a sentence, or None.
|
|
194
|
+
"""
|
|
154
195
|
|
|
155
196
|
@abstractmethod
|
|
156
197
|
def close(self) -> None:
|
|
157
198
|
"""Let go of the probe. Safe to call twice."""
|
|
158
199
|
|
|
200
|
+
def details(self) -> list[str]:
|
|
201
|
+
"""What Help > Diagnostics says of this connection, a line each.
|
|
202
|
+
|
|
203
|
+
How the backend was asked to connect and what it found there, beyond
|
|
204
|
+
the probe and the target the window already knows. Read on the
|
|
205
|
+
window's thread while the worker may be using the connection, so
|
|
206
|
+
only what is already known is said: nothing here asks the probe.
|
|
207
|
+
"""
|
|
208
|
+
return []
|
|
209
|
+
|
|
159
210
|
def read_range(
|
|
160
211
|
self,
|
|
161
212
|
address: int,
|
|
@@ -235,6 +286,18 @@ class Backend(ABC):
|
|
|
235
286
|
#: Where it is not, a file outside them is worth a warning and is
|
|
236
287
|
#: not refused: the backend may know of memory that it does not list.
|
|
237
288
|
map_is_complete: bool = True
|
|
289
|
+
#: What the speed box offers first, for leaving the speed to the
|
|
290
|
+
#: backend: said with what that comes to, since it is not always fast.
|
|
291
|
+
default_speed: str = "Default"
|
|
292
|
+
#: The ways it can restart a target, the one used unless told otherwise
|
|
293
|
+
#: first.
|
|
294
|
+
reset_kinds: tuple[str, ...] = (RESET_DEFAULT,)
|
|
295
|
+
#: Whether it can connect leaving the target running, rather than only
|
|
296
|
+
#: stopping it: ``connect``'s ``stop``.
|
|
297
|
+
can_attach: bool = False
|
|
298
|
+
#: Whether it can program without erasing first, for flash erased
|
|
299
|
+
#: already: ``ERASE_NONE``.
|
|
300
|
+
can_skip_erase: bool = False
|
|
238
301
|
#: Whether a read can be made without the backend first setting the
|
|
239
302
|
#: device up in its own way. See ``Connection.read_range``.
|
|
240
303
|
can_skip_setup: bool = False
|
|
@@ -263,6 +326,7 @@ class Backend(ABC):
|
|
|
263
326
|
target: str,
|
|
264
327
|
frequency: int | None = None,
|
|
265
328
|
interface: str | None = None,
|
|
329
|
+
stop: bool = True,
|
|
266
330
|
) -> Connection:
|
|
267
331
|
"""Attach the probe with this unique id to a target of this name.
|
|
268
332
|
|
|
@@ -36,6 +36,7 @@ from pathlib import Path
|
|
|
36
36
|
from pyflashgui.backends.base import (
|
|
37
37
|
ERASE_CHIP,
|
|
38
38
|
ERASE_SECTORS,
|
|
39
|
+
RESET_DEFAULT,
|
|
39
40
|
Backend,
|
|
40
41
|
BackendError,
|
|
41
42
|
Cancelled,
|
|
@@ -71,13 +72,14 @@ NOT_FOUND = (
|
|
|
71
72
|
)
|
|
72
73
|
|
|
73
74
|
#: Said by Commander when the probe is there and the device does not answer.
|
|
74
|
-
#: Looked for as well as its exit code, which
|
|
75
|
+
#: Looked for as well as its exit code, which can be 0 with it.
|
|
75
76
|
NO_TARGET = "Cannot connect to target"
|
|
76
77
|
|
|
77
|
-
#: A J-Link script that does nothing where
|
|
78
|
+
#: A J-Link script that does nothing where J-Link's software would set the
|
|
78
79
|
#: device up after connecting to it. On some parts that set-up writes to
|
|
79
80
|
#: the device -- an S32K3's RAM is filled, for its error correction -- and
|
|
80
|
-
#: a read made to see what a running program left there finds
|
|
81
|
+
#: a read made to see what a running program left there finds the set-up's
|
|
82
|
+
#: bytes instead.
|
|
81
83
|
NO_SETUP_SCRIPT = "int SetupTarget(void) {\n return 0;\n}\n"
|
|
82
84
|
|
|
83
85
|
#: Where Commander is, when it has been said. None to go and look.
|
|
@@ -145,6 +147,20 @@ def find_commander() -> list[str] | None:
|
|
|
145
147
|
return None
|
|
146
148
|
|
|
147
149
|
|
|
150
|
+
def report() -> list[str]:
|
|
151
|
+
"""For Help > Diagnostics: the Commander used, how it was found, and every
|
|
152
|
+
J-Link software folder there is, newest first."""
|
|
153
|
+
found = find_commander()
|
|
154
|
+
if found is None:
|
|
155
|
+
lines = ["not found"]
|
|
156
|
+
else:
|
|
157
|
+
how = "chosen in Tools > Settings" if _chosen is not None else "found"
|
|
158
|
+
lines = [f"{' '.join(found)} ({how})"]
|
|
159
|
+
folders = _installed()
|
|
160
|
+
lines += [f"installed: {folder}" for folder in folders] or ["no J-Link software folder found"]
|
|
161
|
+
return lines
|
|
162
|
+
|
|
163
|
+
|
|
148
164
|
def _is_seggers(program: Path) -> bool:
|
|
149
165
|
"""Whether a program of Commander's name is Commander, and not Java's jlink.
|
|
150
166
|
|
|
@@ -247,6 +263,38 @@ def run(arguments: Sequence[str], commands: Sequence[str], timeout: float, doing
|
|
|
247
263
|
|
|
248
264
|
|
|
249
265
|
# --- reading what Commander says ---------------------------------------------------------
|
|
266
|
+
#: The line SEGGER's software ends a download with, saying how long each part
|
|
267
|
+
#: took: ``J-Link: Flash download: Total: 3.052s (Prepare: 0.071s, Compare:
|
|
268
|
+
#: 0.014s, Erase: 1.890s, Program & Verify: 1.029s, Restore: 0.046s)``. Older
|
|
269
|
+
#: versions say "Total time needed".
|
|
270
|
+
_DOWNLOAD = re.compile(
|
|
271
|
+
r"Flash download:\s*Total(?: time needed)?:\s*([\d.]+)\s*s\s*\(([^)]*)\)", re.I
|
|
272
|
+
)
|
|
273
|
+
_PART = re.compile(r"([A-Za-z& ]+?):\s*([\d.]+)\s*s")
|
|
274
|
+
#: Said when the flash held the image already, and nothing was written.
|
|
275
|
+
_SKIPPED = re.compile(r"Flash download:\s*Skipped", re.I)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def download_text(output: str) -> str | None:
|
|
279
|
+
"""What J-Link said a download did, as a sentence: what it took to erase,
|
|
280
|
+
and to program and check, which happen inside one command and cannot be
|
|
281
|
+
shown as they go. None if it did not say."""
|
|
282
|
+
if _SKIPPED.search(output) and not _DOWNLOAD.search(output):
|
|
283
|
+
return "J-Link found the flash held this already, and wrote nothing."
|
|
284
|
+
found = _DOWNLOAD.search(output)
|
|
285
|
+
if found is None:
|
|
286
|
+
return None
|
|
287
|
+
parts = {name.strip().lower(): float(seconds) for name, seconds in _PART.findall(found[2])}
|
|
288
|
+
said = []
|
|
289
|
+
if "erase" in parts:
|
|
290
|
+
said.append(f"erased in {parts['erase']:.1f} s")
|
|
291
|
+
if "program & verify" in parts:
|
|
292
|
+
said.append(f"programmed and checked in {parts['program & verify']:.1f} s")
|
|
293
|
+
if not said:
|
|
294
|
+
return None
|
|
295
|
+
return f"J-Link {' and '.join(said)}, {float(found[1]):.1f} s in all."
|
|
296
|
+
|
|
297
|
+
|
|
250
298
|
#: One line of ShowEmuList for each J-Link:
|
|
251
299
|
#: ``J-Link[0]: Connection: USB, Serial number: 801012345, ProductName: J-Link PLUS``
|
|
252
300
|
_PROBE = re.compile(r"Serial number:\s*(\d+)(?:.*?ProductName:\s*([^,\r\n]+))?", re.I)
|
|
@@ -374,6 +422,11 @@ class JLinkConnection(Connection):
|
|
|
374
422
|
def regions(self) -> list[Region]:
|
|
375
423
|
return list(self._regions)
|
|
376
424
|
|
|
425
|
+
def details(self) -> list[str]:
|
|
426
|
+
if self._arguments is None:
|
|
427
|
+
return ["closed"]
|
|
428
|
+
return [f"device: {self._device}", f"Commander run with: {' '.join(self._arguments)}"]
|
|
429
|
+
|
|
377
430
|
def erase_chip(self) -> None:
|
|
378
431
|
self._run(["H", "Erase"], WORK_S, f"Erasing {self._device}")
|
|
379
432
|
|
|
@@ -390,7 +443,7 @@ class JLinkConnection(Connection):
|
|
|
390
443
|
segments: Sequence[Segment],
|
|
391
444
|
erase: str = ERASE_SECTORS,
|
|
392
445
|
progress: Progress = _nothing,
|
|
393
|
-
) -> None:
|
|
446
|
+
) -> str | None:
|
|
394
447
|
if erase not in (ERASE_SECTORS, ERASE_CHIP):
|
|
395
448
|
raise BackendError(f"There is no way of erasing called {erase!r}.")
|
|
396
449
|
import bincopy
|
|
@@ -413,8 +466,9 @@ class JLinkConnection(Connection):
|
|
|
413
466
|
# LoadFile erases the sectors it writes to, and checks what it
|
|
414
467
|
# wrote, by itself.
|
|
415
468
|
commands.append(f"LoadFile {_named(path)}")
|
|
416
|
-
self._run(commands, WORK_S, f"Programming {self._device}")
|
|
469
|
+
said = self._run(commands, WORK_S, f"Programming {self._device}")
|
|
417
470
|
progress(1.0)
|
|
471
|
+
return download_text(said)
|
|
418
472
|
|
|
419
473
|
def read(
|
|
420
474
|
self, address: int, length: int, resume: bool = False, skip_setup: bool = False
|
|
@@ -491,7 +545,20 @@ class JLinkConnection(Connection):
|
|
|
491
545
|
progress(checked / total)
|
|
492
546
|
return Verification(checked=checked, different=different, first=first)
|
|
493
547
|
|
|
494
|
-
def
|
|
548
|
+
def write(self, address: int, data: bytes) -> None:
|
|
549
|
+
"""Stopped while it is written, and set going again, as a read is."""
|
|
550
|
+
with tempfile.TemporaryDirectory(prefix="pyflashgui-jlink-") as folder:
|
|
551
|
+
path = Path(folder) / "write.bin"
|
|
552
|
+
path.write_bytes(data)
|
|
553
|
+
commands = ["H", f"LoadBin {_named(path)}, 0x{address:X}", "G"]
|
|
554
|
+
self._run(commands, WORK_S, f"Writing {len(data)} bytes at {address:08X}")
|
|
555
|
+
|
|
556
|
+
def reset(self, halt: bool = False, kind: str = RESET_DEFAULT) -> None:
|
|
557
|
+
if kind != RESET_DEFAULT:
|
|
558
|
+
raise BackendError(
|
|
559
|
+
"Through J-Link Commander, a device is reset the usual way only. The other "
|
|
560
|
+
"ways are offered through pyOCD."
|
|
561
|
+
)
|
|
495
562
|
# R restarts the device and leaves it stopped; G sets it going.
|
|
496
563
|
self._run(["R"] if halt else ["R", "G"], QUICK_S, f"Resetting {self._device}")
|
|
497
564
|
|
|
@@ -500,6 +567,8 @@ class JLinkConnection(Connection):
|
|
|
500
567
|
|
|
501
568
|
|
|
502
569
|
class JLinkBackend(Backend):
|
|
570
|
+
# Commander's -speed auto: the J-Link finds the fastest that works.
|
|
571
|
+
default_speed = "Default (auto)"
|
|
503
572
|
name = "jlink"
|
|
504
573
|
label = "J-Link Commander"
|
|
505
574
|
interfaces = INTERFACES
|
|
@@ -535,8 +604,8 @@ class JLinkBackend(Backend):
|
|
|
535
604
|
# it that is not, and latin-1 reads any byte there is.
|
|
536
605
|
text = path.read_text(encoding="latin-1")
|
|
537
606
|
except (BackendError, OSError):
|
|
538
|
-
#
|
|
539
|
-
#
|
|
607
|
+
# Without the list, a device whose name is typed in is still
|
|
608
|
+
# programmed.
|
|
540
609
|
text = ""
|
|
541
610
|
self._devices = {device.name.upper(): device for device in parse_devices(text)}
|
|
542
611
|
return self._devices
|
|
@@ -561,6 +630,7 @@ class JLinkBackend(Backend):
|
|
|
561
630
|
target: str,
|
|
562
631
|
frequency: int | None = None,
|
|
563
632
|
interface: str | None = None,
|
|
633
|
+
stop: bool = True,
|
|
564
634
|
) -> Connection:
|
|
565
635
|
if not probe:
|
|
566
636
|
raise BackendError("No J-Link is chosen.")
|
|
@@ -593,4 +663,6 @@ class JLinkBackend(Backend):
|
|
|
593
663
|
# With nothing to do but leave: a run that comes back clean has
|
|
594
664
|
# opened the probe and found the device answering.
|
|
595
665
|
run(arguments, [], QUICK_S, f"Connecting to {target}")
|
|
596
|
-
|
|
666
|
+
connection = JLinkConnection(arguments, target, regions)
|
|
667
|
+
connection.speed = "the speed J-Link chooses" if speed == "auto" else f"{speed} kHz"
|
|
668
|
+
return connection
|