CircuitPython version and board name
Adafruit CircuitPython 10.0.0-beta.3 on 2025-08-29; Pimoroni Pico Plus 2 W with rp2350b
vs.
Adafruit CircuitPython 10.0.0-beta.3 on 2025-08-29; Adafruit Feather M4 Express with samd51j19
Code/REPL
# in all cases, u1 is wired to u2: tx to rx, and cts to rts
import time ; time.sleep(3) # wait for serial after reset
import sys
import board
import busio
def write(size, ch):
print(f'writing {size} bytes...', end=" ")
written = u1.write(f'{size * ch}')
time.sleep(1)
print(f'{written=} {} {u2.in_waiting=}')
if "Pico" in sys.implementation._machine:
u1 = busio.UART(tx=board.GP0, rx=board.GP1, cts=board.GP2, rts=board.GP3)
u2 = busio.UART(tx=board.GP4, rx=board.GP5, cts=board.GP6, rts=board.GP7)
elif "M4" in sys.implementation._machine:
u1 = busio.UART(tx=board.A4, rx=board.A5, cts=board.D9, rts=board.D6)
u2 = busio.UART(tx=board.A2, rx=board.A3, cts=board.D11, rts=board.D10)
u2.reset_input_buffer()
print(f'\nbusio initial {u2.in_waiting=}')
write(64, "A") # presumably fills busio default receiver_buffer_size=64 bytes
write(32, "B") # raspberry pi only: presumably fills raspberrypi hw uart rx buffer 32 bytes
if "Pico" in sys.implementation._machine:
size = 6 # <=5 somehow works; 6 will hang the serial console (CIRCUITPY is OK)
elif "M4" in sys.implementation._machine:
size = 16 # can write as much as memory allows (e.g., 32768)
write(size, "C")
r = u2.read(256)
print(f'read {len(r)} bytes: {r}')
Behavior
Pico output (writing 102 bytes):
busio initial u2.in_waiting=0
writing 64 bytes... written=64 () u2.in_waiting=64
writing 32 bytes... written=32 () u2.in_waiting=64
writing 6 bytes...
• The write blocks, but also the serial console hangs (no control-c). CIRCUITPY is still accessible. Reset is required to regain use of the serial console.
or
Pico output (writing 101 bytes):
busio initial u2.in_waiting=0
writing 64 bytes... written=64 () u2.in_waiting=64
writing 32 bytes... written=32 () u2.in_waiting=64
writing 5 bytes... written=5 () u2.in_waiting=64
read 101 bytes: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCC'
• Flow control limit not reached: bytes written == bytes read.
• Note that in_waiting only counts the receiver_buffer_size, not the pico hardware buffer or the magic extra 5 bytes.
...compare to...
M4 output:
busio initial u2.in_waiting=0
writing 64 bytes... written=64 () u2.in_waiting=64
writing 32 bytes... written=32 () u2.in_waiting=64
writing 16 bytes... written=16 () u2.in_waiting=64
read 64 bytes: b'AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCC'
• Hardware flow control doesn't seem to work. Any number of bytes can be written and the buffer will contain the last 64 bytes sent.
Description
No response
Additional information
I also tested an Adafruit ESP32-S2 Feather, same pinout as the M4, so I plugged it into the same wiring that the M4 was in. Hardware flow control seemed to work here, lending support to the wiring being correct. The ESP32-S2 will write the 64 bytes, then the 32 bytes, then up to 242 more bytes (implying some buffering behind-the-scenes of default receiver_buffer_size of 242+32=274 bytes). If 243 are attempted, the write blocks and the serial console hangs, like raspberrypi. I looked for esp-idf definitions of receive size, but didn't find it.
Note that on the ESP32-S2, in_waiting shows a maximum of 96, which presumably includes the 64 receiver_buffer_size bytes, but only 32 of the behind-the-scenes bytes.
Perhaps it's intended behavior for hardware flow control to block writes indefinitely, ignoring the UART init timeout value. This creates a hazard for any code with hardware flow control in the case that the receiver has trouble.
It's even more hazardous for PIO UART with hardware flow control since only 8 received bytes can be buffered at a time before blocking.
It also rules out the use of asyncio for UART writer + reader configurations communicating with each other, where the reader may not keep up with the receive buffer for a wide variety of reasons.
My goal is to understand the (intended) behaviors of UART across ports, including raspberrypi PIO UART, to be able to write more universal CircuitPython UART code.
CircuitPython version and board name
Code/REPL
Behavior
Pico output (writing 102 bytes):
• The write blocks, but also the serial console hangs (no control-c). CIRCUITPY is still accessible. Reset is required to regain use of the serial console.
or
Pico output (writing 101 bytes):
• Flow control limit not reached: bytes written == bytes read.
• Note that
in_waitingonly counts thereceiver_buffer_size, not the pico hardware buffer or the magic extra 5 bytes....compare to...
M4 output:
• Hardware flow control doesn't seem to work. Any number of bytes can be written and the buffer will contain the last 64 bytes sent.
Description
No response
Additional information
I also tested an Adafruit ESP32-S2 Feather, same pinout as the M4, so I plugged it into the same wiring that the M4 was in. Hardware flow control seemed to work here, lending support to the wiring being correct. The ESP32-S2 will write the 64 bytes, then the 32 bytes, then up to 242 more bytes (implying some buffering behind-the-scenes of
default receiver_buffer_sizeof 242+32=274 bytes). If 243 are attempted, the write blocks and the serial console hangs, like raspberrypi. I looked for esp-idf definitions of receive size, but didn't find it.Note that on the ESP32-S2,
in_waitingshows a maximum of 96, which presumably includes the 64receiver_buffer_sizebytes, but only 32 of the behind-the-scenes bytes.Perhaps it's intended behavior for hardware flow control to block writes indefinitely, ignoring the UART init timeout value. This creates a hazard for any code with hardware flow control in the case that the receiver has trouble.
It's even more hazardous for PIO UART with hardware flow control since only 8 received bytes can be buffered at a time before blocking.
It also rules out the use of
asynciofor UART writer + reader configurations communicating with each other, where the reader may not keep up with the receive buffer for a wide variety of reasons.My goal is to understand the (intended) behaviors of UART across ports, including
raspberrypiPIO UART, to be able to write more universal CircuitPython UART code.