brainelectronics / micropython-modbus

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

Does it work on Raspberry PI Pico? #7

Open Lyansun opened 2 years ago

Lyansun commented 2 years ago

Hey buddy, how does this library work on pico?

brainelectronics commented 2 years ago

Hey, I haven't tested it on a Pico yet but there should not be any issues.

AlKoAl commented 2 years ago

Lyansun, hello. I do the same thing and I've already understood that this library isn't work on Raspberry Pi Pico without changings or adding external WiFi modules. Did you solve this problem?

Hey buddy, how does this library work on pico?

beyonlo commented 2 years ago

Hello @AlKoAl

What exactly do not works? Could you paste here the errors?

AlKoAl commented 2 years ago

Good morning, @beyonlo.

At the begining, there is no specific errors. The whole code (from main.py and boot.py) isn't working on Raspberry Pi Pico because this platform doesn't have WiFi module as well as network library among the preinstalled libraries. It definitely should work on Raspberry Pi Pico W, but i don't have it, and it will probably work on W5100S-EVB-Pico. Modbus RTU maybe works if we comment out network library. (Working on it)

But this is not my problem to work with. I've just try to find some micropython lybrary which can give me the simplest way to develope Modbus RTU slave on this platform. My task is to create a simple transceiver, which will get the modbus commands from the higher level and control devices, such as turning on and off LEDs. (I understand, that it is usually done with microcontrollers programmed on C). Should i use UART-RS-485 transiver to create a wire connection with master? (I don't even know that. I'm frustrated.)

As I understand this library is developed to work with two devises: one will be the slave other is master which is "talking" by WiFi. (Maybe i misunderstood something.) Can I just create only slave on Rasberry and control it with for example Modscan32 (or other programs, I don't now) like you do?

beyonlo commented 2 years ago

This lib should be works without issues in any platform where MicroPython runs, including RPico and RPico-W

At the begining, there is no specific errors. The whole code (from main.py and boot.py) isn't working on Raspberry Pi Pico because this platform doesn't have WiFi module as well as network library among the preinstalled libraries.

You can use the this lib with ModBus RTU via serial (UART, RS232, RS485, etc) using the RPico (without WiFi or Ethernet).

It definitely should work on Raspberry Pi Pico W, but i don't have it, and it will probably work on W5100S-EVB-Pico. Modbus RTU maybe works if we comment out network library. (Working on it)

On the RPico-W (WiFi) or W5100S-EVB-Pico (Ethernet) you can use this lib with ModBus RTU and/or ModBus TCP

But this is not my problem to work with. I've just try to find some micropython lybrary which can give me the simplest way to develope Modbus RTU slave on this platform.

This project (great thanks to @brainelectronics) is the mostly simplest and amazing way to do what do you want using MicroPython.

My task is to create a simple transceiver, which will get the modbus commands from the higher level and control devices, such as turning on and off LEDs. (I understand, that it is usually done with microcontrollers programmed on C). Should i use UART-RS-485 transiver to create a wire connection with master? (I don't even know that. I'm frustrated.)

That depends what do you want. If your communication need to be RS485 for example, so you need to use ModBus RTU. But if is not necessary to use a serial communication, you can use ModBus TCP via Ethernet and/or WiFi. I think that your problem is not about this library, but to understand a bit more about network communication and about how Modbus ptotocol works. I suggest you to research about that, but I will explain a bit about this for you below.

As I understand this library is developed to work with two devises: one will be the slave other is master which is "talking" by WiFi. (Maybe i misunderstood something.)

No. You can do choose to use just the Modbus RTU Slave for example and other software can do ModBus RTU Master.

This library implement the ModBus protocol, supporting all 4 types of ModBus types:

  1. ModBus RTU Slave
  2. ModBus RTU Master
  3. ModBus TCP Slave
  4. ModBus TCP Master

So, you choose from this library what type of ModBus do you want to use. And of course, you can to use all that 4 types working together, if is what do you need.

Here a brief summary how ModBus protocol works and how these 4 types of ModBus implemention (supported by this library) works:

  1. ModBus is a protocol that can run on the Serial communication and TCP communication.
  2. For Serial communication, like as UART, RS232, RS485, RS422 you need to use the ModBus RTU.
  3. For TCP communication, like as Ethernet and WiFi you need to use the ModBus TCP.
  4. Modbus protocol has the concept of request -> response (like as the HTTP protocol) , where Slave is who wait for a request and make a response, and where the Master make the request to the Slave and wait for a response.
  5. On the ModBus RTU there is a concept about Slave address, where each Slave has a specific ID (number), so the Master is capable to choose for what Slave address will make the request. So in a RS485 network for example, you can have many Slaves and just one Master, where this Master can make a request to all Slaves.
  6. On the ModBus TCP, there is as well the concept about Slave address, but instead a ID number, the Slave address is just the IP address and Port that the Slave are listening. So the Master TCP need just to send a request to IP and Port where the Slave TCP is running/listening.

Can I just create only slave on Rasberry and control it with for example Modscan32 (or other programs, I don't now) like you do?

Yes

AlKoAl commented 2 years ago

Oh, thank you so much for such a detailed answer! I should have asked my question earlier, you helped me a lot in organizing my thoughts. You are breathtaking! (Keanu Reeves meme)

beyonlo commented 2 years ago

Oh, thank you so much for such a detailed answer! I should have asked my question earlier, you helped me a lot in organizing my thoughts.

You are welcome!

You are breathtaking! (Keanu Reeves meme)

I saw that video, very good :)

volkar1 commented 1 year ago

It doesn't work on Pi Pico W. It will init the library class, but after you try to read holding register (for example) it will fail because machine is missing attribute 'wait_tx_done'. It fails because of "ctrl_pin" that is needed on MAX485 chip

Full error:

  File "<stdin>", line 18, in <module>
  File "/lib/umodbus/serial.py", line 207, in read_discrete_inputs
  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'
AlKoAl commented 1 year ago

It doesn't work on Pi Pico W. It will init the library class, but after you try to read holding register (for example) it will fail because machine is missing attribute 'wait_tx_done'. It fails because of "ctrl_pin" that is needed on MAX485 chip

Full error:

  File "<stdin>", line 18, in <module>
  File "/lib/umodbus/serial.py", line 207, in read_discrete_inputs
  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'

It is true. I was traing to change the code and ctrl_pin is some of those functions that is available in this repo (it will be possible if you change the example), but "wait_tx_done" isn't available on micropython rp2040 version (but it's OK on ESP32 or ESP8266). You could try to change the function, for example to just "wait" for several miliseconds. It will work, but kinda weird. At the end i've just got problems with synchronization and droped whole idea.

brainelectronics commented 1 year ago

Relates to #34

brainelectronics commented 1 year ago

At the begining, there is no specific errors. The whole code (from main.py and boot.py) isn't working on Raspberry Pi Pico because this platform doesn't have WiFi module as well as network library among the preinstalled libraries. It definitely should work on Raspberry Pi Pico W, but i don't have it, and it will probably work on W5100S-EVB-Pico. Modbus RTU maybe works if we comment out network library. (Working on it)

good news @AlKoAl, the package has been cleaned up with version 2.0.0 and serial as well as tcp dependencies have been removed from modbus, see also https://micropython-modbus.readthedocs.io/en/2.0.0/UPGRADE.html#update-imports

brainelectronics commented 1 year ago

Hey @AlKoAl and @volkar1 the bug is now fixed in 2.1.2

@Lyansun you can get some more informations about the usage with a Raspberry Pi Pico in #34 and #43

j-broome commented 1 year ago

After modifying serial.py lines 58 and 71 (image 1), I am able to setup the Pico as an RTU slave (image 2). When I set a holding register, it looks like the initial response looks funny and that response isn't liked by the master (throws a frame error, but it seems fine after that. Is there something in the code that would cause that? (see image 3) image 1: image image 2: image image 3: image

brainelectronics commented 1 year ago

The issue should be fixed by #45

@j-broome if you have further issues please create a new issue 😊

j-broome commented 1 year ago

I found a discrepancy in the initial response from function code 6 that doesn’t seem to take place with function code 16. I am not near my laptop at the moment, but I took good screenshots I can send on Monday. I’d also like to learn more about got and help contribute to the repo.

Sent from Proton Mail for iOS

On Sat, Dec 31, 2022 at 1:30 AM, Jones @.***> wrote:

The issue should be fixed by #45

@.***(https://github.com/j-broome) if you have further issues please create a new issue 😊

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

brainelectronics commented 1 year ago

Hey @AlKoAl and @volkar1 the bug is now fixed in 2.1.2

@Lyansun you can get some more informations about the usage with a Raspberry Pi Pico in #34 and #43

Hey @AlKoAl, @volkar1 and @Lyansun the bug is now really fixed in 2.1.3 😂 see also #45

brainelectronics commented 1 year ago

I found a discrepancy in the initial response from function code 6 that doesn’t seem to take place with function code 16. I am not near my laptop at the moment, but I took good screenshots I can send on Monday. I’d also like to learn more about got and help contribute to the repo.

No problem you're always welcome! Just one note, if you create an issue please try to paste your code with code highlighting or part of the code so we can support here faster. It's just easier to reproduce than code screenshots 😉

j-broome commented 1 year ago

I used Modbus Poll and watched the communication between my PC and the Pico. When I write a single register (FC6), the initial response is messed up, but then it works. When I do the same thing with FC16 (still only writing a single register), the initial response looks great. So I THINK it is not my transceiver flow control but something weird in response code of that particular function code. I ran out of time on Friday, but the obvious thing to do is try a C library and see if the issue goes away.

Feel free to school me on this, but at first I thought the transceiver had a flow control issues because the response included the exact message I sent. But that went away with FC16. I have proper pull-up and pull-up down resistors on the UART lines as well as in the A and B lines (510 ohms) of the 485 bus.

Sent from Proton Mail for iOS

On Sat, Dec 31, 2022 at 1:39 AM, Jones @.***> wrote:

I found a discrepancy in the initial response from function code 6 that doesn’t seem to take place with function code 16. I am not near my laptop at the moment, but I took good screenshots I can send on Monday. I’d also like to learn more about got and help contribute to the repo.

No problem you're always welcome! Just one note, if you create an issue please try to paste your code with code highlighting or part of the code so we can support here faster. It's just easier to reproduce than code screenshots 😉

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

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.

NormanStudentRobotic commented 1 year ago

Hey can you help me?, I want to connect pico to pico using protocol Modbus RS485 but i got some error, Here is my schematic program Schematic

i use code program from examples folder rtu_host_example.py and rtu_client_example.py, i just edit rx tx pins and uart_id

rtu_host_example.py

import time from umodbus.serial import Serial as ModbusRTUMaster IS_DOCKER_MICROPYTHON = False try: import machine machine.reset_cause() except ImportError: raise Exception('Unable to import machine, are all fakes available?') except AttributeError:

IS_DOCKER_MICROPYTHON = True
import sys

slave_addr = 10

rtu_pins = (0, 1)
baudrate = 9600

host = ModbusRTUMaster( pins=rtu_pins,
baudrate=baudrate,
data_bits=8,
stop_bits=1,
parity=None,
ctrl_pin=12,
uart_id=0
)

if IS_DOCKER_MICROPYTHON:

assert host._uart._is_server is False

register_definitions = { "COILS": { "RESET_REGISTER_DATA_COIL": { "register": 42, "len": 1, "val": 0 }, "EXAMPLE_COIL": { "register": 123, "len": 1, "val": 1 } }, "HREGS": { "EXAMPLE_HREG": { "register": 93, "len": 1, "val": 19 } }, "ISTS": { "EXAMPLE_ISTS": { "register": 67, "len": 1, "val": 0 } }, "IREGS": { "EXAMPLE_IREG": { "register": 10, "len": 1, "val": 60001 } } }

print('Requesting and updating data on RTU client at address {} with {} baud'. format(slave_addr, baudrate)) print()

coil_address = register_definitions['COILS']['EXAMPLE_COIL']['register'] coil_qty = register_definitions['COILS']['EXAMPLE_COIL']['len'] coil_status = host.read_coils( slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) print('Status of COIL {}: {}'.format(coil_address, coil_status)) time.sleep(1)

new_coil_val = 0 operation_status = host.write_single_coil( slave_addr=slave_addr, output_address=coil_address, output_value=new_coil_val) print('Result of setting COIL {} to {}'.format(coil_address, operation_status)) time.sleep(1)

coil_status = host.read_coils( slave_addr=slave_addr, starting_addr=coil_address, coil_qty=coil_qty) print('Status of COIL {}: {}'.format(coil_address, coil_status)) time.sleep(1)

print()

hreg_address = register_definitions['HREGS']['EXAMPLE_HREG']['register'] register_qty = register_definitions['HREGS']['EXAMPLE_HREG']['len'] register_value = host.read_holding_registers( slave_addr=slave_addr, starting_addr=hreg_address, register_qty=register_qty, signed=False) print('Status of HREG {}: {}'.format(hreg_address, register_value)) time.sleep(1)

new_hreg_val = 44 operation_status = host.write_single_register( slave_addr=slave_addr, register_address=hreg_address, register_value=new_hreg_val, signed=False) print('Result of setting HREG {} to {}'.format(hreg_address, operation_status)) time.sleep(1)

register_value = host.read_holding_registers( slave_addr=slave_addr, starting_addr=hreg_address, register_qty=register_qty, signed=False) print('Status of HREG {}: {}'.format(hreg_address, register_value)) time.sleep(1)

print()

ist_address = register_definitions['ISTS']['EXAMPLE_ISTS']['register'] input_qty = register_definitions['ISTS']['EXAMPLE_ISTS']['len'] input_status = host.read_discrete_inputs( slave_addr=slave_addr, starting_addr=ist_address, input_qty=input_qty) print('Status of IST {}: {}'.format(ist_address, input_status)) time.sleep(1)

ireg_address = register_definitions['IREGS']['EXAMPLE_IREG']['register'] register_qty = register_definitions['IREGS']['EXAMPLE_IREG']['len'] register_value = host.read_input_registers( slave_addr=slave_addr, starting_addr=ireg_address, register_qty=register_qty, signed=False) print('Status of IREG {}: {}'.format(ireg_address, register_value)) time.sleep(1)

print()

print('Resetting register data to default values...') coil_address = \ register_definitions['COILS']['RESET_REGISTER_DATA_COIL']['register'] new_coil_val = True operation_status = host.write_single_coil( slave_addr=slave_addr, output_address=coil_address, output_value=new_coil_val) print('Result of setting COIL {}: {}'.format(coil_address, operation_status)) time.sleep(1)

print()

print("Finished requesting/setting data on client")

if IS_DOCKER_MICROPYTHON: sys.exit(0)

Error code program Traceback (most recent call last): File "", line 21, in File "/lib/umodbus/serial.py", line 58, in init ValueError: expecting a Pin

rtu_client_example.py

from umodbus.serial import ModbusRTU

IS_DOCKER_MICROPYTHON = False try: import machine machine.reset_cause() except ImportError: raise Exception('Unable to import machine, are all fakes available?') except AttributeError:

IS_DOCKER_MICROPYTHON = True
import json

rtu_pins = (0, 1)
slave_addr = 10
baudrate = 9600 client = ModbusRTU( addr=slave_addr,
pins=rtu_pins,
baudrate=baudrate,
data_bits=8,
stop_bits=1,
parity=None,

ctrl_pin=12,

uart_id=0             

)

if IS_DOCKER_MICROPYTHON:

assert client._itf._uart._is_server is True

def reset_data_registers_cb(reg_type, address, val):

global client
global register_definitions

print('Resetting register data to default values ...')
client.setup_registers(registers=register_definitions)
print('Default values restored')

register_definitions = { "COILS": { "RESET_REGISTER_DATA_COIL": { "register": 42, "len": 1, "val": 0 }, "EXAMPLE_COIL": { "register": 123, "len": 1, "val": 1 } }, "HREGS": { "EXAMPLE_HREG": { "register": 93, "len": 1, "val": 19 } }, "ISTS": { "EXAMPLE_ISTS": { "register": 67, "len": 1, "val": 0 } }, "IREGS": { "EXAMPLE_IREG": { "register": 10, "len": 1, "val": 60001 } } }

if IS_DOCKER_MICROPYTHON: with open('registers/example.json', 'r') as file: register_definitions = json.load(file)

register_definitions['COILS']['RESET_REGISTER_DATA_COIL']['on_set_cb'] = \ reset_data_registers_cb

print('Setting up registers ...')

client.setup_registers(registers=register_definitions)

print('Register setup done')

print('Serving as RTU client on address {} at {} baud'. format(slave_addr, baudrate))

while True: try: result = client.process() except KeyboardInterrupt: print('KeyboardInterrupt, stopping RTU client...') break except Exception as e: print('Exception during execution: {}'.format(e))

print("Finished providing/accepting data as client")

Error code program Traceback (most recent call last): File "", line 17, in File "/lib/umodbus/serial.py", line 29, in init File "/lib/umodbus/serial.py", line 58, in init ValueError: expecting a Pin

Serial.py

from machine import UART from machine import Pin import struct import time import machine

from . import const as Const from . import functions from .common import Request, CommonModbusFunctions from .common import ModbusException from .modbus import Modbus

from .typing import List, Optional, Union

class ModbusRTU(Modbus):

def __init__(self,
             addr: int,
             baudrate: int = 9600,
             data_bits: int = 8,
             stop_bits: int = 1,
             parity: Optional[int] = None,
             pins: List[Union[int, Pin], Union[int, Pin]] = None,
             ctrl_pin: int = None,
             uart_id: int = 0):
    super().__init__(

        Serial(uart_id=uart_id,
               baudrate=baudrate,
               data_bits=data_bits,
               stop_bits=stop_bits,
               parity=parity,
               pins=pins,
               ctrl_pin=ctrl_pin),
        [addr]
    )

class Serial(CommonModbusFunctions): def init(self, uart_id: int = 0, baudrate: int = 9600, data_bits: int = 8, stop_bits: int = 1, parity=None, pins: List[Union[int, Pin], Union[int, Pin]] = None, ctrl_pin: int = None):

    self._uart = UART(uart_id,
                      baudrate=baudrate,
                      bits=data_bits,
                      parity=parity,
                      stop=stop_bits,
                      # timeout_chars=2,  # WiPy only
                      # pins=pins         # WiPy only
                      tx=pins[0],
                      rx=pins[1]
                      )

    if ctrl_pin is not None:
        self._ctrlPin = Pin(ctrl_pin, mode=Pin.OUT)
    else:
        self._ctrlPin = None

    self._t1char = (1000000 * (data_bits + stop_bits + 2)) // baudrate
    if baudrate <= 19200:
        self._t35chars = (3500000 * (data_bits + stop_bits + 2)) // baudrate
    else:
        self._t35chars = 1750

def _calculate_crc16(self, data: bytearray) -> bytes:

    crc = 0xFFFF

    for char in data:
        crc = (crc >> 8) ^ Const.CRC16_TABLE[((crc) ^ char) & 0xFF]

    return struct.pack('<H', crc)

def _exit_read(self, response: bytearray) -> bool:

    if response[1] >= Const.ERROR_BIAS:
        if len(response) < Const.ERROR_RESP_LEN:
            return False
    elif (Const.READ_COILS <= response[1] <= Const.READ_INPUT_REGISTER):
        expected_len = Const.RESPONSE_HDR_LENGTH + 1 + response[2] + Const.CRC_LENGTH
        if len(response) < expected_len:
            return False
    elif len(response) < Const.FIXED_RESP_LEN:
        return False

    return True

def _uart_read(self) -> bytearray:

    response = bytearray()

    for x in range(1, 40):
        if self._uart.any():
            response.extend(self._uart.read())
            if self._exit_read(response):
                break
        time.sleep_us(self._t35chars)

    return response

def _uart_read_frame(self, timeout: Optional[int] = None) -> bytearray:

    received_bytes = bytearray()

    if timeout == 0 or timeout is None:
        timeout = 2 * self._t35chars

    start_us = time.ticks_us()

    while (time.ticks_diff(time.ticks_us(), start_us) <= timeout):

        if self._uart.any():

            last_byte_ts = time.ticks_us()

            while time.ticks_diff(time.ticks_us(), last_byte_ts) <= self._t35chars:
                r = self._uart.read()

                if r is not None:

                    received_bytes.extend(r)

                    last_byte_ts = time.ticks_us()

        if len(received_bytes) > 0:
            return received_bytes

    return received_bytes

def _send(self, modbus_pdu: bytes, slave_addr: int) -> None:

    serial_pdu = bytearray()
    serial_pdu.append(slave_addr)
    serial_pdu.extend(modbus_pdu)

    crc = self._calculate_crc16(serial_pdu)
    serial_pdu.extend(crc)

    if self._ctrlPin:
        self._ctrlPin(1)
        time.sleep_us(1000)     
        send_start_time = time.ticks_us()

    self._uart.write(serial_pdu)

    if self._ctrlPin:
        total_frame_time_us = self._t1char * len(serial_pdu)
        while time.ticks_us() <= send_start_time + total_frame_time_us:
            machine.idle()
        self._ctrlPin(0)

def _send_receive(self,
                  modbus_pdu: bytes,
                  slave_addr: int,
                  count: bool) -> bytes:

    self._uart.read()

    self._send(modbus_pdu=modbus_pdu, slave_addr=slave_addr)

    return self._validate_resp_hdr(response=self._uart_read(),
                                   slave_addr=slave_addr,
                                   function_code=modbus_pdu[0],
                                   count=count)

def _validate_resp_hdr(self,
                       response: bytearray,
                       slave_addr: int,
                       function_code: int,
                       count: bool) -> bytes:

    if len(response) == 0:
        raise OSError('no data received from slave')

    resp_crc = response[-Const.CRC_LENGTH:]
    expected_crc = self._calculate_crc16(
        response[0:len(response) - Const.CRC_LENGTH]
    )

    if ((resp_crc[0] is not expected_crc[0]) or
            (resp_crc[1] is not expected_crc[1])):
        raise OSError('invalid response CRC')

    if (response[0] != slave_addr):
        raise ValueError('wrong slave address')

    if (response[1] == (function_code + Const.ERROR_BIAS)):
        raise ValueError('slave returned exception code: {:d}'.
                         format(response[2]))

    hdr_length = (Const.RESPONSE_HDR_LENGTH + 1) if count else \
        Const.RESPONSE_HDR_LENGTH

    return response[hdr_length:len(response) - Const.CRC_LENGTH]

def send_response(self,
                  slave_addr: int,
                  function_code: int,
                  request_register_addr: int,
                  request_register_qty: int,
                  request_data: list,
                  values: Optional[list] = None,
                  signed: bool = True) -> None:

    modbus_pdu = functions.response(
        function_code=function_code,
        request_register_addr=request_register_addr,
        request_register_qty=request_register_qty,
        request_data=request_data,
        value_list=values,
        signed=signed
    )
    self._send(modbus_pdu=modbus_pdu, slave_addr=slave_addr)

def send_exception_response(self,
                            slave_addr: int,
                            function_code: int,
                            exception_code: int) -> None:

    modbus_pdu = functions.exception_response(
        function_code=function_code,
        exception_code=exception_code)
    self._send(modbus_pdu=modbus_pdu, slave_addr=slave_addr)

def get_request(self,
                unit_addr_list: List[int],
                timeout: Optional[int] = None) -> Union[Request, None]:

    req = self._uart_read_frame(timeout=timeout)

    if len(req) < 8:
        return None

    if req[0] not in unit_addr_list:
        return None

    req_crc = req[-Const.CRC_LENGTH:]
    req_no_crc = req[:-Const.CRC_LENGTH]
    expected_crc = self._calculate_crc16(req_no_crc)

    if (req_crc[0] != expected_crc[0]) or (req_crc[1] != expected_crc[1]):
        return None

    try:
        request = Request(interface=self, data=req_no_crc)
    except ModbusException as e:
        self.send_exception_response(
            slave_addr=req[0],
            function_code=e.function_code,
            exception_code=e.exception_code)
        return None

    return request

image image

Did I make a mistake on the pin? Is there something I should change? Please help, any help is very helpful

beyonlo commented 1 year ago

@NormanStudentRobotic Hello! Your project is very great, and this lib will fit very well for your project! So, of course you need to run the ModBus RTU Slave on the first RPICO board and the ModBus RTU Master on the second RPICO. Well, I see that your schematic you do not using a control pin to send/receive data over RS-485, so I think that you are using auto control.

Error code program Traceback (most recent call last): File "", line 17, in File "/lib/umodbus/serial.py", line 29, in init File "/lib/umodbus/serial.py", line 58, in init ValueError: expecting a Pin

I see this error in your report. You are using rtu_pins = (0, 1) but RP2 needs to use rtu_pins = (Pin(0), Pin(1)) like as show on the documentation for this lib.

I hope that this can help you.

NormanStudentRobotic commented 1 year ago

Hi I've changed the pin as follows, but I'm getting a new error like this. image

Here is my new error

Traceback (most recent call last): File "", line 117, in File "/lib/umodbus/common.py", line 140, in read_coils File "/lib/umodbus/functions.py", line 394, in bytes_to_bool ValueError: invalid format specifier

brainelectronics commented 1 year ago

Hi @NormanStudentRobotic If you want to use Pin(0) and Pin(1) for your Modbus communication, you also need to set the uart_id=0, see RTU example comment and RP2 UART docs. It is also documented in the example docs

UART0 can be mapped to GPIO 0/1, 12/13 and 16/17, and UART1 to GPIO 4/5 and 8/9.

By default, uart_id is set to 1 in this lib, as UART0 is usually used by the default USB interface or similar.

The following code should fix your issue. I'll also update the documentation and examples accordingly

rtu_pins = (Pin(0), Pin(1))         # (TX, RX)
baudrate = 9600
client = ModbusRTU(
    addr=slave_addr,        # address on bus
    pins=rtu_pins,            # given as tuple (TX, RX)
    baudrate=9600,
    uart_id=1
)
NormanStudentRobotic commented 1 year ago

I've changed uart_id = 0 since the beginning image in serial.py I have also changed image image

Why now i got error like this image can you help me sir?

brainelectronics commented 1 year ago

Hi @NormanStudentRobotic can you provide me more informations about the system you are using? Simply run the following code on your boards

import os
from umodbus import version

os_info = os.uname()
print('MicroPython infos: {}'.format(os_info))
print('Used micropthon-modbus version: {}'.format(version.__version__))

I've created a RP2-RP2 setup according to your prevoius comments setup with the two RS485 modules. I'm using the RTU client and RTU host example as is. Only these changes have been made

rtu_client_example.py

uart_id = 0
rtu_pins = (Pin(0), Pin(1))     # (TX, RX)

client = ModbusRTU(
    addr=10,            # address on bus
    pins=rtu_pins,      # given as tuple (TX, RX)
    baudrate=9600,      # optional, default 9600
    uart_id=uart_id     # optional, default 1, see port specific docs
)

rtu_host_example.py

uart_id = 0
rtu_pins = (Pin(0), Pin(1))     # (TX, RX)

host = ModbusRTUMaster(
    pins=rtu_pins,      # given as tuple (TX, RX)
    baudrate=9600,      # optional, default 9600
    uart_id=uart_id     # optional, default 1, see port specific docs
)

See also #59

mohdrais commented 1 year ago

Hello everybody, I got problem when reading holding register. I tried to read the problems but i dont understand. I am using modbus rs485 (max485 TTL) to read the holding register of CO2, Temperature and humidity sensor Sensor Chart address

This is the result of running the program Running ModBus version: 2.3.4 HOLDING REGISTER request test. Reading qty=3 from address 0: Traceback (most recent call last): File "", line 16, in File "/lib/umodbus/common.py", line 199, in read_holding_registers File "/lib/umodbus/serial.py", line 289, in _send_receive File "/lib/umodbus/serial.py", line 313, in _validate_resp_hdr OSError: no data received from slave

mohdrais commented 1 year ago

I am very sorry folks. Maybe I made a mistake, that is after exchanging the connection from TX outlet from the RS3485 TTL to GPIO 2 and RX to GPIO TX, I got the result and also I got an error. The result is almost identical when I tested with Arduino MEGA and Arduino NANO EVERY. Below is the result from running the program

Running ModBus version: 2.3.4 HOLDING REGISTER request test. Reading qty=3 from address 0: Result: (702, 318, 431) Testing 100 requests, each 30ms. Wait... Error found

This is the programming

cat read_holding_registers.py

import time from machine import Pin, UART from umodbus import version print('Running ModBus version: {}'.format(version.version))

from umodbus.serial import Serial as ModbusRTUMaster rtu_pins = (Pin(0), Pin(1))

address = 0 qty = 3 host = ModbusRTUMaster(uart_id=0,baudrate = 4800, pins=rtu_pins)

host = ModbusRTUMaster(baudrate=4800, data_bits=8, stop_bits=1, parity=None, pins=rtu_pins, ctrl_pin=None, uart_id =0)

print('HOLDING REGISTER request test.') print('Reading qty={} from address {}:'.format(qty, address)) values = host.read_holding_registers(slave_addr=1, 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=1, 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))

Reseva commented 1 year ago

Hi Guys, I want to build up a Modbus TCP communication through ethernet on a W5100S-EVB-Pico board, however when I want to import TCP, it says 'MemoryError: memory allocation failed, allocating 505 bytes'. I tried gc.threshold(2000) also, but same with different allocated bytes. Do you have any idea? Thanks

beyonlo commented 1 year ago

@Reseva Could you please put this code before all others code and paste the result here?

import gc
print(f'mem_alloc: {gc.mem_alloc()} | mem_free: {gc.mem_free()}')

And please, paste always the all output here, so we can help you better.

Reseva commented 1 year ago

Sure, here is the code: import gc print(f'mem_alloc: {gc.mem_alloc()} | mem_free: {gc.mem_free()}') import time from umodbus.tcp import TCP as ModbusTCPMaster import network import ethernet

ethernet.w5x00_init()

===============================================

TCP Slave setup

port = 502 # port to listen to slave_addr = 1 # bus address of modbus_client ist_address = 0 # Input Status Adress input_qty = 1 # Input Status Quantity to Read in Single Request

IP Address of Modbus TCP Server

ip = '192.168.1.20'

Setup Modbus TCP Client

modbus_client = ModbusTCPMaster(slave_ip=ip,slave_port=port,timeout=5) print(f'Updating data to Modbus TCP Server at {ip}:{port}')

And here is the output: MPY: soft reboot mem_alloc: 5408 | mem_free: 44576 Traceback (most recent call last): File "", line 4, in File "/lib/umodbus/tcp.py", line 22, in MemoryError: memory allocation failed, allocating 505 bytes

beyonlo commented 1 year ago

@Reseva There is something wrong on the W5100S-EVB-Pico board, because it has just ~50KB of free memory (mem_alloc: 5408 + mem_free: 44576). As I remember the rp2040 has around 180KB of free memory, maybe the W5100S ethernet is using so much RAM?

Well, problem is that with 50KB free RAM can't run this lib without freeze it in the firmware. So, please, freeze (compile the lib together with MicroPython) the modbus lib inside the firmware to works.

ukrolelo commented 1 year ago

Guyz, any idea? It is working few times and then stuck. modbus_client = ModbusTCPMaster(slave_ip=ip,slave_port=port,timeout=5)

Traceback (most recent call last):
  File "<stdin>", line 119, in <module>
  File "<stdin>", line 69, in mymodbus
  File "/lib/umodbus/tcp.py", line 81, in __init__
OSError: [Errno 12] ENOMEM
ukrolelo commented 1 year ago

after adding

import gc
gc.threshold(4000)

Everything is okay