Introduction
Here is a scenario every embedded engineer eventually runs into. You wire up two boards with three wires - TX, RX, GND - flash a bit of UART code, and it works flawlessly on your desk. A few weeks later the same link is deployed next to a motor, a switching power supply, and three meters of unshielded cable. Bytes get corrupted, characters go missing, and sometimes the link just stops responding until you power-cycle everything. Nothing in your code changed. What changed is the environment, and the signal you are relying on was never designed to survive it.
This article explains why a plain single-ended UART link degrades in electrically noisy settings, why differential signaling (and RS-485 in particular) fixes the problem, and what it actually takes - in hardware and in firmware - to build a link that holds up. I will use a real, if modest, case from my internship as a running thread: a Raspberry Pi commanding a motor over RS-485 to control the vibration frequency of a test bench.
This article will not re-explain how UART framing works at the bit level; Nathan Ragot’s “Art of UARTs” already covers that well. It will also not cover SPI, I2C or CAN - see Eve Gresse’s article on device communication for a broader overview of serial protocols in embedded systems.
Prerequisites
- A working knowledge of UART (start/stop bits, baud rate, framing).
- Basic electronics: what a common ground is, what a wire’s impedance means, roughly how a resistor divider works.
- Some familiarity with bare-metal or RTOS-level microcontroller programming helps for the firmware section, but is not strictly required to follow along.
What you’ll learn
- Why a single-ended UART line is fragile once it leaves a PCB: common ground assumptions, EMI, ground loops.
- The principle of differential signaling and common-mode rejection, and how RS-232, RS-422 and RS-485 compare.
- The hardware side of an RS-485 link: transceivers, the DE/RE direction pins, termination, bus biasing, topology and cabling.
- Why a minimal protocol layer (addressing, CRC, timeout) is needed on top of raw bytes, and why that layer tends to converge toward something like Modbus RTU.
1. Why a plain UART line fails once it leaves the board
A standard UART link is single-ended: each signal (TX, RX) is a voltage measured relative to a single, shared ground reference. That assumption - “both ends agree on what 0V means” - holds trivially when TX and RX sit a few centimeters apart on the same PCB. It stops holding once the two ends are separated by real cable length and live in a real electrical environment, for two related reasons.
Ground is not one point, it’s a network of points. Any real conductor has non-zero resistance and inductance, so as soon as current flows through the ground plane or ground wire connecting two devices, a small voltage difference appears between the two “grounds”. If a motor, contactor, or switching power supply shares that ground path, the current spikes it injects can shift the local ground potential by volts, not millivolts. Since your receiver reads incoming voltage relative to its own local ground, it now sees a corrupted signal even though nothing changed on the transmit side. This is the textbook definition of a ground loop, and it is one of the most common causes of “it works on the bench but not in the field” bugs in industrial electronics.
Long cables are antennas. A signal wire strung near motor phase cables or switching regulators will pick up electromagnetic interference (EMI) - both magnetically and capacitively coupled - and this noise adds directly to your single-ended signal, since the receiver has no way to distinguish “real signal” from “noise picked up along the way”. The longer the cable and the closer it runs to noise sources, the worse it gets.
RS-232 (the classic single-ended serial standard) is explicitly limited to about 15 meters for this reason, and even that is optimistic in a noisy setting.
2. The differential answer
Differential signaling solves both problems at once, and the trick is
simple: instead of sending one signal referenced to ground, send two
signals - usually called A and B, or D+/D- that are always the
exact inverse of each other. The receiver does not look at either wire’s
absolute voltage; it looks at the difference between them (A - B).
Any noise that couples onto the cable (EMI from a nearby motor, a shifting ground potential) tends to affect both wires almost identically, because they run right next to each other (twisted together, even). That shared, identical disturbance is called a common-mode signal, and since the receiver only cares about the difference between A and B, that common-mode noise cancels out almost entirely. This property is called common-mode rejection, and it is the whole reason differential links survive environments that would destroy a single-ended one.
Twisting the pair matters too: any interference that induces a voltage on one loop of the twist induces the same voltage, in the opposite direction, on the next loop, so the net effect on each wire stays balanced and the common-mode assumption holds even better.
This is where RS-485 comes in - it is essentially RS-422’s differential signaling, but designed to allow several transceivers to share the same two wires (a “multidrop” bus), which is exactly what you want on a factory floor with several sensors and actuators on one line.
The table below lines the three standards up on the two axes that matter most once you’re choosing between them: duplex (whether a link can send and receive at the same time, or has to alternate between the two), and how many drivers and receivers can share the same physical bus - a point-to-point link only ever needs 1/1, but a shared industrial bus needs room for many.
| RS-232 | RS-422 | RS-485 | |
|---|---|---|---|
| Signaling | Single-ended | Differential | Differential |
| Duplex | Full | Full | Usually half (2-wire) |
| Drivers / receivers on bus | 1 / 1 | 1 / up to 10 | Up to 32 (more with modern transceivers), multidrop |
| Typical max cable length | ~15 m | ~1200 m | ~1200 m |
| Typical max speed | ~115 kbps | Up to 10 Mbps (short cable) | Up to 10 Mbps (short cable), ~100 kbps at 1200 m |
(Figures are typical values pulled from Analog Devices’ guide to RS-232/422/485; actual limits depend on the transceiver and cable used.)
The takeaway: RS-485 buys the same differential noise immunity as RS-422, but trades away full-duplex operation in exchange for letting dozens of devices share one pair - which is exactly the multidrop bus a factory floor with many sensors and actuators actually needs.
Note the speed/distance trade-off on the RS-485 line: it is fundamentally a transmission-line problem, not a marketing number. A longer cable has more distributed resistance and capacitance, which rounds off each bit’s edges and increases propagation delay end to end; push the baud rate up and the bit period shrinks until it is no longer long enough for the line to settle before the next bit starts, blurring one bit into the next. So treat both figures as “one or the other, not both at once” for long runs.
3. The hardware that makes it work
The transceiver
RS-485 is not something a microcontroller UART pin does natively - you still emit ordinary single-ended TX/RX signals from the MCU, and a transceiver chip converts them to and from the differential pair. The MAX485 (and its many clones/variants) is the classic choice for this, and shows up on nearly every RS-485 breakout board you’ll find.
On the transceiver itself, the MCU side exposes two ordinary single-ended pins: DI (Driver Input, fed by the MCU’s TX) and RO (Receiver Output, feeding the MCU’s RX). Because most cheap RS-485 transceivers are half-duplex - they share the same pair for sending and receiving, one direction at a time - they also expose two extra control pins:
- DE (Driver Enable): drive it high to let the chip put your TX data on the bus; drive it low to tri-state the driver so it doesn’t fight anyone else on the bus.
- RE (Receiver Enable, often active-low): the mirror image, enabling the chip to forward whatever is on the bus back to your MCU’s RX pin.
In practice DE and RE are almost always tied together and driven by a single GPIO: high to talk, low to listen.
┌───────────────┐
MCU TX ──────►│ DI A ├──────┐
│ │ │ twisted
MCU RX ◄──────┤ RO B ├──────┘ pair
│ │
GPIO ───┬───►│ DE │
└───►│ RE (active-low)
└───────────────┘
half-duplex transceiver
(e.g. MAX485)
Termination and biasing
An RS-485 bus is, electrically, a transmission line, and an unterminated transmission line reflects signal energy back from its open end. At the speeds RS-485 is typically pushed to, those reflections arrive back on the line while a bit is still being driven, distorting it. The standard fix is a 120 Ω termination resistor at each end of the bus (not in the middle, not at every node - just the two physical ends), matching the characteristic impedance of typical twisted-pair cable (Analog Devices’ wiring guide).
A second, separate problem is what happens when nobody is driving the bus - every transceiver is tri-stated, and the line is free to float to whatever a stray voltage pushes it to, which a receiver may interpret as random noise and false-trigger on. The fix is fail-safe biasing: a pull-up resistor on A (to the supply rail) and a pull-down resistor on B (to ground), so the idle bus always settles to a known, valid “no one is talking” state instead of floating (a good practical explainer here). These resistors only need to exist once on the whole bus, not at every node.
Topology and cabling
RS-485 wants a daisy-chain topology: node, then node, then node, in a straight line, with termination at the two physical ends. Wiring it as a star (each device on its own branch back to a hub) creates stub lines that behave as unterminated dead ends - signal reflects off each one and comes back to corrupt the bus, and the effect gets worse as stub length or baud rate increases.
Daisy-chain (do this):
[120Ω]──node1───node2───node3──[120Ω]
Star (avoid it, or use a proper repeater/hub if you must):
┌──node1
master ──────┼──node2
└──node3 (each branch is an unterminated stub)
For the cable itself: a twisted pair is what makes common-mode rejection work in the first place, and a shield, grounded at one end only, helps against the worst EMI without introducing its own ground loop. Standard telecom twisted pair (the kind used for Ethernet) is a commonly reused option for short industrial runs, though dedicated RS-485/Profibus cable with a controlled 120 Ω impedance is the correct choice for anything serious.
4. The firmware: it’s not just wiring
The DE/RE timing trap
Toggling DE at the wrong moment is one of the most common bugs in home-grown RS-485 firmware, and it is subtle because it only shows up as occasional garbled last bytes, not an outright failure.
The naive approach is to drop DE as soon as the last byte has been handed to the UART peripheral. On an STM32, that corresponds to watching the TXE flag (transmit data register empty): it goes high as soon as the data register’s content has been moved into the shift register, meaning the peripheral is ready to accept the next byte - but the current byte, including its stop bit, may still physically be shifting out onto the wire. Drop DE (and thus tri-state the transceiver) at that point, and you clip the tail end of the last character before it is actually on the bus.
The correct flag to wait for is TC (transmission complete), which only goes high once the stop bit of the last byte has actually left the shift register (confirmed on ST’s own community forum).
byte on the wire: [ start | D0..D7 | stop ]
TXE flag: ______/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ (high while stop bit still shifting out - too early!)
TC flag: ____________________/‾‾‾‾ (high only once the stop bit is gone)
DE should drop: ^ here, on TC - not on TXE
Tip
If you see intermittent framing errors or a truncated last character on the receiving end of an RS-485 link, and your own firmware toggles DE by hand, check which flag you switch on before looking anywhere else.
Why raw bytes aren’t enough: the case for a protocol layer
RS-485 gives you a shared, reasonably noise-resistant electrical bus. It says nothing about who is allowed to talk, whether the bytes that arrived are the bytes that were sent, or what to do if a device stops answering. Once more than two devices share a bus, or once the link runs through a genuinely noisy environment, you need at minimum:
- Addressing, so a message can target one specific device on a shared bus instead of being received by everyone.
- Error detection (a CRC), because a corrupted bit is a real possibility on a long, noisy line, and a wrong sensor reading silently accepted is worse than a rejected frame.
- A timeout, so a device that starts sending, then gets cut off by noise, does not leave every other device waiting forever for a frame that will never complete.
This is precisely the small set of problems Modbus RTU was designed to solve, which is why it became the de facto standard on top of RS-485 in industrial settings rather than something more elaborate. Its frame is almost deliberately minimal:
| address (1B) | function code (1B) | data (0-252B) | CRC16 (2B) | ≥3.5 char-time silence |
The CRC is a CRC-16 computed over the whole frame except itself, and the “end of frame” marker is not a special byte but simply silence: 3.5 character-times of nothing on the bus tells every listening device that the frame is over (full timing details here). At 9600 baud that is roughly 3.6 ms - short enough to not waste bus time, long enough to reliably distinguish “gap between bytes” from “end of frame”.
You do not have to use Modbus RTU specifically - plenty of custom protocols reinvent the same three ideas - but understanding why they exist saves you from re-discovering the same failure modes the hard way.
5. Feedback from the field: a vibration-test bench
During my first internship, my main assignment (an automated FPGA video test bench) was waiting on hardware to be delivered, so I picked up a side project: closing the loop on a vibration test bench used to qualify products under vibration testing.
The part of that project that actually exercises this article is narrow but concrete: a Raspberry Pi 3 B+, bolted directly onto the vibrating bench itself, needed to command the bench’s vibration motor. The motor’s control interface, per its datasheet, was RS-485 - not a choice I made, but exactly the kind of environment that makes the choice obvious once you’ve read sections 1 and 2. The Pi and its cable sat right next to the motor driving the bench, on a rig that is, by design, shaking itself and everything bolted to it - a single-ended link in that spot would have been fighting EMI from the motor’s drive electronics on top of a physically hostile, vibrating connection, precisely the “it works on the bench, not in the field” failure mode from section 1. The Raspberry Pi talked to the motor through a USB-to-RS-485 adapter rather than a raw MCU UART pin.
Info
Because I used an off-the-shelf USB-to-RS-485 adapter rather than bit-banging a transceiver myself, the DE/RE switching problem from section 4 was already handled by the adapter’s own driver - I never had to touch it. But knowing what that adapter is doing internally - a transceiver chip, termination and biasing at the ends of the run, direction control on the wire - is exactly what let me reason correctly about cable length and routing when the run ended up longer than expected, instead of treating the adapter as a black box.
The rest of that side project - reading the bench’s IMU over SPI with a Lua
script (using the lua-periphery library), estimating vibration frequency
with an FFT, and closing the loop with a basic PID controller, plus a second
Raspberry Pi streaming IMU data over UDP to a lab computer for graphing -
solved a control problem, not a signal-integrity one, so I won’t detail it
further here. RS-485 was the one piece of that chain where “just wire it up”
would not have been enough advice on its own: the motor’s datasheet said
RS-485, and everything in this article is the difference between plugging
that in blindly and actually understanding why it had to be that way.
6. Conclusion: a checklist for a healthy RS-485 link
If you are about to wire up an RS-485 link, or debug one that misbehaves outside the lab, this is roughly the order I’d check things in:
- Topology: daisy-chain, not star. Long stubs are unterminated dead ends waiting to reflect your signal back at you.
- Termination: exactly two 120 Ω resistors, one at each physical end of the bus - not one per node.
- Biasing: a single pull-up/pull-down pair somewhere on the bus, so the idle line settles to a known state instead of floating.
- Cable: twisted pair, ideally shielded and grounded at one end only, kept away from motor and power cabling where possible.
- Direction control: if you toggle DE/RE yourself, switch on TC (transmission complete), never on TXE.
- Protocol: addressing, a CRC, and a timeout - reuse Modbus RTU unless you have a specific reason not to.
None of this is exotic engineering. It is the accumulated answer to a single question - “what happens once this signal has to survive a real cable, in a real, electrically noisy room?” - and once you’ve internalized it, “just use RS-485” stops being a magic incantation and starts being a list of concrete, checkable requirements.
Further reading
- Nathan Ragot, “Art of UARTs, part 1” - UART fundamentals.
- Eve Gresse, “Device communication in embedded systems” - UART/I2C/SPI overview.
- Analog Devices, “Guide to Selecting and Using RS-232, RS-422, and RS-485”
- Analog Devices, “Guidelines for Proper Wiring of an RS-485 Network”
- MAX485E transceiver datasheet
- ST Community, on TXE vs TC during RS-485 direction switching
- Modbus organization, protocol reference
- innon.com, “Bias resistors and Termination resistors on an RS485 network”