基于Spartan-6的高速数据采集、处理和实时传输研究
扫描二维码
随时随地手机看文章
1.前言
随着信息技术的飞速发展,各种数据的实时采集和处理在现代工业控制和科学研究中已成为必不可少的部分,尤其在信号测量、图像处理、音频信号处理等一些高速、高精度的测量中需要对高性能的数据采集技术。这就为数据采集设备的设计提出了两个的要求:1)要求接口简单灵活且有较高的数据传输率; 2)由于数据量通常都较大,要求主机能够对数据做出快速响应,并进行实时分析、处理。
在基于软件无线电的接收机架构中,数字下变频(DDC)技术起着非常重要的作用,也是软件无线电的核心技术之一。数字下变频位于模数转换(ADC)之后,需要处理高速高容量的数据,因此难度较大,不容易实现。针对数字下变频中的这一实际问题以及数据采集设备的两个要求,本报告采用了一种基于FPGA与USB 2.0的数据采集与实时传输方案。
本文所研究的基于Spartan-6的高速数据采集、处理和实时传输系统,就是实现将宽带中频数字接收机输出的高速正交IQ数据传输给FPGA去实现软件无线电的后续信号处理算法。利用Cypress的EZ-USB FX2高速数据传输方案实现将基带数据或者经FPGA处理后输出的数据进行传输,并利用上位机软件进行上位机存储和显示。本文主要从系统的硬件设计和软件设计两个方面分别对高速数据采集模块(宽带中频数字下变频模块)、高速数据传输模块以及上位机软件三个方面进行详细介绍。
2.系统总体方案设计
整个系统分成3个子模块,分别是:(1)高速数据采集模块(宽带中频数字下变频模块);(2)高速数据实时处理和传输模块;(3)上位机软件模块。整个系统框图如图1所示:
其中高速数据采集模块与EZ-USB高速数据处理和传输模块分别由对应的硬件电路和软件组成。上位机软件模块主要是利用Microsoft Visual2008软件利用MFC进行开发。系统整体硬件框图如图2所示:
系统整体工作原理:首先高速数据采集模块对70MHz中频模拟信号进行模数转换,采样速率为60MHz(基于带通采样定理),然后利用Atmel公司的高性能微控制器Atmega16A作为控制单元以异步控制方式对专用数字下变频进行设置和编程,实现将中心为70MHz的数字中频信号搬移到数字基带,基带数据速率仍然为60MHz,因此需要对高速的数字基带信号进行抽取和滤波得到低速的数字基带信号,抽取倍数可通过编程设置,抽取倍数越大得到的数字基带信号速率就越小,低速的数据基带信号再传输给FPGA实现基带数据的码元恢复,得到原始信息。EZ-USB既可以对数字基带信号数据进行实时数据采集和传输也可以对FPGA输出的原始码元信息进行传输,最后通过USB2.0接口将这些数据传输到上位机进行数据实时存储和显示。
3.高速数据采集模块(宽带中频数字下变频模块)
传统的数据采集系统往往采用单片机或数字信号处理器(DSP)作为控制器,控制模/数转换器(ADC)、存储器和其他外围电路的工作。但由于单片机本身的指令周期以及处理速度的影响,其时钟频率较低,各种功能都要靠软件的运行来实现,软件运行时间在整个采样时间中占有很大的比例,效率较低,很难满足系统对数据采集系统实时性和同步性的要求。基于DSP的数据采集系统,虽然处理速度快,但成本较高,过于频繁的中断会使CPU的效率降低,响应速度变差。近年来,基于FPGA的数据采集方案逐渐成为一种具有特殊优势的一种方案,其中最主要的一个优点就是可以实现对数据的并行处理。另外还具有开发周期短,集成度高,功耗低,工作频率高,设计费用低,编程配置灵活等一系列优点。
本报告中采用的高速数据采集与实时传输方案,主要包括以下几个部分:1)高速数据采集以及数字下变频处理部分;(2).高速数据传输部分;(3).上位机数据采集控制部分。系统首先将外部真实世界的模拟信号进行数字化,然后将模数转换器的高速数字信号进行数据缓冲,然后将缓冲数据经过数字下变频处理后传输给FPGA,采用FPGA控制CY7C68013A实现高速数据实时传输与存储,并进行显示。
3.1 高速数据采集及数字下变频处理部分硬件设计
高速数据采集及数字下变频模块是利用ADI的高速模数转换器AD6640按照奈奎斯特带通采用定理进行数据采集(本报告中选择的采样速率为60MHz),然后将60MHz的数字中频信号传输给专用数字下变频器件AD6620(也可以利用FPGA实现数字下变频以及信号抽取滤波等)进行数字下变频和抽取滤波等处理。
高速数据采集(主要是采集70MHz的中频模拟信号)及数字下变频(将70MHz中频搬移到数字基带)处理部分的硬件设计方案如下图1所示:
在该部分的硬件设计中:1)ADC采用的是ADI公司的高性能AD6640来实现对数据的高速采集。AD6640具有如下优点:12位的采样精度,具有65MSPS最小采样率,在25MHz带宽上具有高达80
的无杂散动态范围(SFDR),中频采样率可达70MSPS, 功率消耗大约710mW,采用采用5V单电源供电,片上自带T/R和参考电压,数据以二进制补码形式输出,CMOS输出电平兼容3.3V和5V。2)下变频部分,采用的是美国ADI公司的中频数字接收机专用数字信号处理器AD6620。它的内部集成了NCO(数控晶体振荡器)、数字混频器、二阶级联积分梳妆滤波器(CIC2)、五阶级联积分梳妆滤波器(CIC5)、系数可编程的抽取滤波器(RCF)等。3)控制部分,选用了Xilinx公司的Spartan 6芯片来进行采集控制、数据缓冲、数据处理、数据传输控制及通信等。
3.2 高速数据采集及下变频处理部分软件设计
宽带中频数字下变频器件AD6620内部主要由以下单元组成:频率变换单元、二阶固定系数级联积分梳状滤波器(CIC2)单元、五阶固定系数级联积分梳状滤波器(CIC5)单元以及一个可变系数的RAM系数抽取滤波器(RCF)单元。其中频率变换模块实现中频到数字基带的下变频,CIC2单元和CIC5单元是完成采样速率的抽取功能,通过设置不同的抽取倍数可得到不同速率的基带信号,而可变系数的 RCF单元则是将抽取后的信号进行整形滤波处理,使得滤波器的通带纹波、过渡带带宽以及阻带衰减等设计参数设计的更优化。
下面分别对频率转换单元、CIC2和CIC5滤波器单元以及可变系数RCF滤波器单元分别进行编程介绍。
3.2.1频率变换单元编程设置
频率变换单元主要是利用片内集成的数控晶体振荡器(NCO)来产生一组正交的数字本振信号。NCO模块的目标就是产生理想的正弦波和余弦波,以便与高速模数转换器件AD6640传输的中频实信号进行频率转换,把中频信号的频谱搬移到数字基带。NCO模块产生的正交数字本振信号频率是通过式来计算:
(式 1)
其中, 为NCO模块的本振频率, 为相应通道IF信号输入的频率,在本文中为70Mhz, 为采样频率,本文中应为56MHz,实际为60MHz,
根据式可计算得到 的值为:0010 1010 1010 1010 1010 1010 1010 1010共32位二进制数,那么通过FPGA向地址为0x303的地址写入上述32bit的二进制数。
3.2.2 固定系数CIC滤波器设计及其编程
二阶和五阶级联积分梳状滤波器都是固定系数的抽取滤波器。CIC2处理的信号时频率变换后输出的I、Q两路数字基带信号,
均为60MHz,为了减轻后续处理器的处理难度,需要利用CIC2和CIC5抽取滤波器进行合理抽取滤波,经CIC2抽取滤波后的信号频率为式所示:
其中, 为经过抽取系数 抽取滤波降速后的信号频率。可通过微控制器对CIC2滤波器抽取系数 进行编程,取值范围为2~6中得某一个整数值。第一级的抽取系数取值越大相应的整个芯片功耗就越低。CIC2滤波器的增益和通带衰减计算公式如式2和式3所示:
其中, 为CIC2滤波器增益衰减因子,取值范围为0~6之间的整数值。为了获得最优的动态范围需要将 设置为最小值,但是要注意防止出现溢出。
为输入信号的电平。
CIC2滤波器的离散和连续频率响应公式分别如式4和5所示:
相应的CIC5滤波器和CIC2滤波器的功能是一致的,也是实现抽取和滤波,CIC5滤波器是对CIC2这一级处理后的信号再一次抽取。下面利用ADI公司的SoftCell滤波器设计软件,设计CIC2、CIC5、RCF滤波器,以便获得相应滤波器参数,进行因为对于70MHz中频信号而言,依据射频带通采样定理,可以确定带通采样率为56MSPS就可以满足要求,但是为了增大信噪比的性能可将降采样率设成60MHz,输出频率为0.4MSPS,那么抽取倍数为140。打开滤波器设计软件后的界面如图3所示:
在软件的PortSelect菜单中选择AD6620作为设计的对象,然后在面板的左中间位置输入信号频率60MHz,以及抽取滤波后的输出频率0.4MHz,在Passband这个地方输入通带带宽4MHz,通带纹波设置为0.18,另外在Stopband这个地方设置阻带频率为4.5MHz,衰减为60dB,在5MHz这个频率点,设置衰减为80dB。然后点击compute按钮后就可以计算得到设计好的滤波器频率相应以及相应的各级滤波器系数和RCF滤波器的抽头数。设计完成后CIC2以及CIC5以及RCF滤波器系数可能有多种组合,这种情况下可以对比分析滤波器的响应,选择一种最佳的设计方案。表 1为不同抽取倍数下的带宽和混叠衰减规格:
最后确定选择CIC2滤波器抽取倍数为15,CIC5抽取倍数为2,RCF抽取倍数为2就可以满足系统设计的要求。
3.2.3 可变系数RCF滤波器参数设置
根据2.2.1节设计得到的RCF抽取倍数为30,可以根据式来计算得到抽取滤波器的抽头数为150。
其中, 为60MHz, 为15, 为CIC5滤波器抽取降速后的频率也即2MHz,当输入通道模式时单通道实模式时为1。对可变系数的RCF滤波器进行编程设置时需要先向地址0x30C写入整数 149( =59)。然后滤波器的响应 逐一写入到地址0x000~0xFF中。这样就完成了RCF寄存器的编程设置。
3.2.4 AD6620工作模式寄存器编程
对频率变换单元、固定系数的CIC2和CIC5以及RCF滤波器变化进行编程设置后,要使AD6620正常工作还需要对其工作方式和相关寄存器进行配置。AD6620的工作方式主要由地址0x300H的寄存器中低四位决定,其中,bit0=1时系统处于软复位状态,bit0=0则处于正常状态;bit1=1时系统处于双通道实数输入模式;bit2=1时系统处于单通道复数输入模式;如果bit0~bit2都是0,那么系统处于单通道实数输入模式;bit3=1/0为时钟主从工作模式。因此,当各寄存器配置完成后需要将寄存器bit0拉低,也即置为0状态使其脱离软复位状态,进入正常工作模式。另外,在本文中AD6620读写内部寄存器是采用的异步读写时序,数据输出是并行16位输出模式。[!--empirenews.page--]
总的来说,采用FPGA实现AD6620初始化的过程如下:
1)首先判断AD6620有没有经过硬件复位,如果没有经过硬件复位而又要实现系统复位,那么可以通过程序去编程地址0x300H的寄存器的bit0位置为1,使得AD6620处于软件复位状态,这样就可以对非动态的寄存器进行相关编程;
2)对NCO单元、CIC2、CIC5滤波器的抽取倍数和系数进行编程,将相应的控制系数写入相对应的地址寄存器中;
3)对RCF滤波器系数进行编程,分别向地址0x000H~0xFF中写入 这150个抽头系数,剩下的写入0。
4)对AD6620所有配置工作完成后,必须向地址0x300H的bit0写入0,使其脱离软复位状态,这样AD6620才可以在所配置工作方式下对输入的数据进行处理。
4.实时数据传输模块
基于USB2.0的数据传输设计主要包括以下几个部分:(1)基于EZ-USB FX2的硬件电路设计(2)EZ-USB FX2系列器件CY7C68013A的固件程序(设置CY7C68013A工作模式、传输方式等,需要对其内部寄存器进行相关配置),主要通过Keil C51软件进行开发。
4.1 实时数据传输部分的硬件设计
实时数据传输部分(USB2.0的数据传输)的硬件设计方案如图10所示,USB模块与FPGA间的连线如图11所示。
在高速数据传输部分采用了Cypress公司的EZ-USB FX2 CY7C68013A芯片,采用单片USB 2.0外设可以避免用FPGA直接实现USB通信协议时可靠性低的问题。在数据采集、传输以及测试过程中,CY7C68013A作为从机,负责将FPGA发送给它的基带数据,利用其16位SLAVE FIFO(关于SLAVE FIFO的传输示意图如图所示)将数据缓冲发送给上位机。在SLAVE FIFO数据传输中,上位机应用程序、固件程序和FPGA程序都非常关键,要配合好才能完成数据的高速数据传输。在这个系统中,FPGA起着主控制器作用,CY7C68013A则相当于一个从设备。具体的工作流程是:FPGA把接收过来的16位数字基带信号发送给CY7C68013A,CY7C68013A以SLAVE FIFO的方式将这些数据缓冲,并以批量数据传输的方式发送给上位机,上位机接收到这些数据后加以存储和显示。
4.2 CY7C68013A的固件程序设计
固件程序是指运行在设备CPU中的程序,只有在该程序运行时,外设才能称之为具有给定功能的外部设备。固件程序负责初始化单元,重新设置设备。主要包括设备描述符信息、设备功能代码。设备描述信息描述USB设备的一半特性和配置,如设备类别、接口配置、VID和PID等。主机在设备列举时要获取USB设备的描述符,从而获得设备的配置信息和相关驱动信息。用户可以通过修改固件中的描述符来改变设备的特性。设备功能代码有设备的功能需求决定。通信控制功能代码执行主机请求分析处理和数据交换处理功能。
采用ARM公司的Keil C51 uVision4.02开发CY7C68013A的固件程序。为了简化固件编程,CYPRESS提供了固件编程框架,在此基础上添加我们所需要的配置代码即可完成软件编程。
复位上电时,固件先初始化一些全局变量,然后调用初始化函数ID_Init(),初始化设备一直到没有配置的状态和打开中断,循环1s后枚举,直到端点0接收到SETUP包退出循环,进入循环语句while,执行任务函数,函数包括:
(a)TD_POLL()用户任务调度函数;
(b)如果发现USB设备请求,则执行对应的USB请求;
(c)如果发现USB空闲置位,则调用TD_Suspend()这个挂起函数,调用成功后则内核挂起,直到出现USB远程唤醒信号,调用TD_Resume(),内核唤醒后重新进入while循环。
固件框架程序需要以下几个文件:
(1)FX2.h:库函数申明,以及变量、宏定义、数据类型定义;
(2)Fxregs.h:FX2LP寄存器头文件;
(3)Fw.c:固件框架源文件;
(4)Periph.c:用户调度函数、用户可以修改,在不同的应用中文件名不一样;
(5)Dscr.a51:USB描述符列表,用户可以修改;
(6)Ezusb.lib:EZUSB库文件;
(7)USBJmpTb.OBJ:中断跳转函数目标文件
这种情况下,需要在固件程序中,进行相应的修改。在SLAVE FIFO中,特别是自动传输(CY7C68013A单片机不干预数据传输),固件程序主要完成各个端口的初始化。因此我们要修改两个地方:(1)USB设备描述符列表Dscr.a51,根据实际情况修改里面的端口数和传输方式等;(2)初始化函数void TD_Init(void)。在SLAVE FIFO这种方式下,设置EP2为4缓冲的输出端口,EP6为4缓冲的输入端口。
5. 上位机软件设计
上位机应用程序是系统与用户之间交流的接口,它通过通用驱动程序完成对外设的控制和通信。主机端应用程序负责向FX2的FIFO发送或者接收数据。本报告中采用的固件架构是EZ-USB FX2/FX2LP(CY7C68013, 驱动程序是Cyusb.sys。用Visual Studio2008软件进行上位机开发,利用C++/MFC来开发基于对话框的应用程序,系统的主要功能模块有:打开USB设备、复位USB设备、系统数据测试与显示等。
在VS2008中建立一个MFC 单文档/对话框应用程序后,在路径项目中包含头文件cyapi.h和cyapi.lib所在的路径。然后手动导入cyapi.lib,注意是CV6_7的lib,不要导入BCB的。
开发USB应用程序的一般工作流程如下。
1)首先要创建一个USB设备对象
CCyUSBDevice *USBDevice = new CCyUSBDev(Handle);括号中的Handle是USB所关联对象的句柄,一般在MFC中直接就是m_hwnd。
2)打开USB设备。
可以用到两个函数open();isopen()这两个都可以用来打开USB设备,isopen()还可以判断能否获得USB设备句柄,一般来说,如果只有一个USB设备连接,可以这样打开:
USBDevice->open(0)//打开0号USB设备;如果要判断,可以:
if(!USBDevice->open(0)) //打开失败
{messagebox("USB未连接");}
或者if(!USBDevice->Isopen())
如果连接有多个USB设备,那么可以枚举所有的USB,用到DeviceCount()函数;具体的可以参考cybulk的例子。执行USBDevice->DeviceCount()后,返回所连接的USB设备个数:
if (USBDevice->DeviceCount()) //保证至少有一个USB设备连接
{
for (i = 0; i DeviceCount(); i++) //枚举所有USB设备
{
USBDevice->Open(i);//打开第i号USB设备
m_DeviceListComBox.AddString(USBDevice->DeviceName);//所选择的当前设备名}
}
3)端点枚举
在cybulk的例子中介绍了如何枚举固件中使用的所有端点,也就是使用多个端点的情况,其枚举步骤主要包括一下几个端点:
(1)创建USB设备并打开该设备
CCyUSBDevice *USBDevice=new CCyUSBDevice(m_hWnd);//USB设备USBDevice->Open(0);//打开0号USB设备。
(2)获取所用的端点数目
intepts = USBDevice->EndPointCount();
EndPointCount();函数返回当前所用的端点数+1,也就是包含了控制端点。例如在固件接口描述符Interface Descriptor中设置Number of end points项(第5项)的值为4,则epts的值为4+1=5。
(3)定义端点指针
CCyUSBEndPoint *endpt;CCyUSBEndPoint建立一个端点对象,可建立所有的端点类型,控制端点,bulk端点,ISO端点等;
(4)枚举端点,并获得其属性:端点号,传输方向
for (i=1; i
{
endpt = USBDevice->EndPoints[i];//EndPoints-端点列表,最大16.EndPoints[0]指向控制端点(CCyControlEndPoint),未使用的端点设置为NULL。
if (endpt->Attributes == 2) // Bulk Attributes 判断传输类型bulk,control,等。
{
sprintf(s, "0x%02x", endpt->Address);
if (endpt->Address & 0x80) //Address--判断传输方向in or out 0x8_-in;0x0_-out
{
m_InEndptComBox.AddString(s); //最高位为8,in端点,添加到in组合框m_InEndptComBox.SetItemData(m_InEndptComBox.GetCount()-1,i);
else
{
m_OutEndptComBox.AddString(s); //否则,最高位为0,out端点,添加到out组合框m_OutEndptComBox.SetItemData(m_OutEndptComBox.GetCount()-1,i);
}}}[!--empirenews.page--]
这样,就完成了某个具体端点的选择。如果只需要使用一个端点的话,那上面的代码无疑就显得冗长不够简洁了。仅使用一个端点,可以使用EndPointOf()函数,该函数直接使用指定的端点,返回其指针;例如,要使用端点2,in传输,那么,可以这样:
CCyUSBDevice *USBDevice=new CCyUSBDevice(m_hWnd); //USB设备
USBDevice->Open(0); //打开0号设备,
CCyUSBEndPoint *endpt = USBDevice->EndPointOf(0X82); //使用端点2,in传输可以看到,上面的只需要3行代码,比枚举简洁方便多了。至于获取USB其他属性,这里列出经常使用的几个:(1)USBDevice->DeviceCount()//返回连接到电脑的USB设备个数,从0,1,2.开始命名(2)USBDevice->DeviceName()//返回USB设备名称,也就是固件中 StringDscr2:字段字符串。(3)USBDevice->VendorID//返回USB设备VIDUSBDevice->ProductID//返回USB设备PID。具体的可参考cyapi的文档,在CCyUSBDevice和CCyUSBEndPoint这两个类里可查询到。
4)传输命令控制
可以说,写USB上位机,要对固件进行自定义命令传输,通常都是用输入输出控制传输进行的,即使用控制传输in或者out方式实现对固件的自定义命令。
(1)out控制传输发送vendor命令
采用out方式应该比较符合大家的思维,因为是从上位机发命令到下位机,怎么看都应该是out而不是in。例如你在固件里设置了out的接收buf:
BOOL DR_VendorCmnd(void)
{
switch (SETUPDAT[1])
{
case 0xDD:
{ temp=EP0BUF[0];
EP0BCH=0;
EP0BCL=1;
EP0CS |=bmHSNAK;
break;
}
default:
return(TRUE);
}
return(FALSE);
}
在VS2008的C++/MFC中:
CCyControlEndPoint* CtlEndpoint;//定义一个控制端点
CtlEndpoint->Target = TGT_DEVICE;//不必关注,固定
CtlEndpoint->ReqType = REQ_VENDOR; //请求类型:自定义请求(标准请求等)CtlEndpoint->Direction = DIR_TO_DEVICE; //传输方向:主机->usb设备(out)
CtlEndpoint->ReqCode = 0XDD; //自定义请求码
CtlEndpoint->Value = 0; //这里的设定值将传给setupdat的[2:3]位
CtlEndpoint->Index = 0; //这里的设定值将传给setupdat的[4:5];PUCHAR buf=new UCHAR[1];
ZeroMemory(buf,1);//用0填充buf区,填充大小(1字节)
long buflen=1;//传输的其他字节数:cy控制台console中length的值。
CtlEndpoint->XferData(buf,buflen);
当然,没有规定说一定必须传给下位机至少一个字节的数据;你也可以不传;不过最好下位机同样设置接收字节为0,否则小心有莫名奇妙的错误(XX内存不能为只读等等)。
将temp=EP0BUF[0];去掉,上位机:
PUCHAR buf=new UCHAR; //用0填充buf区,填充大小(1字节)
long buflen=0;
CtlEndpoint->XferData(buf,buflen);
也是可以的,另外,控制传输请尽量用同步的xferdata()而不是异步的begindataxfer()。
(2)in控制传输发送vendor命令
基本上,跟out区别不大。固件中:
BOOL DR_VendorCmnd(void)
{
switch (SETUPDAT[1])
{
case 0xDD:
{
*EP0BUF=0XDD;
EP0BCH=0;
EP0BCL=1;
EP0CS |=bmHSNAK;
break;
}
default:
return(TRUE);
}
return(FALSE);
}
VC中:CCyControlEndPoint* CtlEndpoint; //定义一个控制端点
CtlEndpoint->Target = TGT_DEVICE;//不必关注,固定
CtlEndpoint->ReqType= REQ_VENDOR; //请求类型:自定义请求(标准请求等)CtlEndpoint->Direction = DIR_FROM_DEVICE; //传输方向:usb设备->主机(in)CtlEndpoint->ReqCode = 0XDD; //自定义请求码CtlEndpoint->Value = 0;//这里的设定值将传给setupdat的[2:3]位CtlEndpoint->Index = 0; //这里的设定值将传给setupdat的[4:5]位PUCHAR buf=new UCHAR[1];
ZeroMemory(buf,1); //用0填充buf区,填充大小(1字节)
long buflen=1; //传输的其他字节数:cy控制台console中length的值CtlEndpoint->XferData(buf,buflen);这里的话,推荐尽量设置接收缓冲,不要将buflen设置为0.同样,控制传输请尽量用同步的xferdata()而不是异步的begindataxfer()。
(3)另外,还有两个简化版本的函数Write(out传输)和Read(in传输)也可以进行控制传输。其作用同XferData()是一样的,因为已经明确表示了是in还是out,所以Direction 项的值就省略了。只是看起来代码更加简洁,输入效率更高而已。基本上,大多数时候,使用Write和Read会方便很多。
CCyControlEndPoint* CtlEndpoint; //定义一个控制端点
CtlEndpoint->Target = TGT_DEVICE; //不必关注,固定
CtlEndpoint->ReqType = REQ_VENDOR; //请求类型:自定义请求(标准请求等)CtlEndpoint->ReqCode = 0XDD; //自定义请求码CtlEndpoint->Value = 0; //这里的设定值将传给setupdat的[2:3]位CtlEndpoint->Index = 0; //这里的设定值将传给setupdat的[4:5]位PUCHAR buf=new UCHAR[1];ZeroMemory(buf,1);//用0填充buf区,填充大小(1字节) long buflen=1; //传输的其他字节数:cy控制台console中length的值CtlEndpoint->Write(buf,buflen);
Read的话同理。