RS-485 and RS-232 — The Serial Protocols That Still Run the Industrial World¶
Prerequisites: Communication Interface Overview · CAN Bus Next: EV Nodes — ECU Architecture
Before CAN, Before USB¶
CAN was invented in 1983. USB arrived in 1996. Ethernet predates both, but its early variants were bulky, slow, and completely unsuitable for noisy industrial environments.
Before any of these, machines communicated over serial links. A serial link is the simplest possible communication channel: data is sent one bit at a time over a single path, framed into bytes, timed to a pre-agreed clock rate. No shared clock wire, no elaborate protocol. Just bits, in order, at the right speed.
Two standards emerged from this era and never really left: RS-232 (1960) and RS-485 (1983). Decades later, both are alive in BMS systems, EV chargers, energy storage racks, battery test equipment, and the debug UART header on virtually every microcontroller ever manufactured. Understanding them is less about nostalgia than about reading reality: the systems you will integrate a BMS with almost certainly include one of these interfaces.
The Shared Foundation — UART¶
Before getting into the electrical differences between RS-232 and RS-485, it is worth understanding what both of them are actually carrying: UART (Universal Asynchronous Receiver/Transmitter) data.
UART defines how bytes are packaged into a bitstream. Every UART frame looks the same:
Idle (mark) → Start bit (space) → D0 → D1 → D2 → D3 → D4 → D5 → D6 → D7 → Stop bit(s) (mark) → Idle
A few key points about this frame:
Asynchronous means there is no shared clock signal between sender and receiver. Both sides agree on a baud rate (bits per second) in advance — 9600, 19200, 57600, or 115200 are the common choices in BMS work. The receiver locks onto the falling edge of the start bit and samples each subsequent bit at intervals of 1/baud_rate seconds. If the receiver's crystal is more than a fraction of a percent off from the transmitter's, the last bits in the frame will be sampled at the wrong time — which is why UART is limited to 8 data bits per frame rather than longer payloads.
The start bit is always a space (logic 0, or low). The idle line sits at mark (logic 1, or high). The transition from idle to start bit gives the receiver its synchronization edge.
The stop bit returns the line to idle (mark). Without it, the next frame's start bit would not have a recognizable falling edge to trigger on.
RS-232 and RS-485 do not change this frame structure at all. They only define the electrical signaling that carries the UART bits across a physical cable — the voltage levels, the cable characteristics, the maximum distance. They are physical layer specifications, not protocol specifications.
RS-232 — The Original Serial Port¶
Full name: EIA/TIA-232-F. First published in 1960. Updated to RS-232C in 1969. Widely used until the mid-2000s when USB displaced it on consumer hardware; still present in industrial and embedded contexts.
Voltage Levels¶
RS-232 uses some of the most counterintuitive voltage levels in common use (per TIA/EIA-232-F):
- Logic 1 (Mark, Idle): −3 V to −15 V. Negative voltage is logic high.
- Logic 0 (Space): +3 V to +15 V. Positive voltage is logic low.
- Undefined zone: −3 V to +3 V — the receiver cannot reliably determine the state.
This inversion (negative = 1, positive = 0) and the large voltage swing trace back to teletype and modem compatibility requirements from the 1960s. The large swing was chosen for noise immunity at a time when single-ended analog signals were the only option and long telephone lines carried significant interference.
Modern UART peripherals in microcontrollers (STM32, ESP32, AVR, etc.) operate at TTL or CMOS levels: 3.3 V or 5 V for logic 1, 0 V for logic 0. This is sometimes loosely called "RS-232" by engineers who mean "serial UART" but it is electrically very different — do not connect a 3.3 V UART pin directly to a genuine RS-232 port.
Single-Ended Signaling — The Fundamental Limitation¶
RS-232 is single-ended: every signal is measured relative to a shared ground wire. The transmitter drives TX relative to its local ground. The receiver measures RX relative to its local ground. If the two grounds are not at exactly the same potential — which they rarely are across any meaningful cable distance — the voltage at the receiver's input is shifted by the ground difference.
In a clean office environment, a short RS-232 cable between two devices at the same desk works fine. In an EV powertrain environment — a kW-scale motor drive switching at 10 kHz, a 400 V HV bus nearby, contactors opening and closing — the ground potential can shift by volts in microseconds. That noise adds directly to the signal. A 200 mV noise spike on a 3 V signal (RS-232's minimum margin) will cause bit errors.
Speed and Distance¶
TIA/EIA-232-F specifies a maximum of 20 kbps at up to 15 metres as a conservative baseline. In practice, slower baud rates extend the usable distance because the capacitance of the cable has more time to charge and discharge between bits. A 9600 bps link can often work reliably at 30–50 metres in a low-noise environment, though actual limits depend on cable capacitance and load.
But the distance limitation is not merely about cable length — it is about electrical environment. A 3-metre RS-232 cable next to a VFD (variable frequency drive) will fail faster than a 30-metre cable in a clean control cabinet.
The DB-9 Connector¶
The classic 9-pin D-sub connector (DE-9, universally called DB-9) is the RS-232 physical interface most engineers recognize. Its nine pins include TX (transmit data), RX (receive data), GND, and handshaking signals: RTS (request to send), CTS (clear to send), DTR (data terminal ready), and DSR (data set ready). In most modern embedded applications, only TX, RX, and GND are used — hardware handshaking is handled in firmware or omitted.
Where RS-232 Appears in BMS Work¶
Debug UART: every BMS microcontroller has at least one UART peripheral. Engineers connect a USB-to-UART adapter (FTDI FT232RL, CH340G, CP2102) to the BMS debug header and use a serial terminal (PuTTY, minicom, the Arduino IDE serial monitor) to read real-time log output — cell voltages, SOC estimate, fault flags, state machine transitions. This is TTL-level UART, not true RS-232, but it uses the same UART frame structure.
Bootloader programming: STM32, PIC, and AVR microcontrollers all support UART bootloaders activated by a BOOT pin state at reset. You can reflash BMS firmware over the same debug UART without a JTAG debugger, which is useful in the field.
Legacy chargers: some older AC charging stations and industrial battery chargers use RS-232 for configuration, monitoring, and setpoint control. Integrating a modern BMS with legacy EVSE equipment sometimes means implementing an RS-232 interface.
AT commands: BMS modules with integrated GSM or 4G telemetry modems often expose an AT command interface over UART for configuration and status queries — another RS-232-era convention still in production use.
Why RS-232 Is Not Enough — Four Problems¶
Understanding RS-232's limitations makes RS-485 an obvious solution rather than an arbitrary choice.
Problem 1 — Noise immunity: Single-ended signaling adds common-mode noise directly to the signal. An EV powertrain generates exactly this kind of noise.
Problem 2 — Point-to-point only: RS-232 supports exactly two devices. One transmitter, one receiver. You cannot build a network. A rack of 16 battery modules all reporting to a central controller requires 16 separate RS-232 links — 16 pairs of cables, 16 serial ports, 16 sets of buffers.
Problem 3 — Distance: 15 metres is too short for many industrial battery systems. A grid-connected BESS (Battery Energy Storage System) may have cabinets spread across 50–100 metres of floor space.
Problem 4 — Speed ceiling: 20 kbps (specified) or 115,200 bps (practical with TTL UART) is slow by modern standards.
RS-485 addresses all four with a single architectural change: differential signaling.
RS-485 — The Industrial Serial Standard¶
Full name: EIA/TIA-485-A. Published in 1983 — the same year as CAN, and for similar reasons. Unlike CAN, RS-485 never became dominant in automotive but became deeply embedded in industrial, energy, and infrastructure applications. It is the physical layer standard of choice for Modbus, the dominant industrial communication protocol.
Differential Signaling¶
RS-485 uses two wires per channel, typically called A and B (sometimes labeled + and −, or D+ and D−). The signal is the voltage difference between A and B, not the absolute voltage of either wire.
- Mark (logic 1): A is more positive than B by at least 200 mV. The minimum specified differential threshold is ±200 mV per TIA/EIA-485-A (also documented in Analog Devices AN-960); typical drivers deliver ±1.5 V to ±5 V.
- Space (logic 0): B is more positive than A by at least 200 mV.
- Undefined: the differential is less than ±200 mV. This is the dead band where the receiver should not make a decision.
The noise immunity comes directly from differential measurement. Electromagnetic interference couples into both A and B wires equally — it is a common-mode disturbance. When the receiver subtracts B from A, the common-mode noise cancels out. A 5 V noise spike that lands equally on both wires leaves the differential signal unchanged. Only noise that couples differently onto A than B (differential-mode noise) affects the signal — and for a twisted pair running parallel through space, differential coupling is much lower than common-mode coupling.
This is the same principle used in CAN and in audio balanced interconnects. It works because of physics, not clever software.
Speed and Distance¶
RS-485 is not a single operating point — it trades speed against distance, and the product of the two is roughly constant. The following are commonly cited design guidelines (consistent with TI application report SLLA272 and vendor design guides); exact limits depend on cable type, load, and termination:
- At 10 Mbps: practical distance of a few metres.
- At 1 Mbps: approximately 50–100 metres.
- At 100 kbps: approximately 1,200 metres.
- At 9,600 bps: over 1 km.
For a battery rack system, 100 kbps at 1,200 metres is more than adequate. For BMS-to-charger communication in a datacenter UPS room, 9,600 bps at many hundreds of metres is a common configuration.
Multi-Drop Bus¶
RS-485 supports up to 32 standard unit loads on a single pair of wires, per TIA/EIA-485-A. A "unit load" is defined in terms of the input impedance of the receiver (approximately 12 kΩ). High-impedance receiver variants (1/4, 1/8, or 1/10 unit load) allow 128 or 256 devices on one bus.
This is the critical capability for battery systems: one master controller can poll 16, 32, or more battery modules over a single twisted pair, with a single cable run from one end of the rack to the other.
Half-Duplex and Full-Duplex¶
2-wire RS-485 (half-duplex): A single A/B pair carries both transmit and receive data. Because both directions share the same wires, only one node may transmit at a time. Before transmitting, a node asserts its driver enable (DE) pin; after transmitting, it de-asserts DE to return to high-impedance and allow other nodes to drive the bus. This requires software coordination — which is exactly what Modbus RTU provides.
4-wire RS-485 (full-duplex): Two separate A/B pairs — one for TX, one for RX. The master has dedicated transmit and receive pairs; slaves connect their TX to the master's RX pair and vice versa. Simultaneous full-duplex operation is possible. Uses more wires but simplifies direction control logic.
Most BMS and energy storage applications use 2-wire half-duplex with Modbus RTU, because the cable savings outweigh the complexity of direction control.
Termination and Biasing¶
Two passive component requirements trip up almost every first RS-485 implementation.
Termination¶
Place a 120 Ω resistor at each physical end of the RS-485 cable. The same principle as CAN: the termination matches the characteristic impedance of the twisted pair (~120 Ω), absorbing signal energy at the endpoints and preventing reflections from corrupting subsequent bits.
At low baud rates (9,600 bps) over short distances (a few metres), termination is often omitted in practice and the system works anyway because the bit period is long enough that reflections die out before the sampling point. At higher baud rates or longer cables, missing termination produces intermittent errors that appear only at certain data patterns — one of the more frustrating debugging experiences in embedded systems.
Fail-Safe Biasing¶
When all drivers are in high-impedance (tri-state) mode — during bus idle, or when a node has released the bus after transmitting — the RS-485 bus floats. Neither A nor B is being driven. Depending on termination resistor values and cable characteristics, the differential voltage may settle anywhere within the ±200 mV undefined zone.
A receiver seeing an undefined bus state may output random data. If that garbage lands on a pattern that looks like a valid UART start bit, the microcontroller's UART peripheral will try to receive a phantom byte. This produces framing errors, phantom characters in the receive buffer, and confused state machines — all from an otherwise idle bus.
The solution is fail-safe biasing:
- A pull-up resistor (typically 560 Ω to 1 kΩ) on the A line to Vcc
- A pull-down resistor of the same value on the B line to GND
This ensures that when all drivers are inactive, A is pulled high relative to B — a defined Mark state. The receiver sees a stable idle bus and does not generate spurious data.
Missing fail-safe biasing is the single most common RS-485 implementation mistake. It is also the hardest to debug, because the symptom appears only when no node is transmitting.
Modbus RTU — The Protocol That Makes RS-485 Useful¶
RS-485 is a physical layer. It defines voltage levels and electrical characteristics — nothing about message format, addressing, or error checking. A protocol on top is needed to make the multi-drop bus actually work.
Modbus is the protocol. Published in 1979 by Modicon, now maintained as an open standard (modbus.org). Modbus RTU (Remote Terminal Unit) is the variant that runs over RS-485. It is simple, robust, royalty-free, and supported by virtually every industrial device, energy management system, and battery test instrument on the market.
Frame Structure¶
Every Modbus RTU transaction consists of one request (master to slave) and one response (slave to master):
| Address | Function Code | Data | CRC (16-bit) |
|---------|---------------|-------------|--------------|
| 1 byte | 1 byte | variable | 2 bytes |
Address (1 byte): identifies which slave should respond. Valid slave addresses are 1–247. Address 0 is broadcast — all slaves receive and act but none respond (used for synchronized setpoints). The master has no address field in its request because only the master initiates transactions.
Function Code (1 byte): specifies the operation.
- 0x03: Read Holding Registers — read one or more 16-bit registers from the slave
- 0x06: Write Single Register — write one 16-bit register
- 0x10: Write Multiple Registers — write a block of registers
- 0x01: Read Coils — read binary output states (on/off flags)
Data (variable): for a Read Holding Registers request, the data contains the starting register address (2 bytes) and the number of registers to read (2 bytes). For the response, it contains the register count and the register values.
CRC (2 bytes): CRC-16/Modbus calculated over address + function code + data. The receiver computes the same CRC and compares — a mismatch discards the frame silently. Unlike CAN, Modbus does not have hardware error frames; error handling is the master's responsibility (retry on no response or CRC error).
Register Map¶
Modbus registers are 16-bit values at addresses 0x0000–0xFFFF. A BMS implementing a Modbus slave maps its parameters to register addresses. For example:
| Register | Description | Scaling |
|---|---|---|
| 0x0000 | State of Charge | × 10 (750 = 75.0%) |
| 0x0001 | Pack Voltage | × 10 (3900 = 390.0 V) |
| 0x0002 | Pack Current | signed, × 10 (−1500 = −150.0 A, negative = discharge) |
| 0x0003 | Max Discharge Current | × 1 (200 = 200 A) |
| 0x0004 | Max Charge Current | × 1 (100 = 100 A) |
| 0x0005 | Cell Temperature Max | × 10 (350 = 35.0 °C) |
| 0x0006 | Fault Flags (bitmask) | bit 0 = OV, bit 1 = UV, bit 2 = OT... |
The master — typically an EMS, SCADA system, or a monitoring computer — polls these registers at whatever interval it needs. The slave responds and goes silent; it never initiates communication.
Inter-Frame Gap¶
Modbus defines the end of a frame by silence: a gap of at least 3.5 character times (one character = 10 or 11 bit periods) with no bus activity marks the end of the frame. Both the transmitter and receiver use this silence to detect frame boundaries — there is no explicit length field in the frame. At 9,600 bps, 3.5 characters = 3.65 ms. At 115,200 bps, 0.3 ms.
This is why Modbus RTU implementations must disable byte-level UART receive interrupts and use framing timeouts instead — the 3.5-character silence detector is what identifies a complete, valid frame.
Direction Control in Half-Duplex RS-485¶
Half-duplex RS-485 requires explicit direction control at the transceiver. The transceiver IC (MAX485, MAX3485, SN65HVD485, or similar) has two control pins:
- DE (Driver Enable): assert high to enable the transmit driver. The IC drives A and B based on the TX input.
- RE̅ (Receiver Enable, active-low): assert low to enable the receiver. The IC outputs received data on RO.
When the node is idle (listening): DE = 0, RE̅ = 0. The driver is tri-stated; the receiver is active. When the node is transmitting: DE = 1, RE̅ = 1. The driver is active; the receiver is disabled (its input is the same bus it is driving, so the received data would just be the echo of its own transmission — ignoring it avoids confusion).
In a microcontroller: one GPIO pin tied to both DE and RE̅ (with RE̅ inverted through the connection — since DE = high enables driver and RE̅ = high disables receiver, tying them together means both happen simultaneously when the GPIO is high).
The sequence for a Modbus master transmit:
- Assert GPIO high (DE = 1, RE̅ = 1)
- Write bytes to UART transmit register
- Wait for UART transmit complete interrupt (all bits shifted out, shift register empty)
- De-assert GPIO (DE = 0, RE̅ = 0)
- Enable UART receive, start inter-frame gap timer
The most common RS-485 software bug is releasing the DE line before the last byte has been fully shifted out. The UART "transmit complete" flag can mean different things on different microcontrollers: some flag when the data register is empty (but the shift register still has bits to send), others flag when the shift register is empty. Releasing DE while the last bits are still on the bus leaves them undriven — the bus floats, termination pulls it to an ambiguous state, and the last character is corrupted. Always use the "transmit shift register empty" condition, not "transmit data register empty."
RS-485 in Battery Systems and EV Applications¶
Battery Rack Communication¶
In a stationary BESS — grid-connected storage used for frequency regulation, UPS, or solar shifting — the architecture is typically: one rack BMS (master) connected to 8–16 module BMSs (slaves) over a single RS-485 Modbus link. The rack BMS polls each module for SOC, voltage, temperature, and fault status, aggregates the data, and reports upstream to the EMS or SCADA system.
RS-485 is chosen here over CAN for several reasons: the cable distances within a rack can exceed CAN's practical limits; Modbus is universally understood by EMS vendors and battery test equipment; and the polling model gives the rack BMS complete control over bus timing, which simplifies scheduling.
EV Charger Communication¶
DC fast chargers (and some AC chargers) use RS-485 Modbus for energy metering, remote configuration, start/stop control, and status reporting. A charging station operator connects a central management system to multiple charger units over a single RS-485 bus running through the site — potentially hundreds of metres between the control room and the farthest charger.
BMS to EMS in Grid Storage¶
Grid-scale energy storage systems integrate with EMS platforms from vendors like Schneider, ABB, and Siemens. Nearly all of these platforms include Modbus RTU as a communication option. A BMS that speaks Modbus can integrate directly into an existing energy management infrastructure without a protocol gateway — a significant integration cost reduction.
Battery Test Equipment¶
Cell formation systems, cyclers, and impedance analyzers (Maccor, Bitrode, Neware, Gamry) use RS-485 for instrument control and data logging. BMS teams developing cell characterization data for OCV-SOC tables and ECM parameter fitting will encounter RS-485 in the lab.
Legacy Interoperability¶
RS-485 is so entrenched that BMS designs for industrial and stationary applications routinely include it alongside CAN, even when CAN is the primary vehicle bus. The RS-485 port is for EMS integration, service tools, and third-party monitoring systems — all of which expect Modbus RTU.
RS-232 vs RS-485 vs CAN — A Comparison¶
| Property | RS-232 | RS-485 | CAN |
|---|---|---|---|
| Signaling | Single-ended | Differential | Differential |
| Max specified speed | 20 kbps | 10 Mbps | 1 Mbps (8 Mbps CAN FD) |
| Practical BMS speed | 115,200 bps | 9,600–115,200 bps | 250 kbps–1 Mbps |
| Max distance (at typical speed) | 15 m at 20 kbps | 1,200 m at 100 kbps | 500 m at 125 kbps |
| Nodes | 2 (point-to-point) | 32–256 | 110 |
| Duplex | Full | Half (2-wire) or Full (4-wire) | Half |
| Collision handling | None | Master-slave (Modbus) | CSMA/CR arbitration |
| Error detection | None (optional parity only) | CRC-16 (Modbus frame) | 5 mechanisms + error frames |
| Typical BMS use | Debug/logging UART | Module polling, charger/EMS comms | Vehicle CAN bus |
| Hardware cost | Very low | Low | Low–Medium |
The table shows the intended use cases: RS-232 for point-to-point debug and programming, RS-485 for industrial networks where distance and node count matter, CAN for in-vehicle real-time control where latency, error detection, and arbitration are critical.
Experiment 1 — UART Debug Output from Arduino¶
Materials: Arduino Uno or Nano, USB cable, serial monitor (Arduino IDE or PuTTY).
What to build: A simple sketch that outputs simulated BMS telemetry every 500 ms at 115,200 bps:
SOC: 78.2% | Vpack: 388.4V | I: -42.1A | T_max: 31°C | Faults: NONE
SOC: 77.9% | Vpack: 388.1V | I: -42.1A | T_max: 31°C | Faults: NONE
Add a command parser: if "READ SOC\n" is received over serial, respond with just the SOC value. This simulates a simple ASCII-protocol BMS debug interface.
What to observe: Open the serial monitor and watch the data stream. If you have a logic analyzer, probe the TX pin and measure the bit timing — at 115,200 bps, each bit is 8.68 µs. Verify the UART frame: start bit low, 8 data bits, stop bit high. This is the foundation that both RS-232 and RS-485 carry.
Experiment 2 — RS-485 Half-Duplex Bus Between Two Arduinos¶
Materials: 2× Arduino Uno, 2× MAX485 breakout module, 1× 120 Ω resistor (termination), 3 wires (A, B, GND).
Wiring (per Arduino):
- Arduino TX → MAX485 DI
- Arduino RX ← MAX485 RO
- Arduino GPIO (e.g., D4) → MAX485 DE and RE̅ (tie both together via inverter, or invert in code)
- MAX485 A and B → shared bus wires
Connect a 120 Ω resistor across A and B at each end of the cable. Keep the cable short (1 metre) for this first experiment.
Node A (master): every 500 ms, assert DE, send "REQ:NODE2\n", de-assert DE, listen for 200 ms.
Node B (slave): listen continuously. When "REQ:NODE2" is received, assert DE, respond "SOC=75\n", de-assert DE.
What to observe: The master should print the slave's response to its serial monitor. Add an oscilloscope or logic analyzer to the A/B lines and observe the differential voltage: approximately ±1.5 V when the driver is active, floating to near-zero when idle. Extend the cable to 10 metres and verify the link still works. Compare this to what would happen with a single-ended UART connection over 10 metres near a motor drive.
Experiment 3 — Modbus RTU Slave on Arduino¶
Materials: Same RS-485 setup, PC with a USB-to-RS-485 adapter (CP2102 breakout + MAX485, or a dedicated USB RS-485 stick), QModMaster software (free, Windows/Linux).
What to build: An Arduino sketch implementing a minimal Modbus RTU slave at address 0x01. Support Function Code 0x03 (Read Holding Registers). Define four registers:
| Register 0x0000 | SOC × 10 — starts at 780, decrements by 1 each second | | Register 0x0001 | Pack voltage × 10 — fixed at 3900 (390.0 V) | | Register 0x0002 | Current × 10 — signed, fixed at −420 (−42.0 A) | | Register 0x0003 | Fault flags — 0x0000 initially, set bit 0 on a pushbutton press |
What to observe: Open QModMaster, configure the COM port at 9,600 bps, 8N1, slave address 1. Set it to continuously poll registers 0–3. Watch the SOC register decrement in real time. Press the fault button and watch bit 0 appear in register 3. This is a real Modbus RTU transaction: address byte, function code, start address, register count, response data, CRC — the exact same structure used in a rack BMS communicating with an EMS.
Further Reading¶
Standards
- EIA/TIA-232-F — the RS-232 standard (final revision)
- EIA/TIA-485-A — the RS-485 standard
- Modbus.org — "Modbus Application Protocol Specification V1.1b3" — the definitive Modbus reference, free PDF
Application Notes
- TI SLLA272 — "RS-485/RS-422 Circuit Implementation Guide" — comprehensive RS-485 design guide covering termination, biasing, driver selection, and common mistakes
- TI SLYT054 — "RS-485: Simple, Inexpensive, and Versatile"
- Maxim (Analog Devices) Application Note 3884 — UART framing and clock accuracy requirements
Online References
- CSS Electronics — "Modbus RTU Explained" — excellent illustrated guide
- Lammert Bies (lammertbies.nl) — technical RS-232 and RS-485 reference pages with timing diagrams
Books
- Axelson, J. — Serial Port Complete (2nd ed., Lakeview Research, 2007) — RS-232, RS-485, and USB in practical embedded context