The Problem
A product's RS-485 bus went down at a customer site—after the symptom appeared, the machine stopped responding to any commands.
In the lab, I tested repeatedly—short circuits, full bus contention with random frames, the bus stuck high or low for extended periods—but I couldn't reproduce it. The machine's communication was rock solid. After two days of hunting, I was growing more and more confident in my part of the code, but the problem at the customer site was real, so I had to go look on-site. By then I was already convinced the differential lines in the field were at fault.
It turned out that wasn't it at all—it really was my problem. In the end, troubleshooting revealed that the machine was stuck with the RS-485 transceiver held in transmit mode.
What Caused It
When there is data to send, the code switches to transmit mode first and switches back to receive mode after sending.
That looks perfectly reasonable, but it isn't. This unit doesn't use DMA for UART management, so both TX and RX rely on peripheral interrupts, and switching the RS-485 transceiver's state is also handled in an interrupt. Under certain conditions, all kinds of weird physical-world interference prevent the interrupt from being generated or handled; perhaps a specific exception causes the peripheral to restart. Either way, the end of transmission is never detected via interrupt, and the transceiver stays stuck in transmit mode. Since the device won't proactively generate any data to send, it ends up unable to receive, sending nothing, and hogging the bus.

Normal waveform

Waveform at the active side when the fault occurred
The Channel Mindset
Half-duplex communication and DMA actually have something in common: both need to gather the messy data that arrives and establish a channel for unified transmission.
The machines that have shipped in large volumes all use DMA + RS-485. DMA interrupts are independent of the peripheral—no matter what happens in the physical world, the DMA channel still fires an interrupt when it completes. That's why this kind of problem never happened.
Because the RS-485 feature was added later, I got lazy and, in order to keep the change as small as possible for stability, I didn't use DMA. Lesson learned again.
The Non-Channel Mindset
Since managing a streaming data interface with interrupts + hand-written registers is a non-channel approach, while the RS-485 physical layer is channel-oriented, what should we do?
I don't think we should rely on a non-channel scheme to manage a channel. For example, pulling the transceiver's direction line high/low in a non-channel interrupt is unreasonable. A polling task should be dedicated to channel direction: periodically check whether the device is in transmit mode, and use combined conditions such as whether the TX buffer is empty and whether the bus is idle. If it hasn't been transmitting for a while, force the channel back to receive mode.
(Don't suggest the peripheral's flow-control pin—that timing isn't controllable and isn't universal across transceivers.)
This mechanism will later be added to the Dataflow library for non-channel applications.
stbanana/Dataflow: Dataflow middleware. Enhanced use of DMA and half-duplex.
The RS-485 Mystery