How to Read Soil NPK Sensor with Arduino using RS485 and Modbus RTU
How to Read Soil NPK Sensor with Arduino using RS485 and Modbus RTU
This article explains how to read Nitrogen (N), Phosphorus (P), and Potassium (K) values from the PR-3000-TR-NPK-N01 soil sensor using the RS485 interface with Modbus RTU protocol, and display results on an OLED screen.
Required Components
- Arduino UNO R3
- MAX485 TTL to RS485 conversion module
- PR-3000-TR-NPK-N01 soil NPK sensor
- 128x64 I2C OLED display 0.96 inch
- 12V DC Power Adapter (for sensor)
- 9V 2A Power Adapter (for Arduino)
- Jumper wires: Male-to-Male, Male-to-Female 20cm
- MB-102 Breadboard (optional)
RS485 and Modbus RTU Fundamentals
The PR-3000-TR-NPK-N01 sensor communicates via RS485, a differential signaling standard that offers excellent noise immunity for industrial and field applications. The Modbus RTU protocol organizes data into hexadecimal frames with defined register addresses.
The communication works by sending a Query Frame to request data from specific register addresses, then the sensor responds with a Response Frame containing N, P, and K values stored in separate registers.
Wiring Connections
Arduino to MAX485 Module
| Arduino UNO | MAX485 Module | Function |
|---|---|---|
| 5V | VCC | Power |
| GND | GND | Ground |
| Pin 2 | RO | Receive Output |
| Pin 3 | DI | Driver Input |
| Pin 7 | DE | Driver Enable |
| Pin 8 | RE | Receiver Enable |
MAX485 to NPK Sensor
| MAX485 | Wire Color | NPK Sensor |
|---|---|---|
| B | Blue | B |
| A | Yellow | A |
NPK Sensor to 12V Power Supply
| 12V Power Supply | Wire Color | NPK Sensor |
|---|---|---|
| +12V | Brown | Positive wire |
| GND | Black | Negative wire |
Arduino to OLED Display
| Arduino UNO | OLED 128x64 |
|---|---|
| 5V | VCC |
| GND | GND |
| A4 (SDA) | SDA |
| A5 (SCL) | SCL |
Important: Connect all GND pins together (Arduino, MAX485, 12V supply) to establish a common signal reference.
Arduino Code for Reading NPK Values
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// RS485 pin definitions
#define RS485_RX_PIN 2
#define RS485_TX_PIN 3
#define RS485_DE_PIN 7
#define RS485_RE_PIN 8
// Modbus Register Addresses
#define MODBUS_ADDR 0x01 // Slave Address
#define REG_N 0x0000 // Nitrogen Register
#define REG_P 0x0001 // Phosphorus Register
#define REG_K 0x0002 // Potassium Register
// OLED settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
SoftwareSerial rs485(RS485_RX_PIN, RS485_TX_PIN);
void setup() {
Serial.begin(9600);
rs485.begin(9600);
// Configure DE and RE pins
pinMode(RS485_DE_PIN, OUTPUT);
pinMode(RS485_RE_PIN, OUTPUT);
digitalWrite(RS485_DE_PIN, LOW); // Disable driver temporarily
digitalWrite(RS485_RE_PIN, LOW); // Enable receiver
// Initialize OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("OLED allocation failed"));
while(1);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.display();
}
void loop() {
int nValue = readModbus(REG_N);
int pValue = readModbus(REG_P);
int kValue = readModbus(REG_K);
Serial.print("N: ");
Serial.print(nValue);
Serial.print(" mg/kg, P: ");
Serial.print(pValue);
Serial.print(" mg/kg, K: ");
Serial.print(kValue);
Serial.println(" mg/kg");
// Display on OLED
display.clearDisplay();
display.setCursor(0, 0);
display.print("NPK Sensor RS485");
display.setCursor(0, 16);
display.print("N: ");
display.print(nValue);
display.print(" mg/kg");
display.setCursor(0, 28);
display.print("P: ");
display.print(pValue);
display.print(" mg/kg");
display.setCursor(0, 40);
display.print("K: ");
display.print(kValue);
display.print(" mg/kg");
display.display();
delay(2000);
}
int readModbus(uint16_t regAddr) {
// Build Query Frame
uint8_t query[8];
query[0] = MODBUS_ADDR; // Slave Address
query[1] = 0x03; // Function Code (Read Holding Registers)
query[2] = highByte(regAddr); // Register Address High
query[3] = lowByte(regAddr); // Register Address Low
query[4] = 0x00; // Quantity High
query[5] = 0x01; // Quantity Low (read 1 register)
// Calculate CRC16
uint16_t crc = modbusCrc(query, 6);
query[6] = lowByte(crc);
query[7] = highByte(crc);
// Send Query
digitalWrite(RS485_DE_PIN, HIGH); // Enable driver
digitalWrite(RS485_RE_PIN, HIGH);
rs485.write(query, sizeof(query));
rs485.flush();
digitalWrite(RS485_DE_PIN, LOW); // Disable driver
digitalWrite(RS485_RE_PIN, LOW);
// Wait for Response
delay(100);
uint8_t response[9];
int bytesReceived = 0;
while (rs485.available() > 0) {
response[bytesReceived] = rs485.read();
bytesReceived++;
if (bytesReceived >= 9) break;
}
// Verify CRC and extract value
if (bytesReceived >= 7) {
uint16_t recvCrc = (response[5] << 8) | response[4];
uint16_t calcCrc = modbusCrc(response, 5);
if (recvCrc == calcCrc) {
return (response[3] << 8) | response[4];
}
}
return -1; // Return -1 on read error
}
uint16_t modbusCrc(uint8_t *data, uint8_t len) {
uint16_t crc = 0xFFFF;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
Reference Video
Demonstration video: Using NPK sensor with Arduino
Field Installation Guidelines
- Prepare the soil: Water the ground until moist but not waterlogged. Very dry or saturated soil will give inaccurate readings
- Insert the probe: Push the sensor vertically into the soil at 5-10 cm depth, ensuring the sensing area contacts the soil directly
- Wait for stabilization: Allow 30 seconds to 1 minute for readings to stabilize before recording values from Serial Monitor or OLED
- Avoid recent fertilization: Do not measure immediately after applying fertilizer or chemicals as values will be artificially elevated
Modbus Register Reference Table
| Function | Function Code | Register Address | Quantity |
|---|---|---|---|
| Read Holding Registers | 0x03 | 0x0000 - 0x0002 | As needed |
| Read Input Registers | 0x04 | See Data Sheet | As needed |
Troubleshooting Guide
No Response from Sensor:
- Verify B (blue) and A (yellow) wires are connected to correct terminals
- Confirm sensor receives 12V DC power
- Check that all GND connections are shared common
Reading Returns -1:
- Verify CRC16 calculation implementation
- Check Register Address against sensor Data Sheet
OLED Display Not Working:
- Verify SDA, SCL connections to A4, A5 pins
- Try changing I2C address from 0x3C to 0x3D
อยากทำโปรเจคแบบนี้?
รับทำโปรเจค Arduino / IoT จบงานไว ส่งงานครบ พร้อมสอน
If you need Arduino project service or urgent IoT development, see full service details on the home page
จ้างทำโปรเจคเลย