๐‘๐’๐Ÿ’๐Ÿ–๐Ÿ“ - ๐Œ๐ฎ๐ฅ๐ญ๐ข๐ƒ๐ซ๐จ๐ฉ

ย 

๐‘๐’๐Ÿ’๐Ÿ–๐Ÿ“ - ๐Œ๐ฎ๐ฅ๐ญ๐ข๐ƒ๐ซ๐จ๐ฉ

ย 

01 Overview

The RS485 Multi-Drop Communication Network is a communication system that utilizes the Modbus RTU protocol to enable reliable data exchange between a master device and multiple ESP32 slave devices over a shared RS485 bus. The project is implemented in two configurations: (1) an ESP32 Master communicating with two ESP32 Slaves, and (2) a PC acting as the Master communicating with three ESP32 Slaves through an RS485-to-USB converter. In both configurations, the master sequentially polls each slave for data. Slave 1 collects temperature and humidity readings from a DHT11 sensor, Slave 2 detects touch input using a TTP223 capacitive touch sensor, and Slave 3 serves as an additional Modbus RTU slave for expansion or future sensor integration. The project demonstrates centralized monitoring, reliable serial communication, and scalable network architecture suitable for industrial and educational applications.

Project Use Case

The RS485 Multi-Drop Communication Network can be applied in industrial automation, environmental monitoring, smart building systems, laboratories, and educational environments where multiple sensor nodes need to communicate with a central controller or computer. Using the Modbus RTU protocol over RS485 allows long-distance communication, reduced wiring, and reliable data transmission in electrically noisy environments. The system can be expanded by adding more slave devices, making it suitable for monitoring multiple sensors and devices from a single master station.

02 Hardware and Software Components

Gather everything below before you start. This is your checklist - names, models, and versions only.

Hardware Components

PHOTO Component Description
ย ESP32-WROOM-32 The ESP32-WROOM-32 development board is a powerful, low-power IoT platform with Wi-Fi and Bluetooth, ideal for prototyping and embedded system projects.


MAX485 TTL To RS485 Serialย 

The MAX485 TTL-to-RS485 module is a low-power 5V transceiver that converts TTL signals to RS485/RS422 communication, supporting data speeds of up to 2.5 Mbps.




RS485 to USB 2.0 TTL Serial Converter

Supports various RS485 devices, including cameras, fingerprint machines, CNC machines, PBX systems, and microcontrollers.

Temperature and Humidity sensor module - DHT11

The DHT11 is an affordable and reliable sensor that measures temperature from 0ยฐC to 50ยฐC and relative humidity from 20% to 90% RH.

Digital Touch Sensor

The TTP223B capacitive touch sensor module detects finger touches and outputs a high signal when activated, while consuming low power in standby mode.
Dupont Jumper Wires Flexible wires used to connect electronic components and Arduino pins, providing reliable signal and power connections during prototyping without soldering.

Software Tools

Software Version / Details
Arduino IDE Version 2.3.8
Pythonย  Version 3.14.6
DHT sensor library Version 1.4.7

Project Filesย 

All required files are available in the project repository. Download the repository before proceeding to the Software Setup section.

Respository:
FILE DESCRIPTION
DHT sensor library The DHT Sensor Library developed by Adafruit. It simplifies communication with DHT series sensors (DHT11, DHT22, etc.) by providing easy-to-use functions for reading temperature and humidity values without manually implementing the sensor's communication protocol.
Safety Note: Although this project uses the RS485 communication standard through MAX485 transceiver modules, no RS485 or Modbus library was used. The communication protocol, frame formatting, and CRC16 error checking were implemented manually using the ESP32's Serial2 interface. This demonstrates a custom implementation of a Modbus RTU-like communication protocol over an RS485 physical layer.

03 Hardware Wiring andย  Setup

ESP32 Master and Two Slave Wiring Diagram: Wire the components to the ESP32 boards using the tables below. The first ESP32 acts as the Master, while the two other ESP32 boards act as Slave Nodes in the RS485 communication network.

Schematic Diagram "ESP32 acts as the Master"

Assembly Instructions

  1. Assemble the three ESP32 development boards and mount each board securely on separate breadboards or project bases.
  2. Connect one MAX485 RS485 transceiver module to each ESP32 using the designated UART pins (TX2 and RX2), and connect the RE and DE pins together to the assigned control pin.
  3. Connect the DHT11 temperature and humidity sensor to ESP32 Slave 1 by wiring the VCC, GND, and DATA pins to the appropriate ESP32 GPIO pin.
  4. Connect the TTP223 capacitive touch sensor to ESP32 Slave 2 by wiring the VCC, GND, and OUT pins to the appropriate ESP32 GPIO pin.
  5. Connect all MAX485 modules through the RS485 communication bus by wiring the A terminal to A, B terminal to B, and connecting a common GND between all devices.
  6. Power each ESP32 using a USB cable or an appropriate external power supply, ensuring that all modules receive the required operating voltage.
  7. Verify that all electrical connections are correct, secure, and properly connected before powering on the system.
  8. Upload the corresponding program to the ESP32 Master, ESP32 Slave 1, and ESP32 Slave 2, then open the Serial Monitor on the Master ESP32 to verify successful RS485 communication and sensor data exchange.

PC Master Wiring Diagram: Wire the components to the ESP32 slave boards using the tables below. The PC acts as the Master, while the three ESP32 boards act as Slave Nodes in the RS485 communication network.

Schematic Diagramย  "PC acts as the Master"

ย 


Assembly Instructions

  1. Mount the three ESP32 boards on separate breadboards.
  2. Connect one MAX485 module to each ESP32 using the TX2, RX2, RE, and DE pins.
  3. Connect the DHT11 sensor to ESP32 Slave 1.
  4. Connect the TTP223 touch sensor to ESP32 Slave 2.
  5. Connect all MAX485 modules and the PCโ€™s RS485-to-USB converter to the same RS485 bus (A-to-A, B-to-B, and common GND).
  6. Connect the RS485-to-USB converter to the PC using a USB cable.
  7. Power all ESP32 boards through USB or an external power supply.
  8. Check that all wiring is secure and correct.
  9. Upload the appropriate program to each ESP32 slave.
  10. Open the PC master application, select the correct COM port, and verify communication with all three ESP32 slave devices.
Note: Ensure that all MAX485 modules are connected with the correct A and B terminals, all devices share a common ground, and only one device transmits on the RS485 bus at a time to ensure reliable communication.

04ย Software Setup

ESP32 Master and Two ESP32 Slaves: Follow these steps in order to set up and test the RS485 communication network, with one ESP32 acting as the Master and two ESP32 boards acting as Slave nodes. Do not skip any step.

Project Repository:
Download this first before proceeding.

Step 1: Install the Arduino IDE

  1. Download and install the Arduino IDE on your computer.
  2. Install the ESP32 Board Package in the Arduino IDE through the Boards Manager to enable programming of ESP32 development boards.
  3. Install the required libraries using the Library Manager: DHT Sensor Library by Adafruit Adafruit Unified Sensor
  4. Connect each ESP32 board to the computer using a USB cable and select the appropriate COM port and ESP32 board in the Arduino IDE.
  5. Open the corresponding source code for the Master, Slave 1 (DHT11), and Slave 2 (TTP223).
  6. Configure the communication parameters, including the UART pins, baud rate, slave addresses, and sensor pin assignments, according to the hardware connections.
  7. Compile and upload the appropriate program to each ESP32 board.
  8. Open the Serial Monitor for the Master ESP32 and verify that communication with the slave devices is successfully established and that sensor data is received correctly.

Step 2: Board Settings

Use exactly these settings in your IDE. Wrong settings will cause upload failures.

Setting Value
Board ESP32 Dev module
Baud Rate 115200
Portย  Select the detectedย  COM Port

Step 3: Install Libraries

Install the following libraries via the Library Manager:

  • DHT sensor library

ย 

PC Master and Three ESP32 Slaves: Follow these steps in order to set up and test the RS485 communication network, with the PC acting as the Master and the three ESP32 boards acting as Slave nodes. Do not skip any step.

Project Repository:
Download this first before proceeding.

Step 1: Install the Arduino IDE and Python

  1. Install the Required Software Download and install the Arduino IDE and Python on the PC.
  2. Install the ESP32 Board Package Open the Arduino IDE and install the ESP32 Board Package through the Boards Manager.
  3. Install the Required Libraries Install the required Arduino libraries (e.g., ModbusRTU and DHT Sensor Library by Adafruit, if using the DHT11 sensor) using the Library Manager.
  4. Connect the ESP32 Boards Connect each ESP32 slave board to the PC using a USB cable and select the correct ESP32 board and COM port in the Arduino IDE.
  5. Open the Source Codes Open the corresponding source code for ESP32 Slave 1 (DHT11), ESP32 Slave 2 (TTP223), ESP32 Slave 3, and the Python PC Master application.
  6. Open the Source Codes Open the corresponding source code for ESP32 Slave 1 (DHT11), ESP32 Slave 2 (TTP223), ESP32 Slave 3, and the Python PC Master application.
  7. Upload the ESP32 Programs Compile and upload the appropriate firmware to each ESP32 slave board.
  8. Run the PC Master Application Connect the RS485-to-USB converter to the PC, launch the Python master application, and select the correct COM port.
  9. Verify Communication Confirm that the PC master successfully communicates with all three ESP32 slave devices and correctly receives sensor data and responses over the RS485 network.

    Note: Ensure that the required ESP32 board package and libraries are installed before compiling the program. Verify that the correct COM port and board configuration are selected for each ESP32 to avoid upload or communication errors.

    05 Code for ESP Master

    Copy each file below into the correct location as described in the Software Setup section. This code is designed for a network consisting of one ESP32 Master and two ESP32 Slave nodes. Read the Code Breakdown section to understand the function of each part.

    ย ESP Master
     
    /*
       RS485 MASTER
       ESP32 + MAX485
    */
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    //------------------------------------------------------
    // MODBUS CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= (uint16_t)buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    //------------------------------------------------------
    // Request Slave
    //------------------------------------------------------
    void requestSlave(uint8_t slaveID, uint16_t regCount)
    {
      uint8_t request[8];
    
      request[0] = slaveID;     // Slave Address
      request[1] = 0x03;        // Function Code
      request[2] = 0x00;
      request[3] = 0x00;
      request[4] = highByte(regCount);
      request[5] = lowByte(regCount);
    
      uint16_t crc = ModRTU_CRC(request, 6);
    
      request[6] = lowByte(crc);
      request[7] = highByte(crc);
    
      Serial.println();
    
      if (slaveID == 1)
        Serial.println("----- Slave 1 -----");
      else if (slaveID == 2)
        Serial.println("----- Slave 2 -----");
    
      Serial.print("TX > ");
      printFrame(request, 8);
    
      rs485Transmit();
    
      Serial2.write(request, 8);
      Serial2.flush();
    
      rs485Receive();
    }
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
    
      pinMode(DE_RE, OUTPUT);
    
      rs485Receive();
    
      Serial.println();
      Serial.println("=================================");
      Serial.println("      RS485 MASTER STARTED");
      Serial.println("=================================");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
    requestSlave(1, 2);
      unsigned long start = millis();
    
      uint8_t response[20];
      int index = 0;
    
      while (millis() - start < 1000)
      {
        while (Serial2.available())
        {
          response[index++] = Serial2.read();
        }
    
        if (index >= 9)
          break;
      }
    
      if (index == 0)
      {
        Serial.println("No Response");
      }
     else
    {
      Serial.print("RX < ");
      printFrame(response, index);
    
      // Check CRC
      uint16_t crcCalc = ModRTU_CRC(response, index - 2);
      uint16_t crcRecv = response[index - 2] | (response[index - 1] << 8);
    
      if (crcCalc == crcRecv)
      {
        Serial.println("CRC : PASS");
    
        // Decode only if valid response
        if (response[1] == 0x03 && response[2] == 0x04)
        {
          uint16_t temp = (response[3] << 8) | response[4];
          uint16_t hum  = (response[5] << 8) | response[6];
    
          Serial.print("Temperature : ");
          Serial.print(temp / 10.0, 1);
          Serial.println(" ยฐC");
    
          Serial.print("Humidity    : ");
          Serial.print(hum / 10.0, 1);
          Serial.println(" %");
        }
      }
      else
      {
        Serial.println("CRC : FAIL");
      }
      //--------------------------------------------------
    // REQUEST SLAVE 2
    //--------------------------------------------------
    
    delay(100);
    
    requestSlave(2, 1);
    
    start = millis();
    index = 0;
    
    while (millis() - start < 1000)
    {
        while (Serial2.available())
        {
            response[index++] = Serial2.read();
        }
    
        if (index >= 7)
            break;
    }
    
    if (index == 0)
    {
        Serial.println("No Response from Slave 2");
    }
    else
    {
        Serial.print("RX < ");
        printFrame(response, index);
    
        uint16_t crcCalc = ModRTU_CRC(response, index - 2);
        uint16_t crcRecv = response[index - 2] | (response[index - 1] << 8);
    
        if (crcCalc == crcRecv)
        {
            Serial.println("CRC : PASS");
    
            if (response[1] == 0x03 && response[2] == 0x02)
            {
                uint16_t touch = (response[3] << 8) | response[4];
    
                Serial.print("Touch Status : ");
    
                if (touch == 1)
                    Serial.println("PRESSED");
                else
                    Serial.println("RELEASED");
            }
        }
        else
        {
            Serial.println("CRC : FAIL");
        }
    }
    }delay(3000);
    }
    
    SLAVE 1
     
    /*
       RS485 DHT11 SLAVE
       ESP32 + MAX485
       
    */
    
    #include 
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    #define DHTPIN 15
    #define DHTTYPE DHT11
    
    DHT dht(DHTPIN, DHTTYPE);
    
    //------------------------------------------------------
    // CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
    
      pinMode(DE_RE, OUTPUT);
    
      rs485Receive();
    
      dht.begin();
    
      Serial.println();
      Serial.println("=================================");
      Serial.println("      RS485 DHT11 SLAVE");
      Serial.println("=================================");
      Serial.println("Waiting for Master...");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
      if (Serial2.available() >= 8)
      {
        uint8_t request[8];
    
        int len = Serial2.readBytes(request, 8);
    
        Serial.println();
        Serial.print("RX < ");
        printFrame(request, len);
    
        // ---------------- CRC Check ----------------
    
        uint16_t crcCalc = ModRTU_CRC(request, 6);
        uint16_t crcRecv = request[6] | (request[7] << 8);
    
        if (crcCalc != crcRecv)
        {
          Serial.println("CRC ERROR");
          return;
        }
    
        // ---------------- Read Sensor ----------------
    
        float temperature = dht.readTemperature();
        float humidity = dht.readHumidity();
    
        if (isnan(temperature) || isnan(humidity))
        {
          Serial.println("DHT11 Read Failed!");
          return;
        }
    
        uint16_t temp = (uint16_t)(temperature * 10);
        uint16_t hum  = (uint16_t)(humidity * 10);
    
        // ---------------- Build Response ----------------
    
        uint8_t response[9];
    
        response[0] = 0x01;      // Slave ID
        response[1] = 0x03;      // Function
        response[2] = 0x04;      // 4 data bytes
    
        response[3] = highByte(temp);
        response[4] = lowByte(temp);
    
        response[5] = highByte(hum);
        response[6] = lowByte(hum);
    
        uint16_t crc = ModRTU_CRC(response, 7);
    
        response[7] = lowByte(crc);
        response[8] = highByte(crc);
    
        // ---------------- Send ----------------
    
        delay(2);
    
        rs485Transmit();
    
        Serial2.write(response, 9);
        Serial2.flush();
    
        rs485Receive();
    
        // ---------------- Display ----------------
    
        Serial.println("CRC : PASS");
      }
    }
    
    SLAVE 2
     
    /*
       RS485 TTP223 SLAVE
       ESP32 + MAX485
       
    */
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    #define TOUCH_PIN 15
    
    //------------------------------------------------------
    // CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
    
      pinMode(DE_RE, OUTPUT);
      pinMode(TOUCH_PIN, INPUT);
    
      rs485Receive();
    
      Serial.println();
      Serial.println("=================================");
      Serial.println("      RS485 TTP223 SLAVE");
      Serial.println("=================================");
      Serial.println("Waiting for Master...");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
      if (Serial2.available() >= 8)
      {
        uint8_t request[8];
    
        int len = Serial2.readBytes(request, 8);
    
        Serial.println();
        Serial.print("RX < ");
        printFrame(request, len);
    
        // CRC Check
        uint16_t crcCalc = ModRTU_CRC(request, 6);
        uint16_t crcRecv = request[6] | (request[7] << 8);
    
        if (crcCalc != crcRecv)
        {
          Serial.println("CRC ERROR");
          return;
        }
    
        // Check Slave ID
        if (request[0] != 0x02)
        {
          // Hindi para sa Slave 2
          return;
        }
    
        // Read Touch Sensor
        bool touch = digitalRead(TOUCH_PIN);
    
        // Build Response
        uint8_t response[7];
    
        response[0] = 0x02;   // Slave ID
        response[1] = 0x03;   // Function Code
        response[2] = 0x02;   // 2 bytes of data
    
        response[3] = 0x00;
        response[4] = touch ? 0x01 : 0x00;
    
        uint16_t crc = ModRTU_CRC(response, 5);
    
        response[5] = lowByte(crc);
        response[6] = highByte(crc);
    
        delay(2);
    
        rs485Transmit();
    
        Serial2.write(response, 7);
        Serial2.flush();
    
        rs485Receive();
    
        Serial.print("TX > ");
        printFrame(response, 7);
    
        Serial.print("Touch Status : ");
    
        if (touch)
          Serial.println("PRESSED");
        else
          Serial.println("RELEASED");
    
        Serial.println("CRC : PASS");
      }
    }
    

    06ย Code for PC Master

    Copy each file below into the correct location as described in the Software Setup section. This code is designed for a network consisting of one PC as the Master and three ESP32 Slave nodes. Read the Code Breakdown section to understand the function of each part.

    serial_manager.py
     import serial
    import time
    
    from config import *
    from protocol import build_request
    
    
    class SerialManager:
    
        def __init__(self):
            self.serial = None
            self.connected = False
    
        # ==============================
        # CONNECT
        # ==============================
    
        def connect(self):
    
            try:
    
                self.serial = serial.Serial(
                    port=COM_PORT,
                    baudrate=BAUDRATE,
                    timeout=TIMEOUT
                )
    
                time.sleep(2)
    
                self.connected = True
    
                return True
    
            except Exception as e:
    
                print(e)
                return False
    
        # ==============================
        # DISCONNECT
        # ==============================
    
        def disconnect(self):
    
            if self.serial:
                self.serial.close()
    
            self.connected = False
    
        # ==============================
        # SEND REQUEST
        # ==============================
    
        def send_request(self, slave_id, register_count):
    
            request = build_request(slave_id, register_count)
    
            self.serial.reset_input_buffer()
            self.serial.reset_output_buffer()
    
            self.serial.write(request)
            self.serial.flush()
    
            return request
    
        # ==============================
        # RECEIVE RESPONSE
        # ==============================
    
        def receive_response(self, length):
    
            timeout = time.time() + 0.30
    
            response = b''
    
            while len(response) < length and time.time() < timeout:
                response += self.serial.read(length - len(response))
    
            return response
    
        # ==============================
        # READ SLAVE 1
        # ==============================
    
        def read_slave1(self):
    
            if not self.connected:
                return None
    
            request = self.send_request(SLAVE1_ID, 2)
            response = self.receive_response(9)
    
            if len(response) != 9:
                return None
    
            temperature = ((response[3] << 8) | response[4]) / 10.0
            humidity = ((response[5] << 8) | response[6]) / 10.0
    
            return {
                "request": request,
                "response": response,
                "temperature": temperature,
                "humidity": humidity
            }
    
            # ==============================
        # READ SLAVE 2
        # ==============================
    
        def read_slave2(self):
    
            if not self.connected:
                return None
    
            request = self.send_request(SLAVE2_ID, 2)
            response = self.receive_response(7)
    
            if len(response) != 7:
                return None
    
            touch = bool(response[4])
    
            return {
                "request": request,
                "response": response,
                "touch": touch
            }
    
        # ==============================
        # READ SLAVE 3
        # ==============================
    
        def read_slave3(self):
    
            if not self.connected:
                return None
    
            request = self.send_request(3, 1)
            response = self.receive_response(7)
    
            if len(response) != 7:
                return None
    
            return {
                "request": request,
                "response": response,
                "message": chr(response[3]) + chr(response[4])
            }
    
    
    dashboard.py
     from serial_manager import SerialManager
    from datetime import datetime
    import time
    
    import customtkinter as ctk
    # ==========================================
    # THEME
    # ==========================================
    
    ctk.set_appearance_mode("dark")
    ctk.set_default_color_theme("green")
    
    # ==========================================
    # WINDOW
    # ==========================================
    
    app = ctk.CTk()
    app.title("RS485 Multi-Drop Communication Network")
    app.geometry("1000x600")
    app.resizable(False, False)
    
    # ==========================================
    # COLORS
    # ==========================================
    
    BG = "#101010"
    CARD = "#1B1B1B"
    GREEN = "#00FF66"
    WHITE = "#FFFFFF"
    ORANGE = "#FFA500"
    CYAN = "#00E5FF"
    
    app.configure(fg_color=BG)
    
    # ==========================================
    # HEADER
    # ==========================================
    
    header = ctk.CTkFrame(
        app,
        fg_color="#181818",
        corner_radius=10,
        height=80
    )
    
    header.pack(fill="x", padx=20, pady=(15,10))
    
    title = ctk.CTkLabel(
        header,
        text="RS485 MULTI-DROP COMMUNICATION NETWORK",
        font=("Segoe UI",30,"bold"),
        text_color=GREEN
    )
    
    title.pack(side="left", padx=25)
    
    rightFrame = ctk.CTkFrame(
        header,
        fg_color="transparent"
    )
    
    rightFrame.pack(side="right", padx=25)
    
    status = ctk.CTkLabel(
        rightFrame,
        text="๐ŸŸข Connected",
        font=("Segoe UI",18,"bold"),
        text_color=GREEN
    )
    
    status.pack(anchor="e")
    
    port = ctk.CTkLabel(
        rightFrame,
        text="COM7",
        font=("Segoe UI",16),
        text_color=CYAN
    )
    
    port.pack(anchor="e")
    
    # ==========================================
    # SERIAL
    # ==========================================
    
    sm = SerialManager()
    
    packets_sent = 0
    packets_received = 0
    error_count = 0
    
    if sm.connect():
    
        status.configure(
            text="๐ŸŸข Connected",
            text_color=GREEN
        )
    
        print("======================================")
        print("RS485 SERIAL MONITOR")
        print("======================================")
        print(f"Port      : {sm.serial.port}")
        print(f"Baudrate  : {sm.serial.baudrate}")
        print("Status    : Connected")
        print("======================================")
    
    else:
    
        status.configure(
            text="๐Ÿ”ด Disconnected",
            text_color="red"
        )
    
        print("Failed to connect!")
        
    # ==========================================
    # CARD CONTAINER
    # ==========================================
    
    cards = ctk.CTkFrame(
        app,
        fg_color="transparent"
    )
    
    cards.pack(fill="x", padx=20, pady=10)
    
    
    # ==========================================
    # SLAVE 1 CARD
    # ==========================================
    
    def create_slave1(master):
    
        frame = ctk.CTkFrame(
            master,
            width=280,
            height=340,
            fg_color=CARD,
            border_width=2,
            border_color=GREEN,
            corner_radius=15
        )
    
        frame.pack(side="left", padx=18, pady=10)
        frame.pack_propagate(False)
    
        ctk.CTkLabel(
            frame,
            text="SLAVE 1",
            font=("Segoe UI",24,"bold"),
            text_color=WHITE
        ).pack(pady=(20,15))
    
        ctk.CTkLabel(
            frame,
            text="๐ŸŒก Temperature",
            font=("Segoe UI",18),
            text_color=ORANGE
        ).pack()
    
        temp = ctk.CTkLabel(
            frame,
            text="--.- ยฐC",
            font=("Segoe UI",48,"bold"),
            text_color=ORANGE
        )
        temp.pack(pady=(10,30))
    
        ctk.CTkLabel(
            frame,
            text="๐Ÿ’ง Humidity",
            font=("Segoe UI",18),
            text_color=CYAN
        ).pack()
    
        hum = ctk.CTkLabel(
            frame,
            text="--.- %",
            font=("Segoe UI",48,"bold"),
            text_color=CYAN
        )
        hum.pack(pady=(10,30))
    
        last = ctk.CTkLabel(
            frame,
            text="Last Update\n--:--:--",
            font=("Segoe UI",18),
            text_color=GREEN
        )
        last.pack()
    
        return temp, hum, last
    
    # ==========================================
    # SLAVE 2 CARD
    # ==========================================
    
    def create_slave2(master):
    
        frame = ctk.CTkFrame(
            master,
            width=280,
            height=340,
            fg_color=CARD,
            border_width=2,
            border_color=GREEN,
            corner_radius=15
        )
    
        frame.pack(side="left", padx=18, pady=10)
        frame.pack_propagate(False)
    
        ctk.CTkLabel(
            frame,
            text="SLAVE 2",
            font=("Segoe UI",24,"bold"),
            text_color=WHITE
        ).pack(pady=(20,15))
    
        ctk.CTkLabel(
            frame,
            text="๐Ÿ‘† Touch Sensor",
            font=("Segoe UI",18),
            text_color=CYAN
        ).pack()
    
        status = ctk.CTkLabel(
            frame,
            text="RELEASED",
            font=("Segoe UI",42,"bold"),
            text_color="red"
        )
        status.pack(pady=(30,40))
    
        last = ctk.CTkLabel(
            frame,
            text="Last Update\n--:--:--",
            font=("Segoe UI",18),
            text_color=GREEN
        )
        last.pack()
    
        return status, last
    
    # ==========================================
    # SLAVE 3 CARD
    # ==========================================
    
    def create_slave3(master):
    
        frame = ctk.CTkFrame(
            master,
            width=280,
            height=340,
            fg_color=CARD,
            border_width=2,
            border_color=GREEN,
            corner_radius=15
        )
    
        frame.pack(side="left", padx=18, pady=10)
        frame.pack_propagate(False)
    
        ctk.CTkLabel(
            frame,
            text="SLAVE 3",
            font=("Segoe UI",24,"bold"),
            text_color=WHITE
        ).pack(pady=(20,15))
    
        ctk.CTkLabel(
            frame,
            text="๐Ÿ’ฌ Communication",
            font=("Segoe UI",18),
            text_color=CYAN
        ).pack()
    
        message = ctk.CTkLabel(
            frame,
            text="Waiting...",
            font=("Segoe UI",24,"bold"),
            text_color=WHITE
        )
        message.pack(pady=(30,40))
    
        last = ctk.CTkLabel(
            frame,
            text="Last Update\n--:--:--",
            font=("Segoe UI",18),
            text_color=GREEN
        )
        last.pack()
    
        return message, last
    
    # ==========================================
    # CREATE 3 SLAVE CARDS
    # ==========================================
    
    slave1_temp, slave1_hum, slave1_time = create_slave1(cards)
    
    slave2_status, slave2_time = create_slave2(cards)
    
    slave3_message, slave3_time = create_slave3(cards)
    
    # ==========================================
    # STATISTICS
    # ==========================================
    
    # ==========================================
    # STATISTICS FRAME
    # ==========================================
    
    stats = ctk.CTkFrame(
        app,
        fg_color="#181818",
        corner_radius=15,
        height=120
    )
    
    stats.pack(fill="x", padx=20, pady=20)
    
    sent = ctk.CTkLabel(
        stats,
        text="Packets Sent : 0",
        font=("Segoe UI",18),
        text_color=WHITE
    )
    sent.pack(anchor="w", padx=20, pady=5)
    
    received = ctk.CTkLabel(
        stats,
        text="Packets Received : 0",
        font=("Segoe UI",18),
        text_color=WHITE
    )
    received.pack(anchor="w", padx=20)
    
    errors = ctk.CTkLabel(
        stats,
        text="Errors : 0",
        font=("Segoe UI",18),
        text_color="red"
    )
    errors.pack(anchor="w", padx=20, pady=5)
    
    def update_dashboard():
    
        global packets_sent
        global packets_received
        global error_count
    
        print("\n--------------------------------------")
    
        if sm.connected:
    
            # =============================
            # READ SLAVE 1
            # =============================
    
            print("Polling Slave 1...")
    
            data = sm.read_slave1()
            packets_sent += 1
    
            if data:
    
                packets_received += 1
    
                print("TX:", " ".join(f"{b:02X}" for b in data["request"]))
                print("RX:", " ".join(f"{b:02X}" for b in data["response"]))
    
                print(f"Temperature : {data['temperature']:.1f} ยฐC")
                print(f"Humidity    : {data['humidity']:.1f} %")
    
                slave1_temp.configure(
                    text=f"{data['temperature']:.1f} ยฐC"
                )
    
                slave1_hum.configure(
                    text=f"{data['humidity']:.1f} %"
                )
    
                slave1_time.configure(
                    text="Last Update\n" +
                    datetime.now().strftime("%H:%M:%S")
                )
    
            else:
    
                error_count += 1
                print("ERROR : No response from Slave 1")
    
            # =============================
            # READ SLAVE 2
            # =============================
    
            print("\nPolling Slave 2...")
    
            time.sleep(0.30)
    
            data2 = sm.read_slave2()
    
            packets_sent += 1
    
            if data2:
    
                packets_received += 1
    
                if data2["touch"]:
    
                    slave2_status.configure(
                        text="PRESSED",
                        text_color=GREEN
                    )
    
                else:
    
                    slave2_status.configure(
                        text="RELEASED",
                        text_color="red"
                    )
    
                slave2_time.configure(
                    text="Last Update\n" +
                    datetime.now().strftime("%H:%M:%S")
                )
    
                print("TX:", " ".join(f"{b:02X}" for b in data2["request"]))
                print("RX:", " ".join(f"{b:02X}" for b in data2["response"]))
                print("Touch:", "PRESSED" if data2["touch"] else "RELEASED")
    
            else:
    
                error_count += 1
                print("ERROR : No response from Slave 2")
    
                        # =============================
            # READ SLAVE 3
            # =============================
    
            print("\nPolling Slave 3...")
    
            time.sleep(0.30)
    
            data3 = sm.read_slave3()
    
            packets_sent += 1
    
            if data3:
    
                packets_received += 1
    
                slave3_message.configure(
                    text=data3["message"],
                    text_color=GREEN
                )
    
                slave3_time.configure(
                    text="Last Update\n" +
                    datetime.now().strftime("%H:%M:%S")
                )
    
                print("TX:", " ".join(f"{b:02X}" for b in data3["request"]))
                print("RX:", " ".join(f"{b:02X}" for b in data3["response"]))
                print("Message:", data3["message"])
    
            else:
    
                error_count += 1
    
                slave3_message.configure(
                    text="OFFLINE",
                    text_color="red"
                )
    
                print("ERROR : No response from Slave 3")
    
        sent.configure(
            text=f"Packets Sent : {packets_sent}"
        )
    
        received.configure(
            text=f"Packets Received : {packets_received}"
        )
    
        errors.configure(
            text=f"Errors : {error_count}"
        )
    
        app.after(2000, update_dashboard)
    
        
    update_dashboard()
    app.mainloop()
    
    Protocol.py
    from crc16 import append_crc
    
    
    def build_request(slave_id, register_count):
    
        frame = [
            slave_id,          # Slave ID
            0x03,              # Function Code
            0x00,              # Start Address High
            0x00,              # Start Address Low
            0x00,              # Quantity High
            register_count     # Quantity Low
        ]
    
        return bytes(append_crc(frame))
    
    config.py
     COM_PORT = "COM7"
    BAUDRATE = 9600
    TIMEOUT = 1
    
    SLAVE1_ID = 1
    SLAVE2_ID = 2
    SLAVE3_ID = 3
    
    crc16.py
    def crc16(data):
    
        crc = 0xFFFF
    
        for byte in data:
    
            crc ^= byte
    
            for _ in range(8):
    
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
    
        return crc
    
    
    def append_crc(frame):
    
        crc = crc16(frame)
    
        frame.append(crc & 0xFF)          # CRC Low Byte
        frame.append((crc >> 8) & 0xFF)   # CRC High Byte
    
        return frame 
    
    slave1.pyย 
    from serial_manager import SerialManager
    from config import COM_PORT
    import time
    
    sm = SerialManager()
    
    print("================================")
    print(" RS485 PC MASTER STARTED")
    print("================================")
    
    if sm.connect():
    
        print(f"\nConnected to {COM_PORT}")
    
        while True:
    
            data = sm.read_slave1()
    
            if data:
    
                print("\nSending to Slave 1...\n")
    
                print(
                    "TX :",
                    " ".join(f"{b:02X}" for b in data["request"])
                )
    
                print(
                    "RX :",
                    " ".join(f"{b:02X}" for b in data["response"])
                )
    
                print()
                print(f"Temperature : {data['temperature']} ยฐC")
                print(f"Humidity    : {data['humidity']} %")
                print("--------------------------------")
    
            else:
    
                print("No response from Slave 1")
    
            time.sleep(1)
    
    else:
    
        print("Connection Failed")
    
    slave2.py
     from serial_manager import SerialManager
    import time
    
    sm = SerialManager()
    
    if sm.connect():
    
        print("Connected to RS485")
    
        while True:
    
            data = sm.read_slave2()
    
            if data:
    
                print("\n========================")
    
                print(
                    "TX:",
                    " ".join(f"{b:02X}" for b in data["request"])
                )
    
                print(
                    "RX:",
                    " ".join(f"{b:02X}" for b in data["response"])
                )
    
                if data["touch"]:
                    print("Touch : PRESSED")
                else:
                    print("Touch : RELEASED")
    
            else:
    
                print("No Response")
    
            time.sleep(1)
    
    else:
    
        print("Cannot connect.")
    
    slave3.py
    from serial_manager import SerialManager
    import time
    
    sm = SerialManager()
    
    if sm.connect():
    
        print("==========================")
        print("      TEST SLAVE 3")
        print("==========================")
    
        while True:
    
            data = sm.read_slave3()
    
            if data:
    
                print("Request :", data["request"].hex(" "))
                print("Response:", data["response"].hex(" "))
                print("Message :", data["message"])
                print()
    
            else:
    
                print("No response from Slave 3\n")
    
            time.sleep(1)
    
    else:
    
        print("Failed to connect.")
    
    SLAVE 1 arduino ide
    /*
       RS485 DHT11 SLAVE
       ESP32 + MAX485
       Manual Protocol (No Modbus Library)
    */
    
    #include 
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    #define DHTPIN 15
    #define DHTTYPE DHT11
    
    DHT dht(DHTPIN, DHTTYPE);
    
    //------------------------------------------------------
    // CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
    
      pinMode(DE_RE, OUTPUT);
    
      rs485Receive();
    
      dht.begin();
    
      Serial.println();
      Serial.println("=================================");
      Serial.println("      RS485 DHT11 SLAVE");
      Serial.println("=================================");
      Serial.println("Waiting for Master...");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
      if (Serial2.available() >= 8)
      {
        uint8_t request[8];
    
        int len = Serial2.readBytes(request, 8);
    
        Serial.println();
        Serial.print("RX < ");
        printFrame(request, len);
    
        // ---------------- CRC Check ----------------
    
        uint16_t crcCalc = ModRTU_CRC(request, 6);
        uint16_t crcRecv = request[6] | (request[7] << 8);
    
        if (crcCalc != crcRecv)
        {
          Serial.println("CRC ERROR");
          return;
        }
    
        // ---------------- Read Sensor ----------------
    
        float temperature = dht.readTemperature();
        float humidity = dht.readHumidity();
    
        if (isnan(temperature) || isnan(humidity))
        {
          Serial.println("DHT11 Read Failed!");
          return;
        }
    
        uint16_t temp = (uint16_t)(temperature * 10);
        uint16_t hum  = (uint16_t)(humidity * 10);
    
        // ---------------- Build Response ----------------
    
        uint8_t response[9];
    
        response[0] = 0x01;      // Slave ID
        response[1] = 0x03;      // Function
        response[2] = 0x04;      // 4 data bytes
    
        response[3] = highByte(temp);
        response[4] = lowByte(temp);
    
        response[5] = highByte(hum);
        response[6] = lowByte(hum);
    
        uint16_t crc = ModRTU_CRC(response, 7);
    
        response[7] = lowByte(crc);
        response[8] = highByte(crc);
    
        // ---------------- Send ----------------
    
        delay(2);
    
        rs485Transmit();
    
        Serial2.write(response, 9);
        Serial2.flush();
    
        rs485Receive();
    
        // ---------------- Display ----------------
    
        Serial.println("CRC : PASS");
      }
    } 
    
    SLAVE 2 arduino ide
    /*
       RS485 TTP223 SLAVE
       ESP32 + MAX485
       Manual Protocol (No Modbus Library)
    */
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    #define TOUCH_PIN 15
    
    //------------------------------------------------------
    // CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
      Serial2.setTimeout(50);
    
      pinMode(DE_RE, OUTPUT);
      pinMode(TOUCH_PIN, INPUT);
    
      rs485Receive();
    
      Serial.println();
      Serial.println("=================================");
      Serial.println("      RS485 TTP223 SLAVE");
      Serial.println("=================================");
      Serial.println("Waiting for Master...");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
      static uint8_t request[8];
    
      if (Serial2.readBytes(request, 8) == 8)
      {
    
        Serial.println();
        Serial.print("RX < ");
        printFrame(request, 8);
    
        // CRC Check
        uint16_t crcCalc = ModRTU_CRC(request, 6);
        uint16_t crcRecv = request[6] | (request[7] << 8);
    
        if (crcCalc != crcRecv)
        {
          Serial.println("CRC ERROR");
          return;
        }
    
        // Check Slave ID
        if (request[0] != 0x02)
        {
          // Hindi para sa Slave 2
          return;
        }
    
        // Read Touch Sensor
        bool touch = digitalRead(TOUCH_PIN);
    
        // Build Response
        uint8_t response[7];
    
        response[0] = 0x02;   // Slave ID
        response[1] = 0x03;   // Function Code
        response[2] = 0x02;   // 2 bytes of data
    
        response[3] = 0x00;
        response[4] = touch ? 0x01 : 0x00;
    
        uint16_t crc = ModRTU_CRC(response, 5);
    
        response[5] = lowByte(crc);
        response[6] = highByte(crc);
    
       delay(2);
    
    rs485Transmit();
    
    Serial2.write(response, 7);
    Serial2.flush();              // Hintaying matapos ang transmission
    
    delayMicroseconds(500);
    
    rs485Receive();               // Bumalik sa receive mode
    
    Serial.print("TX > ");
        printFrame(response, 7);
    
        Serial.print("Touch Status : ");
    
        if (touch)
          Serial.println("PRESSED");
        else
          Serial.println("RELEASED");
    
        Serial.println("CRC : PASS");
      }
    }
     
    
    SLAVE 3 arduino ide
    /*
       RS485 SLAVE 3
       ESP32 + MAX485
       Random Message: HI / OK
    */
    
    #define RXD2 16
    #define TXD2 17
    #define DE_RE 4
    
    //------------------------------------------------------
    // CRC16
    //------------------------------------------------------
    uint16_t ModRTU_CRC(uint8_t buf[], int len)
    {
      uint16_t crc = 0xFFFF;
    
      for (int pos = 0; pos < len; pos++)
      {
        crc ^= buf[pos];
    
        for (int i = 0; i < 8; i++)
        {
          if (crc & 1)
          {
            crc >>= 1;
            crc ^= 0xA001;
          }
          else
          {
            crc >>= 1;
          }
        }
      }
    
      return crc;
    }
    
    //------------------------------------------------------
    
    void printFrame(uint8_t frame[], int len)
    {
      for (int i = 0; i < len; i++)
      {
        if (frame[i] < 0x10)
          Serial.print("0");
    
        Serial.print(frame[i], HEX);
    
        if (i < len - 1)
          Serial.print(" ");
      }
    
      Serial.println();
    }
    
    //------------------------------------------------------
    
    void rs485Transmit()
    {
      digitalWrite(DE_RE, HIGH);
    }
    
    void rs485Receive()
    {
      digitalWrite(DE_RE, LOW);
    }
    
    //------------------------------------------------------
    
    void setup()
    {
      Serial.begin(115200);
    
      Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
      Serial2.setTimeout(50);
    
      pinMode(DE_RE, OUTPUT);
    
      // Force receive mode
      digitalWrite(DE_RE, LOW);
    
      delay(500);
    
      // Clear garbage bytes
      while (Serial2.available())
      {
        Serial2.read();
      }
    
      randomSeed(micros());
    
      Serial.println();
      Serial.println("===============================");
      Serial.println("       RS485 SLAVE 3");
      Serial.println("===============================");
      Serial.println("Waiting for Master...");
      Serial.println("SETUP COMPLETE");
    }
    
    //------------------------------------------------------
    
    void loop()
    {
      static uint8_t request[8];
    
      if (Serial2.readBytes(request, 8) == 8)
      {
        Serial.println();
        Serial.print("RX < ");
        printFrame(request, 8);
    
        // CRC Check
        uint16_t crcCalc = ModRTU_CRC(request, 6);
        uint16_t crcRecv = request[6] | (request[7] << 8);
    
        if (crcCalc != crcRecv)
        {
          Serial.println("CRC ERROR");
          return;
        }
    
        // Check Slave ID
        if (request[0] != 0x03)
        {
          return;
        }
    
        // Generate random message
        char c1, c2;
    
        if (random(0, 2) == 0)
        {
          c1 = 'H';
          c2 = 'I';
        }
        else
        {
          c1 = 'O';
          c2 = 'K';
        }
    
        // Build response
        uint8_t response[7];
    
        response[0] = 0x03;
        response[1] = 0x03;
        response[2] = 0x02;
        response[3] = c1;
        response[4] = c2;
    
        uint16_t crc = ModRTU_CRC(response, 5);
    
        response[5] = lowByte(crc);
        response[6] = highByte(crc);
    
        delay(2);
    
        rs485Transmit();
    
        Serial2.write(response, 7);
        Serial2.flush();
    
        delayMicroseconds(500);
    
        rs485Receive();
    
        Serial.print("TX > ");
        printFrame(response, 7);
    
        Serial.print("Message : ");
        Serial.print(c1);
        Serial.println(c2);
      }
    }
    
    ย 

    07ย Code Breakdown

    ESP32 Master and Two ESP32 Slaves: Here is what each part of the code does for the RS485 network with one ESP32 acting as the Master and two ESP32 boards acting as Slave nodes. Read this section after uploading the code.

    Libraries

    Library Purpose

    ย 

    DHT sensor library

    Key Functions

    setup()

    Initializes the ESP32, configures the UART communication, MAX485 transceiver control pins, sensor pins, and starts the Serial and Serial2 interfaces for RS485 communication.

    loop()

    Continuously polls each slave device, receives sensor data, verifies the CRC16 checksum, and displays the received information on the Serial Monitor.

    ย REQUESTSLAVE()ย 

    Constructs and transmits a request frame to a specified slave device through the RS485 bus.

    MODRTU_CRC()ย 

    Calculates the CRC16 (Cyclic Redundancy Check) checksum for every transmitted and received frame to ensure data integrity during communication.

    DHT.READTEMPERATURE() & DHT.READHUMIDITY()ย 

    Reads the temperature and humidity values from the DHT11 sensor connected to Slave 1 before transmitting the data to the Master.

    ย DIGITALREAD()

    Reads the current state of the TTP223 capacitive touch sensor connected to Slave 2 and returns its status when requested by the Master.

    ย FRAME VALIDATION

    Verifies the received slave address, function code, and CRC16 checksum before processing incoming RS485 messages.

    ย  SERIAL2 COMMUNICATION

    Handles the transmission and reception of data frames between the Master and Slave nodes over the RS485 communication bus.

    General Program Workflow

    1. Initialize the ESP32 boards, UART communication, MAX485 transceivers, and connected sensors.
    2. The Master sequentially sends request frames to each slave through the RS485 bus.
    3. The Master sequentially sends request frames to each slave through the RS485 bus. Each addressed slave validates the received request, acquires the requested sensor data, generates a response frame with a CRC16 checksum, and transmits the response back to the Master.
    4. The Master verifies the received data, displays the sensor readings on the Serial Monitor, and continuously repeats the polling process for real-time RS485 communication.

    ย 

    PC Master and Three ESP32 Slaves: Here is what each part of the code does for the RS485 network with a PC acting as the Master and three ESP32 boards acting as Slave nodes. Read this section after uploading and running the code.

    Python Source file

    python file functions

    ย 

    config.py

    crc16.py

    protocol.py

    serial_manager.py

    dashboard.py

    slave1.py

    slave2.py

    slave3.py


    Key Functions

    PC Master (Python)

    Main()

    Initializes the Python application, opens the selected COM port through the RS485-to-USB converter, configures the serial communication parameters, and starts communication with the three ESP32 slave devices.

    POLL_SLAVES()

    Sequentially sends Modbus RTU request frames to Slave 1, Slave 2, and Slave 3 to collect data from each device.

    SEND_REQUEST()ย 

    Constructs a Modbus RTU request frame containing the slave address, function code, register address, and CRC16 checksum, then transmits it through the RS485 bus.

    READ_RESPONSE()ย 

    Receives the response frame from the addressed slave, extracts the returned data, and forwards it for validation.

    ย MODRTU_CRC()ย 

    Calculates the CRC16 (Cyclic Redundancy Check) checksum for transmitted and received Modbus RTU frames to ensure data integrity.

    ย FRAME_VALIDATION()

    Verifies the received slave address, function code, data length, and CRC16 checksum before processing the received information.

    DISPLAY_DATA()

    Displays the received sensor values and communication status on the Python dashboard or terminal.

    SERIAL_COMMUNICATION()

    Handles the transmission and reception of Modbus RTU frames between the PC Master and the ESP32 slave devices through the RS485-to-USB converter.

    ESP32 Slave 1 (DHT11)

    SETUP()

    Initializes the ESP32, UART communication, MAX485 control pins, and the DHT11 sensor.

    LOOP()

    Waits for Modbus RTU requests from the PC Master and responds when its slave address is requested.

    DHT.READTEMPERATURE() & DHT.READHUMIDITY()ย 

    Reads the temperature and humidity values from the DHT11 sensor.

    FRAME_VALIDATION()ย 

    Checks the received slave address, function code, and CRC16 checksum before processing the request.

    ย MODRTU_CRC()ย 

    Calculates the CRC16 checksum for response frames.

    SERIAL2_COMMUNICATION()

    Handles RS485 communication between the slave and the PC Master.

    ESP32 Slave 2 (TTP223)

    SETUP()

    Initializes the ESP32, UART communication, MAX485 control pins, and the TTP223 touch sensor.

    LOOP()

    Waits for requests from the PC Master and sends the touch sensor status when requested.

    ย DIGITALREAD()ย 

    Reads the current state of the TTP223 capacitive touch sensor.

    FRAME_VALIDATION()ย 

    Validates the received request frame before responding.

    ย MODRTU_CRC()ย 

    Calculates the CRC16 checksum for transmitted response frames.

    SERIAL2_COMMUNICATION()

    Handles RS485 communication between the slave and the PC Master.

    ESP32 Slave 3

    SETUP()

    Initializes the ESP32, UART communication, and MAX485 transceiver.

    LOOP()

    Waits for requests from the PC Master and returns the configured register values or device status.

    ย FRAME_VALIDATION()ย 

    Verifies the received slave address, function code, and CRC16 checksum.

    ย MODRTU_CRC()ย 

    Calculates the CRC16 checksum for transmitted response frames.

    SERIAL2_COMMUNICATION()

    Handles RS485 communication between the slave and the PC Master.

    ย 

    ย 

    General Program Workflow

    1. Initialize the PC Master application, ESP32 slave devices, UART communication, MAX485 transceivers, and connected sensors.
    2. The PC Master sequentially sends Modbus RTU request frames to Slave 1, Slave 2, and Slave 3 through the RS485 bus.
    3. Each addressed slave validates the received request, acquires the requested sensor data or device status, generates a response frame with a CRC16 checksum, and transmits the response back to the PC Master.
    4. The PC Master verifies the received response using frame validation and CRC16 checking.
    5. The received data from all slave devices are displayed on the PC application.

    08ย Testing and Calibration

    After uploading the programs to the ESP32 Master, Slave 1, and Slave 2, verify each of the following tests to ensure that the RS485 communication network is functioning correctly.

    RS485 Communication Test

    Power on all ESP32 boards and verify that the Master successfully communicates with each slave over the RS485 bus. Confirm that requests and responses are exchanged without communication errors.

    DHT11 Sensor Test

    Monitor the Serial Monitor and verify that Slave 1 correctly measures and transmits real-time temperature and humidity readings when requested by the Master.

    TTP223 Touch Sensor Test

    Touch the TTP223 sensor connected to Slave 2 and verify that the Master correctly displays the corresponding touch status (Pressed or Released).

    Multi-Drop Communication Test

    Confirm that the Master polls each slave sequentially and that only the addressed slave responds, ensuring reliable communication on the shared RS485 bus.

    Continuous Communication Test

    Allow the system to run continuously and verify that sensor data is updated consistently without communication failures or data corruption.

    ย 

    After setting up the PC as the Master and connecting the three ESP32 boards as Slave 1, Slave 2, and Slave 3, verify each of the following tests to ensure that the RS485 communication network is functioning correctly.

    PC-to-Slave Communication Test

    Power on all ESP32 slave devices and connect the PC to the RS485 network using the RS485-to-USB converter. Run the PC Master application and verify that it successfully communicates with each slave by sending requests and receiving valid responses without communication errors.

    DHT11 Sensor Test (Slave 1)

    Monitor the PC Master application and verify that ESP32 Slave 1 correctly measures and transmits real-time temperature and humidity readings whenever requested by the PC Master.

    TTP223 Touch Sensor Test (Slave 2)

    Touch the TTP223 sensor connected to ESP32 Slave 2 and verify that the PC Master correctly displays the sensor status as Pressed or Released.

    Slave 3 Communication Test

    Verify that the PC Master successfully communicates with ESP32 Slave 3 by polling its Modbus address and confirming that the expected data or status is received correctly.

    Multi-Drop Communication Test

    Confirm that the PC Master sequentially polls Slave 1, Slave 2, and Slave 3, and that only the addressed slave responds at any given time, ensuring reliable communication on the shared RS485 bus.

    Continuous Communication Test

    Allow the system to operate continuously and verify that the PC Master consistently receives updated data from all three ESP32 slave devices without communication failures, data corruption, or CRC errors.

    Common Issue: If communication fails or sensor readings are not received, verify the A and B wiring of the MAX485 modules, ensure all devices share a common ground (GND), check the UART pin connections and slave addresses, and confirm that the baud rate and communication settings are identical on all ESP32 boards.

    09ย System Demonstration

    Video Demonstration "PC Master and 3 ESP32 Slave"

    This video demonstrates the complete setup of the RS485 Multi-Drop Communication Network using a PC Master, USB-to-RS485 converter, and three ESP32 Slave nodes. It shows the sequential polling of each slave and the real-time transmission of temperature, humidity, and touch sensor data over the RS485 communication bus.


    Video Demonstration "ESP32 Master and two ESP32 Slave"

    This video demonstrates the complete setup of an RS485 Multi-Drop Communication Network using one ESP32 Master and two ESP32 Slave nodes. It shows the master sequentially polling each slave and receiving real-time temperature, humidity, and touch sensor data through the RS485 communication bus.

    10ย Conclusion

    Possible Improvements and Future Enhancements

    • Add more sensor nodes to expand the RS485 network and support larger monitoring systems.
    • Integrate an IoT or cloud-based dashboard for remote monitoring and data logging.
    • Improve the system with error detection and automated device control for more reliable and efficient operation.

    11ย References

    • How to use Modbus RTU with ESP32 to read Sensor Data By Mamtaz Alamย 

    12ย Project Authors

    • Mikyla P. Montana
    • Jonna Kayzher S. Apellido

    ๐‘๐’๐Ÿ’๐Ÿ–๐Ÿ“ - ๐Œ๐ฎ๐ฅ๐ญ๐ข๐ƒ๐ซ๐จ๐ฉ

    CreateLabz

    ย 

    #max485#pc dashboard#rs485 multidrop

    Leave a comment

    All comments are moderated before being published