Weird issue with pyserial flow control

EDIT: It seems less weird now. See You will never guess how JLink deals with UART flow control for the full story.

Working on some Python code to control nRF51 and nrF52 targets I encountered a strange issue with pyserial.

I don't have the full explanation, but it seems to be Windows related and all the work has been on the JLink OB. But I did some observation that could be helpful for someone working on the same issue. It is quite fun to work with logic analyzers and sniffing USB packets and all that, but it takes a lot of time so hopefully this blog post will save you some time.

The initial problem was that the following code didn't enable flow control:

import serial
ser = serial.Serial()
ser.port = 'COM1' # For example
ser.baudrate = 115200
ser.rtscts = True

But after I figured out a reason why this didn't work, it started working, so I will not claim the code is wrong. Only that on one occasion it didn't.

More interestingly, the following code always enables flow control (at least when I tried):

import serial
ser = serial.Serial()
ser.port = 'COM1' # For example
ser.baudrate = 115200 # rtscts=False by default

The main difference I observed when it didn't work was in the Set Control Line State Packet, which is one of the USB packets used to set up the UART on the JLink OB. In particular the DTE bit was '0' when it didn't work. (Carrier Control was '1' in all cases.)

From the implementation of there is default setup in open() when no flow control is set:

# if RTS and/or DTR are not set before open, they default to True
if self._rts_state is None:
    self._rts_state = True
if self._dtr_state is None:
    self._dtr_state = True

The state information is used in _reconfigure_port():

if self._rs485_mode is None:
    if self._rtscts:
        comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE
        comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE
   comDCB.fOutxCtsFlow = self._rtscts

So from Microsoft's documentation on the DCB structure:

  • ser.rtscts = False -> Enables the DTR line when the device is opened and leaves it on.
  • ser.rtscts = True -> Enables DTR handshaking.

The code does in fact do different things, but doesn't quite explain the observed behaviour. Anyhow, it seems that using the default value of rtscts is the safest choice, at least on Windows. Posix seems to implement a more direct control of the lines. To those who have some insightful information: Please leave a comment :)