Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'UART' object has no attribute 'wait_tx_done' #34

Closed
volkar1 opened this issue Dec 7, 2022 · 26 comments · Fixed by #42
Closed

'UART' object has no attribute 'wait_tx_done' #34

volkar1 opened this issue Dec 7, 2022 · 26 comments · Fixed by #42
Assignees
Labels
bug Something isn't working

Comments

@volkar1
Copy link

volkar1 commented Dec 7, 2022

I'm trying to use this library on Pi Pico W to communicate with ModbusRTU device. I'm using MAX485 for modbus bridge (required ctrl_pin).

When you try to read holding registers it fails with the following error:

  File "<stdin>", line 18, in <module>
  File "/lib/umodbus/serial.py", line 199, in read_coils
  File "/lib/umodbus/serial.py", line 171, in _send_receive
  File "/lib/umodbus/serial.py", line 163, in _send
AttributeError: 'UART' object has no attribute 'wait_tx_done'

ModbusRTU is trying to call "wait_tx_done" from machine UART lib, and that function is not included in machine library.

while not self._uart.wait_tx_done(2):

@beyonlo
Copy link

beyonlo commented Dec 8, 2022

Hello @volkar1

Could you to try to test the ModBus RTU directly UART to UART just to know if it works? So you can know if problem is really with the RS-485 ctrl_pin. The ModBus RTU should be works with RS485 ctrl_pin in the same way (with no delay) compared with UART, is that correct @brainelectronics? In this comparation you need of course to configure the same baudrate (like as 115200) on the UART and RS-485 tests.

@beyonlo
Copy link

beyonlo commented Dec 8, 2022

@volkar1 Sorry, I understood wrong. I think that is really a bug using RS-485 ctrl_pin.

@beyonlo
Copy link

beyonlo commented Dec 8, 2022

@volkar1 While @brainelectronics do not check this bug, please try if this works for you:

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + data_bits + stop_bits) / baudrate 
            #while not self._uart.wait_tx_done(2):
                 #machine.idle()
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)

Here has a discussion about the same bug. I got this code above from there, on second reply

Please, let us know if works.

@brainelectronics
Copy link
Owner

@volkar1 While @brainelectronics do not check this bug, please try if this works for you:

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + data_bits + stop_bits) / baudrate 
            #while not self._uart.wait_tx_done(2):
                 #machine.idle()
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)

Here has a discussion about the same bug. I got this code above from there, on second reply

Please, let us know if works.

@volkar1 as I don't have a non ESP32 MicroPython board at the moment, could you confirm this suggestion? I would provide a bugfix version the next day. Raspberry Pi Pico W boards will be delivered in a few days so I can also test and resolve #7

@volkar1
Copy link
Author

volkar1 commented Dec 11, 2022

@beyonlo and @brainelectronics thank you for your quick response.

I tested it yesterday and it does transmit, and receive data but it fails quite a lot.

It fails with OSError: invalid response CRC or OSError: no data received from slave

So in case I try to read 3 registers like that:

register_value1 = host.read_input_registers(
            slave_addr=33,
            starting_addr=107,
            register_qty=2,
            signed=False)
register_value2 = host.read_input_registers(
            slave_addr=33,
            starting_addr=109,
            register_qty=2,
            signed=False)
register_value3 = host.read_input_registers(
            slave_addr=33,
            starting_addr=111,
            register_qty=2,
            signed=False)

It fails on the third reading 60% of the times...

since I'm trying to read register 107 through 112 I also tried this:

register_value = host.read_input_registers(
            slave_addr=33,
            starting_addr=107,
            register_qty=6,
            signed=False)

and it fails on about 40% of the readings.

Im wondering if this might be caused by pre-delay or post-delay between switching ctrl_pin.

@beyonlo
Copy link

beyonlo commented Dec 11, 2022

@volkar1 While @brainelectronics do not check this bug, please try if this works for you:

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + data_bits + stop_bits) / baudrate 
            #while not self._uart.wait_tx_done(2):
                 #machine.idle()
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)

Here has a discussion about the same bug. I got this code above from there, on second reply
Please, let us know if works.

@volkar1 as I don't have a non ESP32 MicroPython board at the moment, could you confirm this suggestion? I would provide a bugfix version the next day. Raspberry Pi Pico W boards will be delivered in a few days so I can also test and resolve #7

@brainelectronics Just to be sure: did you tested the ModBus RTU over RS-485 with the ESP32 using that code above and works fine? Because if works on the ESP32, as the MAX485 chip is just a transceiver connected to the UART, should works also on any microcontroller that runs MicroPython, like as RPICO/RPICO-W, right?

I will need to run the ModBus RTU over RS-485 as well, but my 485 board still not came, so I'm testing everything using just UART, supposing that when I add the max485 transceiver to the UART, and enable the ctrl_pin, everything will works like as tested in the UART :)

Thank you in advance!

@brainelectronics
Copy link
Owner

@volkar1 you assumption is correct. Every time you call a holding register read function or any other function that is sending something the control pin is used. If you instead read/write multiple registers with one command as you tested as well, the pin will be used twice. This is the reason why you get a lower failure rate.

So, Yes it is connected to the pre or post delay as you supposed. I'll check and adjust the value as soon as my Pico arrived.

@brainelectronics
Copy link
Owner

@beyonlo yes I use the ESP32 UART but without a control pin for the RS485. I use a board with auto transmission control. Of course I agree with you. The code has to work the same on every board! I assume by end of the week the boards should be here and the fix available

@beyonlo
Copy link

beyonlo commented Dec 12, 2022

@brainelectronics great. My two kits with RS-485 will be arriving in this week 🥳 The kits use the MAX485CSA - that is from the MAXIM brand.

@volkar1 are you using the same chip right?

@volkar1
Copy link
Author

volkar1 commented Dec 12, 2022

@beyonlo I'm using the classic MAX485 module (MAX485CSA) with a chip and a bunch of resistors. The device I'm reading its running at baudrate115200 with 8 data bits, 2 stop bits and no parity (8N2). So I think the setup will be about the same 😊

@brainelectronics
Copy link
Owner

brainelectronics commented Dec 13, 2022

According to the Modbus Protocol the frame would be always 11 bits due to these two options

  • 1 start + 8 data + 1 parity + 1 stop
  • 1 start + 8 data + 2 stops

The minimum time between two frames is defined as 3.5 characters time, defended in this lib by self._t35chars, see

self._t35chars = (3500000 * (data_bits + stop_bits + 2)) // baudrate

This is valid for single register readings. And of course static. As more than one register can be read, which still would only contain 8 data bits, setting more than one register will (if it is not just a set of coils) have more than 8 data bits and thereby (more or less slightly) violate the timing.

Additionally there is no wait time between setting the control pin high and sending the data see

if self._ctrlPin:
self._ctrlPin(1)
self._uart.write(serial_pdu)

The first few bits of a frame could be lost, the CRC is invalid and the client could reject the data.

Above 19200 baud, the time between two frames gets less important/critical, that's why it is usually fixed to 1750us. Nevertheless the time between setting/resetting the pin and sending data becomes more important as more bits "can be lost" within a shorter period of time. In case of @volkar1 it might be a combination of 3 things: no pre send wait time, longer message length, wrong post send wait time.
I assume adding a 1ms delay after setting the pin high could reduce the error rate already.

@beyonlo
Copy link

beyonlo commented Dec 16, 2022

My max485 boards has arrived, I will to test in this weekend! :)

@volkar1 any news to reduce error?

I assume adding a 1ms delay after setting the pin high could reduce the error rate already.

@brainelectronics I will to do that to check if error stop, or at least, reduce! But in my perception using RS485 should lost nothing, like as using ModBus RTU on UART and ModBus TCP right?

@brainelectronics
Copy link
Owner

@brainelectronics I will to do that to check if error stop, or at least, reduce! But in my perception using RS485 should lost nothing, like as using ModBus RTU on UART and ModBus TCP right?

Yes, you're right, there shall be no errors in the RTU Implementation as well. The protocol tries to prevent issues, but it can of course not solve issues caused by wrong timing. I'm looking forward to your findings @beyonlo

@beyonlo
Copy link

beyonlo commented Dec 19, 2022

Hi @brainelectronics I added my two kits/boads: MAX485 module (that are exactly the same of used by @volkar1), but unfortunately do not works. I changed the serial.py conforming the code above.

       if self._ctrlPin:
            self._ctrlPin(1)
            time.sleep_us(1000)

        self._uart.write(serial_pdu)

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + self._data_bits + self._stop_bits) / self._baudrate
            #while not self._uart.wait_tx_done(2):
                 #machine.idle()
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)
  1. There is a error running Slave RTU on the new RC version 2.1.0-rc15.dev39 and in the stable version as well:
$ mpremote run rtu_client_example.py
Traceback (most recent call last):
  File "<stdin>", line 109, in <module>
AttributeError: 'ModbusRTU' object has no attribute 'process'

So, I tried many others versions that I have downloaded in the past that works using RTU over UART, and find one: the micropython-modbus-1.1.0

Slave RTU:

$ mpremote run rtu_client_example.py 
Running ModBus version: 0.0.0

Ps: very strange show 0.0.0 as version - maybe this old version has no this feature to show the correct version.

Master RTU:

>>> from umodbus.serial import Serial as ModbusRTUMaster
>>> rtu_pins = (17, 18)
>>> host = ModbusRTUMaster(baudrate=9600, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
>>> host.read_coils(slave_addr=10, starting_addr=123, coil_qty=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "umodbus/serial.py", line 204, in read_coils
  File "umodbus/serial.py", line 178, in _send_receive
  File "umodbus/serial.py", line 182, in _validate_resp_hdr
OSError: no data received from slave
>>> 

Ps: Before that I added the RS485 board, I tested using just UART and was reading OK.

I don't know if the problem is about the ModBus version, or maybe the cables/wires that I'm using to connect the RS485 boards to ESP32-S3 has any problem. In this week I will buy a multi-meter to check if my all wires/cables are OK (check signal).

I'm using two ESP32 connected via that RS485 boards, where ESP32-01 is running ModBus RTU Slave and ESP32-02 is running ModBus RTU Master

Just to be sure, I'm using 5V in that VCC, is that correct right?

image

@volkar1
Copy link
Author

volkar1 commented Dec 19, 2022

@beyonlo VCC can be either 3.3V or 5V, I think I was using 3.3V and it works just fine. I also set jumper on De, Re pins and then connect it to the pin on microcontroller.

@beyonlo
Copy link

beyonlo commented Dec 19, 2022

@volkar1 and @brainelectronics Works!!! I used a multimeter to find cable/wire problem and works!

I'm using the ModBus 1.1.0 version because others versions I have this error: AttributeError: 'ModbusRTU' object has no attribute 'process'

My test using RS485 was reading 8 COILs each 100ms and I have no errors in a 1 thousand reads:

Master:

>>> from umodbus.serial import Serial as ModbusRTUMaster
>>> rtu_pins = (17, 18)
>>> host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
>>> host.read_coils(slave_addr=10, starting_addr=123, coil_qty=8)
[True, False, False, False, False, False, False, False]

Ps: As this version (1.1.0) is no possible to config in the register_definitions a list of many holding registers, I could not to do a more extensive test reading a high quantity of registers. I just read that 8 COILs.

register_definitions = {
    "COILS": {
        "EXAMPLE_COIL": {
            "register": 123,
            "len": 1,
            "val": 1
        }
    },
...

Slave:

part of rtu_client_example.py:

# ===============================================
# RTU Slave setup
# act as client, provide Modbus data via RTU to a host device
# ModbusRTU can get serial requests from a host device to provide/set data
rtu_pins = (17, 18)         # (TX, RX)
slave_addr = 10             # address on bus as client
baudrate = 115200
client = ModbusRTU(
    addr=slave_addr,        # address on bus
    baudrate=baudrate,      # optional, default 9600
    # data_bits=8,            # optional, default 8
    # stop_bits=1,            # optional, default 1
    # parity=None,            # optional, default None
    pins=rtu_pins,
    ctrl_pin=15)

$ mpremote run rtu_client_example.py 
Running ModBus version: 0.0.0

The changed code was this:

       if self._ctrlPin:
            self._ctrlPin(1)
            time.sleep_us(1000)

        self._uart.write(serial_pdu)

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + self._data_bits + self._stop_bits) / self._baudrate
            #while not self._uart.wait_tx_done(2):
                 #machine.idle()
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)

I did the test from MODSCAN (a windows Master App) as well, reading 8 COILs that same register 123, and works without errors reading each 100ms for two thousands reads

@brainelectronics
Copy link
Owner

@beyonlo could you please re-test with 2.1.1-rc17.dev41? See #41

@brainelectronics
Copy link
Owner

The latest MicroPython version (anything released after 1.19.1) will support UART.flush() which will wait until all data has been sent. But to stay backwards compatible, this library will stick to the "manually" calculated timeout as discussed above

@beyonlo
Copy link

beyonlo commented Dec 28, 2022

@beyonlo could you please re-test with 2.1.1-rc17.dev41? See #41

Thank you very much @brainelectronics for this release, I did the tests over RS485. Follow the great results, with some observations/notes:

register_definitions = {
    "COILS": {
        "EXAMPLE_COIL": {
            "register": 123,
            "len": 1,
            "val": 1
        },
        "COIL_SIGNALS": {
            "register": 125,
            "len": 16,
            "val": [1,1,1,1,1,0,0,0,0,1,1,1,0,0,0,1]
        }
    },
    "HREGS": {
        "EXAMPLE_HREG": {
            "register": 93,
            "len": 1,
            "val": 19
        },
        "HREG_VALUES": {
            "register": 130,
            "len": 33,
            "val": [34, 12, 14, 0, 10, 4, 2, 1345, 34, 8, 1100, 350, 456, 754, 324, 423, 530, 90, 320, 34, 244, 355, 606, 656, 640, 620, 677, 623, 234, 567, 34, 56, 68]
        }
    },
    "ISTS": {
        "EXAMPLE_ISTS": {
            "register": 67,
            "len": 1,
            "val": 0
        }
    },
    "IREGS": {
        "EXAMPLE_IREG": {
            "register": 10,
            "len": 2,
            "val": 60001
        }
    }
}

ModBus RTU Slave:

$ mpremote run rtu_client_example.py 
Running ModBus version: 2.1.1-rc17.dev41

ModBus RTU Master - test with COILS - reading 16 coils - no errors:

$ cat read_coils.py 
import time
from umodbus import version
print('Running ModBus version: {}'.format(version.__version__))

from umodbus.serial import Serial as ModbusRTUMaster
rtu_pins = (17, 18)

address = 125
qty = 16
host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
print('COIL request test.')
print('Reading qty={} from address {}:'.format(qty, address))
values = host.read_coils(slave_addr=10, starting_addr=address, coil_qty=qty)
print('Result: {}'.format(values))

success = True
counter_requests = 100
fred_time = 30
print('Testing {} requests, each {}ms. Wait...'.format(counter_requests, fred_time))
start_time = time.ticks_ms()
for i in range(counter_requests):
    res = host.read_coils(slave_addr=10, starting_addr=address, coil_qty=qty)
    time.sleep_ms(fred_time)
    if res != values:
        print('Error found')
        success = False
        break
end_time = time.ticks_ms()
delta_time = time.ticks_diff(end_time, start_time)
if success:
    print('Done ModBus RTU (via RS485) requests without error in {}ms.'.format(delta_time))
print('Just a observation: the total time was {}ms. So {} / {} requests is {}ms for each request, so was not capable to do each request in {}ms as configured in this test. Is that normal?'.format(delta_time, delta_time, counter_requests, delta_time/counter_requests, fred_time))
$ mpremote run read_coils.py 
Running ModBus version: 2.1.1-rc17.dev41
COIL request test.
Reading qty=16 from address 125:
Result: [True, True, True, True, True, False, False, False, False, True, True, True, False, False, False, True]
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 8479ms.
Just a observation: the total time was 8479ms. So 8479 / 100 requests is 84.79ms for each request, so was not capable to do each request in 30ms as configured in this test. Is that normal?

ModBus RTU Master - test with HOLDING REGISTERS - reading 33 registers - no errors:

$ cat read_holding_registers.py 
import time
from umodbus import version
print('Running ModBus version: {}'.format(version.__version__))

from umodbus.serial import Serial as ModbusRTUMaster
rtu_pins = (17, 18)

address = 130
qty = 33
host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
print('HOLDING REGISTER request test.')
print('Reading qty={} from address {}:'.format(qty, address))
values = host.read_holding_registers(slave_addr=10, starting_addr=address, register_qty=qty, signed=False)
print('Result: {}'.format(values))

success = True
counter_requests = 100
fred_time = 30
print('Testing {} requests, each {}ms. Wait...'.format(counter_requests, fred_time))
start_time = time.ticks_ms()
for i in range(counter_requests):
    res = host.read_holding_registers(slave_addr=10, starting_addr=address, register_qty=qty, signed=False)
    time.sleep_ms(fred_time)
    if res != values:
        print('Error found')
        success = False
        break
end_time = time.ticks_ms()
delta_time = time.ticks_diff(end_time, start_time)
if success:
    print('Done ModBus RTU (via RS485) requests without error in {}ms.'.format(delta_time))
print('Just a observation: the total time was {}ms. So {} / {} requests is {}ms for each request, so was not capable to do each request in {}ms as configured in this test. Is that normal?'.format(delta_time, delta_time, counter_requests, delta_time/counter_requests, fred_time))
$ mpremote run read_holding_registers.py 
Running ModBus version: 2.1.1-rc17.dev41
HOLDING REGISTER request test.
Reading qty=33 from address 130:
Result: (34, 12, 14, 0, 10, 4, 2, 1345, 34, 8, 1100, 350, 456, 754, 324, 423, 530, 90, 320, 34, 244, 355, 606, 656, 640, 620, 677, 623, 234, 567, 34, 56, 68)
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 8375ms.
Just a observation: the total time was 8375ms. So 8375 / 100 requests is 83.75ms for each request, so was not capable to do each request in 30ms as configured in this test. Is that normal?

Note: I think that you already know, but I would like to notice you just to be sure about that. The version 2.1.1-rc17.dev41 has not changed code with that code above, so the serial.py still use while not self._uart.wait_tx_done(2). I just edit the serial.py and add that code:

In the def __init__(...) I added:

        self._data_bits = data_bits
        self._stop_bits = stop_bits
        self._baudrate = baudrate

and in the def _send(...) I added:

        if self._ctrlPin:
            self._ctrlPin(1)
            time.sleep_us(1000)

        self._uart.write(serial_pdu)

        if self._ctrlPin:
            self.char_time_us = 1000000 * (2 + self._data_bits + self._stop_bits) / self._baudrate
            #while not self._uart.wait_tx_done(2):
            #    machine.idle()
            #time.sleep_us(self._t35chars)
            time.sleep_us(0 + int(self.char_time_us * len(serial_pdu)))
            self._ctrlPin(0)

Ps: I did not tests doing requests with functions INPUT STATUS (ISTS) and INPUT REGISTERS (IREG), but I think that should be works like as COILS and HOLDING REGISTERS, right? I will to test that as well soon!

@brainelectronics
Copy link
Owner

@beyonlo could you please re-test one more time with 2.1.2-rc18.dev42?

The change contains also a little speed improvement, I hope it works as expected.

@brainelectronics
Copy link
Owner

Just a observation: the total time was 8479ms. So 8479 / 100 requests is 84.79ms for each request, so was not capable to do each request in 30ms as configured in this test. Is that normal?

@beyonlo As you added a constant delay of 30ms after each read function, the actual time of one read operation is 8479ms - 30ms * 100 = 5479ms / 100 so 54.79ms. If you want to perform it every 30ms, which is currently not possible as 54ms > 30ms, you can use this code

# don't forget to import :) 
import machine

# other code 

for i in range(counter_requests):
    this_start_time = time.ticks_ms()
    res = host.read_holding_registers(slave_addr=10, starting_addr=address, register_qty=qty, signed=False)
    while time.ticks_ms() <= this_start_time + fred_time:
        # time.sleep_ms(1)    # works also of course
        machine.idle()
    if res != values:
        print('Error found')
        success = False
        break
end_time = time.ticks_ms()

With 2.1.2-rc18.dev42 at

time.sleep(self._t35chars)
it should drop by ~10ms per read at 9600 baud and by ~30ms per read at baudrates higher than 19200, just a rough estimation. I expect something around 40ms per read in your tests.

@brainelectronics brainelectronics linked a pull request Dec 28, 2022 that will close this issue
@beyonlo
Copy link

beyonlo commented Dec 28, 2022

@beyonlo could you please re-test one more time with 2.1.2-rc18.dev42?

@brainelectronics this version do not works, Master just congeals when try to read. So I did just a CTRL+C to stop the Master:

Slave:

$ mpremote run rtu_client_example.py 
Running ModBus version: 2.1.2-rc18.dev42

Master:

>>> from umodbus import version
>>> print('Running ModBus version: {}'.format(version.__version__))
Running ModBus version: 2.1.2-rc18.dev42
>>> from umodbus.serial import Serial as ModbusRTUMaster
>>> rtu_pins = (17, 18)
>>> host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
>>> host.read_coils(slave_addr=10, starting_addr=123, coil_qty=1)
Traceback (most recent call last):  <-- Here I did the `CTRL+C`
  File "<stdin>", line 1, in <module>
  File "/lib/umodbus/serial.py", line 353, in read_coils
  File "/lib/umodbus/serial.py", line 284, in _send_receive
  File "/lib/umodbus/serial.py", line 178, in _uart_read
KeyboardInterrupt: 
>>> 
>>> 

Ps: used the same register_definitions like above

@beyonlo
Copy link

beyonlo commented Dec 28, 2022

@brainelectronics I found the problem:

On the:

time.sleep(self._t35chars)
you changed to use self._t35chars, that is microseconds variable, but you do not changed to be time.sleep_us (sleep in microseconds). I just changed to time.sleep_us(self._t35chars) and works fine.

Thank you very much!

@beyonlo
Copy link

beyonlo commented Dec 28, 2022

@brainelectronics Follow the new tests results with that modification that I did above in the serial.py:

Slave:

$ mpremote run rtu_client_example.py 
Running ModBus version: 2.1.2-rc18.dev42

Master - COIL tests:

$ cat read_coils.py 
import time
from umodbus import version
print('Running ModBus version: {}'.format(version.__version__))

from umodbus.serial import Serial as ModbusRTUMaster
rtu_pins = (17, 18)

address = 125
qty = 16
host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
print('COIL request test.')
print('Reading qty={} from address {}:'.format(qty, address))
values = host.read_coils(slave_addr=10, starting_addr=address, coil_qty=qty)
print('Result: {}'.format(values))

success = True
counter_requests = 100
fred_time = 30
print('Testing {} requests, each {}ms. Wait...'.format(counter_requests, fred_time))
start_time = time.ticks_ms()
for i in range(counter_requests):
    res = host.read_coils(slave_addr=10, starting_addr=address, coil_qty=qty)
    time.sleep_ms(fred_time)
    if res != values:
        print('Error found')
        success = False
        break
end_time = time.ticks_ms()
delta_time = time.ticks_diff(end_time, start_time)
delta_time_div_counter_requests = delta_time/counter_requests
modbus_time_operation = delta_time_div_counter_requests - fred_time
if success:
    print('Done ModBus RTU (via RS485) requests without error in {}ms.'.format(delta_time))
    print('The total time was {}ms. So {} / {} requests is {}ms. So, {}ms - {}ms (freq_time delay) = {}ms. So the total time operation used by Modbus Protocol is {}ms'.format(delta_time, delta_time, counter_requests, delta_time_div_counter_requests, delta_time_div_counter_requests, fred_time, modbus_time_operation, modbus_time_operation))
$ mpremote run read_coils.py 
Running ModBus version: 2.1.2-rc18.dev42
COIL request test.
Reading qty=16 from address 125:
Result: [True, True, True, True, True, False, False, False, False, True, True, True, False, False, False, True]
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 4174ms.
The total time was 4174ms. So 4174 / 100 requests is 41.74ms. So, 41.74ms - 30ms (freq_time delay) = 11.74ms. So the total time operation used by Modbus Protocol is 11.74ms

Master - HOLDING REGISTER tests:

$ cat read_holding_registers.py 
import time
from umodbus import version
print('Running ModBus version: {}'.format(version.__version__))

from umodbus.serial import Serial as ModbusRTUMaster
rtu_pins = (17, 18)

address = 130
qty = 33
host = ModbusRTUMaster(baudrate=115200, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=15)
print('HOLDING REGISTER request test.')
print('Reading qty={} from address {}:'.format(qty, address))
values = host.read_holding_registers(slave_addr=10, starting_addr=address, register_qty=qty, signed=False)
print('Result: {}'.format(values))

success = True
counter_requests = 100
fred_time = 30
print('Testing {} requests, each {}ms. Wait...'.format(counter_requests, fred_time))
start_time = time.ticks_ms()
for i in range(counter_requests):
    res = host.read_holding_registers(slave_addr=10, starting_addr=address, register_qty=qty, signed=False)
    time.sleep_ms(fred_time)
    if res != values:
        print('Error found')
        success = False
        break
end_time = time.ticks_ms()
delta_time = time.ticks_diff(end_time, start_time)
delta_time_div_counter_requests = delta_time/counter_requests
modbus_time_operation = delta_time_div_counter_requests - fred_time
if success:
    print('Done ModBus RTU (via RS485) requests without error in {}ms.'.format(delta_time))
    print('The total time was {}ms. So {} / {} requests is {}ms. So, {}ms - {}ms (freq_time delay) = {}ms. So the total time operation used by Modbus Protocol is {}ms'.format(delta_time, delta_time, counter_requests, delta_time_div_counter_requests, delta_time_div_counter_requests, fred_time, modbus_time_operation, modbus_time_operation))
$ mpremote run read_holding_registers.py 
Running ModBus version: 2.1.2-rc18.dev42
HOLDING REGISTER request test.
Reading qty=33 from address 130:
Result: (34, 12, 14, 0, 10, 4, 2, 1345, 34, 8, 1100, 350, 456, 754, 324, 423, 530, 90, 320, 34, 244, 355, 606, 656, 640, 620, 677, 623, 234, 567, 34, 56, 68)
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 4766ms.
The total time was 4766ms. So 4766 / 100 requests is 47.66ms. So, 47.66ms - 30ms (freq_time delay) = 17.66ms. So the total time operation used by Modbus Protocol is 17.66ms

Thank for this new release with improvements on the speed! I think that 12-18ms for each ModBus request is very good! Maybe if we freeze (compile it together the MicroPython) this lib, that results can be faster!

Ps: I do not running anything together this tests, like as my uasyncio applications, I mean, I'm running just this test to be fair with the results.

Off-topic: is possible to run the ModBus Slave TCP and ModBus Slave RTU together with my uasyncio applications, even we knowing that this lib is still blocking (no uasyncio support)?

As Slave (RTU and TCP) runs in a loop:

while True:
    result = client.process()

So I need two that loops running (RTU and TCP Slave). I don't know if is possible to call that loop from the uasyncio, even being blocking for now. Or the way, while uasyncio support is not implemented, to run that two Slaves in separated threads (one thread for each Slave)?

@brainelectronics
Copy link
Owner

@brainelectronics I found the problem:

On the:

time.sleep(self._t35chars)

you changed to use self._t35chars, that is microseconds variable, but you do not changed to be time.sleep_us (sleep in microseconds). I just changed to time.sleep_us(self._t35chars) and works fine.

Thank you very much!

Thank you for the finding! It's now fixed in 2.1.2-rc19.dev42

@beyonlo
Copy link

beyonlo commented Dec 29, 2022

Thank you for the finding! It's now fixed in 2.1.2-rc19.dev42

Hey @brainelectronics

Works perfect with this version:

$ mpremote run read_coils.py 
Running ModBus version: 2.1.2-rc19.dev42
COIL request test.
Reading qty=16 from address 125:
Result: [True, True, True, True, True, False, False, False, False, True, True, True, False, False, False, True]
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 4172ms.
The total time was 4172ms. So 4172 / 100 requests is 41.72ms. So, 41.72ms - 30ms (freq_time delay) = 11.72ms. So the total time operation used by Modbus Protocol is 11.72ms
$ mpremote run read_holding_registers.py 
Running ModBus version: 2.1.2-rc19.dev42
HOLDING REGISTER request test.
Reading qty=33 from address 130:
Result: (34, 12, 14, 0, 10, 4, 2, 1345, 34, 8, 1100, 350, 456, 754, 324, 423, 530, 90, 320, 34, 244, 355, 606, 656, 640, 620, 677, 623, 234, 567, 34, 56, 68)
Testing 100 requests, each 30ms. Wait...
Done ModBus RTU (via RS485) requests without error in 4764ms.
The total time was 4764ms. So 4764 / 100 requests is 47.64ms. So, 47.64ms - 30ms (freq_time delay) = 17.64ms. So the total time operation used by Modbus Protocol is 17.64ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants