Practical case: RS485 Relay Node with Arduino UNO

Practical case: RS485 Relay Node with Arduino UNO — hero

Objective and use case

What you’ll build: A robust, addressable remote relay node using an Arduino UNO that communicates over a long-distance RS485 bus to toggle a 5V relay based on targeted serial commands.

Why it matters / Use cases

  • Agricultural Automation: Control irrigation valves or greenhouse ventilation fans hundreds of meters away where standard 3.3V UART, I2C, or SPI signals would degrade.
  • Industrial Alert Systems: Trigger visual beacons across noisy factory floors. RS485 differential signaling provides high immunity to electromagnetic interference (EMI) from heavy machinery.
  • Wired Home Automation: Create a deterministic, hardwired network of distributed switches that guarantees sub-10ms response times without relying on Wi-Fi coverage.
  • Scalable Multi-drop Networks: Connect up to 32 addressable nodes on a single twisted pair of wires, ensuring each node only reacts to commands matching its specific ID.

Expected outcome

  • The Arduino successfully interprets addressed serial commands and toggles the 5V relay with near-zero latency.
  • Error-free communication is maintained over long wire runs (up to 1,200 meters at 9600 baud).
  • The node correctly filters bus traffic, ignoring commands intended for other devices on the shared network.

Audience: Embedded developers and automation engineers; Level: Intermediate

Architecture/flow: Master Controller → RS485 Bus (Twisted Pair) → MAX485 Transceiver → Arduino UNO (UART) → GPIO → 5V Relay Module

Educational validation note

Before publication, this case passed the Prometeo automated validation gate with status PASS. The validator checked the code blocks, article structure, copy/paste-safe commands and consistency with the supported device catalog.

Published validation evidence

  • Automatic result: PASS.
  • Parsed structure: 3 sections, 3 tables and 2 code blocks detected before publication.
  • Checked code: 1 Python/py_compile, 1 Arduino/arduino-cli compile.
  • Supported catalog: the article text was checked against Prometeo’s validation-capable device profiles, and unsupported stacks block publication.
  • Report findings: no blocking findings.

This validation confirms syntax and tool compatibility for the published material, but it does not replace physical testing on your exact hardware, wiring and runtime environment.

Educational safety note

This project is a low-voltage educational prototype, not a certified product. Before powering the setup, verify the wiring of your Arduino UNO R3, avoid shorting 5 V, GND or digital pins, disconnect power before changing connections, and use proper interface modules for relays, motors or external loads.

Conceptual block diagram

High-level view: what enters the system, what each block processes, and what comes out.

Functional architecture

Master Controller

RS485 Bus (Twisted Pair)

MAX485 Transceiver

Arduino UNO (UART)

GPIO

5V Relay Module

Conceptual signal and responsibility flow between device blocks.

Validation path

Sketch

arduino-cli compile

Upload

Functional test

Conceptual summary of the tools used to check the published material.

Prerequisites

  • Familiarity with basic terminal operations and command-line interfaces.
  • Understanding of standard UART (Serial) communication (Baud rate, TX/RX).
  • Arduino CLI installed and added to your system’s PATH.
  • Python 3.11 installed (for running the master validation script).
  • The pyserial library installed in your Python environment (pip install pyserial).

Materials

  • Microcontroller: Arduino UNO R3 (ATmega328P).
  • Transceiver: MAX485 RS485 module (standard 8-pin breakout board with DI, DE, RE, RO pins).
  • Actuator: 5 V relay module (opto-isolated, active-high or active-low).
  • Master Interface: USB-to-RS485 adapter (to connect your PC to the RS485 bus for testing).
  • Passive Components: 1x 120-ohm resistor (for bus termination, recommended for long cable runs).
  • Cabling: Jumper wires, breadboard, and a twisted pair cable for the A/B RS485 lines.

Setup/Connection

RS485 is a half-duplex standard, meaning data travels in both directions, but only one direction at a time. The MAX485 chip uses Driver Enable (DE) and Receiver Enable (RE) pins to switch between transmitting and receiving. We will tie these two pins together and control them with a single digital pin on the Arduino.

We will use SoftwareSerial on pins 10 and 11 to communicate with the MAX485. This preserves the Arduino’s hardware serial port (pins 0 and 1) for USB debugging and uploading code without needing to disconnect the wiring.

Arduino to MAX485 Module Wiring

MAX485 PinArduino UNO PinFunction / Description
VCC5VPower supply for the transceiver.
GNDGNDCommon ground reference.
RO (Receiver Out)Digital Pin 10Connects to Arduino Software RX.
RE (Receiver Enable)Digital Pin 2Active LOW. Tied to DE.
DE (Driver Enable)Digital Pin 2Active HIGH. Tied to RE.
DI (Driver In)Digital Pin 11Connects to Arduino Software TX.
AUSB-RS485 ‘A’Non-inverting RS485 bus line.
BUSB-RS485 ‘B’Inverting RS485 bus line.

Note: Ensure the ground of the USB-to-RS485 adapter is connected to the Arduino’s ground to maintain a common reference voltage, especially for short-distance bench testing.

Arduino to 5V Relay Module Wiring

Relay Module PinArduino UNO PinFunction / Description
VCC / DC+5VPower supply for the relay coil and optocoupler.
GND / DC-GNDCommon ground reference.
IN / SignalDigital Pin 7Control signal to trigger the relay.

Validated Code

The following sections contain the complete source code for both the Arduino node and the Python master script used to validate the bus.

Arduino Node Firmware

Create a directory named rs485_relay_node and save the following code as rs485_relay_node.ino inside it.

Public preview of the validated file. The complete source is shown to members and in PDF/Print.

/*
 * rs485_relay_node.ino
 * 
 * Implements a half-duplex RS485 slave node.
 * Listens for "<NODE_ID>:<COMMAND>\n".
 * Valid commands: "ON", "OFF".
 */

#include <SoftwareSerial.h>

// Pin Definitions
#define RE_DE_PIN 2     // HIGH = Transmit, LOW = Receive
#define RELAY_PIN 7     // Relay control pin
#define RX_PIN 10       // SoftwareSerial RX
#define TX_PIN 11       // SoftwareSerial TX

// Node Configuration
const String NODE_ID = "N1";
const long BAUD_RATE = 9600;

// Initialize SoftwareSerial for RS485 communication
SoftwareSerial rs485(RX_PIN, TX_PIN);

void setup() {
  // Configure RS485 control pins
  pinMode(RE_DE_PIN, OUTPUT);
  digitalWrite(RE_DE_PIN, LOW); // Default to listen mode

  // Configure Relay pin
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Default relay state (adjust if active-low)

  // Initialize Serial ports
  Serial.begin(BAUD_RATE); // Hardware serial for debugging
  rs485.begin(BAUD_RATE);  // Software serial for RS485 bus

  Serial.println("System Boot: RS485 Relay Node");
  Serial.println("Node ID: " + NODE_ID);
  Serial.println("Listening for commands...");
}

void loop() {
  // Check if data is available on the RS485 bus
  if (rs485.available()) {
    // Read the incoming frame until a newline character
    String incomingMsg = rs485.readStringUntil('\n');
    incomingMsg.trim(); // Remove carriage returns or trailing spaces

    if (incomingMsg.length() > 0) {
      Serial.println("Bus traffic detected: " + incomingMsg);
      processCommand(incomingMsg);
    }
// ...

🔒 This content is premium. With the monthly subscription (7-day free trial) you can unlock the full didactic material and the print-ready PDF pack.🔓 Unlock it — 7-day free trial
/*
 * rs485_relay_node.ino
 * 
 * Implements a half-duplex RS485 slave node.
 * Listens for "<NODE_ID>:<COMMAND>\n".
 * Valid commands: "ON", "OFF".
 */

#include <SoftwareSerial.h>

// Pin Definitions
#define RE_DE_PIN 2     // HIGH = Transmit, LOW = Receive
#define RELAY_PIN 7     // Relay control pin
#define RX_PIN 10       // SoftwareSerial RX
#define TX_PIN 11       // SoftwareSerial TX

// Node Configuration
const String NODE_ID = "N1";
const long BAUD_RATE = 9600;

// Initialize SoftwareSerial for RS485 communication
SoftwareSerial rs485(RX_PIN, TX_PIN);

void setup() {
  // Configure RS485 control pins
  pinMode(RE_DE_PIN, OUTPUT);
  digitalWrite(RE_DE_PIN, LOW); // Default to listen mode

  // Configure Relay pin
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW); // Default relay state (adjust if active-low)

  // Initialize Serial ports
  Serial.begin(BAUD_RATE); // Hardware serial for debugging
  rs485.begin(BAUD_RATE);  // Software serial for RS485 bus

  Serial.println("System Boot: RS485 Relay Node");
  Serial.println("Node ID: " + NODE_ID);
  Serial.println("Listening for commands...");
}

void loop() {
  // Check if data is available on the RS485 bus
  if (rs485.available()) {
    // Read the incoming frame until a newline character
    String incomingMsg = rs485.readStringUntil('\n');
    incomingMsg.trim(); // Remove carriage returns or trailing spaces

    if (incomingMsg.length() > 0) {
      Serial.println("Bus traffic detected: " + incomingMsg);
      processCommand(incomingMsg);
    }
  }
}

void processCommand(String msg) {
  // Check if the message is addressed to this specific node
  if (msg.startsWith(NODE_ID + ":")) {
    // Extract the command portion after the colon
    int separatorIndex = msg.indexOf(':');
    String action = msg.substring(separatorIndex + 1);

    if (action == "ON") {
      digitalWrite(RELAY_PIN, HIGH);
      Serial.println("Action: Relay turned ON");
      sendAcknowledgment("ACK_ON");
    } 
    else if (action == "OFF") {
      digitalWrite(RELAY_PIN, LOW);
      Serial.println("Action: Relay turned OFF");
      sendAcknowledgment("ACK_OFF");
    }
    else {
      Serial.println("Warning: Unknown command received.");
    }
  } else {
    // Message is for another node or malformed; ignore quietly
    Serial.println("Ignored: Addressed to another node.");
  }
}

void sendAcknowledgment(String ackMsg) {
  // 1. Switch MAX485 to Transmit mode
  digitalWrite(RE_DE_PIN, HIGH);

  // 2. Allow a brief moment for the transceiver to stabilize
  delay(5); 

  // 3. Transmit the acknowledgment frame
  String fullAck = NODE_ID + ":" + ackMsg;
  rs485.println(fullAck);

  // 4. Wait for the serial transmission buffer to empty completely
  // This is critical. If we pull RE_DE low too early, the transmission is cut off.
  rs485.flush(); 

  // 5. Allow the final bit to hit the wire before switching states
  delay(5);

  // 6. Switch MAX485 back to Receive mode
  digitalWrite(RE_DE_PIN, LOW);

  Serial.println("Transmitted: " + fullAck);
}

Python Master Test Script

Save the following code as master_controller.py on your PC. This script acts as the central controller, generating RS485 traffic to test the Arduino node.

Public preview of the validated file. The complete source is shown to members and in PDF/Print.

#!/usr/bin/env python3
"""
master_controller.py
Validates the Arduino RS485 Relay Node by sending specific string commands
over a USB-to-RS485 adapter and waiting for acknowledgments.
"""

import serial
import time
import sys

# Windows: 'COM3', Linux: '/dev/ttyUSB0', macOS: '/dev/cu.usbserial-...'
PORT = '/dev/ttyUSB0' 
BAUD = 9600
TIMEOUT = 2.0

def send_and_wait(ser, node_id, command):
    """Sends a formatted command and waits for an acknowledgment."""
    frame = f"{node_id}:{command}\n"
    print(f"\n[Master] Sending : {frame.strip()}")

    # Send the byte-encoded string over the serial port
    ser.write(frame.encode('ascii'))

    # Wait for the node to process and respond
    time.sleep(0.1)

    # Read the response if available
    response_received = False
    start_time = time.time()

    while (time.time() - start_time) < TIMEOUT:
        if ser.in_waiting > 0:
            response = ser.readline().decode('ascii', errors='ignore').strip()
            print(f"[Master] Received: {response}")
            response_received = True
            break
        time.sleep(0.05)

    if not response_received:
        print("[Master] Error: No response received (Timeout).")
# ...

🔒 This content is premium. With the monthly subscription (7-day free trial) you can unlock the full didactic material and the print-ready PDF pack.🔓 Unlock it — 7-day free trial
#!/usr/bin/env python3
"""
master_controller.py
Validates the Arduino RS485 Relay Node by sending specific string commands
over a USB-to-RS485 adapter and waiting for acknowledgments.
"""

import serial
import time
import sys

# Windows: 'COM3', Linux: '/dev/ttyUSB0', macOS: '/dev/cu.usbserial-...'
PORT = '/dev/ttyUSB0' 
BAUD = 9600
TIMEOUT = 2.0

def send_and_wait(ser, node_id, command):
    """Sends a formatted command and waits for an acknowledgment."""
    frame = f"{node_id}:{command}\n"
    print(f"\n[Master] Sending : {frame.strip()}")

    # Send the byte-encoded string over the serial port
    ser.write(frame.encode('ascii'))

    # Wait for the node to process and respond
    time.sleep(0.1)

    # Read the response if available
    response_received = False
    start_time = time.time()

    while (time.time() - start_time) < TIMEOUT:
        if ser.in_waiting > 0:
            response = ser.readline().decode('ascii', errors='ignore').strip()
            print(f"[Master] Received: {response}")
            response_received = True
            break
        time.sleep(0.05)

    if not response_received:
        print("[Master] Error: No response received (Timeout).")

def main():
    try:
        print(f"Opening port {PORT} at {BAUD} baud...")
        with serial.Serial(PORT, BAUD, timeout=TIMEOUT) as ser:
            time.sleep(2) # Allow port initialization

            print("--- Starting Validation Sequence ---")

            # Test 1: Turn Relay ON
            send_and_wait(ser, "N1", "ON")
            time.sleep(2)

            # Test 2: Turn Relay OFF
            send_and_wait(ser, "N1", "OFF")
            time.sleep(2)

            # Test 3: Test Address Filtering (Should be ignored by N1)
            send_and_wait(ser, "N2", "ON")

            print("\n--- Validation Sequence Complete ---")

    except serial.SerialException as e:
        print(f"Serial Port Error: {e}")
        sys.exit(1)
    except KeyboardInterrupt:
        print("\nProcess aborted by user.")
        sys.exit(0)

if __name__ == '__main__':
    main()

Build/Flash/Run commands

Use the Arduino CLI to compile and flash the firmware to your Arduino UNO R3. Ensure your terminal is open in the directory containing rs485_relay_node.

TaskCommand
Update core indexarduino-cli core update-index
Install AVR corearduino-cli core install arduino:avr
Compile sketcharduino-cli compile --fqbn arduino:avr:uno rs485_relay_node
Upload to boardarduino-cli upload --fqbn arduino:avr:uno --port <PORT> rs485_relay_node

Workflow steps:
1. Connect the Arduino UNO to your PC via USB.
2. Identify the port (e.g., COM3 on Windows or /dev/ttyACM0 on Linux) using arduino-cli board list.
3. Run the compilation command to ensure there are no syntax errors.
4. Run the upload command, replacing <PORT> with your specific port identifier.
5. Open a serial monitor (`arduino-

Find this product and/or books on this topic on Amazon

Go to Amazon

As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.

Quick Quiz

Question 1: What is the main objective of the project described in the text?




Question 2: Which communication protocol is used for the long-distance bus in this project?




Question 3: What component does the Arduino UNO toggle based on targeted serial commands?




Question 4: Why is RS485 preferred over standard UART, I2C, or SPI for agricultural automation?




Question 5: What advantage does RS485 offer in noisy industrial environments?




Question 6: What is a key benefit of using this system for wired home automation?




Question 7: How many addressable nodes can be connected on a single twisted pair of wires in this scalable network?




Question 8: How does a specific node know when to react to a command on the multi-drop network?




Question 9: What is the expected latency when the Arduino toggles the relay?




Question 10: Up to what distance can error-free communication be maintained using this setup?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Telecommunications Electronics Engineer and Computer Engineer (official degrees in Spain).

Follow me:
Scroll to Top