ArduinoModbus Library using MAX845 module

Hi,
I'm doing some test with the ArduinoModbus Library and a MAX485 module. In my computer (macOS Big Sur) I installed minimalmodbus. My objective is to get some data from a DHT22 on Arduino Uno R3 (server) and send the data to my computer (client) using this USB-RS4845 adapter.

I wrote this code for the Arduino:

#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>
#include <DHT.h>

#define DHTPIN 13     // Connected to Pin Digital 13 on Arduino
#define DHTTYPE DHT22   // DHT22 temp & hum sensor  (AM2302)
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  
    // dht begin
    dht.begin();

  // start the Modbus RTU server, with (slave) id 42
  if (!ModbusRTUServer.begin(42, 9600)) {
  //   do something if there is an error
    while (1);
  }

  // configure holding registers at address 0x00
  ModbusRTUServer.configureHoldingRegisters(0x00, 2);
    
}

void loop() {

  delay(2000);   // Wait a few seconds between measurements.
  float h = dht.readHumidity();      // Read humidity in %
  float t = dht.readTemperature();   // Read temperature as Celsius
  
  if (isnan(h) || isnan(t)) {
    // Do something to manage the error
    return;
  }

  ModbusRTUServer.holdingRegisterWrite(0, h);
  ModbusRTUServer.holdingRegisterWrite(1, t);
  
  // poll for Modbus RTU requests
  ModbusRTUServer.poll();  
}

and this is the Python3 code running in my computer:

#!/usr/bin/env python3
import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('/dev/tty.usbserial-1420', 42)  # port name, slave address (in decimal)
instrument.serial.port
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout  = 0.25   # seconds
# print (instrument)
# n = instrument.read_bit(1,1)
# instrument.write_bit(0,1)  
# print(n)

## Read temperature (PV = ProcessValue) ##
temperature = instrument.read_register(0, 2)  # Registernumber, number of decimals
print(temperature)
humidity = instrument.read_register(1, 2)  # Registernumber, number of decimals
print(humidity)

## Change temperature setpoint (SP) ##
# NEW_TEMPERATURE = 95
# instrument.write_register(24, NEW_TEMPERATURE, 2)  # Registernumber, value, number of decimals for storagehumidity

I'm getting this CRC error:

Traceback (most recent call last): File "/Users/jjmuriel/Documents/python_work/RS485-MODBUS/read_register.py", line 18, in <module> temperature = instrument.read_register(0, 2) # Registernumber, number of decimals File "/usr/local/lib/python3.9/site-packages/minimalmodbus.py", line 441, in read_register return self._generic_command( File "/usr/local/lib/python3.9/site-packages/minimalmodbus.py", line 1170, in _generic_command payload_from_slave = self._perform_command(functioncode, payload_to_slave) File "/usr/local/lib/python3.9/site-packages/minimalmodbus.py", line 1243, in _perform_command payload_from_slave = _extract_payload( File "/usr/local/lib/python3.9/site-packages/minimalmodbus.py", line 1756, in _extract_payload raise InvalidResponseError(text) minimalmodbus.InvalidResponseError: Checksum error in rtu mode: 'þï' instead of 'ð&' . The response is: 'ïþïþïþï' (plain response: 'ïþïþïþï') [Finished in 0.1s with exit code 1] [cmd: ['python3', '-u', '/Users/jjmuriel/Documents/python_work/RS485-MODBUS/read_register.py']] [dir: /Users/jjmuriel/Documents/python_work/RS485-MODBUS] [path: /opt/local/bin:/opt/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Wireshark.app/Contents/MacOS]

This is the wire schema used with the MAX854:
arduino-rs485-esquema-duplex
(MAX485 A-A Computer)
(MAX485 B-B Computer)

I can't understand the addressing method that implements the ArduinoModbus library and why I'm getting this error. Please, any help will be welcome.

Don't use the ArduinoModbus library on AVR Arduinos, they don't have enough RAM for this rather ressource hungry import from the PC world (it's based on the libmodbus which is the most used Modbus library on PCs).

BTW, your code has some other possible problems.

  float h = dht.readHumidity();      // Read humidity in %
...
  ModbusRTUServer.holdingRegisterWrite(0, h);

Modbus holding registers are always 16bit integers. If you write a float to these registers the results is probably not what you expected.

temperature = instrument.read_register(0, 2)  # Registernumber, number of decimals

The number of decimals parameter does almost certainly not what you expect it to do. It converts the register value, in your case by dividing it by 100.

As your error is a CRC error (wrong checksum) there may be other problems involved, maybe hardware. How is your wiring? I miss a wiring diagram. A link to the exact hardware you're using may help too.

@pylon , thank you. What library do you recommend me for projects like this one, where I'll include several sensors (Arduino working like Modbus Server)?

MAX485 MODULE

This is the wiring diagram:

The Arduino cannot use RS485 from its serial port, you need to convert from ttl serial to RS485, but likewise as far as I can tell the MAC cannot use rs485 directly, you need to change from USB to RS485. From your wiring diagram you only appear to be converting from USB to RS485.
You might be able to use this: https://www.amazon.co.uk/HALJIA-MAX485-Module-Converter-Arduino/dp/B06Y2XHSMW/ref=sr_1_2?dchild=1&keywords=haljia+max485&qid=1619629294&sr=8-2(first one I found).

@countrypaul , I'm using the module that you point (MAX485) connected to the Arduino UNO and the computer is using this other module: USB to RS485 Converter Adapter ch340T chip.


I use this library for my projects but others may fit too.

Using pin 13 is not a perfect choice as this pins is the only one with a default circuit (LED) on the UNO. Although it might work I would change to another pin.

The rest of the circuit seems to be OK.

1 Like

Thank you @pylon . I'll test my schema with this library.

@pylon , I read the documentation included in this library, but I can not understand the structure of the register table for communication exchange:

// data array for modbus network sharing
uint16_t au16data[16] = {
  3, 1415, 9265, 4, 2, 7182, 28182, 8, 0, 0, 0, 0, 0, 0, 1, -1 };

int8_t Modbus::poll (uint16_t * regs, uint8_t u8size)

cyclic poll for slave

*** Only for Modbus Slave *** This method checks if there is any incoming query Afterwards, it would shoot a validation routine plus a register query Avoid any delay() function !!!! After a successful frame between the Master and the Slave, the time-out timer is reset.

Parameters:

*regs register table for communication exchange
u8size size of the register table

How can I include the DHT sensor readings (using inputRegisters) in this register table?

Only for answer my question for others, you can place your numerical data in the register in this way: uint16_t au16data[16] = { <#data1>, <#data2>, <#data3>, <#data4>, <#data5>, <#data6>, <#data7>, 8, 0, 0, 0, 0, 0, 0, 1, -1 };, but if you try to adjust the code example from this library, this Arduino schema does not work (timeout error):

// This code is not working
#include <DHT.h>
#include <ModbusRtu.h>

// data array for modbus network sharing (inputRegister)
uint16_t au16data[2];

DHT dht(11, DHT22);

/**
 *  Modbus object declaration
 *  u8id : node id = 0 for master, = 1..247 for slave
 *  port : serial port
 *  u8txenpin : 0 for RS-232 and USB-FTDI 
 *               or any pin number > 1 for RS-485
 */
Modbus slave(3,Serial,2); // this is slave @1 and RS-232 or USB-FTDI

void setup() {
  dht.begin();
  Serial.begin(9600);
  slave.start();  

}

void loop() {
   
   delay(2000);   // Wait a few seconds between measurements.
   
   float h = dht.readHumidity();
   float t = dht.readTemperature();   // Read temperature as Celsius
 
   if (isnan(h) || isnan(t)) {
     au16data[0] = 0;
     au16data[1] = 0;
     return;
   } 
   au16data[0] = (float)t;
   au16data[1] = (float)h;
     
   slave.poll(au16data, 2);
}

So I decided to use another library from simplemodbusng by Angelo Compagnucci. And with this same wiring diagram, I used the next code and now is fully working:

#include <SimpleModbusSlave.h>
#include <DHT.h>

#define DHTPIN 11     // Connected to Pin D11
#define DHTTYPE DHT22   // DHT 22  (AM2302)
DHT dht(DHTPIN, DHTTYPE);

//////////////// registers of your slave ///////////////////
enum  {
  // just add or remove registers and your good to go...
  // The first register starts at address 0
  TEMP,
  HUM,
  TOTAL_ERRORS,
  // leave this one
  TOTAL_REGS_SIZE 
  // total number of registers for function 3 and 16 share the same register array
};

unsigned int holdingRegs[TOTAL_REGS_SIZE]; // function 3 and 16 register array
////////////////////////////////////////////////////////////

#define RX            0     // Arduino defined pin (PB0, package pin #5)
#define TX            1     // Arduino defined pin (PB1, package pin #6)
#define RS485_EN      2     // pin to set transmission mode on RS485 chip (PB2, package pin #7)
#define BAUD_RATE     9600  // baud rate for serial communication
#define deviceID      3     // this device address


void setup() {

  // dht begin
  dht.begin();  

  /* parameters(long baudrate, 
                unsigned char ID, 
                unsigned char transmit enable pin, 
                unsigned int holding registers size,
                unsigned char low latency)
                
     The transmit enable pin is used in half duplex communication to activate a MAX485 or similar
     to deactivate this mode use any value < 2 because 0 & 1 is reserved for Rx & Tx.
     Low latency delays makes the implementation non-standard
     but practically it works with all major modbus master implementations.
  */
  
  modbus_configure(BAUD_RATE, deviceID, RS485_EN, TOTAL_REGS_SIZE, 0);

}

void loop() {

  delay(2000);   // Wait a few seconds between measurements.
  float h = dht.readHumidity();
  float t = dht.readTemperature();   // Read temperature as Celsius
  
  if (isnan(h) || isnan(t)) {
    holdingRegs[0] = 0;
    holdingRegs[1] = 0;
    return;
  }

  holdingRegs[0] = (float)t;
  holdingRegs[1] = (float)h;
  modbus_update(holdingRegs);
}

For test this schema I used a Windows tool named "Radzio! Modbus MAster Simulation" with this configuration:

And this is the Python code for communication with the Arduino Slave, that I used in macOS:

#!/usr/bin/env python3
import minimalmodbus
import serial

instrument = minimalmodbus.Instrument('/dev/tty.usbserial-1410', 3)  # port name, slave address (in decimal)
instrument.serial.port
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity   = serial.PARITY_NONE
instrument.serial.stopbits = 1
instrument.serial.timeout  = 3   # seconds
# print (instrument)
# n = instrument.read_bit(1,1)
# instrument.write_bit(0,1)  
# print(n)

## Read temperature (PV = ProcessValue) ##
temperature = instrument.read_register(0, 0)  # Registernumber, number of decimals
print(f"Temperatura: {temperature} ºC")
humidity = instrument.read_register(1, 0)  # Registernumber, number of decimals
print(f"Humedad: {humidity} %")

## Change temperature setpoint (SP) ##
# NEW_TEMPERATURE = 95
# instrument.write_register(24, NEW_TEMPERATURE, 2)  # Registernumber, value, number of decimals for storagehumidity

Temperatura: 26 ºC Humedad: 64 % [Finished in 3.6s]

Sure, because this line:

   delay(2000);   // Wait a few seconds between measurements.

is a bad idea. That means the slave code is checked only once every two seconds which is much too slow for the average master.

Try to avoid every delay() call in a Modbus slave!

Thanks for the advice @pylon. I'll test without this instruction.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.