Skip to content

Commit dd6cb5d

Browse files
committed
Reimplement Serial Terminal feature and documentation based on PR feedback
1 parent c4c4c01 commit dd6cb5d

File tree

3 files changed

+168
-65
lines changed

3 files changed

+168
-65
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,33 @@ $ mbed export -i uvision -m K64F
514514

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

517+
### Serial terminal
518+
519+
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.
520+
521+
There are various options to `mbed sterm`:
522+
* `--port <COM port>` to specify system COM port to connect to.
523+
* `--baudrate <numeric>` to select the communication baudrate, where the default value is 9600.
524+
* `--echo <on|off>` to switch local echo (default is `on`).
525+
* `--reset` to reset the connected target by sending Break before opening the serial terminal.
526+
527+
You can also set default port, baudrate and echo mode using the `TERM_PORT`, `TERM_BAUDRATE` and `TERM_ECHO` Mbed CLI configuration options.
528+
529+
The following shortcuts are available within the serial terminal:
530+
- Quit: `CTRL+C` or `CTRL+J`
531+
- Reset: `CTRL+B` or `CTRL+R`
532+
- Echo toggle: `CTRL+E`
533+
- Terminal information: `TAB` or `CTRL+I`
534+
- Help: `CTRL+H`
535+
- Menu: `CTRL+T`
536+
- Change baud rate: `CTRL+T+B`
537+
538+
To automate things, 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 serial terminal to it's COM port:
539+
540+
```
541+
$ mbed compile -t GCC_ARM -m K64F -f --sterm
542+
```
543+
517544
## Testing
518545

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

mbed/mbed.py

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import zipfile
3535
import argparse
3636
import tempfile
37-
from mbed_cdc import mbed_cdc
3837

3938

4039
# Application version
@@ -1752,6 +1751,30 @@ def formaturl(url, format="default"):
17521751
url = 'https://%s/%s' % (m.group(2), m.group(3))
17531752
return url
17541753

1754+
# Wrapper for the MbedTermnal functionality
1755+
def mbed_sterm(port, baudrate=9600, echo=True, reset=False, sterm=False):
1756+
try:
1757+
from mbed_terminal import MbedTerminal
1758+
except (IOError, ImportError, OSError):
1759+
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)
1760+
1761+
result = False
1762+
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)
1763+
if mbed_serial.serial:
1764+
if reset:
1765+
mbed_serial.reset()
1766+
1767+
if sterm:
1768+
# Some boards will reset the COM port after SendBreak, e.g. STLink based
1769+
if not mbed_serial.serial.is_open:
1770+
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)
1771+
1772+
try:
1773+
result = mbed_serial.terminal()
1774+
except:
1775+
pass
1776+
return result
1777+
17551778

17561779
# Subparser handling
17571780
parser = argparse.ArgumentParser(prog='mbed',
@@ -2470,7 +2493,7 @@ def compile_(toolchain=None, target=None, profile=False, compile_library=False,
24702493
error("Unable to flash the target board connected to your system.", 1)
24712494

24722495
if flash or sterm:
2473-
if not mbed_cdc(detected['port'], reset=flash, sterm=sterm):
2496+
if not mbed_sterm(detected['port'], reset=flash, sterm=sterm):
24742497
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)
24752498

24762499
program.set_defaults(target=target, toolchain=tchain)
@@ -2634,16 +2657,14 @@ def export(ide=None, target=None, source=False, clean=False, supported=False, ap
26342657
program.set_defaults(target=target)
26352658

26362659

2637-
# Test command
2660+
# Detect command
26382661
@subcommand('detect',
2639-
dict(name=['-r', '--reset'], dest='reset', action='store_true', help='Reset detected targets (via SendBreak)'),
2640-
dict(name=['-s', '--sterm'], dest='sterm', action='store_true', help='Open serial terminal for detected targets'),
26412662
hidden_aliases=['det'],
2642-
help='Detect connected mbed targets/boards\n\n',
2663+
help='Detect connected Mbed targets/boards\n\n',
26432664
description=(
2644-
"Detects mbed targets/boards connected to this system and shows supported\n"
2665+
"Detect Mbed targets/boards connected to this system and show supported\n"
26452666
"toolchain matrix."))
2646-
def detect(reset=False, sterm=False):
2667+
def detect():
26472668
# Gather remaining arguments
26482669
args = remainder
26492670
# Find the root of the program
@@ -2653,7 +2674,7 @@ def detect(reset=False, sterm=False):
26532674
with cd(program.path):
26542675
tools_dir = program.get_tools_dir()
26552676

2656-
if tools_dir and not (reset or sterm):
2677+
if tools_dir:
26572678
# Prepare environment variables
26582679
env = program.get_env()
26592680

@@ -2666,9 +2687,7 @@ def detect(reset=False, sterm=False):
26662687
if very_verbose:
26672688
error(str(e))
26682689
else:
2669-
if not tools_dir:
2670-
warning("The mbed OS tools were not found in \"%s\". \nLimited information will be shown about connected mbed targets/boards" % program.path)
2671-
2690+
warning("The mbed-os tools were not found in \"%s\". \nLimited information will be shown about connected targets/boards" % program.path)
26722691
targets = program.get_detected_targets()
26732692
if targets:
26742693
unknown_found = False
@@ -2678,13 +2697,47 @@ def detect(reset=False, sterm=False):
26782697
action("Detected unknown target connected to \"%s\" and using com port \"%s\"" % (target['mount'], target['serial']))
26792698
else:
26802699
action("Detected \"%s\" connected to \"%s\" and using com port \"%s\"" % (target['name'], target['mount'], target['serial']))
2681-
mbed_cdc(target['serial'], reset=reset, sterm=sterm)
26822700

26832701
if unknown_found:
26842702
warning("If you're developing a new target, you can mock the device to continue your development. "
2685-
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")
2686-
else:
2687-
error("This command requires that the 'mbed-greentea' python module is installed.\nYou can install mbed-greentea by running 'pip install mbed-greentea'.", 1)
2703+
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")
2704+
2705+
2706+
# Serial terminal command
2707+
@subcommand('sterm',
2708+
dict(name=['-p', '--port'], help='Communication port. Default: auto-detect'),
2709+
dict(name=['-b', '--baudrate'], help='Communication baudrate. Default: 9600'),
2710+
dict(name=['-e', '--echo'], help='Switch local echo on/off. Default: on'),
2711+
dict(name=['-r', '--reset'], action='store_true', help='Reset the targets (via SendBreak) before opening terminal.'),
2712+
hidden_aliases=['term'],
2713+
help='Open serial terminal to connected target.\n\n',
2714+
description=(
2715+
"Open serial terminal to connected target (usually board), or connect to a user-specified COM port\n"))
2716+
def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True):
2717+
# Gather remaining arguments
2718+
args = remainder
2719+
# Find the root of the program
2720+
program = Program(getcwd(), False)
2721+
2722+
port = port or program.get_cfg('TERM_PORT', None)
2723+
baudrate = baudrate or program.get_cfg('TERM_BAUDRATE', 9600)
2724+
echo = echo or program.get_cfg('TERM_ECHO', 'on')
2725+
2726+
if port:
2727+
action("Opening serial terminal to the specified COM port \"%s\"" % port)
2728+
mbed_sterm(port, baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)
2729+
else:
2730+
action("Detecting connected targets/boards to your system...")
2731+
targets = program.get_detected_targets()
2732+
if not targets:
2733+
error("Couldn't detect connected targets/boards to your system.\nYou can manually specify COM port via the '--port' option.", 1)
2734+
2735+
for target in targets:
2736+
if target['name'] is None:
2737+
action("Opening serial terminal to unknown target at \"%s\"" % target['serial'])
2738+
else:
2739+
action("Opening serial terminal to \"%s\"" % target['name'])
2740+
mbed_sterm(target['serial'], baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)
26882741

26892742

26902743
# Generic config command

mbed/mbed_cdc.py renamed to mbed/mbed_terminal.py

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,41 @@
1919
# pylint: disable=too-many-nested-blocks, too-many-public-methods, too-many-instance-attributes, too-many-statements
2020
# pylint: disable=invalid-name, missing-docstring, bad-continuation
2121

22-
def mbed_cdc(port, reset=False, sterm=False, baudrate=9600, timeout= 10, print_term_header=True):
23-
try:
24-
from serial import Serial, SerialException
25-
import serial.tools.miniterm as miniterm
26-
except (IOError, ImportError, OSError):
27-
return False
28-
29-
def get_instance(*args, **kwargs):
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 = bool(echo)
35+
36+
try:
37+
from serial import Serial, SerialException
38+
except (IOError, ImportError, OSError):
39+
return False
40+
3041
try:
31-
serial_port = Serial(*args, **kwargs)
32-
serial_port.flush()
42+
self.serial = Serial(self.port, baudrate=self.baudrate, timeout=self.timeout)
43+
self.serial.flush()
44+
self.serial.reset_input_buffer()
3345
except Exception as e:
34-
error("Unable to open serial port connection to \"%s\"" % port)
46+
print 'error'
47+
self.serial = None
3548
return False
36-
return serial_port
3749

38-
def cdc_reset(serial_instance):
50+
def terminal(self, print_header=True):
3951
try:
40-
serial_instance.sendBreak()
41-
except:
42-
try:
43-
serial_instance.setBreak(False) # For Linux the following setBreak() is needed to release the reset signal on the target mcu.
44-
except:
45-
return False
46-
return True
52+
import serial.tools.miniterm as miniterm
53+
except (IOError, ImportError, OSError):
54+
return False
4755

48-
def cdc_term(serial_instance):
49-
term = miniterm.Miniterm(serial_instance, echo=True)
56+
term = miniterm.Miniterm(self.serial, echo=self.echo)
5057
term.exit_character = '\x03'
5158
term.menu_character = '\x14'
5259
term.set_rx_encoding('UTF-8')
@@ -55,6 +62,32 @@ def cdc_term(serial_instance):
5562
def console_print(text):
5663
term.console.write('--- %s ---\n' % text)
5764

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+
5891
def input_handler():
5992
menu_active = False
6093
while term.alive:
@@ -64,27 +97,27 @@ def input_handler():
6497
c = '\x03'
6598
if not term.alive:
6699
break
67-
if menu_active:
100+
if menu_active and c in ['p', 'b', '\t', '\x01', '\x03', '\x04', '\x05', '\x06', '\x0c', '\x14']:
68101
term.handle_menu_key(c)
69102
menu_active = False
70103
elif c == term.menu_character:
71104
console_print('[MENU]')
72105
menu_active = True # next char will be for menu
73-
elif c == '\x02' or c == '\x12': # ctrl+b/ctrl+r sendbreak
106+
elif c == '\x02': # ctrl+b sendbreak
74107
console_print('[RESET]')
75-
cdc_reset(term.serial)
76-
elif c == '\x03' or c == '\x1d': # ctrl+c/ctrl+]
108+
self.reset()
109+
elif c == '\x03': # ctrl+c
77110
console_print('[QUIT]')
78111
term.stop()
79112
term.alive = False
80113
break
81114
elif c == '\x05': # ctrl+e
82115
console_print('[ECHO %s]' % ('OFF' if term.echo else 'ON'))
83116
term.echo = not term.echo
84-
elif c == '\x08': # ctrl+e
85-
print term.get_help_text()
86-
elif c == '\t': # tab/ctrl+i
87-
term.dump_port_settings()
117+
elif c == '\x08': # ctrl+h
118+
print_help()
119+
# elif c == '\t': # tab/ctrl+i
120+
# term.dump_port_settings()
88121
else:
89122
text = c
90123
for transformation in term.tx_transformations:
@@ -97,10 +130,8 @@ def input_handler():
97130
term.console.write(echo_text)
98131
term.writer = input_handler
99132

100-
if print_term_header:
101-
console_print('Terminal on {p.name} - {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}'.format(p=term.serial))
102-
console_print('Quit: CTRL+C | Reset: CTRL+B | Echo: CTRL+E')
103-
console_print('Info: TAB | Help: Ctrl+H | Menu: Ctrl+T')
133+
if print_header:
134+
console_print("Terminal on {p.name} - {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}".format(p=term.serial))
104135

105136
term.start()
106137

@@ -113,20 +144,12 @@ def input_handler():
113144

114145
return True
115146

116-
117-
result = False
118-
serial_port = get_instance(port, baudrate=baudrate, timeout=timeout)
119-
if serial_port:
120-
serial_port.reset_input_buffer()
121-
if reset:
122-
result = cdc_reset(serial_port)
123-
124-
if sterm:
125-
if not serial_port.is_open:
126-
serial_port = get_instance(port, baudrate=baudrate, timeout=timeout)
147+
def reset(self):
148+
try:
149+
self.serial.sendBreak()
150+
except:
127151
try:
128-
result = cdc_term(serial_port)
152+
self.serial.setBreak(False) # For Linux the following setBreak() is needed to release the reset signal on the target mcu.
129153
except:
130-
pass
131-
132-
return result
154+
return False
155+
return True

0 commit comments

Comments
 (0)