emelianov / modbus-esp8266

Most complete Modbus library for Arduino. A library that allows your Arduino board to communicate via Modbus protocol, acting as a master, slave or both. Supports network transport (Modbus TCP) and Serial line/RS-485 (Modbus RTU). Supports Modbus TCP Security for ESP8266/ESP32.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Modbus RTU Holding Registers Question

Lvro91 opened this issue · comments

Hello, I am a person with very little experience in programming, I have some devices that communicate via Modbus and I wanted to test if I could communicate them to a cloud through an esp32, so I found this wonderful library.
My doubts may be trivial for you, I ask for your patience.
One of the pieces of equipment that I am using monitors electrical variables and stores them in Holdings Registers, most of this data is stored in a single 16-bit register, but in the case of powers, these are stored in 32-bit registers.
My questions are:

  1. What it is this line "bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) {}" and what does it do?

  2. How can i read a uint32_t or a float value? I already tried to replace the variable defined as int16 by (*uint16_t)&value32 but it doesn't work, the Arduino IDE shows me the following error in the same line that i used the (*uint16_t)&value32:

    • Compilation error: expected primary-expression before ')' token
  3. Is there a way to read multiple records at once to save lines of code?

That's all, thank you for your work in this library.
Here i attach my code, I am attentive to your suggestions

P.D: Sorry the bad or robotic english, not my native language.

#include <ModbusRTU.h>
#define SLAVE_ID 1
ModbusRTU mb;
bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) {}

/*** Monophasic Voltage ***/

uint16_t VlnA[1];
uint16_t VlnB[1];
uint16_t VlnC[1];
uint16_t VlnAVG[1];

/*** Triphasic Voltage ***/

uint16_t VAB[1];
uint16_t VBC[1];
uint16_t VCA[1];
uint16_t VLLProm[1];

/*** Currents ***/

uint16_t IA[1];
uint16_t IB[1];
uint16_t IC[1];
uint16_t IAVG[1];

/*** Frecuency ***/

uint16_t HERTZ[1];

/*** Total Power ***/
uint16_t kW[1];
uint16_t kVA[1];
uint16_t kVAr[1];

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);
  mb.begin(&Serial2, 18);
  mb.master();
  pinMode(18, OUTPUT);
  digitalWrite(18, HIGH);
}

void loop() {
  if (!mb.slave()) {
    //(slave address, data address, variable, number of bytes, cb) // don't now the meaning of cb yet
    
    /***  Voltage A-N ***/
    mb.readHreg(1, 1, VlnA, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Voltage B-N ***/
    mb.readHreg(1, 2, VlnB, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Voltage C-N ***/
    mb.readHreg(1, 3, VlnC, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Average Monophasic ***/
    mb.readHreg(1, 4, VlnAVG, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Voltage A-B ***/
    mb.readHreg(1, 5, VAB, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Voltage B-C ***/
    mb.readHreg(1, 6, VBC, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Voltage C-A ***/
    mb.readHreg(1, 7, VCA, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Average Triphasic ***/
    mb.readHreg(1, 8, VLLProm, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Current A ***/
    mb.readHreg(1, 9, IA, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Current B ***/
    mb.readHreg(1, 10, IB, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Current C ***/
    mb.readHreg(1, 11, IC, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Average Current ***/
    mb.readHreg(1, 12, IAVG, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Frecuency ***/
    mb.readHreg(1, 25, HERTZ, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Total Active Power ***/
    mb.readHreg(1, 29, kW, 2, cb);
    while (mb.slave()) {
      mb.task();
    }
    
    /*** Total Aparent Power ***/
    mb.readHreg(1, 31, kVA, 2, cb);
    while (mb.slave()) {
      mb.task();
    }
   
    /*** Total Reactive Power ***/
    mb.readHreg(1, 33, kVAr, 2, cb);
    while (mb.slave()) {
      mb.task();
    }
  1. This is callback function that is executed on receive response from the request. It may be required as the library is built for async use. As you are using while(mb.slave()) to wait for the data you may omit callback.
  2. Modbus protocol operates only 16-bit integer values. All other data types may just be mapped on several sequential registers. Code below pulls float to data2 structure (if I've guessed correctly on datatypes). To make float values represented correctly refer good article and play with byte swapping.
  3. Sequential registers may be pulled together as in code below.
#include <ModbusRTU.h>
#define SLAVE_ID 1
ModbusRTU mb;
bool cb(Modbus::ResultCode event, uint16_t transactionId, void* data) {}

/*** Monophasic Voltage ***/
struct dataset1 {
    uint16_t VlnA;
    uint16_t VlnB;
    uint16_t VlnC;
    uint16_t VlnAVG;

    /*** Triphasic Voltage ***/

    uint16_t VAB;
    uint16_t VBC;
    uint16_t VCA;
    uint16_t VLLProm;

    /*** Currents ***/

    uint16_t IA;
    uint16_t IB;
    uint16_t IC;
    uint16_t IAVG;
};

/*** Frecuency ***/
uint16_t HERTZ;

struct dataset2 {
    /*** Total Power ***/
    float kW;
    float kVA;
    float kVAr;
};

dataset1 data1;
dataset2 data2;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);
  mb.begin(&Serial2, 18);
  mb.master();
  pinMode(18, OUTPUT);
  digitalWrite(18, HIGH);
}

void loop() {
  if (!mb.slave()) {
    //(slave address, data address, variable, number of registers, cb) // number of registers not bytes, cb can be ommited

    mb.readHreg(1, 1, (uint16_t*)&data1, sizeof(dataset1)/2); // probably start reg should be 0 and other start reg should be decreased by 1
    while (mb.slave()) {
      mb.task();
    }

    /*** Frecuency ***/
    mb.readHreg(1, 25, &HERTZ, 1, cb);
    while (mb.slave()) {
      mb.task();
    }
    /*** Total Active Power ***/
    mb.readHreg(1, 29, (uint16_t*)&data2, sizeof(dataset2)/2);
    while (mb.slave()) {
      mb.task();
    }
  }
}

Thanks for your quick response.
I still can't prove it because now if i wanna print this in the serial monitor ("Serial.print(VlnA);" for example) , the IDE indicates the following error:

Compilation error: 'VlnA' was not declared in this scope

This happens with all variables.

If you're trying to extend example provided above

Serial.print(data1.VlnA);

Sorry for my inexperience, I didn't know how to read variables inside a structure, thanks for teaching me that.
Your guess was on point, the start reg was 0.

Now i can read something:

All good with the int16 data, but the total powers are sent as unsigned int32, so i had to change the floats for uint32_t (with floats i was getting only 0s), the problem is that what I'm reading now with uint32_t or unsigned long is not what it should, apparently.

image

kW has an approximate value of 62.500MW
kVAr has an approximate value of 26.230 MVAr
kVA has an approximate value of 68.040 MVA

And the power meter holding registers are sending this

image

So it doesn't occur to me what could be wrong, I'll try to take all the phase powers (those are sent as int16) and then add them up, see if I can get the same value as the totals (should work tho).

Now i have questions regarding the code you post due to my inexperience:

mb.readHreg(1, 0, (uint16_t*)&data1, sizeof(dataset1) / 2);

  • How does it work (uint16_t*)&data1? Does transform the variables to uint16_t? or does it read the memory address of that variables as a uint16_t?
  • First time seeing the "sizeof", It is something intrinsic to the Arduino IDE or it is something inside the modbus library? What does it do? I assume that takes the number of variables inside the dataset but you never now.
  • Why do you divide that?

Anyway, I am very grateful that you answered me, thank you very much.

For int32 value try swap words.

uint32_t tmp;
tmp = data1.VlnA;
tmp = (tmp >> 16) | (tmp << 16);

if it doesn't help probably it's required to swap bytes order.

  1. readHreg expected pointer to uint16_t (or array large enough to fill number of registers requested) as the parameter. We have to force compiler to interpret pointer to dataset1 structure as pointer to uint16_t array.
  2. sizeof is base C language function. It returns size of the object or type in bytes.
  3. readHreg expected count of registers which of two bytes. So to full datraset1 structure with sequential registers we divide structure size by 2.

Sorry for the delay, it was a busy weekend, i can't be more grateful to you, i can read all the data without problem, thank you very much.
Im closing this issue with this.
Have a nice day!