Chapter 176: Modbus RTU Protocol Introduction

Chapter Objectives

After completing this chapter, you will be able to:

  • Understand the fundamental principles of the Modbus protocol.
  • Describe the Modbus master-slave communication model.
  • Explain the structure of a Modbus RTU frame.
  • Identify the different Modbus data types (Coils, Discrete Inputs, Input Registers, Holding Registers).
  • Recognize common Modbus function codes and their purposes.
  • Understand the role of CRC in Modbus RTU for error checking.
  • Appreciate the considerations for implementing Modbus RTU on ESP32 series microcontrollers.

Introduction

In the realm of industrial automation and control systems, communication between devices is paramount. Programmable Logic Controllers (PLCs), sensors, actuators, and Human-Machine Interfaces (HMIs) all need to exchange information reliably. One of the oldest, simplest, and most widely adopted serial communication protocols for this purpose is Modbus. Originally published by Modicon (now Schneider Electric) in 1979 for use with its PLCs, Modbus has become a de facto standard in many industrial applications.

This chapter introduces the fundamentals of the Modbus protocol, focusing specifically on the Modbus RTU (Remote Terminal Unit) variant, which is commonly used over serial lines like RS-485. Understanding Modbus is crucial for anyone working with industrial equipment, as it provides a common language for diverse devices to interact. For ESP32 developers, leveraging Modbus can enable your projects to interface with a vast ecosystem of existing industrial hardware.

Theory

What is Modbus?

Modbus is an open, royalty-free, serial communication protocol based on a master-slave (or client-server in newer Modbus TCP terminology) architecture. This means one device (the master) initiates communication by sending requests, and other devices (the slaves) respond to these requests by providing data or performing an action.

Key Characteristics of Modbus:

  • Simplicity: The protocol is relatively easy to implement and understand.
  • Open Standard: Specifications are publicly available, leading to wide adoption.
  • Versatility: Can be used over various physical layers (serial RS-232, RS-485, Ethernet TCP/IP).
  • Reliability: Includes error-checking mechanisms.

Modbus Communication Model

The Modbus protocol defines a communication transaction between a master and one or more slaves.

  • Master: Only one master is active on a Modbus serial line at any given time. It initiates transactions (queries).
  • Slaves: Up to 247 slaves can be present on a single multi-drop serial line (like RS-485), each with a unique address from 1 to 247. Address 0 is used for broadcast messages (slaves do not respond), and addresses 248-255 are reserved. Slaves only respond; they never initiate communication.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    subgraph "Modbus RTU Communication Cycle"
        direction LR
        A(Master)
        R{Request<br>to Slave 'X'}
        S(Slave 'X')
        P{"Process<br>Request"}
        V(Validate<br>CRC & Data)
        E{Error?}
        Resp(Response)
        Ex(Exception<br>Response)
        F(End Transaction)
        
        A -- Request Frame --> R
        R --> S
        S --> P
        P --> V
        V --> E
        E -- No --> Resp
        E -- Yes --> Ex
        Resp -- Response Frame --> A
        Ex -- Exception Frame --> A
        A --> F
    end

    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef validation fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B;

    class A,S primary;
    class E,V decision;
    class P,R process;
    class Resp,F success;
    class Ex validation;

Communication Flow:

  1. The master sends a request frame to a specific slave device (using its unique address) or to all slave devices (broadcast).
  2. The addressed slave(s) process the request.
  3. If the request is valid and addressed to a single slave, that slave sends a response frame back to the master. No response is sent for broadcast messages.
  4. If an error occurs (e.g., invalid function code, invalid data address), the slave responds with an exception frame.

Modbus Variants

Modbus has several variants, differing mainly in how data is represented and the physical layer used:

Feature Modbus RTU Modbus ASCII Modbus TCP/IP
Physical Layer Serial (RS-232, RS-485) Serial (RS-232, RS-485) Ethernet (TCP/IP)
Data Format Binary Human-readable ASCII characters Binary (encapsulated in TCP packet)
Message Delimiting 3.5 character time silence (timing-dependent) Start (‘:’) and End (CR/LF) characters Managed by TCP/IP stack (MBAP Header)
Error Checking CRC (Cyclic Redundancy Check) LRC (Longitudinal Redundancy Check) CRC (and TCP/IP’s own checksums)
Efficiency High (compact messages) Low (2 ASCII chars per byte of data) Very high bandwidth, but higher overhead per packet
Common Use Case Industrial fieldbus for PLCs, sensors, and drives Less common; used where human-readability is a priority Plant-level networks, SCADA systems, device configuration
  1. Modbus RTU: The most common serial variant. Data is exchanged in binary format. It uses a Cyclic Redundancy Check (CRC) for error detection. This chapter focuses on Modbus RTU.
  2. Modbus ASCII: Another serial variant. Data is exchanged using ASCII characters. It uses a Longitudinal Redundancy Check (LRC) for error detection. It’s less efficient than RTU due to larger message sizes but can be easier to read for human debugging.
  3. Modbus TCP/IP (or Modbus TCP): Used over Ethernet networks. It encapsulates Modbus frames within TCP/IP packets.
  4. Modbus Plus (Modbus+): A proprietary, high-speed, token-passing network.

Modbus RTU Frame Structure

In Modbus RTU, data is transmitted as a sequence of 8-bit bytes. Each message (request or response) is called a frame and has a specific structure. A key characteristic of Modbus RTU is that messages must be transmitted continuously without gaps exceeding 1.5 character times. Gaps of at least 3.5 character times are used to detect the end of a frame and the start of a new one.

A Modbus RTU frame consists of the following fields:

  1. Slave Address (1 byte):
    • Request: The address of the slave device (1-247) the message is intended for. Address 0 is for broadcast.
    • Response: The address of the slave device sending the response.
  2. Function Code (1 byte):
    • Specifies the action requested by the master or the action performed by the slave.
    • Valid codes are typically in the range of 1-127.
    • If the most significant bit of the function code in a response is set (i.e., function code + 0x80), it indicates an exception (error) occurred.
  3. Data (N bytes):
    • This field contains information specific to the function code. It can include register addresses, number of items, or actual data values.
    • The length of this field varies depending on the function code.
  4. CRC (2 bytes):
    • Cyclic Redundancy Check. This is an error-checking field calculated over the entire message (Slave Address, Function Code, and Data fields).
    • The CRC is calculated by both the transmitting and receiving devices. If the calculated CRCs do not match, the message is considered corrupt and discarded.
    • The CRC is transmitted with the Low Byte first, followed by the High Byte.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
block-beta
    columns 4
    A["Slave Address<br/>1 Byte"]:1
    B["Function Code<br/>1 Byte"]:1
    C["Data<br/>N Bytes"]:1
    D["CRC<br/>2 Bytes"]:1
    
    classDef default fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF,font-family:'Open Sans';

Example Frame (Conceptual):

Let’s say a master wants to read two holding registers starting at address 100 from slave device 5.

  • Slave Address: 0x05
  • Function Code: 0x03 (Read Holding Registers)
  • Data (Request):
    • Starting Address High Byte: 0x00
    • Starting Address Low Byte: 0x64 (for address 100)
    • Number of Registers High Byte: 0x00
    • Number of Registers Low Byte: 0x02
  • CRC: Calculated value (2 bytes)

Modbus Data Model (Register Types)

Modbus defines four primary data types or “register” types that devices can expose. These are conceptual tables of data within a slave device.

  1. Coils (Discrete Outputs):
    • Access: Read/Write by the master.
    • Size: Single bit.
    • Purpose: Typically used to control discrete outputs (e.g., turning a relay on/off, controlling an LED).
    • Addressing: From 00001 to 09999 (or 0x0000 to 0x270E).
    • Function Codes:
      • 0x01 (Read Coils)
      • 0x05 (Write Single Coil)
      • 0x0F (Write Multiple Coils)
  2. Discrete Inputs (Status Inputs):
    • Access: Read-Only by the master.
    • Size: Single bit.
    • Purpose: Typically used to read the status of digital inputs (e.g., status of a switch, a sensor contact).
    • Addressing: From 10001 to 19999 (or 0x0000 to 0x270E, but in a separate address space from coils).
    • Function Codes:
      • 0x02 (Read Discrete Inputs)
  3. Input Registers:
    • Access: Read-Only by the master.
    • Size: 16-bit word (two bytes).
    • Purpose: Typically used to read analog input values from sensors (e.g., temperature, pressure, flow rate) that are provided by the slave.
    • Addressing: From 30001 to 39999 (or 0x0000 to 0x270E, separate address space).
    • Function Codes:
      • 0x04 (Read Input Registers)
  4. Holding Registers:
    • Access: Read/Write by the master.
    • Size: 16-bit word (two bytes).
    • Purpose: The most versatile type. Used for various data like configuration parameters, setpoints, analog outputs, or any 16-bit data that needs to be read or written by the master.
    • Addressing: From 40001 to 49999 (or 0x0000 to 0x270E, separate address space).
    • Function Codes:
      • 0x03 (Read Holding Registers)
      • 0x06 (Write Single Holding Register)
      • 0x10 (Write Multiple Holding Registers) (Decimal 16)

Important Note on Addressing:

The 5-digit addressing (e.g., 40001) is a common convention but can be confusing. The actual protocol uses 0-based addressing for the registers (e.g., address 0 for the first holding register). The first digit in the 5-digit scheme often indicates the register type:

  • 0xxxx for Coils
  • 1xxxx for Discrete Inputs
  • 3xxxx for Input Registers
  • 4xxxx for Holding Registers

When programming, you will typically use the 0-based address (e.g., address 0 for 40001).

Common Function Codes

Here’s a summary of some of the most frequently used Modbus function codes:

Code (Dec) Code (Hex) Function Name Object Type Access
1 0x01 Read Coils Coils Read
2 0x02 Read Discrete Inputs Discrete Inputs Read
3 0x03 Read Holding Registers Holding Registers Read
4 0x04 Read Input Registers Input Registers Read
5 0x05 Write Single Coil Coil Write
6 0x06 Write Single Holding Register Holding Register Write
15 0x0F Write Multiple Coils Coils Write
16 0x10 Write Multiple Holding Registers Holding Registers Write

CRC Calculation

The CRC (Cyclic Redundancy Check) is a 16-bit value used to detect errors in transmission. The Modbus RTU specification defines a specific algorithm for CRC-16 calculation (CRC-16-MODBUS).

The steps are roughly:

  1. Initialize a 16-bit register (CRC register) to 0xFFFF.
  2. For each byte in the message (from Slave Address to the end of Data):a. XOR the byte with the low byte of the CRC register.b. For 8 bits:i. If the LSB of the CRC register is 1, right-shift the register by one bit and XOR with 0xA001.ii. If the LSB is 0, just right-shift the register by one bit.
  3. The final content of the CRC register is the CRC value.

Tip: While it’s good to understand that CRC is used, you will rarely need to implement the CRC calculation yourself. Modbus libraries, including the one provided in ESP-IDF, handle CRC generation and validation automatically.

Timing in Modbus RTU

Modbus RTU relies on timing to distinguish frames.

  • Inter-character Timeout: The maximum allowed silence between two characters within a frame is 1.5 character times. If this timeout is exceeded, the receiving device assumes the message is incomplete or corrupted.
  • Inter-frame Delay (End of Frame / Start of Frame): A silence of at least 3.5 character times marks the end of one frame and indicates that the bus is free for a new frame.

The “character time” depends on the baud rate and serial parameters (bits per character). For example, at 9600 baud, 8 data bits, 1 stop bit, no parity (10 bits total per character), one character time is 10 bits / 9600 bits/sec ≈ 1.04 ms.

  • 1.5 character times ≈ 1.56 ms
  • 3.5 character times ≈ 3.64 ms

These timings are critical for robust Modbus RTU communication.

Exception Responses

If a master sends a valid request to an existing slave, but the slave cannot perform the requested action (e.g., master tries to read a non-existent register), the slave returns an exception response.

An exception response consists of:

  1. Slave Address (1 byte): Address of the slave.
  2. Function Code (1 byte): The original function code with its Most Significant Bit (MSB) set to 1. For example, if the request was 0x03, the exception function code will be 0x83.
  3. Exception Code (1 byte): Indicates the type of error.
    • 0x01 (ILLEGAL FUNCTION): The function code received is not an allowable action for the slave.
    • 0x02 (ILLEGAL DATA ADDRESS): The data address received is not an allowable address for the slave.
    • 0x03 (ILLEGAL DATA VALUE): A value contained in the query data field is not an allowable value for the slave.
    • 0x04 (SLAVE DEVICE FAILURE): An unrecoverable error occurred while the slave was attempting to perform the requested action.
    • Other codes exist for more specific errors.
  4. CRC (2 bytes): Standard CRC calculation.
%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    subgraph "Exception Handling"
        A(Master sends request<br>e.g., Read Register 49999) --> B{Slave receives request};
        B --> C{Can action be performed?};
        C -- Yes --> D(Slave executes action<br>and sends normal response);
        C -- "No (e.g., address 49999<br>does not exist)" --> E(Slave constructs<br>Exception Response);
        E --> F["Set original Function Code MSB to 1<br><b>0x03</b> (Read) becomes <b>0x83</b> (Exception)"];
        F --> G["Set Exception Code<br>e.g., <b>0x02</b> for 'Illegal Data Address'"];
        G --> H(Slave sends Exception Response Frame<br>to Master);
    end

    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef validation fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B;

    class A primary;
    class C decision;
    class B,E,F,G process;
    class D success;
    class H validation;

Practical Examples with ESP32

While subsequent chapters (177 and 178) will delve into full Modbus master and slave implementations using the ESP-IDF freemodbus component, this section provides a conceptual overview of how you would prepare an ESP32 for Modbus RTU communication.

Modbus RTU is a serial protocol, typically implemented over RS-485 for industrial environments due to its noise immunity and multi-drop capability. ESP32 microcontrollers have multiple UART (Universal Asynchronous Receiver/Transmitter) peripherals that can be used for serial communication.

1. Hardware Setup

To use Modbus RTU, you’ll need:

  • An ESP32 development board.
  • An RS-485 transceiver module (e.g., based on MAX485 or similar IC). This module converts the ESP32’s TTL level UART signals to differential RS-485 signals.
    • Connect ESP32’s TX pin to the DI (Driver Input) pin of the RS-485 transceiver.
    • Connect ESP32’s RX pin to the RO (Receiver Output) pin of the RS-485 transceiver.
    • Connect an ESP32 GPIO pin to DE (Driver Enable) and RE (Receiver Enable, often tied together or inverted) pins of the transceiver to control transmission/reception direction. This is crucial for half-duplex RS-485 communication.

2. ESP-IDF freemodbus Component

The ESP-IDF includes a component for Modbus communication based on the popular FreeMODBUS library. This component supports Modbus RTU (master and slave) and Modbus TCP (master and slave).

Including the component:

You would typically add freemodbus to the REQUIRES or PRIV_REQUIRES list in your component’s CMakeLists.txt file:

Plaintext
# my_component/CMakeLists.txt
idf_component_register(SRCS "my_component.c"
                    INCLUDE_DIRS "."
                    REQUIRES freemodbus)

Configuration via menuconfig:

The freemodbus component offers extensive configuration options accessible via idf.py menuconfig:

Navigate to Component config -> Modbus configuration (FreeModbus).

Here you can:

  • Enable Modbus stack support (TCP/Serial).
  • Configure Modbus serial support (RTU/ASCII).
  • Set communication parameters (port, baud rate, parity).
  • Define the number of supported Modbus instances.
  • Configure master or slave specific parameters.

3. Conceptual UART Initialization for Modbus

Before the freemodbus library takes over, the underlying UART peripheral needs to be configured. The library itself often handles this, but understanding the UART setup is beneficial.

%%{ init: { 'theme': 'base', 'themeVariables': { 'fontFamily': 'Open Sans' } } }%%
graph TD
    subgraph "ESP-IDF Modbus Initialization"
        A(Start) --> B(Hardware Setup);
        B --> C("Configure UART Pins & Parameters<br><i>(e.g., Port, Baud, TX/RX/RTS Pins)</i>");
        C --> D{"Select Modbus Role<br>in 'menuconfig' or code"};
        D -- Master --> E("Call <b>mbc_master_init()</b>");
        D -- Slave --> F("Call <b>mbc_slave_init()</b>");
        E --> G(Define Data Structures<br>for Master Operations);
        F --> H(Define Data Structures<br>for Slave's Register Map);
        G & H --> I("Call <b>mbc_master_start()</b><br>or <b>mbc_slave_start()</b>");
        I --> J("Enter Main Application Loop<br><i>(Poll slaves or respond to master)</i>");
        J --> K(Success: Modbus<br>Communication Active);
    end

    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;

    class A primary;
    class D decision;
    class B,C,E,F,G,H,I,J process;
    class K success;

C
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define MB_UART_PORT_NUM      (UART_NUM_1) // Example UART port
#define MB_UART_BAUD_RATE     (19200)
#define MB_UART_TXD_PIN       (GPIO_NUM_17) // Example TX pin
#define MB_UART_RXD_PIN       (GPIO_NUM_16) // Example RX pin
#define MB_UART_RTS_PIN       (GPIO_NUM_18) // Used for RS485 DE/RE control
#define MB_UART_CTS_PIN       (UART_PIN_NO_CHANGE) // Not used for RS485 flow control

static const char *TAG = "MODBUS_INIT";

void initialize_modbus_uart(void) {
    uart_config_t uart_config = {
        .baud_rate = MB_UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE, // Or UART_PARITY_EVEN, UART_PARITY_ODD
        .stop_bits = UART_STOP_BITS_1,    // Or UART_STOP_BITS_2
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };

    ESP_LOGI(TAG, "Configuring UART parameters");
    ESP_ERROR_CHECK(uart_driver_install(MB_UART_PORT_NUM, 256 * 2, 256 * 2, 0, NULL, 0));
    ESP_ERROR_CHECK(uart_param_config(MB_UART_PORT_NUM, &uart_config));
    ESP_LOGI(TAG, "Setting UART pins (TX: %d, RX: %d, RTS: %d)",
             MB_UART_TXD_PIN, MB_UART_RXD_PIN, MB_UART_RTS_PIN);
    // For RS485, RTS is often used to control the DE/RE pins of the transceiver.
    // The UART driver can manage this automatically if configured.
    ESP_ERROR_CHECK(uart_set_pin(MB_UART_PORT_NUM, MB_UART_TXD_PIN, MB_UART_RXD_PIN,
                                 MB_UART_RTS_PIN, MB_UART_CTS_PIN));

    // Set RS485 mode. This configures the RTS pin to control the RS485 transceiver direction.
    ESP_ERROR_CHECK(uart_set_mode(MB_UART_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));

    ESP_LOGI(TAG, "UART driver installed and configured for RS485.");
}

// In your main app_main:
// void app_main(void) {
//     initialize_modbus_uart();
//     // ... further Modbus stack initialization (master or slave) would go here ...
//     // Refer to Chapters 177 and 178 for freemodbus usage.
// }

Warning: The code snippet above shows manual UART initialization. When using the freemodbus component from ESP-IDF, much of this setup (especially related to RTS for RS485 control) is handled internally by the library based on menuconfig settings. You would typically call mbc_master_init() or mbc_slave_init() which would configure the UART. However, knowing which pins and UART port are being used is essential.

4. Build Instructions

  1. Ensure your ESP-IDF environment is set up (refer to Chapter 2).
  2. Create a new project or use an existing one.
  3. Add the freemodbus component to your CMakeLists.txt if not already present for a higher-level component.
  4. Write your application code (e.g., main.c).
  5. Configure your project using idf.py menuconfig, paying close attention to Component config -> Modbus configuration.
  6. Build the project: idf.py build

5. Run/Flash/Observe

  1. Flash the compiled firmware to your ESP32: idf.py -p /dev/ttyUSB0 flash (replace /dev/ttyUSB0 with your ESP32’s serial port).
  2. Monitor the output: idf.py -p /dev/ttyUSB0 monitor.
  3. Observation: To observe Modbus RTU communication, you would need:
    • If your ESP32 is a Modbus slave: A Modbus master simulator software on a PC (connected via a USB-to-RS485 adapter) or another Modbus master device.
    • If your ESP32 is a Modbus master: A Modbus slave device or simulator.
    • A logic analyzer or oscilloscope connected to the RS-485 A/B lines can also be used to inspect the raw communication frames.

The actual implementation details for master and slave roles will be covered in the subsequent chapters.

Variant Notes

All ESP32 family variants are capable of implementing Modbus RTU due to the presence of UART peripherals.

  • ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2:
    • All these variants have multiple UART controllers (typically 2 or 3, check the specific datasheet). Any of these UARTs can be configured for Modbus RTU.
    • Physical Layer: An external RS-485 transceiver IC (e.g., MAX485, SP3485) is always required to convert the ESP32’s logic-level UART signals to the differential signals used by RS-485. The ESP32 provides the TX, RX, and a GPIO for direction control (connected to DE/RE pins of the transceiver).
    • The uart_set_mode(port, UART_MODE_RS485_HALF_DUPLEX) function in ESP-IDF is crucial for automatically handling the direction control pin (RTS pin of the UART peripheral).
  • ESP32-S2, ESP32-S3 (USB-OTG):
    • These variants feature USB-OTG controllers. While not directly Modbus RTU, a USB-to-RS485 adapter could be interfaced if the ESP32 is acting as a USB host. However, it’s more common to use the native UART peripherals for Modbus RTU.
  • ESP32-C6, ESP32-H2 (802.15.4 radio for Thread/Zigbee):
    • While these chips have advanced wireless capabilities, Modbus RTU remains a serial protocol. They would implement Modbus RTU using their UARTs just like other variants.
    • They could act as gateways, bridging Modbus RTU devices to wireless networks (e.g., Modbus RTU to Zigbee or Modbus RTU to Thread), but the Modbus RTU part itself is still standard serial communication.
  • Performance and Number of Instances:
    • The processing power of all ESP32 variants is generally more than sufficient for handling Modbus RTU communication, even multiple instances, alongside other application tasks. The freemodbus component in ESP-IDF supports multiple Modbus instances.

Tip: Always consult the specific datasheet for your chosen ESP32 variant to confirm the number of available UART peripherals and their pin assignments.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
Incorrect Wiring No communication at all, or garbled/random data received. Master reports timeouts. 1. Check A/B Lines: Ensure line A on one device connects to A on all others. Same for line B.
2. Verify TX/RX: ESP32 TX connects to transceiver’s DI. ESP32 RX connects to transceiver’s RO.
3. Direction Control: Ensure the GPIO for DE/RE is connected and configured correctly.
Mismatched Serial Parameters Master reports timeouts or CRC errors. Slave does not respond. Logic analyzer shows malformed frames. Verify that all devices on the bus are using identical settings for:
– Baud Rate (e.g., 9600, 19200)
– Data Bits (usually 8)
– Parity (usually None)
– Stop Bits (usually 1)
Incorrect Slave ID The specific slave device does not respond, while others on the same bus might work correctly. Master reports a timeout for that ID. 1. Ensure the master is requesting the correct Slave ID.
2. Verify the ID configured in the slave’s firmware or settings.
3. Make sure no two slaves on the same bus have the same ID.
RS-485 Direction Control Failure Data collisions (both master and slave try to transmit at once). Master receives its own transmitted message back. Slave responses are cut off. This is critical. Use the ESP-IDF UART driver’s built-in RS485 mode:
uart_set_mode(port, UART_MODE_RS485_HALF_DUPLEX);
Ensure the UART RTS pin is assigned in uart_set_pin() and connected to the DE/RE control on the transceiver. The driver will handle the timing automatically.
Missing or Improper Bus Termination Intermittent errors, CRC failures, especially on long cable runs or at high baud rates. Signal reflections cause data corruption. Place a 120 Ohm resistor across the A and B lines at the two extreme ends of the RS-485 bus. Do not place termination resistors on intermediate devices.
Incorrect Register Addressing Slave returns an ILLEGAL_DATA_ADDRESS (0x02) exception code. Master reads incorrect or zero-value data. Remember the difference between protocol address and convention address. A request for register 40001 is a request for Holding Register at address 0. Check your library/code to see which format it expects.
Task Priority / Timing Issues Modbus communication works sporadically, fails under heavy CPU load. Master reports timeouts because slave task is starved of CPU time. Ensure the task running the Modbus stack (e.g., polling via mbc_master_poll()) has a sufficiently high priority. Avoid long-running, blocking code in the same task.

Exercises

  1. Research Function Codes:
    • Research the following Modbus function codes: 0x07 (Read Exception Status), 0x08 (Diagnostics), 0x11 (Report Slave ID), 0x17 (Read/Write Multiple Registers). Briefly describe the purpose of each.
  2. Design a Modbus Map:
    • Imagine an ESP32-based device that monitors temperature and humidity, and controls a small fan.
    • Design a simple Modbus map for this device. Specify:
      • What data would be Input Registers (e.g., temperature, humidity)? Assign addresses (e.g., 30001, 30002).
      • What data would be Coils (e.g., fan control)? Assign an address (e.g., 00001).
      • What data might be Holding Registers (e.g., temperature setpoint for the fan)? Assign an address (e.g., 40001).
  3. CRC Calculation Practice:
    • Using an online Modbus RTU CRC calculator, calculate the 16-bit CRC for the following message (hex bytes):01 03 00 00 00 01 (Slave ID 1, Read Holding Registers, Start Address 0, Read 1 register)
    • What are the two CRC bytes (Low byte first, then High byte)?
  4. Identify RS-485 Transceivers:
    • Search for and list three different RS-485 transceiver ICs (e.g., from Texas Instruments, Analog Devices, Maxim Integrated) that are suitable for use with 3.3V microcontrollers like the ESP32. Note their key features (e.g., voltage supply range, data rate, driver/receiver enable pins).

Summary

  • Modbus is a widely used industrial serial communication protocol based on a master-slave architecture.
  • Modbus RTU is a binary variant commonly used over RS-485, offering efficiency and error checking.
  • A Modbus RTU frame consists of a Slave Address, Function Code, Data, and a 16-bit CRC.
  • Key data types include Coils (R/W bits), Discrete Inputs (R/O bits), Input Registers (R/O 16-bit words), and Holding Registers (R/W 16-bit words).
  • Function codes define the operation to be performed (e.g., read registers, write coils).
  • CRC ensures data integrity; mismatches lead to discarded messages.
  • Proper timing (inter-character and inter-frame delays) is critical for Modbus RTU.
  • Exception responses are used by slaves to indicate errors to the master.
  • ESP32 devices can implement Modbus RTU using their UART peripherals and an external RS-485 transceiver.
  • The ESP-IDF provides the freemodbus component to simplify Modbus master and slave development.

Further Reading

  • ESP-IDF Modbus Documentation:
    • Consult the official ESP-IDF Programming Guide for the Modbus controller and Modbus port API documentation. (Search for “Modbus” on the Espressif documentation website: https://docs.espressif.com/projects/esp-idf/)
  • Modbus Organization:
    • The official source for Modbus specifications and information: http://www.modbus.org/specs.php
    • Specifically, “MODBUS Application Protocol Specification V1.1b3” and “MODBUS over Serial Line Specification and Implementation Guide V1.02”.
  • FreeMODBUS User Manual:

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top