UART DMA is often introduced as a way to save CPU cycles. That is true, but it is not the main design decision. The important question is how the buffer represents a stream whose boundaries, timing, and failure modes are not always known in advance.
A UART does not deliver messages
UART delivers bytes. It does not know whether 40 bytes form one packet, half a packet, or several short commands. A DMA transfer can move those bytes efficiently, but it cannot invent framing.
Start by defining one of these contracts:
- fixed-length frames;
- delimiter-terminated messages;
- length-prefixed packets; or
- an idle-time gap that marks the end of a burst.
The framing rule determines whether a normal DMA buffer is enough or whether a circular buffer and an idle-line event are better.
Choose the buffer model deliberately
A normal DMA transfer is simple for a known block size. When the sender can stop at any byte, a circular buffer is usually more robust. DMA writes continuously, while software keeps a read index. The consumer processes only the bytes between the old and new indices.
That design needs three invariants:
- The producer never overwrites unread data without raising an overflow condition.
- The consumer advances its index only after validating how many bytes are available.
- Wrap-around is handled explicitly rather than hidden in an unsafe pointer calculation.
The buffer size should come from the worst credible burst, not from a convenient power of two. Include protocol overhead, interrupt latency, and the time during which a higher-priority task can block the parser.
DMA does not remove the need for timing analysis
DMA reduces per-byte CPU work, but the system still has to notice new data. On some MCUs, a transfer-complete interrupt is enough for fixed blocks. On others, half-transfer, idle-line, or receiver-timeout events are needed for variable-length traffic.
Measure the worst delay between the hardware event and the software reading the DMA counter. Then test a burst that arrives while the system is handling a display refresh, flash write, radio packet, or other long operation. “It worked at 115200 baud” is not a throughput proof unless the workload was realistic.
The EmbeTronicX STM32 UART DMA example is a useful starting point for understanding peripheral-to-memory transfer. A production design still needs an explicit framing, overflow, and recovery policy.
Keep the electrical interface in scope
Firmware cannot compensate for a marginal UART waveform. Check logic thresholds, cable length, ground reference, ESD exposure, and voltage-domain compatibility. If the line leaves the board, use an appropriate transceiver and protection strategy rather than routing a bare MCU pin to a connector.
For a noisy or long connection, the logic buffer and transceiver selection area can help narrow the device search. The final choice should include fail-safe behavior and powered-off conditions.
Design the error path first
When the buffer overflows, do not silently discard an arbitrary byte and continue as if the stream were valid. Mark the frame invalid, resynchronize using the protocol’s next known boundary, and count the event. Expose counters for CRC failures, framing errors, overruns, and buffer overflow.
The power rail deserves the same attention. A short brownout can reset the UART peripheral while leaving the rest of the board apparently alive. Review the power-management IC options and define what happens to the DMA state after reset.
If the interface includes a current-limited transceiver or termination network, use a component derating check to verify that the protection and bias parts still have margin at the maximum cable fault and ambient temperature.
Finally, keep the board-level implementation reproducible. Connector orientation, transceiver placement, and the inspection of pull-ups or termination parts belong in the PCB assembly process. A reliable UART is a system property, not just a callback function.
Top comments (1)
The line "DMA can move the bytes efficiently, but it cannot invent framing" is the one most people learn the hard way. I ran a device that worked perfectly at the desk and desynced randomly in the field, and the root cause was exactly this: we treated transfer-complete as a message boundary when the sender could stop mid-packet. Half-transfer interrupts plus an idle-line timeout fixed it; the DMA buffer size was never the problem.
The part I'd push even harder is "the buffer size should come from the worst credible burst, not a convenient power of two." Powers of two are nice for wrap-around math, but they invite people to size the buffer from the round number instead of from interrupt latency under the heaviest task in the system. Exposing the overrun counter as first-class telemetry, not a debug-only variable, was what finally told us the framing policy was wrong.