10

问题

pymodbus 主/客户端可以向从/服务器发送请求。从机/服务器使东西准备好返回,并等待主/客户端来接它们。尽管服务器/从机准备就绪,主/客户端只是返回错误“Modbus 错误:[输入/输出] Modbus 错误:[无效消息] 收到不完整的消息,预计至少 2 个字节(收到 0)”。

设置

我通过此适配器将笔记本电脑用作服务器/从机:https ://www.amazon.com/dp/B076WVFXN8/ref=twister_B076X1BS4H?_encoding=UTF8&psc=1

我有一个 Raspberry Pi 3 / BananaPi 作为主/客户端,并附有此适配器:https ://www.aliexpress.com/item/32781613765.html?spm=a2g0s.9042311.0.0.1aec4c4d0EXx8M

我正在关注本教程的大部分设置,除了 Arduino 与笔记本电脑适配器交换:https ://circuitdigest.com/microcontroller-projects/rs485-serial-communication-between-arduino-and-raspberry-pi - Raspberry 的引脚连接与教程中的一样。

我有这个程序作为我的笔记本电脑上的服务器/从机:

#!/usr/bin/env python
from pymodbus.server.sync import StartTcpServer
from pymodbus.server.sync import StartUdpServer
from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSparseDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusBinaryFramer

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def run_server():

    slave_store1 = ModbusSlaveContext(co=ModbusSequentialDataBlock(0, [1]*16))
    slave_store2 = ModbusSlaveContext(di=ModbusSequentialDataBlock(0, [1]*16))
    slave_store3 = ModbusSlaveContext(ir=ModbusSequentialDataBlock(0, [5]*16))
    slave_store4 = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [5]*16))

    slaves = {
        0x01: slave_store1,
        0x02: slave_store2,
        0x03: slave_store3,
        0x04: slave_store4,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/riptideio/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '2.2.0'

    # RTU:
    StartSerialServer(context, framer=ModbusRtuFramer, identity=identity, port='/dev/ttyUSB0', timeout=4, baudrate=115200, stopbits=1, bytesize=8, parity='N') 

if __name__ == "__main__":
    run_server()

server/slave 上的 python 版本是:

$ python3 --version
Python 3.5.2

我用这个命令开始它:

$ python3 pymodbus_sync_serv_example_2019.07.05-1316.py

我在 Raspberry Pi 3 / BananaPi 上有以下作为主/客户端:

#!/usr/bin/env python

import logging
FORMAT = ('%(asctime)-15s %(threadName)-15s '
'%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

UNIT = 0x1

def run_sync_client():

    client = ModbusClient(method='rtu', port='/dev/ttyS2', timeout=4, baudrate=115200, stopbits=1, bytesize=8, parity='N')

    print(client)

    client.connect()

    log.debug("===================================")
    log.debug("Read input registers")
    log.debug("")
    rr = client.read_input_registers(1, 2, unit=3)
    print(rr)

    client.close()

if __name__ == "__main__":
    #for _ in range(10):
    run_sync_client()

测试和分析

我尝试过 Raspberry Pi 3 和 BananaPi。结果相同。

我试过波特率 = 9600、38400,现在是 115200。

正如您在代码中看到的那样,超时已经很高了。

服务器/从属的日志:

2019-07-07 13:35:00,333 MainThread      DEBUG    sync           :45       Client Connected [/dev/ttyUSB0:/dev/ttyUSB0]
2019-07-07 13:35:00,333 MainThread      DEBUG    sync           :522      Started thread to serve client
2019-07-07 13:35:08,341 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x0 0x1 0x0 0x2
2019-07-07 13:35:08,341 MainThread      DEBUG    factory        :137      Factory Request[ReadInputRegistersRequest: 4]
2019-07-07 13:35:08,341 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 13:35:08,342 MainThread      DEBUG    context        :64       validate: fc-[4] address-2: count-2
2019-07-07 13:35:08,342 MainThread      DEBUG    context        :78       getValues fc-[4] address-2: count-2
2019-07-07 13:35:08,342 MainThread      DEBUG    sync           :143      send: [ReadRegisterResponse (2)]- b'030404000500050846'

上面的服务器/从服务器只是在最后一条日志行之后用闪烁的光标等待......

主/客户端的日志:

ModbusSerialClient(rtu baud[115200])
2019-07-07 13:35:04,428 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:165      ===================================
2019-07-07 13:35:04,429 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:166      Read input registers
2019-07-07 13:35:04,430 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:167      
2019-07-07 13:35:04,430 MainThread      DEBUG    transaction    :111      Current transaction state - IDLE
2019-07-07 13:35:04,430 MainThread      DEBUG    transaction    :116      Running transaction 1
2019-07-07 13:35:04,431 MainThread      DEBUG    transaction    :215      SEND: 0x3 0x4 0x0 0x1 0x0 0x2 0x21 0xe9
2019-07-07 13:35:04,431 MainThread      DEBUG    sync           :73       New Transaction state 'SENDING'
2019-07-07 13:35:04,432 MainThread      DEBUG    transaction    :224      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2019-07-07 13:35:08,439 MainThread      DEBUG    transaction    :234      Transaction failed. (Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)) 
2019-07-07 13:35:08,440 MainThread      DEBUG    rtu_framer     :235      Frame - [b''] not ready
2019-07-07 13:35:08,441 MainThread      DEBUG    transaction    :390      Getting transaction 3
2019-07-07 13:35:08,442 MainThread      DEBUG    transaction    :189      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Modbus Error: [Input/Output] Modbus Error: [Invalid Message] Incomplete message received, expected at least 2 bytes (0 received)

主/客户端上的python版本是:

$ python3 --version
Python 3.5.2

我用这个命令开始它:

$ python3 pymodbus_sync_client_example_2019.07.05-1319.py

Raspberry/BananaPi 上 /dev 的权限是:

$ ls -l /dev/ttyS*
crw--w---- 1 root tty     249, 0 Jul  7 11:21 /dev/ttyS0
crw-rw---- 1 root dialout 249, 1 Jul  7 11:22 /dev/ttyS1
crw-rw---- 1 root dialout 249, 2 Jul  7 13:35 /dev/ttyS2
crw-rw---- 1 root dialout 249, 3 Jul  7 11:20 /dev/ttyS3

在笔记本电脑上的服务器/从机上:

$ ls -l /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Jul  7 13:35 /dev/ttyUSB0

我尝试使用 RS485 协议发送简单的数字。它们可以从 master/Raspberry/BananaPi 发送到笔记本电脑,但不能反过来。

我的设备权限设置错误吗?...

我究竟做错了什么?...

我错过了什么?...

由于 RS485 只能以一种方式工作,我认为 pymodbus 不是问题(?)...(我的逻辑是 pymodbus 建立在 RS485 标准中,如果 RS485 的底层不起作用,pymodbus 将不会. 这个假设正确吗?)

我知道有些人在谈论 Raspberry Pi 的引脚电压为 3.3V,不适用于 5V 引脚单元。尽管如此,所有教程似乎都忽略了这一事实并起作用。- 或者他们只是假装它有效?TTL 规范表明所有高于 2.5V 的电压都将被视为 HIGH。所以在理论中,3.3V 应该没问题,正如教程所建议的那样。

我故意还没有在 tx/rx 线上连接任何电阻用于上拉/下拉。教程不建议它们。

我已经使用 modbus 温湿度传感器测试了笔记本电脑上的 RS85 适配器。这似乎完美无缺。所以这个事实表明 BananaPi/Raspberry Pi 和 RS485 适配器组合 + 软件 + 设置在某种程度上存在缺陷。

4

2 回答 2

13

首先,让我开始说很高兴回答这样一个精心设计的问题。不是每个人都花这么多精力来解释他们做了什么以及他们是如何做到的。读完之后,你的问题是加一的。

现在解决你的问题。您错过了您遵循的教程中的一个非常重要的步骤。正如您所说的 Modbus 是半双工1,您只有两条线,并且只允许一个设备在总线上通话,因此您需要一种控制总线的方法,可以这么说。在您的 USB 转 RS485/422 电缆中,这是由电缆上的硬件自动为您完成的(特别是您的电缆使用具有 TXEN -TX 启用信号的无处不在的 FTDI 芯片,请参见此处有关更多详细信息),这就是您注意到电缆工作良好的原因。另一方面,你那小小的 3 美元收发器是可怜的兄弟,它甚至没有 UART,它只是一个单端到差分转换器。这就是为什么你需要提供一个 DE/~RE(驱动启用/不读取启用)信号,让可怜的家伙知道什么时候允许它控制总线。

这是您没有从教程中得到的警告:

重要提示:在向 RS-485 模块写入值之前,必须将引脚 DE 和 RE 设置为高电平。

这似乎很容易,但如果您认为 Modbus 是如何工作的……实际上并不那么容易。这行代码:

rr = client.read_input_registers(1, 2, unit=3)

如果您要成功与 RS485 半双工通信,应该做很多事情:控制总线(在您的设置中将 RE/~DE 信号设置为高电平),发送 Modbus 查询帧,请求 UNIT 上的两个寄存器ID 3,写完查询后(在 3.5 个字符的时间之后)释放总线控制(现在将 RE/~DE 设置为低)并从从站读取答案。

正如我在上面已经提到的链接中解释的那样,这个问题有几种解决方案。我最喜欢的一个(更像是一个硬件人)是通过硬件做总线方向控制信号(最好的方法是让一个收发器通过硬件实现这个功能,就像这个一样,但在链接中你也会发现使用 555 计时器的 DIY 解决方案)。现在,如果您更喜欢软件方式,您有一些选择。您可以调整pymodbus以根据 Modbus 需要切换控制线(我引用的答案中包含一些链接),或者,如果您更喜欢开箱即用的解决方案,请使用libmodbus

如果您决定使用最后一个选项,您可以找到有关如何使用 Rpi 上的 GPIO 引脚构建和安装支持半双工的lidmodbus的所有详细信息,如果您想继续使用 Python,请安装包装器并测试基本示例。还有几个示波器屏幕截图可以查看通过软件和硬件切换线路之间的区别。对于大多数内部或业余爱好者的目的,您应该能够使用软件切换,但我不相信它用于工业或更关键的应用程序。

最后,我认为值得一一回答您的所有问题:

由于 RS485 只能以一种方式工作,我认为 pymodbus 不是问题(?)...(我的逻辑是 pymodbus 建立在 RS485 标准中,如果 RS485 的底层不起作用,pymodbus 将不会. 这个假设正确吗?)

好吧,是的,不是的,也许......正如你在上面读到的,pymodbus并不是真正的问题。它只是期望您或您的硬件处理控制谁访问总线的不那么次要的细节。我认为大多数人将这种库用于 Modbus TCP,所以这对大多数用户来说从来都不是问题。在一般 Modbus 场景中,PLC 通过 RS485 链路上的 Modbus RTU 与另一台设备通信,问题由硬件处理,因此您也不必担心。

我知道有些人在谈论 Raspberry Pi 的引脚电压为 3.3V,不适用于 5V 引脚单元。尽管如此,所有教程似乎都忽略了这一事实并起作用。- 或者他们只是假装它有效?TTL 规范表明所有高于 2.5V 的电压都将被视为 HIGH。所以在理论中,3.3V 应该没问题,正如教程所建议的那样。

正确,MAX485 datahseet指定了 VIH 和 VOL 的阈值,只要您使用 5V 作为收发器的电源,不同的逻辑电平就不会成为问题(在这种特殊情况下,请注意这不是一般声明,如果您混合逻辑电平,其他设备可能会出现故障或最终损坏)。

我故意还没有在 tx/rx 线上连接任何电阻用于上拉/下拉。教程不建议它们。

对于内部项目,您很可能不需要将任何终端电阻器连接到总线上。对于长途巴士(在设备可能相距数百米的工厂或设施中),您可能会担心这个问题。你的微型收发器实际上已经包含了这些终端电阻,所以最好不要增加更多的电阻。对于您的电缆,我没有足够的耐心找到手册(我不知道是否有手册;我有一条类似的电缆,唯一确定的方法是取下盖子并查看其引擎盖下)。

一切就绪并运行后,请注意在您的客户端上:

print(rr)

应该:

print(rr.registers)

如果您想要显示您已阅读的值。

于 2019-07-07T16:09:23.620 回答
2

正如上面的 Marcos G. 所建议的那样,我是否修改了 pymodbus 来控制选择的 GPIO。

我选择了软件解决方案,因为我现在只需要一些快速工作的东西,而无需订购新硬件并等待。我稍后会找到合适/更好的硬件。

修改pymodbus的软件解决方案

在文件夹“client”中找到文件“sync.py”,以修改设置的客户端/主端。

我在这里修改了客户端/主控,因为那一侧的 RS485 硬件很“差”。如果你有两个“差”的硬件,你可能也需要修改服务器端。

文件 sync.py 可能位于

~/.local/lib/python3.5/site-packages/pymodbus/client

这可能会根据您使用的 python 版本而有所不同。我的现在是3.5。“~/”部分表示它在您的主文件夹中。“local”前面的点使文件隐藏为标准。在终端中,您也可以使用命令“ls -al”来显示隐藏文件。Linux 发行版的图形用户界面肯定也能以某种方式显示隐藏文件。

在文件“sync.py”的开头,添加以下代码:

import RPi.GPIO as GPIO
pin_de_re = 7
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin_de_re, GPIO.OUT, initial=GPIO.HIGH)

这可能看起来像下面这样:

more imports ...

from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.client.common import ModbusClientMixin

import RPi.GPIO as GPIO
pin_de_re = 7
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(pin_de_re, GPIO.OUT, initial=GPIO.HIGH)

# --------------------------------------------------------------------------- #
# Logging
# --------------------------------------------------------------------------- #
import logging
_logger = logging.getLogger(__name__)

...more code

根据您的选择设置密码。我的控制引脚为 GPIO4 - 即 Raspberry Pi/BananaPi 中的引脚 7。

接下来,您向下滚动并找到名为

# --------------------------------------------------------------------------- #
# Modbus Serial Client Transport Implementation
# --------------------------------------------------------------------------- #

我修改了这一部分,因为我使用 Modbus RTU 并因此使用串行传输数据。

在该部分中,您必须找到“发送”的定义:

    def _send(self, request):
        """ Sends data on the underlying socket

在该函数中,找到以下行:

            size = self.socket.write(request)

并通过引脚的控制来拥抱它:

            _logger.debug("GPIO - Setting pin high")
            GPIO.output(pin_de_re, 1)
            time.sleep(.300)
            size = self.socket.write(request)
            time.sleep(.300)
            _logger.debug("GPIO - Setting pin low")
            GPIO.output(pin_de_re, 0)

我使用 '_logger.debug("GPIO - Setting pin high/low")' 行的原因是我可以在终端的日志中看到,程序执行这些事情,如果它们是,我可以放心执行。如果他们没有出现在日志中,我已经在错误的地方完成了 - 或者其他什么......

使用 time.sleep(.300) 的原因是让硬件有时间行动。.300 是 0.3 秒。在这种情况下是一个很大的数字。

当我使用上述解决方案时,我得到以下日志。

从机/服务器:

2019-07-07 23:08:43,532 MainThread      DEBUG    sync           :45       Client Connected [/dev/ttyUSB0:/dev/ttyUSB0]
2019-07-07 23:08:43,533 MainThread      DEBUG    sync           :522      Started thread to serve client
2019-07-07 23:08:47,534 MainThread      DEBUG    rtu_framer     :232      Frame check failed, ignoring!!
2019-07-07 23:08:47,535 MainThread      DEBUG    rtu_framer     :128      Resetting frame - Current Frame in buffer - 0x3 0x4 0x0 0x1 0x0 0x82
2019-07-07 23:08:59,543 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x0 0x1 0x0 0x2
2019-07-07 23:08:59,544 MainThread      DEBUG    factory        :137      Factory Request[ReadInputRegistersRequest: 4]
2019-07-07 23:08:59,544 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 23:08:59,544 MainThread      DEBUG    context        :64       validate: fc-[4] address-2: count-2
2019-07-07 23:08:59,544 MainThread      DEBUG    context        :78       getValues fc-[4] address-2: count-2
2019-07-07 23:08:59,545 MainThread      DEBUG    sync           :143      send: [ReadRegisterResponse (2)]- b'030404000500050846'

主/客户:

ModbusSerialClient(rtu baud[115200])
2019-07-07 23:08:55,839 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:165      ===================================
2019-07-07 23:08:55,840 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:166      Read input registers
2019-07-07 23:08:55,841 MainThread      DEBUG    pymodbus_sync_client_example_2019.07.05-1319:167      
2019-07-07 23:08:55,842 MainThread      DEBUG    transaction    :111      Current transaction state - IDLE
2019-07-07 23:08:55,842 MainThread      DEBUG    transaction    :116      Running transaction 1
2019-07-07 23:08:55,843 MainThread      DEBUG    transaction    :215      SEND: 0x3 0x4 0x0 0x1 0x0 0x2 0x21 0xe9
2019-07-07 23:08:55,843 MainThread      DEBUG    sync           :79       New Transaction state 'SENDING'
2019-07-07 23:08:55,844 MainThread      DEBUG    sync           :538      GPIO - Setting pin high
2019-07-07 23:08:55,845 MainThread      DEBUG    sync           :541      GPIO - Setting pin low
2019-07-07 23:08:55,845 MainThread      DEBUG    transaction    :224      Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
2019-07-07 23:08:59,516 MainThread      DEBUG    transaction    :300      Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
2019-07-07 23:08:59,518 MainThread      DEBUG    transaction    :229      RECV: 0x3 0x4 0x4 0x0 0x5 0x0 0x5 0x8 0x46
2019-07-07 23:08:59,519 MainThread      DEBUG    rtu_framer     :180      Getting Frame - 0x4 0x4 0x0 0x5 0x0 0x5
2019-07-07 23:08:59,519 MainThread      DEBUG    factory        :266      Factory Response[ReadInputRegistersResponse: 4]
2019-07-07 23:08:59,520 MainThread      DEBUG    rtu_framer     :115      Frame advanced, resetting header!!
2019-07-07 23:08:59,521 MainThread      DEBUG    transaction    :379      Adding transaction 3
2019-07-07 23:08:59,522 MainThread      DEBUG    transaction    :390      Getting transaction 3
2019-07-07 23:08:59,522 MainThread      DEBUG    transaction    :189      Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
ReadRegisterResponse (2)

传输可能并非总是如此,但它可以查明问题的原因并因此确定潜在的解决方案。

我还不知道我最终会得到什么。更稳定的硬件是肯定的。

关于针对此问题修改 pymodbus 或其他软件,我想在另一个威胁中引用以下帖子

任何在多任务操作系统(如 linux 或 windows)上运行 modbus 的人都永远无法满足串行规范的要求,对此没有争议,任务通常是 10ms,因此满足 3.5us 时序要求是不合适的,也永远不会.

硬件方面的解决方案是可取的。

感谢马科斯 G。

于 2019-07-07T21:23:01.830 回答