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

Does it work on Raspberry PI Pico? #7

Open
Lyansun opened this issue Jun 8, 2022 · 33 comments
Open

Does it work on Raspberry PI Pico? #7

Lyansun opened this issue Jun 8, 2022 · 33 comments
Labels
documentation Improvements or additions to documentation question Further information is requested
Milestone

Comments

@Lyansun
Copy link

Lyansun commented Jun 8, 2022

Hey buddy, how does this library work on pico?

@brainelectronics
Copy link
Owner

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

@AlKoAl
Copy link

AlKoAl commented Sep 1, 2022

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
Copy link

beyonlo commented Sep 1, 2022

Hello @AlKoAl

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

@AlKoAl
Copy link

AlKoAl commented Sep 2, 2022

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
Copy link

beyonlo commented Sep 2, 2022

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
Copy link

AlKoAl commented Sep 2, 2022

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
Copy link

beyonlo commented Sep 2, 2022

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 :)

@brainelectronics brainelectronics added the question Further information is requested label Nov 12, 2022
@volkar1
Copy link

volkar1 commented Dec 7, 2022

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
Copy link

AlKoAl commented Dec 7, 2022

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
Copy link
Owner

Relates to #34

@brainelectronics
Copy link
Owner

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
Copy link
Owner

brainelectronics commented Dec 29, 2022

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

@brainelectronics brainelectronics added the documentation Improvements or additions to documentation label Dec 29, 2022
@brainelectronics brainelectronics added this to the Documentation milestone Dec 29, 2022
@j-broome
Copy link

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
Copy link
Owner

The issue should be fixed by #45

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

@j-broome
Copy link

j-broome commented Dec 31, 2022 via email

@brainelectronics
Copy link
Owner

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
Copy link
Owner

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
Copy link

j-broome commented Dec 31, 2022 via email

@j-broome
Copy link

j-broome commented Jan 2, 2023

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
Copy link

NormanStudentRobotic commented Jan 27, 2023

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
Copy link

beyonlo commented Jan 27, 2023

@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
Copy link

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
Copy link
Owner

brainelectronics commented Jan 29, 2023

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
Copy link

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
Copy link
Owner

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
Copy link

mohdrais commented May 1, 2023

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
Copy link

mohdrais commented May 2, 2023

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
Copy link

Reseva commented Jun 17, 2023

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
Copy link

beyonlo commented Jun 20, 2023

@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
Copy link

Reseva commented Jun 28, 2023

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
Copy link

beyonlo commented Jun 28, 2023

@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
Copy link

ukrolelo commented Oct 31, 2023

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
Copy link

after adding

import gc
gc.threshold(4000)

Everything is okay

hmaerki pushed a commit to hmaerki/fork_micropython-modbus that referenced this issue Dec 24, 2023
hmaerki pushed a commit to hmaerki/fork_micropython-modbus that referenced this issue Dec 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests

10 participants