Skip to content

Commit 66f734d

Browse files
authored
Merge pull request #664 from screamerbg/feature_term
New feature: Serial Terminal (revised)
2 parents 0ca65a8 + dbb455f commit 66f734d

File tree

3 files changed

+258
-14
lines changed

3 files changed

+258
-14
lines changed

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,32 @@ $ mbed export -i uvision -m K64F
519519

520520
Mbed CLI creates a `.uvprojx` file in the projectfiles/uvision folder. You can open the project file with uVision.
521521

522+
### Serial terminal
523+
524+
You can open a serial terminal to the COM port of a connected Mbed target (usually board) using the `mbed sterm` command. If no COM port is specified, Mbed CLI will attempt to detect the connected Mbed targets and their COM ports.
525+
526+
There are various options to `mbed sterm`:
527+
* `--port <COM port>` to specify system COM port to connect to.
528+
* `--baudrate <numeric>` to select the communication baudrate, where the default value is 9600.
529+
* `--echo <on|off>` to switch local echo (default is `on`).
530+
* `--reset` to reset the connected target by sending Break before opening the serial terminal.
531+
532+
You can also set default port, baudrate and echo mode using the `TERM_PORT`, `TERM_BAUDRATE` and `TERM_ECHO` Mbed CLI configuration options.
533+
534+
The following shortcuts are available within the serial terminal:
535+
- Ctrl+b - Send Break (reset target)
536+
- Ctrl+c - Exit terminal
537+
- Ctrl+e - Toggle local echo
538+
- Ctrl+h - Help
539+
- Ctrl+t - Menu escape key
540+
- _More shortcuts can be viewed within the serial terminal's help menu (Ctrl+h)._
541+
542+
You can also add the `--sterm` option to `mbed compile -f` to compile a new program, flash the program/firmware image to the connected target and then open the serial terminal to it's COM port:
543+
544+
```
545+
$ mbed compile -t GCC_ARM -m K64F -f --sterm
546+
```
547+
522548
## Testing
523549

524550
Use the `mbed test` command to compile and run tests.

mbed/mbed.py

+77-14
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,30 @@ def formaturl(url, format="default"):
17661766
url = 'https://%s%s%s/%s' % (m.group(2) if (m.group(2) and (m.group(5) or m.group(3) != 'git')) else '', m.group(6), m.group(7) or '', m.group(8))
17671767
return url
17681768

1769+
# Wrapper for the MbedTermnal functionality
1770+
def mbed_sterm(port, baudrate=9600, echo=True, reset=False, sterm=False):
1771+
try:
1772+
from mbed_terminal import MbedTerminal
1773+
except (IOError, ImportError, OSError):
1774+
error("The serial terminal functionality requires that the 'mbed-terminal' python module is installed.\nYou can install mbed-terminal by running 'pip install mbed-terminal'.", 1)
1775+
1776+
result = False
1777+
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)
1778+
if mbed_serial.serial:
1779+
if reset:
1780+
mbed_serial.reset()
1781+
1782+
if sterm:
1783+
# Some boards will reset the COM port after SendBreak, e.g. STLink based
1784+
if not mbed_serial.serial.is_open:
1785+
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)
1786+
1787+
try:
1788+
result = mbed_serial.terminal()
1789+
except:
1790+
pass
1791+
return result
1792+
17691793

17701794
# Subparser handling
17711795
parser = argparse.ArgumentParser(prog='mbed',
@@ -2402,12 +2426,13 @@ def status_(ignore=False):
24022426
dict(name='--build', help='Build directory. Default: build/'),
24032427
dict(name=['-c', '--clean'], action='store_true', help='Clean the build directory before compiling'),
24042428
dict(name=['-f', '--flash'], action='store_true', help='Flash the built firmware onto a connected target.'),
2429+
dict(name=['--sterm'], action='store_true', help='Open serial terminal after compiling. Can be chained with --flash'),
24052430
dict(name=['-N', '--artifact-name'], help='Name of the built program or library'),
24062431
dict(name=['-S', '--supported'], dest='supported', const=True, choices=["matrix", "toolchains", "targets"], nargs="?", help='Shows supported matrix of targets and toolchains'),
24072432
dict(name='--app-config', dest="app_config", help="Path of an app configuration file (Default is to look for 'mbed_app.json')"),
24082433
help='Compile code using the mbed build tools',
24092434
description="Compile this program using the mbed build tools.")
2410-
def compile_(toolchain=None, target=None, profile=False, compile_library=False, compile_config=False, config_prefix=None, source=False, build=False, clean=False, flash=False, artifact_name=None, supported=False, app_config=None):
2435+
def compile_(toolchain=None, target=None, profile=False, compile_library=False, compile_config=False, config_prefix=None, source=False, build=False, clean=False, flash=False, sterm=False, artifact_name=None, supported=False, app_config=None):
24112436
# Gather remaining arguments
24122437
args = remainder
24132438
# Find the root of the program
@@ -2487,23 +2512,24 @@ def compile_(toolchain=None, target=None, profile=False, compile_library=False,
24872512
+ args,
24882513
env=env)
24892514

2515+
if flash or sterm:
2516+
detected = program.detect_target()
2517+
try:
2518+
from mbed_host_tests.host_tests_toolbox import flash_dev
2519+
except (IOError, ImportError, OSError):
2520+
error("The '-f/--flash' option requires that the 'mbed-greentea' python module is installed.\nYou can install mbed-greentea by running 'pip install mbed-greentea'.", 1)
2521+
24902522
if flash:
24912523
fw_name = artifact_name if artifact_name else program.name
24922524
fw_fbase = os.path.join(build_path, fw_name)
24932525
fw_file = fw_fbase + ('.hex' if os.path.exists(fw_fbase+'.hex') else '.bin')
24942526
if not os.path.exists(fw_file):
24952527
error("Build program file (firmware) not found \"%s\"" % fw_file, 1)
2496-
detected = program.detect_target()
2497-
2498-
try:
2499-
from mbed_host_tests.host_tests_toolbox import flash_dev, reset_dev
2500-
except (IOError, ImportError, OSError):
2501-
error("The '-f/--flash' option requires that the 'mbed-greentea' python module is installed.\nYou can install mbed-ls by running 'pip install mbed-greentea'.", 1)
2502-
25032528
if not flash_dev(detected['msd'], fw_file, program_cycle_s=2):
25042529
error("Unable to flash the target board connected to your system.", 1)
25052530

2506-
if not reset_dev(detected['port']):
2531+
if flash or sterm:
2532+
if not mbed_sterm(detected['port'], reset=flash, sterm=sterm):
25072533
error("Unable to reset the target board connected to your system.\nThis might be caused by an old interface firmware.\nPlease check the board page for new firmware.", 1)
25082534

25092535
program.set_defaults(target=target, toolchain=tchain)
@@ -2667,12 +2693,12 @@ def export(ide=None, target=None, source=False, clean=False, supported=False, ap
26672693
program.set_defaults(target=target)
26682694

26692695

2670-
# Test command
2696+
# Detect command
26712697
@subcommand('detect',
26722698
hidden_aliases=['det'],
2673-
help='Detect connected mbed targets/boards\n\n',
2699+
help='Detect connected Mbed targets/boards\n\n',
26742700
description=(
2675-
"Detects mbed targets/boards connected to this system and shows supported\n"
2701+
"Detect Mbed targets/boards connected to this system and show supported\n"
26762702
"toolchain matrix."))
26772703
def detect():
26782704
# Gather remaining arguments
@@ -2697,7 +2723,7 @@ def detect():
26972723
if very_verbose:
26982724
error(str(e))
26992725
else:
2700-
warning("The mbed tools were not found in \"%s\". \nLimited information will be shown about connected mbed targets/boards" % program.path)
2726+
warning("The mbed-os tools were not found in \"%s\". \nLimited information will be shown about connected targets/boards" % program.path)
27012727
targets = program.get_detected_targets()
27022728
if targets:
27032729
unknown_found = False
@@ -2710,7 +2736,44 @@ def detect():
27102736

27112737
if unknown_found:
27122738
warning("If you're developing a new target, you can mock the device to continue your development. "
2713-
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")
2739+
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")
2740+
2741+
2742+
# Serial terminal command
2743+
@subcommand('sterm',
2744+
dict(name=['-p', '--port'], help='Communication port. Default: auto-detect'),
2745+
dict(name=['-b', '--baudrate'], help='Communication baudrate. Default: 9600'),
2746+
dict(name=['-e', '--echo'], help='Switch local echo on/off. Default: on'),
2747+
dict(name=['-r', '--reset'], action='store_true', help='Reset the targets (via SendBreak) before opening terminal.'),
2748+
hidden_aliases=['term'],
2749+
help='Open serial terminal to connected target.\n\n',
2750+
description=(
2751+
"Open serial terminal to connected target (usually board), or connect to a user-specified COM port\n"))
2752+
def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True):
2753+
# Gather remaining arguments
2754+
args = remainder
2755+
# Find the root of the program
2756+
program = Program(getcwd(), False)
2757+
2758+
port = port or program.get_cfg('TERM_PORT', None)
2759+
baudrate = baudrate or program.get_cfg('TERM_BAUDRATE', 9600)
2760+
echo = echo or program.get_cfg('TERM_ECHO', 'on')
2761+
2762+
if port:
2763+
action("Opening serial terminal to the specified COM port \"%s\"" % port)
2764+
mbed_sterm(port, baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)
2765+
else:
2766+
action("Detecting connected targets/boards to your system...")
2767+
targets = program.get_detected_targets()
2768+
if not targets:
2769+
error("Couldn't detect connected targets/boards to your system.\nYou can manually specify COM port via the '--port' option.", 1)
2770+
2771+
for target in targets:
2772+
if target['name'] is None:
2773+
action("Opening serial terminal to unknown target at \"%s\"" % target['serial'])
2774+
else:
2775+
action("Opening serial terminal to \"%s\"" % target['name'])
2776+
mbed_sterm(target['serial'], baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)
27142777

27152778

27162779
# Generic config command

mbed/mbed_terminal.py

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
2+
#!/usr/bin/env python2
3+
4+
# Copyright (c) 2016 ARM Limited, All Rights Reserved
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
10+
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
11+
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
15+
# either express or implied.
16+
17+
18+
# pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-lines, line-too-long,
19+
# pylint: disable=too-many-nested-blocks, too-many-public-methods, too-many-instance-attributes, too-many-statements
20+
# pylint: disable=invalid-name, missing-docstring, bad-continuation
21+
22+
23+
# Global class used for global config
24+
class MbedTerminal(object):
25+
serial = None # Serial() object
26+
port = None
27+
baudrate = None
28+
echo = None
29+
30+
def __init__(self, port, baudrate=9600, echo=True, timeout=10):
31+
self.port = port
32+
self.baudrate = int(baudrate)
33+
self.timeout = int(timeout)
34+
self.echo = False if str(echo).lower() == 'off' else True
35+
36+
try:
37+
from serial import Serial, SerialException
38+
except (IOError, ImportError, OSError):
39+
return False
40+
41+
try:
42+
self.serial = Serial(self.port, baudrate=self.baudrate, timeout=self.timeout)
43+
self.serial.flush()
44+
self.serial.reset_input_buffer()
45+
except Exception as e:
46+
print 'error'
47+
self.serial = None
48+
return False
49+
50+
def terminal(self, print_header=True):
51+
try:
52+
import serial.tools.miniterm as miniterm
53+
except (IOError, ImportError, OSError):
54+
return False
55+
56+
term = miniterm.Miniterm(self.serial, echo=self.echo)
57+
term.exit_character = '\x03'
58+
term.menu_character = '\x14'
59+
term.set_rx_encoding('UTF-8')
60+
term.set_tx_encoding('UTF-8')
61+
62+
def console_print(text):
63+
term.console.write('--- %s ---\n' % text)
64+
65+
def get_print_help():
66+
return """
67+
--- Mbed Serial Terminal (0.3a)
68+
--- Based on miniterm from pySerial
69+
---
70+
--- Ctrl+b Send Break (reset target)
71+
--- Ctrl+c Exit terminal
72+
--- Ctrl+e Toggle local echo
73+
--- Ctrl+h Help
74+
--- Ctrl+t Menu escape key, followed by:
75+
--- p Change COM port
76+
--- b Change baudrate
77+
--- Tab Show detailed terminal info
78+
--- Ctrl+a Change encoding (default UTF-8)
79+
--- Ctrl+f Edit filters
80+
--- Ctrl+l Toggle EOL
81+
--- Ctrl+r Toggle RTS
82+
--- Ctrl+d Toggle DTR
83+
--- Ctrl+c Send control character to remote
84+
--- Ctrl+t Send control character to remote
85+
"""
86+
87+
def print_help():
88+
term.console.write(get_print_help())
89+
90+
91+
def input_handler():
92+
menu_active = False
93+
while term.alive:
94+
try:
95+
c = term.console.getkey()
96+
except KeyboardInterrupt:
97+
c = '\x03'
98+
if not term.alive:
99+
break
100+
if menu_active and c in ['p', 'b', '\t', '\x01', '\x03', '\x04', '\x05', '\x06', '\x0c', '\x14']:
101+
term.handle_menu_key(c)
102+
menu_active = False
103+
elif c == term.menu_character:
104+
console_print('[MENU]')
105+
menu_active = True # next char will be for menu
106+
elif c == '\x02': # ctrl+b sendbreak
107+
console_print('[RESET]')
108+
self.reset()
109+
elif c == '\x03': # ctrl+c
110+
console_print('[QUIT]')
111+
term.stop()
112+
term.alive = False
113+
break
114+
elif c == '\x05': # ctrl+e
115+
console_print('[ECHO %s]' % ('OFF' if term.echo else 'ON'))
116+
term.echo = not term.echo
117+
elif c == '\x08': # ctrl+h
118+
print_help()
119+
# elif c == '\t': # tab/ctrl+i
120+
# term.dump_port_settings()
121+
else:
122+
text = c
123+
for transformation in term.tx_transformations:
124+
text = transformation.tx(text)
125+
term.serial.write(term.tx_encoder.encode(text))
126+
if term.echo:
127+
echo_text = c
128+
for transformation in term.tx_transformations:
129+
echo_text = transformation.echo(echo_text)
130+
term.console.write(echo_text)
131+
term.writer = input_handler
132+
133+
if print_header:
134+
console_print("Terminal on {p.name} - {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}".format(p=term.serial))
135+
136+
term.start()
137+
138+
try:
139+
term.join(True)
140+
except KeyboardInterrupt:
141+
pass
142+
term.join()
143+
term.close()
144+
145+
return True
146+
147+
def reset(self):
148+
try:
149+
self.serial.sendBreak()
150+
except:
151+
try:
152+
self.serial.setBreak(False) # For Linux the following setBreak() is needed to release the reset signal on the target mcu.
153+
except:
154+
return False
155+
return True

0 commit comments

Comments
 (0)