RS485 is a very inexpensive way to wire devices together in a network. A single twisted pair (such as two wires from regular UTP network cable) at low speeds can operate over a kilometre.

Devices must be daisy-chained together, and long runs should be properly terminated typically with 120 ohm resistors at either end.

Connecting devices in star topology causes signal reflections and generally won’t work except for very short runs.

One solution is to use multiple MAX485 (or similar) transceivers to create multiple physical network segments. Only one UART is required unless you need concurrent requests.


This example allows for one transceiver to be in receive mode, with others in transmit. That means slave addresses must be unique across both segments. Resistors R2 and R8 are current limit resistors to guard against both transceivers being switched into receive mode at the same time.

The logic for controlling this would be in a callback registered via IO::RS485::Controller::onSetDirection().

For completeness, here’s example connections for a 3.3v transceiver:


R22 is optional, typically installed only on longer runs. D3 is additional transient protection - always a good idea as a first line of defence even if the transceiver itself has some built in.

namespace IO::RS485


constexpr unsigned DEFAULT_BAUDRATE = 9600
class Controller : public IO::Controller
#include <Controller.h>

Public Types

using SetDirectionCallback = void (*)(uint8_t segment, Direction direction)

Callback to handle hardware transmit/receive selection Typically called from interrupt context so implementation MUST be marked IRAM_ATTR.

  • segment – Identifies physical connection for shared/multiplexed port

  • direction

Public Functions

inline virtual const FlashString &classname() const override

Get the class name for this Controller.

virtual void start() override

Start the controller.

Controllers may now initiate communications with registered devices. Typically they’ll query device status, etc.

virtual void stop() override

Stop all controllers.

This method is called by the Device Manager, applications shouldn’t need it.


MUST call canStop() first to ensure it’s safe to stop!

inline void onSetDirection(SetDirectionCallback callback)

Set the transmit callback handler.

Typically with RS485 a GPIO is used to toggle between transmit/receive modes. Using a callback allows flexibility for your particular hardware implementation. You don’t need to set this up if your hardware handles the switch automatically.



inline void setDirection(IO::Direction direction)

Whilst a port is acquired, call this method to being or end transmission.


Port should normally be left in receive mode on request completion.



virtual void handleEvent(Request *request, Event event) override

Implementations override this method to process events as they pass through the stack.

class Device : public IO::Device
#include <Device.h>

Base device class for communicating with an RS485 slave.

Subclassed by IO::DMX512::Device, IO::Modbus::Device

Public Functions

inline virtual uint16_t address() const override

Devices with a numeric address should implement this method.

virtual void handleEvent(IO::Request *request, Event event) override

Implementations may override this method to customise event handling.

struct Config
#include <Device.h>

RS485 configuration.

struct Slave
#include <Device.h>

Public Members

uint16_t address

Network device address or ‘slave ID’

uint8_t segment

Application-defined value for multiplexed serial ports. For example, several MAX485 transceivers can be connected to one serial port with appropriate multiplexing logic.

See IO::RS485::Controller::SetDirectionCallback

unsigned baudrate

Serial link speed

unsigned timeout

Max time between command/response in milliseconds.