Skip to content

HardwareSerial hangs in flush() with flow control enabled and no device attached #2122

Closed
@fronders

Description

@fronders

Describe the bug
I'm using Sparkfun's SARA-R5 library - an LTE modem using UART connection.
The init sequence includes autobaud selection - it cycles through different UART baudrates and reinitiailizes the UART like this (original code here):

void SARA_R5::beginSerial(unsigned long baud)
{
  delay(100);
  if (_hardSerial != nullptr)
  {
    _hardSerial->end();
    _hardSerial->begin(baud);
  }
...
  delay(100);
}

Then it sends ATE0 and waits for OK response on selected baudrate - classic stuff.

The problem comes up when HardwareSerial has hw flow control enabled and there's no device attached (i.e. CTS not asserted). The end() function gets stuck forever because of flush() waiting for the ringbuffer to become empty (line 499).

void HardwareSerial::flush()
{
// If we have never written a byte, no need to flush. This special
// case is needed since there is no way to force the TXC (transmit
// complete) bit to 1 during initialization
if (!_written) {
return;
}
while ((_serial.tx_head != _serial.tx_tail)) {
// nop, the interrupt handler will free up space for us
}
// If we get here, nothing is queued anymore (DRIE is disabled) and
// the hardware finished transmission (TXC is set).
}

But because hw flow control enabled and CTS not asserted (i.e. SARA-R5 not connected) the bytes pending in the ringbuffer never get transmitted (remember it tries to send ATE0) so the whole while loop just hangs forever.

I've made a "dirty" fix that removes the problem in this specific case - if CTS is enabled and no CTS asserted (UART CTS flag is not set) then simply reset ringbuffer:

void HardwareSerial::flush()
{
  // If we have never written a byte, no need to flush. This special
  // case is needed since there is no way to force the TXC (transmit
  // complete) bit to 1 during initialization
  if (!_written) {
    return;
  }

  // If we have CTS flow control enabled and CTS is not asserted, no need to flush - just reset tx buffer.
  if (_serial.pin_cts != NC && !__HAL_UART_GET_FLAG(&_serial.handle, UART_FLAG_CTS)) {
    _serial.tx_head = 0;
    _serial.tx_tail = 0;
    return;
  }

  while ((_serial.tx_head != _serial.tx_tail)) {
    // nop, the interrupt handler will free up space for us
  }
  // If we get here, nothing is queued anymore (DRIE is disabled) and
  // the hardware finished transmission (TXC is set).
}

But this generally opens a broader issue some of HardwareSerial code has infinite loops relying purely on interrupts and no timeout exit: flush() and write(). Kinda similar to what #1834 brings up

Desktop (please complete the following information):

  • OS: Windows 10 x64
  • Arduino IDE version: 2.2.1
  • STM32 core version: 2.6.0
  • Tools menu settings if not the default: -g3, -Os, newlib nano + printf, USB CDC (supersede U(S)ART). USART Enabled (generic 'Serial), own board variant
  • Upload method: SWD

Board (please complete the following information):
Custom PCB using STM32L496RGT6 MCU connected via UART with CTS\RTS to a u-blox SARA-R5 modem

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions