tag 标签: cdc

相关博文
  • 热度 13
    2015-1-24 00:22
    1544 次阅读|
    0 个评论
    在ASF中有一个STDIO_USB_EXAMPLE的例子,可以方便的演示CDC串口。但是为了更好的理解这个例子,我们自己重新创建,然后通过ASF添加函数,这样可以更好的掌握整个过程。下面是我创建项目的过程,供大家参考。   首先创建项目,选择gcc c asf project。     然后选择xmega128B1以及xmega128B1-xplained模板。     在ASF向导中,添加USB Device(server)- cdc_stdio     然后打开 config_usb.h 。先注释掉第49行的强制警告提示,不然每次编译都会产生一个警告。   //#warning You must refill the following definitions with a correct values   复制代码   然后将124/125行修改为下面语句   #define  UDI_CDC_ENABLE_EXT(port)          stdio_usb_enable() #define  UDI_CDC_DISABLE_EXT(port)         stdio_usb_disable()   复制代码   最后添加包含头文件 stdio_usb.h   #include stdio_usb.h   复制代码   再打开文件 conf_clock.h ,先将系统时钟改为SYSCLK_SRC_RC32MHZ。   //#define CONFIG_SYSCLK_SOURCE          SYSCLK_SRC_RC2MHZ #define CONFIG_SYSCLK_SOURCE        SYSCLK_SRC_RC32MHZ //#define CONFIG_SYSCLK_SOURCE        SYSCLK_SRC_RC32KHZ //#define CONFIG_SYSCLK_SOURCE        SYSCLK_SRC_XOSC //#define CONFIG_SYSCLK_SOURCE        SYSCLK_SRC_PLL   复制代码   然后允许CONFIG_OSC_RC32_CAL 和CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC   #define CONFIG_USBCLK_SOURCE                USBCLK_SRC_RCOSC #define CONFIG_OSC_RC32_CAL                 48000000UL #define CONFIG_OSC_AUTOCAL_RC32MHZ_REF_OSC  OSC_ID_USBSOF   复制代码   最后在打开main.c,在main函数中输入下面代码   sysclk_init();         board_init();           // Initialize interrupt vector table support.         irq_initialize_vectors();           // Enable interrupts         cpu_irq_enable();           stdio_usb_init();           uint8_t ch;           while (true) {                   ch = getchar();                 putchar(ch);                 LED_Toggle(LED2_GPIO);         }   复制代码   编译项目文件,通过Flip将编译后HEX写入芯片。     重新上电(记得先取下短路块),应该就可以找到串口了。     打开一个串口终端,选择上面的串口,并设置波特率为115200,随便输入什么,如果屏幕上可以显示出你输入的字符,LED同时闪动,就代表成功了。
  • 热度 15
    2014-3-17 14:43
    6302 次阅读|
    2 个评论
           时钟对于 FPGA 就像我们的心脏,时刻控制着“跳动”的频率以及“血液”的流速;时钟域好比通过心脏的血液血型,不同血型的血液会产生排斥作用。在设计中建议时钟越少越好,好比于人有两个甚至更多的心脏,其内脏工作将会多么混乱。但是某些情况下多时钟又不可避免,比如从 FPGA 外部输入的数据,其自带有个随路时钟,数据终归要在 FPGA 内部时钟域下处理,这来自外部的“血液”如何处理才能与内部的“血液”融合呢?配对及转换工作则是必不可少的,这就引入本节的主题:跨时钟域处理( Clock Domain Crossing ):        跨时钟域处理需要两方面的工作: 1. 设计者处理; 2. FPGA 工具( Vivado )处理。 1.       设计者处理        首先讲解一下如果不进行跨时钟域处理,会出现什么问题呢?如图 1 所示路径, QA 属于 CLKA 时钟域的数据输出,另一个时钟 CLKB 去捕获节点 REG A 的输出 QA ,假定 CLKA 与 CLKB 是异步时钟,它们之间的相位并不固定,因此捕获过程中可能会出现建立冲突( setup violation )和保持冲突( hold violation ),如图 2 所示,左右分别为发生建立冲突和保持冲突的情况。 图 1 图 2        当冲突出现时(我感觉整个人都不好了),会发生什么事情呢?在发生建立冲突或者保持冲突,捕获节点( REG B )会处于一个不定的状态,正常的状态是高电平或者低电平,而此时的状态停留在高电平和低电平的中间,无效的电平 X ,称这个状态为 亚稳态 。如图 3 所示,捕获节点输出保持在亚稳态,可能在整个时钟周期内都保持在亚稳态,由于不正确的状态,其后连接的逻辑在功能实现上就会出现问题,比如一个判断信号上升沿的逻辑,通常判断 D == HIGH D_PREV == LOW ( D 为信号当前电平状态, D_PREV 为信号上个时钟的电平状态)是否成立,而发生亚稳态时则 D_PREV == X ,这个上升沿将会错过。因此,加入跨时钟域处理设计是必须的。 图 3        对于 单比特信号 的跨时钟域处理,常用的方法是“打两拍”,即在捕获时钟域中加入两个寄存器进行时钟转换,如图 4 所示,加入 REG B1 和 REG B2 ,虽然 REG B1 处于亚稳态状态,但是 REG B2 的输出 QB2 能稳定在正常的电平上,由于 REG B1 和 REG B2 之间没有多余的逻辑, REG B1 能有充裕的时间稳定状态,此情况下 REG B2 能完美地隐藏 REG B1 的亚稳态。在捕获时钟的频率比较高的情况下,如果一个 REG B2 还未能隐藏亚稳态,拍数也可以增加三个或者更多,当然一般情况下,两拍足矣。 图 4        对于 多比特总线数据 的跨时钟域处理,能否也使用 “ 打两拍 ” 的方法呢?答案见图 5 ,虽然 REG B2 的输出是稳定的,稳定在哪一个电平是不确定的,不过会在当前时钟或者下一个时钟输出正确电平,即偏差在一个时钟周期,也就是说不能保证所有比特位的状态一致,也是这个原因,导致传输多比特总线数据时各比特位不同步,常用的解决方法是加入 FIFO 隔离,如图 6 所示, FIFO 能有效地隔离两个时钟域,避免亚稳态的发生。 图 5        图 6 2.       FPGA 工具( Vivado )处理        Vivado 采用 XDC 对时序进行约束,默认情况下,会分析所有时钟的路径,当然也包括跨时钟域的路径。        设计经过综合实现后,在 Implementation 中点击 Report Clock Interaction (见图 7 ),得到设计中所有时钟的交互情况,如图 8 所示,共有两个时钟: CLK_REG 和 CLK_USR ,红色区域表示 Timed ( unsafe ),说明两个时钟间有时序路径。 图 7 图 8        可以发现在图 8 底部时序分析的结果,有红色报警说明有路径时序未收敛,打开时序分析报告(见图 9 ), source clock 是 CLK_REG , destination clock 是 CLK_USR ,说明是一条跨时钟域路径,其中 Requirement 只有 0.001ns ,显然对跨时钟域路径的分析不合理,因此通常在保证 设计者处理 完成后,添加时序约束,是 Vivado 忽略对跨时钟域路径进行时序分析。 图 9        可以通过设置时钟组( clock group )或者设置假路径( false path )处理跨时钟域路径。如图 10 所示,在 clock interaction 中红色区域右击,选择 Set Clock Groups 或者 Set False Path , 图 10        Set Clock Group 和 Set False Path 的区别是,前者设置了双向( CLKA-TO-CLKB 和 CLKB-TO-CLKA )的路径,而后者只设置单向( CLKA-TO-CLKB 或者 CLKB-TO-CLKA )的路径,此例中因为只有单向区域有路径交互,因此使用 Set False Path 即可。 图 11        添加完约束后,时序报告中 Inter-Clock Paths 没显示有路径,并且没有未收敛的时序路径,如图 12 所示, Clock Interaction 中原先红色区域变成了蓝色( User Ignored Paths ),可以确认约束生效。 图 12
  • 热度 29
    2013-11-18 16:33
    7719 次阅读|
    1 个评论
    汇总1:STM32的USB例程修改步骤,来自http://blog.csdn.net/cy757/archive/2010/01/01/5117610.aspx 以下是笔者将ST的Custom_HID例程修改为“自定义USB设备”例程时总结出来的,因为笔者也是刚刚学USB开发不久,某些方面理解错误在所难免,请各位大虾指正。   一、usb_desc.c文件 根据你程序使用的通信方式修改。usb_desc.h文件中定义要根据usb_desc.c文件中的数组的大小;ConfigDescriptor 下添加需要处理的端点;根据需要添加或删除报告描述符(主要用于HID)和CDC接口描述符(主要用于实现USB转串口)等。具体方法可以下载个“电脑圈圈”使用D12编写的例子。   二、Usb_conf.h文件: 1、修改需要处理那些中断 CNTR_CTRM 处理数据正确传输后控制,比如说响应主机 CNTR_DOVRM /* DMA OVeR/underrun Mask */ CNTR_ERRM /* ERRor Mask */ CNTR_WKUPM 0 /* WaKe UP Mask */ CNTR_SUSPM /* SUSPend Mask */ CNTR_RESETM 主要处理USB复位后进行一些初始化任务 CNTR_SOFM /* Start Of Frame Mask */ CNTR_ESOFM /* Expected Start Of Frame Mask */ 如: usb_conf.h中的#define IMR_MSK (CNTR_CTRM | CNTR_SOFM | CNTR_RESETM )是决定USB_CNTR寄存器中的那个USB相关中断启动还是屏蔽。   2、根据需要增加端点缓存地址,要根据缓存区的地址修改,防止数据重叠 如下为根据每个缓冲区的大小为64字节修改: #define ENDP1_TXADDR (0xC0) #define ENDP1_RXADDR (0xD0) #define ENDP2_TXADDR (0x100) #define ENDP2_RXADDR (0x140) #define ENDP3_TXADDR (0x180) #define ENDP3_RXADDR (0x1C0) 3、修改/* CTR service routines */下的EPX_IN_Callback和EPX_OUT_Callback。注释掉需要处理的函数。NOP_Process表示不处理。   三usb_prop.c文件 1、修改void XX_Reset(void)(如:void Joystick_Reset(void)) 一般/* Initialize Endpoint 0 */的不用修改,如下为举例说明端点1的初始化,其他端口原理一样。 SetEPType(ENDP1, EP_INTERRUPT);//设置端点1类型 /*EP_BULK 批量端点 EP_CONTROL 控制端点 EP_ISOCHRNOUS 同步端点 EP_INTERRUPT 中断端点*/ SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置端点1缓冲区基地址 SetEPTxCount(ENDP1, 64);// 配置Tx 缓冲计数器 SetEPRxStatus(ENDP1, EP_RX_DIS);// //设置端点接收关闭 SetEPTxStatus(ENDP1, EP_TX_NAK);// //设置端点1发送不应答 /* #define EP_RX_DIS (0x0000) // EndPoint RX DISabled 端点接收关闭 #define EP_RX_STALL (0x1000) // EndPoint RX STALLed 端点接收延迟 #define EP_RX_NAK (0x2000) // EndPoint RX NAKed 端点接收不应答 #define EP_RX_VALID (0x3000) // EndPoint RX VALID端点接收有效 #define EP_TX_DIS (0x0000) //EndPoint TX DISabled #define EP_TX_STALL (0x0010) // EndPoint TX STALLed #define EP_TX_NAK (0x0020) // EndPoint TX NAKed #define EP_TX_VALID (0x0030) // EndPoint TX VALID */ 2、删除不相干的描述符等。 如自定义的USB设备就不需要以下结构体初始化: ONE_DESCRIPTOR Joystick_Report_Descriptor ONE_DESCRIPTOR Mouse_Hid_Descriptor 3、修改RESULT XX_Data_Setup(u8 RequestNo)的数据类请求处理。 如Custom_HID例程修改为“自定义USB设备”例程时可以将以下代码删除 if ((RequestNo == GET_DESCRIPTOR) (Type_Recipient == (STANDARD_REQUEST | INTERFACE_RECIPIENT)) (pInformation-USBwIndex0 == 0)) { if (pInformation-USBwValue1 == REPORT_DESCRIPTOR) { CopyRoutine = Joystick_GetReportDescriptor; } else if (pInformation-USBwValue1 == HID_DESCRIPTOR_TYPE) { CopyRoutine = Joystick_GetHIDDescriptor; } }   4、删除不相干的获得描述符返回函数 如自定义的USB设备就不需要以下函数: Joystick_GetReportDescriptor Joystick_GetHIDDescriptor 四、usb_endp.c文件 1、增加之前定义的中断数据处理函数 如: void EP1_OUT_Callback(void) { 这些写接收代码 } 五、数据发送和接收,举例说明 1、数据接收 u8 DataLen; DataLen = GetEPRxCount(ENDP1); PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen); SetEPRxValid(ENDP1); USART1_Send(DataLen); count_out = 1; 2、数据发送 UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64); SetEPTxCount(ENDP1, 64); SetEPTxValid(ENDP1);   =========================================================================== 汇总2:STM32 USB 程序将BULK EP改成双缓冲机制后,一直狂飚到了1MB/S!来自:http://www.powermcu.com/bbs/viewthread.php?tid=693 前天测试自己编写的USB驱动程序时候发现从主机到STM32的OUT传输(主机到设备)速率竟然只有最高33KB/S,实在是晕死了。经过研究后发现是驱动程序中设置的PIPE MaxTransferSize参数的关系,原先设置64只能33KB/S,后参考其他USB设备驱动程序的值,设置成了65535,再测试USB OUT的速度,达到了500KB/S,终于解决了驱动程序的瓶颈。不过算下USB 2.0全速的通讯速率是12Mb/S,排除掉CRC、令牌、SOF等等开销怎么也应该不止最大500KB/S啊。到网上看了看,基本上应该能达到600KB/S~700KB/S以上,我现在的速度应该还有很大的提升才是。 看看程序,发现 void EP3_OUT_Callback(void)//EP3 OUT的回调函数,当EP3接收到数据时候中断调用该函数 { count_out = GetEPRxCount(ENDP3);//获得接收到的数据长度 PMAToUserBufferCopy(buffer_out, ENDP3_RXADDR, count_out);//将数据从USB EP3 RX的缓冲区拷贝到用户指定的数组中 SetEPRxValid(ENDP3); //完成拷贝后置有效状态,从而EP3发送ACK主机可以进行下一个数据包的发送 } 试着将PMAToUserBufferCopy这句注释掉(这样STM32就不处理接收到的数据了)后再测试速度,惊奇地发现速度竟然达到了997KB/S!晚上仔细想了想,数据肯定是要使用的,这个数据拷贝的过程的时间消费总是少不了的;由于通常情况下USB设备BULK数据接收的步骤就是:接收到数据,置NAK-将缓冲区数据拷贝到用户区(用户处理过程)-发ACK通知主机完成了完整的接收可以发送下一个-主机发送下一个,按照以上的步骤USB接收一步步的进行,只要STM32不完成数据处理,状态就一直是NAK,主机就会不停地发送该数据包,浪费了带宽,因此就会导致我上面最大速度500KB/S难以再增加的情况!不甘心啊~~   昨天晚上又仔细研究了STM32的技术参考手册的USB章节内容,里面提到BULK可以采用双缓冲机制(PING-PONG)进行处理,正好可以解决上面的情况。双缓冲机制的原理就是分配2块接收缓冲,STM32的用户处理和USB接口可以分别交替占用2个缓冲区,当USB端点接收数据写其中一个缓冲区的时候,用户的应用程序可以同时处理另一个缓冲区,这样缓冲区依次交换占有者,只要用户处理程序在USB端点接收的时间片段内完成处理,就能够完全不影响USB的通讯速度! 程序部分修改   一、EP3_OUT的设置修改, //ZYP:修改EP3为BULK双缓冲方式------------------------- SetEPType(ENDP3, EP_BULK); SetEPDoubleBuff(ENDP3); SetEPDblBuffAddr(ENDP3, ENDP3_BUF0Addr, ENDP3_BUF1Addr); SetEPDblBuffCount(ENDP3, EP_DBUF_OUT, VIRTUAL_COM_PORT_DATA_SIZE); ClearDTOG_RX(ENDP3); ClearDTOG_TX(ENDP3); ToggleDTOG_TX(ENDP3); SetEPRxStatus(ENDP3, EP_RX_VALID); SetEPTxStatus(ENDP3, EP_TX_DIS); //------------------------------------------------------   二、EP3_OUT回调函数的修改 void EP3_OUT_Callback(void) { //ZYP:以下是修改成EP3双缓冲OUT后的处理函数 if (GetENDPOINT(ENDP3) EP_DTOG_TX)//先判断本次接收到的数据是放在哪块缓冲区的 { FreeUserBuffer(ENDP3, EP_DBUF_OUT); //先释放用户对缓冲区的占有,这样的话USB的下一个接收过程可以立刻进行,同时用户并行进行下面处理 count_out = GetEPDblBuf0Count(ENDP3);//读取接收到的字节数 PMAToUserBufferCopy(buffer_out, ENDP3_BUF0Addr, count_out); } else { FreeUserBuffer(ENDP3, EP_DBUF_OUT); count_out = GetEPDblBuf1Count(ENDP3); PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out); } } 经过上面的修改,终于解决了STM32在处理接收数据时导致主机等待的情况,用BUS HOUND软件测试了下 哈哈,这下终于爽了。 PS:上面的FreeUserBuffer(ENDP3, EP_DBUF_OUT); 这句话的上下位置是关键,如果放到函数的后面,则仍旧会有主机等待STM32处理数据的情况,速度仍然是500KB/S! 把这句话放在拷贝函数的前面的话就真正把双缓冲PING-PONG机制用起来了。大致算了下PMAToUserBufferCopy(buffer_out, ENDP3_BUF1Addr, count_out);这句话当count_out为最大值64的时候STM32执行需要302个周期,72MHZ情况下约4.2微秒执行时间,而USB传输按照12Mb/s的线速度传输64字节的数据至少也得40微秒,因此只要PMAToUserBufferCopy的时间不超过40微秒,就不会导致缓冲区竞争的情况。   =============================================================================== 汇总3:STM32的USB中断说明,来自:http://bbs.ednchina.com/BLOG_ARTICLE_238817.HTM STM32的USB模块可以产生三种中断:USB唤醒中断、USB高优先级中断和USB低优先级中断,在STM32的参考手册中没有详细说明这三种中断对应哪些事件,现说明如下: 1)USB唤醒中断:在中断向量表中的位置是42。这个中断在USB设备从暂停模式唤醒时产生,唤醒事件由USB_ISTR寄存器的WKUP位标识。 2)USB高优先级中断:在中断向量表中的位置是19。这个中断仅由USB同步(Isochronous)模式传输或双缓冲块(Bulk)传输模式下的正确传输事件产生,正确传输事件由USB_ISTR寄存器的CTR位标识。 3)USB低优先级中断:在中断向量表中的位置是20。这个中断由所有其它的USB事件产生,例如正确传输(不包括同步模式和双缓冲块模式)、USB复位等,事件标志位在USB_ISTR寄存器中。 在STM32的USB开发包的例子中包含了上述中断的处理,例如在USB扬声器的例子中,CTR_HP函数处理USB高优先级中断;在所有例子中都有USB_Istr()函数处理USB低优先级中断   =============================================================================== 汇总4:如何使用STM32的USB库支持控制端点0,来自:http://bbs.ednchina.com/BLOG_ARTICLE_242276.HTM 首先我们先回顾一下控制端点的传输方式: 控制端点的传输有三个阶段,SETUP阶段、数据阶段和状态阶段;数据阶段又分为数据入(DATA IN)和数据出(DATA OUT),控制端点传输可以没有数据阶段;状态阶段有状态入(STATUS IN)和状态出(STATUS OUT)。 总结起来,控制端点有如下三种可能的传输过程(以下括号中的0或1表示DATA0或DATA1传输): 一、 SETUP DATA_IN(0) DATA_IN(1) DATA_IN(0) ...... STATUS_OUT(1) 二、 SETUP DATA_OUT(0) DATA_OUT(1) DATA_OUT(0) ...... STATUS_IN(1) 三、 SETUP STATUS_IN(1) 这里做一个约定,把上述过程一定义为“数据入过程”,过程二定义为“数据出过程”,过程三定义为“无数据过程”。所有的USB控制端点的数据传输都可以而且只用这三种传输过程表示。HID的SET_REPORT是数据出过程,HID的GET_REPORT是数据入过程,USB的GET DEVICE DESCRIPTOR是数据入过程,USB的SET CONFIGURATION是无数据过程,等等。 接下来,我们看看STM32的USB库是如何处理控制端点0的传输。 根据USB协议,每个SETUP包都由8个字节构成,用户程序可以通过结构体Device_Info(类型DEVICE_INFO)访问SETUP包的数据,因为在整个的USB处理中都要用到结构体Device_Info的内容,库中定义了一个全局的指针pInformation指向这个结构体,用户可以通过这个指针访问结构体的内容。 对应SETUP包的8个字节,用户可以用下述方式访问: pInformation-USBbmRequestType (字节类型) pInformation-USBbRequest (字节类型) pInformation-USBwValue (双字节类型) pInformation-USBwIndex (双字节类型) pInformation-USBwLength (双字节类型) 使用pInformation-USBwValue0访问wValue的低字节,pInformation-USBwValue1访问wValue的高字节。 使用pInformation-USBwIndex0访问USBwIndex的低字节,pInformation-USBwIndex1访问USBwIndex的高字节。 使用pInformation-USBwLength0访问USBwLength的低字节,pInformation-USBwLength1访问USBwLength的高字节。 通过分析SETUP包的8个字节,可以判断出一个SETUP的传输过程是属于数据入过程、数据出过程还是无数据过程。STM32的USB库中处理了所有的USB协议文本中定义的标准SETUP命令,对于USB协议文本中未定义的命令,USB库按照数据入过程、数据出过程或无数据过程通过回调函数交给用户程序处理。 全局变量Device_Property(DEVICE_PROP类型)封装了所有的回调函数,DEVICE_PROP定义如下: typedef struct _DEVICE_PROP { void (*Init)(void); // 设备初始化回调函数 void (*Reset)(void); // USB复位回调函数 void (*Process_Status_IN)(void); // STATUS_IN阶段处理回调函数 void (*Process_Status_OUT)(void); // STATUS_OUT阶段处理回调函数 RESULT (*Class_Data_Setup)(u8 RequestNo); // 数据入/出过程处理回调函数 RESULT (*Class_NoData_Setup)(u8 RequestNo); // 无数据过程处理回调函数 RESULT (*Class_Get_Interface_Setting)(u8 Interface, u8 AlternateSetting); // GET_INTERFACE 回调函数 u8* (*GetDeviceDescriptor)(u16 Length); // GET_DEVICE_DESCRIPTION回调函数 u8* (*GetConfigDescriptor)(u16 Length); // GET_CONFIGURATION_DESCRIPTION回调函数 u8* (*GetStringDescriptor)(u16 Length); // GET_STRING_DESCRIPTION回调函数 u8 MaxPacketSize; // 最大包长度 } DEVICE_PROP; 结合SETUP的三种传输过程,用户通过实现不同的回调函数即可完成对各种USB类命令的处理,下面以HID的SET REPORT为例说明。 在介绍具体实现之前,先介绍一下另一个回调函数CopyRoutine的概念,这个函数的原型是: u8 *CopyRoutine(u16 length); // 返回一个缓冲区指针 USB库通过这个函数获得用户的数据缓冲区地址,从而可以在数据出过程中把收到的数据拷贝到用户缓冲区,或在数据入过程中把用户缓冲区的数据拷贝到USB发送缓冲区。每个数据出过程可能有若干次DATA_OUT传输,USB库每完成一次这样的传输都会调用一次回调函数CopyRoutine,参数length是本次传输所收到的数据字节数目,CopyRoutine必须返回一个缓冲区指针,这个缓冲区必须能够容纳length字节的数据,CopyRoutine返回到USB库之后,USB库将把收到的数据拷贝到用户指定的缓冲区。同样每个数据入过程也可能有若干次DATA_IN传输,每次需要向主机传输数据时,USB库都会调用一次回调函数CopyRoutine,参数length是本次传输所要发送的数据字节数目,CopyRoutine必须返回一个缓冲区指针,这个缓冲区中必须包含要求的数据字节,USB库将把用户缓冲区的数据拷贝到USB缓冲区并择机发送出去。 当以length=0调用CopyRoutine时,CopyRoutine需要返回用户缓冲区的长度,因为CopyRoutine的返回类型是一个指针,所以需要通过类型的强制转换返回缓冲区长度。这个功能是为了处理用户缓冲区的长度与主机SETUP数据请求长度不符的情况,而不至于造成用户缓冲区的溢出。 介绍完上述若干概念和回调函数,再看SET_REPORT的实现就很容易了。 SET_REPORT是一个数据出过程,因此需要实现一个Class_Data_Setup回调函数,示例如下: RESULT HID_Data_Setup(u8 RequestNo) { u8 *(*CopyRoutine)(u16 length); CopyRoutine = NULL; if (pInformation-USBbmRequestType == CLASS_REQUEST|INTERFACE_RECIPIENT RequestNo == SET_REPORT) CopyRoutine = My_Data_Request; if (CopyRoutine == NULL) return USB_UNSUPPORT; pInformation-Ctrl_Info.CopyData = CopyRoutine; pInformation-Ctrl_Info.Usb_wOffset = 0; pInformation-Usb_wLength = (*CopyRoutine)(0); return USB_SUCCESS; } // End of HID_Data_Setup() u8 My_Buffer ; u8 *My_Data_Request(u16 length) { if (length == 0) return (u8*)10; // 假定你的REPORT长度和Buffer长度为10 return My_Buffer; } 上面介绍的CopyRoutine用于把多次传输的数据包合并到一个完整的缓冲区中,因此只有到STATUS阶段才能够指导一次SETUP传输是否结束,所以用户程序需要在回调函数Process_Status_IN中处理从SET_REPORT接收到的数据。因为所有的回调函数都是USB中断处理的一部分,所以更好的办法是在Process_Status_IN中设置一个标记,然后在用户主程序中判断这个标记并做处理。 注意,STM32的USB库设计成以回调函数处理用户命令请求,包含类命令请求,是为了能够清晰地区分库程序和用户程序,使这两者不会混在一起,这样的好处是非常明显的,当USB库需要更新升级时,只需替换掉相应的程序模块,而不必修改用户已经完成的程序。 以上的介绍都可以在STM32 USB库的说明手册中找到。 上述示意代码是以My_Buffer长度为10字节为例,而USB库的默认包长度为16字节,因此My_Data_Request并没有多包的处理。 关于多包的缓冲区处理的示意代码可以是这样的: u8 *My_Data_Request(u16 length) { if (length == 0) return (u8*)100; // 假定你的REPORT长度和Buffer长度为100 return My_Buffer ; } 这里有一个库中使用的变量pInformation-Ctrl_Info.Usb_wOffset,这个变量回在传输每个数据包时候由库中的程序按数据包长度增加,如最大包长为16字节时,第一次调用My_Data_Request时Usb_wOffset=0,第二次调用My_Data_Request时Usb_wOffset=16,第三次调用My_Data_Request时Usb_wOffset=32,依此类推。这样就可以使用Usb_wOffset作为My_Buffer的下标从My_Data_Request返回。 对于提问“如何传递length?在上面没有看到这个参数的传递过程”的回答: 参数length是用于检测缓冲区长度是否足够,如果你有足够长的缓冲区,可以不必检测,上述示例中使用了一个固定的缓冲区,所以不必使用参数length检测缓冲区长度。
相关资源