Skip to content

New feature: Serial Terminal (revised) #664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 16, 2018
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,32 @@ $ mbed export -i uvision -m K64F

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

### Serial terminal

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.
Copy link
Contributor

@yennster yennster May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mbed CLI will attempt to detect the connected Mbed targets and their COM ports.

Does this mean you can use the mbed sterm command with multiple Mbed boards plugged into the computer at the same time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It would detect all and iterate through them (one by one, not simultaneously).


There are various options to `mbed sterm`:
* `--port <COM port>` to specify system COM port to connect to.
* `--baudrate <numeric>` to select the communication baudrate, where the default value is 9600.
* `--echo <on|off>` to switch local echo (default is `on`).
* `--reset` to reset the connected target by sending Break before opening the serial terminal.

You can also set default port, baudrate and echo mode using the `TERM_PORT`, `TERM_BAUDRATE` and `TERM_ECHO` Mbed CLI configuration options.

The following shortcuts are available within the serial terminal:
- Ctrl+b - Send Break (reset target)
- Ctrl+c - Exit terminal
- Ctrl+e - Toggle local echo
- Ctrl+h - Help
- Ctrl+t - Menu escape key
- _More shortcuts can be viewed within the serial terminal's help menu (Ctrl+h)._

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:

```
$ mbed compile -t GCC_ARM -m K64F -f --sterm
```

## Testing

Use the `mbed test` command to compile and run tests.
Expand Down
91 changes: 77 additions & 14 deletions mbed/mbed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1751,6 +1751,30 @@ def formaturl(url, format="default"):
url = 'https://%s/%s' % (m.group(2), m.group(3))
return url

# Wrapper for the MbedTermnal functionality
def mbed_sterm(port, baudrate=9600, echo=True, reset=False, sterm=False):
try:
from mbed_terminal import MbedTerminal
except (IOError, ImportError, OSError):
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)

result = False
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)
if mbed_serial.serial:
if reset:
mbed_serial.reset()

if sterm:
# Some boards will reset the COM port after SendBreak, e.g. STLink based
if not mbed_serial.serial.is_open:
mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo)

try:
result = mbed_serial.terminal()
except:
pass
return result


# Subparser handling
parser = argparse.ArgumentParser(prog='mbed',
Expand Down Expand Up @@ -2366,12 +2390,13 @@ def status_(ignore=False):
dict(name='--build', help='Build directory. Default: build/'),
dict(name=['-c', '--clean'], action='store_true', help='Clean the build directory before compiling'),
dict(name=['-f', '--flash'], action='store_true', help='Flash the built firmware onto a connected target.'),
dict(name=['--sterm'], action='store_true', help='Open serial terminal after compiling. Can be chained with --flash'),
dict(name=['-N', '--artifact-name'], help='Name of the built program or library'),
dict(name=['-S', '--supported'], dest='supported', const=True, choices=["matrix", "toolchains", "targets"], nargs="?", help='Shows supported matrix of targets and toolchains'),
dict(name='--app-config', dest="app_config", help="Path of an app configuration file (Default is to look for 'mbed_app.json')"),
help='Compile code using the mbed build tools',
description="Compile this program using the mbed build tools.")
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):
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):
# Gather remaining arguments
args = remainder
# Find the root of the program
Expand Down Expand Up @@ -2451,23 +2476,24 @@ def compile_(toolchain=None, target=None, profile=False, compile_library=False,
+ args,
env=env)

if flash or sterm:
detected = program.detect_target()
try:
from mbed_host_tests.host_tests_toolbox import flash_dev
except (IOError, ImportError, OSError):
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)

if flash:
fw_name = artifact_name if artifact_name else program.name
fw_fbase = os.path.join(build_path, fw_name)
fw_file = fw_fbase + ('.hex' if os.path.exists(fw_fbase+'.hex') else '.bin')
if not os.path.exists(fw_file):
error("Build program file (firmware) not found \"%s\"" % fw_file, 1)
detected = program.detect_target()

try:
from mbed_host_tests.host_tests_toolbox import flash_dev, reset_dev
except (IOError, ImportError, OSError):
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)

if not flash_dev(detected['msd'], fw_file, program_cycle_s=2):
error("Unable to flash the target board connected to your system.", 1)

if not reset_dev(detected['port']):
if flash or sterm:
if not mbed_sterm(detected['port'], reset=flash, sterm=sterm):
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)

program.set_defaults(target=target, toolchain=tchain)
Expand Down Expand Up @@ -2631,12 +2657,12 @@ def export(ide=None, target=None, source=False, clean=False, supported=False, ap
program.set_defaults(target=target)


# Test command
# Detect command
@subcommand('detect',
hidden_aliases=['det'],
help='Detect connected mbed targets/boards\n\n',
help='Detect connected Mbed targets/boards\n\n',
description=(
"Detects mbed targets/boards connected to this system and shows supported\n"
"Detect Mbed targets/boards connected to this system and show supported\n"
"toolchain matrix."))
def detect():
# Gather remaining arguments
Expand All @@ -2661,7 +2687,7 @@ def detect():
if very_verbose:
error(str(e))
else:
warning("The mbed tools were not found in \"%s\". \nLimited information will be shown about connected mbed targets/boards" % program.path)
warning("The mbed-os tools were not found in \"%s\". \nLimited information will be shown about connected targets/boards" % program.path)
targets = program.get_detected_targets()
if targets:
unknown_found = False
Expand All @@ -2674,7 +2700,44 @@ def detect():

if unknown_found:
warning("If you're developing a new target, you can mock the device to continue your development. "
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")
"Use 'mbedls --mock ID:NAME' to do so (see 'mbedls --help' for more information)")


# Serial terminal command
@subcommand('sterm',
dict(name=['-p', '--port'], help='Communication port. Default: auto-detect'),
dict(name=['-b', '--baudrate'], help='Communication baudrate. Default: 9600'),
dict(name=['-e', '--echo'], help='Switch local echo on/off. Default: on'),
dict(name=['-r', '--reset'], action='store_true', help='Reset the targets (via SendBreak) before opening terminal.'),
hidden_aliases=['term'],
help='Open serial terminal to connected target.\n\n',
description=(
"Open serial terminal to connected target (usually board), or connect to a user-specified COM port\n"))
def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True):
# Gather remaining arguments
args = remainder
# Find the root of the program
program = Program(getcwd(), False)

port = port or program.get_cfg('TERM_PORT', None)
baudrate = baudrate or program.get_cfg('TERM_BAUDRATE', 9600)
echo = echo or program.get_cfg('TERM_ECHO', 'on')

if port:
action("Opening serial terminal to the specified COM port \"%s\"" % port)
mbed_sterm(port, baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)
else:
action("Detecting connected targets/boards to your system...")
targets = program.get_detected_targets()
if not targets:
error("Couldn't detect connected targets/boards to your system.\nYou can manually specify COM port via the '--port' option.", 1)

for target in targets:
if target['name'] is None:
action("Opening serial terminal to unknown target at \"%s\"" % target['serial'])
else:
action("Opening serial terminal to \"%s\"" % target['name'])
mbed_sterm(target['serial'], baudrate=baudrate, echo=echo, reset=reset, sterm=sterm)


# Generic config command
Expand Down
155 changes: 155 additions & 0 deletions mbed/mbed_terminal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@

#!/usr/bin/env python2

# Copyright (c) 2016 ARM Limited, All Rights Reserved
# SPDX-License-Identifier: Apache-2.0

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied.


# pylint: disable=too-many-arguments, too-many-locals, too-many-branches, too-many-lines, line-too-long,
# pylint: disable=too-many-nested-blocks, too-many-public-methods, too-many-instance-attributes, too-many-statements
# pylint: disable=invalid-name, missing-docstring, bad-continuation


# Global class used for global config
class MbedTerminal(object):
serial = None # Serial() object
port = None
baudrate = None
echo = None

def __init__(self, port, baudrate=9600, echo=True, timeout=10):
self.port = port
self.baudrate = int(baudrate)
self.timeout = int(timeout)
self.echo = False if str(echo).lower() == 'off' else True

try:
from serial import Serial, SerialException
except (IOError, ImportError, OSError):
return False

try:
self.serial = Serial(self.port, baudrate=self.baudrate, timeout=self.timeout)
self.serial.flush()
self.serial.reset_input_buffer()
except Exception as e:
print 'error'
self.serial = None
return False

def terminal(self, print_header=True):
try:
import serial.tools.miniterm as miniterm
except (IOError, ImportError, OSError):
return False

term = miniterm.Miniterm(self.serial, echo=self.echo)
term.exit_character = '\x03'
term.menu_character = '\x14'
term.set_rx_encoding('UTF-8')
term.set_tx_encoding('UTF-8')

def console_print(text):
term.console.write('--- %s ---\n' % text)

def get_print_help():
return """
--- Mbed Serial Terminal (0.3a)
--- Based on miniterm from pySerial
---
--- Ctrl+b Send Break (reset target)
--- Ctrl+c Exit terminal
--- Ctrl+e Toggle local echo
--- Ctrl+h Help
--- Ctrl+t Menu escape key, followed by:
--- p Change COM port
--- b Change baudrate
--- Tab Show detailed terminal info
--- Ctrl+a Change encoding (default UTF-8)
--- Ctrl+f Edit filters
--- Ctrl+l Toggle EOL
--- Ctrl+r Toggle RTS
--- Ctrl+d Toggle DTR
--- Ctrl+c Send control character to remote
--- Ctrl+t Send control character to remote
"""

def print_help():
term.console.write(get_print_help())


def input_handler():
menu_active = False
while term.alive:
try:
c = term.console.getkey()
except KeyboardInterrupt:
c = '\x03'
if not term.alive:
break
if menu_active and c in ['p', 'b', '\t', '\x01', '\x03', '\x04', '\x05', '\x06', '\x0c', '\x14']:
term.handle_menu_key(c)
menu_active = False
elif c == term.menu_character:
console_print('[MENU]')
menu_active = True # next char will be for menu
elif c == '\x02': # ctrl+b sendbreak
console_print('[RESET]')
self.reset()
elif c == '\x03': # ctrl+c
console_print('[QUIT]')
term.stop()
term.alive = False
break
elif c == '\x05': # ctrl+e
console_print('[ECHO %s]' % ('OFF' if term.echo else 'ON'))
term.echo = not term.echo
elif c == '\x08': # ctrl+h
print_help()
# elif c == '\t': # tab/ctrl+i
# term.dump_port_settings()
else:
text = c
for transformation in term.tx_transformations:
text = transformation.tx(text)
term.serial.write(term.tx_encoder.encode(text))
if term.echo:
echo_text = c
for transformation in term.tx_transformations:
echo_text = transformation.echo(echo_text)
term.console.write(echo_text)
term.writer = input_handler

if print_header:
console_print("Terminal on {p.name} - {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}".format(p=term.serial))

term.start()

try:
term.join(True)
except KeyboardInterrupt:
pass
term.join()
term.close()

return True

def reset(self):
try:
self.serial.sendBreak()
except:
try:
self.serial.setBreak(False) # For Linux the following setBreak() is needed to release the reset signal on the target mcu.
except:
return False
return True