Skip to main content
Kinematrix provides a complete Modbus implementation supporting both RTU and ASCII modes with master and slave configurations for industrial communication.

Overview

Modbus is a widely-used industrial communication protocol designed for reliable data exchange between electronic devices in automation and control systems.

Modbus Slave

Respond to master requests and manage register banks

Register Banks

Organize and manage digital and analog data

Protocol Features

FeatureDescription
ModesRTU (binary) and ASCII (text-based)
Baud Rates9600-115200 bps (configurable)
Error DetectionCRC16 checksum validation
Register TypesDigital I/O (DO/DI) and Analog (AO/AI)
Network SizeUp to 10 devices per network
Function CodesRead/write digital and analog values

Module Architecture

The Modbus implementation consists of three main classes:
modbusSlave       // Communication handler and protocol engine
modbusDevice      // Device identity and register bank management  
modbusRegBank     // Data storage and type conversion

Function Codes

Kinematrix supports standard Modbus function codes:
lib/modules/communication/wired/modbus/modbus.h
// Read functions
#define READ_DO   0x01  // Read Digital Outputs (Coils)
#define READ_DI   0x02  // Read Digital Inputs  
#define READ_AO   0x03  // Read Analog Outputs (Holding Registers)
#define READ_AI   0x04  // Read Analog Inputs (Input Registers)

// Write functions
#define WRITE_DO  0x05  // Write Single Digital Output
#define WRITE_AO  0x06  // Write Single Analog Output

Modbus Slave Implementation

Basic Configuration

#define ENABLE_MODULE_MODBUS_SLAVE
#include "Kinematrix.h"

SoftwareSerial modbusSerial(10, 11);  // RX, TX
modbusSlave slave;
modbusDevice device;

void setup() {
  Serial.begin(115200);
  
  // Configure Modbus serial communication
  slave.setBaud(&modbusSerial, 9600);
  
  // Set device ID (1-247)
  device.setId(1);
  
  // Link device to slave
  slave._device = &device;
  
  // Initialize registers
  device.add(0);   // Digital register at address 0
  device.add(100); // Analog register at address 100
  device.add(101); // Analog register at address 101
}

void loop() {
  // Process incoming Modbus requests
  slave.run();
}

Modbus Slave Class

lib/modules/communication/wired/modbus/modbusSlave.h
class modbusSlave {
public:
    modbusSlave(void);
    void setBaud(SoftwareSerial *, word);
    word getBaud(void);
    void calcCrc(void);
    void checkSerial(void);
    void serialRx(void);
    void getDigitalStatus(byte, word, word);
    void getAnalogStatus(byte, word, word);
    void setStatus(byte, word, word);
    void run(void (*callback)() = nullptr);

    modbusDevice *_device;

private:
    SoftwareSerial *_serial;
    byte *_msg, _len;
    word _baud, _crc, _frameDelay;
};

With Custom Callback

void onModbusRequest() {
  Serial.println("Modbus request received");
}

void loop() {
  slave.run(onModbusRequest);
}

Register Bank Management

The register bank stores and manages both digital and analog data with type conversion utilities.

Register Bank Class

lib/modules/communication/wired/modbus/modbusRegBank.h
class modbusRegBank {
public:
    modbusRegBank(void);
    void add(word);
    word get(word);
    void set(word, word);

    // Integer operations
    void sendDataInt(int value, long address);
    int getDataInt(long address);
    
    // Long operations
    void sendDataLong(long value, long address);
    long getDataLong(long address);
    
    // String operations
    void sendDataString(const String &value, long address, size_t maxLength);
    String getDataString(long address, size_t maxLength);
    
    // Float operations
    void sendDataFloat(float value, long address);
    float getDataFloat(long address);
    
    // Double operations
    void sendDataDouble(double value, long address);
    double getDataDouble(long address);

private:
    modbusDigReg *_digRegs, *_lastDigReg;
    modbusAnaReg *_anaRegs, *_lastAnaReg;
};

Register Types

lib/modules/communication/wired/modbus/modbusRegBank.h
// Digital register (1 bit per address)
struct modbusDigReg {
    word address;
    byte value;           // 0 or 1
    modbusDigReg *next;
};

// Analog register (16-bit per address)
struct modbusAnaReg {
    word address;
    word value;           // 0-65535
    modbusAnaReg *next;
};

Data Type Handling

Integer Values

// Write integer to registers
device.sendDataInt(1234, 100);

// Read integer from registers  
int value = device.getDataInt(100);

Long Values

// Long values use 2 consecutive registers
device.sendDataLong(123456789L, 200);

long timestamp = device.getDataLong(200);

Float Values

// Float uses 2 registers (IEEE 754 format)
float temperature = 25.67;
device.sendDataFloat(temperature, 300);

float reading = device.getDataFloat(300);

Double Values

// Double uses 4 registers
double precision = 3.14159265359;
device.sendDataDouble(precision, 400);

double value = device.getDataDouble(400);

String Values

// Strings stored as ASCII in consecutive registers
device.sendDataString("SENSOR_01", 500, 16);

String deviceName = device.getDataString(500, 16);

Complete Sensor Example

#define ENABLE_MODULE_MODBUS_SLAVE
#include "Kinematrix.h"

SoftwareSerial modbusSerial(10, 11);
modbusSlave slave;
modbusDevice device;

// Sensor simulation
float temperature = 25.0;
float humidity = 60.0;
int sensorStatus = 1;  // 1 = OK, 0 = Error

void setup() {
  Serial.begin(115200);
  
  // Configure Modbus
  slave.setBaud(&modbusSerial, 9600);
  device.setId(1);
  slave._device = &device;
  
  // Register map:
  // 0-9: Digital registers
  // 100-199: Analog registers
  
  // Digital: Sensor status
  device.add(0);
  device.set(0, sensorStatus);
  
  // Analog: Temperature (x100)
  device.add(100);
  
  // Analog: Humidity (x100)
  device.add(101);
  
  Serial.println("Modbus Sensor Ready - ID: 1");
}

void loop() {
  // Update sensor readings
  updateSensors();
  
  // Update Modbus registers
  device.sendDataFloat(temperature, 100);
  device.sendDataFloat(humidity, 101);
  device.set(0, sensorStatus);
  
  // Process Modbus requests
  slave.run();
  
  delay(100);
}

void updateSensors() {
  // Simulate sensor readings
  static unsigned long lastUpdate = 0;
  
  if (millis() - lastUpdate > 5000) {
    lastUpdate = millis();
    
    temperature = 20.0 + random(0, 100) / 10.0;
    humidity = 50.0 + random(0, 200) / 10.0;
    sensorStatus = (temperature < 40.0) ? 1 : 0;
    
    Serial.print("T: ");
    Serial.print(temperature);
    Serial.print("°C, H: ");
    Serial.print(humidity);
    Serial.println("%");
  }
}

Device Configuration

Modbus Device Class

lib/modules/communication/wired/modbus/modbusDevice.h
class modbusDevice : public modbusRegBank {
public:
    modbusDevice(void);
    void setId(byte id);  // Set device ID (1-247)
    byte getId(void);     // Get device ID

private:
    byte _id;
};

Multiple Devices

modbusDevice sensor1;
modbusDevice sensor2;

void setup() {
  sensor1.setId(1);
  sensor2.setId(2);
  
  // Device 1 registers
  sensor1.add(0);
  sensor1.add(100);
  
  // Device 2 registers  
  sensor2.add(0);
  sensor2.add(200);
}

Protocol Configuration

RTU vs ASCII Mode

lib/modules/communication/wired/modbus/modbus.h
#define RTU    0x01  // Binary mode (default)
#define ASCII  0x02  // ASCII mode

#define MASTER 0x01  // Master role
#define SLAVE  0x02  // Slave role

Register Types

#define DO  0x00  // Digital Output (Coil)
#define DI  0x01  // Digital Input
#define AI  0x03  // Analog Input
#define AO  0x04  // Analog Output (Holding Register)

Communication Parameters

lib/modules/communication/wired/modbus/modbus.h
#define DEVMAX          10    // Maximum devices on network
#define QUEMAX          10    // Maximum control register queue
#define SERIALMAXDELAY  100   // Serial wait time (microseconds)
#define SERIALBAUD      9600  // Default baud rate

CRC Error Detection

Modbus RTU uses CRC16 for error detection:
void modbusSlave::calcCrc(void) {
    // CRC calculation using lookup tables
    // _auchCRCHi[] and _auchCRCLo[]
}
The CRC is automatically calculated and verified for all messages.

Register Address Mapping

Standard Modbus Address Ranges

Register TypeFunction CodeAddress RangeUsage
Coils (DO)01, 050-9999Read/write digital outputs
Discrete Inputs (DI)0210000-19999Read-only digital inputs
Input Registers (AI)0430000-39999Read-only analog inputs
Holding Registers (AO)03, 0640000-49999Read/write analog outputs

Example Register Map

// Digital Coils (0-99)
device.add(0);   // Emergency stop
device.add(1);   // Motor enable
device.add(2);   // Alarm status

// Analog Holding Registers (100-199)
device.add(100); // Temperature sensor
device.add(101); // Pressure sensor
device.add(102); // Flow rate
device.add(103); // Motor speed setpoint

// Analog Input Registers (200-299)
device.add(200); // Voltage reading
device.add(201); // Current reading

Hardware Configuration

RS-485 Wiring

Modbus typically uses RS-485 for multi-drop networks:
Arduino          MAX485 Module
  TX    -------> DI
  RX    <------- RO  
  D2    -------> DE/RE (direction control)
  5V    -------> VCC
  GND   -------> GND
  
RS-485 Bus:
  A     <------> A (all devices)
  B     <------> B (all devices)
  
Termination: 120Ω resistor between A and B at both ends

MAX485 Direction Control

#define DE_PIN 2

void setup() {
  pinMode(DE_PIN, OUTPUT);
  digitalWrite(DE_PIN, LOW);  // Receive mode
}

// Before transmitting
digitalWrite(DE_PIN, HIGH);  // Transmit mode
modbusSerial.write(data);
delay(10);
digitalWrite(DE_PIN, LOW);   // Back to receive mode

Troubleshooting

Check:
  • Device ID matches the request
  • Baud rate matches on master and slave
  • RS-485 wiring (A to A, B to B)
  • Termination resistors (120Ω at both ends)
  • DE/RE pin control on MAX485
Debug:
void loop() {
  if (modbusSerial.available()) {
    Serial.print("Received: 0x");
    Serial.println(modbusSerial.read(), HEX);
  }
  slave.run();
}
Possible causes:
  • Electrical noise on RS-485 bus
  • Incorrect baud rate
  • Missing termination resistors
  • Cable too long (max 1200m)
Solutions:
  • Add 120Ω termination resistors
  • Use shielded twisted-pair cable
  • Reduce baud rate for longer cables
  • Check ground connections
Check:
  • Power supply stability
  • DE/RE timing (add delays if needed)
  • Buffer overflow (increase QUEMAX)
  • Cable quality and connections
Solution:
// Add delay after direction change
digitalWrite(DE_PIN, HIGH);
delayMicroseconds(50);  // Let transceiver settle
modbusSerial.write(buffer, length);
modbusSerial.flush();
delayMicroseconds(50);
digitalWrite(DE_PIN, LOW);

Best Practices

Bus Termination: Always install 120Ω termination resistors at both ends of the RS-485 bus to prevent signal reflections.
Register Organization: Group related registers together and document your register map. This makes debugging and integration much easier.
Timing: Modbus RTU requires specific inter-frame delays. The library handles this automatically, but be aware when debugging timing-critical applications.

Industrial Applications

Process Control

Monitor and control industrial processes with PLC communication

Building Automation

HVAC control, lighting systems, and energy monitoring

Factory Automation

Conveyor systems, robotic controls, and production monitoring

Remote Monitoring

Sensor networks for environmental and equipment monitoring

Serial Variants

Alternative serial communication methods

LoRa

Wireless alternative for remote monitoring

Build docs developers (and LLMs) love