diff --git a/README.md b/README.md index 5193b547..fe172a90 100755 --- a/README.md +++ b/README.md @@ -514,6 +514,33 @@ $ 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. + +There are various options to `mbed sterm`: +* `--port ` to specify system COM port to connect to. +* `--baudrate ` to select the communication baudrate, where the default value is 9600. +* `--echo ` 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: +- Quit: `CTRL+C` or `CTRL+J` +- Reset: `CTRL+B` or `CTRL+R` +- Echo toggle: `CTRL+E` +- Terminal information: `TAB` or `CTRL+I` +- Help: `CTRL+H` +- Menu: `CTRL+T` +- Change baud rate: `CTRL+T+B` + +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: + +``` +$ mbed compile -t GCC_ARM -m K64F -f --sterm +``` + ## Testing Use the `mbed test` command to compile and run tests. diff --git a/mbed/mbed.py b/mbed/mbed.py index 9c882cb0..07aebdd9 100644 --- a/mbed/mbed.py +++ b/mbed/mbed.py @@ -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', @@ -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=['-s', '--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 @@ -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) @@ -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 @@ -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 @@ -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 diff --git a/mbed/mbed_terminal.py b/mbed/mbed_terminal.py new file mode 100644 index 00000000..0c5d8e1a --- /dev/null +++ b/mbed/mbed_terminal.py @@ -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 = bool(echo) + + 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