Search code examples
arduinoarduino-esp32

Incorrect Reading NPK Sensor


I want to read this sensor, it is an NPK Sensor. For this, I have this board, and this hat of this board to read the Mobus 485 link.

For this, the origin code that the hat gives us. To read the sensor I use the next tutorial, and finally the config of the NPK sensor:

enter image description here

Then after all of this, I generated this code:

#define PIN_5V_EN    0
#define CAN_TX_PIN   22
#define CAN_RX_PIN   21
#define RS485_TX_PIN 19
#define RS485_RX_PIN 18
#define RS485_EN_PIN 23
#define RE 23
#define DE 0

#include <Arduino.h>
#include <HardwareSerial.h>
#include <ModbusMaster.h>
// instantiate ModbusMaster object
ModbusMaster node;
HardwareSerial Serial485(2);

// Modbus RTU requests for reading NPK values
const byte water[] = {0x01, 0x03, 0x02, 0x01, 0x00, 0x01, 0xd4, 0x72};
const byte temp[] = {0x01, 0x03, 0x02, 0x00, 0x00, 0x01, 0x85, 0xb2};
const byte ec[] = {0x01, 0x03, 0x02, 0x02, 0x00, 0x01, 0x24, 0x72};
const byte ph[] = {0x01, 0x03, 0x02, 0x03, 0x00, 0x01, 0x75, 0xb2};
const byte nitro[] = {0x01, 0x03, 0x02, 0x04, 0x00, 0x01, 0xce, 0x73};
const byte phos[]  = {0x01, 0x03, 0x02, 0x05, 0x00, 0x01, 0x95, 0xb3};
const byte pota[]  = {0x01, 0x03, 0x02, 0x06, 0x00, 0x01, 0x65, 0xb3};
const byte dev_add[]  = {0x01, 0x03, 0x10, 0x00, 0x00, 0x01, 0x80, 0xca};
const byte dev_baud[]  = {0x01, 0x03, 0x10, 0x01, 0x00, 0x01, 0xd1, 0x0A};


// A variable used to store NPK values
byte values[11];

void CANTask(void *prarm);

void setup()
{
    pinMode(RS485_EN_PIN, OUTPUT);

    pinMode(PIN_5V_EN, OUTPUT);

    Serial.begin(9600);
    Serial485.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
    delay(5);
    node.begin(2, Serial485);


}

void loop()
{
    static uint32_t i;
    uint8_t j, result;
    uint16_t data[6];

    byte val1,val2,val3,val4,val5,val6,val7,val8,val9;
    val1 = water_func();
    delay(250);
    val2 = temperature();
    delay(250);
    val3 = ec_func();
    delay(250);
    val5 = ph_func();
    delay(250);
    val5 = nitrogen();
    delay(250);
    val6 = phosphorous();
    delay(250);
    val7 = potassium();
    delay(250);
    val8 = dev_add_func();
    delay(250);
    val9 = dev_baud_func();
    delay(250);

    // Print values to the serial monitor
    Serial.print("Water: ");
    Serial.print(val1);
    Serial.println(" mg/kg");
    Serial.print("Tempetature: ");
    Serial.print(val2);
    Serial.println(" ºC");
    Serial.print("EC: ");
    Serial.print(val3);
    Serial.println(" mg/kg");
    Serial.print("PH: ");
    Serial.print(val4);
    Serial.println(" mg/kg");
    Serial.print("Nitrogen: ");
    Serial.print(val5);
    Serial.println(" mg/kg");
    Serial.print("Phosphorous: ");
    Serial.print(val6);
    Serial.println(" mg/kg");
    Serial.print("Potassium: ");
    Serial.print(val7);
    Serial.println(" mg/kg");
    Serial.print("Devive_Address: ");
    Serial.println(val8);
    Serial.print("Devive_Baud: ");
    Serial.println(val8);
    
    delay(20000);
}

byte water_func(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(water,sizeof(water))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

byte temperature(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(temp,sizeof(temp))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

byte ec_func(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(ec,sizeof(ec))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

byte ph_func(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(ph,sizeof(ph))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}


byte nitrogen(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(nitro,sizeof(nitro))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}
 
byte phosphorous(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(phos,sizeof(phos))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}
 
byte potassium(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(pota,sizeof(pota))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

byte dev_add_func(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(dev_add,sizeof(dev_add))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

byte dev_baud_func(){
  digitalWrite(DE,HIGH);
  digitalWrite(RE,HIGH);
  delay(10);
  if(Serial485.write(dev_baud,sizeof(dev_baud))==8){
    digitalWrite(DE,LOW);
    digitalWrite(RE,LOW);
    for(byte i=0;i<7;i++){
    Serial.print(Serial485.read(),HEX);
    values[i] = Serial485.read();
    Serial.print(values[i],HEX);
    }
    Serial.println();
  }
  return values[4];
}

But when I ran it, this is the output: enter image description here

I think I have some issues with how to transmit the message and read it, any idea?

Thanks!!!


Solution

  • I don't have that sensor so be aware that I am guessing here but I can give you a couple of pointers that might help.

    First of all, the datasheet for the sensor mentions the following:

    The default baud rate is 4800bit / s and the default address is 0x01

    So the first thing to try is to select that baud rate:

    Serial485.begin(4800, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN);
    

    I have no idea why the tutorial goes for 9600 baud but it seems wrong.

    The next thing you need to realize is that you are doing some things in your code that are redundant. You are loading the ModbusMaster.h library but not using it. You are instead sending and receiving Modbus frames manually.

    The easiest thing to do is just drop the library by removing these lines:

    #include <ModbusMaster.h>
    // instantiate ModbusMaster object
    ModbusMaster node;
    
    ....
    node.begin(2, Serial485);
    

    But my preferred solution would be to actually use the library (do not reinvent the wheel, right?).

    This is a short example to read temperature values from your sensor with ModbusMaster.h:

    #define PIN_5V_EN    0
    #define CAN_TX_PIN   22
    #define CAN_RX_PIN   21
    #define RS485_TX_PIN 19
    #define RS485_RX_PIN 18
    #define RS485_EN_PIN 23
    #define RE 23
    #define DE 0
    
    #include <Arduino.h>
    #include <HardwareSerial.h>
    #include <ModbusMaster.h>
    // instantiate ModbusMaster object
    ModbusMaster node;
    HardwareSerial Serial485(2);
    
    // Modbus RTU requests for reading NPK values
    // These are not needed, the library will do its magic
    
    
    // A variable used to store NPK values
    byte values[11];
    
    void setup()
    {
        pinMode(RS485_EN_PIN, OUTPUT);
    
        pinMode(PIN_5V_EN, OUTPUT);
    
        Serial.begin(9600);
        Serial485.begin(4800, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); // Changed to 4800, default value
        delay(5);
        node.begin(1, Serial485); // Changed to ID=1, default value
    
    
    }
    
    void loop()
    {
        uint8_t result;
        uint16_t data_temperature;
        
        result = node.readHoldingRegisters(0x0200, 1); // use library to read holding register 0x0200, just 1 register that contains the temperature
        
        // do something with data if read is successful-->store
        if (result == node.ku8MBSuccess) {
            data_temperature = node.getResponseBuffer(0);
        }
    
        // Print values to the serial monitor
        Serial.print("Tempetature: ");
        Serial.print(data_temperature/100); //scale divinding by 100
        Serial.println(" ºC");
        
        
        delay(20000);
    
    }
    

    As you can see I've changed both the baud rate and the Modbus slave ID to 1 with node.begin(1, Serial485); to keep the default values as per the datasheet.

    If you you use the library you don't have to take care of building, sending and receiving frames. Plus the CRCs are calculated and checked for you.

    Finally, a couple of things that are really useful when you are debuggin a project like yours are:

    • A USB RS485 adaptor for your laptop/desktop computer. If you have one of these you can connect to your sensor using something like QModMaster then you can check the correct baud rate or what registers to read directly from a GUI instead of messing with code.
    • A scope makes the job much easier. You can check the wiring is correct and really see the frames running on the bus.

    You can get a glimpse of how easy it easy to debug the configuration and map register of a Modbus device just following these steps (you should get the same temperature value you got using the Arduino code above):

    -Download, extract and run QModMaster (qModMaster.exe). You might need to run as administrator.

    -Set up your port selecting Modbus RTU from menu Options:

    Modbus settings

    You might need to go to your Device Manager to check your port number. Other settings are the same as for your board.

    -Save your settings by clicking OK. Now at the main window change Function Code to Read Holding Registers (0x03) and choose value 512 (0x0200) for Start Address , you should see something like this:

    QModMaster main screen

    -Finally, go to Connect on menu Commands or click on the third icon starting from the left. Your temperature value (multiplied by 100) should appear on the first register (the box showing -/- just before connecting).

    If you don't get the value, the only problem could be either something wrong with your sensor (power or otherwise) or a wiring issue. For my USB adaptor I'd have to connect A+, B- and GND (do not forget that one): USB RS485 adaptor