Modbus Communication Protocol Implementation Guide
Modbus is a serial communication protocol developed by Modicon (now Schneider Electric) in 1979 for programmable logic controllers (PLCs). Due to its simplicity and reliability, it has become a de facto standard in industrial automation.
Protocol Overview
Modbus operates on a master-slave architecture where one master device communicates with multiple slave devices. The protocol supports three main variants:
- Modbus RTU: Binary transmission mode, typically used with RS-485
- Modbus ASCII: ASCII character-based mode, commonly used with RS-232
- Modbus TCP/IP: TCP/IP-based mode for Ethernet communication
Data Storage Model
Modbus defines four distinct data types:
| Data Type | Access | Size | Typical Use |
|---|---|---|---|
| Coils | Read/Write | 1 bit | Binary control signals |
| Discrete Inputs | Read-only | 1 bit | Sensor status |
| Holding Registers | Read/Write | 16 bits | Configuration parameters |
| Input Registers | Read-only | 16 bits | Measurement values |
Common Function Codes
| Function Code | Name | Description |
|---|---|---|
| 0x01 | Read Coils | Read binary output status |
| 0x02 | Read Discrete Inputs | Read binary input status |
| 0x03 | Read Holding Registers | Read 16-bit configuration data |
| 0x04 | Read Input Registers | Read 16-bit sensor data |
| 0x05 | Write Single Coil | Write single binary output |
| 0x06 | Write Single Register | Write single 16-bit value |
| 0x0F | Write Multiple Coils | Write multiple binary outputs |
| 0x10 | Write Multiple Registers | Write multiple 16-bit values |
RS-485 Wiring Configuration
RS485 uses differential signaling for robust communication in noisy industrial environments. Proper wiring is critical:
Master Device Slave Device 1 Slave Device 2
A --------------- A --------------- A
B --------------- B --------------- B
GND ------------- GND ------------- GND
| |
120Ω 120Ω
Key requirements:
- Use shielded twisted pair cable (24 AWG recommended)
- Install 120Ω termination resistors at both ends of the bus
- All devices must share a common ground reference
- Maximum cable length: 1200 meters (depending on baud rate)
- Use bus topology, avoid star topology
STM32 Implementation Example
UART Configuration for Modbus RTU
For STM32F103C8T6, the serial peripheral can be configured as follows:
typedef struct {
USART_TypeDef *instance;
uint32_t baud_rate;
uint8_t data_bits;
uint8_t stop_bits;
uint8_t parity;
} modbus_uart_config_t;
void uart_init(modbus_uart_config_t *config) {
USART_InitTypeDef usart_init;
usart_init.USART_BaudRate = config->baud_rate;
usart_init.USART_WordLength = config->data_bits == 8 ?
USART_WordLength_8b : USART_WordLength_9b;
usart_init.USART_StopBits = config->stop_bits == 1 ?
USART_StopBits_1 : USART_StopBits_2;
usart_init.USART_Parity = config->parity == 0 ?
USART_Parity_None : USART_Parity_Even;
usart_init.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart_init.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART2, &usart_init);
USART_Cmd(USART2, ENABLE);
}
Serial Port Layer Implementation
typedef enum {
MB_PORT_OK = 0,
MB_PORT_ERROR
} mb_status_t;
mb_status_t serial_send_byte(uint8_t data) {
while (USART_GetFlagStatus(USART2, USART_FLAG_TC) == RESET);
USART_SendData(USART2, data);
return MB_PORT_OK;
}
mb_status_t serial_receive_byte(uint8_t *data) {
if (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) != RESET) {
*data = USART_ReceiveData(USART2);
return MB_PORT_OK;
}
return MB_PORT_ERROR;
}
CRC16 Calculation
Modbus RTU uses CRC-16 for error detection:
uint16_t calculate_crc16(const uint8_t *buffer, uint16_t length) {
uint16_t crc = 0xFFFF;
for (uint16_t i = 0; i < length; i++) {
crc ^= buffer[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = crc >> 1;
}
}
}
return crc;
}
Modbus TCP Implementation
For Ethernet-based communication, an external PHY module (such as W5500) is required since STM32F103C8T6 lacks an integrated Ethernet controller:
typedef struct {
uint8_t transaction_id[2];
uint8_t protocol_id[2];
uint8_t length[2];
uint8_t unit_id;
uint8_t function_code;
} modbus_tcp_header_t;
int modbus_tcp_process_request(uint8_t *request, uint16_t req_len,
uint8_t *response, uint16_t *resp_len) {
modbus_tcp_header_t *header = (modbus_tcp_header_t *)request;
uint8_t function_code = header->function_code;
switch (function_code) {
case 0x03: // Read holding registers
return handle_read_holding_registers(request, req_len,
response, resp_len);
case 0x06: // Write single register
return handle_write_register(request, req_len,
response, resp_len);
default:
return -1;
}
}
Frame Format
RTU Frame Structure
[Address] [Function Code] [Data] [CRC Low] [CRC High]
1 byte 1 byte N bytes 1 byte 1 byte
Frame timing requirement: Minimum 3.5 character idle time between frames.
TCP Frame Structure
[Transaction ID] [Protocol ID] [Length] [Unit ID] [Function Code] [Data]
2 bytes 2 bytes 2 bytes 1 byte 1 byte N bytes
Testing Tools
- Modbus Poll: Master device simulator for testing slave devices
- Modbus Slave: Simulates slave device responses
- QModMaster: Open-source Modbus testing utility
- Virtual Serial Port Driver: Creates paired COM ports for testing
For Linux, virtual COM port pairs can be created using socat:
sudo socat -d -d pty,raw,echo=0 pty,raw,echo=0
Development Considerations
- Baud Rate Selection: Higher baud rates (up to 115200) work well for short distances; lower rates (9600-19200) provide better reliability for longer cables.
- Timeout Handling: Implement proper timeout mechanisms for Modbus RTU frame detection.
- Error Handling: Monitor CRC errors and implement retry logic for reliable communication.
- Address Assignment: Each slave device must have a unique address (1-247) on the bus.
Conclusion
Modbus remains a widely adopted protocol in industrial automation due to its simplicity, robustness, and vendor neutrality. Understanding the protocol fundamentals and proper implemantation techniques enables reliable communication between PLCs, sensors, and control devices in industrial environments.