基于Cypress CyAPI(FX3)开发USB嗅探器双端点XferData报错问题
基于Cypress CyAPI(FX3)实现多端点数据嗅探的故障排查与解决
场景背景
开发数据嗅探类工具用于监测、存储设备传输数据,支撑自有程序测试工作。对接Cypress FX3系列设备时,需要基于CyAPI实现抓数逻辑:跳过设备打开、初始化阶段的配置交互报文,待设备启动正式业务数据流后,抓取对应端点的有效业务数据。
初始实现采用双线程同步架构:
- 线程1:调用设备原生API实例化
Device对象,依次执行open()、init()完成设备初始化(该阶段交互数据不需要抓取),初始化完成后通过std::promise发出同步信号,再调用getData()启动正式业务数据传输,传输结束后执行close()关闭设备。 - 线程2:实例化CyAPI封装类
WrapperCyAPI对象,依次执行open()、init()获取设备句柄,等待同步信号触发后调用usb.getData()抓取传输数据,完成后关闭设备。
单端点正常实现代码
主逻辑代码:
#include <iostream> #include <thread> #include <vector> #include <tuple> #include <future> #include <DeviceAPI.h> #include <WrapperCyAPI.h> int main() { std::promise<void> prm; std::future<void> fut{ prm.get_future() }; std::thread th1{ [&prm]() { Device device; device.open(); device.init(); // init阶段的配置交互数据不需要抓取 prm.set_value(); device.getData(); device.close(); } }; std::thread th2{ [&fut]() { WrapperCyAPI usb; usb.open(); usb.init(); fut.wait(); std::vector<uint8_t> data{ usb.getData() }; usb.close(); } }; th1.join(); th2.join(); }
单端点读逻辑:
std::vector<uint8_t> USB::getData() { long receivedSize{ static_cast<long>(buffer.size()) }; device->EndPointOf(ENDPOINT_IN)->XferData(&buffer[0], receivedSize); return { buffer }; }
该场景下功能运行正常,单个IN端点传输100KB数据可完整接收。
双端点场景故障表现
设备同时向2个IN端点传输数据(每个端点传输100KB,总数据量200KB)时,修改代码适配双端点读取后XferData调用报错。
修改后主逻辑相关片段:
int main() { // 其余逻辑不变 std::thread th2{ [&fut]() { WrapperCyAPI usb; usb.open(); usb.init(); fut.wait(); std::tuple<std::vector<uint8_t>, std::vector<uint8_t>> data{ usb.getData() }; usb.close(); } }; }
修改后的双端点读逻辑:
std::tuple<std::vector<uint8_t>, std::vector<uint8_t>> USB::getData() { long receivedSize{ static_cast<long>(buffer1.size()) }; device->EndPointOf(ENDPOINT_IN1)->XferData(&buffer1[0], receivedSize); receivedSize = static_cast<long>(buffer2.size()); device->EndPointOf(ENDPOINT_IN2)->XferData(&buffer2[0], receivedSize); return { buffer1, buffer2 }; }
故障现象:ENDPOINT_IN1调用XferData可正常获取100KB数据,ENDPOINT_IN2调用XferData返回false,无法获取数据。
故障根因
- 同步串行调用逻辑存在硬伤:
XferData是阻塞式同步接口,当前实现会等第一个端点100KB数据全部接收完成后,才向第二个端点提交读请求。FX3设备端端点缓冲区、主机侧USB驱动栈的端点缓冲区容量非常有限(高速USB模式下单端点缓冲通常仅数KB到十几KB),第二个端点的100KB数据在等待第一个端点传输的过程中,会因为没有待处理的读请求、缓冲区满被丢弃,直接导致第二个端点请求超时返回false。 - 双句柄访问存在驱动兼容性问题:Windows下USB设备默认是独占访问模式,当前在两个线程分别打开原生API句柄、CyAPI句柄,单端点场景能跑通仅代表当前驱动配置允许共享打开,但部分版本的CyUSB3驱动在多句柄访问场景下,不会自动为未提交读请求的端点维持传输调度,第二个端点的传输根本没有被驱动层接管,自然收不到数据。
- USB批量传输(Bulk Transfer)本身没有端到端流控保证,必须提前提交读请求在驱动层排队,才能保证数据不丢;等数据已经从设备发出后再临时提交读请求,必然出现丢包。
可行解决方案
- 改用异步并行提交传输请求,替换串行同步调用逻辑
不要等第一个端点读完再提交第二个端点的请求,初始化完成后同时给两个端点提交Overlapped异步读请求,等待两个传输全部完成后再回收数据。参考实现如下:#include <windows.h> std::tuple<std::vector<uint8_t>, std::vector<uint8_t>> USB::getData() { OVERLAPPED ov1 = {0}, ov2 = {0}; ov1.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); ov2.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); long len1 = static_cast<long>(buffer1.size()); long len2 = static_cast<long>(buffer2.size()); // 同时提交两个端点的异步读请求,不等待第一个完成 device->EndPointOf(ENDPOINT_IN1)->XferData(&buffer1[0], len1, &ov1); device->EndPointOf(ENDPOINT_IN2)->XferData(&buffer2[0], len2, &ov2); // 等待两个传输全部完成 HANDLE events[] = {ov1.hEvent, ov2.hEvent}; WaitForMultipleObjects(2, events, TRUE, 5000); // 按需设置超时时间 // 获取实际传输长度 DWORD transferred1 = 0, transferred2 = 0; GetOverlappedResult(device->EndPointOf(ENDPOINT_IN1)->hDevice, &ov1, &transferred1, FALSE); GetOverlappedResult(device->EndPointOf(ENDPOINT_IN2)->hDevice, &ov2, &transferred2, FALSE); CloseHandle(ov1.hEvent); CloseHandle(ov2.hEvent); // 按实际接收长度截断缓冲区 buffer1.resize(transferred1); buffer2.resize(transferred2); return {buffer1, buffer2}; } - 为每个端点创建独立接收线程,提前排队读请求
给两个IN端点分别创建独立的接收线程,每个线程内循环调用XferData持续接收数据,同步信号触发后再从环形接收队列中截取有效业务段数据,避免端点因为没有待处理读请求出现缓冲区溢出。注意要给每个端点配置足够容量的环形队列,适配高速传输场景。 - 规避双句柄访问的兼容性问题
最稳妥的实现方式是不要在两个线程分别打开设备句柄,直接基于CyAPI单独实现初始化阶段的交互逻辑,过滤掉初始化阶段的配置报文,等初始化完成后再开始记录业务数据,从根源上避免多句柄访问带来的驱动端点调度异常。 - 细节校验:调用
XferData前确认传入的长度参数为缓冲区实际分配大小,传输完成后必须按接口返回的实际接收长度截断缓冲区,不要直接返回整个预分配的buffer,避免混入无效数据。
内容的提问来源于stack exchange,提问作者Alireza Nikpay
相关产品推荐
相关产品推荐

