Just completed testing of the new serial receive code and confirmed it can remain synchronized with inputs from 9300 and 9800 baud. It look about two weeks to figure out the new algorithm and code it. The best part was the final solution required no more resources that the overly-simple original. Like the transmit, the receive thread only consumes one virtual machine cycle per bit and only needed one additional (repurposed) unary function.
The diagram below is a little complex to explain in detail here, but might be of interest in showing some of the analysis behind the algorithm.
The problem being solved here is the synchronization between the transmitter and receiver. Sure, they both run at "9600 baud", but the reality is the clocks are going to drift. This results is the clock slipping one bit ahead or behind periodically. The sampling point also needs adjustment to keep away from the clock edge and prevent spurious data caused by jitter.
The new algorithm examines six sample points over two bit periods. The two bits in question are the stop then start bit. This is guaranteed to be a high-to-low transition regardless of the data being received. The position of this transition is monitored and the data bit sample point is adjusted to avoid any clock jitter/slippage. In addition, the timing is also adjusted when the transition gets too close to either edge of the sampling window.
The state machine has a 10-bit cycle to match the start, the 8 data, and stop bits. If the clock drifts too far then one cycle is either added or removed. If the sample position has moved such that the next data bit sample would align wtih the start bit then an additional empty skip bit is added. This ignores the start bit and creates an 11-bit cycle to realign the timing of the next 10-bit cycle correctly.
A similar thing is done for the other direction when an additional double cycle is added. This cycle samples two bits in the one cycle and then jump ahead by two bits. The result is a 9-bit cycle and a timing adjustment in the other direction.
These adjustments can compensate for a slip of up to one sample period per byte. The serial ports are sampled on every line, so either 4 or 5 lines per bit, or 40 or 50 lines per byte. This translates to an error of 2.5% (1/40) or 2% (1/50) and provides a window of 9400-9800 baud for the serial connection.