diff --git a/TESTS/host_tests/serial_comms.py b/TESTS/host_tests/serial_comms.py new file mode 100644 index 0000000000..f51ac7d60f --- /dev/null +++ b/TESTS/host_tests/serial_comms.py @@ -0,0 +1,38 @@ +""" +Copyright (c) 2019 Arm Limited and affiliates. + +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. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from mbed_host_tests import BaseHostTest + + +MSG_KEY_ECHO_MESSAGE = "echo_message" + + +class SerialComms(BaseHostTest): + """Host side test that handles messages sent using serial classes.""" + + def __init__(self): + """Initialize an object.""" + super(SerialComms, self).__init__() + + def setup(self): + """Register call backs to handle message from the target.""" + self.register_callback(MSG_KEY_ECHO_MESSAGE, self.cb_echo_message) + + def cb_echo_message(self, key, value, timestamp): + """Send back the key and value received.""" + self.send_kv(key, value) diff --git a/TESTS/mbed_drivers/unbuffered_serial/main.cpp b/TESTS/mbed_drivers/unbuffered_serial/main.cpp new file mode 100644 index 0000000000..5002d827bb --- /dev/null +++ b/TESTS/mbed_drivers/unbuffered_serial/main.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2019 Arm Limited and affiliates. + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#if !DEVICE_SERIAL +#error [NOT_SUPPORTED] serial communication not supported for this target +#else + +#include "mbed.h" +#include "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" + + +using namespace utest::v1; + + +/** + * Macros for setting console flow control. + */ +#define CONSOLE_FLOWCONTROL_RTS 1 +#define CONSOLE_FLOWCONTROL_CTS 2 +#define CONSOLE_FLOWCONTROL_RTSCTS 3 +#define mbed_console_concat_(x) CONSOLE_FLOWCONTROL_##x +#define mbed_console_concat(x) mbed_console_concat_(x) +#define CONSOLE_FLOWCONTROL mbed_console_concat(MBED_CONF_TARGET_CONSOLE_UART_FLOW_CONTROL) + + +#define MSG_KEY_ECHO_MESSAGE "echo_message" +#define MSG_VALUE_HELLO_WORLD "Hello, world!" + +#define EXPECTED_ECHOED_STRING "{{" MSG_KEY_ECHO_MESSAGE ";" MSG_VALUE_HELLO_WORLD "}}" +// The target is expected to transmit Greentea messages with \n (or \r\n) or they are not detected by the host +#define STRING_TO_SEND EXPECTED_ECHOED_STRING "\n" + + +static UnbufferedSerial unbuffered_serial_obj( + USBTX, USBRX, MBED_CONF_PLATFORM_STDIO_BAUD_RATE +); + +static ssize_t unbuffered_serial_read(void *buffer, ssize_t length) +{ + if (length == 0) { + return 0; + } + + // Ignore the `\n` character previously sent to the host in the previous + // key-value pair that may not have been removed from the FIFO. + unsigned char *buf = static_cast(buffer); + unbuffered_serial_obj.read(buf, 1); + ssize_t i = (buf[0] == '{') ? 1 : 0; + + // Get the message sent by the host + for (; i < length; i++) { + TEST_ASSERT_EQUAL_UINT(1, unbuffered_serial_obj.read(buf + i, 1)); + } + + return length; +} + + +// Test that data sent using an UnbufferedSerial object is correctly sent. +// The test case sends a Greentea key-value pair message from the target to the +// host using an UnbufferedSerial object and expects the message +// to be echoed back by the host. The host response is received via the Greentea +// framework usual route using greentea_parse_kv(). Success is determined upon +// reception of the echoed message which indicates that the message was received +// by the host as it was sent by the target. +static void test_serial_write() +{ + char tx_msg[] = STRING_TO_SEND; + + TEST_ASSERT_EQUAL_UINT( + strlen(tx_msg) + 1, + unbuffered_serial_obj.write(tx_msg, strlen(tx_msg) + 1) + ); + + char rx_key[30] = {0}; + char rx_value[30] = {0}; + greentea_parse_kv(rx_key, rx_value, sizeof(rx_key), sizeof(rx_value)); + + TEST_ASSERT_EQUAL_STRING(MSG_KEY_ECHO_MESSAGE, rx_key); + TEST_ASSERT_EQUAL_STRING(MSG_VALUE_HELLO_WORLD, rx_value); +} + + +// Test that data received using an UnbufferedSerial object is correctly received. +// The test case sends a Greentea key-value pair message from the target to the +// host via the Greentea framework usual route using greentea_send_kv(). +// It expects the message to be echoed back to the target. An UnbufferedSerial +// object is used to handle the received message. Succes is determined upon +// reception of a key-value pair matching the key-value pair sent by the target. +static void test_serial_read() +{ + greentea_send_kv(MSG_KEY_ECHO_MESSAGE, MSG_VALUE_HELLO_WORLD); + + char rx_msg[sizeof(EXPECTED_ECHOED_STRING)] = {0}; + // Exclude the null terminator which is not read + ssize_t expected_rx_msg_length = sizeof(EXPECTED_ECHOED_STRING) - 1; + + unbuffered_serial_read(rx_msg, expected_rx_msg_length); + + TEST_ASSERT_EQUAL_STRING(EXPECTED_ECHOED_STRING, rx_msg); +} + + +utest::v1::status_t greentea_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(12, "serial_comms"); + + return greentea_test_setup_handler(number_of_cases); +} + + +utest::v1::status_t greentea_failure_handler( + const Case *const source, const failure_t reason +) +{ + greentea_case_failure_abort_handler(source, reason); + return STATUS_CONTINUE; +} + + +Case cases[] = { + Case( + "Bytes are correctly sent", + test_serial_write, greentea_failure_handler + ), + Case( + "Bytes are correctly received", + test_serial_read, greentea_failure_handler + ), +}; + + +Specification specification( + greentea_setup, cases, greentea_test_teardown_handler +); + + +int main() +{ +#if CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTS + unbuffered_serial_obj.set_flow_control( + SerialBase::RTS, STDIO_UART_RTS, NC + ); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_CTS + unbuffered_serial_obj.set_flow_control( + SerialBase::CTS, NC, STDIO_UART_CTS + ); +#elif CONSOLE_FLOWCONTROL == CONSOLE_FLOWCONTROL_RTSCTS + unbuffered_serial_obj.set_flow_control( + SerialBase::RTSCTS, STDIO_UART_RTS, STDIO_UART_CTS + ); +#endif + return !Harness::run(specification); +} + +#endif // !DEVICE_SERIAL diff --git a/drivers/RawSerial.h b/drivers/RawSerial.h index 41b0522b27..5fb9ce123d 100644 --- a/drivers/RawSerial.h +++ b/drivers/RawSerial.h @@ -37,7 +37,8 @@ namespace mbed { * @{ */ -/** A serial port (UART) for communication with other serial devices +/** @deprecated + * A serial port (UART) for communication with other serial devices * This is a variation of the Serial class that doesn't use streams, * thus making it safe to use in interrupt handlers with the RTOS. * @@ -59,10 +60,15 @@ namespace mbed { * } * @endcode */ -class RawSerial: public SerialBase, private NonCopyable { +class + MBED_DEPRECATED_SINCE( + "mbed-os-6.0.0", + "Use UnbufferedSerial instead." + ) RawSerial: public SerialBase, private NonCopyable { public: - /** Create a RawSerial port, connected to the specified transmit and receive pins, with the specified baud. + /** @deprecated + * Create a RawSerial port, connected to the specified transmit and receive pins, with the specified baud. * * @param tx Transmit pin * @param rx Receive pin @@ -71,31 +77,41 @@ public: * @note * Either tx or rx may be specified as NC if unused */ + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") RawSerial(PinName tx, PinName rx, int baud = MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE); - /** Write a char to the serial port + /** @deprecated + * Write a char to the serial port * * @param c The char to write * * @returns The written char or -1 if an error occurred */ + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") int putc(int c); - /** Read a char from the serial port + /** @deprecated + * Read a char from the serial port * * @returns The char read from the serial port */ + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") int getc(); - /** Write a string to the serial port + /** @deprecated + * Write a string to the serial port * * @param str The string to write * * @returns 0 if the write succeeds, EOF for error */ + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") int puts(const char *str); + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") int printf(const char *format, ...) MBED_PRINTF_METHOD(1, 2); + + MBED_DEPRECATED("The class has been deprecated and will be removed in the future.") int vprintf(const char *format, std::va_list arg); #if !(DOXYGEN_ONLY) diff --git a/drivers/SerialBase.h b/drivers/SerialBase.h index 3f02c4ec82..e57483ed93 100644 --- a/drivers/SerialBase.h +++ b/drivers/SerialBase.h @@ -39,7 +39,7 @@ namespace mbed { */ /** A base class for serial port implementations - * Can't be instantiated directly (use Serial or RawSerial) + * Can't be instantiated directly (use UnbufferedSerial or UARTSerial) * * @note Synchronization level: Set by subclass */ diff --git a/drivers/UnbufferedSerial.h b/drivers/UnbufferedSerial.h new file mode 100644 index 0000000000..3909644dc5 --- /dev/null +++ b/drivers/UnbufferedSerial.h @@ -0,0 +1,179 @@ +/* mbed Microcontroller Library + * Copyright (c) 2019 ARM Limited + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_UNBUFFERED_SERIAL_H +#define MBED_UNBUFFERED_SERIAL_H + +#include "platform/platform.h" + +#if DEVICE_SERIAL || defined(DOXYGEN_ONLY) + +#include + +#include "drivers/SerialBase.h" +#include "platform/FileHandle.h" +#include "platform/mbed_toolchain.h" +#include "platform/NonCopyable.h" + + +namespace mbed { + +/** + * \defgroup drivers_UnbufferedSerial UnbufferedSerial class + * \ingroup drivers-public-api-uart + * @{ + */ + +/** + * Class implementation for unbuffered I/O for an interrupt driven application + * or one that needs to have more control. + */ +class UnbufferedSerial: + private SerialBase, + public FileHandle, + private NonCopyable { +public: + /** + * Create a serial port instance connected to the specified transmit and + * receive pins, with the specified baud rate. + * + * @param tx Transmit pin + * @param rx Receive pin + * @param baud The baud rate of the serial port (optional, defaults to MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE) + * + * @note + * Either tx or rx may be specified as NC if unused + */ + UnbufferedSerial( + PinName tx, + PinName rx, + int baud = MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE + ); + + /** Create a UnbufferedSerial port, connected to the specified transmit and + * receive pins, with a particular baud rate. + * @param static_pinmap reference to structure which holds static pinmap + * @param baud The baud rate of the serial port (optional, defaults to + * MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE) + */ + UnbufferedSerial( + const serial_pinmap_t &static_pinmap, + int baud = MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE + ); + + /** Write the contents of a buffer to a file + * + * Blocks until all data is written + * + * @param buffer The buffer to write from + * @param size The number of bytes to write + * @return The number of bytes written + */ + virtual ssize_t write(const void *buffer, size_t size); + + /** Read the contents of a file into a buffer + * + * Blocks and reads exactly one character + * + * @param buffer The buffer to read in to + * @param size The number of bytes to read + * @return The number of bytes read + */ + virtual ssize_t read(void *buffer, size_t size); + + /** Move the file position to a given offset from from a given location + * + * Not valid for a device type FileHandle like UnbufferedSerial. + * In case of UnbufferedSerial, returns ESPIPE + * + * @param offset The offset from whence to move to + * @param whence The start of where to seek + * SEEK_SET to start from beginning of file, + * SEEK_CUR to start from current position in file, + * SEEK_END to start from end of file + * @return The new offset of the file, negative error code on failure + */ + virtual off_t seek(off_t offset, int whence = SEEK_SET) + { + return -ESPIPE; + } + + /** Get the size of the file + * + * @return Size of the file in bytes + */ + virtual off_t size() + { + return -EINVAL; + } + + /** Check if the file in an interactive terminal device + * + * @return True if the file is a terminal + * @return False if the file is not a terminal + * @return Negative error code on failure + */ + virtual int isatty() + { + return true; + } + + /** Close a file + * + * @return 0 on success, negative error code on failure + */ + virtual int close() + { + return 0; + } + + + /** Check for poll event flags + * Check the events listed in events to see if data can be read or written + * without blocking. + * Call is nonblocking - returns state of events. + * + * @param events bitmask of poll events we're interested in - POLLIN/POLLOUT etc. + * + * @returns bitmask of poll events that have occurred. + */ + virtual short poll(short events) const; + +#if DEVICE_SERIAL_FC + // For now use the base enum - but in future we may have extra options + // such as XON/XOFF or manual GPIO RTSCTS. + using SerialBase::Flow; + // In C++11, we wouldn't need to also have using directives for each value + using SerialBase::Disabled; + using SerialBase::RTS; + using SerialBase::CTS; + using SerialBase::RTSCTS; + + /** Set the flow control type on the serial port + * + * @param type the flow control type (Disabled, RTS, CTS, RTSCTS) + * @param flow1 the first flow control pin (RTS for RTS or RTSCTS, CTS for CTS) + * @param flow2 the second flow control pin (CTS for RTSCTS) + */ + void set_flow_control(Flow type, PinName flow1 = NC, PinName flow2 = NC); +#endif // DEVICE_SERIAL_FC +}; + +} // namespace mbed + +#endif // DEVICE_SERIAL || defined(DOXYGEN_ONLY) + +#endif // MBED_UNBUFFERED_SERIAL_H diff --git a/drivers/source/UnbufferedSerial.cpp b/drivers/source/UnbufferedSerial.cpp new file mode 100644 index 0000000000..56b19f8327 --- /dev/null +++ b/drivers/source/UnbufferedSerial.cpp @@ -0,0 +1,113 @@ +/* mbed Microcontroller Library + * Copyright (c) 2006-2019 ARM Limited + * 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. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drivers/UnbufferedSerial.h" + +#if DEVICE_SERIAL + +#include "platform/mbed_critical.h" + +namespace mbed { + +UnbufferedSerial::UnbufferedSerial( + PinName tx, + PinName rx, + int baud +) : SerialBase(tx, rx, baud) +{ + // No lock needed in the constructor +} + +UnbufferedSerial::UnbufferedSerial( + const serial_pinmap_t &static_pinmap, int baud +) : SerialBase(static_pinmap, baud) +{ + // No lock needed in the constructor +} + +ssize_t UnbufferedSerial::write(const void *buffer, size_t size) +{ + const unsigned char *buf = static_cast(buffer); + + if (size == 0) { + return 0; + } + + bool lock_api = !core_util_in_critical_section(); + + if (lock_api) { + lock(); + } + + for (size_t i = 0; i < size; i++) { + _base_putc(buf[i]); + } + + if (lock_api) { + unlock(); + } + + return size; +} + +ssize_t UnbufferedSerial::read(void *buffer, size_t size) +{ + unsigned char *buf = static_cast(buffer); + + if (size == 0) { + return 0; + } + + lock(); + + buf[0] = _base_getc(); + + unlock(); + + return 1; +} + +short UnbufferedSerial::poll(short events) const +{ + short revents = 0; + if ( + (events & POLLIN) + && (const_cast (this))->SerialBase::readable() + ) { + revents |= POLLIN; + } + if ( + (events & POLLOUT) + && (const_cast (this))->SerialBase::writeable() + ) { + revents |= POLLOUT; + } + return revents; +} + +#if DEVICE_SERIAL_FC +void UnbufferedSerial::set_flow_control(Flow type, PinName flow1, PinName flow2) +{ + lock(); + SerialBase::set_flow_control(type, flow1, flow2); + unlock(); +} +#endif // DEVICE_SERIAL_FC + +} // namespace mbed + +#endif // #if DEVICE_SERIAL diff --git a/mbed.h b/mbed.h index dc61b2ca8c..cc43626340 100644 --- a/mbed.h +++ b/mbed.h @@ -70,6 +70,7 @@ #include "drivers/Ethernet.h" #include "drivers/CAN.h" #include "drivers/RawSerial.h" +#include "drivers/UnbufferedSerial.h" #include "drivers/UARTSerial.h" #include "drivers/FlashIAP.h" #include "drivers/MbedCRC.h" diff --git a/platform/mbed_lib.json b/platform/mbed_lib.json index 8d02199e8d..14604557e7 100644 --- a/platform/mbed_lib.json +++ b/platform/mbed_lib.json @@ -32,7 +32,7 @@ }, "default-serial-baud-rate": { - "help": "Default baud rate for a Serial or RawSerial instance (if not specified in the constructor)", + "help": "Default baud rate for a serial object (if not specified in the constructor)", "value": 9600 },