Apologies for the long post, but it should be worth the read for anyone dealing with fully-featured OS and microcontroller communications using USB Serial (CDC-ACM).
USB Serial (aka USB CDC-ACM) supports a few useful PSTN requests that are supported by generic OS drivers:
- GET_LINE_CODING (0x20) and SET_LINE_CODING (0x21) transfer a zero value with a 7-byte data record with
- 32-bit baud rate (bits per second), in little-endian byte order
- 8-bit stop bit count (0=1 stop bit, 1=1.5 stop bits, 2=2 stop bits)
- 8-bit parity type (0=none, 1=odd, 2=even, 3=mark, 4=space)
- 8-bit data bit count (5, 6, 7, 8, or 16)
, allowing e.g. Linux drivers/usb/class/cdc-acm.c driver to maintain the corresponding termios fields. See include/uapi/linux/usb/cdc.h:struct usb_cdc_line_coding for a C definition of this structure. - SET_CONTROL_LINE_STATE (0x22) sends a 16-bit value from host to device, with all but the two lowest bits zero.
- Bit 1: This corresponds to RS-232 RTS (Ready-To-Send) state.
- Bit 0: This corresponds to RS-232 DTR (Data Terminal Ready) state.
The generic Linux CDC-ACM driver sends this message automatically with bit 0 set when the tty device is first opened, and cleared when last open descriptor closes, allowing the MCU to check if there is an application on the host computer accessing the tty device or not.
(In Teensyduino, the USB (Serial) object evaluates to a boolean corresponding to the bit 0, and is thus true if the tty device is open on a Linux host.) - SEND_BREAK (0x23) with value corresponding to termios tcsendbreak() with the duration specified in milliseconds.
Similarly, there is one useful notification:
- SERIAL_STATE (0x20), with zero value but a 16-bit little-endian data, with the lowest seven bits having useful information:
- Bit 6, bOverRun: Set if device discarded data due to buffer overrun
- Bit 5, bParity: Set if device detected a parity error
- Bit 4, bFraming: Set if device detected a framing error
- Bit 3, bRingSignal: State of ring signal detection on device
- Bit 2, bBreak: State of break detection on device
- Bit 1, bTxCarrier: Corresponds to RS-232 DSR (Data Set Ready)
- Bit 0, bRxCarrier: Corresponds to RS-232 DCD (Data Carrier Detect)
The generic Linux CDC-ACM driver maps
SERIAL_STATE to
TIOCMSET/TIOCMBIC/TIOCMBIS ioctl()s on the open tty device, and maintains the termios structure for the tty device via
SET_LINE_CODING; see
drivers/usb/class/cdc-acm.c:
acm_tty_ioctl() and
acm_tty_set_termios().
As you can see, there is no CTS (Clear To Send) support at all!
This is because the USB device is supposed to NAK the bulk data packets when it is not ready to receive more data yet. Thus, a suitable USB Serial converter should respect the handshaking protocol using the abovementioned USB properties. When internal buffers are close to full, host USB data packets are NAK'ed, and the downstream device is told to stop sending more data (via XOFF if software flow contro, via CTS=0 if hardware flow control). That is,
all flow control should be implemented in the USB-UART bridge, because USB CDC-ACM just cannot do it fully (not without vendor extensions).
As it happens, Stefan Wagner (wagiminator, I like his work) has a WCH CH551/2/4-based OLED project (
sources,
hardware whose terminal-mode
usb_cdc.c (
sdcc, so C99/C11/C23 and not C++ code) looks straightforward to modify for a software-or-hardware flow control USB-UART converter. This way, the software side treats the USB connection as a normal bulk bidirectional data stream without any flow control; the CDC-ACM driver manages the USB flow control; and the USB-UART converter handles the serial flow control. The part count is ridiculously low and cheap.
The
Silicon Labs AN758 application node contains most of the useful USB info needed, but note it specifically does not implement or describe implementing any flow control.
For USB CDC-ACM protocols, one should note that the native FS packet size is 8, 16, 32, or 64 bytes depending on the endpoint configuration, and 512 bytes for HS. USB 2.0 is inherently half-duplex, and while you can easily reach about 950,000 bytes/second
in one direction, requiring a response packet for each one will decimate the bandwidth. XMODEM is unfortunate in that it uses 132-byte packets, which requires an extra FS packet. A custom protocol would yield somewhat better bandwidth, but because of the data ack/nak response packets, would not reach anywhere near what USB-UART converter handling the flow control can (because no response data packets are needed for flow control).