From 22cbcd55f0446e4fc75d54a136e8d3a791d2b722 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Fri, 25 Mar 2016 10:20:12 +0000 Subject: [PATCH] stmhal: Properly handle RTS/CTS flow control for buf/unbuf transfers. Fixes issues #1912 and #1913. UART documentation is also updated. --- docs/library/pyb.UART.rst | 93 ++++++++++++++++++++++++++++++++++----- stmhal/uart.c | 52 +++++++++++++++++----- 2 files changed, 122 insertions(+), 23 deletions(-) diff --git a/docs/library/pyb.UART.rst b/docs/library/pyb.UART.rst index 5fc5b22a5a..536b6f467d 100644 --- a/docs/library/pyb.UART.rst +++ b/docs/library/pyb.UART.rst @@ -40,7 +40,8 @@ using the standard stream methods:: To check if there is anything to be read, use:: - uart.any() # returns True if any characters waiting + uart.any() # returns the number of characters waiting + *Note:* The stream functions ``read``, ``write``, etc. are new in MicroPython v1.3.4. Earlier versions use ``uart.send`` and ``uart.recv``. @@ -57,7 +58,7 @@ Constructors initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. - + The physical pins of the UART busses are: - ``UART(4)`` is on ``XA``: ``(TX, RX) = (X1, X2) = (PA0, PA1)`` @@ -66,12 +67,16 @@ Constructors - ``UART(3)`` is on ``YB``: ``(TX, RX) = (Y9, Y10) = (PB10, PB11)`` - ``UART(2)`` is on: ``(TX, RX) = (X3, X4) = (PA2, PA3)`` + The Pyboard Lite supports UART(1), UART(2) and UART(6) only. Pins are as above except: + + - ``UART(2)`` is on: ``(TX, RX) = (X1, X2) = (PA2, PA3)`` + Methods ------- .. only:: port_pyboard - .. method:: uart.init(baudrate, bits=8, parity=None, stop=1, \*, timeout=1000, flow=None, timeout_char=0, read_buf_len=64) + .. method:: uart.init(baudrate, bits=8, parity=None, stop=1, \*, timeout=1000, flow=0, timeout_char=0, read_buf_len=64) Initialise the UART bus with the given parameters: @@ -79,7 +84,7 @@ Methods - ``bits`` is the number of bits per character, 7, 8 or 9. - ``parity`` is the parity, ``None``, 0 (even) or 1 (odd). - ``stop`` is the number of stop bits, 1 or 2. - - ``flow`` sets the flow control type. Can be None, ``UART.RTS``, ``UART.CTS`` + - ``flow`` sets the flow control type. Can be 0, ``UART.RTS``, ``UART.CTS`` or ``UART.RTS | UART.CTS``. - ``timeout`` is the timeout in milliseconds to wait for the first character. - ``timeout_char`` is the timeout in milliseconds to wait between characters. @@ -103,16 +108,18 @@ Methods .. method:: uart.any() - Returns the number of characters waiting (may be 0). + Returns the number of bytes waiting (may be 0). .. method:: uart.writechar(char) Write a single character on the bus. ``char`` is an integer to write. - Return value: ``None``. + Return value: ``None``. See note below if CTS flow control is used. .. method:: uart.read([nbytes]) Read characters. If ``nbytes`` is specified then read at most that many bytes. + If ``nbytes`` are available in the buffer, returns immediately, otherwise returns + when sufficient characters arrive or the timeout elapses. .. only:: port_pyboard @@ -124,9 +131,9 @@ Methods .. method:: uart.readall() - Read as much data as possible. + Read as much data as possible. Returns after the timeout has elapsed. - Return value: a bytes object or ``None`` on timeout. + Return value: a bytes object or ``None`` if timeout prevents any data being read. .. method:: uart.readchar() @@ -144,9 +151,11 @@ Methods .. method:: uart.readline() - Read a line, ending in a newline character. + Read a line, ending in a newline character. If such a line exists, return is + immediate. If the timeout elapses, all available data is returned regardless + of whether a newline exists. - Return value: the line read or ``None`` on timeout. + Return value: the line read or ``None`` on timeout if no data is available. .. method:: uart.write(buf) @@ -157,7 +166,8 @@ Methods bytes are used for each character (little endian), and ``buf`` must contain an even number of bytes. - Return value: number of bytes written or ``None`` on timeout. + Return value: number of bytes written. If a timeout occurs and no bytes + were written returns ``None``. .. method:: uart.sendbreak() @@ -173,4 +183,63 @@ Constants .. data:: UART.RTS .. data:: UART.CTS - to select the flow control type + to select the flow control type. + +Flow Control +------------ + +.. only:: port_pyboard + + On Pyboards V1 and V1.1 ``UART(2)`` and ``UART(3)`` support RTS/CTS hardware flow control + using the following pins: + + - ``UART(2)`` is on: ``(TX, RX, nRTS, nCTS) = (X3, X4, X2, X1) = (PA2, PA3, PA1, PA0)`` + - ``UART(3)`` is on :``(TX, RX, nRTS, nCTS) = (Y9, Y10, Y7, Y6) = (PB10, PB11, PB14, PB13)`` + + On the Pyboard Lite only ``UART(2)`` supports flow control on these pins: + + ``(TX, RX, nRTS, nCTS) = (X1, X2, X4, X3) = (PA2, PA3, PA1, PA0)`` + + In the following paragraphs the term "target" refers to the device connected to + the UART. + + When the UART's ``init()`` method is called with ``flow`` set to one or both of + ``UART.RTS`` and ``UART.CTS`` the relevant flow control pins are configured. + ``nRTS`` is an active low output, ``nCTS`` is an active low input with pullup + enabled. To achieve flow control the Pyboard's ``nCTS`` signal should be connected + to the target's ``nRTS`` and the Pyboard's ``nRTS`` to the target's ``nCTS``. + + CTS: target controls Pyboard transmitter + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + If CTS flow control is enabled the write behaviour is as follows: + + If the Pyboard's ``uart.write(buf)`` method is called, transmission will stall for + any periods when ``nCTS`` is ``False``. This will result in a timeout if the entire + buffer was not transmitted in the timeout period. The method returns the number of + bytes written, enabling the user to write the remainder of the data if required. In + the event of a timeout, a character will remain in the UART pending ``nCTS``. The + number of bytes composing this character will be included in the return value. + + If ``uart.writechar()`` is called when ``nCTS`` is ``False`` the method will time + out unless the target asserts ``nCTS`` in time. If it times out ``OSError 116`` + will be raised. The character will be transmitted as soon as the target asserts ``nCTS``. + + RTS: Pyboard controls target's transmitter + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + If RTS flow control is enabled, behaviour is as follows: + + If buffered input is used (``read_buf_len`` > 0), incoming characters are buffered. + If the buffer becomes full, the next character to arrive will cause ``nRTS`` to go + ``False``: the target should cease transmission. ``nRTS`` will go ``True`` when + characters are read from the buffer. + + Note that the ``any()`` method returns the number of bytes in the buffer. Assume a + buffer length of ``N`` bytes. If the buffer becomes full, and another character arrives, + ``nRTS`` will be set False, and ``any()`` will return the count ``N``. When + characters are read the additional character will be placed in the buffer and will + be included in the result of a subsequent ``any()`` call. + + If buffered input is not used (``read_buf_len`` == 0) the arrival of a character will + cause ``nRTS`` to go ``False`` until the character is read. diff --git a/stmhal/uart.c b/stmhal/uart.c index 272b207eb7..caa4d56007 100644 --- a/stmhal/uart.c +++ b/stmhal/uart.c @@ -320,6 +320,10 @@ int uart_rx_char(pyb_uart_obj_t *self) { data = self->read_buf[self->read_buf_tail]; } self->read_buf_tail = (self->read_buf_tail + 1) % self->read_buf_len; + if (__HAL_UART_GET_FLAG(&self->uart, UART_FLAG_RXNE) != RESET) { + // UART was stalled by flow ctrl: re-enable IRQ now we have room in buffer + __HAL_UART_ENABLE_IT(&self->uart, UART_IT_RXNE); + } return data; } else { // no buffering @@ -347,6 +351,11 @@ STATIC bool uart_tx_wait(pyb_uart_obj_t *self, uint32_t timeout) { } STATIC HAL_StatusTypeDef uart_tx_data(pyb_uart_obj_t *self, uint8_t *data, uint16_t len) { + if (self->uart.Init.HwFlowCtl & UART_HWCONTROL_CTS) { + // CTS can hold off transmission for an arbitrarily long time. Apply + // the overall timeout rather than the character timeout. + return HAL_UART_Transmit(&self->uart, data, len, self->timeout); + } // The timeout specified here is for waiting for the TX data register to // become empty (ie between chars), as well as for the final char to be // completely transferred. The default value for timeout_char is long @@ -385,25 +394,25 @@ void uart_irq_handler(mp_uint_t uart_id) { } if (__HAL_UART_GET_FLAG(&self->uart, UART_FLAG_RXNE) != RESET) { - #if defined(MCU_SERIES_F7) - int data = self->uart.Instance->RDR; // clears UART_FLAG_RXNE - #else - int data = self->uart.Instance->DR; // clears UART_FLAG_RXNE - #endif - data &= self->char_mask; if (self->read_buf_len != 0) { uint16_t next_head = (self->read_buf_head + 1) % self->read_buf_len; if (next_head != self->read_buf_tail) { - // only store data if room in buf + // only read data if room in buf + #if defined(MCU_SERIES_F7) + int data = self->uart.Instance->RDR; // clears UART_FLAG_RXNE + #else + int data = self->uart.Instance->DR; // clears UART_FLAG_RXNE + #endif + data &= self->char_mask; if (self->char_width == CHAR_WIDTH_9BIT) { ((uint16_t*)self->read_buf)[self->read_buf_head] = data; } else { self->read_buf[self->read_buf_head] = data; } self->read_buf_head = next_head; + } else { // No room: leave char in buf, disable interrupt + __HAL_UART_DISABLE_IT(&self->uart, UART_IT_RXNE); } - } else { - // TODO set flag for buffer overflow } } } @@ -427,6 +436,15 @@ STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k } else { mp_printf(print, "%u", self->uart.Init.Parity == UART_PARITY_EVEN ? 0 : 1); } + if (self->uart.Init.HwFlowCtl) { + mp_printf(print, ", flow="); + if (self->uart.Init.HwFlowCtl & UART_HWCONTROL_RTS) { + mp_printf(print, "RTS%s", self->uart.Init.HwFlowCtl & UART_HWCONTROL_CTS ? "|" : ""); + } + if (self->uart.Init.HwFlowCtl & UART_HWCONTROL_CTS) { + mp_printf(print, "CTS"); + } + } mp_printf(print, ", stop=%u, timeout=%u, timeout_char=%u, read_buf_len=%u)", self->uart.Init.StopBits == UART_STOPBITS_1 ? 1 : 2, self->timeout, self->timeout_char, @@ -434,7 +452,7 @@ STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k } } -/// \method init(baudrate, bits=8, parity=None, stop=1, *, timeout=1000, timeout_char=0, read_buf_len=64) +/// \method init(baudrate, bits=8, parity=None, stop=1, *, timeout=1000, timeout_char=0, flow=0, read_buf_len=64) /// /// Initialise the UART bus with the given parameters: /// @@ -444,6 +462,7 @@ STATIC void pyb_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k /// - `stop` is the number of stop bits, 1 or 2. /// - `timeout` is the timeout in milliseconds to wait for the first character. /// - `timeout_char` is the timeout in milliseconds to wait between characters. +/// - `flow` is RTS | CTS where RTS == 256, CTS == 512 /// - `read_buf_len` is the character length of the read buffer (0 to disable). STATIC mp_obj_t pyb_uart_init_helper(pyb_uart_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { static const mp_arg_t allowed_args[] = { @@ -847,7 +866,7 @@ STATIC mp_uint_t pyb_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t return MP_STREAM_ERROR; } - // wait to be able to write the first character + // wait to be able to write the first character. EAGAIN causes write to return None if (!uart_tx_wait(self, self->timeout)) { *errcode = EAGAIN; return MP_STREAM_ERROR; @@ -859,6 +878,17 @@ STATIC mp_uint_t pyb_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t if (status == HAL_OK) { // return number of bytes written return size; + } else if (status == HAL_TIMEOUT) { // UART_WaitOnFlagUntilTimeout() disables RXNE interrupt on timeout + if (self->read_buf_len > 0) { + __HAL_UART_ENABLE_IT(&self->uart, UART_IT_RXNE); // re-enable RXNE + } + // return number of bytes written + if (self->char_width == CHAR_WIDTH_8BIT) { + return size - self->uart.TxXferCount - 1; + } else { + int written = self->uart.TxXferCount * 2; + return size - written - 2; + } } else { *errcode = mp_hal_status_to_errno_table[status]; return MP_STREAM_ERROR;