pyflashgui 0.1.0__tar.gz → 0.2.0__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.
Files changed (128) hide show
  1. pyflashgui-0.2.0/PKG-INFO +126 -0
  2. pyflashgui-0.2.0/PYPI.md +86 -0
  3. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/README.md +19 -10
  4. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/__init__.py +1 -1
  5. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/__main__.py +11 -0
  6. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/backends/base.py +59 -5
  7. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/backends/jlink_backend.py +53 -4
  8. pyflashgui-0.2.0/pyflashgui/backends/pyocd_backend.py +608 -0
  9. pyflashgui-0.2.0/pyflashgui/backends/pyocd_packs.py +382 -0
  10. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/backends/simulated.py +37 -5
  11. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/flasher.py +198 -45
  12. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/memory_map.py +36 -2
  13. pyflashgui-0.2.0/pyflashgui/core/pack_manager.py +174 -0
  14. pyflashgui-0.2.0/pyflashgui/core/shortcut.py +259 -0
  15. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/__init__.py +1 -0
  16. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/about.md +1 -1
  17. pyflashgui-0.2.0/pyflashgui/help/changelog.md +159 -0
  18. pyflashgui-0.2.0/pyflashgui/help/console.md +70 -0
  19. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/files.md +8 -10
  20. pyflashgui-0.2.0/pyflashgui/help/main-window.png +0 -0
  21. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/manual.md +19 -5
  22. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/memory-map.md +2 -1
  23. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/memory.md +14 -4
  24. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/help/probe.md +30 -8
  25. pyflashgui-0.2.0/pyflashgui/resources/tick.svg +7 -0
  26. pyflashgui-0.2.0/pyflashgui/ui/buttons.py +40 -0
  27. pyflashgui-0.2.0/pyflashgui/ui/console_view.py +349 -0
  28. pyflashgui-0.2.0/pyflashgui/ui/crop_dialog.py +171 -0
  29. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/files_view.py +192 -71
  30. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/folders.py +2 -0
  31. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/main_window.py +338 -33
  32. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/memory_view.py +73 -33
  33. pyflashgui-0.2.0/pyflashgui/ui/packs_dialog.py +431 -0
  34. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/panes.py +17 -0
  35. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/probe_bar.py +109 -14
  36. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/read_dialog.py +4 -3
  37. pyflashgui-0.2.0/pyflashgui/ui/scripting.py +350 -0
  38. pyflashgui-0.2.0/pyflashgui/ui/scripts_menu.py +138 -0
  39. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/session.py +7 -0
  40. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/theme.py +67 -3
  41. pyflashgui-0.2.0/pyflashgui/ui/workspace_menu.py +254 -0
  42. pyflashgui-0.2.0/pyflashgui.egg-info/PKG-INFO +126 -0
  43. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui.egg-info/SOURCES.txt +14 -0
  44. pyflashgui-0.2.0/tests/test_backends.py +687 -0
  45. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_compare.py +16 -4
  46. pyflashgui-0.2.0/tests/test_console.py +283 -0
  47. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_flash_panes.py +230 -15
  48. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_flasher.py +54 -2
  49. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_jlink_backend.py +37 -0
  50. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_launcher.py +124 -0
  51. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_memory_map.py +22 -3
  52. pyflashgui-0.2.0/tests/test_packs.py +527 -0
  53. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_read_back.py +163 -32
  54. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_readme.py +3 -2
  55. pyflashgui-0.2.0/tests/test_scripts_menu.py +100 -0
  56. pyflashgui-0.2.0/tests/test_shortcut.py +251 -0
  57. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_theme.py +65 -0
  58. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_workspaces.py +208 -2
  59. pyflashgui-0.1.0/PKG-INFO +0 -80
  60. pyflashgui-0.1.0/PYPI.md +0 -40
  61. pyflashgui-0.1.0/pyflashgui/backends/pyocd_backend.py +0 -271
  62. pyflashgui-0.1.0/pyflashgui/core/shortcut.py +0 -154
  63. pyflashgui-0.1.0/pyflashgui/help/changelog.md +0 -46
  64. pyflashgui-0.1.0/pyflashgui/help/main-window.png +0 -0
  65. pyflashgui-0.1.0/pyflashgui.egg-info/PKG-INFO +0 -80
  66. pyflashgui-0.1.0/tests/test_backends.py +0 -251
  67. pyflashgui-0.1.0/tests/test_shortcut.py +0 -108
  68. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/LICENSE +0 -0
  69. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/NOTICE +0 -0
  70. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/backends/__init__.py +0 -0
  71. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/__init__.py +0 -0
  72. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/checkout.py +0 -0
  73. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/compare.py +0 -0
  74. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/context.py +0 -0
  75. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/events.py +0 -0
  76. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/excepthook.py +0 -0
  77. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/images.py +0 -0
  78. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/installation.py +0 -0
  79. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/layout.py +0 -0
  80. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/packages.py +0 -0
  81. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/paths.py +0 -0
  82. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/settings.py +0 -0
  83. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/slow_start.py +0 -0
  84. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/timing.py +0 -0
  85. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/updates.py +0 -0
  86. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/worker.py +0 -0
  87. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/core/workspaces.py +0 -0
  88. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/resources/__init__.py +0 -0
  89. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/resources/pyflashgui.ico +0 -0
  90. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/resources/pyflashgui.png +0 -0
  91. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/resources/pyflashgui.svg +0 -0
  92. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/resources/pyflashgui_small.svg +0 -0
  93. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/__init__.py +0 -0
  94. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/confirm.py +0 -0
  95. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/detached.py +0 -0
  96. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/erase_dialog.py +0 -0
  97. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/event_colours.py +0 -0
  98. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/fonts.py +0 -0
  99. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/help_menu.py +0 -0
  100. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/map_view.py +0 -0
  101. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/messages.py +0 -0
  102. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/pane_bar.py +0 -0
  103. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui/ui/persist.py +0 -0
  104. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui.egg-info/dependency_links.txt +0 -0
  105. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui.egg-info/entry_points.txt +0 -0
  106. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui.egg-info/requires.txt +0 -0
  107. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyflashgui.egg-info/top_level.txt +0 -0
  108. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/pyproject.toml +0 -0
  109. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/setup.cfg +0 -0
  110. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_build_version.py +0 -0
  111. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_changelog.py +0 -0
  112. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_checkout.py +0 -0
  113. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_erase.py +0 -0
  114. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_events.py +0 -0
  115. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_excepthook.py +0 -0
  116. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_folders.py +0 -0
  117. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_fonts.py +0 -0
  118. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_headers.py +0 -0
  119. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_help.py +0 -0
  120. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_icon.py +0 -0
  121. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_images.py +0 -0
  122. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_installation.py +0 -0
  123. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_messages.py +0 -0
  124. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_notice.py +0 -0
  125. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_packages.py +0 -0
  126. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_packaging.py +0 -0
  127. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_persist.py +0 -0
  128. {pyflashgui-0.1.0 → pyflashgui-0.2.0}/tests/test_slow_start.py +0 -0
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyflashgui
3
+ Version: 0.2.0
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.45
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
+ [![CI](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml/badge.svg)](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
48
+ [![release](https://img.shields.io/github/v/release/davhodg/pyflashgui)](https://github.com/davhodg/pyflashgui/releases)
49
+ [![PyPI](https://img.shields.io/pypi/v/pyflashgui)](https://pypi.org/project/pyflashgui/)
50
+ ![python](https://img.shields.io/badge/python-3.12+-blue)
51
+ ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey)
52
+ ![license](https://img.shields.io/badge/license-Apache--2.0-green)
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
+ ![pyflashgui on its simulated probe: the Files pane with a firmware file and a read, the memory map under them, the bytes of the read beside them, and the Event Log below](https://raw.githubusercontent.com/davhodg/pyflashgui/master/pyflashgui/help/main-window.png)
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
+
101
+ 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.
102
+
103
+ ## Probes and targets
104
+
105
+ 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.
106
+
107
+ 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.
108
+
109
+ The [README](https://github.com/davhodg/pyflashgui#probes) says which probes need which drivers.
110
+
111
+ ## More
112
+
113
+ - [Source, issues and releases](https://github.com/davhodg/pyflashgui)
114
+ - [Standalone Windows installer](https://github.com/davhodg/pyflashgui/releases)
115
+ - [Questions and ideas](https://github.com/davhodg/pyflashgui/discussions)
116
+ - [Manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md)
117
+
118
+ ## Safety notice and licence
119
+
120
+ 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.
121
+
122
+ 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.
123
+
124
+ 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.
125
+
126
+ pyflashgui is provided under the Apache License 2.0, without warranty of any kind.
@@ -0,0 +1,86 @@
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
+ [![CI](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml/badge.svg)](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
8
+ [![release](https://img.shields.io/github/v/release/davhodg/pyflashgui)](https://github.com/davhodg/pyflashgui/releases)
9
+ [![PyPI](https://img.shields.io/pypi/v/pyflashgui)](https://pypi.org/project/pyflashgui/)
10
+ ![python](https://img.shields.io/badge/python-3.12+-blue)
11
+ ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey)
12
+ ![license](https://img.shields.io/badge/license-Apache--2.0-green)
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
+ ![pyflashgui on its simulated probe: the Files pane with a firmware file and a read, the memory map under them, the bytes of the read beside them, and the Event Log below](https://raw.githubusercontent.com/davhodg/pyflashgui/master/pyflashgui/help/main-window.png)
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
+
61
+ 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.
62
+
63
+ ## Probes and targets
64
+
65
+ 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.
66
+
67
+ 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.
68
+
69
+ The [README](https://github.com/davhodg/pyflashgui#probes) says which probes need which drivers.
70
+
71
+ ## More
72
+
73
+ - [Source, issues and releases](https://github.com/davhodg/pyflashgui)
74
+ - [Standalone Windows installer](https://github.com/davhodg/pyflashgui/releases)
75
+ - [Questions and ideas](https://github.com/davhodg/pyflashgui/discussions)
76
+ - [Manual](https://github.com/davhodg/pyflashgui/blob/master/pyflashgui/help/manual.md)
77
+
78
+ ## Safety notice and licence
79
+
80
+ 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.
81
+
82
+ 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.
83
+
84
+ 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.
85
+
86
+ 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
  [![CI](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml/badge.svg)](https://github.com/davhodg/pyflashgui/actions/workflows/ci.yml)
8
- [![release](https://img.shields.io/github/v/release/davhodg/pyflashgui?include_prereleases)](https://github.com/davhodg/pyflashgui/releases)
8
+ [![release](https://img.shields.io/github/v/release/davhodg/pyflashgui)](https://github.com/davhodg/pyflashgui/releases)
9
9
  [![PyPI](https://img.shields.io/pypi/v/pyflashgui)](https://pypi.org/project/pyflashgui/)
10
10
  ![python](https://img.shields.io/badge/python-3.12+-blue)
11
11
  ![platform](https://img.shields.io/badge/platform-Windows%20%7C%20Linux-lightgrey)
@@ -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 must be on the PATH.
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
- At the end of the first run it offers to add pyflashgui to the Start menu, or to the applications menu on Linux, and *Tools > Settings > Add shortcut to Start menu* does the same later. The entry starts the launcher, so a start from the menu after a pull still picks up new libraries.
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
- pyflashgui has not yet been tried with any of these probes: this is what pyOCD says it supports, and how.
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. Of this, listing the probe, connecting and reading have been tried, on one board.
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 a Start menu entry was added, take that away as well:
135
- it is `%APPDATA%\Microsoft\Windows\Start Menu\Programs\pyflashgui.lnk` (on Linux,
136
- `~/.local/share/applications/pyflashgui.desktop`).
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.
@@ -2,5 +2,5 @@
2
2
  # SPDX-FileCopyrightText: 2026 davhodg
3
3
  """pyflashgui — a graphical flash programming tool."""
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.2.0"
6
6
  APP_NAME = "pyflashgui" # single place to change the display name
@@ -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,6 +77,16 @@ 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
92
  # CMSIS-DAP v2 probe and no ST-Link, and says nothing about why.
@@ -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
- Progress = Callable[[float], None]
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,9 +177,21 @@ 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:
@@ -235,6 +276,18 @@ class Backend(ABC):
235
276
  #: Where it is not, a file outside them is worth a warning and is
236
277
  #: not refused: the backend may know of memory that it does not list.
237
278
  map_is_complete: bool = True
279
+ #: What the speed box offers first, for leaving the speed to the
280
+ #: backend: said with what that comes to, since it is not always fast.
281
+ default_speed: str = "Default"
282
+ #: The ways it can restart a target, the one used unless told otherwise
283
+ #: first.
284
+ reset_kinds: tuple[str, ...] = (RESET_DEFAULT,)
285
+ #: Whether it can connect leaving the target running, rather than only
286
+ #: stopping it: ``connect``'s ``stop``.
287
+ can_attach: bool = False
288
+ #: Whether it can program without erasing first, for flash erased
289
+ #: already: ``ERASE_NONE``.
290
+ can_skip_erase: bool = False
238
291
  #: Whether a read can be made without the backend first setting the
239
292
  #: device up in its own way. See ``Connection.read_range``.
240
293
  can_skip_setup: bool = False
@@ -263,6 +316,7 @@ class Backend(ABC):
263
316
  target: str,
264
317
  frequency: int | None = None,
265
318
  interface: str | None = None,
319
+ stop: bool = True,
266
320
  ) -> Connection:
267
321
  """Attach the probe with this unique id to a target of this name.
268
322
 
@@ -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,
@@ -247,6 +248,38 @@ def run(arguments: Sequence[str], commands: Sequence[str], timeout: float, doing
247
248
 
248
249
 
249
250
  # --- reading what Commander says ---------------------------------------------------------
251
+ #: The line SEGGER's software ends a download with, saying how long each part
252
+ #: took: ``J-Link: Flash download: Total: 3.052s (Prepare: 0.071s, Compare:
253
+ #: 0.014s, Erase: 1.890s, Program & Verify: 1.029s, Restore: 0.046s)``. Older
254
+ #: versions say "Total time needed".
255
+ _DOWNLOAD = re.compile(
256
+ r"Flash download:\s*Total(?: time needed)?:\s*([\d.]+)\s*s\s*\(([^)]*)\)", re.I
257
+ )
258
+ _PART = re.compile(r"([A-Za-z& ]+?):\s*([\d.]+)\s*s")
259
+ #: Said when the flash held the image already, and nothing was written.
260
+ _SKIPPED = re.compile(r"Flash download:\s*Skipped", re.I)
261
+
262
+
263
+ def download_text(output: str) -> str | None:
264
+ """What J-Link said a download did, as a sentence: what it took to erase,
265
+ and to program and check, which happen inside one command and cannot be
266
+ shown as they go. None if it did not say."""
267
+ if _SKIPPED.search(output) and not _DOWNLOAD.search(output):
268
+ return "J-Link found the flash held this already, and wrote nothing."
269
+ found = _DOWNLOAD.search(output)
270
+ if found is None:
271
+ return None
272
+ parts = {name.strip().lower(): float(seconds) for name, seconds in _PART.findall(found[2])}
273
+ said = []
274
+ if "erase" in parts:
275
+ said.append(f"erased in {parts['erase']:.1f} s")
276
+ if "program & verify" in parts:
277
+ said.append(f"programmed and checked in {parts['program & verify']:.1f} s")
278
+ if not said:
279
+ return None
280
+ return f"J-Link {' and '.join(said)}, {float(found[1]):.1f} s in all."
281
+
282
+
250
283
  #: One line of ShowEmuList for each J-Link:
251
284
  #: ``J-Link[0]: Connection: USB, Serial number: 801012345, ProductName: J-Link PLUS``
252
285
  _PROBE = re.compile(r"Serial number:\s*(\d+)(?:.*?ProductName:\s*([^,\r\n]+))?", re.I)
@@ -390,7 +423,7 @@ class JLinkConnection(Connection):
390
423
  segments: Sequence[Segment],
391
424
  erase: str = ERASE_SECTORS,
392
425
  progress: Progress = _nothing,
393
- ) -> None:
426
+ ) -> str | None:
394
427
  if erase not in (ERASE_SECTORS, ERASE_CHIP):
395
428
  raise BackendError(f"There is no way of erasing called {erase!r}.")
396
429
  import bincopy
@@ -413,8 +446,9 @@ class JLinkConnection(Connection):
413
446
  # LoadFile erases the sectors it writes to, and checks what it
414
447
  # wrote, by itself.
415
448
  commands.append(f"LoadFile {_named(path)}")
416
- self._run(commands, WORK_S, f"Programming {self._device}")
449
+ said = self._run(commands, WORK_S, f"Programming {self._device}")
417
450
  progress(1.0)
451
+ return download_text(said)
418
452
 
419
453
  def read(
420
454
  self, address: int, length: int, resume: bool = False, skip_setup: bool = False
@@ -491,7 +525,17 @@ class JLinkConnection(Connection):
491
525
  progress(checked / total)
492
526
  return Verification(checked=checked, different=different, first=first)
493
527
 
494
- def reset(self, halt: bool = False) -> None:
528
+ def write(self, address: int, data: bytes) -> None:
529
+ """Stopped while it is written, and set going again, as a read is."""
530
+ with tempfile.TemporaryDirectory(prefix="pyflashgui-jlink-") as folder:
531
+ path = Path(folder) / "write.bin"
532
+ path.write_bytes(data)
533
+ commands = ["H", f"LoadBin {_named(path)}, 0x{address:X}", "G"]
534
+ self._run(commands, WORK_S, f"Writing {len(data)} bytes at {address:08X}")
535
+
536
+ def reset(self, halt: bool = False, kind: str = RESET_DEFAULT) -> None:
537
+ if kind != RESET_DEFAULT:
538
+ raise BackendError("J-Link Commander resets each device its own way, and no other.")
495
539
  # R restarts the device and leaves it stopped; G sets it going.
496
540
  self._run(["R"] if halt else ["R", "G"], QUICK_S, f"Resetting {self._device}")
497
541
 
@@ -500,6 +544,8 @@ class JLinkConnection(Connection):
500
544
 
501
545
 
502
546
  class JLinkBackend(Backend):
547
+ # Commander's -speed auto: the J-Link finds the fastest that works.
548
+ default_speed = "Default (auto)"
503
549
  name = "jlink"
504
550
  label = "J-Link Commander"
505
551
  interfaces = INTERFACES
@@ -561,6 +607,7 @@ class JLinkBackend(Backend):
561
607
  target: str,
562
608
  frequency: int | None = None,
563
609
  interface: str | None = None,
610
+ stop: bool = True,
564
611
  ) -> Connection:
565
612
  if not probe:
566
613
  raise BackendError("No J-Link is chosen.")
@@ -593,4 +640,6 @@ class JLinkBackend(Backend):
593
640
  # With nothing to do but leave: a run that comes back clean has
594
641
  # opened the probe and found the device answering.
595
642
  run(arguments, [], QUICK_S, f"Connecting to {target}")
596
- return JLinkConnection(arguments, target, regions)
643
+ connection = JLinkConnection(arguments, target, regions)
644
+ connection.speed = "the speed J-Link chooses" if speed == "auto" else f"{speed} kHz"
645
+ return connection