1

我在 Windows 上使用 FTDI D2xx 驱动程序在 BitBash 模式下运行 tcl8.5/tk8.5 应用程序(问题发生在 XP、7 和 8 上)。还使用 ftd2xx c 扩展来访问 FTDI dll。

我有一个用户的报告,该应用程序最初运行良好,但在一天不使用后,在后台运行,它突然从 5M 增长到 100M 并开始消耗 99% 的 cpu。(情况不妙!)

在此之前我遇到过 USB 问题,特别是如果 USB 是“热拔出”的。它可能会导致应用程序被阻止并且 Windows 无法杀死它。我的应用程序从不需要读取 USB,它只是写入(控制它),但我发现在 BitBash 模式下,FTDI 芯片会发送回连续的数据流。处理读取会清除读取缓冲区,因此如果 USB 被拔出,则没有待阻止的读取,我可以优雅地退出。

但是现在,我认为 readhandler 给我带来了问题。我编写了一小段测试代码,模仿了我认为可以解决问题根源的实际应用程序。无论如何,我不理解 tcl 的行为。

这是代码:

    package require -exact ftd2xx 1.2.1

    # define a read handler
    # gets called by a fileevent readable
    #
    proc readchan {} {
        variable cnt
        set len [gets $::handle buf]

        # buffer is non zero

        if {$len > 0} {
            incr cnt
            set end [eof $::handle]
            puts "$len ($cnt) eof $end"; 
            # if it wasn't an eof output the buffer
            if {!$end} {
                    puts $buf
            }
        } elseif {$len == 0} {
            # was a zero length read
            puts -nonewline "0"
            set end [eof $::handle]
            if { !$end } {
                    # eof makes the $len invalid?
                    puts "-0"
                    #puts "\nlen is 0 and eof is $end - exit!";
                    #exit
            }
        } else {
            # len was negative (-1) so data in buffer but no end of line (in binary mode)
            if { [eof $::handle] } {
                    puts "EOF w/len 0"
            } else {
                    puts -nonewline "."
            }
        }
}

#  main code 
#
# find the usb device and open it
set usb [ftd2xx list]
lassign $usb d
lassign $d d id e loc f serial
puts "found USB $serial"
set handle [ftd2xx open -serial $serial]
puts "Opened USB $handle"
#
# configure it for bitbash mode and for binary, non blocking
#
set bitmode 0xFF01
chan configure $handle -tranlation binary -bitmode $bitmode -blocking 0
fileevent $handle readable readchan

# output something to the usb every 100ms or so required to cause failure
#
while {1} {
    set continue 0
    after 100 {set continue 1}

    # putting:
    # "a" made it crash after 245 "timeouts" - no flush
    # null made it past 255 - no flush
    # "aa" made it crash after 164 "timeouts - no flush

    puts $handle "aa"

    #flushing $handle makes it crash after first flush
    #flush $handle
    puts -nonewline "w"
    vwait continue
}

以下是上述代码的输出:

$ tclsh85 usbfailtest.tcl
found USB AH009L40
Opened USB ftd2xx0
w...w....w....w....w....w....w...w....w.126976 (1) eof 1  <<< 126K length with eof true
..w....w....w....w...w....w....w....w..126976 (2) eof 1   <<< "w" output for usb write
.w....w....w...w....w....w....w....w...126976 (3) eof 1   <<< this is 3rd non-zero read
w...w....w....w....w....w...w....w....w.126976 (4) eof 1  <<< "." output when len -1
..w....w...w....w....w....w....w...w...126976 (5) eof 1   <<< line takes about 1 sec 
w....w....w....w....w...w....w....w....w126976 (6) eof 1
.
. (output skipped)
.
.w....w....w....w...w....w....w....w...126976 (156) eof 1
w....w....w...w....w....w....w....w....w126976 (157) eof 1
..w....w....w....w....w....w...w....w..126976 (158) eof 1
.w....w....w...w....w....w....w....w...126976 (159) eof 1
w....w...w....w....w....w....w....w...w.126976 (160) eof 1
..w....w....w....w....w...w....w....w..126976 (161) eof 1
.w....w....w....w...w....w....w....w...126976 (162) eof 1
w....w...w....w....w....w....w....w...w.126976 (163) eof 1
..w....w....w....w....w...w....w102695 (164) eof 0  << a 102K buffer with EOF false
aa  << all but two of the 102K buffer were null?
0-0 << zero length buffer with no eof
2 (165) eof 0
aa  << two length buffer, no eof, and we got two chars
3 (166) eof 0
aaa << three length bufffer, no eof, and we got three chars
2 (167) eof 0
aa  << etc.
0-0  << another zero zero  these scroll out *very* fast.
0-0
0-0
0-0
2 (168) eof 0
aa  << an so forth
0-0
0-0
.
. (output skipped)
.
43 (268) eof 0
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
..w...22192 (269) eof 1
w...w....w....w....w....w...w....w....w.126976 (270) eof 1  << revert to "slow" mode
..w....w....w....w...w....w....w....w..126976 (271) eof 1
.w....w....w...w....w....w....w....w...126976 (272) eof 1
w....w...w....w....w....w....w....w...w.126976 (273) eof 1
..w....w....w....w....w...w....w....w..126976 (274) eof 1

输出说明:

首先,读取处理程序被多次调用(每个“.”),缓冲区不完整(长度 -1)并且没有 eof 条件。最终,某些事情要么超时,要么达到内部缓冲区限制,读取被迫完成。同时提出了一个EOF。“...126987”的每一行需要大约一秒钟的时间来写。

在一定数量的 EOF 之后(即 ....126987 的行 - 在这种情况下为 164 - 非常可重复),取决于写入通道的数据量以及是否刷新通道,在没有 EOF 的情况下完成读取(第 164线)。

到目前为止,读取事件中断率是可以容忍的,但随后它会猛增并消耗大量处理它的周期。在较慢的机器上,没有时间做任何有用的事情。

我对此有很多难以表达的问题。但首先:

我不明白为什么读取处理程序会在 0 字节未决和非 eof 条件下被调用。缓冲区中是否必须至少有一个字节才能“可读”?

我不明白为什么 EOF 是瞬态的。我希望USB端口上的EOF意味着它被拔掉了,但事实并非如此。

如果我在写入 USB 后刷新通道,我得到的只是 0 长度读取,即使我终止应用程序并重新启动它,这种情况仍然存在。清除此问题(进入慢速模式)的唯一方法是从 USB 中拔出设备并重新开始(不刷新)。我不知道该怎么做。我必须刷新通道才能让写入实际进入 FTDI 芯片。

我应该为此使用固定长度的读取而不是获取吗?

4

2 回答 2

1

我用读取而不是获取来实现实际代码。使用我的客户作为测试台,他报告问题已解决。从现在开始,我将对二进制通道使用 read 而不是 gets。

于 2014-01-11T14:28:11.147 回答
0

我对 ftd2xx 包一无所知,但我看到了一些奇怪的东西。一般来说,当 eof 返回 1 时,最好在处理程序中关闭通道。每当数据准备好或通道从外部关闭时(可能是驱动程序或操作系统关闭它?),文件事件处理程序就会被调用,如果您不告诉 TCL 在处理程序中关闭通道,TCL 将永远重复调用它(告诉你做某事)。从文件事件手册:

“如果底层文件或设备上存在文件结尾或错误条件,则通道也被认为是可读的。脚本检查这些条件并适当处理它们很重要;例如,如果没有特殊检查对于文件结束,可能会发生无限循环,其中脚本没有读取数据,返回并立即再次调用。”

于 2013-11-16T04:51:44.467 回答