MAX485 TTL to RS-485 Modules for soil NPK sensor

I've been thinking about this and it seems that it would be useful if there were a way of monitoring the RS485 bus when messages were being sent and hopefully received.

This bit of code is for an Arduino UNO using a software serial port (AltSoftSerial) to listen in on the RS485 bus. It is limited to a RS485 bus speed of 19200 baud (actually a bit higher, but not 38400).

// RS485 Monitor Demo - Arduino UNO
//
// Uses AltSoftSerial library for a software serial port for RS485 comms
// Get it at https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
// Software serial Tx = pin 9 , Rx = pin 8
//
// 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
//

#include <AltSoftSerial.h>

AltSoftSerial softSerial;  // software serial port - for RS485

#define RE_PIN 7
#define DE_PIN 6

uint8_t rxByteCount = 0;

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

  pinMode(RE_PIN, OUTPUT);
  pinMode(DE_PIN, OUTPUT);
  digitalWrite(RE_PIN, LOW); // Enable RS485 receiver
  digitalWrite(DE_PIN, LOW); // Disable RS485 transmitter

  Serial.println(F("Ready for data."));
}

void loop() {
  uint8_t rxByte;

  if (softSerial.available()) {
    rxByte = softSerial.read();
    rxByteCount++;
    Serial.print((rxByte >> 4) & 0x0F, HEX);
    Serial.print(rxByte & 0x0F, HEX);
    Serial.print(" ");
  }
  if ( rxByteCount > 15 ) {
    rxByteCount = 0;
    Serial.println();
  }
}

If you have a spare Arduino UNO and MAX485 line driver board, then you can use it to listen in to the bytes on the bus. It prints them out in hexadecimal. Just extend your bus to wire up the extra RS485 module.

You should then be able to watch what your code sends out and the response coming back.

Hopefully it will help you in debugging where the problem might be.

So this got me a bit intrigued. I don't have one of those soil sensors, so I used the demo version of WinModbus to simulate it. I also modified the code slightly to print out differently.

Here's the code I used:

#include <SoftwareSerial.h>

#define RE 8
#define DE 7

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];
SoftwareSerial mod(2, 3);

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

void loop() {
  byte val1, val2, val3;
  Serial.print("   Nitrogen: ");
  val1 = nitrogen();
  Serial.print(" = ");
  Serial.print(val1);
  Serial.println(" mg/kg");
  delay(250);

  Serial.print("Phosphorous: ");
  val2 = phosphorous();
  Serial.print(" = ");
  Serial.print(val2);
  Serial.println(" mg/kg");
  delay(250);

  Serial.print("  Potassium: ");
  val3 = potassium();
  Serial.print(" = ");
  Serial.print(val3);
  Serial.println(" mg/kg");
  Serial.println();
  Serial.println();
  delay(5000);
}

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

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

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

And here's a screen capture from WinModbus:


I've given the raw values 1, 2 & 3 to the three registers holding Nitrogen, Phosphorus & Potassium. And this is what the serial monitor shows from sketch start:

   Nitrogen: FF FF FF FF FF FF FF  = 255 mg/kg
Phosphorous: 1 3 2 0 1 79 84  = 1 mg/kg
  Potassium: 1 3 2 0 2 39 85  = 2 mg/kg

   Nitrogen: 1 3 2 0 3 F8 45  = 3 mg/kg
Phosphorous: 1 3 2 0 1 79 84  = 1 mg/kg
  Potassium: 1 3 2 0 2 39 85  = 2 mg/kg

   Nitrogen: 1 3 2 0 3 F8 45  = 3 mg/kg
Phosphorous: 1 3 2 0 1 79 84  = 1 mg/kg
  Potassium: 1 3 2 0 2 39 85  = 2 mg/kg

The initial value received on the first request is garbage, but after that all the responses are out by 1 message. Compare the raw Phosphorus response which the sketch displays to the message that WinModbus has actually sent. The sketch is displaying the response that belongs to the Nitrogen query.

At first glance, I can't see if i've missed something obvious but I'll report back with more if I do figure this out!

Since my problem seem to be something different than the one discussed in this Topic i started a new Topic: Modbus: NPK-Sensor does not respond at all

@markd833 thanks for your suggestions. I really appreciate it. I will try what happens using WinModbus right now.

Ok, so here's a different version of the original code that now uses the AltSoftSerial library from PJRC - see link in code.

// Crude demo code to read NPK Soil sensor based on the How 2 Electronics code at
// https://how2electronics.com/measure-soil-nutrient-using-arduino-soil-npk-sensor/
// but without the display.
//
// Use with caution as I don't have the real sensor, just simulated it using WinModbus.
//
// Uses PJRC AltSoftSerial library which is better than the standard SoftwareSerial library
// Get it here: https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
//
// All pin numbers are for an Arduino UNO.

#include <AltSoftSerial.h>

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

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;

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( 1000 );
}

void loop() {
  byte val1, val2, val3;
  Serial.print("   Nitrogen: ");
  val1 = nitrogen();
  Serial.print(" = ");
  Serial.print(val1);
  Serial.println(" mg/kg");
  delay(250);

  Serial.print("Phosphorous: ");
  val2 = phosphorous();
  Serial.print(" = ");
  Serial.print(val2);
  Serial.println(" mg/kg");
  delay(250);

  Serial.print("  Potassium: ");
  val3 = potassium();
  Serial.print(" = ");
  Serial.print(val3);
  Serial.println(" mg/kg");
  Serial.println();
  Serial.println();
  delay(5000);
}

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();
  
  // switch RS-485 to receive mode
  digitalWrite(DE, LOW);
  digitalWrite(RE, LOW);

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

  // 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(100);
  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(100);
  for (byte i = 0; i < 7; i++) {
    values[i] = mod.read();
    Serial.print(values[i], HEX);
    Serial.print(' ');
  }
  return values[4];
}

The output from the code when talking to the simulated sensor (using WinModbus) now looks like:

   Nitrogen: 1 3 2 0 1 79 84  = 1 mg/kg
Phosphorous: 1 3 2 0 2 39 85  = 2 mg/kg
  Potassium: 1 3 2 0 3 F8 45  = 3 mg/kg

   Nitrogen: 1 3 2 0 1 79 84  = 1 mg/kg
Phosphorous: 1 3 2 0 2 39 85  = 2 mg/kg
  Potassium: 1 3 2 0 3 F8 45  = 3 mg/kg

   Nitrogen: 1 3 2 0 1 79 84  = 1 mg/kg
Phosphorous: 1 3 2 0 2 39 85  = 2 mg/kg
  Potassium: 1 3 2 0 3 F8 45  = 3 mg/kg

At least now the response is working correctly. I don't know why there was a problem with the original SoftwareSerial code. The original author must have got it working so maybe an update to the library broke something along the way.

EDIT: I tried a slightly different Modbus simulator called ModRSsim2 and had to tweak the crude delay to 200ms before reading in the data, otherwise it always reported back 0xFF (= -1) indicating that there was no data to read.

I've posted some code in this discussion that might help. It uses the ModbusMaster library rather than direct writing to the serial port.

i have had success using esp8266 to connect to rs485 and display on screen (including web server/blynk). And it is working perfectly. The power supply for it is only 5v (using the same 5v power supply for both npk sensor and esp8266). I have some pictures