I discovered that there is a problem with the RasPi UART clock scheme that results in variable UART clocking and subsequent corruption, described here.
“The mini UART port can be accessed using the /dev/ttyS0 device in Linux. One issue with the mini UART controller is that its baud rate is linked to the VPU core frequency so as the VPU frequency changes depending on processor load so does the baud rate of the UART controller. This will cause problems as serial devices expect a constant baud rate during communication.”
Using the PL011 UART rather than the mini resolved my problem. I wish I had known about this before expending so much time on it.