<p>大约在同一时间,我面临着同样的问题-选择哪个库来实现python modbus主程序,但在我的情况下选择串行通信(modbus RTU),因此我的观察结果仅对modbus RTU有效。</p>
<p>在我的考试中,我没有太多的注意文档,但串行RTU主机的例子是最容易找到modbus tk,但仍然在源代码中,而不是在wiki等</p>
<h2>长话短说:</h2>
<h3>最小modbus:</h3>
<ul>
<li>优点:
<ul>
<li>轻量级模块</li>
<li>对于读取~10个寄存器的应用程序,性能可以接受</li>
</ul></li>
<li>缺点:
<ul>
<li>读取~64个寄存器时(对于我的应用程序)速度慢得令人无法接受</li>
<li>CPU负载相对较高</li>
</ul></li>
</ul>
<h3>pymodbus:</h3>
<p>区别特征:依赖于串行流(<a href="https://groups.google.com/forum/#!msg/pymodbus/k2ZpTw-3BOE/M9oQ1HHWOS0J">post by the author</a>),并且必须动态设置串行超时,否则性能将很低(必须针对可能的最长响应调整串行超时)</p>
<ul>
<li>优点:
<ul>
<li>低CPU负载</li>
<li>可接受的性能</li>
</ul></li>
<li>缺点:
<ul>
<li>即使动态设置了超时,性能也比modbus tk低2倍;如果将超时设置为常量,性能会差得多(但查询时间是常量)</li>
<li>对硬件敏感(我认为是由于对来自串行缓冲区的处理流的依赖)或事务可能存在内部问题:如果每秒执行不同的读取或读/写大约20次或更多,则可能会得到混淆的响应。较长的超时有助于但并不总是使pymodbus RTU在串行线路上的实现不足以在生产中使用。</li>
<li>添加对动态串行端口超时设置的支持需要额外的编程:继承基本同步客户端类并实现套接字超时修改方法</li>
<li>响应验证不像modbus tk那样详细。例如,在总线衰减的情况下,只抛出异常,而modbus tk在相同的情况下返回错误的从机地址或CRC错误,这有助于确定问题的根本原因(可能是超时时间太短、错误的总线终止/缺少或浮地等)</li>
</ul></li>
</ul>
<h3>modbus tk:</h3>
<p>特点:探测串行缓冲区数据,快速汇编和返回响应。</p>
<ul>
<li>专业人士
<ul>
<li>最佳性能;比动态超时的pymodbus快2倍</li>
</ul></li>
<li>缺点:
<ul>
<li>与pymodbus相比,大约高出4倍的CPU负载//<strong>可以大大提高,从而使这一点无效;请参见末尾的编辑部分</strong></li>
<li>较大请求的CPU负载增加//<strong>可以大大改进,使这一点无效;请参阅末尾的编辑部分</strong></li>
<li>代码没有pymodbus优雅</li>
</ul></li>
</ul>
<p>6个多月以来,我一直在使用pymodbus,因为它具有最佳的性能/CPU负载比,但在较高的请求率下,不可靠的响应成为一个严重问题,最终我转向了更快的嵌入式系统,并增加了对modbus tk的支持,这对我来说是最好的。</p>
<h2>对细节感兴趣的人</h2>
<p>我的目标是实现最短的响应时间。</p>
<h3>设置:</h3>
<ul>
<li>波特率:153600
<ul>
<li>与实现modbus从机的微控制器16MHz时钟同步)</li>
<li>我的rs-485总线只有50米</li>
</ul></li>
<li>FTDI FT232R转换器和串行TCP网桥(在RFC2217模式下使用com4com作为网桥)</li>
<li>在USB到串行转换器的情况下,为串行端口配置的最低超时和缓冲区大小(以降低延迟)</li>
<li>自动发送rs-485适配器(总线处于主导状态)</li>
</ul>
<h3>用例场景:</h3>
<ul>
<li>每秒轮询5、8或10次,支持在两次之间进行异步访问</li>
<li>读/写10到70个寄存器的请求</li>
</ul>
<h3>典型的长期(周)表现:</h3>
<ul>
<li>MinimalModbus:初始测试后丢弃ts公司</li>
<li>pymodbus:读取64个寄存器大约30毫秒;有效地达到30个请求/秒
<ul>
<li>但是响应不可靠(在多线程同步访问的情况下)</li>
<li>github上可能有一个线程安全的fork,但它在master后面,我还没有尝试过(<a href="https://github.com/xvart/pymodbus/network">https://github.com/xvart/pymodbus/network</a>)</li>
</ul></li>
<li>modbus tk:读取64个寄存器的时间约为16ms;对于较小的请求,有效时间高达70-80个请求/秒</li>
</ul>
<h3>基准</h3>
<p><strong>代码:</strong></p>
<pre><code>import time
import traceback
import serial
import modbus_tk.defines as tkCst
import modbus_tk.modbus_rtu as tkRtu
import minimalmodbus as mmRtu
from pymodbus.client.sync import ModbusSerialClient as pyRtu
slavesArr = [2]
iterSp = 100
regsSp = 10
portNbr = 21
portName = 'com22'
baudrate = 153600
timeoutSp=0.018 + regsSp*0
print "timeout: %s [s]" % timeoutSp
mmc=mmRtu.Instrument(portName, 2) # port name, slave address
mmc.serial.baudrate=baudrate
mmc.serial.timeout=timeoutSp
tb = None
errCnt = 0
startTs = time.time()
for i in range(iterSp):
for slaveId in slavesArr:
mmc.address = slaveId
try:
mmc.read_registers(0,regsSp)
except:
tb = traceback.format_exc()
errCnt += 1
stopTs = time.time()
timeDiff = stopTs - startTs
mmc.serial.close()
print mmc.serial
print "mimalmodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
print " !mimalmodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb)
pymc = pyRtu(method='rtu', port=portNbr, baudrate=baudrate, timeout=timeoutSp)
errCnt = 0
startTs = time.time()
for i in range(iterSp):
for slaveId in slavesArr:
try:
pymc.read_holding_registers(0,regsSp,unit=slaveId)
except:
errCnt += 1
tb = traceback.format_exc()
stopTs = time.time()
timeDiff = stopTs - startTs
print "pymodbus:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
print " !pymodbus:\terrCnt: %s; last tb: %s" % (errCnt, tb)
pymc.close()
tkmc = tkRtu.RtuMaster(serial.Serial(port=portNbr, baudrate=baudrate))
tkmc.set_timeout(timeoutSp)
errCnt = 0
startTs = time.time()
for i in range(iterSp):
for slaveId in slavesArr:
try:
tkmc.execute(slaveId, tkCst.READ_HOLDING_REGISTERS, 0,regsSp)
except:
errCnt += 1
tb = traceback.format_exc()
stopTs = time.time()
timeDiff = stopTs - startTs
print "modbus-tk:\ttime to read %s x %s (x %s regs): %.3f [s] / %.3f [s/req]" % (len(slavesArr),iterSp, regsSp, timeDiff, timeDiff/iterSp)
if errCnt >0:
print " !modbus-tk:\terrCnt: %s; last tb: %s" % (errCnt, tb)
tkmc.close()
</code></pre>
<p><strong>结果:</strong></p>
<pre><code>platform:
P8700 @2.53GHz
WinXP sp3 32bit
Python 2.7.1
FTDI FT232R series 1220-0
FTDI driver 2.08.26 (watch out for possible issues with 2.08.30 version on Windows)
pymodbus version 1.2.0
MinimalModbus version 0.4
modbus-tk version 0.4.2
</code></pre>
<p>读取100 x 64寄存器:</p>
<p>不节电</p>
<pre><code>timeout: 0.05 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 9.135 [s] / 0.091 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 6.151 [s] / 0.062 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.280 [s] / 0.023 [s/req]
timeout: 0.03 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 7.292 [s] / 0.073 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 3.170 [s] / 0.032 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]
timeout: 0.018 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 4.481 - 7.198 [s] / 0.045 - 0.072 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 3.045 [s] / 0.030 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]
</code></pre>
<p>最大节电</p>
<pre><code>timeout: 0.05 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 10.289 [s] / 0.103 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 6.074 [s] / 0.061 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.358 [s] / 0.024 [s/req]
timeout: 0.03 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 8.166 [s] / 0.082 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 4.138 [s] / 0.041 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.327 [s] / 0.023 [s/req]
timeout: 0.018 [s]
Serial<id=0xd57330, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 64 regs): 7.776 [s] / 0.078 [s/req]
pymodbus: time to read 1 x 100 (x 64 regs): 3.169 [s] / 0.032 [s/req]
modbus-tk: time to read 1 x 100 (x 64 regs): 2.342 [s] / 0.023 [s/req]
</code></pre>
<p>读取100 x 10寄存器:</p>
<p>不节电</p>
<pre><code>timeout: 0.05 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 6.246 [s] / 0.062 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 6.199 [s] / 0.062 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.577 [s] / 0.016 [s/req]
timeout: 0.03 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 3.088 [s] / 0.031 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 3.143 [s] / 0.031 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req]
timeout: 0.018 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 3.066 [s] / 0.031 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 3.006 [s] / 0.030 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.533 [s] / 0.015 [s/req]
</code></pre>
<p>最大节电</p>
<pre><code>timeout: 0.05 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.05, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 6.386 [s] / 0.064 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 5.934 [s] / 0.059 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.499 [s] / 0.015 [s/req]
timeout: 0.03 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.03, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 3.139 [s] / 0.031 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 3.170 [s] / 0.032 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.562 [s] / 0.016 [s/req]
timeout: 0.018 [s]
Serial<id=0xd56350, open=False>(port='com22', baudrate=153600, bytesize=8, parity='N', stopbits=1, timeout=0.018, xonxoff=False, rtscts=False, dsrdtr=False)
mimalmodbus: time to read 1 x 100 (x 10 regs): 3.123 [s] / 0.031 [s/req]
pymodbus: time to read 1 x 100 (x 10 regs): 3.060 [s] / 0.031 [s/req]
modbus-tk: time to read 1 x 100 (x 10 regs): 1.561 [s] / 0.016 [s/req]
</code></pre>
<h3>实际应用:</h3>
<p>modbus rpc网桥的负载示例(约3%由rpc服务器部分引起)</p>
<ul>
<li><p>5 x 64寄存器每秒同步读取和同时读取</p></li>
<li><p>串行端口超时设置为0.018s的异步访问</p>
<ul>
<li><p>modbus tk协议</p>
<ul>
<li>10条规则:{'currentCpuUsage':20.6,'requestsPerSec':73.2}//<strong>可以改进;请参见下面的编辑部分</strong></li>
<li>64条规则:{'currentCpuUsage':31.2,'requestsPerSec':41.91}//<strong>可以改进;请参见下面的编辑部分</strong></li>
</ul></li>
<li><p>pymodbus:</p>
<ul>
<li>10条规则:{'currentCpuUsage':5.0,'requestsPerSec':36.88}</li>
<li>64 regs:{'currentCpuUsage':5.0,'requestsPerSec':34.29}</li>
</ul></li>
</ul></li>
</ul>
<p><strong>编辑:</strong>可以方便地改进modbus tk库以减少CPU使用。
在原始版本中,在发送请求后,T3.5睡眠通过主机一次汇编一个字节的响应。分析证明大部分od时间都花在串行端口访问上。这可以通过尝试从串行缓冲区读取预期长度的数据来改进。根据<a href="http://pyserial.sourceforge.net/pyserial_api.html?highlight=read#serial.Serial.read">pySerial documentation</a>的说法,如果设置了超时,则应该是安全的(响应丢失或太短时不会挂断):</p>
<pre><code>read(size=1)
Parameters: size – Number of bytes to read.
Returns: Bytes read from the port.
Read size bytes from the serial port. If a timeout is set it may return less characters as
requested. With no timeout it will block until the requested number of bytes is read.
</code></pre>
<p>按以下方式修改“modbus_rtu.py”后:</p>
<pre><code>def _recv(self, expected_length=-1):
"""Receive the response from the slave"""
response = ""
read_bytes = "dummy"
iterCnt = 0
while read_bytes:
if iterCnt == 0:
read_bytes = self._serial.read(expected_length) # reduces CPU load for longer frames; serial port timeout is used anyway
else:
read_bytes = self._serial.read(1)
response += read_bytes
if len(response) >= expected_length >= 0:
#if the expected number of byte is received consider that the response is done
#improve performance by avoiding end-of-response detection by timeout
break
iterCnt += 1
</code></pre>
<p>修改modbus tk后,实际应用程序中的CPU负载显著下降,而没有显著的性能损失(仍优于pymodbus):</p>
<p>modbus rpc网桥的更新负载示例(约3%是由rpc服务器部分引起的)</p>
<ul>
<li><p>5 x 64寄存器每秒同步读取和同时读取</p></li>
<li><p>串行端口超时设置为0.018s的异步访问</p>
<ul>
<li><p>modbus tk协议</p>
<ul>
<li>10条规则:{'currentCpuUsage':7.8,'requestsPerSec':66.81}</li>
<li>64个regs:{'currentCpuUsage':8.1,'requestsPerSec':37.61}</li>
</ul></li>
<li><p>pymodbus:</p>
<ul>
<li>10条规则:{'currentCpuUsage':5.0,'requestsPerSec':36.88}</li>
<li>64 regs:{'currentCpuUsage':5.0,'requestsPerSec':34.29}</li>
</ul></li>
</ul></li>
</ul>