6-in-1 soil sensor

Hello @markd833 Please I need help with this. I am working with a 6-in-1 soil sensor and I keep on getting this error "Sensor timeout or incomplete frame". Below is the code I have been using.

#include <SoftwareSerial.h>

// Define the pins for RS485 communication
#define RE 8
#define DE 7

// Modbus RTU requests for reading NPK, temperature, moisture, and conductivity values
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};
const byte temperature[] = {0x01, 0x03, 0x00, 0x01, 0x00, 0x01, 0xd4, 0x0b};
const byte moisture[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0a};
const byte conductivity[] = {0x01, 0x03, 0x00, 0x02, 0x00, 0x01, 0x25, 0xca};

byte soilSensorResponse[7]; // Response length for single register read
SoftwareSerial mod(2, 3); // Software serial for RS485 communication

void setup() {
  Serial.begin(9600); // Initialize serial communication for debugging
  mod.begin(4800);    // Initialize software serial communication at 4800 baud rate (adjust if necessary)
  pinMode(RE, OUTPUT); // Set RE pin as output
  pinMode(DE, OUTPUT); // Set DE pin as output

  // Ensure RE and DE are low to start in receive mode
  digitalWrite(RE, LOW);
  digitalWrite(DE, LOW);
}

void sendRequest(const byte request[], size_t requestSize) {
  // Start the transmission mode for RS485
  digitalWrite(DE, HIGH);
  digitalWrite(RE, HIGH);
  delay(10);

  // Send the request frame to the soil sensor
  mod.write(request, requestSize);

  // End the transmission mode and set to receive mode for RS485
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);
  delay(10);
}

bool receiveResponse(byte response[], size_t responseSize) {
  unsigned long startTime = millis();
  while (mod.available() < responseSize && millis() - startTime < 1000) {
    delay(1);
  }

  if (mod.available() >= responseSize) {
    for (size_t i = 0; i < responseSize; i++) {
      response[i] = mod.read();
      Serial.print(response[i], HEX);
      Serial.print(" ");
    }
    Serial.println();
    return true;
  } else {
    Serial.println("Sensor timeout or incomplete frame");
    return false;
  }
}

void loop() {
  sendRequest(nitro, sizeof(nitro));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int Nitrogen_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    Serial.print("Nitrogen: ");
    Serial.print(Nitrogen_Int);
    Serial.println(" mg/kg");
  }
  delay(2000);

  sendRequest(phos, sizeof(phos));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int Phosphorus_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    Serial.print("Phosphorus: ");
    Serial.print(Phosphorus_Int);
    Serial.println(" mg/kg");
  }
  delay(2000);

  sendRequest(pota, sizeof(pota));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int Potassium_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    Serial.print("Potassium: ");
    Serial.print(Potassium_Int);
    Serial.println(" mg/kg");
  }
  delay(2000);

  sendRequest(temperature, sizeof(temperature));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int Temperature_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    float Temperature_Celsius = Temperature_Int / 10.0;
    if (Temperature_Int > 0x7FFF) { // Check if temperature is negative
      Temperature_Celsius = 0x10000 - Temperature_Int;
      Temperature_Celsius = -Temperature_Celsius / 10.0;
    }
    Serial.print("Temperature: ");
    Serial.print(Temperature_Celsius);
    Serial.println(" °C");
  }
  delay(2000);

  sendRequest(moisture, sizeof(moisture));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int Moisture_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    float Moisture_Percent = Moisture_Int / 10.0;
    Serial.print("Moisture: ");
    Serial.print(Moisture_Percent);
    Serial.println(" %RH");
  }
  delay(2000);

  sendRequest(conductivity, sizeof(conductivity));
  if (receiveResponse(soilSensorResponse, sizeof(soilSensorResponse))) {
    int EC_Int = int(soilSensorResponse[3] << 8 | soilSensorResponse[4]);
    float EC = EC_Int / 100.0;
    Serial.print("EC: ");
    Serial.print(EC);
    Serial.println(" mS/cm");
  }
  delay(2000);
}

After the while loop exits, I would print out the value returned by mod.available(). If it's zero, then the sensor isn't responding to your canned modbus messages.

Do you have the user manual for your specific soil sensor? Does it tie in with the register addrresses you are using?

I have tried to find a user manual and I couldn't. The link that the sensor came with to aid with the setup was leading to an error page. I tried using registers from a manual I found online.

I would advise you to contact the seller and ask for the user manual for your specific sensor. There are a lot of similarities between the register addresses that the various NPK type sensors use. There are also some differences. It may be possible to work out which one you have but there will always be an element of doubt unless the actual user manual is provided.

Does our sensor have any brand name and model number printed on a label on the side? Maybe it's JXCT or ComWinTop?

I got the sensor from AliExpress and I can't find a way to reach the seller. The sensor doesn't have a brand name or label on the side.


I need to use this for a school project that's due Friday. Is there a way to speculate the registers to be used?

Ok, that's not giving us much time but we can have a go. I think the first thing you need to establish is if you have wired up your sensor correctly. I've used an UNO when trying to help other forum users.

  • Which Arduino do you have?
  • Which RS485 module are you using?
  • How have you connected the Arduino, RS485 module and sensor together? Can you provide a drawing - hand drawn and photographed is fine.
  • How are you powering your sensor?

Here is some code I put together for an UNO that attempts to read a value from each register from 0x00 to 0x20 assuming a device address of 0x01 - which is usually the default address. The code then tries with a device address of 255, which I think sometimes gets a device with an unknown address to reveal itself.

You may need to change the values for RE & DE to match your pins.

// Attempt to access a JXCT type NPK sensor to read registers 0x00 to 0x2F
// to see if there is any response from the NPK sensor.
//
// This attempt uses SoftwareSerial & raw Modbus packets.
//
// Also requires the CRC library by Rob Tillaart - install it via Library Manager
//

#include <SoftwareSerial.h>
#include "CRC16.h"

#define RE 7
#define DE 6

const uint32_t TIMEOUT = 500UL;

uint8_t msg[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};
uint8_t values[11];
uint8_t regAddr = 0;
uint8_t devAddr = 0x01;

SoftwareSerial swSerial(2, 3);  // RX, TX

CRC16 crc(CRC16_MODBUS_POLYNOME,
          CRC16_MODBUS_INITIAL,
          CRC16_MODBUS_XOR_OUT,
          CRC16_MODBUS_REV_IN,
          CRC16_MODBUS_REV_OUT);
          
void setup() {
  Serial.begin(9600);
  swSerial.begin(9600);
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

  Serial.println("Modbus NPK register scanner.");
  Serial.println("Trying address 0x01");
  scan( 0x01 );
  Serial.println("Trying general address 0xFF");
  scan( 0xFF );
  Serial.println("Done. Press RESET to rescan");
}

void loop() {
}

void scan( uint8_t devAddr ) {
  uint32_t startTime = 0;
  uint16_t crcValue = 0;

  for ( regAddr=0; regAddr<33; regAddr++ )
  {
    // insert the device address
    msg[ 0 ] = devAddr;
    
    // insert the register address
    msg[ 3 ] = regAddr;
  
    // generate the CRC16 Modbus checksum
    crc.restart();
    for (uint8_t i=0; i<sizeof( msg )-2; i++ ) {
      crc.add( msg[i] );
    }
    crcValue = crc.calc();
  
    // and insert it into the test message
    msg[ sizeof(msg)-2 ] = (crcValue & 0xFF);
    msg[ sizeof(msg)-1 ] = ((crcValue >> 8) & 0xFF);
    
    Serial.print("TX: ");
    printHexMessage( msg, sizeof(msg) );
  
    // send the command
    digitalWrite(DE, HIGH);
    digitalWrite(RE, HIGH);
    delay( 5 );
    swSerial.write( msg, sizeof(msg) );
    swSerial.flush();
    digitalWrite(DE, LOW);
    digitalWrite(RE, LOW);
  
    Serial.print("RX: ");
    
    // read any data received and print it out
    startTime = millis();
    while ( millis() - startTime <= TIMEOUT ) {
      if (swSerial.available()) {
        printHexByte(swSerial.read());
      }
    }
    Serial.println();
    delay(200);
  }
}

void printHexMessage( uint8_t values[], uint8_t sz ) {
  for (uint8_t i = 0; i < sz; i++) {
    printHexByte( values[i] );
  }
  Serial.print("  -   ");
}

void printHexByte(byte b)
{
  Serial.print((b >> 4) & 0xF, HEX);
  Serial.print(b & 0xF, HEX);
  Serial.print(' ');
}

Hopefully you will get responses from some of the register queries.

A forum member called @qubits-us has also helped in getting RS485 comms going and given the short timescales, 2 brain cells will be better than 1!

  • Which Arduino do you have? Arduino Nano

  • Which RS485 module are you using? Max 485 TTL To Rs-485 Interface Module

  • How have you connected the Arduino, RS485 module and sensor together? Can you provide a drawing - hand drawn and photographed is fine.

  • How are you powering your sensor? I intend to use a battery once i solder but for now its still via by arduino connected to my PC
    [/quote]

Sorry, I have no idea where the wires are going based on that photograph.

Split this topic from an older one that was hijacked

A similar sensor... perhaps they use the same circuit board.

Alright. I'll send in a hand drawn circuit diagram

Also, I tried the code you sent in,


and I got this result.

If you didn't see anything displayed next to RX: on any line, then your sensor isn't responding at all. Hopefully it will be a simple wiring problem.

Ok. A few points:

  • It's not generally a good idea to power other devices from the 5V pin on the Arduino - you can probably power the RS485 module but I would power the sensor from another source
  • 5V is at the bottom end of the range of voltages that the sensor can be powered from (but I would think it should work from 5V)
  • You need a connection between the GND of the RS485 module and the GND of the Arduino.
  • Make sure that the pins you are using for RE & DE are the same as what your code is using.

Thank you. I'll check the wires for continuity and use a different source for the power supply

I just checked the wires and found some faulty wires. Then got this output on the serial monitor. I also used a different power source for the sensor.
I'm getting a value for the RX now

08:33:04.595 -> TX: 01 03 00 0A 00 01 A4 08 - RX: FF 08:33:05.322 -> TX: 01 03 00 0B 00 01 F5 C8 - RX: 08:33:06.032 -> TX: 01 03 00 0C 00 01 44 09 - RX: FF 08:33:06.781 -> TX: 01 03 00 0D 00 01 15 C9 - RX: 08:33:07.469 -> TX: 01 03 00 0E 00 01 E5 C9 - RX: 08:33:08.223 -> TX: 01 03 00 0F 00 01 B4 09 - RX: FF 08:33:08.913 -> TX: 01 03 00 10 00 01 85 CF - RX: 08:33:09.647 -> TX: 01 03 00 11 00 01 D4 0F - RX: FF 08:33:10.341 -> TX: 01 03 00 12 00 01 24 0F - RX: FF 08:33:11.060 -> TX: 01 03 00 13 00 01 75 CF - RX: 08:33:11.779 -> TX: 01 03 00 14 00 01 C4 0E - RX: FF

Also, I have been able to get the sensor manual from the seller. Since I'm a new user I'm unable to post an attachment.

It doesn't look like the sensor is responding at all. Make sure RE & DE are connected correctly and your RO & DI go to the correct pins too.

Look in your user manual to see if it mentions baud rate. It'll probably be 9600 or sometimes 4800. You should set your software serial port to that setting.

I'll check back later today as I'm away from a screen for a few hours.