USB to RS485 Module with MODBUS-RTU RS485 Soil Sensor on Raspberry Pi 4B

If I purchase 2 of the USB to RS485 Module from DFRobot (USB to RS485 Module / Adapter / Converter - DFRobot) and 2 types of soil sensors which require that USB converter (Industrial RS485 Soil nitrogen, phosphorus and potassium detect sensor for Scientific Experiment) + (IP68 Waterproof RS485 Soil Temperature, Moisture and pH Meter for Vegetable Cultivation), then can I use both on the Raspberry Pi 4B as it has 4 USB ports? And how do I set up the sensors with this USB converter for the Raspberry Pi 4B?

The USB shows up as a serial port. Suggest you use the Minimal Modbus Python Library on the Pi 4B to interface with the serial port. I have a similar Waveshare module and it works well with RS485 sensors (Anemometer & Wind direction).

In the end I changed to a RS485 Expansion HAT for Raspberry Pi - 2-Channel Isolated board because I wanted to two separate RS485 communication paths. The board also provided isolation and shows up as two serial ports on the Pi. My setup was on a Pi 3B+, which is running as a base unit for a weather station of the roof of my house.

As you have two sensors you can set this up on the same RS485 circuit. You don’t need two of the modules. RS485 can multidrop a number of devices, just like I2C, each has its own unique address.

Minimal Modbus documentation.
https://minimalmodbus.readthedocs.io/en/stable/

Getting all this to work can be a bit frustrating to begin with. Happy to help where I can.
Regards
Jim

1 Like

Is there a tutorial or step-by-step walkthrough of how I could set up my 2 soil sensors using this one RS485 expansion board, as I’m a beginner and don’t have much technical knowledge with this, even a schematic of how I could set it up would be much appreciated.

Would this be better for controlling two RS485 sensors (Dual-channel Raspberry Pi 4 RS485 Expansion Modbus HAT - DFRobot) as it’s a dual-channel expansion board?

1 Like

@Devansh256408 The DFRobot Dual channel is is similar to the Waveshare Dual Channel. It shows up as 2 serial ports on the Pi. Both modules would do the same thing. The Waveshare one benefits from having some isolation built into the board. Considering they are about the same price, the Waveshare one represents better value for money.

The USB to RS485 module will work for what you want to do. I have tested the Waveshare one, but NOT the DFRobot one. DFRobot is much cheaper and if it does the same job then all good.

The Waveshare one claims to be for industrial use and has protection built in, probably why it is more expensive. I would say the DFRobot one it similar to the MAX485 - TTL UART to RS485 Converter Module with a TTL to USB chip inside.

I will answer the other post you made after I have constructed a wiring diagram, you may find a tutorial on YouTube but it may not help. There can be a lot of misinformation on YouTube.

Regards
Jim

EDIT: I think I watched this video when beginning the Weather Station to try and understand Modbus. https://www.youtube.com/watch?v=FUfxETGIyUo

1 Like

Wiring to connect USB RS485 and Soil sensors.

To set the address you use a Minimal Modbus command to write to a register.
Note: you need to current address to setup the RS485 connection and it is easy to get it wrong.

When setting up the Anemometer & Wind Direction devices I managed to stuff it up. The modbus commands are a bit confusing, eventually I was able to figure it out and get it right. Was a bit of a panic for a while.

Once the addresses are set up the programming is also a bit tricky, happy to step you through it if you decide to go ahead.

Cheers
Jim

3 Likes

Thanks a lot. I will probably buy the two soil sensors and one USB to RS485 Module / Adapter / Converter - DFRobot and set it up as you have just shown me. However, I will probably need more help with the programming and technical bit.

But once again thanks for your responses.

2 Likes




I decided to buy the hardware, so the 2 soil sensors and the USB to RS485 module, and then I connected the sensors to the sun module as you showed in the diagram. How do I now set up my Pi 4B, what do I install, and what scripts do I run to get input from these sensors?

Thank You.

@Devansh256408 First, use only one sensor and get that working.
Both sensors have the same address, so one needs to be changed, this MUST be done with only one connected.
Suggest you get both working individually before changing the address of one.
It would be good to label them too so you know which is which.

I am assuming you know how to use a Raspberry Pi and load the OS on a SD card.
I will be using Thonny and Python as I know and have experience with them.

Pi 4B Bookworm OS
If you use the Bookworm OS you will need to setup a separate virtual environment.
Decide where you want to place the program and create a folder.
In my setup it is /home/pi/WeatherStation

Run Thonny, click Tools tab click Options, click the Interpreter tab.
In the bottom right you will see New Virtual Environment, click that and follow the instructions using the folder you created above.

Pi 4B Bullseye OS
You might find it better using the legacy OS Bullseye, no need to set up a virtual environment.

Installing Python packages using Thonny is easier than manually using pip or something else. I only use pip if the package is not available in the PyPi database.

Decided to set this up on a spare Pi 4B to be sure of the process and not rely on my memory.
Next post will have more details.

Cheers
Jim

1 Like

@Devansh256408 Continue from previous post.

My Setup
Pi 4B 64 bit Bullseye Python Virtual Environment.
/home/pi/MyEnv/Python
This is what the SD card in the Pi contained; previously it had been used it to explore virtual environments. The folder MyEnv was created and then Thonny used to set up a Virtual Environment. The folder Python was created after this as a place to store Python programs.

Find the Serial Port name.
Open a Terminal window, type sudo ls /dev, this will display all devices on the Pi
Plug the RS485 USB device into the Pi
type sudo ls /dev again, you should find a new serial device, ttyUSB0, it might have a different name on your Pi. The device name can be found from close examination of the two lists. Screen shot of my setup.

Run Thonny
click Tools click Manage Packages
type minimalmodbus in search bar
click on the displayed minimalmodbus link to instal it
Once installed the name minimalmodbus should appear in the installed package list on the left

Note: There is no standard for register allocation and how they are used across instruments. There is no standard for the RS485 addresses used. However the communications protocol is well defined. Minimal Modbus Documentation.

The documentation page for the RS485 Soil Sensor(N&P&K) Arduino WiKi- DFRobot lists the information needed to access the device.

  • Address code: It is the address of the sensor, which is unique in the communication network (factory default 0x01).
  • Function code: Function indication of the command sent by the host. This sensor only uses function code 0x03 (reading register data).
  • Data area: The data area is specific communication data. Note that the high byte of 16bits data is first!
07D0H 42001 (decimal) Device address Read and write 1-254 (factory default 1)
07D1H 42002 (decimal) Device baud rate Read and write 0 represents 2400 1 represents 4800 2 represents 9600

The documentation page lists the string of bytes needed to communicate with the device if not using minimalmodbus. We can use this to construct miminalmodbus commands.

Regards
Jim

1 Like

@Devansh256408 Continue from previous post.

Python code to access the Soil Sensors

#!/usr/bin/env python3
############################################################################
#  RS485 Setup Modbus Device
#   Use to read data and set Address
#
#   Created 01 July 2024
############################################################################
import minimalmodbus

############################################################################
# RS485 Setup ModBus
############################################################################
mb_address = 1
RS485 = minimalmodbus.Instrument('/dev/ttyUSB0',mb_address)
RS485.serial.baudrate = 9600                    # BaudRate
RS485.serial.bytesize = 8                       # Number of data bits to be requested
RS485.serial.parity = minimalmodbus.serial.PARITY_NONE # Parity NONE ODD or EVEN
RS485.serial.stopbits = 1                       # Number of stop bits
RS485.serial.timeout  = 2.0                     # Timeout time in seconds
RS485.mode = minimalmodbus.MODE_RTU             # Mode to be used (RTU or ascii mode)
RS485.clear_buffers_before_each_transaction = True
RS485.close_port_after_each_call = True
print('RS485 Channel 1 Modbus Config:') 
print(RS485) 

############################################################################
# Read Register Values
############################################################################
# command format: register address, additional registers, read function, decimal point
# get temporary Nitrogen value
data = RS485.read_register(0x1E, 0, 3, False)       
print ("Nitrogen = ",data)

# get temporary Phosphorus value
data = RS485.read_register(0x1F, 0, 3, False)       
print ("Phosphorus = ",data)

# get temporary Potassium  value
data = RS485.read_register(0x20, 0, 3, False)       
print ("Potassium = ",data)

# get address
address = RS485.read_register(0x07D0, 0, 3, False)
print ('Address = ',address)
# should return 1

# get Baud Rate
baud = RS485.read_register(0x07D1, 0, 3, False)
print ('Baud Rate = ',baud)
# should return 2,  0 =  2400, 1 = 4800, 2 = 9600

############################################################################
# Changing Address
############################################################################
# WARNING: Writing to the address register will change access to the device. Be absolutely sure you know what you are doing before you change the address. Personal experience: thought I had set address 2 but had actually set 20. Panic until I realised what I had done. 

#RS485.write_register(0x07D0, 2, 0) # set address to 2
#RS485.write_register(0x07D0, 4, 0) # set address to 4

Decide what address you want to use and edit above lines. Run the code once, then change mb_address to the one you set and comment out the write_register line or remove it completely.

Once you have both sensors working ok, individually, you can connect them together. The program code then needs to be changed to include both devices. Can show you how to do that when you get up to it.

Cheers
Jim

1 Like

So I now have managed to make both sensors give me output via python script individually, so how do I now make them both work at once?

Python script for the NPK soil sensor:

#!/usr/bin/env python3

# NPK soil sensor

import minimalmodbus
import struct
import crc16

mb_address = 1
RS485 = minimalmodbus.Instrument('/dev/ttyUSB0', mb_address)
RS485.serial.baudrate = 9600
RS485.serial.bytesize = 8
RS485.serial.parity = minimalmodbus.serial.PARITY_NONE
RS485.serial.stopbits = 1
RS485.serial.timeout = 2.0
RS485.mode = minimalmodbus.MODE_RTU
RS485.clear_buffers_before_each_transaction = True
RS485.close_port_after_each_call = True
print('RS485 Channel 1 Modbus Config:')
print(RS485)

def read_sensor_data():
    try:
        nitrogen_raw = RS485.read_register(0x1E, 0, 3, False)
        nitrogen_value = nitrogen_raw

        phosphorus_raw = RS485.read_register(0x1F, 0, 3, False)
        phosphorus_value = phosphorus_raw

        potassium_raw = RS485.read_register(0x20, 0, 3, False)
        potassium_value = potassium_raw

        address = RS485.read_register(0x07D0, 0, 3, False)
        print('Address = ', address)

        baud = RS485.read_register(0x07D1, 0, 3, False)
        print('Baud Rate = ', baud)

        print("Nitrogen = ", nitrogen_value, "mg/kg")
        print("Phosphorus = ", phosphorus_value, "mg/kg")
        print("Potassium = ", potassium_value, "mg/kg")

    except Exception as e:
        print("Error occurred:", e)

read_sensor_data()

Python script for the other soil sensor:

#!/usr/bin/env python3

# Temperature, Humidity, Electrical Conductivity and pH soil sensor

import minimalmodbus
import struct
import crc16

mb_address = 0x01
RS485 = minimalmodbus.Instrument('/dev/ttyUSB0', mb_address)
RS485.serial.baudrate = 9600
RS485.serial.bytesize = 8
RS485.serial.parity = minimalmodbus.serial.PARITY_NONE
RS485.serial.stopbits = 1
RS485.serial.timeout = 2.0
RS485.mode = minimalmodbus.MODE_RTU
RS485.clear_buffers_before_each_transaction = True
RS485.close_port_after_each_call = True
print('RS485 Channel 1 Modbus Config:')
print(RS485)

def read_sensor_data():
    try:
        moisture_raw = RS485.read_register(0x0000, 0, 3, False)
        moisture_value = moisture_raw / 10.0

        temperature_raw = RS485.read_register(0x0001, 0, 3, False)
        temperature_value = temperature_raw / 10.0

        ec_raw = RS485.read_register(0x0002, 0, 3, False)
        ec_value = ec_raw

        ph_raw = RS485.read_register(0x0003, 0, 3, False)
        ph_value = ph_raw / 10.0

        print("Moisture Content = {:.1f}%".format(moisture_value))
        print("Temperature = {:.1f} °C".format(temperature_value))
        print("Electrical Conductivity (EC) = {} ?S/cm".format(ec_value))
        print("pH = {:.1f}".format(ph_value))

    except Exception as e:
        print("Error occurred:", e)

read_sensor_data()

Ok, excellent; you can get values back from the sensors now.
A few comments.

  • The python script I posted was to generate confidence in the basic reading of registers in the sensors. You don’t need to read the address and baud rate every time.
  • If you change the False to True the returned value will be divided by 10 by minimalmodbus.
  • I didn’t notice both sensors are different, I can clearly that in the pic now and they are linked in the first post. Apologies. To get them to work together, the address of one needs to be changed.

Python Code to change address. Run once.
To change back, swap the address numbers.
Doesn’t have to be 1 or 2, any number from 1 to 247 is ok.

#!/usr/bin/env python3
# Change Address
import minimalmodbus

mb_address = 1
change_address = 2
RS485 = minimalmodbus.Instrument('/dev/ttyUSB0',mb_address)
RS485.serial.baudrate = 9600                    # BaudRate
RS485.serial.bytesize = 8                       # Number of data bits to be requested
RS485.serial.parity = minimalmodbus.serial.PARITY_NONE # Parity NONE ODD or EVEN
RS485.serial.stopbits = 1                       # Number of stop bits
RS485.serial.timeout  = 2.0                     # Timeout time in seconds
RS485.mode = minimalmodbus.MODE_RTU             # Mode to be used (RTU or ascii mode)
RS485.clear_buffers_before_each_transaction = True
RS485.close_port_after_each_call = True

# get current address
address = RS485.read_register(0x07D0, 0, 3, False)
print ('Current Address = ',address)

print('To change address type <YES> ??')
change = input()
if change == 'YES':
    RS485.write_register(0x07D0, change_address, 0)
    print('Address changed to',change_address)
else:
    print('Address NOT changed.')

Suggested code to access both sensors.

#!/usr/bin/env python3
# NPK soil sensor
# Temperature, Humidity, Electrical Conductivity and pH soil sensor

import minimalmodbus
import struct
import crc16

NPK_address = 1
NPK = minimalmodbus.Instrument('/dev/ttyUSB0', NPK_address)
NPK.serial.baudrate = 9600
NPK.serial.bytesize = 8
NPK.serial.parity = minimalmodbus.serial.PARITY_NONE
NPK.serial.stopbits = 1
NPK.serial.timeout = 2.0
NPK.mode = minimalmodbus.MODE_RTU
NPK.clear_buffers_before_each_transaction = True
NPK.close_port_after_each_call = True

TEMP_address = 2
TEMP = minimalmodbus.Instrument('/dev/ttyUSB0', TEMP_address)
TEMP.serial.baudrate = 9600
TEMP.serial.bytesize = 8
TEMP.serial.parity = minimalmodbus.serial.PARITY_NONE
TEMP.serial.stopbits = 1
TEMP.serial.timeout = 2.0
TEMP.mode = minimalmodbus.MODE_RTU
TEMP.clear_buffers_before_each_transaction = True
TEMP.close_port_after_each_call = True

def read_NPK_sensor_data():
    try:
        nitrogen_raw = NPK.read_register(0x1E, 0, 3, True)

        phosphorus_raw = NPK.read_register(0x1F, 0, 3, True)

        potassium_raw = NPK.read_register(0x20, 0, 3, True)

        print("Nitrogen = ", nitrogen_value, "mg/kg")
        print("Phosphorus = ", phosphorus_value, "mg/kg")
        print("Potassium = ", potassium_value, "mg/kg")

    except Exception as e:
        print("Error occurred:", e)

def read_TEMP_sensor_data():
    try:
        moisture_raw = RS485.read_register(0x0000, 0, 3, True)

        temperature_raw = RS485.read_register(0x0001, 0, 3, True)

        ec_raw = RS485.read_register(0x0002, 0, 3, False)
        ec_value = ec_raw

        ph_raw = RS485.read_register(0x0003, 0, 3, True)

        print("Moisture Content = {:.1f}%".format(moisture_value))
        print("Temperature = {:.1f} °C".format(temperature_value))
        print("Electrical Conductivity (EC) = {} ?S/cm".format(ec_value))
        print("pH = {:.1f}".format(ph_value))

    except Exception as e:
        print("Error occurred:", e)

read_NPK_sensor_data()
read_TEMP_sensor_data()

The tricky part is changing the address, when setting up the Wind Direction and Wind Speed sensors I managed to stuff it up. Set the address to 20 instead on 2. Caused me panic as I thought I had broken the sensor.

Anyway …
Regards
Jim

1 Like

Thank you a lot, Jim. I appreciate how much you helped me. I finished the whole setup with both sensors working simultaneously. Thanks.

2 Likes

Good to hear !!!

1 Like