1
0
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:
benpicco 2019-10-15 13:39:08 +02:00 committed by GitHub
commit 10824e1cd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 433 additions and 0 deletions

131
dist/tools/bmp/README.md vendored Normal file
View 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
View 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
View File

@ -0,0 +1,4 @@
humanize
pygdbmi
pyserial
progressbar

View File

@ -0,0 +1,2 @@
TERMPROG ?= $(RIOTTOOLS)/bmp/bmp.py
TERMFLAGS ?= $(BMP_OPTIONS) term

View 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