CMB27 / ModbusRTUSlave

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

Unexpected slave response if large request received #25

Closed gerrygralton closed 11 months ago

gerrygralton commented 11 months ago

If a master node sends a request for more than the number of available registers, this library seems to send a very unexpected response. It just returns a very large, never ending stream of data. Using the code below, with 4 declared input registers, if 5 or more registers are requested by the master the error occurs.

#include <ModbusRTUSlave.h>

#define de PB15
#define re PB14

HardwareSerial rs485_serial(PC7, PC6);
ModbusRTUSlave modbus(rs485_serial, de);

const uint16_t numInputRegisters = 4;
uint16_t inputRegisters [numInputRegisters] = {0};

void setup() {
  pinMode(re, OUTPUT);
  pinMode(de, OUTPUT);
  rs485_serial.begin(19200, SERIAL_8E1);

  modbus.begin(1, 19200, SERIAL_8E1);
  modbus.configureInputRegisters(inputRegisters, numInputRegisters);

  inputRegisters[0] = (uint16_t) 100;
  inputRegisters[1] = (uint16_t) 34;
}

void loop() {
  modbus.poll(); 
}
CMB27 commented 11 months ago

I am noticing a couple of things in your code.

gerrygralton commented 11 months ago

What processor are you using?

An STM32 F4xx chip. It shouldn't make any difference but I'd be interested if you don't manage to reproduce this on other hardware.

It appears you are configuring holding registers, not input registers.

Ah, whoops. The error appears when using either input or holding registers. That was just me fiddling to reproduce the error. I'll edit the code for consistency.

CMB27 commented 11 months ago

I am not seeing the issue when running the following code on an Arduino Uno.

#include <SoftwareSerial.h>
#include <ModbusRTUSlave.h>

const uint8_t rxPin = 10;
const uint8_t txPin = 11;
const uint8_t dePin = 13;

SoftwareSerial mySerial(rxPin, txPin);
ModbusRTUSlave modbus(mySerial, dePin);

const uint16_t numInputRegisters = 4;
uint16_t inputRegisters[numInputRegisters];

void setup() {
  modbus.configureInputRegisters(inputRegisters, numInputRegisters);
  modbus.begin(1, 38400, SERIAL_8N1);

  inputRegisters[0] = 0x002A;
  inputRegisters[1] = 0x0000;
  inputRegisters[2] = 0x0808;
  inputRegisters[3] = 0xFFFF;
}

void loop() {
  modbus.poll();
}

When requesting 4 input registers, this is the response I get: 01 04 08 00 2A 00 00 08 08 FF FF 2D DD. When requesting 5 input registers, this is the response I get: 01 84 02 C2 C1, which is the appropriate exception response.

gerrygralton commented 11 months ago

So if I use your code the response I get is image

Asking for 4 registers works as expected but 5 registers dumps a never ending stream of repeated bytes after printing the exception code. The first modbus tool I used, mbpoll, stopped listening after receiving the exception code but the bus was clearly full.

This is a screengrab from IoNinja. Blue is my request and green is the MCU's response.

CMB27 commented 11 months ago

I think I see what is going on here.

When I use RS-485, I normally connect the DE and RE pins of the driver together, so when it is transmitting it is not receiving. I see in the initial code you posted, you have an RE pin setup.

It looks like whenever a response is transmitted, the library also processes the transmitted message as though it were a request from the master device.

When you request 4 input registers, the library recognizes the function code, but it always expects 8 bytes with that function code, so it ignores the message.

However, when you request 5 input registers, the library sends and exception response: 01 84 02 C2 C1 (02 - ILLEGAL DATA ADDRESS). Then it sees the 01 in the receiving buffer and assumes the message there is addressed to it, and that 84 is the function code, so it responds with with the exception response code: 01 84 01 82 C0 (01 - ILLEGAL FUNCTION). And it continues to respond to its own exception responses with exception responses.

CMB27 commented 11 months ago

I've added this bit of code to the end of the _writeResponse function to quickly clear out the receiving buffer after transmitting.

while(_serial->available()) {
  _serial->read();
}

This should prevent the issue you've been having.

gerrygralton commented 11 months ago

Thanks Chris! This does look like it has fixed the issue. It would be great if you could release this to Arduino libraries.

gerrygralton commented 11 months ago

Would this issue be better served by adding an RE pin option to the library? I've noticed that the ArduinoRS485 library seems to have that functionality and it seems like it could be quite useful here too.