emelianov / modbus-esp8266

Most complete Modbus library for Arduino. A library that allows your Arduino board to communicate via Modbus protocol, acting as a master, slave or both. Supports network transport (Modbus TCP) and Serial line/RS-485 (Modbus RTU). Supports Modbus TCP Security for ESP8266/ESP32.
Other
513 stars 185 forks source link

Timeout on ModbusRTU with Esp32S2 #85

Closed DawidDiaco closed 3 years ago

DawidDiaco commented 3 years ago

Hello,

I was trying to use your library in order to use my Esp32S2 as a master device. I'm using ST3485EBD transceiver and I successfully managed to send commands to slave device, and I can see that it does react on my requests. Unfortunately, no matter what I was doing I'm unable to readback from the device, and I'm getting a timeout. I suspect that my issue might be related with the fact that i don't do anything with the RTS pin. For now my code looks like this:

// those are my Serial1 pins
#define GPIO_TXD 17
#define GPIO_RXD 18
#define GPIO_RTS 19

void setup() 
{
    Serial1.begin(19200, SERIAL_8E1);
    mb.begin(&Serial1);
    mb.setBaudrate(19200);
    mb.master();
}

void loop() 
{   
    if (!mb.slave()) 
    {
        mb.writeHreg(1, 0xD001, 0x3000, cbWrite);
    }
    mb.task();
    yield();
}
emelianov commented 3 years ago

Hello,

void setup() 
{
    Serial1.begin(19200, SERIAL_8E1);
    mb.begin(&Serial1, GPIO_RTS); // Specify direction control (RTS) pin
    //mb.setBaudrate(19200); // No need to call .setBaudrate() for ESP32/ESP8266
    mb.master();
}
DawidDiaco commented 3 years ago

Hello, sorry for the long response, but I was pretty bussy with other stuff.

Unfotunetly, the code that you have provided doesn't work in my case. I've eaven tried to steer the RTS pin manualny, but without any success. I've been measuring signals on the pins:

IMG_20201126_205108

Maybe there is a problem with my PCB? In theory, if I connect pin RE, and DE, the transciver should work automatically. Could you also look at my schematic?

msedge_SWIZLEZOkQ

I'm pretty new to the world of electronics, so I would be grateful for any advice that you have.

emelianov commented 3 years ago

How about to specify pins for Serial1 ?

Serial1.begin(19200, SERIAL_8E1, GPIO_RXD, GPIO_TXD);

From the library perspective i can suggest only to play with RTS direct/inverse control:

mb.begin(&Serial1, GPIO_RTS, false);

All other things might be hardware related. (Schematics looks ok in my opinion)

Just for reference:

bool begin(SoftwareSerial* port, int16_t txPin=-1, bool direct=true);
bool begin(HardwareSerial* port, int16_t txPin=-1, bool direct=true);

Assing Serial port. txPin controls transmit enable for MAX-485.

DawidDiaco commented 3 years ago

I've just tried what you suggested, and I think that I can see what is causing the issue. After I applied your code, I was able to read from the RTS pin, and it looked like this:

IMG_20201127_140816

As you can see, the RTS pin is changing to LOW state about 800 microseconds too quickly, and I'm unable to finish my transmission. I guess that transmission length can vary, so I would assume than simple delay of RTS pin is not an option. Do you think there is anything I can do about it from the software side?

emelianov commented 3 years ago

After some investigation i found that in some situations Modbus object has reported as free before previous transaction ended. readHreg, wrietHreg, readCoil and writeCoil methods were affected by the bug. Updated code have pushed to master branch.

DawidDiaco commented 3 years ago

I've tried out the latest code base, and unfortunately, that doesn't resolve my issue. I'm still getting the same result on the osciloscope. However I've tried to modify the rawSend, and I managed to get it working. Now it looks like this:

bool ModbusRTU::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) {
    uint16_t newCrc = crc16(slaveId, frame, len);
    if (_txPin >= 0) {
        digitalWrite(_txPin, _direct?HIGH:LOW);
        delay(1);
    }
    #ifdef ESP32
    portENTER_CRITICAL(&mux);
    #endif
    _port->write(slaveId);      //Send slaveId
    _port->write(frame, len);   // Send PDU
    _port->write(newCrc >> 8);  //Send CRC
    _port->write(newCrc & 0xFF);//Send CRC
    _port->flush();
    delayMicroseconds(830);
    if (_txPin >= 0)
        digitalWrite(_txPin, _direct?LOW:HIGH);
    #ifdef ESP32
    portEXIT_CRITICAL(&mux);
    #endif
    return true;
}

I've added delayMicroseconds(830);, but this solution doesn't really satisfies me. I'dont think that I will be able to make someting more elegant than this. Could I help you somehow gathering more data, that we can think about something more reasonable?

gonzabrusco commented 3 years ago

Just for the sake of trying.... try removing "portENTER_CRITICAL(&mux);" and "portEXIT_CRITICAL(&mux);" and check if you see any change.

@emelianov Why do you need a mutex if internally the Serial.write and Serial.flush use UART_MUTEX_LOCK ?

https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-uart.c#L377

emelianov commented 3 years ago

@DawidDiaco, It's strange. With my hardware with latest code signals are as expected. For mb.begin(&Serial1, GPIO_RTS); : image

emelianov commented 3 years ago

Why do you need a mutex if internally the Serial.write and Serial.flush use UART_MUTEX_LOCK ?

@gonzabrusco, Critical section is required to prevent code execution from interruption because of task switching. Delay between (or inside) _port->write() may be interpreted as end of frame by remote. Also we need to change direction control pin immediate after data have sent.

DawidDiaco commented 3 years ago

What version of ESP are you using for testing? ESP32S2 is fairly new, and arduino libs that I'm using are still in beta version, so they might be buggy. I'm also wondering if there is any important architectural difference between these two, that I'm missing out. I've only made sure that the oscilators are the same speed (8Mhz), but maybe there is something else that may differ - here is the datasheet for ESP32S2.

@gonzabrusco I've tried it. Doesn't change anything.

gonzabrusco commented 3 years ago

@DawidDiaco it seems Serial.flush() is not working in ESP32S2 ??

Could you test a simple code (without modbus) and test if Serial.flush is working? (write some data to serial port, then flush it and then switch a digital pin output). You could set a slow baudrate so it's more visible. It seems that in your case Serial.Flush is not blocking and still proceeds with the execution of the program.

emelianov commented 3 years ago

@DawidDiaco, Have no S2 in my lab. Just tested with generic ESP32 (and ESP8266 as well). I do remember that saw somewhere discussion on ESP32's .flush() returns earlier that buffer were completely sent. But unable to find tor real that it was the conclusion.

From other hand delay you've added is ok.

DawidDiaco commented 3 years ago

Looks like you guys are right. The Serial.flush() is not waiting for the end of transmission. I will later create an issue on the Esspressif github.

IMG_20201201_193140

gonzabrusco commented 3 years ago

You could try Serial.flush(false) and see if it makes a difference? (setting it to false discards RX data and sends out TX data).

https://github.com/espressif/arduino-esp32/blob/8fcc91485364b334c51447686bc168e9160d093f/cores/esp32/HardwareSerial.cpp#L162

DawidDiaco commented 3 years ago

Setting this parameter (not matter if i set it to true or false) doesn't affect anything. The transmission look exactly the same as in my previous post.

gonzabrusco commented 3 years ago

Ok it was just a random test. This clearly is an espressif problem.