While most dedicated serial I/O chips implement CTS/RTS flow control in hardware, many of the serial I/O ports incorporated into microcontrollers do not -- or claim to, but do not implement it correctly.
For example, the STM32's serial ports advertise Request to Send support, but this merely deasserts RTS whenever the receiver data register is not empty. This is incorrect for two reasons:
1) The RTS signal will deassert between *each* character received while the RXNE interrupt is pending
(if you throw this RTS signal on a logic analyzer, you get a great look at ChibiOS's interrupt latency
I observed these RTS glitches causing the sender to throttle its output unnecessarily -- by as much as 30% at very high baud rates.
2) There is nothing protecting the firmware's input queue from overflowing.
The driver may be able to service the USART, but firmware may have no space for the byte read!
This can happen when the thread consuming input is blocked for any reason.
In my case it was waiting for CTS on another port.
When implemented correctly,
RTS is deasserted when the input FIFO becomes "nearly full" (i.e. rises beyond its "high water mark")
It should be reasserted when the input FIFO becomes less full (i.e. falls below its "low water mark")
How to implement this with as little change to the existing serial drivers as possible?
The existing input queue "notify" hook can be used to reassert RTS when a FIFO falls back below its low water mark.
But, there's nothing I could find in the existing STM32 serial driver interrupt service that would allow one to deassert RTS when the FIFO rises past high water.
Most serial drivers queue a received byte via a hard coded call to sdIncomingDataI().
By simply vectoring this call through an "inputHandler" function pointer (stored in the SerialDriver object), custom versions can:
1) Handle the high water case of RTS,
2) Generate CHN_INPUT_AVAILABLE only on receipt of a new line character or other delimiter,
3) etc...
The SerialDriver object would be initialized with the inputHandler pointing to sdIncomingDataI(), so there'd no change in the behavior of exiting applications.
The only change required to each serial driver is to replace the sdIncomingDataI() call in the ISR to:
Code: Select all
sdp->inputHandler(sdp, byteReceived);