Help with understanding data of NPK sensor ModBus

Hello, I'm trying to interpret the data received from the NPK sensor connected to a Modbus RS485.

Looking at the vendor's datasheet, the data received should have the following format byte.

here is a printout of my received message from sensor:

|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

---VV---
|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

---VV---
|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

---VV---
|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

---VV---
|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

---VV---
|1|3|4|3|E8|1|3F|3B|C3|FF|FF|FF-----

I can't find where is my temperature information, from my other post in this forum I understood the data info I need is on bytes 4 and 5

BUT:
base on this info if 4 and 5 hold the temp I need it to mean bytes 3E8 = 100/2 = 10degrees (wrong temp)

if bytes 6 and 7 hold temp information => 13F = 319 /2 31.2 deg... my be correct

How do I combine these 2 bytes [6,7] and return the information I need on my code below?

here is my function code:

byte temperature(){
  Serial.println("---VV---");
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  Serial.flush();

  if(mod.write(msgt,sizeof(msgt))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
  

    for(byte i=0;i<12;i++){
    values[i] = mod.read();
    Serial.print("|");
    Serial.print(values[i],HEX);
   
    }
   
    Serial.println("-----");
    Serial.flush();
    delay(3000);

  }
  return values[6,7]; //but wrong
}
 union {
    uint32_t jj;
    float ff;
  } u;

byte index_of_first_int = 6;

u.jj = ((unsigned long) values[index_of_first_int] << 16 | values[index_of_first_int+1] );

Serial.println(u.ff);

It is only in bytes 4 & 5 if your modbus request has a start register that is the temperature register. Show us all the code as the issue is likely to be in the code you didn't post.

@KASSIMSAMJI that's a complicated way of solving a simple problem. Why are you shifting a byte by 16 bits?

I would simply cast the high byte to an unsigned int, shift it left 8 bits and or in the low byte.

Hello, here is my full code..:

#include <SoftwareSerial.h>

#define RE 8
#define DE 7

const byte msgt[] = {0x01,0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B};
byte values[11];



SoftwareSerial mod(2, 3);

void setup() {
  
  Serial.begin(4800);

  
  mod.begin(4800);


  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
  
  delay(500);
}

void loop() {

  byte temp;



  temp = temperature();

  Serial.print("Temp:");
  Serial.println(float(temp/10.0));
}


byte temperature(){
  Serial.println("---VV---");
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  Serial.flush();

  if(mod.write(msgt,sizeof(msgt))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
  

    for(byte i=0;i<9;i++){
    values[i] = mod.read();
    Serial.print("|");
    Serial.print(values[i],HEX);
   
    }
   
    Serial.println("-----");
    Serial.flush();
    delay(3000);

  }
  return values[6,7];
}




But why is it only in bytes 4 and 5?
In the datasheet of the product I can see 4/5 first data area and 6/7 second data area (see picture below)
.. sorry I'm trying to understand this received message .. not too much experience with this type of coding.

even because byte 4/5 gives me a weird but 6/7 is a more realistic temperature.

screenshot of datasheet

The value you put in register length in the tx message determines how many "data area" sets you get back.

If you ask for 1 register, then you get back 1 data area consisting of 2 bytes. Similarly if you ask for 2 registers, then you get back 2 data areas.

Each register you ask for is 1 16-bit word - i.e. 2 bytes. If you ask for 3 registers then you will get back 3 "data area", or 6 bytes.

Oh, so I'm a little confused again, so sorry to ask all these questions.

So based on your explanation TX message is composed like this:

address code: 0x01
function code: 0x03
Register start address (2 bytes): 0x00, 0x00
Register length (2 Bytes): 0x00, 0x02
Check code low: 0xC4
check code hight: 0x0B

final message is following,
{0x01,0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B};

like this I'm setting 2 registers right?

question (because I think I didn't understand how to calculate the tx message):

if I want to calculate the tx message for the PH value (see table below):

address code: 0x01
function code: 0x03
Register start address (2 bytes): 0x00, 0x00
Register length (2 Bytes): 0x00, 0x04
Check code low: 0x44
check code hight: 0x09

final message:
{0x01,0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x09};
Is this correct?

so my message received will have 4 data areas?

Thanks so much for the time you spending on answering my questions.
really appreciate

No problems, keep asking and I'll try and answer.

Let's see. It's not register length, but number of registers you want the sensor to return.

So that decodes to device address 01, function code 03, start address 0x0000 and requesting 2 registers (which would be moisture content and temperature).

Hopefully I've got what follows correct!

For pH, looking at that table, the register address for pH is 0x0003. If you just wanted to read pH on its own, then the final tx message would be:

01 03 00 03 00 01 74 0A

So, 0x0003 goes into the register start address and the number of registers to read is 0x0001.

What that says is: I want the contents of 4 registers starting at register address 0x0000. The sensor should respond with 1 message containing 4 of what the datasheet calls "data areas".

Data area 1 would hold the moisture content, data area 2 the temperature, data area 3 the conductivity and data area 4 the pH.

Thanks so much, I finally tried and looks like I understood how to structure my message and read my results, at least I tried a few measures, and looks like works the data I received make more sense.

But like always i still have a quick question, here is my function to read the humidity, I know as we talk that the humidity values are on byte 4 and 5 inside my "values" array.

How can I combine this 2 bytes e to return the value:

first attempt:

byte humidity(){
  
  Serial.flush();
  // Serial.println("---VV---");
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  
 
  
  if(mod.write(msgh,sizeof(msgh))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);

    for(byte i=0;i<7;i++){
    values[i] = mod.read();
    Serial.print("|");
    Serial.print(values[i],HEX);
   
    }
 
    Serial.println();
    Serial.flush();

  }
  return values[3|4]; // first attempt
}

second attempt:

byte humidity(){
  
  Serial.flush();

  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  
  byte result ;
  
  if(mod.write(msgh,sizeof(msgh))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);

    for(byte i=0;i<7;i++){
    values[i] = mod.read();
    Serial.print("|");
    Serial.print(values[i],HEX);
   
    }
    result = (values[3] | values[4]);
    Serial.println();


    Serial.flush();


  }
  return results;
}

example:

values contains the following value of byte:

|1|3|2|1|D6|38|4A

I'm trying to get 1D6 which corresponds to my humidity value of 470 but can't combine the 2 bytes.

Thanks a lot

Your second attempt was almost correct. If you want to combine the 2 bytes from values[3] and values[4] into one 16-bit value, then try:

return (uint16_t)(values[3] << 8 | values[4]);

You also need to change the data type returned by the function to the same type - i.e. uint16_t like this:

uint16_t humidity(){

And, don't forget to change the data type of the variable that is going to hold the returned value as well.

You don't have a variable called results in your function. Did you get a warning when you compiled your sketch?

Thanks a lot like always for your help.

i have done a few trials and it looks like all work correctly, I think I face the last issue...

in my code, if I single request one data like:

void loop() {
  uint16_t temp;
  temp = temperature();
  Serial.println("Temp:");
  Serial.println(float(temp/10.0));
  delay(2000);
}

and than

void loop() {
  uint16_t hum;
  hum = humidity();
  Serial.println("Humidity:");
  Serial.println(float(hum/10.0));
  delay(2000);
}

the reading I'm getting are all correct, but if i combine humidity and temperature one after the other in the same loop are not correct.

void loop() {

  uint16_t temp, hum;

  temp = temperature();
  delay(250);
  hum = humidity();
  delay(250);

  Serial.println("Temp:");
  Serial.println(float(temp/10.0));
  Serial.println("Humidity:");
  Serial.println(float(hum/10.0));

  delay(2000);
  Serial.println("---------------------------");
 
}

in particular, the temperature value goes to humidity and temperature gets the humidity value.. looks inverted:

here is my full code:

#include <SoftwareSerial.h>

#define RE 8
#define DE 7

const byte msgh[] = {0x01,0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};
const byte msgt[] = {0x01,0x03, 0x00, 0x01, 0x00, 0x01, 0xD5, 0xCA};

byte values[11];



SoftwareSerial mod(2, 3);

void setup() {
  
  Serial.begin(4800);

  
  mod.begin(4800);


  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
  
  delay(500);
}

void loop() {


  uint16_t temp, hum;

  temp = temperature();
  delay(250);
  hum = humidity();
  delay(250);

  Serial.println("Temp:");
  Serial.println(float(temp/10.0));
  Serial.println("Humidity:");
  Serial.println(float(hum/10.0));

  delay(2000);
  Serial.println("---------------------------");
 
}


uint16_t temperature(){
  
  Serial.flush();

  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  uint16_t result ;
  if(mod.write(msgt,sizeof(msgt))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);

    for(byte i=0;i<7;i++){
    values[i] = mod.read();
 
    }
    result = (uint16_t)(values[3] << 8 | values[4]);
  
    Serial.flush();
    
  }
  return result;
}

uint16_t humidity(){
  
  Serial.flush();

  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(20);
  
  uint16_t result ;
  
  if(mod.write(msgh,sizeof(msgh))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);

    for(byte i=0;i<7;i++){
    values[i] = mod.read();

   
    }
    result = (uint16_t)(values[3] << 8 | values[4]);
   
    Serial.flush();

  }
  return result;
}


You've discovered the main flaw in the NPK sensor code that is being published on various websites. It's not helped by the fact that they all copy each other with each author not understanding the code they have published.

Have a read of my post #115 in this discussion for what I think the problem is with that code:

Post #116 in that same discussion has a piece of code that uses a Modbus library to correctly handle the comms. See if it works for you. You may need to change the register addresses to suit your particular NPK sensor.

Thanks a lot, I think I solved sending a single message requesting al data back at one time.

It looks like the data I, getting back is correct.

void testfunc(){
  
  Serial.flush();
 
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(50);
  
  byte allvalues[20];
  
  if(mod.write(test,sizeof(test))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    delay(100);
    for(byte i=0;i<19;i++){
    allvalues[i] = mod.read();
    Serial.print("|");
    Serial.print(allvalues[i],HEX);
   
    }

    uint16_t hum =  (uint16_t)(allvalues[3] << 8 | allvalues[4]);
    Serial.println();
    Serial.print(hum,HEX);
    Serial.print("*hum*");
    Serial.println(double(hum/10.0));
    uint16_t temp =  (uint16_t)(allvalues[5] << 8 | allvalues[6]);
    Serial.print(temp,HEX);
    Serial.print("*temp*");
    Serial.println(double(temp/10.0));
    uint16_t cond =  (uint16_t)(allvalues[7] << 8 | allvalues[8]);
    Serial.print(cond,HEX);
    Serial.print("*cond*");
    Serial.println(double(cond));
    uint16_t ph =  (uint16_t)(allvalues[9] << 8 | allvalues[10]);
    Serial.print(ph,HEX);
    Serial.print("*ph*");
    Serial.println(double(ph/10));
    uint16_t nit =  (uint16_t)(allvalues[11] << 8 | allvalues[12]);
    Serial.print(nit,HEX);
    Serial.print("*nit*");
    Serial.println(double(nit/10));
    uint16_t pos =  (uint16_t)(allvalues[13] << 8 | allvalues[14]);
    Serial.print(pos,HEX);
    Serial.print("*pos*");
    Serial.println(double(pos/10));
    uint16_t pot =  (uint16_t)(allvalues[15] << 8 | allvalues[16]);
    Serial.print(pot,HEX);
    Serial.print("*pot*");
    Serial.println(double(pot/10));

    Serial.flush();
  

  }

}

I have now questions (like always), on the NPK sensor I have some of the values that require some calibration data to be written in, do you have some examples of how the message needs to be structured and how to write this data into the register?

On the Nitrogen value as you can see i can write the initial calibration value how should i send the initial calibration value for example 50kg/mg?

Thanks

Is there an example in your user manual on how to do this?

I've not come across any talk in the various NPK discussions here that involved writing any data or calibration values to the sensor.

Actually not too much on the manual, I have contacted the seller because the value of Nitro Potassium are always 0 and they told me first have to write in the initial calibration value.
I've tried to search online but actually not too many examples of writing online.

Actually, even for Huimidy Temperature and PH, there is a way to set up the initial calibration value whit the registry address example 0050/51/52 and more..but the manuals they provide don't show any way how to do it.

I attached the full manuals translated from Chinese.

Manual.pdf (3.7 MB)

This is pure speculation on my part so may not work, but here goes.

To read those values, your canned modbus message is using function code 03 (the 2nd byte) which is the function code for "Read Holding Registers".

There is a function code 06 which is "Write Single Holding Register".

I think the message would be something like:
01 06 <2 bytes for register address> <2 bytes for the value to write> <2 byte CRC-16 checksum lo byte then hi byte>

However, if you are writing a calibration value, then that would presume that there was some form of calibration procedure? Having spent a little time in a calibration lab many years ago, I would have thought that the calibration values would be set at the factory and only altered if the device went off for calibration.

I'm curious as to what values you would be putting into the calibration registers and how you would go about determining them for yourself. In the cal lab, we mainly handled multimeters and oscilloscopes, but the equipment we used to calibrate them was certified and traceable back to a national standard.

Thanks a lot and sorry for my late replay, busy at work, I quickly tried to write in some value and it works but when I go to read the parameters it just print out what I write…as you mentioned what I should write in is not clearly mention on the manual, base on the online support what they told me , I should write the initial calibration value with their kit.. but I doubt.. I think sensor not working.. is quite precise on temp and soil humidity but definitely not on NPK.

It sounds like the calibration values may not have been programmed in after manufacture. I would suggest returning it for a replacement or refund.

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