
SETUP事件正确接收后,根据该事件提供的请求类型进行对主机的响应。SETUP数据结构的wLength字段说明的是请求返回或者提供的数据长度。 如果判断出的请求信息错误或者说不被支持,STM32 USB设备需要中断此次请求: ' _' h a* f* Z- ^* l. Z% d SetEPR_RXStatus(ENDP0, EP_RX_VALID); SetEPR_TXStatus(ENDP0, EP_TX_STALL); & c! c; D i( S5 A& F 正确获取到请求信息后,如果wLength为0,设备需要发送一个0长度数据包以响应主机: // ***************************************************************************** , e8 {8 J9 C- `8 n" s; p5 m* F // Function Name : SETUP0_Trans0Data // Description : & F& K! n% U' X( m- _ // Input : $ p/ B9 w, j! F" L5 } // Output : 4 D. ]' ~; ~$ ? A/ `) u // Return : // ***************************************************************************** . [, b6 W' \) o9 F* v RESULT SETUP0_Trans0Data(void) { ) i- e+ f& z9 B // Send 0-length data frame as ACK to host 3 R8 j8 O& j6 T/ ~: j SetBuffDescTable_TXCount(ENDP0, 0); C) G' W) j: L( O SetEPR_RXStatus(ENDP0, EP_RX_NAK); SetEPR_TXStatus(ENDP0, EP_TX_VALID); 7 [1 I/ ~, G" j" D return RESULT_SUCCESS; + S2 g* D' l& h8 {0 K3 w } ) Q0 s! x4 P0 I5 L: R9 b4 M 如果wLength不为0,设备则需要根据请求的数据长度发送数据包以响应主机: // ***************************************************************************** " I2 M; U2 M! K. F // Function Name : SETUP0_TransData // Description : & j* M6 w B9 [& f6 d1 |; a5 m // Input : // Output : // Return : // ***************************************************************************** 4 I1 z* N( N5 Y/ F& e RESULT SETUP0_TransData(void) { $ z+ ]( X; e* ] unsigned short wLength = vsDeviceInfo.TransInfo.wLength; " m; P4 ^3 m7 r! U unsigned short wOffset = vsDeviceInfo.TransInfo.wOffset; 7 n" v) _+ G8 B; h2 ]! I3 L unsigned short wMaxSize = vsDeviceInfo.TransInfo.wPacketSize; if(wLength) 2 S% \4 R0 I8 T/ f3 T8 A { if(wLength > wMaxSize) { 0 ~ x, F* ?/ c6 \8 K I& e& | wLength = wMaxSize; ( [7 e! M; @& I+ y2 x1 Y } ) }1 b; U* m D$ a& X# N0 R, q ) x2 J- Q! V& ?( K9 y/ z // Copy the transfer buffer to the endpoint0's buffer : C1 }3 C4 G/ x% _1 ~, A* Z" p/ T BufferCopy_UserToPMA( vsDeviceInfo.TransInfo.pBuffer+wOffset, // transfer buffer ! @1 ^1 e2 G+ m9 }. J GetBuffDescTable_TXAddr(ENDP0), // endpoint 0 TX address wLength); SetBuffDescTable_TXCount(ENDP0, wLength); SetEPR_RXStatus(ENDP0, EP_RX_NAK); % C1 U$ T% Z3 L1 Y* M7 i8 X7 A) ~ SetEPR_TXStatus(ENDP0, EP_TX_VALID); ( _. G# Q0 m" o) }* P & @5 E: H+ g. V7 X# k" v // Update the data lengths vsDeviceInfo.TransInfo.wLength -= wLength; vsDeviceInfo.TransInfo.wOffset += wLength; # C) B2 E! @* L8 q 0 ^8 ]% r4 j! c return RESULT_LASTDATA; } 6 R- |# e/ d' ?2 C6 ]6 f * J% Y. M1 P4 e8 }4 M6 C# }( f return RESULT_SUCCESS; # S# T" {( Y" G0 c" [7 a7 j } 0 {( \* u2 V/ \ ^0 i* l% o# Z2 y 如果发送的数据长度大于端点设置的最大数据包长度,数据将分割为若干次发送,记录发送数据的状态包含在结构体TRANSFER_INFO中: 3 z6 z, I" _/ X0 h // ***************************************************************************** ! p- } n0 i; M" ?5 z; u' c) Q // TRANSFER_INFO // ***************************************************************************** 9 e* n' \/ W2 q$ d3 X" ~* x9 X0 l typedef struct _TRANSFER_INFO . `! H7 f9 n, q7 I. P k { 5 C# v$ Y( p0 V: R% f$ @$ { unsigned short wLength; // total lengths data will be transmit 8 ~) q+ K$ U) F unsigned short wOffset; // number of data be transmited 7 |* [: f" j! M& V% X8 o unsigned short wPacketSize; // endpoints packet max size unsigned char* pBuffer; // address of data buffer } ( A" v% n9 T4 U8 }% f' L: l TRANSFER_INFO, 4 z$ n6 O# _# p1 V* g$ J4 M# R *PTRANSFER_INFO; TRANSFER_INFO.wLength记录发送的数据长度,如果非0,表示有数据需要被发送。 TRANSFER_INFO.wOffset记录已发送的数据长度,用以确定数据缓冲TRANSFER_INFO.pBuffer的偏移量。 0 j8 ]' q/ } Y2 Q4 d% H1 K ' N M5 x4 o- ^9 t 需要了解的一点:USB主机向USB设备正确发送一请求后(这部分的处理由硬件完成),USB主机将间隔若干次的向USB设备索取响应数据,STM32 USB TX状态为NAK说明不响应USB主机,USB主机在超时后退出此次请求;TX状态为STLL说明中断此次请求,USB主机将无条件退出请求;TX状态为 VALID说明设备已准备好数据发送,USB主机将从USB设备读取数据。 # E5 N# V) ]6 S) D g6 \9 [9 g) Y 以非0长度数据请求的GET_DESCRIPTOR请求为例的响应过程: 1 E3 t- y# w7 W. e- v5 Q) j7 { CTR_SETUP0()->SETUP0_Data()->SR_GetDescriptor()->SETUP0_TransData() ! w* ^5 U$ z, s4 ?* s1 @* ~# k+ B RESULT SR_GetDescriptor(void) { 4 d# p3 I; r# X' J( L: H // RequestType: device->host, standard request and device recipient : h- n1 ]/ r" y( e) C+ Y8 V if(vsDeviceInfo.SetupData.bmRequestType == RT_D2H_STANDARD_DEVICE) { . U/ K S- w- _5 { // SetupData.wValue.b.MSB: descriptor type - L- X/ L7 b9 u# L // SetupData.wValue.b.LSB: descriptor index 7 X# e4 o6 S) ?3 Q0 p switch(vsDeviceInfo.SetupData.wValue.b.MSB) % d/ n, F0 n: o% @5 J* ? { case DESCRIPTOR_DEVICE: return SR_GetDescriptor_Device(); case DESCRIPTOR_CONFIG: return SR_GetDescriptor_Config(); : J2 N" I6 P" K! { case DESCRIPTOR_STRING: return SR_GetDescriptor_String(); 8 ]- e; Q; z; [7 A default: return RESULT_UNSUPPORT; / r5 N S* H4 v! [1 q } & O$ O/ j9 J% W } 2 G1 I. A) ^3 U) D* N $ v( y6 \- p# F" w4 h3 J return RESULT_UNSUPPORT; " n7 T* z9 v7 G9 y4 E } . a; K: P8 c W( M/ o GET_DESCRIPTOR请求属于USB协议中的标准请求(standard request)并且数据方向为设备至主机(device->host),分设备描述符、配置描述符、字符串描述符三种。已设备描述符为例: / I8 g3 ~. \3 I! n6 }( Q$ V* W RESULT SR_GetDescriptor_Device(void) . k8 Q0 d6 U, J: y { $ q) E4 {! Z: ~) h, W // Assigned the device descriptor to the transfer 2 j- o9 V) t- T$ k+ ? ^ vsDeviceInfo.TransInfo.wOffset = 0; vsDeviceInfo.TransInfo.wPacketSize = ENDP0_PACKETSIZE; vsDeviceInfo.TransInfo.pBuffer = DescBuffer_Device.pBuff; e4 `* V& A6 W6 d& y2 {$ ]# ?7 r vsDeviceInfo.TransInfo.wLength = DescBuffer_Device.wLen; 9 m: F: @; P3 x3 E( l( x0 e vsDeviceInfo.eControlState = CS_GET_DESCRIPTOR; if(vsDeviceInfo.TransInfo.wLength > vsDeviceInfo.SetupData.wLength.w) { vsDeviceInfo.TransInfo.wLength = vsDeviceInfo.SetupData.wLength.w; } & B, g( w7 V6 G 9 A9 p6 W) r3 Z4 V return SETUP0_TransData(); 0 V& d& b4 s* |5 t/ `9 |* G: |" D } 这里说明了发送数据的长度、缓冲、偏移、端点包大小以及当前的控制状态,并说明了如果发送的数据长度超出请求的数据长度,则将舍弃超出的部分。数据配置好后,调用SETUP0_TransData()进行数据发送。 ) u' z' I( `0 v" a( n 在USB主机查询到USB设备准备就绪后,将读取出这些数据,完成后,USB设备将产生IN事件,此时将响应CTR_IN0()函数: // ***************************************************************************** // Function Name : CTR_IN // Description : ( _! t- _; `& P- k8 y& n // Input : // Output : / V% l) c( R5 ?- J% _, i1 t // Return : ' n( O7 K8 \7 k4 z+ }! r4 D9 M2 k // ***************************************************************************** & C) G( ~1 H5 i, {5 C Y" J- Q void CTR_IN0(void) { $ a& A5 n {. s/ ~( m; u5 y2 D switch(vsDeviceInfo.eControlState) - {1 }: o/ i& Z) R { / E7 u3 t. r4 s& z case CS_GET_DESCRIPTOR: 9 w6 g2 g* q c- N( q$ V1 B( ` if(SETUP0_TransData() == RESULT_SUCCESS) 2 D, w& v k, D { : |" n/ b9 C, } SetEPR_TXStatus(ENDP0, EP_TX_NAK); SetEPR_RXStatus(ENDP0, EP_RX_VALID); } break; 3 ?7 ] y% z: S5 `1 K 9 R ~0 T$ ]* r' `6 s case CS_SET_ADDRESS: ) q1 E1 V8 h5 K& _- d3 W2 M SetEPR_TXStatus(ENDP0, EP_TX_NAK); - _9 Y* h. F. P5 F3 f SetEPR_RXStatus(ENDP0, EP_RX_VALID); 3 M: H9 K$ C* _8 _9 v6 d: J2 U 3 D- p: T8 a' z2 b SetDADDR(0x0080 | vsDeviceInfo.bDeviceAddress); vsDeviceInfo.eDeviceState = DS_ADDRESSED; 6 f6 c2 K) t5 `' [3 c- ] break; 8 N. l( g, p- G' y0 l4 D, Y . n: c' x( E2 W4 `2 ?# m! W9 N2 ` case CS_SET_CONFIGURATION: , g/ C0 N# Y7 [, F$ H2 R' {; U SetEPR_TXStatus(ENDP0, EP_TX_NAK); 7 b2 g5 h4 }) x2 P2 ? SetEPR_RXStatus(ENDP0, EP_RX_VALID); vsDeviceInfo.eDeviceState = DS_CONFIGURED; break; & H, X% k' T# s/ ~- }. N- g# ^ default: . K# g( n6 p6 X: j- V break; 8 [; R/ P8 E$ S2 H8 _( N } } 0 m) I) C' o4 |' S/ _/ c 再这如果响应GET_DESCRIPTOR请求发送的数据如果全部发送完毕,SETUP0_TransData()返回RESULT_SUCCESS,并设置TX状态为NAK;否则返回RESULT_LASTDATA,将继续发送剩余的数据直到数据全部被发送。至此,整个的GET_DESCRIPTOR请求过程完成。 0长度的数据请求在发送0长度数据响应后,因为不存在可能还未传送的数据,因而IN事件后直接结束此次请求。 在数据方向为USB主机->USB设备时,如果正确接收到数据,将响应CTR_OUT0()函数,处理过程类同CTR_IN0()函数。 在USB设备的枚举过程中,USB的一些描述符数据结构需要了解,具体在USB协议中有详细的说明,在usb_desc(.c/.h)文件中,定义了这些结构,这些结构是特定的: 8 p( A8 E# A; m; I- m4 ~! ~ ! ^' X' H8 F2 Q) o. K1 K( |" _4 j 设备描述符:长度、格式固定,其中VENDOR_ID与PRODUCT_ID决定上位机驱动的识别。设备分属类别决定了设备的性质,如果为自定义USB设备,设备分属类别值为0,同时上位机驱动必须配合编写;如果为标准USB设备,则必须使用这些标准设备的驱动、数据结构等等,条件是你必须了解这些标准设备的一些信息,好处是省去一些麻烦的驱动编写。 $ k6 b" U& d+ t- v. K" K) B const unsigned char cbDescriptor_Device[DESC_SIZE_DEVICE] = & N5 ?3 \" U2 T5 o. ~& L6 v: [4 T9 B/ p { # z. b t; E' E( ^4 m+ v+ t( F9 u DESC_SIZE_DEVICE, // bLength: 18 $ j }& N- n$ t( _/ Z: |5 g$ ^ DESCRIPTOR_DEVICE, // descriptor type + G4 Z0 b/ d% N0 C4 d) _$ P! A 0x00, // bcdUSB LSB: USB release number -> USB2.0 : R9 u0 P6 {5 N9 @& j 0x02, // bcdUSB MSB: USB release number -> USB2.0 0x00, // bDeviceClass: Class information in the interface descriptors . J1 E' i3 `9 \7 F. ^2 P5 } 0x00, // bDeviceSubClass: E) j, M1 @& M1 P8 |- O 0x00, // bDeviceProtocol: & v+ v, u( T- a6 q$ p 0x40, // bMaxPacketSize0: LowS(8), FullS(8,16,32,64), HighS(64) + L( L# D* r# R* b3 F F# F LOWORD(VENDOR_ID), // idVendor LSB: 3 o4 e9 A5 n8 A HIWORD(VENDOR_ID), // idVendor MSB: ; x2 C9 H, S3 [% Y$ v ?& A6 L LOWORD(PRODUCT_ID), // idProduct LSB: ! R6 F+ H+ A7 U( ` HIWORD(PRODUCT_ID), // idProduct MSB: LOWORD(DEVICE_VERSION), // bcdDevice LSB: HIWORD(DEVICE_VERSION), // bcdDevice MSB: 8 j \# P! S& ^5 G, J + A. a# A9 @: q; B7 W' R5 d 0x01, // iManufacturer: Index of string descriptor describing manufacturer 0x02, // iProduct: Index of string descriptor describing product 0x03, // iSerialNumber: Index of string descriptor describing the device serial number 0x01 // bNumConfigurations: number of configurations ' Y+ b2 t& ~8 q, q+ f }; 4 t* c- R4 S9 u5 j 配置描述符:前9个字节格式固定,后面紧跟的各种描述结构跟实际配置有关,每增加一种描述结构,该描述结构的第一字节说明了结构的长度,第二直接说明了结构的类型。在配置描述符中一般包含配置描述、接口描述、端点描述,如果需要同样可增加自定义的描述。使用标准USB设备类别时,配置描述符的结构也必须满足此类标准设备的数据结构。 const unsigned char cbDescriptor_Config[DESC_SIZE_CONFIG] = / k/ D/ n' r: l5 W2 d" O { // Descriptor of configuration 6 o( p. c0 \' K' o3 ] 0x09, // lengths DESCRIPTOR_CONFIG, // descriptor type DESC_SIZE_CONFIG, // Total configuration descriptor lengths LSB ) }- Z9 }" d6 x7 d# f% q6 x9 J 0x00, // Total configuration descriptor lengths MSB 0x01, // bNumInterfaces: Total number of interfaces # K, z5 q& A) v- @7 a) i 0x01, // bConfigurationValue: Configuration value 0x00, // iConfiguration: Index of string descriptor describing the configuration 6 V* K5 n5 X ]. \' o 0xA0, // bmAttributes: bus powered // bit 4...0 : Reserved, set to 0 4 `: ]1 U. ]* m+ ~6 ^ // bit 5 : Remote wakeup (1:yes) ( |' H2 }: r4 {6 C+ q3 T' v+ [0 v // bit 6 : Self power (1:yes) * e( Q! x2 s s. P // bit 7 : Reserved, set to 1 6 \8 W) d& s7 [9 X 8 c1 v3 i% N" }; X) g' G0 k 0x32, // bMaxPower: this current is used for detecting Vbus = 100mA . R5 L8 ?, K, J$ L- L 9 T0 w( O+ [- b/ y7 |1 T0 Z8 m# @5 j // Descriptor of interface 0x09, $ v( q& j6 a( r0 ?9 _1 W/ F DESCRIPTOR_INTERFACE, [ Z- q2 q; N6 ? 0x00, // bInterfaceNumber: Number of Interface ) B7 o# x3 o* S- N1 ] 0x00, // bAlternateSetting: Alternate setting + U! K S. I& o2 d0 I 0x02, // bNumEndpoints: Number of endpoints except EP0 ; _% Z, H; g* r- y# A 0x00, // bInterfaceClass: % k5 [& a5 O% z5 ] 0x00, // bInterfaceSubClass: " ?5 P8 i0 y+ Z- [, V' y! X) I' ] 0x00, // nInterfaceProtocol: 0 g# K; s8 `8 i# q& f 0x00, // iInterface: Index of string descriptor describing the interface , P- x v3 e+ M$ q7 h4 }; g , {) j# c: o+ O+ G // Descriptor of endpoint1 OUT 0x07, / y! F) p: P7 z; n3 j DESCRIPTOR_ENDPOINT, 0x01, // bEndpointAddress + b( c" f" i. I // bit 3...0 : the endpoint number // bit 6...4 : reserved // bit 7 : 0(OUT), 1(IN) 0x03, // bmAttributes // bit 1...0 : Transfer type 0 E3 m( F b" m9 r // 00(CONTROL), 01(ISOCHRONOUS), 10(BULK), 11(INTERRUPT) // bit 3...2 : Synchronization type // 00(No Synch), 01(Asynchronous), 10(Adaptive), 11(Synchronous) // bit 5...4 : Endpoint Usage type // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) 0 \& y; o) V, c; i6 v // bit 7...6 : Reserved, must be zero 8 x6 { d/ J; w 0x40, // packet size LSB 0x00, // packet size MSB 2 _. C6 x1 U7 |" s7 Q6 M$ g1 V: q3 X 0x20, // polling interval time: 32ms // Descriptor of endpoint2 IN 0x07, ; n$ X9 k* h7 G) P# y! Z DESCRIPTOR_ENDPOINT, & t1 m3 u4 a7 k 0x82, // bEndpointAddress // bit 3...0 : the endpoint number // bit 6...4 : reserved // bit 7 : 0(OUT), 1(IN) 4 [4 I( Y, H) K. N. P9 j 0x03, // bmAttributes // bit 1...0 : Transfer type // 00(CONTROL), 01(ISOCHRONOUS), 10(BULK), 11(INTERRUPT) // bit 3...2 : Synchronization type ) l$ P1 X. P) u: w. R4 |4 { // 00(No Synch), 01(Asynchronous), 10(Adaptive), 11(Synchronous) // bit 5...4 : Endpoint Usage type // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) // bit 7...6 : Reserved, must be zero % ?( N( i. e/ y: p# L5 S 0x40, // packet size LSB + n( ~! R L- B6 Q2 U7 F* ? 0x00, // packet size MSB & z2 L& e3 e; x0 N 0x20 // polling interval time: 32ms / ^% o) w3 Z4 H }; 1 k; c1 Y& E# E' c) F% b! V 字符串描述符:定义了与设备有关的一些信息,常见的为以下四种,如果有需要,同样可以定义自己的字符串描述符。 0 c8 Y; @; Z9 T5 ^ const unsigned char cbDescriptor_StringLangID[DESC_SIZE_STRING_LANGID] = ; {, Z. e9 E! `/ e2 d% h' I( u9 A { DESC_SIZE_STRING_LANGID, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor * d1 D# l1 R6 U( f" D* W 0x09, // LangID LSB: 0x04 // LangID MSB: 0x0409(U.S. English) }; + d* V% W+ w, v9 Z9 V& y! r+ k: k$ X const unsigned char cbDescriptor_StringVendor[DESC_SIZE_STRING_VENDOR] = { % O& h5 y! G( R7 k% v DESC_SIZE_STRING_VENDOR, // bLength " S0 q! u& t# N8 j Q7 }5 p+ q DESCRIPTOR_STRING, // bDescriptorType = String Descriptor 2 M" J4 l C8 \$ C$ ^4 w- r // String: "LaBiXiaoXiaoXin" 'L',0, 'a',0, 'B',0, 'i',0, 'X',0, 'i',0, 'a',0, 'o',0, 'X',0, 'i',0, 'a',0, 'o',0, 'X',0, 'i',0, 'n',0 }; 1 _1 n( u8 x* K# _) [5 @ const unsigned char cbDescriptor_StringProduct[DESC_SIZE_STRING_PRODUCT] = { : |' p. w( ` T- S0 S DESC_SIZE_STRING_PRODUCT, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor // String: "STM32 ezUSB-CORE V1.01" 'S',0, 'T',0, 'M',0, '3',0, '2',0, ' ',0, 'e',0, 'z',0, 'U',0, 'S',0, 'B',0, '-',0, 'C',0, 'O',0, 'R',0, 'E',0, ' ',0, 'V',0, '1',0, '.',0, '0',0, '1',0 9 U% x' A# m& D5 G }; const unsigned char cbDescriptor_StringSerial[DESC_SIZE_STRING_SERIAL] = 6 v/ w; n) u8 y" j1 g1 v { : \# w; q+ N1 q DESC_SIZE_STRING_SERIAL, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor ( Z* L5 l1 P7 L ( M+ V) \1 ^. C) a. W, ~ // String: "ezUSB-CORE Demo 2008/11/18" # p9 l- W+ k0 r* q 'e',0, 'z',0, 'U',0, 'S',0, 'B',0, '-',0, 'C',0, 'O',0, 'R',0, 'E',0, ' ',0, 'D',0, 'e',0, 'm',0, 'o',0, ' ',0, '2',0, '0',0, '0',0, '8',0, '/',0, '1',0, '1',0, '/',0, '1',0, '8',0 9 j `" ?, k# p, N4 @/ n6 m }; ( C6 w$ D0 v4 p8 a 了解这些描述符的用法以及作用,最好的方法的是编写自定义的USB上位机驱动以及应用程序,这样你可以深刻了解USB设备与主机间的数据交换方式以及实现手段,下篇将开始介绍USB上位机驱动以及应用程序的编写以及开发环境的建立,STM32 USB设备的固件程序如有什么疑问,请朋友们多花几分种时间留言、讨论,共同学习与进步。 |
最全USB HID开发资料,悉心整理一个月,亲自测试
实战经验 | 选择USBX模块生成USB CDC ACM无PD的项目
STM32 USB HID键盘例程
刘氓兔的杂谈【001】-片上USB 高速PHY
【经验分享】在进行 USB CDC 类开发时,无法发送 64整数倍的数据
【源码】STLINK-V3MINI 高速USB仿真器,成功改刷【高速CMSIS-DAP】
在线直播|无需编写任何代码即可在STM32上实现USB-C Power Delivery
STM32 USB CDC 虚拟多串口
圈圈发布USB图书第二版有感,以及分享一些我学习USB过程...
USB Audio设计与实现
回复:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)
RE:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)
RE:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)