ESP32, Hardware Serial, With Flow Control, .availableForWrite() goes to zero

Summary:
I expect the Hardware Serial TX buffer to not fill up but it does. I expect that as bytes are sent out the TX line the buffer would return to full size, 128.

Details:
I am trying to write a Hardwareserial loop back test.
Not only do I want to test TX to RX but also RTS to CTS, the flow control.

Problem, When I enable hardware flow control I find that I can only send a total of 128 characters and then the MySerial1.write(testString, dataLength); command fails to return and hangs my program.

Here is the serial monitor report where you can see the decreasing space in the serial buffer report from this code

Serial.print("Serial available for write ");

Serial.println(MySerial1.availableForWrite());

From Serial Monitor

ESP32 UART1 Flow Control Loopback Test

SerialLoopBackTest V0.0.1

Compiled at: Jan  2 2026 19:36:45

UART1 initialized with Hardware Flow Control.

Ensure GPIO 15 (RX1) is connected to GPIO 2 (TX1)

and GPIO 32 (RTS1) is connected to GPIO 33 (CTS1).

__*******************___

Serial available for write 128

Sending data: Hello, flow control loopback test data!

Data sent. 

Received data: �

CTS pin state: 0

RTS pin state: 0

---

Serial available for write 89

Sending data: Hello, flow control loopback test data!

Data sent. 

CTS pin state: 0

RTS pin state: 0

---

Serial available for write 50

Sending data: Hello, flow control loopback test data!

Data sent. 

CTS pin state: 0

RTS pin state: 0

---

Serial available for write 11

Sending data: Hello, flow control loopback test data!

//Here is the code

SerialLoopBackTest20260102_2008.zip (1.5 KB)

I tried but was not able to find a catagory like HardwareSerial library for this topic. Is there a better way?

Your description and test data shows the RTS and CTS are working just as designed. Unfortunately, your TX buffer and the RX buffer will always be identical sizes and will not trigger the data control.

Next time, post the code in code tags. By now, you have likely figured out that it is working correctly.
Also, use code tags for your log data; wading through multiple pages of one-line logs is boring.

Regarding “..TX buffer and the RX buffer will always be identical sizes…”

This misses the point. The TX Buffer becomes filled and eventually program execution stops.

I had the expectation that as the underlying library code for the .write function transmits the bytes out the TX Buffer would return to the full size.

And now it occurs to me that the RX Buffer might also be filling up because of the loop back around which the test is designed.

This misses the point. The TX Buffer becomes filled and eventually program execution stops. A program which stops is not working correctly.

Regarding

“Also, use code tags for your log data; wading through multiple pages of one-line logs is boring.”

Please provide an example of “code tags” or reference to such practices.

More information, Flow control OFF.
Screen shot below of Serial monitor of sketch operating with flow control OFF.
(I commented out the line: “// MySerial1.setHwFlowCtrlMode(UART_HW_FLOWCTRL_CTS_RTS);”

You see for each time it writes serial data the TX Buffer remains at 128 and the program runs indefinitely.

It is mentioned in the pinned post you should have read when you first joined the forum. It is titled 'How to get the best from the forum. ' A copy has been pinned to every sub-category for your convenience. When typing a reply like this, it is the icon I underlined


in red in the following screen grab.

I doubt there has ever been a computer made that used a buffer for output that did not stop once the buffer was filled, or they documented that data was lost.

Your original post says you expect a certain behaviour. If that is not what is happening, please post a link to the erroneous documentation so an Arduino employee can see it and act on it.

I hope I have understood your issue correctly.

Oh I utterly misunderstood.
I thought you ware asking for some kind of tagging in the Serial Monitor output I was writing to make it more understandable..

IIRC, The use of of the code tag did not give me what I have become accustomed to with Github Markdown and I confuse easily.

I am trying to figure out my part of such code.
My use case is a factory test where I connect and disconnect loop back from TX RX and RTS CTS.

In this use case, the main loop the code should report the status of the individual loop backs.

And the code should not be blocked.

I have made some progress but not yet achieved this goal.

Some Progress To Report

I have a version VERSION "V0.0.2" which is working much closer to my goal.

I had to set the RTS and CTS pins and move around the line which enabled the flow control.

How it works:

With a jumper on RTS CTS data is sent.

With out the jumper the buffer fills and data stops.

Reattaching the jumper and data transmission resumes, emptying the TX buffer.

However when I try to read the status of the flow control lines with digitalRead(CTS) I always get zero.

I confirm that the voltage going into the CTS pin changes from 0 to 3.3V when the jumper is removed.

SerialLoopBackTest VERSION "V0.0.2" firmware

SerialLoopBackTest20260103_1719FeatureRequest.zip (2.0 KB)

Did I forget to mention that code goes in a code block as in < CODE/ >. Same for verbose compile or upload logs if they contain errors.

I think that digitalRead() may be sending some log message saying that the pin is not in INPUT state and it can't be read. That pin is actually attached to the CTS signal from the GPIO Matrix, directly connected to the UART peripheral.

Try enablling log messages (set it to INFO level in the IDE Tools Menu).
Read the message in the UART0 (console).
It is necessary to enable log output using Serial.setDebugOutput(true);

Please remove MySerial1.setDebugOutput(true); //for UART from yout code.
It will send all log messages to UART1 instead of UART0 (console).

My suggestion would to connect the CTS pin to another pin that is set in INPUT mode in order to read it. Maybe it is also possible to connect it to a resistor and an LED in order to see its state.

@Forrest_Erickson2002 - I have found an issue with RTS/CTS pin configuration in ESP32 Arduino Core code.

Please refer to the comments in the open issue about it:

rocorb,
Thanks for directing me to debug messages. This is new to me. I set for

I added the Serial.setDebugOutput(true); into setup.

I compile and run the code and have this:

Which I now need to read and learn from.

Ten thousand thanks!

The logged message:

ESP32 UART1 Flow Control Loopback Test
SerialLoopBackTest V0.0.3
Compiled at: Jan  4 2026 06:00:09
CTS pin state: 1
RTS pin state: 1
E (31) uart: tout_thresh = 50 > maximum value = 1
UART1 initialized with Hardware Flow Control.
Ensure GPIO 15 (RX1) is connected to GPIO 2 (TX1)
and GPIO 32 (RTS1) is connected to GPIO 33 (CTS1).
[    44][E][esp32-hal-gpio.c:190] __digitalRead(): IO 33 is not set as GPIO.
[    59][E][esp32-hal-gpio.c:190] __digitalRead(): IO 32 is not set as GPIO.
CTS pin state: 0
RTS pin state: 0
__*******************___
Serial RX available for read 0
Serial available for write 128
Sending data: Hello, flow control loopback test data!
Data sent. 
[    76][E][esp32-hal-gpio.c:190] __digitalRead(): IO 33 is not set as GPIO.
[    89][E][esp32-hal-gpio.c:190] __digitalRead(): IO 32 is not set as GPIO.
CTS pin state: 0
RTS pin state: 0
Received data: Hello, flow control loopback test data!
[  1196][E][esp32-hal-gpio.c:190] __digitalRead(): IO 33 is not set as GPIO.
[  1207][E][esp32-hal-gpio.c:190] __digitalRead(): IO 32 is not set as GPIO.
CTS pin state: 0
RTS pin state: 0
---
Serial RX available for read 0
Serial available for write 128
Sending data: Hello, flow control loopback test data!
Data sent. 
[  3224][E][esp32-hal-gpio.c:190] __digitalRead(): IO 33 is not set as GPIO.

“Pin is not in INPUT state” Guess confirmed
So it appears that once the RTS and CTS pins are set as flow control their state cannot be read by digitalRead().

Reference I am using

I have spent time reading but not yet fully understanding the HardwareSerial library at this URL: arduino-esp32/cores/esp32/HardwareSerial.h at master · espressif/arduino-esp32 · GitHub

I do not see how the library intends for the code to check the status of the flow control lines.

It is the CTS which gives us information about the ability of the device to which my product is attached that I assume I must check before transmitting data, and the status of which I should make available to the user trying to trouble shoot connections (with flow control ON).

My Assumptions on the utility of CTS
I would think that watching the TX buffer for less than 128, meaning that I have already written data that has not been and cannot be sent is bad practice.
It is CTS I must check before a mySerial1.write() to TX.

@Forrest_Erickson2002

There is an issue with RTS and CTS pin configuration in Arduino Core 3.3.5
This has been detected and it will be addressed soon within a PR.

In the meanwhile, it can be fixed through a workaround, using ESP-IDF call to set the pins right after Serial1.begin(...). Adapt your code to follow these steps:

1- include the ESP-IDF header file for using the necessary fucntion:

#include "driver/uart.h"

2- set the pins right after Serial1.begin(...) in order to ensure that RTS and CTS are correctly attached to the UART signal matrix. Example:

  // Begin UART1 with 115200 baud, 8N1 configuration
  Serial1.begin(115200, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);
  // use ESP-IDF to configure all pins, including RTS and CTS
  uart_set_pin((uart_port_t)1, UART1_TX_PIN, UART1_RX_PIN, UART1_RTS_PIN, UART1_CTS_PIN);
  
  // Enable Hardware Flow Control in order to use RTS and CTS 
  // Loop back shall work connecting RX1 to TX1 and RTS1 to CTS1
  // If RTS1 and CTS1 are not cross connected, no data is transmited 
  if (!Serial1.setHwFlowCtrlMode(UART_HW_FLOWCTRL_CTS_RTS, 64)) {
    Serial.println("ERROR: Failed to enable hardware flow control!");
    while(1) delay(1000);
  }

There is an open issue in ESP32 Arduino Github that explains the problem and details a working example and applies this workaround.
If necessary check the commentaties in the issue number 12199:
github com /espressif/arduino-esp32/issues/12199