【问题标题】:pylibftdi Device.read skips some bytespylibftdi Device.read 跳过一些字节
【发布时间】:2019-08-21 13:01:56
【问题描述】:

我有一个 FPGA,它通过 FT2232H 在 USB 总线上传输数据,我观察到大约 10% 的数据必须丢弃,因为帧中的某些字节丢失了。以下是技术细节:

  • FPGA 是 Artix 7。每 9 ms 准备好一批 4002 字节。这样算出 444,667 字节/秒的数据。
  • 我的笔记本电脑在 Ubuntu 18.04LTS 上运行 python 3.7(来自 anaconda)
  • FPGA/FT2232H 通过以下初始化行打开:
SYNCFF = 0x40
SIO_RTS_CTS_HS = (0x1 << 8)
self.device = pylibftdi.Device(mode='t', interface_select=pylibftdi.INTERFACE_A, encoding='latin1')
self.device.ftdi_fn.ftdi_set_bitmode(0xff, SYNCFF)
self.device.ftdi_fn.ftdi_read_data_set_chunksize(0x10000)
self.device.ftdi_fn.ftdi_write_data_set_chunksize(0x10000)
self.device.ftdi_fn.ftdi_setflowctrl(SIO_RTS_CTS_HS)
self.device.flush()
  • 然后通过这个简单的行读取数据:

raw_usb_data = my_fpga.device.read(0x10000)

我观察到以下情况:

  1. 每批我总是得到0x10000 的数据,这正是我所期望的。
  2. 使用 device.read 一次读取 2**16 = 65,536 字节应该需要 147.4 毫秒,因为每 9 毫秒就准备好一批。但是对该线的计时给出了 143 毫秒的平均值,标准偏差为 6.6 毫秒。

我的第一个猜测是某处没有缓冲区/很小的缓冲区,并且由于操作系统(优先级问题?)或 python(垃圾收集?)在某个时间点做其他事情太久而丢失了一些信息。

如何减少读取设备时丢失的字节数?

【问题讨论】:

  • FPGA 是否发送二进制数据,但您告诉Device 处于文本模式?
  • 是的,好点。 FPGA 发送二进制数据,由 Device 以文本模式读取,然后转换为bytes 进行处理。我需要更新它,但我可以确认当设备设置为二进制模式时我遇到了同样的问题。很抱歉造成混乱!
  • FT2232H 具有 4kB 内部缓冲器。你很有可能受到它们的限制。对于我的应用程序,我使用 pyserial 和一个子进程来收集数据并通过管道将它们发送到主进程以解决 GIL。管道本身在 Windows 下有一个约 8kb 的内部缓冲区,因此有时也必须对其进行调整。不确定 plibftdi 在内部做什么,以及切换到普通 VCP 是否适合您。
  • 啊,“偏差”是正常的,因为我们正在谈论 USB。它的行为基本上就像一个“面向共享数据包的管道”,具有草率的时序保证。可以通过更改驱动程序的延迟设置(至少在 Windows 下)来减少延迟,但永远不会得到它。
  • 啊!我不知道这一切。谢谢你提供的详情。你有链接到我可以用来开始与人打交道的示例代码吗?我从未使用过那个包,但如果我能绕过 GIL 并最终能够停止丢弃字节,那就太好了!

标签: python fpga ftdi


【解决方案1】:

FT2232H 具有容量约为 4 kbits 的内部 FIFO 缓冲区。你很有可能受到它们的限制。不确定 pylibftdi 如何处理它们,但如果您可以使用 VCP 驱动程序,则使用替代方法可能会起作用。这允许您将 FT2232H 寻址为标准端口,例如通过pyserial。

我的一个项目的一些摘录实际上适用于 >12 Mbps 的波特率(UART 限制为 12 Mbps,但例如快速光电可以达到 ~25 Mbps):

import traceback
import serial
import serial.tools.list_ports
import multiprocessing
import multiprocessing.connection

def IO_proc(cntr_pipe, data_pipe):
    try:
        search_str="USB VID:PID=0403:6010 SER="
        ports     = [x.device for x in serial.tools.list_ports.comports() if search_str in x.hwid]
        baud_rate = 12000000 #only matters for uart and not for fast opto or fifo mode
        ser       = serial.Serial(port, baud_rate)

    while not cntr_pipe.closed:
        time.sleep(0)
        in_data = ser.read(ser.inWaiting())

        [...do some pattern matching, package identification etc...]

        data_pipe.send_bytes(in_data)

        except EOFError:
            ret_code = 2
        except Exception as e:
            cntr_pipe.send(traceback.format_exc())
            cntr_pipe.close()
            ret_code = 4
        finally:
            cntr_pipe.close()
            ser.close()

multiprocessing.connection.BUFSIZE = 2 ** 20 #only required for windows
child_cntr, parent_cntr = multiprocessing.Pipe()
child_data, parent_data = multiprocessing.Pipe()
process                 = multiprocessing.Process(target = IO_proc, args=(child_cntr, child_data))

#called frequently
def update():
    if child_cntr.poll():
        raise Exception("error",child_cntr.recv())

        buf = bytes()

        while parent_data.poll():
            buf += parent_data.recv_bytes()

    [...do something fancy...]

我试图 c&p 一个最小的例子。它未经测试,所以如果它不能开箱即用,请原谅我。要使这个工作真正需要确保加载 VCP 而不是 D2XX 驱动程序。

P.S:实际上,在扫描我的文件时,我意识到 pylibftdi 方式应该和我使用“装饰器”类一样好,以防加载 D2XX 驱动程序:

try:    import pylibftdi
except: pylibftdi = None


class pylibftdi_device:
    def __init__(self,speed):
        self.dev = pylibftdi.Device(interface_select=2)
        self.dev.baudrate = speed
        self.buf = b''

    def write(self, data):
        self.dev.write(data)

    def read(self, bytecount):
        while bytecount > len(self.buf):
            self._read()

        ret      = self.buf[:bytecount]
        self.buf = self.buf[bytecount:]
        return ret

    def flushInput(self):
        self.dev.flush_input()#FT_PURGE_RX
        self.buf = b''

    def _read(self):
        self.buf += self.dev.read(2048)

    @property
    def in_waiting(self):
        self._read()
        return len(self.buf)

    def close(self):
        self.dev.close()


def find_device_UART(baudrate=12000000,index=1, search_string="USB VID:PID=0403:6010 SER="):
    if pylibftdi:
        return pylibftdi_device(baudrate),"pylibftdi_device"
    try:
        ports = [x.device for x in serial.tools.list_ports.comports() if search_string in x.hwid]
        module_logger.info(str(ports))
        if len(ports) == 0:
            return None,"no device found"
        else:
            ser = serial.Serial(ports[index],baudrate)
            return ser,"found device %s %d"%(ser.name,ser.baudrate)
    except serial.SerialException as e:
        return None,"error during device detection - \n"+str(e)

因此,与您的示例的主要区别在于更频繁地读取 recv 缓冲区并将其放入缓冲区,然后稍后搜索数据包。也许这对您的应用程序来说完全是多余的,您只需要进行较小的读取调用以确保缓冲区永远不会溢出。

【讨论】:

  • 这听起来像是一个计划,非常感谢示例代码!您是否预见到这两种方法的性能差异?
  • 根据我的经验,主要限制来自 FT2232H 的相对较小的内部缓冲区以及驱动程序缺乏缓冲更高的数量。因此,频繁和定期的阅读至关重要。因此,既然您已经启动并运行了 pylibftdi,我很可能会坚持使用它。
  • 好的,我下周试试,看看能减少多少丢帧。
  • 我尝试了几个选项,但结果好坏参半。首先,我将代码的读取部分移至通过管道进行通信的单独进程。这个过程唯一要做的就是读取 FPGA。其次,我尝试在短时间内非常频繁地读取 FPGA,并将其放入缓冲区以供以后处理 -> 它没有帮助,非常短的读取实际上可能使情况变得更糟。所以我的下一个选择是给 pyserial 一个机会,但我怀疑 pylibftdi 是垃圾:我一定在这里忽略了一些东西。
  • 虽然不是一切都很糟糕:代码现在看起来更干净了
猜你喜欢
  • 1970-01-01
  • 2011-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-20
  • 1970-01-01
相关资源
最近更新 更多