brainelectronics / micropython-modbus

MicroPython Modbus RTU Slave/Master and TCP Server/Slave library
GNU General Public License v3.0
112 stars 45 forks source link

Problem with RS485 direction control #68

Open ondrej1024 opened 1 year ago

ondrej1024 commented 1 year ago

Description

I am using a ST3485 RS485 transceiver with the DE/RE pins shortened and connected to a GPIO pin.

Running as Modbus master, the request is received correctly by the slave but the response is not received and umodbus throws an error (see below).

I suspect some timing issue with the transceiver switching to read mode but can't quite find the root cause of this.

Any ideas?

Reproduction steps

1. Initialization:

mbm0 = ModbusRTUMaster(pins=(Pin(GP_UART0_TX), Pin(GP_UART0_RX)),ctrl_pin=GP_UART0_DC, uart_id=0)

2. Send request to slave

mbm0.read_holding_registers(42,1,1)

MicroPython version

MicroPython v1.19.1-994-ga4672149b

MicroPython board

Raspberry Pico

MicroPython Modbus version

v2.3.4

Relevant log output

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/lib/umodbus/common.py", line 199, in read_holding_registers
  File "/lib/umodbus/serial.py", line 287, in _send_receive
  File "/lib/umodbus/serial.py", line 177, in _uart_read
  File "/lib/umodbus/serial.py", line 149, in _exit_read
IndexError: bytearray index out of range

User code

No response

Additional informations

No response

ondrej1024 commented 1 year ago

I did some more testing and found that when reading the response I always get first a single byte 0x00 and after that the actual response frame. The single byte causes the reported IndexError in _exit_read

So I did a simple modification throwing away always the first single byte of the response. This basically works. However quite often the response frame is not received completely, leading to CRC error.

So I was wondering if software direction control via IO pin has actually been tested and if it is reliable?

kommando828 commented 1 year ago

Have you fitted a 120 ohm resistor on the line connection on the slave. I run RS485 over wifi and all my slaves need a 120 ohm resistor as they all think they are the last one on the cable.

ondrej1024 commented 1 year ago

I have a regular RS485 bus with 120 Ohm termination. The bus itself works fine. When using HW direction control I don't get any errors.

beyonlo commented 1 year ago

@ondrej1024 Hello!

So I was wondering if software direction control via IO pin has actually been tested and if it is reliable?

I'm using this lib for RS-485 using the MAX485CSA chip, that use a control pin, like do you are using, and are working without errors mostly of time - here you can see one of my tests. I wrote mostly of time because sometimes happen Invalid CRC reading COILS WHEN first I write multiple COILS and after that I try to read the COILS again - here you can see this problem happening. Bellow a example of error:

>>> host.read_coils(slave_addr=10, starting_addr=125, coil_qty=16)
[True, True, True, True, True, False, False, False, False, True, True, True, False, False, False, True]
>>> host.write_multiple_coils(slave_addr=10, starting_address=125, output_values=[1,1,0])
True
>>> host.read_coils(slave_addr=10, starting_addr=125, coil_qty=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/lib/umodbus/common.py", line 136, in read_coils
  File "/lib/umodbus/serial.py", line 289, in _send_receive
  File "/lib/umodbus/serial.py", line 322, in _validate_resp_hdr
OSError: invalid response CRC

Look that first read_coils() works, but after write_multiple_coils() the read_coils() stop to works, or sometimes works and sometimes not works

I don't know if your problem is the same of that, or if your problem has something to do with this problem, but the @brainelectronics opened an issue for that #52

ondrej1024 commented 1 year ago

I use only reading/writing holding registers, no coils.

Anyway I found that I have some issue with the ground reference of my circuitry. With a proper reference I always get a valid response from the slave, so SW direction control is working fine.

However I still see an initial 0x00 byte in the RX buffer immediately after sending the request. So I have to take care of this, otherwise I get a CRC error. I don't have an explanation for this.

brainelectronics commented 1 year ago

Hey @ondrej1024 may you can give release 2.3.5 a try again? MicroPython v1.20.0 is recommended as it introduces the flush function for UART, implemented in #75, for older MicroPython firmware versions the timing has been improved as well. In case the reported issue was based on a HW problem you might close this issue

ondrej1024 commented 1 year ago

Yes, I will test this the next days and report back.

ondrej1024 commented 1 year ago

I have tested release 2.3.5 now with MP 1.20.0 but I still see the same issue. I get CRC error.

This is the code I use:

from machine import Pin
from umodbus.serial import Serial as ModbusRTUMaster

GP_UART0_TX =  0  # RS485 DI
GP_UART0_RX =  1  # RS485 RO
GP_UART0_DC =  2  # RS485 DC

mbm0 = ModbusRTUMaster(pins=(Pin(GP_UART0_TX), Pin(GP_UART0_RX)), ctrl_pin=GP_UART0_DC, uart_id=0)
mbm0.read_holding_registers(1,1,1)

And this is the error message:

Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
  File "/lib/umodbus/common.py", line 199, in read_holding_registers
  File "/lib/umodbus/serial.py", line 314, in _send_receive
  File "/lib/umodbus/serial.py", line 347, in _validate_resp_hdr
OSError: invalid response CRC

The above code works fine with release 2.3.4 and this patched version of serial.py: serial.txt

brainelectronics commented 1 year ago

Hi again @ondrej1024 may you can give release 2.3.7 another try again? I've added you proposed wait time after the flush. In case the reported issue was based on a HW problem you might close this issue

icchalmers commented 11 months ago

Just wanted to say this is likely a hardware issue and add a potential fix for anyone else that ends up here from searching.

I use the same SP3485. The datasheet says that if ~RE and DE are both pulled high (as they are when using a common GPIO to enable transmission) then the RO output should be High-Z. On my shoddy breadboard setup, RO was actually getting pulled low. This looks like a start bit on the UART RX, resulting in the reception of a 0x00 character.

Adding a pullup on RO is a hardware fix. If you need a software bodge fix, clear any bytes received before the peripheral should have had a chance to respond.

while self._uart.any() > 0:
    # print('ditching bad rx')
    self._uart.read()

For example, somewhere in the _send(...) method around here (or just right before/after if you don't care too much about affecting the timings):

https://github.com/brainelectronics/micropython-modbus/blob/e9ab05a2f1b37796446208a024368725953fc299/umodbus/serial.py#L278-L287

icchalmers commented 11 months ago

Comparison of without pullup resistor on RO vs with pullup, showing what looks like a failed RX to the UART as it is missing the stop bit. Top orange trace is RO (UART RX on the Pico I'm using).

Without: without_pullup

With: with_pullup

With the pullup in place, the expected packets are received and CRCs pass etc.

ondrej1024 commented 11 months ago

I agree, also my problem seems to be a hardware issue. There is no pullup on the RO line here and, although I haven't tried to solder one, I am pretty sure it would fix it. So I guess we can close this issue now.