mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-25 06:23:53 +01:00
Merge pull request #12251 from maz3max/bmp-tool
Add helper script for Black Magic Probe
This commit is contained in:
commit
10824e1cd0
131
dist/tools/bmp/README.md
vendored
Normal file
131
dist/tools/bmp/README.md
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
Black Magic Probe Helper script
|
||||
================================
|
||||
|
||||
This script can detect [Black Magic Probes](https://github.com/blacksphere/blackmagic/wiki) and act as a flashloader (and more).
|
||||
It is compatible with Linux and macOS.
|
||||
|
||||
All important options that can be set via the monitor command are available as arguments.
|
||||
|
||||
For example, SWD is the default protocol, but JTAG can be used by specifying `--jtag`.
|
||||
|
||||
```
|
||||
usage: bmp.py [-h] [--jtag] [--swd] [--connect-srst] [--tpwr]
|
||||
[--serial SERIAL] [--port PORT] [--attach ATTACH]
|
||||
[--gdb-path GDB_PATH] [--term-cmd TERM_CMD]
|
||||
[{list,flash,erase,debug,term,reset}] [file]
|
||||
|
||||
Black Magic Tool helper script.
|
||||
|
||||
positional arguments:
|
||||
{list,flash,erase,debug,term,reset}
|
||||
choose a task to perform
|
||||
file file to load to target (hex or elf)
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--jtag use JTAG transport
|
||||
--swd use SWD transport (default)
|
||||
--connect-srst reset target while connecting
|
||||
--tpwr enable target power
|
||||
--serial SERIAL choose specific probe by serial number
|
||||
--port PORT choose specific probe by port
|
||||
--attach ATTACH choose specific target by number
|
||||
--gdb-path GDB_PATH path to GDB
|
||||
--term-cmd TERM_CMD serial terminal command
|
||||
```
|
||||
|
||||
## Available Actions
|
||||
* `list` lists connected targets (default action)
|
||||
* `flash` load file to target
|
||||
* `erase` erase target flash
|
||||
* `debug` start GDB shell that is attached to target
|
||||
* `term` start TTY emulator program to look into connected UART
|
||||
* `reset` reset target (using RST line)
|
||||
|
||||
## Examples (tested with BluePill STM32F103F8C6)
|
||||
* test connection:
|
||||
```
|
||||
user@pc:~$ ./bmp.py --connect-srst
|
||||
found following Black Magic GDB servers:
|
||||
[/dev/ttyACM0] Serial: BDD391D6 <- default
|
||||
connecting to [/dev/ttyACM0]...
|
||||
connecting successful.
|
||||
scanning using SWD...
|
||||
found following targets:
|
||||
STM32F1 medium density M3/M4
|
||||
```
|
||||
* flashing:
|
||||
```
|
||||
user@pc:~$ ./bmp.py --connect-srst flash example.elf
|
||||
found following Black Magic GDB servers:
|
||||
[/dev/ttyACM0] Serial: BDD391D6 <- default
|
||||
connecting to [/dev/ttyACM0]...
|
||||
connecting successful.
|
||||
scanning using SWD...
|
||||
found following targets:
|
||||
STM32F1 medium density M3/M4
|
||||
|
||||
attaching to target successful.
|
||||
downloading... total size: 742.5K
|
||||
downloading section [.text] (12.8K) |
|
||||
100%|##########################################################################|
|
||||
downloading section [.relocate] (120B) |
|
||||
100%|##########################################################################|
|
||||
downloading finished
|
||||
checking flash successful.
|
||||
killing successful.
|
||||
```
|
||||
* erasing:
|
||||
```
|
||||
user@pc:~$ ./bmp.py --connect-srst erase
|
||||
found following Black Magic GDB servers:
|
||||
[/dev/ttyACM0] Serial: BDD391D6 <- default
|
||||
connecting to [/dev/ttyACM0]...
|
||||
connecting successful.
|
||||
scanning using SWD...
|
||||
found following targets:
|
||||
STM32F1 medium density M3/M4
|
||||
|
||||
attaching to target successful.
|
||||
erasing...
|
||||
erasing target successful.
|
||||
```
|
||||
* open GDB shell:
|
||||
```
|
||||
user@pc:~$ ./bmp.py --connect-srst debug example.elf
|
||||
found following Black Magic GDB servers:
|
||||
[/dev/ttyACM0] Serial: BDD391D6 <- default
|
||||
connecting to [/dev/ttyACM0]...
|
||||
GNU gdb (Debian 8.2.1-2+b1) 8.2.1
|
||||
Copyright (C) 2018 Free Software Foundation, Inc.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law.
|
||||
Type "show copying" and "show warranty" for details.
|
||||
This GDB was configured as "x86_64-linux-gnu".
|
||||
Type "show configuration" for configuration details.
|
||||
For bug reporting instructions, please see:
|
||||
<http://www.gnu.org/software/gdb/bugs/>.
|
||||
Find the GDB manual and other documentation resources online at:
|
||||
<http://www.gnu.org/software/gdb/documentation/>.
|
||||
|
||||
For help, type "help".
|
||||
Type "apropos word" to search for commands related to "word"...
|
||||
Reading symbols from /home/user/dev/helloriot/bin/bluepill/example.elf...done.
|
||||
Remote debugging using /dev/ttyACM0
|
||||
Assert SRST during connect: enabled
|
||||
Target voltage: unknown
|
||||
Available Targets:
|
||||
No. Att Driver
|
||||
1 STM32F1 medium density M3/M4
|
||||
Attaching to program: /home/user/dev/helloriot/bin/bluepill/example.elf, Remote target
|
||||
reset_handler_default ()
|
||||
at /home/max/dev/RIOT/cpu/cortexm_common/vectors_cortexm.c:79
|
||||
79 {
|
||||
(gdb)
|
||||
```
|
||||
* open UART:
|
||||
```
|
||||
user@pc:~$ ./bmp.py --connect-srst term
|
||||
main(): This is RIOT! (Version: 2019.10-devel-800-g4f5d6)
|
||||
```
|
||||
287
dist/tools/bmp/bmp.py
vendored
Executable file
287
dist/tools/bmp/bmp.py
vendored
Executable file
@ -0,0 +1,287 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
#
|
||||
# @author Maximilian Deubel <maximilian.deubel@ovgu.de>
|
||||
|
||||
# Black Magic Probe helper script
|
||||
# This script can detect connected Black Magic Probes and can be used as a flashloader and much more
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import humanize
|
||||
import serial.tools.list_ports
|
||||
from progressbar import Bar, Percentage, ProgressBar
|
||||
from pygdbmi.gdbcontroller import GdbController
|
||||
import distutils.spawn
|
||||
|
||||
parser = argparse.ArgumentParser(description='Black Magic Tool helper script.')
|
||||
parser.add_argument('--jtag', action='store_true', help='use JTAG transport')
|
||||
parser.add_argument('--swd', action='store_true', help='use SWD transport (default)')
|
||||
parser.add_argument('--connect-srst', action='store_true', help='reset target while connecting')
|
||||
parser.add_argument('--tpwr', action='store_true', help='enable target power')
|
||||
parser.add_argument('--serial', help='choose specific probe by serial number')
|
||||
parser.add_argument('--port', help='choose specific probe by port')
|
||||
parser.add_argument('--attach', help='choose specific target by number', default='1')
|
||||
parser.add_argument('--gdb-path', help='path to GDB', default='gdb-multiarch')
|
||||
parser.add_argument('--term-cmd', help='serial terminal command',
|
||||
default='picocom --nolock --imap lfcrlf --baud 115200 %s')
|
||||
parser.add_argument('action', help='choose a task to perform', nargs='?',
|
||||
choices=['list', 'flash', 'erase', 'debug', 'term', 'reset'],
|
||||
default='list')
|
||||
parser.add_argument('file', help='file to load to target (hex or elf)', nargs='?')
|
||||
|
||||
TIMEOUT = 100 # seconds
|
||||
|
||||
|
||||
# find a suitable gdb executable, falling back to defaults if needed
|
||||
def find_suitable_gdb(gdb_path):
|
||||
if distutils.spawn.find_executable(gdb_path):
|
||||
return gdb_path
|
||||
else:
|
||||
for p in ['arm-none-eabi-gdb', 'gdb-multiarch']:
|
||||
p = distutils.spawn.find_executable(p)
|
||||
if p:
|
||||
print("GDB EXECUTABLE NOT FOUND! FALLING BACK TO %s" % p, file=sys.stderr)
|
||||
return p
|
||||
print("CANNOT LOCATE SUITABLE GDB EXECUTABLE!", file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
# find all connected BMPs and store both GDB and UART interfaces
|
||||
def detect_probes():
|
||||
gdb_ports = []
|
||||
uart_ports = []
|
||||
for p in serial.tools.list_ports.comports():
|
||||
if p.vid == 0x1D50 and p.pid in {0x6018, 0x6017}:
|
||||
if re.fullmatch(r'COM\d\d', p.device):
|
||||
p.device = '//./' + p.device
|
||||
if 'GDB' in str(p.interface) \
|
||||
or re.fullmatch(r'/dev/cu\.usbmodem([A-F0-9]*)1', p.device) \
|
||||
or p.location[-1] == '0' and os.name == 'nt':
|
||||
gdb_ports.append(p)
|
||||
else:
|
||||
uart_ports.append(p)
|
||||
return gdb_ports, uart_ports
|
||||
|
||||
|
||||
# search device with specific serial number <snr> in list <l>
|
||||
def search_serial(snr, l):
|
||||
for p in l:
|
||||
if snr in p.serial_number:
|
||||
return p.device
|
||||
|
||||
|
||||
# parse GDB output for targets
|
||||
def detect_targets(gdbmi, res):
|
||||
targets = []
|
||||
while True:
|
||||
for msg in res:
|
||||
if msg['type'] == 'target':
|
||||
m = re.fullmatch(pattern=r"\s*(\d)+\s*(.*)\\n", string=msg['payload'])
|
||||
if m:
|
||||
targets.append(m.group(2))
|
||||
elif msg['type'] == 'result':
|
||||
assert msg['message'] == 'done', str(msg)
|
||||
return targets
|
||||
|
||||
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
|
||||
|
||||
|
||||
def gdb_write_and_wait_for_result(gdbmi, cmd, description, expected_result='done'):
|
||||
res = gdbmi.write(cmd, timeout_sec=TIMEOUT)
|
||||
while True:
|
||||
for msg in res:
|
||||
if msg['type'] == 'result':
|
||||
if msg['message'] == expected_result:
|
||||
print(description, "successful.")
|
||||
return True
|
||||
else:
|
||||
print(description, "failed.", file=sys.stderr)
|
||||
return False
|
||||
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
|
||||
|
||||
|
||||
def parse_download_msg(msg):
|
||||
m = re.fullmatch(
|
||||
pattern=r"\+download,"
|
||||
r"\{(?:section=\"(.*?)\")?,?(?:section-sent=\"(.*?)\")?,?"
|
||||
r"(?:section-size=\"(.*?)\")?,?(?:total-sent=\"(.*?)\")?,?"
|
||||
r"(?:total-size=\"(.*?)\")?,?\}",
|
||||
string=msg['payload'])
|
||||
if m:
|
||||
section_name = m.group(1)
|
||||
section_sent = int(m.group(2)) if m.group(2) else None
|
||||
section_size = int(m.group(3)) if m.group(3) else None
|
||||
total_sent = int(m.group(4)) if m.group(4) else None
|
||||
total_size = int(m.group(5)) if m.group(5) else None
|
||||
return section_name, section_sent, section_size, total_sent, total_size
|
||||
|
||||
|
||||
def download_to_flash(gdbmi):
|
||||
res = gdbmi.write('-target-download', timeout_sec=TIMEOUT)
|
||||
first = True # whether this is the first status message
|
||||
current_sec = None # name of current section
|
||||
pbar = ProgressBar()
|
||||
while True:
|
||||
for msg in res:
|
||||
if msg['type'] == 'result':
|
||||
assert msg['message'] == 'done', "download failed: %s" % str(msg)
|
||||
if pbar.start_time:
|
||||
pbar.finish()
|
||||
print("downloading finished")
|
||||
return
|
||||
elif msg['type'] == 'output':
|
||||
section_name, section_sent, section_size, total_sent, total_size = parse_download_msg(msg)
|
||||
if section_name:
|
||||
if first:
|
||||
first = False
|
||||
print("downloading... total size: %s"
|
||||
% humanize.naturalsize(total_size, gnu=True))
|
||||
if section_name != current_sec:
|
||||
if pbar.start_time:
|
||||
pbar.finish()
|
||||
current_sec = section_name
|
||||
print("downloading section [%s] (%s)" % (
|
||||
section_name, humanize.naturalsize(section_size, gnu=True)))
|
||||
pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=section_size).start()
|
||||
if section_sent:
|
||||
pbar.update(section_sent)
|
||||
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
|
||||
|
||||
|
||||
def check_flash(gdbmi):
|
||||
res = gdbmi.write('compare-sections', timeout_sec=TIMEOUT)
|
||||
while True:
|
||||
for msg in res:
|
||||
if msg['type'] == 'result':
|
||||
assert msg['message'] == 'done', "checking failed: %s" % str(msg)
|
||||
print("checking successful")
|
||||
return
|
||||
elif msg['type'] == 'console':
|
||||
assert 'matched' in msg['payload'] and 'MIS-MATCHED' not in msg['payload'], \
|
||||
"checking failed: %s" % str(msg)
|
||||
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
|
||||
|
||||
|
||||
def choose_bmp_port(gdb_ports):
|
||||
print("found following Black Magic GDB servers:")
|
||||
for i, s in enumerate(gdb_ports):
|
||||
print("\t[%s]" % s.device, end=' ')
|
||||
if len(s.serial_number) > 1:
|
||||
print("Serial:", s.serial_number, end=' ')
|
||||
if i == 0:
|
||||
print("<- default", end=' ')
|
||||
print('')
|
||||
port = gdb_ports[0].device
|
||||
if args.port:
|
||||
port = args.port
|
||||
elif args.serial:
|
||||
port = search_serial(args.serial, gdb_ports)
|
||||
assert port, "no BMP with this serial found"
|
||||
print('connecting to [%s]...' % port)
|
||||
return port
|
||||
|
||||
|
||||
# terminal mode, opens TTY program
|
||||
def term_mode(uart_ports):
|
||||
port = uart_ports[0].device
|
||||
if args.port:
|
||||
port = args.port
|
||||
elif args.serial:
|
||||
port = search_serial(args.serial, uart_ports)
|
||||
assert port, "no BMP with this serial found"
|
||||
os.system(args.term_cmd % port)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# debug mode, opens GDB shell with options
|
||||
def debug_mode(port):
|
||||
gdb_args = ['-ex \'target extended-remote %s\'' % port]
|
||||
if args.tpwr:
|
||||
gdb_args.append('-ex \'monitor tpwr enable\'')
|
||||
if args.connect_srst:
|
||||
gdb_args.append('-ex \'monitor connect_srst enable\'')
|
||||
if args.jtag:
|
||||
gdb_args.append('-ex \'monitor jtag_scan\'')
|
||||
else:
|
||||
gdb_args.append('-ex \'monitor swdp_scan\'')
|
||||
gdb_args.append('-ex \'attach %s\'' % args.attach)
|
||||
os.system(" ".join(['\"' + args.gdb_path + '\"'] + gdb_args + [args.file]))
|
||||
|
||||
|
||||
def connect_to_target(port):
|
||||
# open GDB in machine interface mode
|
||||
gdbmi = GdbController(gdb_path=args.gdb_path, gdb_args=["--nx", "--quiet", "--interpreter=mi2", args.file])
|
||||
assert gdb_write_and_wait_for_result(gdbmi, '-target-select extended-remote %s' % port, 'connecting',
|
||||
expected_result='connected')
|
||||
# set options
|
||||
if args.connect_srst:
|
||||
gdbmi.write('monitor connect_srst enable', timeout_sec=TIMEOUT)
|
||||
if args.tpwr:
|
||||
gdbmi.write('monitor tpwr enable', timeout_sec=TIMEOUT)
|
||||
# scan for targets
|
||||
if not args.jtag:
|
||||
print("scanning using SWD...")
|
||||
res = gdbmi.write('monitor swdp_scan', timeout_sec=TIMEOUT)
|
||||
else:
|
||||
print("scanning using JTAG...")
|
||||
res = gdbmi.write('monitor jtag_scan', timeout_sec=TIMEOUT)
|
||||
targets = detect_targets(gdbmi, res)
|
||||
assert len(targets) > 0, "no targets found"
|
||||
print("found following targets:")
|
||||
for t in targets:
|
||||
print("\t%s" % t)
|
||||
print("")
|
||||
return gdbmi
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
assert not (args.swd and args.jtag), "you may only choose one protocol"
|
||||
assert not (args.serial and args.port), "you may only specify the probe by port or by serial"
|
||||
g, u = detect_probes()
|
||||
assert len(g) > 0, "no Black Magic Probes found 😔"
|
||||
|
||||
if args.action == 'term':
|
||||
term_mode(u)
|
||||
else:
|
||||
port = choose_bmp_port(g)
|
||||
|
||||
args.file = args.file if args.file else ''
|
||||
args.gdb_path = find_suitable_gdb(args.gdb_path)
|
||||
|
||||
if args.action == 'debug':
|
||||
debug_mode(port)
|
||||
sys.exit(0)
|
||||
|
||||
gdbmi = connect_to_target(port)
|
||||
|
||||
if args.action == 'list':
|
||||
sys.exit(0)
|
||||
|
||||
assert gdb_write_and_wait_for_result(gdbmi, '-target-attach %s' % args.attach, 'attaching to target')
|
||||
|
||||
# reset mode: reset device using reset pin
|
||||
if args.action == 'reset':
|
||||
assert gdb_write_and_wait_for_result(gdbmi, 'monitor hard_srst', 'resetting target')
|
||||
sys.exit(0)
|
||||
# erase mode
|
||||
elif args.action == 'erase':
|
||||
print('erasing...')
|
||||
assert gdb_write_and_wait_for_result(gdbmi, '-target-flash-erase', 'erasing target')
|
||||
sys.exit(0)
|
||||
# flashloader mode: flash, check and restart
|
||||
elif args.action == 'flash':
|
||||
download_to_flash(gdbmi)
|
||||
check_flash(gdbmi)
|
||||
|
||||
# kill and reset
|
||||
assert gdb_write_and_wait_for_result(gdbmi, 'kill', 'killing')
|
||||
4
dist/tools/bmp/requirements.txt
vendored
Normal file
4
dist/tools/bmp/requirements.txt
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
humanize
|
||||
pygdbmi
|
||||
pyserial
|
||||
progressbar
|
||||
2
makefiles/tools/bmp-serial.inc.mk
Normal file
2
makefiles/tools/bmp-serial.inc.mk
Normal file
@ -0,0 +1,2 @@
|
||||
TERMPROG ?= $(RIOTTOOLS)/bmp/bmp.py
|
||||
TERMFLAGS ?= $(BMP_OPTIONS) term
|
||||
9
makefiles/tools/bmp.inc.mk
Normal file
9
makefiles/tools/bmp.inc.mk
Normal file
@ -0,0 +1,9 @@
|
||||
FLASHER ?= $(RIOTTOOLS)/bmp/bmp.py
|
||||
DEBUGGER = $(RIOTTOOLS)/bmp/bmp.py
|
||||
RESET ?= $(RIOTTOOLS)/bmp/bmp.py
|
||||
|
||||
FLASHFILE ?= $(ELFFILE)
|
||||
|
||||
FFLAGS ?= $(BMP_OPTIONS) flash $(FLASHFILE)
|
||||
DEBUGGER_FLAGS ?= $(BMP_OPTIONS) debug $(ELFFILE)
|
||||
RESET_FLAGS ?= $(BMP_OPTIONS) reset
|
||||
Loading…
x
Reference in New Issue
Block a user