
SETUP事件正确接收后,根据该事件提供的请求类型进行对主机的响应。SETUP数据结构的wLength字段说明的是请求返回或者提供的数据长度。 如果判断出的请求信息错误或者说不被支持,STM32 USB设备需要中断此次请求: $ A& K1 K+ X) N) z! I/ t SetEPR_RXStatus(ENDP0, EP_RX_VALID); " q! a% _2 r" i SetEPR_TXStatus(ENDP0, EP_TX_STALL); 4 J5 c& w% j! l/ g8 i . V# q. w. y( o, X; @" A& S( C! Y 正确获取到请求信息后,如果wLength为0,设备需要发送一个0长度数据包以响应主机: 6 d2 m) r' J$ B, ~# Y+ q // ***************************************************************************** // Function Name : SETUP0_Trans0Data ) k9 @' T$ |' s! b7 z! a // Description : // Input : + V( U) g; o3 U) G* C8 k4 d) X // Output : 1 D8 G! q, E$ ^3 ^ // Return : * t" P2 n0 K' m, {* V a // ***************************************************************************** RESULT SETUP0_Trans0Data(void) 4 p$ ]2 _: v1 `3 A { // Send 0-length data frame as ACK to host SetBuffDescTable_TXCount(ENDP0, 0); SetEPR_RXStatus(ENDP0, EP_RX_NAK); SetEPR_TXStatus(ENDP0, EP_TX_VALID); return RESULT_SUCCESS; } 如果wLength不为0,设备则需要根据请求的数据长度发送数据包以响应主机: // ***************************************************************************** C5 r, e3 _+ `- r // Function Name : SETUP0_TransData // Description : // Input : // Output : + c2 V# t8 d f8 b+ o6 M // Return : % e4 u; U7 E/ B; p8 i0 b // ***************************************************************************** RESULT SETUP0_TransData(void) { unsigned short wLength = vsDeviceInfo.TransInfo.wLength; 2 A$ y0 H3 v! M5 m g- `) o unsigned short wOffset = vsDeviceInfo.TransInfo.wOffset; & H3 o5 [& C1 x. {% ^* B unsigned short wMaxSize = vsDeviceInfo.TransInfo.wPacketSize; - Z& M' h6 d. j3 W; U4 {1 V 3 N# ]( x' H' f( r) h C. z if(wLength) { if(wLength > wMaxSize) 5 Q2 u2 R D/ t* b l2 C1 G7 S1 z { , A& I. E# o O wLength = wMaxSize; 2 M I" {- `. h& b } & f1 m! r/ k" E4 S // Copy the transfer buffer to the endpoint0's buffer BufferCopy_UserToPMA( vsDeviceInfo.TransInfo.pBuffer+wOffset, // transfer buffer GetBuffDescTable_TXAddr(ENDP0), // endpoint 0 TX address wLength); SetBuffDescTable_TXCount(ENDP0, wLength); SetEPR_RXStatus(ENDP0, EP_RX_NAK); * Y' K% U- i2 P; } u' s SetEPR_TXStatus(ENDP0, EP_TX_VALID); ' t+ H0 u/ a* {6 N- R+ T1 C & c: b( L: b4 z5 I* G7 q) t$ q // Update the data lengths vsDeviceInfo.TransInfo.wLength -= wLength; . _; Q% U! i( K X6 p! Z5 A: Q vsDeviceInfo.TransInfo.wOffset += wLength; + |) a3 b: N$ Z- @' A return RESULT_LASTDATA; } # s R. C$ g/ g4 e# S return RESULT_SUCCESS; } 如果发送的数据长度大于端点设置的最大数据包长度,数据将分割为若干次发送,记录发送数据的状态包含在结构体TRANSFER_INFO中: // ***************************************************************************** 4 ^0 b2 {- \% s* E7 O$ r // TRANSFER_INFO / n* o: b3 |9 m2 H$ I! m- w( r' s // ***************************************************************************** typedef struct _TRANSFER_INFO { unsigned short wLength; // total lengths data will be transmit unsigned short wOffset; // number of data be transmited unsigned short wPacketSize; // endpoints packet max size unsigned char* pBuffer; // address of data buffer ( q3 f9 T5 c. o* t } 4 }6 A/ m: }/ e: W$ l TRANSFER_INFO, *PTRANSFER_INFO; 2 H( z6 J; K, i2 R TRANSFER_INFO.wLength记录发送的数据长度,如果非0,表示有数据需要被发送。 TRANSFER_INFO.wOffset记录已发送的数据长度,用以确定数据缓冲TRANSFER_INFO.pBuffer的偏移量。 ; E( g& v. I, }+ K* c 需要了解的一点:USB主机向USB设备正确发送一请求后(这部分的处理由硬件完成),USB主机将间隔若干次的向USB设备索取响应数据,STM32 USB TX状态为NAK说明不响应USB主机,USB主机在超时后退出此次请求;TX状态为STLL说明中断此次请求,USB主机将无条件退出请求;TX状态为 VALID说明设备已准备好数据发送,USB主机将从USB设备读取数据。 以非0长度数据请求的GET_DESCRIPTOR请求为例的响应过程: 5 m, f' |& r* |6 Q- O1 T4 k CTR_SETUP0()->SETUP0_Data()->SR_GetDescriptor()->SETUP0_TransData() . c Q* {' v$ c, _ RESULT SR_GetDescriptor(void) { // RequestType: device->host, standard request and device recipient 7 v' Z8 t2 \4 x" t0 _; b if(vsDeviceInfo.SetupData.bmRequestType == RT_D2H_STANDARD_DEVICE) 4 e& N* T: n0 E8 v1 N& _. R { ( |2 t1 n- Y: T& N+ Z // SetupData.wValue.b.MSB: descriptor type // SetupData.wValue.b.LSB: descriptor index switch(vsDeviceInfo.SetupData.wValue.b.MSB) 5 s0 I7 w6 F( y4 o7 M$ a { case DESCRIPTOR_DEVICE: return SR_GetDescriptor_Device(); case DESCRIPTOR_CONFIG: return SR_GetDescriptor_Config(); 4 L+ G/ M( F/ h9 K+ ^2 k2 S case DESCRIPTOR_STRING: return SR_GetDescriptor_String(); $ T& H# k9 Q; r% |7 G " Y* H7 v. \) b; C/ \) b0 I default: return RESULT_UNSUPPORT; 3 `, o+ Z0 { ~% I } " N+ ]$ o( G4 G8 i' }' f } 2 c9 _% ]" |( j return RESULT_UNSUPPORT; ( v( V1 C3 x8 Z- j7 } } GET_DESCRIPTOR请求属于USB协议中的标准请求(standard request)并且数据方向为设备至主机(device->host),分设备描述符、配置描述符、字符串描述符三种。已设备描述符为例: RESULT SR_GetDescriptor_Device(void) { // Assigned the device descriptor to the transfer " e& v- h" P0 {6 q( c0 t" X vsDeviceInfo.TransInfo.wOffset = 0; vsDeviceInfo.TransInfo.wPacketSize = ENDP0_PACKETSIZE; vsDeviceInfo.TransInfo.pBuffer = DescBuffer_Device.pBuff; " _( h! F- u( V vsDeviceInfo.TransInfo.wLength = DescBuffer_Device.wLen; 8 v8 ?1 w" W; @" n) J3 E; F vsDeviceInfo.eControlState = CS_GET_DESCRIPTOR; if(vsDeviceInfo.TransInfo.wLength > vsDeviceInfo.SetupData.wLength.w) 9 X$ O: s, D8 S% x ~7 ^ { / s" o9 Q4 Z. `# M( H0 l0 \ vsDeviceInfo.TransInfo.wLength = vsDeviceInfo.SetupData.wLength.w; $ n$ w7 n, t+ c7 M4 e } 5 i* L( }7 }& g& n return SETUP0_TransData(); } 这里说明了发送数据的长度、缓冲、偏移、端点包大小以及当前的控制状态,并说明了如果发送的数据长度超出请求的数据长度,则将舍弃超出的部分。数据配置好后,调用SETUP0_TransData()进行数据发送。 ! H) m! M( W! d& u0 x! d$ G 在USB主机查询到USB设备准备就绪后,将读取出这些数据,完成后,USB设备将产生IN事件,此时将响应CTR_IN0()函数: 1 ]( f" ]8 | s! n/ U2 Z# ] // ***************************************************************************** + K% j* C0 {( J) J5 i \+ ?$ o // Function Name : CTR_IN b9 G7 ^2 r/ x" ]; l // Description : + c* p* Q2 U3 ^9 y& w // Input : / [0 ~( ]! a2 V! h // Output : ' ~/ |: b" O' M) g! J1 Q // Return : - T/ P- ]- _+ E // ***************************************************************************** void CTR_IN0(void) , G {. I) H8 Z3 h { switch(vsDeviceInfo.eControlState) { 4 K3 Y* n A7 Z) f; W+ h- h case CS_GET_DESCRIPTOR: if(SETUP0_TransData() == RESULT_SUCCESS) { SetEPR_TXStatus(ENDP0, EP_TX_NAK); SetEPR_RXStatus(ENDP0, EP_RX_VALID); } break; case CS_SET_ADDRESS: 5 Y+ y' d6 c* `1 G: q# n SetEPR_TXStatus(ENDP0, EP_TX_NAK); SetEPR_RXStatus(ENDP0, EP_RX_VALID); # S X' V9 ~ K; S7 M( x ) I; g# j* [* L' @. x# ` T SetDADDR(0x0080 | vsDeviceInfo.bDeviceAddress); vsDeviceInfo.eDeviceState = DS_ADDRESSED; : Z) {; J5 ?- B- p C break; 2 _6 V2 h; p0 N! P case CS_SET_CONFIGURATION: SetEPR_TXStatus(ENDP0, EP_TX_NAK); - ~8 R( q- n/ c$ G SetEPR_RXStatus(ENDP0, EP_RX_VALID); vsDeviceInfo.eDeviceState = DS_CONFIGURED; 2 L6 Y) A+ D+ s* U. x8 ^; D! q break; 9 l- v% f, N1 [/ q+ @; T5 J default: break; 5 C7 B9 U% {, s& X" Q0 H% X5 H/ C4 o } } 9 i5 u: h9 t6 q: s 再这如果响应GET_DESCRIPTOR请求发送的数据如果全部发送完毕,SETUP0_TransData()返回RESULT_SUCCESS,并设置TX状态为NAK;否则返回RESULT_LASTDATA,将继续发送剩余的数据直到数据全部被发送。至此,整个的GET_DESCRIPTOR请求过程完成。 2 x; h8 j- ~! @7 l @8 z$ V/ [ 0长度的数据请求在发送0长度数据响应后,因为不存在可能还未传送的数据,因而IN事件后直接结束此次请求。 ( L" ^9 Q8 h; u! R8 N: g# \ 在数据方向为USB主机->USB设备时,如果正确接收到数据,将响应CTR_OUT0()函数,处理过程类同CTR_IN0()函数。 ! p( Q* `1 r% u; {5 B 在USB设备的枚举过程中,USB的一些描述符数据结构需要了解,具体在USB协议中有详细的说明,在usb_desc(.c/.h)文件中,定义了这些结构,这些结构是特定的: 设备描述符:长度、格式固定,其中VENDOR_ID与PRODUCT_ID决定上位机驱动的识别。设备分属类别决定了设备的性质,如果为自定义USB设备,设备分属类别值为0,同时上位机驱动必须配合编写;如果为标准USB设备,则必须使用这些标准设备的驱动、数据结构等等,条件是你必须了解这些标准设备的一些信息,好处是省去一些麻烦的驱动编写。 , o1 H) h- R" k. d" B# M const unsigned char cbDescriptor_Device[DESC_SIZE_DEVICE] = 9 m* n1 c: I5 @* i/ U { ! |; |& ^: p$ j/ w) I' D DESC_SIZE_DEVICE, // bLength: 18 DESCRIPTOR_DEVICE, // descriptor type 9 R& R! @: k. I/ Z8 u . }+ J+ c- @6 H3 _ 0x00, // bcdUSB LSB: USB release number -> USB2.0 4 T M) T' p" L5 Y" L8 Z9 \ 0x02, // bcdUSB MSB: USB release number -> USB2.0 - K4 e% } c0 }% b/ l9 U 0x00, // bDeviceClass: Class information in the interface descriptors 0x00, // bDeviceSubClass: 0x00, // bDeviceProtocol: ' c9 ^0 s, C7 l' w 0x40, // bMaxPacketSize0: LowS(8), FullS(8,16,32,64), HighS(64) 6 M: Q* N, I j LOWORD(VENDOR_ID), // idVendor LSB: HIWORD(VENDOR_ID), // idVendor MSB: LOWORD(PRODUCT_ID), // idProduct LSB: HIWORD(PRODUCT_ID), // idProduct MSB: : }; B2 R4 ]" p9 N! R c2 _" b LOWORD(DEVICE_VERSION), // bcdDevice LSB: HIWORD(DEVICE_VERSION), // bcdDevice MSB: 1 v: W3 K! A+ M) [3 s# L 0x01, // iManufacturer: Index of string descriptor describing manufacturer % r- A+ v( Q* u6 T* y 0x02, // iProduct: Index of string descriptor describing product ( N6 ^+ t, {" y9 X' ~7 y 0x03, // iSerialNumber: Index of string descriptor describing the device serial number ; r1 {0 h, }6 _. E. u) C 0x01 // bNumConfigurations: number of configurations }; ) u: b0 F8 R4 k7 ?1 k 配置描述符:前9个字节格式固定,后面紧跟的各种描述结构跟实际配置有关,每增加一种描述结构,该描述结构的第一字节说明了结构的长度,第二直接说明了结构的类型。在配置描述符中一般包含配置描述、接口描述、端点描述,如果需要同样可增加自定义的描述。使用标准USB设备类别时,配置描述符的结构也必须满足此类标准设备的数据结构。 " k, C$ T6 F. [1 U8 E4 X const unsigned char cbDescriptor_Config[DESC_SIZE_CONFIG] = { // Descriptor of configuration 0x09, // lengths % N( F7 L$ k2 O; P7 f DESCRIPTOR_CONFIG, // descriptor type 8 H" N- N# o2 e) O. o) Y DESC_SIZE_CONFIG, // Total configuration descriptor lengths LSB 0x00, // Total configuration descriptor lengths MSB , p$ O& L% v+ \5 p 0x01, // bNumInterfaces: Total number of interfaces 0x01, // bConfigurationValue: Configuration value " v5 H3 ? N$ ?; Q 0x00, // iConfiguration: Index of string descriptor describing the configuration 0xA0, // bmAttributes: bus powered _1 X8 V( ]- \0 {( s; c5 g // bit 4...0 : Reserved, set to 0 ! z% u% e0 X0 U% L# K8 C9 q // bit 5 : Remote wakeup (1:yes) 3 o5 ?9 e1 M% x/ N; @! b' M, m" t // bit 6 : Self power (1:yes) : z( {" Y3 c% L- b // bit 7 : Reserved, set to 1 1 m$ z0 N M9 B) ~ ( q8 Y1 |- _6 p 0x32, // bMaxPower: this current is used for detecting Vbus = 100mA ( `& \, ?9 i! e+ S }1 R1 ^) F ( Z- g' Q5 ^" w E, f+ B _ // Descriptor of interface 0x09, ) j- _' u: Y: D' T DESCRIPTOR_INTERFACE, + F0 W1 O' W7 u 0x00, // bInterfaceNumber: Number of Interface % E* \* V& H& o% J0 r) ^ 0x00, // bAlternateSetting: Alternate setting 0x02, // bNumEndpoints: Number of endpoints except EP0 ; ?! H, ]( g: y2 Y 0x00, // bInterfaceClass: % f+ p7 g! c c 0x00, // bInterfaceSubClass: 0x00, // nInterfaceProtocol: 0x00, // iInterface: Index of string descriptor describing the interface 2 }1 n' I/ H; d5 f6 _! m" [ U3 M / K+ I) A1 L) P& { // Descriptor of endpoint1 OUT ! v3 |! O+ U6 y% R 0x07, $ D, j5 h1 N! {) p( u DESCRIPTOR_ENDPOINT, 4 f( x5 f3 b" @3 M0 D0 e 0x01, // bEndpointAddress // bit 3...0 : the endpoint number // bit 6...4 : reserved // bit 7 : 0(OUT), 1(IN) 0x03, // bmAttributes // bit 1...0 : Transfer type / g; E) g. s0 {; R" y& \ // 00(CONTROL), 01(ISOCHRONOUS), 10(BULK), 11(INTERRUPT) // bit 3...2 : Synchronization type // 00(No Synch), 01(Asynchronous), 10(Adaptive), 11(Synchronous) \. M- [& K- M s; ^( s5 d // bit 5...4 : Endpoint Usage type $ b. U) L. }5 c# I, A // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) // bit 7...6 : Reserved, must be zero ? z- V& W- r$ ?) c 0x40, // packet size LSB * n- K* x1 H( z; e 0x00, // packet size MSB 0x20, // polling interval time: 32ms // Descriptor of endpoint2 IN * J3 r* l, q5 v d+ Z) P2 ` 0x07, % i/ w. {7 E% h1 O DESCRIPTOR_ENDPOINT, * S0 M( c8 p# S- z8 y0 z) |6 A k 0x82, // bEndpointAddress // bit 3...0 : the endpoint number # n5 ]1 }) e; u/ z6 n // bit 6...4 : reserved // bit 7 : 0(OUT), 1(IN) ' U! h0 w6 H3 X" R 7 `; c, C+ r! ~. s/ C/ j 0x03, // bmAttributes // bit 1...0 : Transfer type : y, ~, _* ?# B: k) \& A // 00(CONTROL), 01(ISOCHRONOUS), 10(BULK), 11(INTERRUPT) // bit 3...2 : Synchronization type // 00(No Synch), 01(Asynchronous), 10(Adaptive), 11(Synchronous) J: ]9 X* y& Y2 ]3 {/ K" v // bit 5...4 : Endpoint Usage type // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) // bit 7...6 : Reserved, must be zero 1 r5 D! g" `7 o+ D; L' ^" A 0x40, // packet size LSB 2 y4 s6 p: D5 c9 z: I, h m 0x00, // packet size MSB ) Z8 m+ J! c7 \! R5 n 0x20 // polling interval time: 32ms }; 7 e6 Q) U$ Y) \4 t+ F* c 3 a' _( f' ^0 v 字符串描述符:定义了与设备有关的一些信息,常见的为以下四种,如果有需要,同样可以定义自己的字符串描述符。 1 L0 g- G6 s" }6 J( U const unsigned char cbDescriptor_StringLangID[DESC_SIZE_STRING_LANGID] = { DESC_SIZE_STRING_LANGID, // bLength 3 g2 x8 Y0 L3 y$ ^& t5 `; D DESCRIPTOR_STRING, // bDescriptorType = String Descriptor 4 k/ D) t+ K, [3 h9 U 0x09, // LangID LSB: ) x( U" V, Z- F9 I 0x04 // LangID MSB: 0x0409(U.S. English) }; 6 C+ A! e. w4 I# h$ J% }0 M 5 P1 i+ r, i: L* [& k3 X$ K const unsigned char cbDescriptor_StringVendor[DESC_SIZE_STRING_VENDOR] = 1 E0 e( k# I7 C% z( R6 q% W { DESC_SIZE_STRING_VENDOR, // bLength & X% ]; k8 P$ w E9 k1 V, L DESCRIPTOR_STRING, // bDescriptorType = String Descriptor // String: "LaBiXiaoXiaoXin" 'L',0, 'a',0, 'B',0, 'i',0, 'X',0, 'i',0, 'a',0, 'o',0, 3 I0 W$ J; V& l0 N" l* E 'X',0, 'i',0, 'a',0, 'o',0, 'X',0, 'i',0, 'n',0 $ `; u/ C; w0 { B3 e* N }; " @& j) U. p/ g+ v4 Z const unsigned char cbDescriptor_StringProduct[DESC_SIZE_STRING_PRODUCT] = { DESC_SIZE_STRING_PRODUCT, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor 7 L% U3 C: E2 R' p: y X # Y9 r% D7 n) ^$ c" e9 v+ _2 g // String: "STM32 ezUSB-CORE V1.01" # M9 j5 }8 R! t5 T '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 }; ! V& s# H1 S) S* k4 {/ v const unsigned char cbDescriptor_StringSerial[DESC_SIZE_STRING_SERIAL] = { ( K0 e- u2 r5 X# p" s+ C DESC_SIZE_STRING_SERIAL, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor // String: "ezUSB-CORE Demo 2008/11/18" 'e',0, 'z',0, 'U',0, 'S',0, 'B',0, '-',0, 'C',0, 'O',0, 'R',0, 'E',0, ' ',0, i* C, Y& h* S; J '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 * Y x4 _4 L6 A! i- s0 k, m7 \ }; . M# C# p. \/ A2 s 0 P, i( u- O/ y5 | 了解这些描述符的用法以及作用,最好的方法的是编写自定义的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设备的枚举(下)