Whether it is RS-485 or MODBUS, their applications are very extensive in the fields of industrial automation, the Internet of Things, and embedded systems. Today, we will introduce the Modbus-RTU protocol based on RS-485 communication. We will gradually explain what Modbus is, from the 485 protocol to the Modbus protocol and then to the porting based on the FreeModbus library.1. What is RS-485?In the world of communication, RS-485 is more like a shared “country road”, rather than a point-to-point “highway” (like RS-232).
-
Electrical Characteristics: It uses differential signaling for data transmission. In simple terms, it represents logic “0” and “1” using the voltage differential on two lines (A and B). This design gives it a strong common-mode interference immunity capability, effectively canceling out noise introduced during long-distance transmission.
-
Topology: It supports bus topology, allowing up to 32 (standard) or even 128 (enhanced) device nodes to be connected on a single bus, greatly simplifying wiring.
-
Transmission Distance: Without using repeaters, the communication distance can reach 1200 meters, perfectly covering scenarios such as factory workshops and large buildings.
-
Half-Duplex Mode: Most RS-485 communications are half-duplex, meaning that at any given time, only one device on the bus can be “speaking” (sending data), while other devices must be “listening” (receiving data). This requires a clear “speaking rights” management mechanism.
Professional Summary: RS-485 solves the physical connection issues of long-distance, multi-node, and anti-interference, providing an ideal physical layer solution for networking field devices.
2. What is the Modbus Protocol?
If RS-485 is the road, then Modbus is the “traffic rules” that govern how data is packaged, addressed, and parsed.
Modbus is an application layer messaging protocol located at the 7th layer of the OSI model. It is independent of the underlying physical medium (although it is most commonly used over RS-485, it can also be used over TCP/IP networks, i.e., Modbus TCP).
Its core design philosophy is the Master-Slave architecture:
-
Master: The “commander” that actively initiates requests, usually a PLC, industrial computer, or SCADA system.
-
Slave: The “executor” that passively responds to requests, which can be variable frequency drives, sensors, smart meters, and other field devices. Each slave has a unique slave address (1-247).
Modbus defines various data models for accessing different functional areas within devices:
| Data Type | Object Type | Access Method | Function Code Example | |
|---|---|---|---|---|
| Coils | Read/Write | Bit | Read: 0x01 | Write: 0x05, 0x0F |
| Discrete Inputs | Read-Only | Bit | Read: 0x02 | |
| Holding Registers | Read/Write | 16-bit Word | Read: 0x03 | Write: 0x06, 0x10 |
| Input Registers | Read-Only | 16-bit Word | Read: 0x04 |
Professional Tip: Understanding these four data types is key to mastering Modbus programming. For example, the state of a digital output (DO) is typically mapped to a “coil”, while the measurement value of a temperature sensor is stored in an “input register”.
3. Collaboration: How RS-485 and Modbus Perfectly Integrate?
When RS-485 is combined with Modbus, a complete communication system is formed.
1. Physical Connection: All devices (master and slaves) connect their RS-485 interfaces (A and B lines) in parallel on the same twisted pair. Termination resistors of 120Ω need to be connected at both ends of the bus to eliminate communication errors caused by signal reflections at the ends of the cable.
2. Communication Process:
-
Master Inquiry: The master encapsulates a request according to the Modbus protocol frame format. The frame includes the target slave address, function code (what to do), starting address of the data, data length, and CRC checksum. It then broadcasts this over the RS-485 bus.
-
Slave Response: All slaves on the bus will receive this request, but only the slave with the matching address will process it. It performs the corresponding operation (such as reading register values or changing coil states) and then organizes a response frame according to the protocol format, sending it back to the bus.
-
Master Parsing: After receiving the response, the master parses the data, completing one communication cycle.
An example of a professional communication frame (reading holding registers):
Assuming the master wants to read from slave address 1, starting at address 0x0000 for 2 holding registers.
-
Master Request Frame (Hex):
<span><span>01 03 00 00 00 02 C4 0B</span></span> -
<span><span>01</span></span>: Slave address -
<span><span>03</span></span>: Function code (read holding registers) -
<span><span>00 00</span></span>: Starting address high/low byte -
<span><span>00 02</span></span>: Register count high/low byte -
<span><span>C4 0B</span></span>: CRC16 checksum (low byte first) -
Slave Response Frame (assuming register values are 0x1234 and 0x5678):
<span><span>01 03 04 12 34 56 78 0A B3</span></span> -
<span><span>01</span></span>: Slave address -
<span><span>03</span></span>: Function code -
<span><span>04</span></span>: Number of subsequent data bytes (4 bytes) -
<span><span>12 34 56 78</span></span>: Data from the two registers (0x1234 and 0x5678) -
<span><span>0A B3</span></span>: CRC16 checksum
For more protocol content, please refer to the Modbus protocol specification (in Chinese) or other online resources.
4. FreeModbus
4.1 Introduction
<span><span>FreeMODBUS</span></span> is a widely used open-source Modbus protocol stack designed for embedded systems. It implements both Modbus master and slave protocols and is widely used due to its small size, strong portability, and license-friendly (BSD style) nature. FreeMODBUS provides an implementation of Modbus TCP/ASCII/RTU protocols, especially suitable for running on resource-constrained microcontrollers (such as STM32, GD32, ESP32/8266, AVR, 8051, etc.).
Main Features
-
Supports Multiple Modes:
-
Slave Mode: Acts as a server, responding to master requests. This is the most commonly used function.
-
Master Mode: Acts as a client, actively initiating requests to slaves. (Note: Master functionality may be incomplete or require additional configuration in some versions or branches).
Supports Three Transmission Modes:
-
RTU: The most commonly used mode, based on serial communication (such as UART), with high efficiency.
-
ASCII: More readable, but less efficient than RTU.
-
TCP: Based on Ethernet and Socket communication.
Portability: It works through a porting layer related to the hardware platform, and users need to implement these interface functions based on the MCU and RTOS (such as FreeRTOS, μC/OS, or no operating system) they are using.
Open Source and Free: Uses a permissive BSD license, allowing use in commercial projects without copyright concerns.
4.2. Obtaining Source Code Get the FreeMODBUS source code from the official source or reliable repositories: http://www.freemodbus.org/4.3. Source Code Structure
-
<span><span>portevent.c</span></span>: Event handling interface. -
<span><span>portserial.c</span></span>: Serial port send/receive interface. -
<span><span>porttimer.c</span></span>: Timer interface (for RTU mode’s 3.5T timeout).
-
<span><span>mb.c</span></span>: Protocol stack entry, application interface. -
<span><span>rtu/mbrtu.c</span></span>: RTU mode processing. -
<span><span>ascii/mbascii.c</span></span>: ASCII mode processing. -
<span><span>tcp/mbtcp.c</span></span>: TCP mode processing.
-
<span><span>modbus/</span></span>: Core files, platform-independent, should not be modified. -
<span><span>port/</span></span>: Core of the porting work. You need to create or modify files for your platform here.
4.4. Porting
Serial Port Porting (<span><span>portserial.c</span></span>)
xMBPortSerialInit(): Initializes UART: baud rate, data bits (8), stop bits (1), parity bit (according to Modbus configuration). Configures the GPIO for 485 direction control as output mode and initializes it to receive state (low level). Enables UART receive and transmit interrupts.
xMBPortSerialPutByte(): Sends a byte to UART.
xMBPortSerialGetByte(): Receives a byte from UART.
Complete the serial port’s interrupt service routines (receive, transmit)
Key Logic for RS-485 Direction Control:
-
Start Sending: Before the protocol stack prepares to send a frame of data (usually when you implement
<span><span>xMBPortSerialPutByte</span></span>for the first time, or in the function pointed to by<span><span>pvMBFrameStartCur</span></span>), set the direction pin to send. -
End Sending: After the last byte (CRC checksum byte) has been sent, switch the direction pin back to receive using the TX complete interrupt or a timer. It is crucial to ensure that the entire data packet is completely sent from the physical bus before switching direction! You can check the UART’s TC (Transmission Complete) flag, not TXE (Transmit Data Register Empty).
Timer Porting (<span><span>porttimer.c</span></span>)
Mainly used for RTU mode’s T3.5 timeout. T3.5 represents the time for sending 3.5 characters over the serial port, which is primarily used to determine the end of a frame.
<span><span>xMBPortTimersInit(): Initializes a hardware timer, configured to generate a T3.5 timeout interrupt (for example, at a baud rate of 9600, T3.5 ≈ 3.5 * 11 / 9600 ≈ 4ms. When the baud rate exceeds 19200, this timeout is set to a fixed value of 1.75ms or 2ms).</span></span>
<span><span>vMBPortTimersEnable()</span></span>
<span><span>vMBPortTimersDisable()</span></span>
Timer Interrupt Service Routine
-
When T3.5 times out, call
<span><span>pxMBPortCBTimerExpired()</span></span>, notifying the protocol stack that a frame of data has been received.eMBErrorCode eMBRTUInit( UCHAR ucSlaveAddress, ULONG ulBaudRate ){ eMBErrorCode eStatus = MB_ENOERR; ULONG usTimerT35_50us; ( void )ucSlaveAddress; ENTER_CRITICAL_SECTION(); /* Modbus RTU uses 8 Databits. */ if( xMBPortSerialInit( ulBaudRate) != TRUE ) { eStatus = MB_EPORTERR; } else { /* If baudrate > 19200 then we should use the fixed timer values * t35 = 1750us. Otherwise t35 must be 3.5 times the character time. */ if( ulBaudRate > 19200 ) { usTimerT35_50us = 35; /* 1800us. */ } else { /* The timer reload value for a character is given by: * * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 ) * = 11 * Ticks_per_1s / Baudrate * = 220000 / Baudrate * The reload for t3.5 is 1.5 times this value and similary * for t3.5. */ usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate ); } if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE ) { eStatus = MB_EPORTERR; } } EXIT_CRITICAL_SECTION(); return eStatus;}
CRC Porting:
Complete the usMBCRC16 function in the mbcrc.c file as needed, which can be implemented in hardware or software, using either function or lookup table methods. Polynomial 0xA001.
unsigned short usMBCRC16( unsigned char *pucFrame, unsigned short usLen ){unsigned short i;unsigned short crc_res = 0xFFFF;while(usLen--){ crc_res ^= *pucFrame++; for(i = 0;i < 8;i++) { if(crc_res&0x01) { crc_res = (crc_res>>1)^0xA001; } else { crc_res = (crc_res>>1); } }}return crc_res;}
Configuration <span><span>mbconfig.h</span></span>
Enable <span><span>MB_RTU_ENABLED</span></span>, <span><span>MB_ASCII_ENABLED</span></span>, etc. as needed.
/*! rief If Modbus ASCII support is enabled. */#define MB_ASCII_ENABLED ( 0 )/*! rief If Modbus RTU support is enabled. */#define MB_RTU_ENABLED ( 1 )/*! rief If Modbus TCP support is enabled. */#define MB_TCP_ENABLED ( 0 )/*! rief If the <em>Report Slave ID</em> function should be enabled. */#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED ( 0 )/*! rief If the <em>Read Holding Registers</em> function should be enabled. */#define MB_FUNC_READ_HOLDING_ENABLED ( 1 )/*! rief If the <em>Write Single Register</em> function should be enabled. */#define MB_FUNC_WRITE_HOLDING_ENABLED ( 0 )/*! rief If the <em>Write Multiple registers</em> function should be enabled. */#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED ( 0 )/*! rief If the <em>Read/Write Multiple Registers</em> function should be enabled. */#define MB_FUNC_READWRITE_HOLDING_ENABLED ( 0 )#define MB_FUNC_READ_INPUT_ENABLED ( 1 )
Implement Callback Functions
The Modbus protocol stack accesses your device data by calling the callback functions you register, implement the following callback functions as needed:
-
<span><span>eMBRegInputCB()</span></span>: Read input registers (04 function code). -
<span><span>eMBRegHoldingCB()</span></span>: Read/Write holding registers (03, 06, 16 function codes). -
<span><span>eMBRegCoilsCB()</span></span>: Read/Write coils (01, 05, 15 function codes). -
<span><span>eMBRegDiscreteCB()</span></span>: Read discrete inputs (02 function code).
Main Process:
Initialize Modbus address and baud rate
eMBInit( MB_RTU, mbAddress, mbBaudrate); // Initialize Modbus
Start Modbus
eMBEnable(); // Start Modbus
Main loop callseMBPoll();
while(1){.....eMBPoll(); // Modbus protocol polling}
Debugging and Testing
-
Use a Debugger
-
Step through the code to ensure the serial port can correctly send and receive data.
-
Check the waveform of the 485 direction control signal to ensure it is high when sending data and promptly switches back to low after sending.
Use Modbus Debugging Tools
-
Use tools such as Modbus Poll or serial port tools with Modbus debugging on a PC.
-
Connect a USB to 485 adapter to your device.
-
Test basic read/write function codes (such as 03, 06, 16).
Common Issues
-
Check physical connections.
-
Check if the slave address is correct.
-
Use a logic analyzer or oscilloscope to capture the 485 bus waveform to see if data is being sent and if the direction control signal is correct.
-
Check if
<span><span>eMBPoll()</span></span>is being called in a loop.
-
CRC Errors: Check if the baud rate and data format match those of the master.
-
No Response:
-
Incomplete Response: This is usually due to switching the 485 direction too early, causing the last byte’s stop bit to not be fully sent before switching to receive mode, resulting in an incomplete CRC byte.
Professional Points in Development and Practice
In actual projects, successfully implementing stable and reliable Modbus-RS-485 communication also requires attention to:
-
Ports and Interrupts: Slave software typically configures the serial port for interrupt receive mode to avoid missing data sent by the master.
-
Direction Control: RS-485 is half-duplex, and the transmitter must be enabled before sending, and immediately switch back to receive state after sending. This timing control must be very precise; otherwise, the beginning part of the response frame may be lost.
-
Data Byte Order: When a 32-bit floating point or 32-bit integer needs to be stored in two consecutive 16-bit registers, the byte order (big-endian/little-endian) must be clearly defined. This is a common “pitfall” during data interchange between devices.
-
Exception Handling: The software must handle all Modbus-defined exceptions, such as illegal function codes, illegal data addresses, etc., and return the correct exception response frame.
Conclusion
Implementing Modbus RS-485 in software is a process of transforming rigorous protocol specifications into efficient and robust code. It tests not only the programmer’s understanding of the protocol itself but also their comprehensive ability to manage real-time performance, resource management, and stability in embedded systems.
Despite the emergence of various new industrial Ethernet protocols, this classic combination still holds an unshakable position in today’s industrial field due to its simplicity, open-source nature, maturity, and low cost. Understanding the underlying principles and key details of software implementation is a necessary course for every industrial communication, embedded development, and IoT engineer.