Helping setting up modbus client

So, I'm trying to setup Arduinos to be Modbus TCP/IP clients. I think I'm having a hard time understanding what code I need to have the modbus work.

One I want to have setup with a switch on it that takes the value of the switch and just 'holds' it in a coil. A server on another machine can suck up that data.

The other arduino I want to have a light attached to it, and a single discrete coil. When the coil gets written to by a server, the light comes on and off.

I think the pieces I need in my code to at least do the first one are:

#include <ArduinoModbus.h>
#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
ModbusTCPClient modbusTCPClient(ethClient);
slave.begin();

and then when in my looping code I run:

buttonState = digitalRead(switchPin);
if (buttonState == HIGH) {
    modbusTCPClient.coilWrite(0x05, 0x01);
  } else {
    modbusTCPClient.coilWrite(0x05, 0x00);
 }

My thought here is that this should:

  • Include the right libraries
  • Start modbus TCP client
  • Start my gizmo up as a slave
  • If the switch is high, write a value of 1 to register 5
  • If the switch is low, write a value of 0 to register 5

I've included my whole script below - but its first off ugly because I have a bunch of extra testing code in there. Second, it has some logic to read a switch, and turn on a light, and also write the value of the switch to the coil. It does two things at once.

For reference, I'm using
Server Tester on another PC to test and see if this works. I'm connecting to 10.0.1.88 in that app - but I'm confused past that what I should be setting it as. I've been doing 'Holding Register r03/w16' on device ID 1, Starting register 1 and getting errors.

Any help here would be great! Thanks!

#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoModbus.h>
#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library

//trying to set the IP of the shield
byte mac[] = { 0x88, 0xAB, 0xBE, 0x14, 0x9A, 0xED };
IPAddress local_IP(10,0,1,88);
IPAddress gateway(10,0,1,1);
IPAddress subnet(255,255,255,0);
IPAddress PrimaryDns(10,0,1,1);

//Variables for the switch to function
const int ledPin = 2;  // LED connected to digital pin 1
const int switchPin = 5; //Switch Input connected to pin 5
int buttonState = 0;

//Stuff needed to make Modbus work
EthernetClient ethClient;
ModbusTCPClient modbusTCPClient(ethClient);

//1 time run
void setup() {

  //set pin modes for the 2 pins defined above
  pinMode(ledPin, OUTPUT);  // sets the digital pin 13 as output
  pinMode(switchPin, INPUT);

  //turn on serial logging
  Serial.begin(9600);

  while (!Serial) {
  ;// wait for serial port to connect. Needed for native USB port only
  }


  // initialize the Ethernet device
  Ethernet.begin(mac, local_IP, PrimaryDns, gateway, subnet);

  // Init the ModbusTCPSlave object
  slave.begin();
  
  delay(5000);

  IPAddress ip = Ethernet.localIP();

  Serial.print("My IP address: ");
  Serial.println(ip);

}


void loop() {
  // put your main code here, to run repeatedly:
  Serial.println("Starting Loop");


  buttonState = digitalRead(switchPin);
  Serial.println("Value of switch:");
  Serial.print(buttonState);
  delay(1000);

 if (buttonState == HIGH) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
    modbusTCPClient.coilWrite(0x05, 0x01);
  } else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
    modbusTCPClient.coilWrite(0x05, 0x00);

 }

  Serial.print("My IP address: ");
  Serial.println(Ethernet.localIP());
}

You don't have a slave object declared anywhere

The thing you have to remember/learn is that when dealing with ModBus, the Server is the slave and the Client is the master.

Take a look at the examples that come with the library

This is how a slave device controls an LED

and

This is how your Master would toggle that LED

OMG - you are right. I grew up in the Master / Slave world - and I want to think Server / Client - but its Client / Server. ARGH.

OK - let me try again - thank you

So, the finer details are still a bit lost on me, but I think this is making more sense. I actually dont' need to really program anything new at all.

For the Arduino I want to have listen for a Client to write an output and then turn on an LED, that's exactly what EthernetModbusServerLED does and makes more sense. I would change the IP of my device in there, and change the value of ledPin to my output of 2. Then I go to my client of choice, connect to the IP of the Arduino - write a value of 1 to my coil at 0 and it should turn on.

The other one is a bit odder to me. This program is literally acting like a client where its writing a value to another device. Thats not what I want for my second Arduino. What I want is a device setup with a switch, and that value can be read FROM another client. For that one, I would use the ServerLED one again, but instead of an output, I would program it for an input.

Do I have that right?

Yes, I think so. When your Master is just reporting the state of the switch, it is not "coil" since those are an output device and you are configuring an "input" device. You want a discreetInput or you can use the more generic holding register. Look at the Modbus Server Class api for more details... ArduinoModbus/docs/api.md at master · arduino-libraries/ArduinoModbus · GitHub

So you are willing to build a multi-master setup? Control that it's supported by the libraries you use.

Thank you all ahead of time for your help! I’m trying so hard to get this working!

Again - I’m using almost the stock app as guidance here but I had to tweak a few things. My full code is below.

My client only reads and writes Holding registers. I didn’t think that was a big deal. I changed the line in the code:

'modbusTCPServer.configureCoils(0x01, 1);'
To
'modbusTCPServer.configureHoldingRegisters(0x01, 1);'

I also changed the updateLED subroutine and change the coilRead line to:
'int coilValue = modbusTCPServer.holdingRegisterRead(0x01);'
'Serial.println("Value is");'
'Serial.println(coilValue);'

I added the serial.println stuff so I could see the values without having to have LEDs all hooked up.

Either way, I can see when I go to my client and read or write 40001 that the Arduino says ‘Client Connected’ and then it just keeps saying Value is 0. If I write 1 or 16 or 200 to 40001 it just keeps saying the value is 0.

I’m not getting errors, but that value isn’t changing. From what I can tell, the command 'modbusTCPServer.poll();'
Is bascialy saying - “I’m allowing you to read or write to me”. But between that and the client, it isn’t working.

If I read the value, I get an error in my client saying 'Winsock error occurred during select of socket for readability - error=0"

I did hook up Wireshark and I can see that my client is trying to and is kinda talking to the Arduino. Like, when it reads, it says it’s reading Modbus, but it’s just not working. I’m still digging into that - and I can post the output if you need it.

Any ideas?

#include <SPI.h>
#include <Ethernet.h>

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
// The IP address will be dependent on your local network:
byte mac[] = { 0x88, 0xAB, 0xBE, 0x14, 0x9A, 0xED };
IPAddress local_IP(10,0,1,88);
IPAddress gateway(10,0,1,1);
IPAddress subnet(255,255,255,0);
IPAddress PrimaryDns(10,0,1,1);

EthernetServer ethServer(502);

ModbusTCPServer modbusTCPServer;

const int ledPin = 2;

void setup() {
  // You can use Ethernet.init(pin) to configure the CS pin
  //Ethernet.init(10);  // Most Arduino shields
  //Ethernet.init(5);   // MKR ETH shield
  //Ethernet.init(0);   // Teensy 2.0
  //Ethernet.init(20);  // Teensy++ 2.0
  //Ethernet.init(15);  // ESP8266 with Adafruit Featherwing Ethernet
  //Ethernet.init(33);  // ESP32 with Adafruit Featherwing Ethernet

  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Ethernet Modbus TCP Example");

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, local_IP, PrimaryDns, gateway, subnet);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  // start the server
  ethServer.begin();
  
  // start the Modbus TCP server
  if (!modbusTCPServer.begin()) {
    Serial.println("Failed to start Modbus TCP Server!");
    while (1);
  }

  // configure the LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // configure a single coil at address 0x00
  //modbusTCPServer.configureCoils(0x05, 1);
  modbusTCPServer.configureHoldingRegisters(0x05, 1);
}

void loop() {
  // listen for incoming clients
  EthernetClient client = ethServer.available();

  if (client) {
    // a new client connected
    Serial.println("new client");

    // let the Modbus TCP accept the connection 
    modbusTCPServer.accept(client);

    while (client.connected()) {
      // poll for Modbus TCP requests, while client connected
      modbusTCPServer.poll();

      // update the LED
      updateLED();
    }

    Serial.println("client disconnected");
  }
}

void updateLED() {
  // read the current value of the coil
  //int coilValue = modbusTCPServer.coilRead(0x05);
  int coilValue = modbusTCPServer.holdingRegisterRead(0x05);
  Serial.println("Value is");
  Serial.println(coilValue);

  if (coilValue) {
    // coil value set, turn LED on
    digitalWrite(ledPin, HIGH);
  } else {
    // coild value clear, turn LED off
    digitalWrite(ledPin, LOW);
  }
}

That is first holding register, 0x00 in hex (zero based).
0x01 would be 40002

Thank you so much! I'm still getting an error - is there a client you would suggest I try so I can be on the same page with you all and follow those specific errors?

Sorry, never played with ethernet/tcp , so I can't really give suggestions for that. If you have problems with modbus part, I'll try to support.

So - I've made progress! Using several different clients I'm able to write data to my register. That part seems to be working - I can see the number change in my Arduino back and forth. Whats funny is that I can't read that register.

Shoudl I be able to read a register created by

modbusTCPServer.configureHoldingRegisters(0x01, 1);

I mean, 40x registers are Rread and Write - but is there somethign I missing that causes my connection to time out?

How do you "see the number change in my Arduino back and forth" if you can not read the register?

Hi all - believe it or not, I've been working on this a lot. I've made some real progress, but I have one more hurdle and I can't figure it out.

I've ended up using Libmodbus on my Arduino. All it does is act as a Modbus Server - and turn a light on if a Client tells it to do so over Modbus.

My client is a Raspberry Pi using Python and pymodbus. Its a client with a physical input. If the input goes HI, then it turns a light on its own IO, and then tells the Arduino to also turn on.

It 'almost' works. When I run the program on my PI, the serial output on my Aruduino 'starts'. It loops and keeps saying "Value is 0" over and over. Thing is, when I flip the switch on the PI, it doesn't change - it keeps saying "value is 0" even though it should be 1. When I kill the program on my PI, the serial output on my Arduino says "Client Disconnected". So - I know the 2 can see each other, but the value isn't getting written.

To see if I could figure out where my mistake was, I setup my PC to be a Client and a Server.

  • When my PC was a Client, I used some freebie program to write random values to 40002 on the Arduino. It worked fine - I could write values to that register, and watch it change in my serial monitor.
  • To test the PI, I setup my PC with a freebie server package. When the PI tried to write its values to my PC it also worked fine. No problems.

So, the Pi can talk to my PC, and my PC can talk to the Arduino, but when I ask the Pi to talk to the ardunio - the ardunio recognizes that a socket is open, but the value of the register doesn't change.

I've included my scripts below. I feel like this is a data mismatch or something, but I don't see any options to edit that anywhere. Any help would be hugely appreciated!

My Arduino code:

#include <SPI.h>
#include <Ethernet.h>

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
// The IP address will be dependent on your local network:
byte mac[] = { 0x88, 0xAB, 0xBE, 0x14, 0x9A, 0xED };
IPAddress local_IP(10,0,1,88);
IPAddress gateway(10,0,1,1);
IPAddress subnet(255,255,255,0);
IPAddress PrimaryDns(10,0,1,1);

EthernetServer ethServer(502);

ModbusTCPServer modbusTCPServer;

const int ledPin = 2;

void setup() {
  // You can use Ethernet.init(pin) to configure the CS pin
  //Ethernet.init(10);  // Most Arduino shields
  //Ethernet.init(5);   // MKR ETH shield
  //Ethernet.init(0);   // Teensy 2.0
  //Ethernet.init(20);  // Teensy++ 2.0
  //Ethernet.init(15);  // ESP8266 with Adafruit Featherwing Ethernet
  //Ethernet.init(33);  // ESP32 with Adafruit Featherwing Ethernet

  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("Ethernet Modbus TCP Example");

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, local_IP, PrimaryDns, gateway, subnet);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    while (true) {
      delay(1); // do nothing, no point running without Ethernet hardware
    }
  }
  if (Ethernet.linkStatus() == LinkOFF) {
    Serial.println("Ethernet cable is not connected.");
  }

  // start the server
  ethServer.begin();
  
  // start the Modbus TCP server
  if (!modbusTCPServer.begin()) {
    Serial.println("Failed to start Modbus TCP Server!");
    while (1);
  }

  // configure the LED
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);

  // configure a single coil at address 0x00
  //modbusTCPServer.configureCoils(0x01, 1);
  modbusTCPServer.configureHoldingRegisters(0x01, 1);
}

void loop() {
  // listen for incoming clients
  EthernetClient client = ethServer.available();

  if (client) {
    // a new client connected
    Serial.println("new client");

    // let the Modbus TCP accept the connection 
    modbusTCPServer.accept(client);

    while (client.connected()) {
      // poll for Modbus TCP requests, while client connected
      modbusTCPServer.poll();

      // update the LED
      updateLED();
    }

    Serial.println("client disconnected");
  }
}

void updateLED() {
  // read the current value of the coil
  //int coilValue = modbusTCPServer.coilRead(0x01);
  int coilValue = modbusTCPServer.holdingRegisterRead(0x01);
  Serial.println("Value is");
  Serial.println(coilValue);

  if (coilValue) {
    // coil value set, turn LED on
    digitalWrite(ledPin, HIGH);
  } else {
    // coild value clear, turn LED off
    digitalWrite(ledPin, LOW);
  }
}

My Pi code:

import RPi.GPIO as GPIO
import pymodbus
from pymodbus.client import ModbusTcpClient
import time

# Setup GPIO
SWITCH_PIN = 17# GPIO pin for the switch
LED_PIN = 18# GPIO pin for the LED

# MODBUS configuration
MODBUS_IP = '10.0.1.88'
MODBUS_PORT = 503
MODBUS_REGISTER = 40002

# Setup GPIO mode
GPIO.setmode(GPIO.BCM)
GPIO.setup(SWITCH_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)  # Switch input (with pull-down resistor)
GPIO.setup(LED_PIN, GPIO.OUT)  # LED output

# Setup MODBUS TCP client
client = ModbusTcpClient(MODBUS_IP, port=MODBUS_PORT)

def write_modbus(value):
    """Write a value to the MODBUS register 40001"""
    try:
        # Write the value to the register 40001
        client.write_register(MODBUS_REGISTER, value)
        print(f"Written value {value} to MODBUS register {MODBUS_REGISTER}")
    except Exception as e:
        print(f"Error writing to MODBUS: {e}")

def read_switch():
    """Read the status of the switch"""
    return GPIO.input(SWITCH_PIN) == GPIO.HIGH

def control_led_and_modbus():
    """Control LED and write to MODBUS based on switch state"""
    if read_switch():
        print("Switch is ON. Turning on LED.")
        GPIO.output(LED_PIN, GPIO.HIGH)  # Turn on LED
        write_modbus(55)  # Write value 1 to MODBUS register
    else:
        print("Switch is OFF. Turning off LED.")
        GPIO.output(LED_PIN, GPIO.LOW)  # Turn off LED
        write_modbus(19)  # Write value 0 to MODBUS register

def main():
    """Main loop to monitor the switch and control the LED and MODBUS"""
    try:
        while True:
            control_led_and_modbus()
            time.sleep(0.5)  # Check every half second
    except KeyboardInterrupt:
        print("Exiting program")
    finally:
        GPIO.cleanup()  # Clean up GPIO settings

if __name__ == '__main__':
    main()

I feel like that was a lot of detail - and I hope the question didn't get lost. Essentially, my Arduino Server has a holding register that a Client can write to. I have that setup, but when a Raspbery Pi Client writes to the register on the Arduino - it comes back 0. Any other Client, like my PC, can write any value.

Why would the Arduino value not change when its written to by the RPi running PyModbus?

Do you have some documentation describing this will be translated to 0x01 ?
If not, try with
MODBUS_REGISTER = 2

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.