Summary
On Linux, a Turing 5" revision C display (chs_5inch.dev1_rom1.88) cannot be
driven through the stock pyserial/cdc_acm path: the bulk OUT endpoint accepts
exactly one ~250-byte frame and then jams until a USB reset. The identical
endpoints work perfectly when driven with raw libusb bulk transfers. I'd like to
propose an opt-in, Linux-gated libusb transport for rev C (pyserial stays the
default everywhere), and I'm opening this first to gauge interest and preferred
shape.
This is tested on a single unit, and the proposal deliberately changes no
defaults — see the caveats and open questions at the end.
Environment
- Display: Turing 5" rev C, ID
chs_5inch.dev1_rom1.88
- awake:
1d6b:0106 "Android", serial 20080411 → /dev/ttyACM1
- asleep:
1a86:ca21 "UsbMonitor", serial CT21INCH → /dev/ttyACM0
- OS: Linux (kernel 6.x/liquorix), Python 3.13
- Project: current
main (3.10.0-109-g4df61d9), i.e. with the new
SERIAL_OPEN_ATTEMPTS open-retry loop in lcd_comm.py
- Kernel driver:
cdc_acm binds the awake interface and creates /dev/ttyACM1
Symptom
With REVISION: C, the app opens the port fine, then _hello() reads an empty
display ID and loops forever ("Display returned invalid or unsupported ID, try
again in 1 second"). Nothing ever draws.
What I measured
I drove the panel through the stock rev C driver (pristine origin/main, no
local changes) and instrumented every serial_write to record the exact stall
point. Key results:
- Open works. Stock
openSerial (with the retry loop) opens /dev/ttyACM1
on the first attempt. So the retry loop does its job — the failure is not at
the open stage.
- Handshake fails. The HELLO exchange returns an empty ID; stock
_hello
retries indefinitely.
- Byte-level, flow control ON (stock
rtscts=True): the very first 250-byte
write drains 0 bytes (write timeout). The stock port is opened with
hardware RTS/CTS flow control, and this CDC-ACM gadget appears not to assert
CTS, so nothing transmits.
- Byte-level, flow control OFF (
rtscts=False): the first 250-byte frame
drains, but the next write stalls (Write timeout). i.e. the bulk OUT
endpoint accepts exactly one ~250-byte frame, then jams. Recovery requires
a USB reset / physical replug.
- Same endpoints over raw libusb: HELLO returns
chs_5inch.dev1_rom1.88
immediately and full-screen frames stream without limit.
So the jam is a cdc_acm data-path limit, reproduced against current
upstream, and independent of the retry loop and of flow control (I tested
both). Happy to share the standalone repro harness.
Corroboration
The OEM Windows software for these panels also bypasses the serial/COM path and
speaks raw USB bulk (WinUsb_WritePipe / LibUsbDotNet) — consistent with the
serial path being unusable for sustained transfer on this hardware.
Proposal
An opt-in, Linux-only libusb transport for rev C:
- A config option (default off) selects it, e.g.
REV_C_TRANSPORT: SERIAL | LIBUSB, read with a safe default so pyserial stays the default for everyone
and existing rev C users are unaffected.
- Implemented inside the rev C class (no new
REVISION; routes through the
existing factory).
- Uses
pyusb (already a dependency for TUR_USB). On Linux it detaches
cdc_acm (detach_kernel_driver) and drives bulk OUT/IN directly.
- Gated to Linux with graceful fallback:
detach_kernel_driver is a
Linux/usbfs capability (not supported on libusb's Windows backend), so this
only engages on Linux; Windows/macOS keep pyserial untouched.
Caveats & open questions
- Single unit tested (5", ROM 88). Rev C USB IDs are unreliable across sizes,
so I can't promise other rev C variants behave identically — which is exactly
why this is opt-in and default-off.
- Do Windows/macOS rev C users see any equivalent stall over the COM port?
The jam is a Linux cdc_acm behavior; Windows uses usbser.sys. If the serial
path already works there, the libusb transport only ever needs to engage on
Linux (as proposed).
- Preferred opt-in mechanism? I noticed
config.yaml changes are blocked by
CI (do-not-commit-config-yaml), so I'd read the option with a default and let
you add the documented key to the shipped template — unless you'd prefer a
different mechanism.
Would you welcome a PR along these lines? Happy to adjust the shape (config key
name, gating, docs) to whatever fits the project.
Summary
On Linux, a Turing 5" revision C display (
chs_5inch.dev1_rom1.88) cannot bedriven through the stock pyserial/
cdc_acmpath: the bulk OUT endpoint acceptsexactly one ~250-byte frame and then jams until a USB reset. The identical
endpoints work perfectly when driven with raw libusb bulk transfers. I'd like to
propose an opt-in, Linux-gated libusb transport for rev C (pyserial stays the
default everywhere), and I'm opening this first to gauge interest and preferred
shape.
This is tested on a single unit, and the proposal deliberately changes no
defaults — see the caveats and open questions at the end.
Environment
chs_5inch.dev1_rom1.881d6b:0106"Android", serial20080411→/dev/ttyACM11a86:ca21"UsbMonitor", serialCT21INCH→/dev/ttyACM0main(3.10.0-109-g4df61d9), i.e. with the newSERIAL_OPEN_ATTEMPTSopen-retry loop inlcd_comm.pycdc_acmbinds the awake interface and creates/dev/ttyACM1Symptom
With
REVISION: C, the app opens the port fine, then_hello()reads an emptydisplay ID and loops forever ("Display returned invalid or unsupported ID, try
again in 1 second"). Nothing ever draws.
What I measured
I drove the panel through the stock rev C driver (pristine
origin/main, nolocal changes) and instrumented every
serial_writeto record the exact stallpoint. Key results:
openSerial(with the retry loop) opens/dev/ttyACM1on the first attempt. So the retry loop does its job — the failure is not at
the open stage.
_helloretries indefinitely.
rtscts=True): the very first 250-bytewrite drains 0 bytes (write timeout). The stock port is opened with
hardware RTS/CTS flow control, and this CDC-ACM gadget appears not to assert
CTS, so nothing transmits.
rtscts=False): the first 250-byte framedrains, but the next write stalls (
Write timeout). i.e. the bulk OUTendpoint accepts exactly one ~250-byte frame, then jams. Recovery requires
a USB reset / physical replug.
chs_5inch.dev1_rom1.88immediately and full-screen frames stream without limit.
So the jam is a
cdc_acmdata-path limit, reproduced against currentupstream, and independent of the retry loop and of flow control (I tested
both). Happy to share the standalone repro harness.
Corroboration
The OEM Windows software for these panels also bypasses the serial/COM path and
speaks raw USB bulk (
WinUsb_WritePipe/ LibUsbDotNet) — consistent with theserial path being unusable for sustained transfer on this hardware.
Proposal
An opt-in, Linux-only libusb transport for rev C:
REV_C_TRANSPORT: SERIAL | LIBUSB, read with a safe default so pyserial stays the default for everyoneand existing rev C users are unaffected.
REVISION; routes through theexisting factory).
pyusb(already a dependency forTUR_USB). On Linux it detachescdc_acm(detach_kernel_driver) and drives bulk OUT/IN directly.detach_kernel_driveris aLinux/usbfs capability (not supported on libusb's Windows backend), so this
only engages on Linux; Windows/macOS keep pyserial untouched.
Caveats & open questions
so I can't promise other rev C variants behave identically — which is exactly
why this is opt-in and default-off.
The jam is a Linux
cdc_acmbehavior; Windows usesusbser.sys. If the serialpath already works there, the libusb transport only ever needs to engage on
Linux (as proposed).
config.yamlchanges are blocked byCI (
do-not-commit-config-yaml), so I'd read the option with a default and letyou add the documented key to the shipped template — unless you'd prefer a
different mechanism.
Would you welcome a PR along these lines? Happy to adjust the shape (config key
name, gating, docs) to whatever fits the project.