rpprog 1.0.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.
- rpprog-1.0.0/LICENSE +21 -0
- rpprog-1.0.0/PKG-INFO +104 -0
- rpprog-1.0.0/README.md +59 -0
- rpprog-1.0.0/pyproject.toml +35 -0
- rpprog-1.0.0/rpprog/rpprog.py +769 -0
- rpprog-1.0.0/rpprog.egg-info/PKG-INFO +104 -0
- rpprog-1.0.0/rpprog.egg-info/SOURCES.txt +10 -0
- rpprog-1.0.0/rpprog.egg-info/dependency_links.txt +1 -0
- rpprog-1.0.0/rpprog.egg-info/entry_points.txt +2 -0
- rpprog-1.0.0/rpprog.egg-info/requires.txt +5 -0
- rpprog-1.0.0/rpprog.egg-info/top_level.txt +1 -0
- rpprog-1.0.0/setup.cfg +4 -0
rpprog-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stefan Wagner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rpprog-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rpprog
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
5
|
+
Author-email: Stefan Wagner <wagiminator@web.de>
|
|
6
|
+
Maintainer-email: Stefan Wagner <wagiminator@web.de>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Stefan Wagner
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://wagiminator.github.io/
|
|
29
|
+
Project-URL: Repository, https://github.com/wagiminator/MCU-Flash-Tools
|
|
30
|
+
Project-URL: Issues, https://github.com/wagiminator/MCU-Flash-Tools/issues
|
|
31
|
+
Keywords: ISP,PICOBOOT,RP2040,RP2350,Raspberry Pi Pico,Pico 2,UF2,microcontroller
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
38
|
+
Requires-Python: >=3.6
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: pyusb
|
|
42
|
+
Requires-Dist: pyserial
|
|
43
|
+
Requires-Dist: libusb-package; platform_system == "Windows"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
47
|
+
## Description
|
|
48
|
+
With this tool, Raspberry Pi RP2040 and RP2350 microcontrollers (e.g. on the Raspberry Pi Pico, Pico W, Pico 2 and Pico 2 W) can be flashed via USB using the PICOBOOT interface of the factory built-in USB bootloader (BOOTSEL mode). In contrast to drag-and-drop, the USB drive does not need to be mounted. Firmware can be written from BIN, UF2, Intel HEX and ELF files and is verified afterwards. In addition, the flash can be erased and read, and a running firmware can be restarted into BOOTSEL mode.
|
|
49
|
+
|
|
50
|
+
Supported microcontrollers: RP2040, RP2350A, RP2350B (Arm and RISC-V firmware), RP2354A/B.
|
|
51
|
+
|
|
52
|
+
## Preparations
|
|
53
|
+
On Linux you do not need to install a driver. However, by default Linux will not expose enough permission to upload your code. In order to fix this, open a terminal and run the following commands:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", MODE="666"' | sudo tee /etc/udev/rules.d/99-rp2-boot.rules
|
|
57
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000f", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-rp2-boot.rules
|
|
58
|
+
sudo udevadm control --reload-rules
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
On Windows, the PICOBOOT interface of the RP2040 needs the WinUSB driver. Install it with the [Zadig](https://zadig.akeo.ie/) tool: click "Options" -> "List All Devices", select "RP2 Boot (Interface 1)" and install the WinUSB driver. The board must be connected in BOOTSEL mode. The RP2350 provides the necessary Microsoft OS descriptors, so Windows usually installs the driver automatically.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
Ensure that the [prerequisites](https://packaging.python.org/en/latest/tutorials/installing-packages/) for installing Python packages are met. Then execute the following command in the command line:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pip install rpprog
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
To enter the USB bootloader (BOOTSEL mode), hold the BOOTSEL button while connecting the board to the USB port of your PC, then release the button. Alternatively, `-b` restarts a running firmware into BOOTSEL mode, either via the reset interface of the Raspberry Pi Pico SDK (stdio_usb) or via a "1200 baud touch" of a USB serial port (e.g. MCU-Templates usb_cdc library, Arduino cores). If several serial ports are present, select the port with `-p`.
|
|
72
|
+
|
|
73
|
+
The flash size is detected automatically by searching for mirrored data, which requires a non-empty flash. If the flash is empty, the default size of the Raspberry Pi Pico (2MB) or Pico 2 (4MB) is used for `-e` and `-r`; use `-s` to set another size. Before writing, rpprog checks whether the image contains a valid second stage bootloader (RP2040) or an IMAGE_DEF block (RP2350) and shows a warning if not. UF2 files of the wrong chip family are rejected.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Usage: rpprog [-h] [-b] [-p PORT] [-e] [-f FLASH] [-a ADDR] [-r READ]
|
|
77
|
+
[-s SIZE] [-x] [-n]
|
|
78
|
+
|
|
79
|
+
Optional arguments:
|
|
80
|
+
-h, --help show this help message and exit
|
|
81
|
+
-b, --bootsel restart running firmware into BOOTSEL mode first
|
|
82
|
+
-p PORT, --port PORT serial port for the 1200 baud touch (with -b)
|
|
83
|
+
-e, --erase perform a whole chip erase
|
|
84
|
+
-f FLASH, --flash FLASH write BIN/UF2/HEX/ELF file to flash and verify
|
|
85
|
+
-a ADDR, --address ADDR flash address for BIN files (default: 0x10000000)
|
|
86
|
+
-r READ, --read READ read flash and save it as BIN file
|
|
87
|
+
-s SIZE, --size SIZE size for -e and -r (default: detected flash size)
|
|
88
|
+
-x, --reboot reboot and start firmware (implied with -e and -f)
|
|
89
|
+
-n, --noreboot stay in BOOTSEL mode after -e or -f
|
|
90
|
+
|
|
91
|
+
Without action arguments, rpprog shows the chip, its bootrom version,
|
|
92
|
+
the flash size and whether a bootable firmware is present.
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
rpprog -f firmware.uf2
|
|
96
|
+
rpprog -b -f firmware.bin
|
|
97
|
+
rpprog -r backup.bin
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Links
|
|
101
|
+
- [MCU Flash Tools](https://github.com/wagiminator/MCU-Flash-Tools)
|
|
102
|
+
- [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
103
|
+
- [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
104
|
+
- [Raspberry Pi picotool](https://github.com/raspberrypi/picotool)
|
rpprog-1.0.0/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
2
|
+
## Description
|
|
3
|
+
With this tool, Raspberry Pi RP2040 and RP2350 microcontrollers (e.g. on the Raspberry Pi Pico, Pico W, Pico 2 and Pico 2 W) can be flashed via USB using the PICOBOOT interface of the factory built-in USB bootloader (BOOTSEL mode). In contrast to drag-and-drop, the USB drive does not need to be mounted. Firmware can be written from BIN, UF2, Intel HEX and ELF files and is verified afterwards. In addition, the flash can be erased and read, and a running firmware can be restarted into BOOTSEL mode.
|
|
4
|
+
|
|
5
|
+
Supported microcontrollers: RP2040, RP2350A, RP2350B (Arm and RISC-V firmware), RP2354A/B.
|
|
6
|
+
|
|
7
|
+
## Preparations
|
|
8
|
+
On Linux you do not need to install a driver. However, by default Linux will not expose enough permission to upload your code. In order to fix this, open a terminal and run the following commands:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", MODE="666"' | sudo tee /etc/udev/rules.d/99-rp2-boot.rules
|
|
12
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000f", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-rp2-boot.rules
|
|
13
|
+
sudo udevadm control --reload-rules
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
On Windows, the PICOBOOT interface of the RP2040 needs the WinUSB driver. Install it with the [Zadig](https://zadig.akeo.ie/) tool: click "Options" -> "List All Devices", select "RP2 Boot (Interface 1)" and install the WinUSB driver. The board must be connected in BOOTSEL mode. The RP2350 provides the necessary Microsoft OS descriptors, so Windows usually installs the driver automatically.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
Ensure that the [prerequisites](https://packaging.python.org/en/latest/tutorials/installing-packages/) for installing Python packages are met. Then execute the following command in the command line:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
pip install rpprog
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
To enter the USB bootloader (BOOTSEL mode), hold the BOOTSEL button while connecting the board to the USB port of your PC, then release the button. Alternatively, `-b` restarts a running firmware into BOOTSEL mode, either via the reset interface of the Raspberry Pi Pico SDK (stdio_usb) or via a "1200 baud touch" of a USB serial port (e.g. MCU-Templates usb_cdc library, Arduino cores). If several serial ports are present, select the port with `-p`.
|
|
27
|
+
|
|
28
|
+
The flash size is detected automatically by searching for mirrored data, which requires a non-empty flash. If the flash is empty, the default size of the Raspberry Pi Pico (2MB) or Pico 2 (4MB) is used for `-e` and `-r`; use `-s` to set another size. Before writing, rpprog checks whether the image contains a valid second stage bootloader (RP2040) or an IMAGE_DEF block (RP2350) and shows a warning if not. UF2 files of the wrong chip family are rejected.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Usage: rpprog [-h] [-b] [-p PORT] [-e] [-f FLASH] [-a ADDR] [-r READ]
|
|
32
|
+
[-s SIZE] [-x] [-n]
|
|
33
|
+
|
|
34
|
+
Optional arguments:
|
|
35
|
+
-h, --help show this help message and exit
|
|
36
|
+
-b, --bootsel restart running firmware into BOOTSEL mode first
|
|
37
|
+
-p PORT, --port PORT serial port for the 1200 baud touch (with -b)
|
|
38
|
+
-e, --erase perform a whole chip erase
|
|
39
|
+
-f FLASH, --flash FLASH write BIN/UF2/HEX/ELF file to flash and verify
|
|
40
|
+
-a ADDR, --address ADDR flash address for BIN files (default: 0x10000000)
|
|
41
|
+
-r READ, --read READ read flash and save it as BIN file
|
|
42
|
+
-s SIZE, --size SIZE size for -e and -r (default: detected flash size)
|
|
43
|
+
-x, --reboot reboot and start firmware (implied with -e and -f)
|
|
44
|
+
-n, --noreboot stay in BOOTSEL mode after -e or -f
|
|
45
|
+
|
|
46
|
+
Without action arguments, rpprog shows the chip, its bootrom version,
|
|
47
|
+
the flash size and whether a bootable firmware is present.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
rpprog -f firmware.uf2
|
|
51
|
+
rpprog -b -f firmware.bin
|
|
52
|
+
rpprog -r backup.bin
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
- [MCU Flash Tools](https://github.com/wagiminator/MCU-Flash-Tools)
|
|
57
|
+
- [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
58
|
+
- [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
59
|
+
- [Raspberry Pi picotool](https://github.com/raspberrypi/picotool)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rpprog"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "USB Programming Tool for Raspberry Pi RP2040 and RP2350"
|
|
9
|
+
keywords = ["ISP", "PICOBOOT", "RP2040", "RP2350", "Raspberry Pi Pico", "Pico 2", "UF2", "microcontroller"]
|
|
10
|
+
dependencies = ["pyusb", "pyserial", "libusb-package; platform_system == 'Windows'"]
|
|
11
|
+
requires-python = ">=3.6"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name="Stefan Wagner", email="wagiminator@web.de" },
|
|
14
|
+
]
|
|
15
|
+
maintainers = [
|
|
16
|
+
{ name="Stefan Wagner", email="wagiminator@web.de" },
|
|
17
|
+
]
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = {file = "LICENSE"}
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
rpprog = "rpprog.rpprog:_main"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://wagiminator.github.io/"
|
|
34
|
+
Repository = "https://github.com/wagiminator/MCU-Flash-Tools"
|
|
35
|
+
Issues = "https://github.com/wagiminator/MCU-Flash-Tools/issues"
|
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ===================================================================================
|
|
3
|
+
# Project: rpprog - USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
4
|
+
# Version: v1.0
|
|
5
|
+
# Year: 2026
|
|
6
|
+
# Author: Stefan Wagner
|
|
7
|
+
# Github: https://github.com/wagiminator
|
|
8
|
+
# License: MIT License
|
|
9
|
+
# ===================================================================================
|
|
10
|
+
#
|
|
11
|
+
# Description:
|
|
12
|
+
# ------------
|
|
13
|
+
# Simple Python tool for flashing Raspberry Pi RP2040 and RP2350 microcontrollers
|
|
14
|
+
# (e.g. Raspberry Pi Pico, Pico W, Pico 2, Pico 2 W) via USB using the PICOBOOT
|
|
15
|
+
# interface of the factory built-in USB bootloader (BOOTSEL mode). No drive has to
|
|
16
|
+
# be mounted. Supported file formats: BIN, UF2, HEX (Intel HEX) and ELF.
|
|
17
|
+
#
|
|
18
|
+
# The tool can also restart a running firmware into BOOTSEL mode, either via the
|
|
19
|
+
# reset interface of the Raspberry Pi Pico SDK (stdio_usb) or via a "1200 baud
|
|
20
|
+
# touch" of a USB serial port (e.g. MCU-Templates usb_cdc library, Arduino cores).
|
|
21
|
+
#
|
|
22
|
+
# References:
|
|
23
|
+
# -----------
|
|
24
|
+
# - Raspberry Pi picotool: https://github.com/raspberrypi/picotool
|
|
25
|
+
# - RP2040 and RP2350 datasheets (chapter "USB Bootloader", "PICOBOOT Interface")
|
|
26
|
+
# - Microsoft UF2: https://github.com/microsoft/uf2
|
|
27
|
+
#
|
|
28
|
+
# Dependencies:
|
|
29
|
+
# -------------
|
|
30
|
+
# - PyUSB
|
|
31
|
+
# - PySerial (only for the 1200 baud touch)
|
|
32
|
+
# - libusb-package (Windows only, provides the libusb DLL)
|
|
33
|
+
#
|
|
34
|
+
# Operating Instructions:
|
|
35
|
+
# -----------------------
|
|
36
|
+
# You need to install PyUSB and PySerial to use rpprog. Install them via
|
|
37
|
+
# "pip install pyusb pyserial". On Windows, also install the libusb backend via
|
|
38
|
+
# "pip install libusb-package".
|
|
39
|
+
#
|
|
40
|
+
# Linux users need permission to access the device. Run:
|
|
41
|
+
# echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", MODE="666"' | sudo tee /etc/udev/rules.d/99-rp2-boot.rules
|
|
42
|
+
# echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000f", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-rp2-boot.rules
|
|
43
|
+
# sudo udevadm control --reload-rules
|
|
44
|
+
#
|
|
45
|
+
# On Windows, the PICOBOOT interface of the RP2040 needs the WinUSB driver. Install it
|
|
46
|
+
# with Zadig (https://zadig.akeo.ie/): click "Options" and "List All Devices", select
|
|
47
|
+
# "RP2 Boot (Interface 1)" and install the WinUSB driver. The RP2350 provides the
|
|
48
|
+
# Microsoft OS descriptors, so Windows usually installs the driver automatically.
|
|
49
|
+
#
|
|
50
|
+
# Hold the BOOTSEL button while connecting the board via USB (BOOTSEL mode). Run:
|
|
51
|
+
# - rpprog.py [-h] [-b] [-p PORT] [-e] [-f FLASH] [-a ADDR] [-r READ] [-s SIZE]
|
|
52
|
+
# [-x] [-n]
|
|
53
|
+
# -h, --help show help message and exit
|
|
54
|
+
# -b, --bootsel restart running firmware into BOOTSEL mode first
|
|
55
|
+
# -p PORT, --port PORT serial port for the 1200 baud touch (with -b)
|
|
56
|
+
# -e, --erase perform a whole chip erase
|
|
57
|
+
# -f FLASH, --flash FLASH write BIN/UF2/HEX/ELF file to flash and verify
|
|
58
|
+
# -a ADDR, --address ADDR flash address for BIN files (default: 0x10000000)
|
|
59
|
+
# -r READ, --read READ read flash and save it as BIN file
|
|
60
|
+
# -s SIZE, --size SIZE size for -e and -r (default: detected flash size)
|
|
61
|
+
# -x, --reboot reboot and start firmware (implied with -e and -f)
|
|
62
|
+
# -n, --noreboot stay in BOOTSEL mode after -e or -f
|
|
63
|
+
# Without action arguments, the chip and its bootloader are shown.
|
|
64
|
+
#
|
|
65
|
+
# - Example:
|
|
66
|
+
# python3 rpprog.py -f firmware.uf2
|
|
67
|
+
# python3 rpprog.py -b -f firmware.bin
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
import sys
|
|
71
|
+
import time
|
|
72
|
+
import struct
|
|
73
|
+
import argparse
|
|
74
|
+
import usb.core
|
|
75
|
+
import usb.util
|
|
76
|
+
|
|
77
|
+
# ===================================================================================
|
|
78
|
+
# Main Function
|
|
79
|
+
# ===================================================================================
|
|
80
|
+
|
|
81
|
+
def _main():
|
|
82
|
+
# Parse command line arguments
|
|
83
|
+
parser = argparse.ArgumentParser(description='Minimal command line interface for RP2040/RP2350 PICOBOOT')
|
|
84
|
+
parser.add_argument('-b', '--bootsel', action='store_true', help='restart running firmware into BOOTSEL mode first')
|
|
85
|
+
parser.add_argument('-p', '--port', help='serial port for the 1200 baud touch (with -b)')
|
|
86
|
+
parser.add_argument('-e', '--erase', action='store_true', help='perform a whole chip erase')
|
|
87
|
+
parser.add_argument('-f', '--flash', help='write BIN/UF2/HEX/ELF file to flash and verify')
|
|
88
|
+
parser.add_argument('-a', '--address', type=lambda x: int(x, 0), default=RP_FLASH_START, metavar='ADDR',
|
|
89
|
+
help='flash address for BIN files (default: 0x10000000)')
|
|
90
|
+
parser.add_argument('-r', '--read', help='read flash and save it as BIN file')
|
|
91
|
+
parser.add_argument('-s', '--size', type=lambda x: int(x, 0), help='size for -e and -r (default: detected flash size)')
|
|
92
|
+
parser.add_argument('-x', '--reboot', action='store_true', help='reboot and start firmware (implied with -e and -f)')
|
|
93
|
+
parser.add_argument('-n', '--noreboot', action='store_true', help='stay in BOOTSEL mode after -e or -f')
|
|
94
|
+
args = parser.parse_args(sys.argv[1:])
|
|
95
|
+
|
|
96
|
+
# Check arguments
|
|
97
|
+
if args.reboot and args.noreboot:
|
|
98
|
+
sys.stderr.write('ERROR: Options -x and -n exclude each other!\n')
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
if args.size is not None and args.size <= 0:
|
|
101
|
+
sys.stderr.write('ERROR: Invalid size!\n')
|
|
102
|
+
sys.exit(1)
|
|
103
|
+
|
|
104
|
+
# Read firmware file
|
|
105
|
+
image = None
|
|
106
|
+
if args.flash is not None:
|
|
107
|
+
try:
|
|
108
|
+
image = FirmwareImage(args.flash, args.address)
|
|
109
|
+
except Exception as ex:
|
|
110
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
# Restart running firmware into BOOTSEL mode
|
|
114
|
+
if args.bootsel:
|
|
115
|
+
try:
|
|
116
|
+
if find_bootsel_device() is None:
|
|
117
|
+
print('Restarting firmware into BOOTSEL mode ...')
|
|
118
|
+
method = enter_bootsel(args.port)
|
|
119
|
+
print('SUCCESS: Restart requested via %s.' % method)
|
|
120
|
+
print('Waiting for USB bootloader ...')
|
|
121
|
+
if not wait_bootsel_device(RP_BOOTSEL_TIMEOUT):
|
|
122
|
+
raise Exception('USB bootloader did not appear')
|
|
123
|
+
else:
|
|
124
|
+
print('INFO: Device is already in BOOTSEL mode.')
|
|
125
|
+
except Exception as ex:
|
|
126
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
# Establish connection to USB bootloader
|
|
130
|
+
try:
|
|
131
|
+
print('Connecting to USB bootloader ...')
|
|
132
|
+
isp = Programmer()
|
|
133
|
+
print('SUCCESS: Found %s with bootrom v%d.' % (isp.chipname, isp.romversion))
|
|
134
|
+
except Exception as ex:
|
|
135
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
# Performing actions
|
|
139
|
+
try:
|
|
140
|
+
# Show chip info only (no action arguments)
|
|
141
|
+
if not any( (args.erase, args.flash, args.read, args.reboot) ):
|
|
142
|
+
for name, value in isp.info():
|
|
143
|
+
print('%-12s %s' % (name + ':', value))
|
|
144
|
+
isp.close()
|
|
145
|
+
print('DONE.')
|
|
146
|
+
sys.exit(0)
|
|
147
|
+
|
|
148
|
+
# Check image
|
|
149
|
+
if image is not None:
|
|
150
|
+
isp.checkimage(image)
|
|
151
|
+
if image.family is not None and image.family not in isp.families:
|
|
152
|
+
raise Exception('UF2 family %s does not match %s' % (uf2_family_name(image.family), isp.chipname))
|
|
153
|
+
for msg in image.warnings(isp.chip):
|
|
154
|
+
print('WARNING: ' + msg)
|
|
155
|
+
|
|
156
|
+
# Determine flash size (if needed)
|
|
157
|
+
size = args.size
|
|
158
|
+
if size is None and (args.erase or args.read):
|
|
159
|
+
print('Detecting flash size ...')
|
|
160
|
+
size = isp.flashsize()
|
|
161
|
+
if size:
|
|
162
|
+
print('SUCCESS: Flash size is %s.' % format_size(size))
|
|
163
|
+
else:
|
|
164
|
+
size = isp.defaultsize
|
|
165
|
+
print('INFO: Flash is empty, size cannot be detected, using %s (set size with -s).' % format_size(size))
|
|
166
|
+
|
|
167
|
+
# Perform chip erase
|
|
168
|
+
if args.erase:
|
|
169
|
+
print('Erasing %s of flash ...' % format_size(size))
|
|
170
|
+
with Progress(size, 'Erasing') as progress:
|
|
171
|
+
isp.erase(RP_FLASH_START, size, progress.update)
|
|
172
|
+
print('SUCCESS: Flash is erased.')
|
|
173
|
+
|
|
174
|
+
# Flash firmware image
|
|
175
|
+
if image is not None:
|
|
176
|
+
print('Flashing %s to %s (%d bytes in %d sectors) ...' % \
|
|
177
|
+
(args.flash, isp.chipname, image.datasize, len(image.sectors)))
|
|
178
|
+
total = len(image.sectors) * RP_SECTOR_SIZE
|
|
179
|
+
with Progress(total, 'Writing') as progress:
|
|
180
|
+
isp.writesectors(image.sectors, not args.erase, progress.update)
|
|
181
|
+
with Progress(total, 'Verifying') as progress:
|
|
182
|
+
isp.verifysectors(image.sectors, progress.update)
|
|
183
|
+
print('SUCCESS: %d bytes written and verified.' % image.datasize)
|
|
184
|
+
|
|
185
|
+
# Read flash
|
|
186
|
+
if args.read is not None:
|
|
187
|
+
print('Reading %s of flash ...' % format_size(size))
|
|
188
|
+
with Progress(size, 'Reading') as progress:
|
|
189
|
+
data = isp.read(RP_FLASH_START, size, progress.update)
|
|
190
|
+
with open(args.read, 'wb') as f: f.write(data)
|
|
191
|
+
print('SUCCESS: %d bytes written to %s.' % (len(data), args.read))
|
|
192
|
+
|
|
193
|
+
# Reboot and start firmware or stay in bootloader
|
|
194
|
+
if (args.reboot or args.erase or image is not None) and not args.noreboot:
|
|
195
|
+
print('Rebooting and starting firmware ...')
|
|
196
|
+
isp.reboot()
|
|
197
|
+
else:
|
|
198
|
+
isp.close()
|
|
199
|
+
|
|
200
|
+
except Exception as ex:
|
|
201
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
202
|
+
isp.close()
|
|
203
|
+
sys.exit(1)
|
|
204
|
+
|
|
205
|
+
print('DONE.')
|
|
206
|
+
sys.exit(0)
|
|
207
|
+
|
|
208
|
+
# ===================================================================================
|
|
209
|
+
# Programmer Class
|
|
210
|
+
# ===================================================================================
|
|
211
|
+
|
|
212
|
+
class Programmer:
|
|
213
|
+
# Find device and connect to PICOBOOT interface
|
|
214
|
+
def __init__(self):
|
|
215
|
+
self.dev = find_bootsel_device()
|
|
216
|
+
if self.dev is None:
|
|
217
|
+
raise Exception('No RP2040/RP2350 in BOOTSEL mode found')
|
|
218
|
+
|
|
219
|
+
# Chip parameters
|
|
220
|
+
if self.dev.idProduct == RP2040_PRODUCT_ID:
|
|
221
|
+
self.chip = 'RP2040'
|
|
222
|
+
self.families = (UF2_FAMILY_RP2040,)
|
|
223
|
+
self.defaultsize = RP2040_DEFAULT_SIZE
|
|
224
|
+
else:
|
|
225
|
+
self.chip = 'RP2350'
|
|
226
|
+
self.families = (UF2_FAMILY_RP2350_ARM_S, UF2_FAMILY_RP2350_ARM_NS, UF2_FAMILY_RP2350_RISCV,
|
|
227
|
+
UF2_FAMILY_ABSOLUTE, UF2_FAMILY_DATA)
|
|
228
|
+
self.defaultsize = RP2350_DEFAULT_SIZE
|
|
229
|
+
self.chipname = self.chip
|
|
230
|
+
|
|
231
|
+
# Find PICOBOOT interface (vendor class, two bulk endpoints)
|
|
232
|
+
try:
|
|
233
|
+
cfg = self.dev.get_active_configuration()
|
|
234
|
+
except usb.core.USBError:
|
|
235
|
+
cfg = None
|
|
236
|
+
try:
|
|
237
|
+
if cfg is None:
|
|
238
|
+
self.dev.set_configuration()
|
|
239
|
+
cfg = self.dev.get_active_configuration()
|
|
240
|
+
except Exception:
|
|
241
|
+
raise Exception('Failed to access USB device. Check permissions')
|
|
242
|
+
self.intf = None
|
|
243
|
+
for intf in cfg:
|
|
244
|
+
if intf.bInterfaceClass == 0xff and intf.bNumEndpoints == 2:
|
|
245
|
+
self.intf = intf
|
|
246
|
+
if self.intf is None:
|
|
247
|
+
raise Exception('PICOBOOT interface not found (disabled by the firmware?)')
|
|
248
|
+
self.ifnum = self.intf.bInterfaceNumber
|
|
249
|
+
self.ep_out = usb.util.find_descriptor(self.intf, custom_match = lambda e: \
|
|
250
|
+
usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
|
|
251
|
+
self.ep_in = usb.util.find_descriptor(self.intf, custom_match = lambda e: \
|
|
252
|
+
usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
|
|
253
|
+
|
|
254
|
+
# Claim interface
|
|
255
|
+
try:
|
|
256
|
+
if self.dev.is_kernel_driver_active(self.ifnum):
|
|
257
|
+
self.dev.detach_kernel_driver(self.ifnum)
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
try:
|
|
261
|
+
usb.util.claim_interface(self.dev, self.ifnum)
|
|
262
|
+
except Exception:
|
|
263
|
+
raise Exception('Failed to claim PICOBOOT interface. Check permissions/driver')
|
|
264
|
+
|
|
265
|
+
# Reset interface and get bootrom version
|
|
266
|
+
self.token = 1
|
|
267
|
+
self.xip = None # XIP state (None: unknown)
|
|
268
|
+
self.eject = False # drive ejected (exclusive access)
|
|
269
|
+
self.reset()
|
|
270
|
+
rom = self.read(RP_ROM_START + 0x10, 4)
|
|
271
|
+
if rom[0:2] != b'Mu':
|
|
272
|
+
raise Exception('Invalid bootrom signature')
|
|
273
|
+
self.romversion = rom[3]
|
|
274
|
+
|
|
275
|
+
# RP2350: get chip info (package, chip ID)
|
|
276
|
+
self.chipid = None
|
|
277
|
+
if self.chip == 'RP2350':
|
|
278
|
+
info = self.getsysinfo(SYS_INFO_CHIP_INFO)
|
|
279
|
+
if info[0] & SYS_INFO_CHIP_INFO:
|
|
280
|
+
self.chipname = 'RP2350' + ('A' if info[1] & 1 else 'B')
|
|
281
|
+
self.chipid = (info[3] << 32) | info[2]
|
|
282
|
+
|
|
283
|
+
# -------------------------------------------------------------------------------
|
|
284
|
+
# PICOBOOT Protocol
|
|
285
|
+
# -------------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
# Reset PICOBOOT interface (clear halt condition of the endpoints)
|
|
288
|
+
def reset(self):
|
|
289
|
+
for ep in (self.ep_in, self.ep_out):
|
|
290
|
+
try: self.dev.clear_halt(ep)
|
|
291
|
+
except Exception: pass
|
|
292
|
+
self.dev.ctrl_transfer(0x41, PICOBOOT_IF_RESET, 0, self.ifnum, None, RP_USB_TIMEOUT)
|
|
293
|
+
|
|
294
|
+
# Get status of the last command (returns status code)
|
|
295
|
+
def status(self):
|
|
296
|
+
try:
|
|
297
|
+
s = self.dev.ctrl_transfer(0xc1, PICOBOOT_IF_CMD_STATUS, 0, self.ifnum, 16, RP_USB_TIMEOUT)
|
|
298
|
+
return struct.unpack('<IIBB', bytes(s[:10]))[1]
|
|
299
|
+
except Exception:
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
# Send command, transfer data and wait for acknowledge
|
|
303
|
+
def command(self, cmdid, args=b'', data=None, length=0, timeout=None):
|
|
304
|
+
if timeout is None: timeout = RP_USB_TIMEOUT
|
|
305
|
+
if data is not None: length = len(data)
|
|
306
|
+
cmd = struct.pack('<IIBBHI', PICOBOOT_MAGIC, self.token, cmdid, len(args), 0, length)
|
|
307
|
+
cmd += args + bytes(16 - len(args))
|
|
308
|
+
self.token += 1
|
|
309
|
+
reply = None
|
|
310
|
+
try:
|
|
311
|
+
self.ep_out.write(cmd, RP_USB_TIMEOUT)
|
|
312
|
+
if length:
|
|
313
|
+
if cmdid & 0x80:
|
|
314
|
+
reply = bytes(self.ep_in.read(length, timeout))
|
|
315
|
+
if len(reply) != length:
|
|
316
|
+
raise Exception('Data transfer incomplete')
|
|
317
|
+
else:
|
|
318
|
+
self.ep_out.write(data, timeout)
|
|
319
|
+
# Acknowledge in opposite direction
|
|
320
|
+
if cmdid & 0x80:
|
|
321
|
+
self.ep_out.write(b'\x00', timeout)
|
|
322
|
+
else:
|
|
323
|
+
self.ep_in.read(64, timeout)
|
|
324
|
+
except Exception as ex:
|
|
325
|
+
code = self.status()
|
|
326
|
+
try: self.reset()
|
|
327
|
+
except Exception: pass
|
|
328
|
+
reason = PICOBOOT_STATUS.get(code, str(ex))
|
|
329
|
+
raise Exception('PICOBOOT command 0x%02x failed (%s)' % (cmdid, reason))
|
|
330
|
+
if cmdid not in (PC_READ, PC_WRITE, PC_EXIT_XIP):
|
|
331
|
+
self.xip = None # other commands may change XIP state
|
|
332
|
+
return reply
|
|
333
|
+
|
|
334
|
+
# Exclusive access (0: not exclusive, 1: exclusive, 2: exclusive and eject drive)
|
|
335
|
+
def exclusive(self, mode):
|
|
336
|
+
if mode == EXCLUSIVE_AND_EJECT and self.eject:
|
|
337
|
+
return
|
|
338
|
+
self.command(PC_EXCLUSIVE_ACCESS, bytes((mode,)))
|
|
339
|
+
self.eject = (mode == EXCLUSIVE_AND_EJECT)
|
|
340
|
+
|
|
341
|
+
# Leave XIP mode (necessary before flash operations; the mass storage interface
|
|
342
|
+
# may change the XIP state as long as the drive is not ejected)
|
|
343
|
+
def exitxip(self):
|
|
344
|
+
if self.xip is not False or not self.eject:
|
|
345
|
+
self.command(PC_EXIT_XIP)
|
|
346
|
+
self.xip = False
|
|
347
|
+
|
|
348
|
+
# Get system info (RP2350 only), returns list of 32-bit words
|
|
349
|
+
def getsysinfo(self, flags):
|
|
350
|
+
reply = self.command(PC_GET_INFO, struct.pack('<BBHIII', PICOBOOT_GET_INFO_SYS, 0, 0, flags, 0, 0),
|
|
351
|
+
length=256)
|
|
352
|
+
return list(struct.unpack('<64I', reply))
|
|
353
|
+
|
|
354
|
+
# -------------------------------------------------------------------------------
|
|
355
|
+
# Memory Operations
|
|
356
|
+
# -------------------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
# Read memory (ROM, RAM or flash)
|
|
359
|
+
def read(self, addr, size, progress=None):
|
|
360
|
+
if is_flash(addr):
|
|
361
|
+
self.exitxip()
|
|
362
|
+
data = bytearray()
|
|
363
|
+
while len(data) < size:
|
|
364
|
+
chunk = min(RP_SECTOR_SIZE, size - len(data))
|
|
365
|
+
data += self.command(PC_READ, struct.pack('<II', addr + len(data), chunk), length=chunk)
|
|
366
|
+
if progress: progress(chunk)
|
|
367
|
+
return bytes(data)
|
|
368
|
+
|
|
369
|
+
# Erase flash (address and size are aligned to sectors)
|
|
370
|
+
def erase(self, addr, size, progress=None):
|
|
371
|
+
start = addr & ~(RP_SECTOR_SIZE - 1)
|
|
372
|
+
end = (addr + size + RP_SECTOR_SIZE - 1) & ~(RP_SECTOR_SIZE - 1)
|
|
373
|
+
self.exclusive(EXCLUSIVE_AND_EJECT)
|
|
374
|
+
self.exitxip()
|
|
375
|
+
while start < end:
|
|
376
|
+
if (start % RP_BLOCK_SIZE == 0) and (end - start >= RP_BLOCK_SIZE):
|
|
377
|
+
chunk = RP_BLOCK_SIZE
|
|
378
|
+
else:
|
|
379
|
+
chunk = RP_SECTOR_SIZE
|
|
380
|
+
self.command(PC_FLASH_ERASE, struct.pack('<II', start, chunk), timeout=RP_USB_TIMEOUT_ERASE)
|
|
381
|
+
start += chunk
|
|
382
|
+
if progress: progress(chunk)
|
|
383
|
+
|
|
384
|
+
# Erase (optional) and write flash sectors (dict: address -> 4096 bytes)
|
|
385
|
+
def writesectors(self, sectors, erase=True, progress=None):
|
|
386
|
+
self.exclusive(EXCLUSIVE_AND_EJECT)
|
|
387
|
+
self.exitxip()
|
|
388
|
+
for addr in sorted(sectors):
|
|
389
|
+
if erase:
|
|
390
|
+
self.command(PC_FLASH_ERASE, struct.pack('<II', addr, RP_SECTOR_SIZE), timeout=RP_USB_TIMEOUT_ERASE)
|
|
391
|
+
self.command(PC_WRITE, struct.pack('<II', addr, RP_SECTOR_SIZE), data=sectors[addr],
|
|
392
|
+
timeout=RP_USB_TIMEOUT_ERASE)
|
|
393
|
+
if progress: progress(RP_SECTOR_SIZE)
|
|
394
|
+
|
|
395
|
+
# Verify flash sectors
|
|
396
|
+
def verifysectors(self, sectors, progress=None):
|
|
397
|
+
for addr in sorted(sectors):
|
|
398
|
+
if self.read(addr, RP_SECTOR_SIZE) != sectors[addr]:
|
|
399
|
+
raise Exception('Verification failed at 0x%08x' % addr)
|
|
400
|
+
if progress: progress(RP_SECTOR_SIZE)
|
|
401
|
+
|
|
402
|
+
# Detect flash size by searching for mirrored data (returns 0 if flash is empty)
|
|
403
|
+
def flashsize(self):
|
|
404
|
+
first = self.read(RP_FLASH_START, 2 * RP_PAGE_SIZE)
|
|
405
|
+
if first[:RP_PAGE_SIZE] == first[RP_PAGE_SIZE:]:
|
|
406
|
+
return 0
|
|
407
|
+
size = RP_FLASH_MAX_SIZE // 2
|
|
408
|
+
while size >= RP_SECTOR_SIZE:
|
|
409
|
+
if self.read(RP_FLASH_START + size, 2 * RP_PAGE_SIZE) != first:
|
|
410
|
+
break
|
|
411
|
+
size //= 2
|
|
412
|
+
return size * 2
|
|
413
|
+
|
|
414
|
+
# Check if firmware image fits into the XIP address range
|
|
415
|
+
def checkimage(self, image):
|
|
416
|
+
for addr in image.sectors:
|
|
417
|
+
if not is_flash(addr) or not is_flash(addr + RP_SECTOR_SIZE - 1):
|
|
418
|
+
raise Exception('Address 0x%08x is outside of the flash address range' % addr)
|
|
419
|
+
|
|
420
|
+
# -------------------------------------------------------------------------------
|
|
421
|
+
# Info, Reboot, Close
|
|
422
|
+
# -------------------------------------------------------------------------------
|
|
423
|
+
|
|
424
|
+
# Get chip info as list of (name, value)
|
|
425
|
+
def info(self):
|
|
426
|
+
result = [('Chip', self.chipname), ('Bootrom', 'v%d' % self.romversion)]
|
|
427
|
+
if self.chipid is not None:
|
|
428
|
+
result.append(('Chip ID', '%016X' % self.chipid))
|
|
429
|
+
try:
|
|
430
|
+
serial = usb.util.get_string(self.dev, self.dev.iSerialNumber)
|
|
431
|
+
result.append(('USB serial', serial))
|
|
432
|
+
except Exception:
|
|
433
|
+
pass
|
|
434
|
+
size = self.flashsize()
|
|
435
|
+
result.append(('Flash', format_size(size) + ' (detected)' if size else 'empty (size cannot be detected)'))
|
|
436
|
+
if size:
|
|
437
|
+
result.append(('Firmware', describe_firmware(self.read(RP_FLASH_START, RP_SECTOR_SIZE), self.chip)))
|
|
438
|
+
return result
|
|
439
|
+
|
|
440
|
+
# Reboot and start firmware from flash
|
|
441
|
+
def reboot(self):
|
|
442
|
+
try:
|
|
443
|
+
if self.chip == 'RP2040':
|
|
444
|
+
self.command(PC_REBOOT, struct.pack('<III', 0, RP2040_SRAM_END, RP_REBOOT_DELAY))
|
|
445
|
+
else:
|
|
446
|
+
self.command(PC_REBOOT2, struct.pack('<IIII', REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE,
|
|
447
|
+
RP_REBOOT_DELAY, RP_FLASH_START, 0))
|
|
448
|
+
except Exception:
|
|
449
|
+
pass
|
|
450
|
+
self.close()
|
|
451
|
+
|
|
452
|
+
# Release USB device (and the drive, if exclusive access was requested)
|
|
453
|
+
def close(self):
|
|
454
|
+
try: self.exclusive(NOT_EXCLUSIVE)
|
|
455
|
+
except Exception: pass
|
|
456
|
+
try: usb.util.release_interface(self.dev, self.ifnum)
|
|
457
|
+
except Exception: pass
|
|
458
|
+
try: usb.util.dispose_resources(self.dev)
|
|
459
|
+
except Exception: pass
|
|
460
|
+
|
|
461
|
+
# ===================================================================================
|
|
462
|
+
# Firmware Image Class (BIN, UF2, HEX, ELF)
|
|
463
|
+
# ===================================================================================
|
|
464
|
+
|
|
465
|
+
class FirmwareImage:
|
|
466
|
+
def __init__(self, filename, binaddr=None):
|
|
467
|
+
if binaddr is None: binaddr = RP_FLASH_START
|
|
468
|
+
with open(filename, 'rb') as f: raw = f.read()
|
|
469
|
+
if len(raw) == 0:
|
|
470
|
+
raise Exception('File is empty')
|
|
471
|
+
self.family = None
|
|
472
|
+
self.chunks = [] # list of (address, data)
|
|
473
|
+
name = filename.lower()
|
|
474
|
+
if raw[:4] == UF2_MAGIC_START0.to_bytes(4, 'little') or name.endswith('.uf2'):
|
|
475
|
+
self.parseuf2(raw)
|
|
476
|
+
elif raw[:4] == b'\x7fELF':
|
|
477
|
+
self.parseelf(raw)
|
|
478
|
+
elif name.endswith('.hex') or name.endswith('.ihex'):
|
|
479
|
+
self.parsehex(raw)
|
|
480
|
+
else:
|
|
481
|
+
self.chunks.append((binaddr, raw))
|
|
482
|
+
if not self.chunks:
|
|
483
|
+
raise Exception('File contains no data for the flash')
|
|
484
|
+
|
|
485
|
+
# Build sectors (4096 bytes, unused bytes are 0xff)
|
|
486
|
+
self.sectors = {}
|
|
487
|
+
self.datasize = 0
|
|
488
|
+
for addr, data in self.chunks:
|
|
489
|
+
self.datasize += len(data)
|
|
490
|
+
for i in range(len(data)):
|
|
491
|
+
a = addr + i
|
|
492
|
+
s = a & ~(RP_SECTOR_SIZE - 1)
|
|
493
|
+
if s not in self.sectors:
|
|
494
|
+
self.sectors[s] = bytearray(b'\xff' * RP_SECTOR_SIZE)
|
|
495
|
+
self.sectors[s][a - s] = data[i]
|
|
496
|
+
for s in self.sectors:
|
|
497
|
+
self.sectors[s] = bytes(self.sectors[s])
|
|
498
|
+
|
|
499
|
+
# UF2 file: 512-byte blocks with 256 bytes payload each
|
|
500
|
+
def parseuf2(self, raw):
|
|
501
|
+
if len(raw) % 512:
|
|
502
|
+
raise Exception('Invalid UF2 file size')
|
|
503
|
+
for i in range(0, len(raw), 512):
|
|
504
|
+
block = raw[i:i+512]
|
|
505
|
+
m0, m1, flags, addr, size, blockno, numblocks, family = struct.unpack('<8I', block[:32])
|
|
506
|
+
mend = struct.unpack('<I', block[508:512])[0]
|
|
507
|
+
if m0 != UF2_MAGIC_START0 or m1 != UF2_MAGIC_START1 or mend != UF2_MAGIC_END:
|
|
508
|
+
raise Exception('Invalid UF2 block %d' % (i // 512))
|
|
509
|
+
if flags & UF2_FLAG_NOT_MAIN_FLASH:
|
|
510
|
+
continue
|
|
511
|
+
if flags & UF2_FLAG_FAMILY_ID_PRESENT:
|
|
512
|
+
if family == UF2_FAMILY_ABSOLUTE and addr == UF2_ABS_BLOCK_ADDR:
|
|
513
|
+
continue # RP2350-E10 workaround block
|
|
514
|
+
if self.family is None and family not in (UF2_FAMILY_ABSOLUTE, UF2_FAMILY_DATA):
|
|
515
|
+
self.family = family
|
|
516
|
+
if size > 476:
|
|
517
|
+
raise Exception('Invalid payload size in UF2 block %d' % (i // 512))
|
|
518
|
+
self.chunks.append((addr, block[32:32+size]))
|
|
519
|
+
|
|
520
|
+
# Intel HEX file (record types 00, 01, 02, 04; 03 and 05 are ignored)
|
|
521
|
+
def parsehex(self, raw):
|
|
522
|
+
base = 0
|
|
523
|
+
for line in raw.decode('ascii', errors='replace').splitlines():
|
|
524
|
+
line = line.strip()
|
|
525
|
+
if not line: continue
|
|
526
|
+
if line[0] != ':':
|
|
527
|
+
raise Exception('Invalid HEX file')
|
|
528
|
+
rec = bytes.fromhex(line[1:])
|
|
529
|
+
if len(rec) < 5 or len(rec) != rec[0] + 5 or (sum(rec) & 0xff):
|
|
530
|
+
raise Exception('Invalid HEX record: %s' % line)
|
|
531
|
+
addr, rtype, data = (rec[1] << 8) | rec[2], rec[3], rec[4:-1]
|
|
532
|
+
if rtype == 0x00: self.chunks.append((base + addr, data))
|
|
533
|
+
elif rtype == 0x01: break
|
|
534
|
+
elif rtype == 0x02: base = ((data[0] << 8) | data[1]) << 4
|
|
535
|
+
elif rtype == 0x04: base = ((data[0] << 8) | data[1]) << 16
|
|
536
|
+
|
|
537
|
+
# ELF file (32-bit little endian, loadable segments at their physical address)
|
|
538
|
+
def parseelf(self, raw):
|
|
539
|
+
if raw[4] != 1 or raw[5] != 1:
|
|
540
|
+
raise Exception('Only 32-bit little endian ELF files are supported')
|
|
541
|
+
phoff, = struct.unpack('<I', raw[28:32])
|
|
542
|
+
phentsize, phnum = struct.unpack('<HH', raw[42:46])
|
|
543
|
+
for i in range(phnum):
|
|
544
|
+
p = phoff + i * phentsize
|
|
545
|
+
ptype, offset, vaddr, paddr, filesz = struct.unpack('<5I', raw[p:p+20])
|
|
546
|
+
if ptype == 1 and filesz > 0: # PT_LOAD
|
|
547
|
+
self.chunks.append((paddr, raw[offset:offset+filesz]))
|
|
548
|
+
|
|
549
|
+
# Check for typical problems of the image
|
|
550
|
+
def warnings(self, chip):
|
|
551
|
+
result = []
|
|
552
|
+
first = self.sectors.get(RP_FLASH_START)
|
|
553
|
+
if first is None:
|
|
554
|
+
result.append('Image does not start at 0x10000000, the chip may not boot')
|
|
555
|
+
elif chip == 'RP2040' and not rp2040_boot2_valid(first):
|
|
556
|
+
result.append('No valid second stage bootloader (boot2) found, the RP2040 will not boot')
|
|
557
|
+
elif chip == 'RP2350' and RP2350_BLOCK_MARKER not in first:
|
|
558
|
+
result.append('No IMAGE_DEF block found in the first 4KB, the RP2350 will not boot')
|
|
559
|
+
return result
|
|
560
|
+
|
|
561
|
+
# ===================================================================================
|
|
562
|
+
# Helper Functions
|
|
563
|
+
# ===================================================================================
|
|
564
|
+
|
|
565
|
+
# Get libusb backend (libusb-package provides the libusb DLL on Windows)
|
|
566
|
+
def _backend():
|
|
567
|
+
try:
|
|
568
|
+
import libusb_package
|
|
569
|
+
return libusb_package.get_libusb1_backend()
|
|
570
|
+
except ImportError:
|
|
571
|
+
return None
|
|
572
|
+
|
|
573
|
+
# Find device in BOOTSEL mode
|
|
574
|
+
def find_bootsel_device():
|
|
575
|
+
for pid in (RP2040_PRODUCT_ID, RP2350_PRODUCT_ID):
|
|
576
|
+
dev = usb.core.find(idVendor = RP_VENDOR_ID, idProduct = pid, backend = _backend())
|
|
577
|
+
if dev is not None:
|
|
578
|
+
return dev
|
|
579
|
+
return None
|
|
580
|
+
|
|
581
|
+
# Wait for device in BOOTSEL mode
|
|
582
|
+
def wait_bootsel_device(timeout):
|
|
583
|
+
end = time.time() + timeout
|
|
584
|
+
while time.time() < end:
|
|
585
|
+
if find_bootsel_device() is not None:
|
|
586
|
+
time.sleep(0.5) # let the OS finish enumeration
|
|
587
|
+
return True
|
|
588
|
+
time.sleep(0.2)
|
|
589
|
+
return False
|
|
590
|
+
|
|
591
|
+
# Restart running firmware into BOOTSEL mode, returns used method
|
|
592
|
+
def enter_bootsel(port=None):
|
|
593
|
+
# Pico SDK reset interface (vendor class, subclass 0x00, protocol 0x01)
|
|
594
|
+
if port is None:
|
|
595
|
+
for dev in usb.core.find(find_all = True, backend = _backend()):
|
|
596
|
+
try:
|
|
597
|
+
for cfg in dev:
|
|
598
|
+
for intf in cfg:
|
|
599
|
+
if intf.bInterfaceClass == 0xff and intf.bInterfaceSubClass == RESET_INTERFACE_SUBCLASS \
|
|
600
|
+
and intf.bInterfaceProtocol == RESET_INTERFACE_PROTOCOL:
|
|
601
|
+
try:
|
|
602
|
+
dev.ctrl_transfer(0x21, RESET_REQUEST_BOOTSEL, 0, intf.bInterfaceNumber, None, 2000)
|
|
603
|
+
except Exception:
|
|
604
|
+
pass # device may reset before answering
|
|
605
|
+
return 'reset interface of %04x:%04x' % (dev.idVendor, dev.idProduct)
|
|
606
|
+
except Exception:
|
|
607
|
+
continue
|
|
608
|
+
|
|
609
|
+
# 1200 baud touch on a USB serial port
|
|
610
|
+
try:
|
|
611
|
+
import serial
|
|
612
|
+
from serial.tools.list_ports import comports
|
|
613
|
+
except ImportError:
|
|
614
|
+
raise Exception('No reset interface found and PySerial is not installed')
|
|
615
|
+
if port is None:
|
|
616
|
+
candidates = [p.device for p in comports() if (p.vid, p.pid) in RP_SERIAL_IDS or p.vid == RP_VENDOR_ID]
|
|
617
|
+
if len(candidates) == 0:
|
|
618
|
+
raise Exception('No running firmware with reset interface or USB serial port found (set port with -p)')
|
|
619
|
+
if len(candidates) > 1:
|
|
620
|
+
raise Exception('Several serial ports found (%s), select one with -p' % ', '.join(candidates))
|
|
621
|
+
port = candidates[0]
|
|
622
|
+
try:
|
|
623
|
+
ser = serial.Serial(port, 1200)
|
|
624
|
+
ser.dtr = False
|
|
625
|
+
time.sleep(0.1)
|
|
626
|
+
ser.close()
|
|
627
|
+
except Exception as ex:
|
|
628
|
+
raise Exception('Failed to open serial port %s (%s)' % (port, str(ex)))
|
|
629
|
+
return '1200 baud touch on ' + port
|
|
630
|
+
|
|
631
|
+
# Check if address is in the XIP flash range
|
|
632
|
+
def is_flash(addr):
|
|
633
|
+
return RP_FLASH_START <= addr < RP_FLASH_START + RP_FLASH_MAX_SIZE
|
|
634
|
+
|
|
635
|
+
# CRC-32/MPEG-2 (used by the RP2040 boot2 checksum)
|
|
636
|
+
def crc32_mpeg2(data):
|
|
637
|
+
crc = 0xffffffff
|
|
638
|
+
for b in data:
|
|
639
|
+
crc ^= b << 24
|
|
640
|
+
for _ in range(8):
|
|
641
|
+
crc = ((crc << 1) ^ 0x04c11db7) & 0xffffffff if crc & 0x80000000 else (crc << 1) & 0xffffffff
|
|
642
|
+
return crc
|
|
643
|
+
|
|
644
|
+
# Check boot2 checksum of the RP2040 (first 252 bytes, CRC in bytes 252..255)
|
|
645
|
+
def rp2040_boot2_valid(sector):
|
|
646
|
+
return crc32_mpeg2(sector[:252]) == struct.unpack('<I', sector[252:256])[0]
|
|
647
|
+
|
|
648
|
+
# Short description of the firmware in the first flash sector
|
|
649
|
+
def describe_firmware(sector, chip):
|
|
650
|
+
if sector == b'\xff' * len(sector):
|
|
651
|
+
return 'none (first sector is empty)'
|
|
652
|
+
if chip == 'RP2040':
|
|
653
|
+
return 'valid boot2 found' if rp2040_boot2_valid(sector) else 'no valid boot2 (will not boot)'
|
|
654
|
+
return 'IMAGE_DEF block found' if RP2350_BLOCK_MARKER in sector else 'no IMAGE_DEF block in the first 4KB'
|
|
655
|
+
|
|
656
|
+
# Format size in bytes as string
|
|
657
|
+
def format_size(size):
|
|
658
|
+
if size >= 1024 * 1024 and size % (1024 * 1024) == 0: return '%d MB' % (size // (1024 * 1024))
|
|
659
|
+
if size >= 1024 and size % 1024 == 0: return '%d KB' % (size // 1024)
|
|
660
|
+
return '%d bytes' % size
|
|
661
|
+
|
|
662
|
+
# Name of UF2 family ID
|
|
663
|
+
def uf2_family_name(family):
|
|
664
|
+
return UF2_FAMILY_NAMES.get(family, '0x%08x' % family)
|
|
665
|
+
|
|
666
|
+
# Simple progress bar
|
|
667
|
+
class Progress:
|
|
668
|
+
def __init__(self, total, desc):
|
|
669
|
+
self.total = max(total, 1)
|
|
670
|
+
self.done = 0
|
|
671
|
+
self.desc = desc
|
|
672
|
+
self.show()
|
|
673
|
+
def update(self, n):
|
|
674
|
+
self.done = min(self.done + n, self.total)
|
|
675
|
+
self.show()
|
|
676
|
+
def show(self):
|
|
677
|
+
pct = self.done * 100 // self.total
|
|
678
|
+
bar = '#' * (pct // 4)
|
|
679
|
+
sys.stdout.write('\r%-10s [%-25s] %3d%%' % (self.desc, bar, pct))
|
|
680
|
+
sys.stdout.flush()
|
|
681
|
+
def __enter__(self):
|
|
682
|
+
return self
|
|
683
|
+
def __exit__(self, exc_type, exc, tb):
|
|
684
|
+
sys.stdout.write('\n')
|
|
685
|
+
sys.stdout.flush()
|
|
686
|
+
|
|
687
|
+
# ===================================================================================
|
|
688
|
+
# Protocol Constants
|
|
689
|
+
# ===================================================================================
|
|
690
|
+
|
|
691
|
+
RP_VENDOR_ID = 0x2e8a # Raspberry Pi
|
|
692
|
+
RP2040_PRODUCT_ID = 0x0003 # RP2040 USB bootloader
|
|
693
|
+
RP2350_PRODUCT_ID = 0x000f # RP2350 USB bootloader
|
|
694
|
+
RP_SERIAL_IDS = ((0x16c0, 0x27dd),) # USB CDC of the MCU-Templates (1200 baud touch)
|
|
695
|
+
|
|
696
|
+
RP_USB_TIMEOUT = 3000 # timeout for USB operations (ms)
|
|
697
|
+
RP_USB_TIMEOUT_ERASE = 10000 # timeout for flash erase and write (ms)
|
|
698
|
+
RP_BOOTSEL_TIMEOUT = 10 # time to wait for the USB bootloader after restart (s)
|
|
699
|
+
RP_REBOOT_DELAY = 500 # delay before reboot (ms)
|
|
700
|
+
|
|
701
|
+
RP_ROM_START = 0x00000000 # boot ROM
|
|
702
|
+
RP_FLASH_START = 0x10000000 # flash (XIP)
|
|
703
|
+
RP_FLASH_MAX_SIZE = 0x01000000 # max addressable flash size (16 MB)
|
|
704
|
+
RP2040_SRAM_END = 0x20042000 # end of SRAM (RP2040)
|
|
705
|
+
RP_PAGE_SIZE = 256 # flash page size (programming)
|
|
706
|
+
RP_SECTOR_SIZE = 4096 # flash sector size (erasing)
|
|
707
|
+
RP_BLOCK_SIZE = 65536 # flash block size (fast erase)
|
|
708
|
+
RP2040_DEFAULT_SIZE = 2 * 1024 * 1024 # default flash size (Raspberry Pi Pico)
|
|
709
|
+
RP2350_DEFAULT_SIZE = 4 * 1024 * 1024 # default flash size (Raspberry Pi Pico 2)
|
|
710
|
+
RP2350_BLOCK_MARKER = b'\xd3\xde\xff\xff' # PICOBIN_BLOCK_MARKER_START (0xffffded3)
|
|
711
|
+
|
|
712
|
+
PICOBOOT_MAGIC = 0x431fd10b
|
|
713
|
+
PICOBOOT_IF_RESET = 0x41
|
|
714
|
+
PICOBOOT_IF_CMD_STATUS = 0x42
|
|
715
|
+
|
|
716
|
+
PC_EXCLUSIVE_ACCESS = 0x01
|
|
717
|
+
PC_REBOOT = 0x02 # RP2040 only
|
|
718
|
+
PC_FLASH_ERASE = 0x03
|
|
719
|
+
PC_READ = 0x84
|
|
720
|
+
PC_WRITE = 0x05
|
|
721
|
+
PC_EXIT_XIP = 0x06
|
|
722
|
+
PC_ENTER_CMD_XIP = 0x07
|
|
723
|
+
PC_REBOOT2 = 0x0a # RP2350 only
|
|
724
|
+
PC_GET_INFO = 0x8b # RP2350 only
|
|
725
|
+
|
|
726
|
+
NOT_EXCLUSIVE = 0
|
|
727
|
+
EXCLUSIVE = 1
|
|
728
|
+
EXCLUSIVE_AND_EJECT = 2
|
|
729
|
+
|
|
730
|
+
PICOBOOT_GET_INFO_SYS = 1
|
|
731
|
+
SYS_INFO_CHIP_INFO = 0x0001
|
|
732
|
+
REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE = 0x4
|
|
733
|
+
|
|
734
|
+
RESET_INTERFACE_SUBCLASS = 0x00
|
|
735
|
+
RESET_INTERFACE_PROTOCOL = 0x01
|
|
736
|
+
RESET_REQUEST_BOOTSEL = 0x01
|
|
737
|
+
|
|
738
|
+
PICOBOOT_STATUS = {
|
|
739
|
+
0: 'ok', 1: 'unknown command', 2: 'invalid command length', 3: 'invalid transfer length',
|
|
740
|
+
4: 'invalid address', 5: 'bad alignment', 6: 'interleaved write', 7: 'rebooting',
|
|
741
|
+
8: 'unknown error', 9: 'invalid state', 10: 'not permitted', 11: 'invalid argument',
|
|
742
|
+
12: 'buffer too small', 13: 'precondition not met', 14: 'modified data', 15: 'invalid data',
|
|
743
|
+
16: 'not found', 17: 'unsupported modification'
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
UF2_MAGIC_START0 = 0x0a324655
|
|
747
|
+
UF2_MAGIC_START1 = 0x9e5d5157
|
|
748
|
+
UF2_MAGIC_END = 0x0ab16f30
|
|
749
|
+
UF2_FLAG_NOT_MAIN_FLASH = 0x00000001
|
|
750
|
+
UF2_FLAG_FAMILY_ID_PRESENT = 0x00002000
|
|
751
|
+
UF2_ABS_BLOCK_ADDR = 0x10ffff00 # address of the RP2350-E10 workaround block
|
|
752
|
+
|
|
753
|
+
UF2_FAMILY_RP2040 = 0xe48bff56
|
|
754
|
+
UF2_FAMILY_ABSOLUTE = 0xe48bff57
|
|
755
|
+
UF2_FAMILY_DATA = 0xe48bff58
|
|
756
|
+
UF2_FAMILY_RP2350_ARM_S = 0xe48bff59
|
|
757
|
+
UF2_FAMILY_RP2350_RISCV = 0xe48bff5a
|
|
758
|
+
UF2_FAMILY_RP2350_ARM_NS = 0xe48bff5b
|
|
759
|
+
|
|
760
|
+
UF2_FAMILY_NAMES = {
|
|
761
|
+
UF2_FAMILY_RP2040: 'rp2040', UF2_FAMILY_ABSOLUTE: 'absolute', UF2_FAMILY_DATA: 'data',
|
|
762
|
+
UF2_FAMILY_RP2350_ARM_S: 'rp2350-arm-s', UF2_FAMILY_RP2350_RISCV: 'rp2350-riscv',
|
|
763
|
+
UF2_FAMILY_RP2350_ARM_NS: 'rp2350-arm-ns'
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
# ===================================================================================
|
|
767
|
+
|
|
768
|
+
if __name__ == "__main__":
|
|
769
|
+
_main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rpprog
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
5
|
+
Author-email: Stefan Wagner <wagiminator@web.de>
|
|
6
|
+
Maintainer-email: Stefan Wagner <wagiminator@web.de>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 Stefan Wagner
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://wagiminator.github.io/
|
|
29
|
+
Project-URL: Repository, https://github.com/wagiminator/MCU-Flash-Tools
|
|
30
|
+
Project-URL: Issues, https://github.com/wagiminator/MCU-Flash-Tools/issues
|
|
31
|
+
Keywords: ISP,PICOBOOT,RP2040,RP2350,Raspberry Pi Pico,Pico 2,UF2,microcontroller
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
38
|
+
Requires-Python: >=3.6
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: pyusb
|
|
42
|
+
Requires-Dist: pyserial
|
|
43
|
+
Requires-Dist: libusb-package; platform_system == "Windows"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# USB Programming Tool for Raspberry Pi RP2040 and RP2350
|
|
47
|
+
## Description
|
|
48
|
+
With this tool, Raspberry Pi RP2040 and RP2350 microcontrollers (e.g. on the Raspberry Pi Pico, Pico W, Pico 2 and Pico 2 W) can be flashed via USB using the PICOBOOT interface of the factory built-in USB bootloader (BOOTSEL mode). In contrast to drag-and-drop, the USB drive does not need to be mounted. Firmware can be written from BIN, UF2, Intel HEX and ELF files and is verified afterwards. In addition, the flash can be erased and read, and a running firmware can be restarted into BOOTSEL mode.
|
|
49
|
+
|
|
50
|
+
Supported microcontrollers: RP2040, RP2350A, RP2350B (Arm and RISC-V firmware), RP2354A/B.
|
|
51
|
+
|
|
52
|
+
## Preparations
|
|
53
|
+
On Linux you do not need to install a driver. However, by default Linux will not expose enough permission to upload your code. In order to fix this, open a terminal and run the following commands:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="0003", MODE="666"' | sudo tee /etc/udev/rules.d/99-rp2-boot.rules
|
|
57
|
+
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2e8a", ATTR{idProduct}=="000f", MODE="666"' | sudo tee -a /etc/udev/rules.d/99-rp2-boot.rules
|
|
58
|
+
sudo udevadm control --reload-rules
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
On Windows, the PICOBOOT interface of the RP2040 needs the WinUSB driver. Install it with the [Zadig](https://zadig.akeo.ie/) tool: click "Options" -> "List All Devices", select "RP2 Boot (Interface 1)" and install the WinUSB driver. The board must be connected in BOOTSEL mode. The RP2350 provides the necessary Microsoft OS descriptors, so Windows usually installs the driver automatically.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
Ensure that the [prerequisites](https://packaging.python.org/en/latest/tutorials/installing-packages/) for installing Python packages are met. Then execute the following command in the command line:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
pip install rpprog
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
To enter the USB bootloader (BOOTSEL mode), hold the BOOTSEL button while connecting the board to the USB port of your PC, then release the button. Alternatively, `-b` restarts a running firmware into BOOTSEL mode, either via the reset interface of the Raspberry Pi Pico SDK (stdio_usb) or via a "1200 baud touch" of a USB serial port (e.g. MCU-Templates usb_cdc library, Arduino cores). If several serial ports are present, select the port with `-p`.
|
|
72
|
+
|
|
73
|
+
The flash size is detected automatically by searching for mirrored data, which requires a non-empty flash. If the flash is empty, the default size of the Raspberry Pi Pico (2MB) or Pico 2 (4MB) is used for `-e` and `-r`; use `-s` to set another size. Before writing, rpprog checks whether the image contains a valid second stage bootloader (RP2040) or an IMAGE_DEF block (RP2350) and shows a warning if not. UF2 files of the wrong chip family are rejected.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Usage: rpprog [-h] [-b] [-p PORT] [-e] [-f FLASH] [-a ADDR] [-r READ]
|
|
77
|
+
[-s SIZE] [-x] [-n]
|
|
78
|
+
|
|
79
|
+
Optional arguments:
|
|
80
|
+
-h, --help show this help message and exit
|
|
81
|
+
-b, --bootsel restart running firmware into BOOTSEL mode first
|
|
82
|
+
-p PORT, --port PORT serial port for the 1200 baud touch (with -b)
|
|
83
|
+
-e, --erase perform a whole chip erase
|
|
84
|
+
-f FLASH, --flash FLASH write BIN/UF2/HEX/ELF file to flash and verify
|
|
85
|
+
-a ADDR, --address ADDR flash address for BIN files (default: 0x10000000)
|
|
86
|
+
-r READ, --read READ read flash and save it as BIN file
|
|
87
|
+
-s SIZE, --size SIZE size for -e and -r (default: detected flash size)
|
|
88
|
+
-x, --reboot reboot and start firmware (implied with -e and -f)
|
|
89
|
+
-n, --noreboot stay in BOOTSEL mode after -e or -f
|
|
90
|
+
|
|
91
|
+
Without action arguments, rpprog shows the chip, its bootrom version,
|
|
92
|
+
the flash size and whether a bootable firmware is present.
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
rpprog -f firmware.uf2
|
|
96
|
+
rpprog -b -f firmware.bin
|
|
97
|
+
rpprog -r backup.bin
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Links
|
|
101
|
+
- [MCU Flash Tools](https://github.com/wagiminator/MCU-Flash-Tools)
|
|
102
|
+
- [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
103
|
+
- [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
104
|
+
- [Raspberry Pi picotool](https://github.com/raspberrypi/picotool)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rpprog
|
rpprog-1.0.0/setup.cfg
ADDED