MAX485 TTL to RS-485 Modules for soil NPK sensor

thank you very much for your help.
i think now i have gotten it how it works.
i will work on the project this weekend and will tell the results if they worked. but i am pretty sure it does.
thank you and with best regards M0iN94

1 Like

A post was split to a new topic: Npk soil sensor

Hi can you share the code the mendapatkan NPK value

Does anyone have the inquiry code to get 7 values ?

I have problems with my soil sensor (7-in-1) I try to make a code that only need 1 Inquiry code for take the 7 values. I found a Inquiry code that everyone use it. but in my code show me 6553 for every value, and i don´t know what it is. It´s not the connection because i tried with the 3-in-1 code, and it´s works. I think is the Inquiry code ,but i don´t understand why.

#include <SoftwareSerial.h>
#include <Wire.h>
#define RE 19  //22
#define DE 18  //23
#define DI 4  //17 TX2  //1 TX0
#define RO 15  //16 RX2  //3 RX0

#define led_on 23
#define led_capture 27
#define led_Soilsensor 25

const byte test[8] = {0X01,0X03,0X00,0X00,0X00,0X07,0X04,0X08};
byte values[19] = {};

int moisture_val, CE, N_val, P_val, K_val;
float temperature_val = 0.00;
float PH_val = 0.00;
String chain;

SoftwareSerial mod(RO,DI);

void setup() {
  Serial.begin(9600);
  mod.begin(9600);
  
  pinMode(DE, OUTPUT);
  pinMode(RE, OUTPUT);
  pinMode(led_on, OUTPUT);
  pinMode(led_capture, OUTPUT);
  pinMode(led_Soilsensor, OUTPUT);
  digitalWrite(led_on, HIGH);
}

void loop() {
  collect_data();
  
  moisture_val = (values[3]<<8 | values[4])* 0.1;  
  temperature_val = (values[5]<<8 | values[6])* 0.1;
  CE = (values[7]<<8 | values[8])* 0.1;
  PH_val = (values[9]<<8 | values[10])* 0.1;
  N_val = (values[11]<<8 | values[12]);
  P_val = (values[13]<<8 | values[14]);
  K_val = (values[15]<<8 | values[16]);


  Serial.print("Humidity: "); Serial.println(moisture_val);
  Serial.print("Temperature: "); Serial.println(temperature_val);
  Serial.print("CE: "); Serial.println(CE);
  Serial.print("PH: "); Serial.println(PH_val);
  Serial.print("N: "); Serial.println(N_val);
  Serial.print("P: "); Serial.println(P_val);
  Serial.print("K: "); Serial.println(K_val);
  delay(1000);

}

float collect_data(){
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);
  if(mod.write(test,sizeof(test))==8){
    //mod.flush();
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);
  for(byte i=0;i<19;i++){
    values[i] = mod.read();
  }  
 }
 digitalWrite(led_capture, HIGH);
 delay(100);
 digitalWrite(led_capture, LOW);
 delay(100);
}

And it show me this:

What does the datasheet for your specific sensor say about Modbus communication?

Hoja mdbus 1_merged.pdf (7,5 MB)
Sorry for the disorder

hi,can i have the solution you have done for the all in one sensor...

Has anyone managed to solve the data reading correctly from the soil sensor?
Please can you share the code.

Which soil sensor? There seems to be a 3 parameter version, a 7 parameter version and a few other 1 or 2 parameter versions available as well.

What is the issue with your code? What doesn't work?

I am working with a 7 parameter soil sensor (JXCT-IoT). I did tests to print each parameter and it prints the correct value but when I put all the code, the values ​​are mixed for example the soil moisture Temperature: 0.7 % value is printed as the PH Ph: 24.3 PH value.
It should be clarified that I am using arduino uno, rs485, the sensor works with 5v, the altsoft library and the pins (6, 7, 8, 9) for the communication of the arduino uno with the sensor.

Please, if any of you managed to solve this problem, could you guide me or share the code. I will be very grateful for your help.

Post your current code and a link to the datasheet for your 7 parameter sensor.

Thank you @markd833 for your answer, this the manual link sensor soil 7 in 1 and my current code:

#include <AltSoftSerial.h>

// RO to pin 8 & DI to pin 9 when using AltSoftSerial
#define RE 6
#define DE 7

const byte temp[] = {0x01,0x03, 0x00, 0x13, 0x00, 0x01, 0x75, 0xcf};//
const byte mois[]  = {0x01,0x03,0x00,0x12,0x00,0x01,0x24,0x0F};
const byte econ[] = {0x01,0x03, 0x00, 0x15, 0x00, 0x01, 0x95, 0xce};
const byte ph[] = {0x01,0x03, 0x00, 0x06, 0x00, 0x01, 0x64, 0x0b};//0x0B64

const byte nitro[] = { 0x01, 0x03, 0x00, 0x1E, 0x00, 0x01, 0xE4, 0x0C };
const byte phos[] = { 0x01, 0x03, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc };
const byte pota[] = { 0x01, 0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0 };

byte values[11];
AltSoftSerial mod;

float envhumidity = 0.0, envtemperature = 0.0, soil_ph = 0.0, soil_mois = 0.0, soil_temp = 0.0;
byte val1 = 0, val2 = 0, val3 = 0, val4 = 0,val5 = 0, val6 = 0, val7 = 0;

void setup() {
  Serial.begin(9600);
  mod.begin(9600);
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);

  // put RS-485 into receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  delay(3000);
}

void loop() {
  val1 = moisture();
  soil_mois = val1/10.0;
  delay(1000);
  soil_temp = temperature()/10.0;
  delay(1000);
  val3 = econduc();
  delay(1000);
  val4 = phydrogen()/10;
  soil_ph = val4;
  delay(1000);
  val5 = nitrogen();
  delay(1000);
  val6 = phosphorous();
  delay(1000);
  val7 = potassium();
  delay(1000);

  Serial.print("Moisture: ");Serial.print(soil_mois);Serial.println(" %");
  delay(1000);
  Serial.print("Temperature: ");Serial.print(soil_temp);Serial.println(" C");
  delay(1000);
  Serial.print("EC: ");Serial.print(val3);Serial.println(" us/cm");
  delay(1000);
  Serial.print("ph: ");Serial.print(soil_ph);Serial.println(" ph");
  delay(1000);
  Serial.print("Nitrogen: "); Serial.print(val5);Serial.println(" mg/kg");
  delay(1000);
  Serial.print("Phosphorous: ");Serial.print(val6);Serial.println(" mg/kg");
  delay(1000);
  Serial.print("Potassium: ");Serial.print(val7);Serial.println(" mg/kg");
  Serial.println();
  delay(3000);
}

byte moisture() {
  // clear the receive buffer
  mod.flushInput();

  // switch RS-485 to transmit mode
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);

  // write out the message
  for (uint8_t i = 0; i < sizeof(mois); i++) mod.write(mois[i]);

  // wait for the transmission to complete
  mod.flush();

  // switching RS485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  // delay to allow response bytes to be received!
  delay(200);

  // read in the received bytes
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

byte temperature() {
  // clear the receive buffer
  mod.flushInput();

  // switch RS-485 to transmit mode
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);

  // write out the message
  for (uint8_t i = 0; i < sizeof(temp); i++) mod.write(temp[i]);

  // wait for the transmission to complete
  mod.flush();

  // switching RS485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  // delay to allow response bytes to be received!
  delay(200);

  // read in the received bytes
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[3]<<8|values[4];
}

byte econduc() {
  // clear the receive buffer
  mod.flushInput();

  // switch RS-485 to transmit mode
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);

  // write out the message
  for (uint8_t i = 0; i < sizeof(econ); i++) mod.write(econ[i]);

  // wait for the transmission to complete
  mod.flush();

  // switching RS485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  // delay to allow response bytes to be received!
  delay(200);

  // read in the received bytes
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

byte phydrogen() {
  // clear the receive buffer
  mod.flushInput();
  // switch RS-485 to transmit mode
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);

  // write out the message
  for (uint8_t i = 0; i < sizeof(ph); i++) mod.write(ph[i]);

  // wait for the transmission to complete
  mod.flush();

  // switching RS485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  // delay to allow response bytes to be received!
  delay(200);

  // read in the received bytes
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

byte nitrogen() {
  // clear the receive buffer
  mod.flushInput();

  // switch RS-485 to transmit mode
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);

  // write out the message
  for (uint8_t i = 0; i < sizeof(nitro); i++) mod.write(nitro[i]);

  // wait for the transmission to complete
  mod.flush();

  // switching RS485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  // delay to allow response bytes to be received!
  delay(200);

  // read in the received bytes
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

byte phosphorous() {
  mod.flushInput();
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);
  for (uint8_t i = 0; i < sizeof(phos); i++) mod.write(phos[i]);
  mod.flush();
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);
  // delay to allow response bytes to be received!
  delay(200);
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

byte potassium() {
  mod.flushInput();
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(1);
  for (uint8_t i = 0; i < sizeof(pota); i++) mod.write(pota[i]);
  mod.flush();
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);
  // delay to allow response bytes to be received!
  delay(200);
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    // Serial.print(values[i], HEX);
    // Serial.print(' ');
  }
  return values[4];
}

For anybody else reading this thread that is having problems with the wrong results coming back from the NPK sensor, here's why:

The issue is down to the awful code circulating around various websites that is used to read from one of those NPK sensors. Specifically in how the reading of the response from the sensor is handled.

The code transmits a request for the first parameter and then assumes that the response from the NPK sensor is instantly available as soon as the request is transmitted. The code then attempts to read the non-existant response by blindly reading in the number of bytes expected to be available without carrying out any checks first. This results in the first request returning only 0xFF (actually -1 signalling no character available).

The code then transmits a request for the second parameter and once again reads in the expected number of response bytes without carrying out any checks. However, what is read is actually the response to the first parameter query.

Similarly when requesting the third parameter, what is read in is the response to the second parameter query.

The code then goes back and requests the first parameter and actually reads in the response to the third parameter.

That's why the values that are read back are out of step with the requests.

There is a bit of code i've posted at post #14 in the following discussion that should work for an UNO. It's no substitute for a proper Modbus library:

1 Like

A follow-up to my previous post. Here is my attempt to use the ModbusMaster library on an Arduino UNO with a software serial port using AltSoftSerial rather than the canned Modbus messages and error prone software that seems to be circulating various websites:

// Attempt to access a JXCT NPK sensor to read Nitrogen, Potassium & Phosphorus
// values using an Arduino UNO clone.
//
// This attempt uses the AltSoftSerial & ModbusMaster libraries. Get the libraries
// via the Arduino IDE or manually:
// Get AltSoftSerial at https://github.com/PaulStoffregen/AltSoftSerial
// Get ModbusMaster  at https://github.com/4-20ma/ModbusMaster
//
// RS485 module connected to Arduino UNO as follows:
// RS485 DI signal to pin 9
// RS485 RO signal to pin 8
// RS485 RE signal to pin 7
// RS485 DE signal to pin 6
// RS485 VCC to 5V
// RS485 GND to GND
//
// Assumes that the sensor address is 1, Nitrogen is address 0x1E, Phosphorus is address 0x1F
// and Potassium is address 0x20. Change the code if yours are different.
//
// NOTE: I do not have this sensor, so I simulated it using ModRSsim2 available from https://sourceforge.net/projects/modrssim2/
// 

#include <ModbusMaster.h>
#include <AltSoftSerial.h>

#define NITROGEN_ADDR   0x1E
#define PHOSPHORUS_ADDR 0x1F
#define POTASSIUM_ADDR  0x20

#define MAX485_DE      6
#define MAX485_RE_NEG  7

AltSoftSerial swSerial;
ModbusMaster node;

// Put the MAX485 into transmit mode
void preTransmission()
{
  digitalWrite(MAX485_RE_NEG, 1);
  digitalWrite(MAX485_DE, 1);
}

// Put the MAX485 into receive mode
void postTransmission()
{
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);
}

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

  // configure the MAX485 RE & DE control signals and enable receive mode
  pinMode(MAX485_RE_NEG, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  digitalWrite(MAX485_RE_NEG, 0);
  digitalWrite(MAX485_DE, 0);

  // Modbus communication runs at 9600 baud
  swSerial.begin(9600);

  // Modbus slave ID of NPK sensor is 1
  node.begin(1, swSerial);

  // Callbacks to allow us to set the RS485 Tx/Rx direction
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);
}

void loop() {
  uint8_t result;

  // NITROGEN
  result = node.readHoldingRegisters(NITROGEN_ADDR, 1);
  if (result == node.ku8MBSuccess)
  {
    Serial.print("   Nitrogen: ");
    Serial.print(node.getResponseBuffer(0x0));
    Serial.println(" mg/kg");
  } else {
    printModbusError( result );
  }

  // PHOSPHORUS
  result = node.readHoldingRegisters(PHOSPHORUS_ADDR, 1);
  if (result == node.ku8MBSuccess)
  {
    Serial.print("Phosphorous: ");
    Serial.print(node.getResponseBuffer(0x0));
    Serial.println(" mg/kg");
  } else {
    printModbusError( result );
  }

  // POTASSIUM
  result = node.readHoldingRegisters(POTASSIUM_ADDR, 1);
  if (result == node.ku8MBSuccess)
  {
    Serial.print("  Potassium: ");
    Serial.print(node.getResponseBuffer(0x0));
    Serial.println(" mg/kg");
  } else {
    printModbusError( result );
  }
  Serial.println();
  delay(2000);
}

// print out the error received from the Modbus library
void printModbusError( uint8_t errNum )
{
  switch ( errNum ) {
    case node.ku8MBSuccess:
      Serial.println(F("Success"));
      break;
    case node.ku8MBIllegalFunction:
      Serial.println(F("Illegal Function Exception"));
      break;
    case node.ku8MBIllegalDataAddress:
      Serial.println(F("Illegal Data Address Exception"));
      break;
    case node.ku8MBIllegalDataValue:
      Serial.println(F("Illegal Data Value Exception"));
      break;
    case node.ku8MBSlaveDeviceFailure:
      Serial.println(F("Slave Device Failure"));
      break;
    case node.ku8MBInvalidSlaveID:
      Serial.println(F("Invalid Slave ID"));
      break;
    case node.ku8MBInvalidFunction:
      Serial.println(F("Invalid Function"));
      break;
    case node.ku8MBResponseTimedOut:
      Serial.println(F("Response Timed Out"));
      break;
    case node.ku8MBInvalidCRC:
      Serial.println(F("Invalid CRC"));
      break;
    default:
      Serial.println(F("Unknown Error"));
      break;
  }
}

And this is the wiring diagram:

I've also added in printing of Modbus errors. Check that your NPK sensor is set for Modbus address 1 and that the Nitrogen, Phosphorus & Potassium addresses are the same as I used.

can you please email the working code to my email id madhukar.sths@gmail.com

Thanks and regards

can you please share the working code that tnkhoa2012 shared with you ? i have been getting random data. please help!
madhukar.sths@gmail.com
thanks and regards.

Post the code you are using and the serial monitor output.

I HAVE SHARED IT IN THE FORUM

Vào Th 4, 4 thg 1, 2023 vào lúc 00:02 Madhukarji via Arduino Forum <notifications@arduino.discoursemail.com> đã viết:

actually whats happening is , i am getting data of N P and K but when ever I add the register value any other i.e soil moisture, PH , temperature or EC. Nitrogen data starts getting random.

this is the code:
#include <SoftwareSerial.h>

const byte N[] = {0x01,0x03, 0x00, 0x1e, 0x00, 0x01, 0xE4, 0x0c};
const byte P[] = {0x01,0x03, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc};
const byte K[] = {0x01,0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0};
const byte sM[] = {0x01,0x03, 0x00, 0x12, 0x00, 0x01, 0x24, 0x0F};
const byte pH[] = {0x01, 0x03, 0x00, 0x06, 0x00, 0x01, 0X64, 0x0B};

byte values[11];

SoftwareSerial mod(23,22);

void setup() {
Serial.begin(9600);
mod.begin(9600);
delay(3000);
}

void loop() {

sPrint("Nitrogen:", getValue(N, sizeof(N)), "ppm");
delay(250);

sPrint("Phosphorus:", getValue(P, sizeof(P)), "ppm");
delay(250);

sPrint("Potassium:", getValue(K, sizeof(K)), "ppm");
delay(250);

sPrint("Humidity:", getValue(sM, sizeof(sM)), "%RH");
delay(250);

sPrint("pH:", getValue(pH, sizeof(pH)), "pH");
delay(250);

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

void sPrint(String name, uint16_t value, String unit){
Serial.println(name + value + unit);
}

uint16_t getValue(const byte * inArray, size_t size){
delay(10);
if(mod.write(inArray, size)==8){
for(byte i=0;i<7;i++){
//Serial.print(mod.read(),HEX);
values[i] = mod.read();
//values[i] = inArray[i];
Serial.print(values[i],HEX);
}
Serial.println();
}
uint16_t rValue = (values[3]<<8)|values[4];
return rValue;
}
########################
i am using this ttl485
image

connection are okay, as i am getting serial data.

thanks and regards