ESP32 MODBUS between multiple devices

Thread Starter

zazas321

Joined Nov 29, 2015
722
Hello. I have an automation system with ESP32 devices. I use MQTT to communicate between each node and the server ( Raspberry PI). On our custom ESP32 PCB, we have also designed an MODBUS in case MQTT fails and we still want to transfer data between the nodes.

I have never used MODBUS before and just trying to understand it for now.
Our circuit looks like :
1601364972628.png

We use this ADM2582 chip and connect USART2 TX and RX to the pins of the IC as well as additional GPIO for direction.


We use ethernet cable (POE) which also has MODBUS pins wired to it (pin 1, 2 on J3). We daisy chain connect all the ESP32 nodes together using this cable. One cable comes in, the 2nd cable comes out.

I have come across this MODBUS RTU library for ESP32:
https://github.com/bertmelis/esp32ModbusRTU

I am reading the usage step by step and trying to understand it:

Since I am using USART2, I would need to create serial object:
Code:
HardwareSerial modbus(2);
and use this object :
Code:
esp32ModbusRTU myModbus(&modbus, DE_PIN);
But I cannot understand this function:
1601365360557.png

How do I know my server ID, and what address?? If I understand properly,this function is supposed to transmit some data through MODBUS right?


Just some additional question:
Can I transfer JSON format data using modbus?
 
Last edited:

bogosort

Joined Sep 24, 2011
524
How do I know my server ID, and what address?? If I understand properly,this function is supposed to transmit some data through MODBUS right?
You should read up on the Modbus protocol. The 60,000-ft view is that one device is a Modbus master, which can read and/or write to one or more other devices that act as Modbus slaves. Each slave presents its data as addressable "registers", and the Modbus protocol specifies four basic data types, classified by the data size and read/write access:
1601383282181.png
That image from Wikipedia's Modbus page shows that there are two 1-bit data types ("coils" for data that should have read-write access, and "discrete inputs" for read-only single bit data), and two 16-bit data types (read-only: "input registers"; read-write: "holding registers"). The address space describes how those registers are accessed.

Let's use a simple example. Suppose you have a device that collects temperature data as 32-bit integers, which you want to make available to another device. You setup the temperature collection device as a Modbus slave and, using your Modbus library's API, have it write the last ten temperature samples to the first ten input registers.

In absolute terms, this corresponds to addresses 30001 through 30011. However, the Modbus protocol specifies read/write "functions" for each data type, and these functions invariably use relative addressing. For example, the library's readInputRegisters() function can only read from addresses that are between 30001 and 39999, so it's redundant to include the '3' in any input register address. Instead, the function expects a relative offset referenced to the absolute address. In other words, input register address 0 corresponds to absolute address 30001; likewise, input register address 1 corresponds to absolute address 30002. Thus, in the readInputRegisters() example you provided, the author of the code is asking for address 52 + 30001 = 30053. Since a length of '2' was provided, the function will return an array of data with two elements, the values of addresses 52 and 53 (in absolute terms, 30053 and 30054).

Note that, with most libraries, you never have to worry about absolute addressing. You simply call the appropriate function you want -- e.g., writeHoldingRegisters() -- with the address offset, and it will translate the address into the correct address space.

Back to the example: using the library's API, your temperature collector writes the last ten temp values to addresses 0 through 9. When your master device wants to query these values, it calls readInputRegisters(1, 0, 10). The first parameter (1, the 'serverID') refers to the slave device's Modbus node number, which you set in the slave when you initialize the Modbus object. Each Modbus slave on the network should be given its own node number, but you seem to be using only one slave, so '1' is enough.

You could also set up a few "coils" on the slave, which the master can use to control the slave, e.g., starting/stopping data collection. Once you get the hang of how communication works, Modbus is quite easy to use.

Just some additional question:
Can I transfer JSON format data using modbus?
Yes, using the 16-bit registers, you can send anything you want in 16-bit chunks. Your master device will have to re-encode the data as JSON format. For example, suppose your JSON string is
Code:
{
 "temperature": 8394
}
Assuming ASCII encoding, each character in the string is 8 bits, and so each holding register could hold two characters. If your JSON is short and always the same string except for the value field, then encoding/decoding is easy. On the other hand, if your JSON is large or apt to change, then it'd probably be best to treat each register as representing a different JSON key (with its value in the data), and have your master code create the JSON from the data.
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
You should read up on the Modbus protocol. The 60,000-ft view is that one device is a Modbus master, which can read and/or write to one or more other devices that act as Modbus slaves. Each slave presents its data as addressable "registers", and the Modbus protocol specifies four basic data types, classified by the data size and read/write access:
View attachment 218314
That image from Wikipedia's Modbus page shows that there are two 1-bit data types ("coils" for data that should have read-write access, and "discrete inputs" for read-only single bit data), and two 16-bit data types (read-only: "input registers"; read-write: "holding registers"). The address space describes how those registers are accessed.

Let's use a simple example. Suppose you have a device that collects temperature data as 32-bit integers, which you want to make available to another device. You setup the temperature collection device as a Modbus slave and, using your Modbus library's API, have it write the last ten temperature samples to the first ten input registers.

In absolute terms, this corresponds to addresses 30001 through 30011. However, the Modbus protocol specifies read/write "functions" for each data type, and these functions invariably use relative addressing. For example, the library's readInputRegisters() function can only read from addresses that are between 30001 and 39999, so it's redundant to include the '3' in any input register address. Instead, the function expects a relative offset referenced to the absolute address. In other words, input register address 0 corresponds to absolute address 30001; likewise, input register address 1 corresponds to absolute address 30002. Thus, in the readInputRegisters() example you provided, the author of the code is asking for address 52 + 30001 = 30053. Since a length of '2' was provided, the function will return an array of data with two elements, the values of addresses 52 and 53 (in absolute terms, 30053 and 30054).

Note that, with most libraries, you never have to worry about absolute addressing. You simply call the appropriate function you want -- e.g., writeHoldingRegisters() -- with the address offset, and it will translate the address into the correct address space.

Back to the example: using the library's API, your temperature collector writes the last ten temp values to addresses 0 through 9. When your master device wants to query these values, it calls readInputRegisters(1, 0, 10). The first parameter (1, the 'serverID') refers to the slave device's Modbus node number, which you set in the slave when you initialize the Modbus object. Each Modbus slave on the network should be given its own node number, but you seem to be using only one slave, so '1' is enough.

You could also set up a few "coils" on the slave, which the master can use to control the slave, e.g., starting/stopping data collection. Once you get the hang of how communication works, Modbus is quite easy to use.


Yes, using the 16-bit registers, you can send anything you want in 16-bit chunks. Your master device will have to re-encode the data as JSON format. For example, suppose your JSON string is
Code:
{
"temperature": 8394
}
Assuming ASCII encoding, each character in the string is 8 bits, and so each holding register could hold two characters. If your JSON is short and always the same string except for the value field, then encoding/decoding is easy. On the other hand, if your JSON is large or apt to change, then it'd probably be best to treat each register as representing a different JSON key (with its value in the data), and have your master code create the JSON from the data.
Thank you very much for good information! I understand more about MODBUS now. Il try and get it working just need to fix some hardware issues on my PCB's first.
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
H
You should read up on the Modbus protocol. The 60,000-ft view is that one device is a Modbus master, which can read and/or write to one or more other devices that act as Modbus slaves. Each slave presents its data as addressable "registers", and the Modbus protocol specifies four basic data types, classified by the data size and read/write access:
View attachment 218314
That image from Wikipedia's Modbus page shows that there are two 1-bit data types ("coils" for data that should have read-write access, and "discrete inputs" for read-only single bit data), and two 16-bit data types (read-only: "input registers"; read-write: "holding registers"). The address space describes how those registers are accessed.

Let's use a simple example. Suppose you have a device that collects temperature data as 32-bit integers, which you want to make available to another device. You setup the temperature collection device as a Modbus slave and, using your Modbus library's API, have it write the last ten temperature samples to the first ten input registers.

In absolute terms, this corresponds to addresses 30001 through 30011. However, the Modbus protocol specifies read/write "functions" for each data type, and these functions invariably use relative addressing. For example, the library's readInputRegisters() function can only read from addresses that are between 30001 and 39999, so it's redundant to include the '3' in any input register address. Instead, the function expects a relative offset referenced to the absolute address. In other words, input register address 0 corresponds to absolute address 30001; likewise, input register address 1 corresponds to absolute address 30002. Thus, in the readInputRegisters() example you provided, the author of the code is asking for address 52 + 30001 = 30053. Since a length of '2' was provided, the function will return an array of data with two elements, the values of addresses 52 and 53 (in absolute terms, 30053 and 30054).

Note that, with most libraries, you never have to worry about absolute addressing. You simply call the appropriate function you want -- e.g., writeHoldingRegisters() -- with the address offset, and it will translate the address into the correct address space.

Back to the example: using the library's API, your temperature collector writes the last ten temp values to addresses 0 through 9. When your master device wants to query these values, it calls readInputRegisters(1, 0, 10). The first parameter (1, the 'serverID') refers to the slave device's Modbus node number, which you set in the slave when you initialize the Modbus object. Each Modbus slave on the network should be given its own node number, but you seem to be using only one slave, so '1' is enough.

You could also set up a few "coils" on the slave, which the master can use to control the slave, e.g., starting/stopping data collection. Once you get the hang of how communication works, Modbus is quite easy to use.


Yes, using the 16-bit registers, you can send anything you want in 16-bit chunks. Your master device will have to re-encode the data as JSON format. For example, suppose your JSON string is
Code:
{
"temperature": 8394
}
Assuming ASCII encoding, each character in the string is 8 bits, and so each holding register could hold two characters. If your JSON is short and always the same string except for the value field, then encoding/decoding is easy. On the other hand, if your JSON is large or apt to change, then it'd probably be best to treat each register as representing a different JSON key (with its value in the data), and have your master code create the JSON from the data.






Hey. I was not able to find any library that would make sense to me.
I have decided to build my own modbus functions. I am looking at modbus application notes:
https://modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf

The starting address for various function codes are 0x0000 to 0xFFFF therefore I cannot fully understand your previously suggested table with addresses:
1603278587126.png

1603278535495.png


For example, wirint to a single coil:

1603278641663.png

Based on the documentation I have read about writing data to a coil, I have written my own function:
Code:
void Write_coil(int slave_ID, uint16_t starting_address, uint16_t Output_value){
  digitalWrite(DIR,HIGH);
  uint8_t buff[8];
  uint16_t crc;
  uint16_t swapped_crc;
  byte address_LSB = (starting_address & 0x00FF);
  byte address_MSB = ((starting_address & 0xFF00) >>8);
  byte Output_LSB = (Output_value & 0x00FF);
  byte Output_MSB = ((Output_value & 0xFF00) >>8);


  
  Serial2.write(slave_ID);
  Serial2.write(5);
  Serial2.write(address_MSB);
  Serial2.write(address_LSB);
  Serial2.write(Output_MSB);
  Serial2.write(Output_LSB);
  
  buff[0]=slave_ID;
  buff[1]=5;
  buff[2]=address_MSB;
  buff[3]=address_LSB;
  buff[4]=Output_MSB;
  buff[5]=Output_LSB;


  crc=CRC16_2(buff,6);
  swapped_crc=swapByteOrder(crc);

  Serial2.write((swapped_crc & 0xFF00) >>8); //CRC MSB
  Serial2.write((swapped_crc & 0x00FF)); // CRC LSB
  buff[6]=(swapped_crc & 0xFF00) >>8;
  buff[7]=(swapped_crc & 0x00FF);
  
  //QUANTITY TO READ
  Serial.print( "address_MSB Byte: " );   Serial.println( address_MSB );   // <-- prints 9
  Serial.print( "address_LSB Byte: " );   Serial.println( address_LSB );   // <-- prints 60
  Serial.print( "Output_MSB Byte: " );   Serial.println( Output_MSB );   // <-- prints 9
  Serial.print( "Output_LSB Byte: " );   Serial.println( Output_LSB );   // <-- prints 60
  for(int i=0 ; i<8; i++){
    Serial.print("Byte number ");
    Serial.print(i);
    Serial.print("=");
    Serial.println(buff[i]);
  }

  
  digitalWrite(DIR,LOW);
}

What I am concred though is how do I determine which device is my master device and which is my slave device? How do I configure one of my ESP32 devices to be master?

I just want to be able to write to coil on one ESP32 (master), and read from another ESP32 devices when required (Slave)

I can implement simmilar function for reading single coil just by changing function code, but that is not going to work since I havent declared my master device? Im not sure on that..

Also, from reading the datasheet about reading coil:
1603280266515.png
It is not fully clear for me how do I handle the response? When I to read coil on my ESP32 device, how can I read the data ?
 
Last edited:

bogosort

Joined Sep 24, 2011
524
Hey. I was not able to find any library that would make sense to me.
I have decided to build my own modbus functions.
I'd recommend against implementing your own Modbus library, especially if you're having a hard time understanding existing libraries. What's your goal here? To become an expert in Modbus? To allow your devices to communicate with third-party devices that speak Modbus? Do you really want to spend the time and effort to implement and thoroughly test all the tedious details of a communications protocol -- message parsing, out-of-order handling, error handling, time outs, etc., etc.?

The starting address for various function codes are 0x0000 to 0xFFFF therefore I cannot fully understand your previously suggested table with addresses:
View attachment 220172
This is a needlessly confusing aspect of Modbus due to its long history and weak standard enforcement among the various implementations. The most important thing to keep in mind is that when people talk about the location of data in Modbus, they are referring to either addresses, register numbers, or offsets, all of which are related but distinct concepts.

The address field in a Modbus packet is 16 bits wide, so the address space for any request is 0x0000 to 0xFFFF, a total of 65,536 possible addresses. That much is unambiguous and clear. But each request also includes a Modbus function that accesses a particular type of register. There are four register types, each with an associated number: coils = 0, discrete inputs = 1, input registers = 3, holding registers = 4. Each type has its own 16-bit address space.

The address of a register is of the form <type><value>, where <value> is a four-digit number in the range [1, 9999]. For example, the address of holding register 1 is 40001. The address of input register 42 is 30042. If you see a document that refers to address 40001, they are referencing holding register 1. If the document refers to holding register 1, they are referencing address 40001.

Then there is the address offset, which is what actually gets sent in Modbus requests. The address field in the Modbus packet represents an offset in the corresponding address space for the register type. That is, offset 0 corresponds to the first register in the address space; offset 1 to the second, and so on. For example, if the Modbus master wants to read the contents of holding register 100, it sends a request to read multiple holding registers (function 3) with an address field (offset) of 99 and a register count of 1. In turn, the Modbus slave would return the value stored at address 40100.

To make it even more confusing, the table above refers to standard Modbus, where the maximum number of addresses per entity is 9,998. A de facto extension allows the full 16-bit space by writing the entity number as a six-digit value, but I wouldn't worry about this.

What I am concred though is how do I determine which device is my master device and which is my slave device? How do I configure one of my ESP32 devices to be master?
You decide which is the master and which is the slave. Typically, there are different libraries for masters and slaves. As for which to choose for your devices, the master is the device that reads from/writes to the slave device. All a slave can do is answer queries from the master.

I just want to be able to write to coil on one ESP32 (master), and read from another ESP32 devices when required (Slave)
A coil is a single-bit data value, meant to be connected to a relay or such. If all you need is a single switch control, then Modbus is overkill. This comes back to my original question: what's you goal?
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
I'd recommend against implementing your own Modbus library, especially if you're having a hard time understanding existing libraries. What's your goal here? To become an expert in Modbus? To allow your devices to communicate with third-party devices that speak Modbus? Do you really want to spend the time and effort to implement and thoroughly test all the tedious details of a communications protocol -- message parsing, out-of-order handling, error handling, time outs, etc., etc.?


This is a needlessly confusing aspect of Modbus due to its long history and weak standard enforcement among the various implementations. The most important thing to keep in mind is that when people talk about the location of data in Modbus, they are referring to either addresses, register numbers, or offsets, all of which are related but distinct concepts.

The address field in a Modbus packet is 16 bits wide, so the address space for any request is 0x0000 to 0xFFFF, a total of 65,536 possible addresses. That much is unambiguous and clear. But each request also includes a Modbus function that accesses a particular type of register. There are four register types, each with an associated number: coils = 0, discrete inputs = 1, input registers = 3, holding registers = 4. Each type has its own 16-bit address space.

The address of a register is of the form <type><value>, where <value> is a four-digit number in the range [1, 9999]. For example, the address of holding register 1 is 40001. The address of input register 42 is 30042. If you see a document that refers to address 40001, they are referencing holding register 1. If the document refers to holding register 1, they are referencing address 40001.

Then there is the address offset, which is what actually gets sent in Modbus requests. The address field in the Modbus packet represents an offset in the corresponding address space for the register type. That is, offset 0 corresponds to the first register in the address space; offset 1 to the second, and so on. For example, if the Modbus master wants to read the contents of holding register 100, it sends a request to read multiple holding registers (function 3) with an address field (offset) of 99 and a register count of 1. In turn, the Modbus slave would return the value stored at address 40100.

To make it even more confusing, the table above refers to standard Modbus, where the maximum number of addresses per entity is 9,998. A de facto extension allows the full 16-bit space by writing the entity number as a six-digit value, but I wouldn't worry about this.


You decide which is the master and which is the slave. Typically, there are different libraries for masters and slaves. As for which to choose for your devices, the master is the device that reads from/writes to the slave device. All a slave can do is answer queries from the master.


A coil is a single-bit data value, meant to be connected to a relay or such. If all you need is a single switch control, then Modbus is overkill. This comes back to my original question: what's you goal?


Thanks for your answer once again. I have found it to be very informative and useful.

Let me tell you about the project.. i am building a pick to light automation system. Initially we only considered to have devices communicating using MQTT protocol whicj I have implemented and everything seems to be working fine.

However, as we started to expand the system and connected more and more ESP devices, I am noticing various problems.. the 2.4Ghz wifi is very busy in the room where we are implementing these devices and I have various wifi issues...

Luckily we also designed RS485 in case mqtt fails..

All I am trying to do now is just replicate my mqtt messages with wired modbus messages.


All I need is to be able to send numbers,strings and json data from the master to slave and back.. The master device which in my case will be raspberry PI starts various messages to esp32 in most cases will be strings such as "start", "finish","next".
Based on the string the esp will do so ething and reply to master.


The reason why I thought using my custom library is because all those libraries have so many funcionalities built in that I do not understand or need. For example, why do I need to call modbus loop, why do I need to use callbacks and etc
 

bogosort

Joined Sep 24, 2011
524
All I need is to be able to send numbers,strings and json data from the master to slave and back.. The master device which in my case will be raspberry PI starts various messages to esp32 in most cases will be strings such as "start", "finish","next".
Based on the string the esp will do so ething and reply to master.
A common way of doing this kind of thing with Modbus is to set up a table of holding registers that correspond to state variables in your slave devices. Holding registers are usually preferred to the other types because they are more flexible (can store 16-bit values, can be read/write, etc.). The master controls the slaves by writing to the appropriate registers, and the slaves communicate their state back to the master through designated registers.

So, for example, rather than sending various strings to a single "command" register that must be parsed, you might designate holding register 100 as the "start" register for device 1, which is enabled by the master writing a 1 to it. When the slave has completed the start process, it writes a 0 to the "start" register. The master can query the register to see when the slave is ready for the next step, and so on. The start register for device 2 might be at holding register 200, and so on.

The reason why I thought using my custom library is because all those libraries have so many funcionalities built in that I do not understand or need. For example, why do I need to call modbus loop, why do I need to use callbacks and etc
The Modbus loop is necessary for the same reason that the Arduino loop is necessary: it is the event loop that runs forever, so that the device can wait for things to happen. Without the Modbus loop, your slave would simply listen for a query once and then close the Modbus connection.

Callbacks are usually not necessary, but they can be very useful when you are writing event-driven code: once some event is finished, the library executes whatever callback you associated with the event. Callbacks can make your asynchronous code behave in a synchronous manner. I think that if you start with the basic examples from the library and get some experience with how Modbus works, you'll find that it will be much, much easier to use the library rather than trying to design your own.
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
Okay! Thanks for the suggestion I will try to implement some basic modbus communications.

But how can I control RE,DE pins of RS 485? Do I just drive it high when sending data and then just keep it low at all other times?

Also, for example Raspberry pi initiates a task by sending some data. Will my slave devices be constantly polling and waiting for the task beggining? I want esp32 modules to immediately stsrt a task once raspberry PI initates but it has to poll in non blocking mode since the slave deviced are also doing some other things and measurements..

Also, would it make more sense to use one address and just send different messages ? The receiving device would decode a message and act accordingly?
Or is it better to for example dedicate address offset 100 to "start"
Address offset 200 to "next"
Address offset 300 to "finish"
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
So I have used modbus examples to come up with a sketch:
Masters code:

Code:
/*
  ModbusRTU ESP8266/ESP32
  Read multiple coils from slave device example

  (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com)
  https://github.com/emelianov/modbus-esp8266

  modified 13 May 2020
  by brainelectronics

  This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/
#include <ModbusRTU.h>
#define RXD2 16
#define TXD2 17
#define DIR 15
#define REG1 10
ModbusRTU mb;

uint16_t send_data = 5;
uint16_t storing_data = 0; // place holder to store data

bool cbWrite(Modbus::ResultCode event, uint16_t transactionId, void* data) {
  Serial.printf_P("Request result: 0x%02X, Mem: %d\n", event, ESP.getFreeHeap());

  return true;
}

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
  mb.begin(&Serial2);
  mb.master();
  pinMode(DIR, OUTPUT);    // sets the digital pin 13 as output
  digitalWrite(DIR, LOW); // sets the digital pin 13 on
}

void loop() {
  if (!mb.slave()) {
    mb.readHreg(1, REG1, &storing_data, 1, cbWrite);
    Serial.print("storing data value1=");
    Serial.println(storing_data);
    mb.readHreg(1, REG1-1, &storing_data, 1, cbWrite);
    Serial.print("storing data value2=");
    Serial.println(storing_data);
    mb.readHreg(1, REG1+1, &storing_data, 1, cbWrite);
    Serial.print("storing data value3=");
    Serial.println(storing_data);
  }
  mb.task();
  yield();

}
1603341522770.png

I am not fully understanding the use of few functions in the masters code:

1. if (!mb.slave()) - What is the point of this function?

2.mb.task() - Must be called inside loop to be able to transmit/receive messages?

3. what does yield() function do in this code? Even though I do not have any delay in my masters code, You can see that it is reading data every 1 second. Does it have to do something with the yield function?



My slave code:

Code:
/*
  ModbusRTU ESP8266/ESP32
  Simple slave example

  (c)2019 Alexander Emelianov (a.m.emelianov@gmail.com)
  https://github.com/emelianov/modbus-esp8266

  modified 13 May 2020
  by brainelectronics

  This code is licensed under the BSD New License. See LICENSE.txt for more info.
*/

#include <ModbusRTU.h>

#define RXD2 16
#define TXD2 17
#define DIR 15
#define REG1 10
#define SLAVE_ID 1

ModbusRTU mb;

uint16_t send_data = 100;


void setup() {
  pinMode(DIR, OUTPUT);    
  digitalWrite(DIR, HIGH); 
  Serial.begin(9600, SERIAL_8N1);
  Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
  mb.begin(&Serial2);
  mb.setBaudrate(9600);

  mb.slave(SLAVE_ID);//Initializng modbus slave device with ID 1
  mb.addHreg(REG1); // add the register with address 1
  mb.Hreg(REG1, 100);
  //mb.writeHreg(1, 100, &send_data, 1, cbWrite);
}
void loop() {
  mb.task();
  yield();
  
}
All I am doing is just adding a value "100" to REG1.

What I am confused about is what are the differences between:
1. mb.Hreg(REG1, 100);
AND
2. mb.writeHreg(1, REG1, &send_data , 1, cbWrite);

As you mentioned previously, the first function will add the value 100 to the REG1 which is equivalent to the second function right? The second function is also suppose to write value 100 to REG1 since send_data is decalred as 100.


I am not able to receive anything on my master side. Also, for my actual project, the messaging would have to be the other way arround, instead of slave writing something and master reading, My master would always initiate a write and then the slave would respond.

Perhaps its time to take my oscilator and probe the RS485 IC pins
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
It could also be that I am not properly controlling DE/RE pins of my master device.
From the modbus documentatiaon:
1603343971369.png

To read the holding resgiter, I first need to request data and then get the response. But I have set the direction pin to "LOW" to
Code:
  pinMode(DIR, OUTPUT);    // sets the digital pin 13 as output
  digitalWrite(DIR, LOW); // sets the digital pin 13 on
But if the direction pin is LOW , I will not be able to make a request since during the request I am writing data. Is that right>?
 

bogosort

Joined Sep 24, 2011
524
But how can I control RE,DE pins of RS 485? Do I just drive it high when sending data and then just keep it low at all other times?
No idea, I use Modbus TCP, but I'd bet you can find libraries to handle the RS485 side of things.

Also, for example Raspberry pi initiates a task by sending some data. Will my slave devices be constantly polling and waiting for the task beggining? I want esp32 modules to immediately stsrt a task once raspberry PI initates but it has to poll in non blocking mode since the slave deviced are also doing some other things and measurements..
I handle this with threads, but that doesn't seem like an option for you. It shouldn't be a problem, though. You can implement a state machine inside your slave's loop(): every iteration, it checks the state of the appropriate local Modbus registers to see what it should be doing. If it has nothing to do, it simply calls mb.task() to process incoming requests.

Here's the basic idea (please don't copy/paste, it's just a skeleton to show the idea):
C:
typedef enum { STATE_INIT, STATE_START, STATE_NEXT, STATE_FINISH, STATE_ERROR, STATE_IDLE } state_t;
state_t current_state = STATE_INIT;

void loop() {
  switch (current_state) {
    case STATE_INIT:
      doInitializations(); // set up registers, etc.
      current_state = STATE_IDLE;
      break;
    case STATE_START:
      current_state = doStart(); // doStart should return the appropriate next state
      break;
    // ....
    case STATE_IDLE:
      current_state = checkRegisters(); // look for updates to our state, return STATE_IDLE if nothing
      mb.task(); // process Modbus requests
      break;
  }
  // do any other stuff you need
  yield(); // free up the cpu for other tasks
}
A possibly better alternative is to use callbacks in your slave device to drive the state machine. For example, use the onSetHreg() method to create a callback function that checks which register the master has changed, and then do the appropriate action.

Also, would it make more sense to use one address and just send different messages ? The receiving device would decode a message and act accordingly?
Or is it better to for example dedicate address offset 100 to "start"
Address offset 200 to "next"
Address offset 300 to "finish"
Both are viable options, but I prefer to use multiple addresses with numeric values as they don't require parsing. If you use the string method, remember that you'll still be using multiple registers as each register can only hold 16 bits worth of data. You'll need some out-of-band way to mark "end of string", such as null-terminating your strings. To me, the extra complexity isn't worth it -- the data will not be human-readable in either case.

A nice thing about multiple numeric registers is that it is very easy to add new functionality. For example, you might start with a map such as

Code:
100  start (0 = idle, 1 = in start process)
101  status (0 = OK, 1 = read error, 2 = unknown error)
102  id (id number of item)

200  next (0 = not in state, 1 = in next process)
201  status
202  id

300  finish (0 = not in state, 1 = in finishing process)
301  status
302  total time in seconds taken
Later you might decide to add more functionality, such as a "pause" control register to the finish state (or whatever). Unlike the string case, the register numbers are deterministic and you don't have to worry about leaving enough room for a long string.

That said, it's entirely up to you as the designer to choose what works best for your application.
 

bogosort

Joined Sep 24, 2011
524
I am not fully understanding the use of few functions in the masters code:

1. if (!mb.slave()) - What is the point of this function?
I've never heard of this library until you pointed it out to me, so I'm not sure why you're asking me. :) But it didn't take me much effort to look up the API of the library (https://github.com/emelianov/modbus-esp8266/blob/master/API.md) to see that when the slave() method is called with no arguments, it returns the slave ID for the active request or 0 if no request is in progress. Therefore, !mb.slave() is true when there is no request in progress. In other words, it guards against the slave trying to send a request when one is already in progress.

2.mb.task() - Must be called inside loop to be able to transmit/receive messages?
If you don't process Modbus within the loop(), then what do you think will happen? What happens to code that is executed outside of the loop? It runs once and never again. So, if you want to keep processing Modbus requests, it needs to be in the infinite loop.

3. what does yield() function do in this code? Even though I do not have any delay in my masters code, You can see that it is reading data every 1 second. Does it have to do something with the yield function?
A simple google search revealed that calling yield() gives the ESP8266 some CPU time to execute its background network processes.

Note that your master code is logically incorrect:
C:
mb.readHreg(1, REG1, &storing_data, 1, cbWrite);
You are calling readHreg -- which reads the value of a holding register -- but you act as if you are writing to the register. You need to use writeHreg.

What I am confused about is what are the differences between:
1. mb.Hreg(REG1, 100);
AND
2. mb.writeHreg(1, REG1, &send_data , 1, cbWrite);

As you mentioned previously, the first function will add the value 100 to the REG1 which is equivalent to the second function right? The second function is also suppose to write value 100 to REG1 since send_data is decalred as 100.
They are not at all equivalent. The Hreg function is used by the slave to set its local register values. The writeHreg function is used by the master to set the remote register value on the slave.

It's important that you understand the relationship between master/slave and read/write. Only a master can read/write; the slave never calls these functions. When the master wants to get data from the slave, it reads from the slave. When the master wants to send data to the slave, it writes to the slave.
 

Thread Starter

zazas321

Joined Nov 29, 2015
722
Thanks again for clearing some things out. I have set up a logic analyzer and tommorow going to see what kind of signals im putting in and getting out.

So when a master writes data to the slave, the slave can only respond using mb.Hreg(REG1, 100); function?

For example imagine my master device have to send some data when the button is pressed:
Code:
if(button_pressed == True){
     send_data = 1;
     mb.writeHreg(1, REG1, &send_data , 1, cbWrite);  //write variable "send_data" to REG1 for a slave ID 1
}
uint16_t received_data;
   mb.readHreg(1, REG1, &received_data, 1, cbWrite);  //read variable from register and store it in received_data"
   if(received_data == 0){
      //slave device responded with 0
    }
else{
   //slave device hasnt responded yet
}


Is the code above how its supposed to be?

And in the meantime slave device, according to the API will be reading local REG1, and waiting for the value "1"
1603382825058.png

Code:
if (mb.Hreg(REG1) == 1){
    //Value 1 detected, do something
    mb.Hreg(REG1, 0); // put 0 in REG1 to reply to master?

    }
Is my understanding correct?

Also, can you confrim that I understand the modbus receive modbus properly:
When the device (master) wants to get data from the slave (readHreg), it first need transmit a frame of data with slave ID, address that you want to read from and how many bytes to read and crc. After building a proper frame, then the RS485 must be switched to receive mode and start reading in incoming bytes?

Just a skeleton code to describe how I understand:#

Code:
  digitalWrite(DIR,HIGH) // set direction pin to high because now we transmitting a frame to initiate reading from register
  Serial2.write(slave_ID);
  Serial2.write(3); // function code for reading register
  Serial2.write(address_MSB);
  Serial2.write(address_LSB);
  Serial2.write(Quantity_MSB);
  Serial2.write(Quantity_LSB);
  Serial2.write(CRC_MSB);
  Serial2.write(CRC_LSB);


digitalWrite(DIR,LOW) // set direction pin to LOW to start reading data from that register

  if (Serial2.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial2.read();
}
 

bogosort

Joined Sep 24, 2011
524
So when a master writes data to the slave, the slave can only respond using mb.Hreg(REG1, 100); function?
Good question because it gets to the heart of your misunderstanding.

The register table in the slave's memory is passively served by the slave. What I mean by this is that the protocol takes care of the details of responding to the master with the requested information. Your code in the slave doesn't need to "respond" to the master.

The slave device starts up and initializes its registers (mb.Hreg() in your library); it then sits around waiting for something to do -- this is the main program loop(). If you want it to respond to Modbus requests, you include mb.task() in the loop. Any Modbus requests will be processed automatically; you don't have to write code to handle requests.

Meanwhile, the master device starts up and sends a Modbus request to the slave to set register. Let's say the master will send a request to the slave to write the value 171 (0xAB) in register 1. Since we're writing a single value, we'll use the appropriate version of writeHreg, which has this signature:
C:
uint16_t writeHreg(uint8_t slaveId, uint16_t offset, uint16_t value, cbTransaction cb = nullptr);
So, you're code would write something like this. (Note that holding register 1 has offset 0, but we can use offset 1 [register 2] if its less confusing.)
C:
writeHreg(1, 1, 171,  cbWrite);
The request should then trigger whatever code you've associated with the callback cbWrite.

On the slave end, on the next pass through mb.task(), the library will receive the request from the master and write 171 to offset 1. It will do this behind the scenes and your code will never know about it unless you either associate a callback with the register change, or you poll the value of offset 1 to see if it has changed.

If you want to poll the value, on startup have your slave initialize offset 1 with the value of 0:
C:
mb.Hreg(1, 0);
Then, in the loop, call mb.Hreg(1) every second or so and print the value if it changes.
 
Top