Python串口读取速度极慢,如何加速?

时间:2017-11-11 07:10:10

标签: python performance

我正在使用以下代码与Digital Multi Meter(DMM)进行通信 它工作正常。我可以发送命令和读取结果。 我不使用readline,因为我读了二进制数据。

问题:

问题是非常慢。 用Ruby编写的相同代码要快得多。 当在python中花费30秒时,红宝石需要2或3秒(使用相同的速度)。 所以这不是硬件问题 Ruby代码和Python之间的唯一区别是在Python中我使用inWaiting()来读取所有可用的字符。在Ruby中,read()函数读取所有这些而不仅仅是一个。

代码:

这是代码: 在read_retry函数中,我检查我必须读取多少个字符。 我读了它们,然后调用data_is_ok函数来检查它是否已经完成 如您所见,返回的数据中嵌入了'\ r'。 当最后一个字符是'\ r'(没有更多可用数据)时,读取完成 所以有一个循环来读取大量的块。

import serial
[...]

def data_is_ok(data):
  # No status code yet
  if len(data) < 2: return False

  # Non-OK status
  if len(data) == 2 and data[0] != "0" and data[1] == "\r": return True

  # Non-OK status with extra data on end
  if len(data) > 2 and data[0] != "0":
    raise ValueError('Error parsing status from meter (Non-OK status with extra data on end)')

  # We should now be in OK state
  if data[0] != "0" or data[1] != "\r":
    raise ValueError('Error parsing status from meter (status:%c size:%d)' % (data[0], len(data)))

  return len(data) >= 4 and data[-1] == "\r"

def read_retry():
  retry_count = 0
  data = ""

  while retry_count < 500 and not data_is_ok(data):
    bytesToRead = ser.inWaiting()
    data += ser.read(bytesToRead)
    if data_is_ok(data): return data
    time.sleep (0.001)
    retry_count += 1
  raise ValueError('Error parsing status from meter:  %c %d %r %r' % (data[0],len(data),data[1] == '\r', data[-1] == '\r'))

[...]

# Serial port settings
try:
  ser = serial.Serial(port='/dev/cu.usbserial-AK05FTGH', baudrate=115200, bytesize=8, parity='N', stopbits=1, timeout=5, rtscts=False, dsrdtr=False)
except serial.serialutil.SerialException, err:
  print "Serial Port /dev/cu.usbserial-AK05FTGH doesn't respond"
  print err
  sys.exit()

[...]

ser.write(cmd+'\r')
data = read_retry()

我使用过cProfile profiler。大多数时间花在time.sleep

这是一个摘录:

         363096 function calls (363085 primitive calls) in 28.821 seconds

   Ordered by: internal time
   List reduced from 127 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    19050   25.245    0.001   25.245    0.001 {time.sleep}
        1    1.502    1.502    1.502    1.502 {posix.open}

问题:
是否可以使代码更快?

3 个答案:

答案 0 :(得分:3)

你可以做几件事。

您不应该需要time.sleep()。如果您知道只需要2个字节,那么请ser.read(2),如果您想限制等待时间ser.timeout = 0.01 编辑,除非您在一个单独的主题中。 Python线程贪婪。 I / O操作释放线程,以便主线程可以运行。但是,在您的情况下,您始终读取缓冲区ser.read(ser.inWaiting())中的数据。我发现你需要强制串口等待I / O ser.read(ser.inWaiting() + 32)。只需确保您也有超时,这样您就不会永远等待I / O.此外,您当前的超时是5秒,这是一个很长的等待时间。 结束编辑

您可以使用readline ser.readline()这可能只读取'\ r \ n'。我不确定。

如果您使用的是Python3,那么ser.read()将返回字节,因此任何比较都将为false。 data[0] == '0'需要data[0:1] == b'0'

另一件事是通过调试器运行代码以确保获得您期望的数据。字符串比较可能是错误的,这会让你不必要地循环多次。

答案 1 :(得分:2)

我遇到了同样的问题,即在使用serial.readline方法时pySerial库的读取速度非常慢。

我正在运行Windows 7,并尝试了pySerial的不同版本,从2.7到3.4。

让我展示我的关注。发送器以0.02 = 1的波特率发送每0.02s的新数据。

如果我执行以下操作

import serial

if __name__ == '__main__':

    serial_port = serial.Serial('COM26', 115200)   

    while(True):            
        print(serial_port.is_open)
        serial_port.readline()        
        print(serial_port.in_waiting)    

我看到缓冲区中的字节数正在增加。写入缓冲区要快于读取。

以下代码段显示了此问题的

解决方案,该代码段必须使用serial.read方法并一次读取所有缓冲的字节。如果我执行这个

import serial

if __name__ == '__main__':

    serial_port = serial.Serial('COM26', 115200)

    while(True):        
        print(serial_port.is_open)
        serial_port.read(serial_port.in_waiting)                   
        print(serial_port.in_waiting)

然后我看到serial_port.read(serial_port.in_waiting)可以非常快速地读取,并且避免了缓冲区中字节累积的问题。

我现在对解决方案还可以,但是也许有人可以解释为什么这样做。

谢谢。

答案 2 :(得分:0)

read_retry()中的

尝试使用超时而不是重试计数

import time

def read_retry():
  timeout = 500 * 0.001
  data = ""
  tic = time.time()
  while toc - tic < timeout and not data_is_ok(data):
    bytesToRead = ser.inWaiting()
    data += ser.read(bytesToRead)
    if data_is_ok(data): return data
    toc = time.time()
  raise ValueError('Error parsing status from meter:  %c %d %r %r' % (data[0],len(data),data[1] == '\r', data[-1] == '\r'))