CMB27 / ModbusRTUSlave

This is an Arduino library that implements the slave/server logic of the Modbus RTU protocol.
MIT License
74 stars 22 forks source link

Compatibility with MAX485 for Modbus Communication #50

Open karamdali opened 20 hours ago

karamdali commented 20 hours ago

I’m using this library with an ESP32 as the Modbus master and an STM32 Bluepill as the slave, connected via MAX485 transceivers. However, I consistently encounter a timeout error when using the MAX485 module.

Interestingly, if I connect the ESP32 directly to the STM32 using UART (bypassing the MAX485), everything works perfectly without any timeouts or issues. Setup

With MAX485 (Fails):

STM32 Serial-----MAX485------MAX485-------ESP32

Tx ------------->DI   |  A<----------> A  |  DI<-------Tx

Rx<------------- DO |   B<---------> B   |  DO------>Rx

PA3------------>RE  | GND<------->GND |  RE<-------- D23

PA3 ------------>DE                         DE<-------D23

When connected in this configuration with MAX485 modules, I get a timeout error.

I’ve confirmed that:

Direct UART (Works):

`STM32 Serial          ESP32

Tx ------------------->Rx

Rx<-------------------Tx

GND<----------------->GND`

This direct UART setup works perfectly without any issues.

I’ve also tested using an FTDI chip with the same outcome: UART works, but MAX485 times out.

Questions and Observations

Any insights into ensuring compatibility with MAX485 or recommended changes to the code would be appreciated. this is the stm32 code using this library (esp32(master) use esp-idf with its modbus library):

`#include <ModbusRTUSlave.h>

const byte ledPins[1] = {PC13};
const int dePin =PA3;
//HardwareSerial UART(PA10, PA9);
ModbusRTUSlave modbus(Serial, dePin); 

bool coils[1]={1};
uint16_t inputRegisters[1];

void setup() {
  pinMode(ledPins[0], OUTPUT);
  modbus.configureCoils(coils, 1);                   
  modbus.configureInputRegisters(inputRegisters, 1);     
  modbus.begin(1, 9600);
  inputRegisters[0]=200;
}

void loop() {
  modbus.poll();
  digitalWrite(ledPins[1], coils[0]);
}`
CMB27 commented 15 hours ago

Thank you for the clear explanation of the problem.

As for the code, it looks pretty good, the only issue I see is in loop(). I think you meant ledPins[0] instead of ledPins[1].

It would be helpful if you could get a logic analyzer on the pins so we can see exactly what the timings are. I don't have a STM32 board, so I can't do this myself at present.

karamdali commented 9 hours ago

Thank you for your reply. I can't get a logic analyzer anytime soon; it might take months to arrive.However, I've replaced the STM32 with an Arduino Nano 33 IoT, but the same issue remains: it works with direct UART, but I get a timeout error when using MAX485. Here’s the code I'm using with the Nano 33 IoT:

#include <ModbusRTUSlave.h>
const byte ledPins[1] = {13};
const int dePin =5;
ModbusRTUSlave modbus(Serial1, dePin); 

bool coils[1]={1};
uint16_t inputRegisters[1];

void setup() {
  pinMode(ledPins[0], OUTPUT);
  modbus.configureCoils(coils, 1);                   
  modbus.configureInputRegisters(inputRegisters, 1);     
  modbus.begin(1, 1200);
  inputRegisters[0]=200;
}

void loop() {
  modbus.poll();
  digitalWrite(ledPins[0], coils[0]);
}

P.S. What I have is a DIY oscilloscope, which I use to check the signals coming out from both sides. but I can't confirm if the timing is correct.

CMB27 commented 2 hours ago

I do have an Arduino Nano 33 IoT. I ran the following test.

Hardware Setup

Arduino Nano 33 IoTModbusRTU-Test-ShieldModbusRTU-Test-ShieldArduino Nano ESP32

Nano 33 IoT RS-485 XCVR RS-485 XCVR RS-485 XCVR RS-485 XCVR Nano ESP32
TX (1) DI A (D+) A (D+) RO RX (D0)
RX (0) RO B (D-) B (D-) DI TX (D1)
13 DE GND GND DE D13
13 RE RE D13

I also used Nano2UNO-Adapter-3V3s to connect the nano boards to the shields. But these shouldn't affect much.

Code on Nano 33 IoT

#include "ModbusRTUSlave.h" // version 2.0.6
const int8_t ledPin = 5;
const int8_t dePin = 13;
ModbusRTUSlave modbus(Serial1, dePin);

bool coil;
uint16_t inputRegister = 200;

void setup() {
  pinMode(ledPin, OUTPUT);
  modbus.configureCoils(&coil, 1);
  modbus.configureInputRegisters(&inputRegister, 1);
  modbus.begin(1, 1200);
}

void loop() {
  modbus.poll();
  digitalWrite(ledPin, coil);
}

This is nearly equivalent to what you posted. I'm just not a fan of single value arrays.

Code on Nano ESP32

#include <ModbusRTUMaster.h> // version 2.0.0
const int8_t buttonPin = D2;
const int8_t dePin = D13;
ModbusRTUMaster modbus(Serial0, dePin);

bool coil;
uint16_t inputRegister;
unsigned long transactionCounter = 0;
unsigned long errorCounter = 0;

const char* errorStrings[] = {
  "success",
  "invalid id",
  "invalid buffer",
  "invalid quantity",
  "response timeout",
  "frame error",
  "crc error",
  "unknown comm error",
  "unexpected id",
  "exception response",
  "unexpected function code",
  "unexpected response length",
  "unexpected byte count",
  "unexpected address",
  "unexpected value",
  "unexpected quantity"
};

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  Serial.begin(115200);
  Serial0.begin(1200);
  modbus.begin(1200);
  delay(1000);
}

void loop() {
  coil = !digitalRead(buttonPin);
  uint8_t error;
  error = modbus.writeSingleCoil(1, 0, coil);
  printLog(1, 5, 0, 1, error);
  error = modbus.readInputRegisters(1, 0, &inputRegister, 1);
  printLog(1, 4, 0, 1, error);
}

void printLog(uint8_t unitId, uint8_t functionCode, uint16_t startingAddress, uint16_t quantity, uint8_t error) {
  transactionCounter++;
  if (error) errorCounter++;
  char string[128];
  sprintf(string, "%ld %ld %02X %02X %04X %04X %s", transactionCounter, errorCounter, unitId, functionCode, startingAddress, quantity, errorStrings[error]);
  Serial.print(string);
  if (error == MODBUS_RTU_MASTER_EXCEPTION_RESPONSE) {
    sprintf(string, ": %02X", modbus.getExceptionResponse());
    Serial.print(string);
  }
  else if(functionCode == 4) {
    Serial.print(" inputRegister = ");
    Serial.print(inputRegister);
  }
  else if(functionCode == 5) {
    Serial.print(" coil = ");
    Serial.print(coil);
  }
  Serial.println();
}

I know this is a bit long. Most of this is for debugging.

Results

I didn't encounter any issues. Everything worked as expected.

I am starting to think the issue may be with the software on the ESP32. As you can see In my example code for the Nano ESP32, I used ModbusRTUMaster, which is different than what you are using.

I did a little research MAX485 boards. They seem to be pretty standard RS-485 transceivers. The one concern I have is that are 5V devices, and I know the microcontrollers you are using are 3.3V. I'm not sure this is the issue though. Most 5V devices will accept 3.3V signals on their inputs, and it looks like the MAX485 has a resistor on its RO output, which should protect your microcontrollers.