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

Single Coil Write and Holding Register Write Initial Response Error #50

Open j-broome opened 1 year ago

j-broome commented 1 year ago

Using an RS-485 Transceiver with automatic flow control (MAX22028) and also tried with THVD-2410 (flow control pin). THVD-2410 works with the flow control code, but when I try to use a transceiver without the flow control pin, the single coil write and single register write responses are duplicated 3 times. Timeout shows to be 8020 microseconds. The issue is masked with the THVD-2410 and likely 95% of people attempting this because the transceiver keeps the responses from reaching the 485 bus. Below is my test code to recreate the issue.

from umodbus.serial import ModbusRTU from machine import Pin tx = machine.Pin(0) rx = machine.Pin(1)

rtu_pins = (tx, rx) # (TX, RX) slave_addr = 3 # address on bus as client baudrate = 9600 client = ModbusRTU( addr=slave_addr, # address on bus baudrate=baudrate, # optional, default 9600 pins=rtu_pins, # given as tuple (TX, RX)

data_bits=8, # optional, default 8

# stop_bits=1,          # optional, default 1
# parity=None,          # optional, default None
ctrl_pin=None,          # optional, control DE/RE
uart_id=0             # optional, see port specific documentation

)

When I do not use flow control I get 3 consecutive response messages on the Rx line with function code 6: image

Using function code 16 works though: image

THVD-2410 (w/flow control) setup - this works flawlessly - absolutely no errors image

MAX22028 (automatic flow control) setup - only fails on the initial response image After settings a holding register to say 0, the initial response coming out of the device is wrong, but it does somehow take that value, so when the register is read with FC3, it indeed has the correct value in it.

Wondering if this is happening because the RX buffer is not actually getting cleared somehow, but I can't find any discrepancies in the umodbus code.

Originally posted by @j-broome in https://github.com/brainelectronics/micropython-modbus/issues/7#issuecomment-1369274898

beyonlo commented 1 year ago

Hello @j-broome

I'm working with RS-485 using the the MAX485CSA chip that use of course the flow control (the ctrl_pin) and works fine. Unfortunately I don't have a RS-485 chip with automatic flow control to do some tests. But just for a test, could you please to try to change the speed from 9600 to 115200? Need to configure that new speed in both sides, Slave and Master.

I see that in the https://github.com/brainelectronics/micropython-modbus/blob/develop/umodbus/serial.py#L116 there is a different timeout calculation for the baudrate speed. So, maybe can be a good test to check.

j-broome commented 1 year ago

Wow! Using 115,200 baud works. Do you have an idea on how to fix this for 9600? The company I am at uses only 9600 for their field communication. image

beyonlo commented 1 year ago

Wow! Using 115,200 baud works.

@j-broome I'm glad that that works for you!

Do you have an idea on how to fix this for 9600? The company I am at uses only 9600 for their field communication.

Well, now we can to confirm that problem is probably a relation between baudrate and that https://github.com/brainelectronics/micropython-modbus/blob/develop/umodbus/serial.py#L116 timeout calculation. I'm sorry, I don't know how to fix that, but the @brainelectronics is the expert and maybe he can help you with that fix.

Anyway, if you want to try another test, change that code to:

        self._t35chars = 1750   # 1750us (approx. 1.75ms)
        #if baudrate <= 19200:
            # 4010us (approx. 4ms) @ 9600 baud
            #self._t35chars = (3500000 * (data_bits + stop_bits + 2)) // baudrate
        #else:
            #self._t35chars = 1750   # 1750us (approx. 1.75ms)

So, this test code, no matter what the baudrate you choose, the timeout will be always self._t35chars = 1750. Probably what I'm suggesting you is a bad idea, because if there is a different calculation for different baudrate is because is necessary, but you can just to try! :)

brainelectronics commented 1 year ago

Will check by tomorrow after fixing #35 for you @beyonlo.

@j-broome I also assume the change of the wait time here https://github.com/brainelectronics/micropython-modbus/blob/59e1c6c8bc41c9b893f0c17d54229c924b7c3817/umodbus/serial.py#L260 could solve your issue. It might ran into a timeout as it is waiting to long for a response and thereby requesting the data again, that's potentially why you see the request several times

j-broome commented 1 year ago

@brainelectronics I am actually seeing the response 3 times. The request only happens once.

j-broome commented 1 year ago

Any update on this? I am still seeing some errors.

brainelectronics commented 1 year ago

@j-broome not yet. I was busy with #55 this evening. I'll check & resolve it until the end of this week.

Did you play around with the mentioned sleep time in my previous comment?

j-broome commented 1 year ago

Yes, I attempted several things, but still seeing the issue on the higher baud rates as well.

brainelectronics commented 1 year ago

Hi again @j-broome I've now tested the setup on real hardware with two RP2. Once with 9600 baud, and then with 115200 baud using the rtu_host_example.py and rtu_client_example.py. Only modification was the usage of ctrl_pin, I've used Pin 2 on both devices.

I've monitored the ctrl_pin and the UART data pins on both devices, see the attached Saleae logs. RP2-RP2-log.zip

At 9600 baud the control pin is activated round about 1.15ms before the first byte is sent from host to client and deactivated round about 1.10ms after the last byte. Similar timings on client side. The client sends its answer after round about 9ms. RP2-RP2-9600

At 115200 baud the control pin is also activated round about 1.15ms before the first byte is sent from host to client and deactivated round about 0.65ms after the last byte. Similar timings on client side. The client sends its answer after round about 7ms. RP2-RP2-115200

The pre-send timing is the same for 115200 and 9600 baud, as it is always, independent of the used baudrate, waiting for 1000us + the MicroPython processing time to get to the UART write state (which is around 0.15ms), see https://github.com/brainelectronics/micropython-modbus/blob/f795516f0b64a94c15b092dccca30cb22dc71596/umodbus/serial.py#L252-L257 The post-send timing depends on the calculated frame time. This depends on the length of the message and the calculated time for a single byte based on the baudrate, see https://github.com/brainelectronics/micropython-modbus/blob/f795516f0b64a94c15b092dccca30cb22dc71596/umodbus/serial.py#L115 https://github.com/brainelectronics/micropython-modbus/blob/f795516f0b64a94c15b092dccca30cb22dc71596/umodbus/serial.py#L257-L263

My assumption is that your device is still in transmission mode (control pin active), while the other one is already sending its response. After the first successful exchange of data, it will notice this state and send the response a bit later. Nevertheless does this assumption not explain why it only happens on function code 6, but not on function code 16 ... It could explain why 115200 baud are working, but 9600 baud not. Like I said, at 115200 baud the control pin is only 0.65ms active after the transmission finished, whereas at 9600 baud, it is active for 1.15ms. Those 0.5ms could make the difference.

Could you maybe capture the control pin states and UART data with a logic analyser on your side as I did?

j-broome commented 1 year ago

I have no control pin as I am using the MAX22028 uart to 485 chip with auto flow control. I switched to FreeRTOS with libmodbus and now have no issues, so I do not believe it is the hardware.

brainelectronics commented 1 year ago

Hey @j-broome 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