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

Allow inversion of serial signals #74

Open sandyscott opened 1 year ago

sandyscott commented 1 year ago

Add "invert" argument to Serial and ModbusRTU class constructors, and include the INV_RX and INV_TX constants as class variables to be used to fill this argument. This is passed to the UART constructor.

beyonlo commented 1 year ago

@brainelectronics I used the https://github.com/sandyscott/micropython-modbus/tree/develop to do this test, but I have error, maybe I do not used correctly:

I tried to use invert on the rtu_client_example.py example. Here part that I changed from that example:

client = ModbusRTU(
    addr=slave_addr,        # address on bus
    pins=rtu_pins,          # given as tuple (TX, RX)
    baudrate=baudrate,      # optional, default 9600
    # data_bits=8,          # optional, default 8
    # stop_bits=1,          # optional, default 1
    # parity=None,          # optional, default None
    ctrl_pin=15,          # optional, control DE/RE
    uart_id=uart_id,         # optional, default 1, see port specific docs
    invert=1
)

Added just line invert=1

I believe that param 0 on invert is to not invert (False) and 1 is to invert (True). Am I correct?

I have this error:

$ mpremote run rtu_client_example.py 
MicroPython infos: (sysname='esp32', nodename='esp32', release='1.20.0', version='v1.20.0 on 2023-04-26', machine='ESP32S3 module (spiram) with ESP32S3')
Used micropthon-modbus version: 0.0.0
Using pins (37, 38) with UART ID 1
Traceback (most recent call last):
  File "<stdin>", line 26, in <module>
  File "umodbus/serial.py", line 68, in __init__
  File "umodbus/serial.py", line 124, in __init__
ValueError: invalid inversion mask

Ps: When I use invert=0 I have no error

sandyscott commented 1 year ago

@beyonlo The idea was the that the invert argument works the same way as at does with the built-in machine.UART.init().

from https://docs.micropython.org/en/latest/library/machine.UART.html

invert specifies which lines to invert.

  • 0 will not invert lines (idle state of both lines is logic high).
  • UART.INV_TX will invert TX line (idle state of TX line now logic low).
  • UART.INV_RX will invert RX line (idle state of RX line now logic low).
  • UART.INV_TX | UART.INV_RX will invert both lines (idle state at logic low).

As machine.UART is not in the namespace if you've only imported ModbusRTU or Serial, I added those two constants to the classes, so you can use them like this:

import ModbusRTU

# Create object with both signals inverted:
client = ModbusRTU(
    addr=slave_addr,        # address on bus
    pins=rtu_pins,          # given as tuple (TX, RX)
    baudrate=baudrate,      # optional, default 9600
    # data_bits=8,          # optional, default 8
    # stop_bits=1,          # optional, default 1
    # parity=None,          # optional, default None
    ctrl_pin=15,          # optional, control DE/RE
    uart_id=uart_id,         # optional, default 1, see port specific docs
    invert=ModbusRTU.INV_TX | ModbusRTU.INV_RX
)

There might be a more intuitive way to do it, maybe:

client = ModbusRTU( ... invert=ModbusRTU.UART.INV_TX | ModbusRTU.UART.INV_RX )

- or just rely on the user to import the constants directly from `machine.UART`
```python
import ModbusRTU
from machine import UART

client = ModbusRTU(
    ...
    invert=UART.INV_TX | UART.INV_RX
)
beyonlo commented 1 year ago

@sandyscott I'm sorry, I was thinking that invert is just to configure tx pin change to be rx and rx pin change to be tx, in cases that user do not need to "invert" the wires when connect they inverted.

Anyway now that I know that is not about changes pins tx and rx, what is advantage to have a UART with lines inverted, from high to low and vice-versa?

Well, I did changes as you give me the example, but do not works. I think if all working in invert=0 and I invert lines TX and RX from Slave and Master, should be continue working right?

rtu_client_example.py:

client = ModbusRTU(
    addr=slave_addr,        # address on bus
    pins=rtu_pins,          # given as tuple (TX, RX)
    baudrate=baudrate,      # optional, default 9600
    # data_bits=8,          # optional, default 8
    # stop_bits=1,          # optional, default 1
    # parity=None,          # optional, default None
    ctrl_pin=15,          # optional, control DE/RE
    uart_id=uart_id,         # optional, default 1, see port specific docs
    invert=ModbusRTU.INV_TX | ModbusRTU.INV_RX
)

rtu_host_example.py:

host = ModbusRTUMaster(
    pins=rtu_pins,          # given as tuple (TX, RX)
    baudrate=baudrate,      # optional, default 9600
    # data_bits=8,          # optional, default 8
    # stop_bits=1,          # optional, default 1
    # parity=None,          # optional, default None
    ctrl_pin=15,          # optional, control DE/RE
    uart_id=uart_id,         # optional, default 1, see port specific docs
    invert=ModbusRTUMaster.INV_TX | ModbusRTUMaster.INV_RX
)

Slave RTU:

$ mpremote run rtu_client_example.py 
MicroPython infos: (sysname='esp32', nodename='esp32', release='1.20.0', version='v1.20.0 on 2023-04-26', machine='ESP32S3 module (spiram) with ESP32S3')
Used micropthon-modbus version: 0.0.0
Using pins (37, 38) with UART ID 1
Setting up registers ...
Register setup done
Serving as RTU client on address 10 at 115200 baud

Master RTU:

$ mpremote run rtu_host_example.py 
MicroPython infos: (sysname='esp32', nodename='esp32', release='1.20.0', version='v1.20.0 on 2023-04-26', machine='ESP32S3 module (spiram) with ESP32S3')
Used micropthon-modbus version: 0.0.0
Using pins (17, 18) with UART ID 1
Requesting and updating data on RTU client at address 10 with 115200 baud

Traceback (most recent call last):
  File "<stdin>", line 54, in <module>
  File "examples/common/host_tests.py", line 130, in run_sync_host_tests
  File "umodbus/common.py", line 136, in read_coils
  File "umodbus/serial.py", line 331, in _send_receive
  File "umodbus/serial.py", line 355, in _validate_resp_hdr
OSError: no data received from slave
sandyscott commented 1 year ago

If you want to swap the RX & TX pins you can just reverse their order in the tuple you supply to the pins argument, but that will cause problems for hardware UARTs.

It's useful to be able to flip the polarity of the signals because not all serial transciever chips invert the signals internally. For example I'm using an ADM2483 RS485 transciever in a project. To produce a standard-compliant logic 1 at the output - ie. the B signal is a greater voltage than the A signal - you need drive the TxD pin low, and vice-versa. While you could flip them in hardware, the correct way of doing it is to invert the input signal to the transciever, which is apparently a convention that dates from how RS232 was implemented in the past. I assume that this is the reason that the built-in machine.UART provides this feature.

As for the example, I can't see why that wouldn't work, the only thing that's a little odd are the pins your example is using - both common/rtu_client_common and common/rtu_host_common specify pins 25 & 26 for the esp32, but that's not what's being used in your output. I'll try to make some time to investigate further, but it might not be possible for a little while.

beyonlo commented 1 year ago

If you want to swap the RX & TX pins you can just reverse their order in the tuple you supply to the pins argument, but that will cause problems for hardware UARTs.

It's useful to be able to flip the polarity of the signals because not all serial transciever chips invert the signals internally. For example I'm using an ADM2483 RS485 transciever in a project. To produce a standard-compliant logic 1 at the output - ie. the B signal is a greater voltage than the A signal - you need drive the TxD pin low, and vice-versa. While you could flip them in hardware, the correct way of doing it is to invert the input signal to the transciever, which is apparently a convention that dates from how RS232 was implemented in the past. I assume that this is the reason that the built-in machine.UART provides this feature.

Hello @sandyscott thank you for the explanation!

As for the example, I can't see why that wouldn't work, the only thing that's a little odd are the pins your example is using - both common/rtu_client_common and common/rtu_host_common specify pins 25 & 26 for the esp32, but that's not what's being used in your output. I'll try to make some time to investigate further, but it might not be possible for a little while.

I changed that pins of examples to pins of my boards (Slave and Master), that's why is different. But if I remove the line invert=ModbusRTU.INV_TX | ModbusRTU.INV_RX on the Slave and line invert=ModbusRTUMaster.INV_TX | ModbusRTUMaster.INV_RX on the Master, all works.

hmaerki commented 9 months ago

Proposal:

Now the developer may implement the ctrl_pin_cb function.