Your plants, like you, require food. In order to be healthier and more productive, plants need the “big three” essential nutrients nitrogen, phosphorus, and potassium, also known as NPKs.
If the soil in your garden doesn’t have enough of these nutrients, plants won’t grow to their full potential. Therefore, it is essential to measure the soil’s current N, P, and K levels to determine how much additional nutrient content must be added to increase crop fertility.
Using an NPK Soil Sensor and an Arduino, you can quickly determine the levels of these nutrients in the soil.
What Is NPK? and Why It’s So Important?
The letters NPK stand for the three major nutrients that plants require to grow and thrive: nitrogen, phosphorus, and potassium.
Nitrogen is responsible for the growth and greenness of plant leaves.
Phosphorus helps the plant grow strong roots, fruit, and flowers.
Potassium improves the overall health and hardiness of a plant.
JXCT Soil NPK sensor
The JXCT Soil NPK sensor is a low-cost, quick-responding, reasonably accurate, and portable sensor. It aids in the real-time monitoring of NPK nutrient content in soil for smart agriculture.
The soil NPK sensor can detect the levels of nitrogen, phosphorus, and potassium in the soil (not in water). It helps determine soil fertility, allowing for a more systematic assessment of soil condition.
The sensor operates on 5-30V and consumes very little power. According to the datasheet, it is capable of measuring nitrogen, phosphorus, and potassium with a resolution of up to 1 mg/kg (mg/l).
The sensor includes a stainless steel probe that is rust-proof, electrolytic resistant, and salt-alkali resistant. It can therefore be used with any type of soil, including alkaline soil, acid soil, substrate soil, seedling bed soil, and coconut bran soil.
The probe is sealed to the body with high-density epoxy resin to prevent moisture from entering the body.
The best part is that the sensor has an IP68 rating, which means it is protected against dust and moisture, allowing it to operate normally for a very long time.
To be used effectively over long distances, the sensor features the RS485 communication interface and supports the standard Modbus-RTU communication protocol.
It should be noted that the sensor cannot be used with an Arduino directly. To communicate with Arduino, you’ll need an RS-485 transceiver module that converts a UART serial stream to RS-485.
Technical Specifications
Here are the specifications:
Power | 5V-30V |
Measuring Range | 0-1999 mg/kg (ml/l) |
Operating Temperature | 5-45 °C |
Resolution | 1mg/kg (ml/l) |
Precision | ±2% F.S. |
Output Signal | RS485 |
Protection Class | IP68 |
Heads-up
The JXCT soil NPK sensor is an electrical conductivity sensor (EC sensor). It does not directly measure the soil’s NPK content, but rather estimates it based on the electrical conductivity of the soil.
Be aware that this method is tricky and prone to producing inaccurate results.
For your information, there are inexpensive sensors out there that work on the same principle.
Soil NPK sensor Pinout
The sensor comes with a 2m cable with tinned copper wires. The pinout is shown in the figure below.
VCC is the VCC pin. Connects to 5V – 30V.
A is a differential signal that is connected to the A pin of the MAX485 Modbus Module.
B is another differential signal that is connected to the B pin of the MAX485 Modbus Module.
GND is the Ground pin.
Wiring a Soil NPK Sensor to an Arduino
As previously stated, the NPK sensor cannot be used directly with an Arduino. To communicate with Arduino, you’ll need an RS-485 transceiver module that converts a UART serial stream to RS-485, such as the one shown below.
Okay, so let’s get to the wiring.
The soil NPK Sensor has four wires. The power wire is brown and should be connected to the 5V-30V power supply. The ground wire is black and should be connected to a common ground.
The yellow wire of the NPK sensor should be connected to the RS485 module’s A pin, and the blue wire should be connected to the RS485 module’s B pin.
Connect the RS485 module’s R0 and DI pins to the Arduino’s digital pins 2 and 3, respectively. These digital pins will be used as virtual RX and TX serial lines.
Note that if you are using a Mega or Mega 2560, you should use digital pins 10 and 11, as digital pins 2 and 3 do not support change interrupts, which is a known limitation of the softwareSerial library.
The RS485 module’s VCC pin should be connected to the Arduino’s 5V output, and the DE and RE pins should be connected to digital pins 7 and 8, respectively.
Finally, make sure your circuit and Arduino share a common ground.
The wiring is shown in the image below.
Usage Instructions
Choose an appropriate measuring location, avoid stones, make sure the steel probe does not come into contact with any hard objects, and insert the sensor vertically into the soil.
Optionally, the sensor can be inserted horizontally into the pit, in which case the pit is excavated vertically with a diameter greater than 20 cm before being tightly backfilled.
What is Modbus?
Just in case you don’t know what Modbus is, it’s probably a good idea to brush up on it before we move on.
Modbus is a de-facto standard for industrial communication protocols because it is open source and royalty-free. For data transfer, it uses RS-485, RS-422, and RS-232 interfaces, as well as Ethernet TCP/IP networks (the Modbus TCP protocol).
There are various implementation types of Modbus. The following are the most popular protocol types:
- Modbus RTU (The one we are going to configure)
- Modbus ASCII
- Modbus TCP
Modbus RTU Protocol
Modbus RTU is a master-slave protocol. In this protocol, only the master device (in our case, an Arduino) is allowed to initiate communication. The other devices on the network are known as slaves (in our case, NPK sensors), and they can only respond to requests. Modbus RTU can support up to 247 devices on the same physical network.
Modbus RTU Data Frame
Over the Modbus bus, messages are exchanged between the master and slave in the form of data frames. There are Request and Response frames. A request is a message sent by the master to one of the slaves. A response is a message sent by the slave to the master.
The request also includes a checksum which is used to make sure the messaged is not corrupted on the way to the slave.
A typical Modbus RTU message contains the address of the SlaveID device, the function code, the data based on the function code, and the CRC of the checksum.
The following is an example of a Modbus RTU Request frame instructing slave #1 to return the value of a single register beginning at address 2.
All slaves except slave #1 ignore this message. Slave #1 then sends a response message that looks like this:
Modbus RTU requests for reading NPK Sensor
The following are three distinct Modbus RTU requests for reading the Nitrogen (N), Phosphorous (P), and Potassium (K) values from the NPK Sensor. If the request is successful, the sensor sends a Response message containing the reading.
For Reading Nitrogen
The Modbus RTU request for reading the Nitrogen level is:
You will receive a similar response:
From the Response, you can obtain the nitrogen level. For instance, if the response contains the value 0x0020 in the data field, the nitrogen level can be calculated as follows:
Nitrogen = 0x0020HEX = 32DEC => 32 mg/kg
For Reading Phosphorous
Similarly, the Modbus RTU request for reading the phosphorus level is:
You will receive a similar response:
From the Response, you can obtain the phosphorus level. For instance, if the response contains the value 0x0025 in the data field, the phosphorus level can be calculated as follows:
Phosphorous = 0x0025HEX = 37DEC => 37 mg/kg
For Reading Potassium
Similar to the previous two requests, the Modbus RTU request for reading the Potassium level is:
You will receive a similar response:
From the Response, you can obtain the potassium level. For instance, if the response contains the value 0x0030 in the data field, the potassium level can be calculated as follows:
Potassium = 0x0030HEX = 48DEC => 48 mg/kg
Arduino Example Code
The following test sketch reads the nitrogen, phosphorus, and potassium values from the soil NPK sensor and prints them to the serial monitor.
#include <SoftwareSerial.h>
#include <Wire.h>
// RE and DE Pins set the RS485 module
// to Receiver or Transmitter mode
#define RE 8
#define DE 7
// Modbus RTU requests for reading NPK 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};
// A variable used to store NPK values
byte values[11];
// Sets up a new SoftwareSerial object
// Digital pins 10 and 11 should be used with a Mega or Mega 2560
SoftwareSerial mod(2, 3);
//SoftwareSerial mod(10, 11);
void setup() {
// Set the baud rate for the Serial port
Serial.begin(9600);
// Set the baud rate for the SerialSoftware object
mod.begin(9600);
// Define pin modes for RE and DE
pinMode(RE, OUTPUT);
pinMode(DE, OUTPUT);
delay(500);
}
void loop() {
// Read values
byte val1,val2,val3;
val1 = nitrogen();
delay(250);
val2 = phosphorous();
delay(250);
val3 = potassium();
delay(250);
// Print values to the serial monitor
Serial.print("Nitrogen: ");
Serial.print(val1);
Serial.println(" mg/kg");
Serial.print("Phosphorous: ");
Serial.print(val2);
Serial.println(" mg/kg");
Serial.print("Potassium: ");
Serial.print(val3);
Serial.println(" mg/kg");
delay(2000);
}
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++){
//Serial.print(mod.read(),HEX);
values[i] = mod.read();
Serial.print(values[i],HEX);
}
Serial.println();
}
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++){
//Serial.print(mod.read(),HEX);
values[i] = mod.read();
Serial.print(values[i],HEX);
}
Serial.println();
}
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++){
//Serial.print(mod.read(),HEX);
values[i] = mod.read();
Serial.print(values[i],HEX);
}
Serial.println();
}
return values[4];
}
You will see something like this.