|
SETUP事件正确接收后,根据该事件提供的请求类型进行对主机的响应。SETUP数据结构的wLength字段说明的是请求返回或者提供的数据长度。 8 ?6 H' J7 w+ r0 r* n3 ^5 R 如果判断出的请求信息错误或者说不被支持,STM32 USB设备需要中断此次请求: Y! K, v9 a$ x1 z SetEPR_RXStatus(ENDP0, EP_RX_VALID); SetEPR_TXStatus(ENDP0, EP_TX_STALL); % u; q5 G, r% S8 i7 ~+ E 正确获取到请求信息后,如果wLength为0,设备需要发送一个0长度数据包以响应主机: * X3 o7 ^6 I! q5 F8 q: G // ***************************************************************************** P8 q& Z3 d. [" Z# e9 z- T // Function Name : SETUP0_Trans0Data ! P3 W' t0 \; J3 y( G: } // Description : // Input : Q0 F+ T; Z& W4 V // Output : // Return : 4 y: a" t1 I9 w* R // ***************************************************************************** ( n4 z6 a1 r; D$ u4 E( d RESULT SETUP0_Trans0Data(void) { $ x; a( R: S! q; V8 _% c+ S% d // Send 0-length data frame as ACK to host 8 b* l3 m; P# H, S1 t& _; R$ _- g SetBuffDescTable_TXCount(ENDP0, 0); SetEPR_RXStatus(ENDP0, EP_RX_NAK); 8 V1 a6 q8 Q6 T [: ]6 g9 w SetEPR_TXStatus(ENDP0, EP_TX_VALID); 9 A& Y, Y7 C/ C$ z! X" } return RESULT_SUCCESS; : Q5 I" M1 G: w& @; G# \+ j4 _ } " d7 f) Q: a$ | 如果wLength不为0,设备则需要根据请求的数据长度发送数据包以响应主机: 9 [( o1 I4 C* i. ~# R // ***************************************************************************** // Function Name : SETUP0_TransData // Description : // Input : // Output : // Return : // ***************************************************************************** RESULT SETUP0_TransData(void) { 0 M. u& A7 W, F V) \ unsigned short wLength = vsDeviceInfo.TransInfo.wLength; unsigned short wOffset = vsDeviceInfo.TransInfo.wOffset; unsigned short wMaxSize = vsDeviceInfo.TransInfo.wPacketSize; . W( O" P' s' {# J: S, F$ [7 } if(wLength) 6 a' d2 P' M3 n5 C0 m { if(wLength > wMaxSize) + b& f4 F! R( ^! z; ?" C { 4 I, l7 ]+ A; H9 I4 v s2 u3 X wLength = wMaxSize; ! z, q" ^+ I: Y } // Copy the transfer buffer to the endpoint0's buffer BufferCopy_UserToPMA( vsDeviceInfo.TransInfo.pBuffer+wOffset, // transfer buffer # p5 m" t7 Y9 I& b; Z1 b' W GetBuffDescTable_TXAddr(ENDP0), // endpoint 0 TX address wLength); 7 V7 J" k, i! v/ `& E/ t; J : @! l; y- n- B2 k SetBuffDescTable_TXCount(ENDP0, wLength); + L: ^$ q* C4 V# ?# O( h, t SetEPR_RXStatus(ENDP0, EP_RX_NAK); SetEPR_TXStatus(ENDP0, EP_TX_VALID); // Update the data lengths vsDeviceInfo.TransInfo.wLength -= wLength; vsDeviceInfo.TransInfo.wOffset += wLength; ( U% o6 J7 t6 y4 T. ~ return RESULT_LASTDATA; } return RESULT_SUCCESS; 4 W8 z F9 j. l) c6 o } ! H! w0 e6 Z! h4 f6 A* B 如果发送的数据长度大于端点设置的最大数据包长度,数据将分割为若干次发送,记录发送数据的状态包含在结构体TRANSFER_INFO中: 3 F- u! L- G1 L8 j( z // ***************************************************************************** 9 B, r# U. i6 k' D& Z" K6 ~+ c0 W- ~ // TRANSFER_INFO // ***************************************************************************** 1 `' G1 \' P1 y% ] typedef struct _TRANSFER_INFO 6 e) G5 O0 Z% o3 i3 n% X { 8 s2 k+ l9 d1 R 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 . M' l+ q& }: k: w; R3 V } TRANSFER_INFO, . y: k! A+ d J *PTRANSFER_INFO; 0 z3 X& G; J& s# A. @' m1 l, x8 t& z TRANSFER_INFO.wLength记录发送的数据长度,如果非0,表示有数据需要被发送。 TRANSFER_INFO.wOffset记录已发送的数据长度,用以确定数据缓冲TRANSFER_INFO.pBuffer的偏移量。 $ f* B4 o# I d) e L$ U7 T( @ 需要了解的一点:USB主机向USB设备正确发送一请求后(这部分的处理由硬件完成),USB主机将间隔若干次的向USB设备索取响应数据,STM32 USB TX状态为NAK说明不响应USB主机,USB主机在超时后退出此次请求;TX状态为STLL说明中断此次请求,USB主机将无条件退出请求;TX状态为 VALID说明设备已准备好数据发送,USB主机将从USB设备读取数据。 7 j1 h: y7 [7 E" i3 ^ 以非0长度数据请求的GET_DESCRIPTOR请求为例的响应过程: 2 D6 V4 s3 s& @& M. ^+ K0 Z CTR_SETUP0()->SETUP0_Data()->SR_GetDescriptor()->SETUP0_TransData() RESULT SR_GetDescriptor(void) { + j/ Y$ f2 [5 x" K // RequestType: device->host, standard request and device recipient & d/ x+ A" o5 Y7 C' |! h' Z7 e if(vsDeviceInfo.SetupData.bmRequestType == RT_D2H_STANDARD_DEVICE) { ! ?- A6 X D9 P0 O$ P // SetupData.wValue.b.MSB: descriptor type - {" m+ |7 q% m6 a1 h, w; c* v // SetupData.wValue.b.LSB: descriptor index switch(vsDeviceInfo.SetupData.wValue.b.MSB) { 8 V4 o" v! ?. V3 k4 F case DESCRIPTOR_DEVICE: return SR_GetDescriptor_Device(); 3 w& i: ~5 g% J- ?% l0 C O4 P, s case DESCRIPTOR_CONFIG: return SR_GetDescriptor_Config(); % k6 m3 p% Z3 l! V% t# s( V- T case DESCRIPTOR_STRING: return SR_GetDescriptor_String(); 0 _8 D; p: A6 `' C* o, t default: return RESULT_UNSUPPORT; 0 \" d% { |7 Y } } 4 a0 q) ]( c, R0 i' |) q! G # Y% h3 d5 ~: b( ? return RESULT_UNSUPPORT; } GET_DESCRIPTOR请求属于USB协议中的标准请求(standard request)并且数据方向为设备至主机(device->host),分设备描述符、配置描述符、字符串描述符三种。已设备描述符为例: RESULT SR_GetDescriptor_Device(void) 3 h7 j5 M3 d0 L2 {/ b' o' ` { , B3 E8 z/ J& g# p9 m% m0 V" }' [ // Assigned the device descriptor to the transfer vsDeviceInfo.TransInfo.wOffset = 0; $ C* T2 Q0 Z% Z1 S) t8 N4 L vsDeviceInfo.TransInfo.wPacketSize = ENDP0_PACKETSIZE; 3 F/ o$ H4 n" I/ G1 X9 ~& r vsDeviceInfo.TransInfo.pBuffer = DescBuffer_Device.pBuff; vsDeviceInfo.TransInfo.wLength = DescBuffer_Device.wLen; - q3 a% q2 A5 W/ k8 }7 s vsDeviceInfo.eControlState = CS_GET_DESCRIPTOR; if(vsDeviceInfo.TransInfo.wLength > vsDeviceInfo.SetupData.wLength.w) 6 ]5 m/ ]* T- S1 i1 L$ R { 4 l; N* z; f: W- b6 e' I vsDeviceInfo.TransInfo.wLength = vsDeviceInfo.SetupData.wLength.w; } % C& B, T3 B! x: d return SETUP0_TransData(); ; H3 M! X+ \ `4 F7 Z3 c. D } 这里说明了发送数据的长度、缓冲、偏移、端点包大小以及当前的控制状态,并说明了如果发送的数据长度超出请求的数据长度,则将舍弃超出的部分。数据配置好后,调用SETUP0_TransData()进行数据发送。 $ q. E; r: O9 g5 a& W( O 在USB主机查询到USB设备准备就绪后,将读取出这些数据,完成后,USB设备将产生IN事件,此时将响应CTR_IN0()函数: // ***************************************************************************** // Function Name : CTR_IN // Description : // Input : // Output : 9 _- y. ]' e7 [+ U" G // Return : // ***************************************************************************** `" I) d8 F+ A# K; U [! T void CTR_IN0(void) " |9 h, I1 c- h. p: ~0 S* ] { 8 R/ A; O* j$ @" L4 m+ t& B) R switch(vsDeviceInfo.eControlState) % k0 I& m# c- @- @$ ~ { case CS_GET_DESCRIPTOR: 8 i, L) T5 U. {2 A" h if(SETUP0_TransData() == RESULT_SUCCESS) { b0 _' H4 k6 U6 R: M4 D8 f SetEPR_TXStatus(ENDP0, EP_TX_NAK); 2 u& c6 Q5 K# h- ]/ F8 K SetEPR_RXStatus(ENDP0, EP_RX_VALID); ; N% t/ i& _* i+ |; R } break; case CS_SET_ADDRESS: ; n& j1 }8 G2 P: g' Y* z8 o# ~ SetEPR_TXStatus(ENDP0, EP_TX_NAK); SetEPR_RXStatus(ENDP0, EP_RX_VALID); ! x; _7 m) |7 G/ N$ L5 c2 d ' p1 j+ _2 b* K- e- \ SetDADDR(0x0080 | vsDeviceInfo.bDeviceAddress); vsDeviceInfo.eDeviceState = DS_ADDRESSED; 9 J4 c" {: V) l3 B% r break; : ~- m4 v3 c4 a case CS_SET_CONFIGURATION: 0 x Z: K c2 k5 J' u0 P$ R SetEPR_TXStatus(ENDP0, EP_TX_NAK); 6 z6 U9 g6 l% r9 J8 t SetEPR_RXStatus(ENDP0, EP_RX_VALID); vsDeviceInfo.eDeviceState = DS_CONFIGURED; break; 2 N+ W5 _3 {7 c# L default: break; , L0 v. z; g* C$ q } % [1 G; j: [7 v3 n6 n, Q } , r5 D. o1 c, l# x! v 再这如果响应GET_DESCRIPTOR请求发送的数据如果全部发送完毕,SETUP0_TransData()返回RESULT_SUCCESS,并设置TX状态为NAK;否则返回RESULT_LASTDATA,将继续发送剩余的数据直到数据全部被发送。至此,整个的GET_DESCRIPTOR请求过程完成。 ' _) i( s$ O t/ L' m' s 0长度的数据请求在发送0长度数据响应后,因为不存在可能还未传送的数据,因而IN事件后直接结束此次请求。 M7 q$ V1 n0 q: c" U! p5 } 在数据方向为USB主机->USB设备时,如果正确接收到数据,将响应CTR_OUT0()函数,处理过程类同CTR_IN0()函数。 在USB设备的枚举过程中,USB的一些描述符数据结构需要了解,具体在USB协议中有详细的说明,在usb_desc(.c/.h)文件中,定义了这些结构,这些结构是特定的: 设备描述符:长度、格式固定,其中VENDOR_ID与PRODUCT_ID决定上位机驱动的识别。设备分属类别决定了设备的性质,如果为自定义USB设备,设备分属类别值为0,同时上位机驱动必须配合编写;如果为标准USB设备,则必须使用这些标准设备的驱动、数据结构等等,条件是你必须了解这些标准设备的一些信息,好处是省去一些麻烦的驱动编写。 const unsigned char cbDescriptor_Device[DESC_SIZE_DEVICE] = W3 `: ]( p7 F R. J# n { DESC_SIZE_DEVICE, // bLength: 18 DESCRIPTOR_DEVICE, // descriptor type ; ]% D6 `& J3 c8 Q( G ' O) M+ E* O' o, G8 a 0x00, // bcdUSB LSB: USB release number -> USB2.0 0x02, // bcdUSB MSB: USB release number -> USB2.0 ]) g# L3 g- |1 }' b! D x ) I9 Q+ S: G% B( U, l' C4 N0 g- Y 0x00, // bDeviceClass: Class information in the interface descriptors ) ~# m2 q8 e$ n/ C9 Z0 W6 F 0x00, // bDeviceSubClass: 0 }" f- r6 i7 C3 j 0x00, // bDeviceProtocol: 1 b. B0 U j! P5 G 0x40, // bMaxPacketSize0: LowS(8), FullS(8,16,32,64), HighS(64) ) K" @& a- D6 N2 |6 \ " x' J( F: A% f: g# [% q LOWORD(VENDOR_ID), // idVendor LSB: HIWORD(VENDOR_ID), // idVendor MSB: / ^# h+ v6 I! c: T LOWORD(PRODUCT_ID), // idProduct LSB: HIWORD(PRODUCT_ID), // idProduct MSB: LOWORD(DEVICE_VERSION), // bcdDevice LSB: HIWORD(DEVICE_VERSION), // bcdDevice MSB: # |4 U4 f% v% U- R, x4 T. f 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 4 u& x8 F4 H% W) ~, D 0x01 // bNumConfigurations: number of configurations }; + Q) G: G( F$ Q( n: s 8 y |* |$ L* l/ E 配置描述符:前9个字节格式固定,后面紧跟的各种描述结构跟实际配置有关,每增加一种描述结构,该描述结构的第一字节说明了结构的长度,第二直接说明了结构的类型。在配置描述符中一般包含配置描述、接口描述、端点描述,如果需要同样可增加自定义的描述。使用标准USB设备类别时,配置描述符的结构也必须满足此类标准设备的数据结构。 const unsigned char cbDescriptor_Config[DESC_SIZE_CONFIG] = 0 S. v B8 [- ^7 r6 F. l { , y2 ^: e; \0 T // Descriptor of configuration 0x09, // lengths # V3 o0 i4 d4 G+ g" y DESCRIPTOR_CONFIG, // descriptor type 3 j A d. x& G( U; _ DESC_SIZE_CONFIG, // Total configuration descriptor lengths LSB - d$ p; n; n) o, s1 p 0x00, // Total configuration descriptor lengths MSB ! U( z0 H, p: `1 d S9 M6 t 5 @5 t, y0 a3 E1 r# S3 `3 k/ W 0x01, // bNumInterfaces: Total number of interfaces 0x01, // bConfigurationValue: Configuration value 0x00, // iConfiguration: Index of string descriptor describing the configuration P7 Y0 f o" [5 ^6 b$ Q 0xA0, // bmAttributes: bus powered // bit 4...0 : Reserved, set to 0 0 g; U& A. |9 l* o s // bit 5 : Remote wakeup (1:yes) ) G1 v4 `* E3 D9 Z9 s# s. S // bit 6 : Self power (1:yes) " ^0 `5 M' x" y+ z$ X6 I, `+ ? // bit 7 : Reserved, set to 1 0x32, // bMaxPower: this current is used for detecting Vbus = 100mA 6 T* X1 C+ b1 I# c ) W% \* Z; k6 Y' b& } 8 l( r H. Q/ |9 ^ // Descriptor of interface 0x09, DESCRIPTOR_INTERFACE, 0x00, // bInterfaceNumber: Number of Interface 0x00, // bAlternateSetting: Alternate setting 0x02, // bNumEndpoints: Number of endpoints except EP0 0x00, // bInterfaceClass: # l3 x% z! b' C% s9 R 0x00, // bInterfaceSubClass: 0x00, // nInterfaceProtocol: 8 v/ S# y3 |' f) V6 N% N 0x00, // iInterface: Index of string descriptor describing the interface 2 d' B7 T5 ]) l! Z3 \ `! V- z* z ' U- Y$ f4 _+ ]$ o* W9 Q) j- f // Descriptor of endpoint1 OUT 9 |# G ?; s2 \0 H# q5 j+ l 0x07, $ a5 i' ?. R9 Q) e. S8 ~% ~! A% Q DESCRIPTOR_ENDPOINT, - l; F7 x, ?9 ^' I: h8 ^+ b, S) w ! R/ L; Q7 b! A) @7 | 0x01, // bEndpointAddress // bit 3...0 : the endpoint number // bit 6...4 : reserved 1 M! `& J9 z" \9 S" d // bit 7 : 0(OUT), 1(IN) ' V# f2 R' V( T9 X 0x03, // bmAttributes 5 x3 X% {7 C+ ?' H // bit 1...0 : Transfer type & N; Y' }. f |4 w! ?7 @9 v3 o // 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 ; N! o% [. z# f/ n% B // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) $ [$ X" k5 p6 Q, K$ Z) u // bit 7...6 : Reserved, must be zero 2 U; N+ \7 E+ q& [& O. t 0x40, // packet size LSB 0x00, // packet size MSB ( S" E* V" y$ c, X+ g9 O$ I 0x20, // polling interval time: 32ms - k( n4 j7 S7 e2 v/ e0 G ; g/ K4 G' h+ F2 u: k9 Y1 n // Descriptor of endpoint2 IN 0x07, DESCRIPTOR_ENDPOINT, # _3 }* Y. | O 0x82, // bEndpointAddress & a$ Q* O5 x9 z. A // bit 3...0 : the endpoint number // bit 6...4 : reserved # S; P& q. J& b6 Y; V! a: [# R // bit 7 : 0(OUT), 1(IN) 6 r' U3 b& @0 Q! S( Q 3 P/ u4 _$ b0 D3 e 0x03, // bmAttributes : Z. n6 c( v7 i/ ~, J // bit 1...0 : Transfer type // 00(CONTROL), 01(ISOCHRONOUS), 10(BULK), 11(INTERRUPT) . L/ M) ^4 |1 Z8 z( C( R // bit 3...2 : Synchronization type // 00(No Synch), 01(Asynchronous), 10(Adaptive), 11(Synchronous) 6 |. I4 L8 p: g4 ` // bit 5...4 : Endpoint Usage type ! g0 v9 R) O" O' R$ K- O/ z0 ^ // 00(data), 01(Feedback), 10(Implicit feedback data endpoint), 11(Reserved) ! b: K( O( o, [5 W: n // bit 7...6 : Reserved, must be zero / H5 J4 P' N& T6 \) I % m/ a F* C0 r 0x40, // packet size LSB 0x00, // packet size MSB , d) ~; M9 `' Y( j: b6 B6 [ 0x20 // polling interval time: 32ms 5 b" s% F$ U: }/ m }; % V4 m+ L1 r u' x 4 v7 o* L8 j( H# E; t 字符串描述符:定义了与设备有关的一些信息,常见的为以下四种,如果有需要,同样可以定义自己的字符串描述符。 - ~1 Q# v: ^! [( _9 J0 K const unsigned char cbDescriptor_StringLangID[DESC_SIZE_STRING_LANGID] = - c$ F% O9 c) l3 x- _5 L/ _ { DESC_SIZE_STRING_LANGID, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor 2 v: M4 X4 w. v0 Q! S 0x09, // LangID LSB: * |% }: ]9 {+ q0 T# O* N 0x04 // LangID MSB: 0x0409(U.S. English) ! {7 _ d7 z0 B7 G; { }; const unsigned char cbDescriptor_StringVendor[DESC_SIZE_STRING_VENDOR] = / i0 K6 x2 o! M5 E& r { DESC_SIZE_STRING_VENDOR, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor q+ ?" N, d: @7 t/ f: g2 P 3 b. t+ _0 k2 \5 V! I // String: "LaBiXiaoXiaoXin" + I) y# B$ E( X# \! t5 T '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 4 T% `( y0 I K( I A$ j }; $ b. n/ s& m- l9 {- F( z const unsigned char cbDescriptor_StringProduct[DESC_SIZE_STRING_PRODUCT] = 4 o* A: R# H. D% `! I) x { 2 f& `% h. g2 r7 G% e3 n* W DESC_SIZE_STRING_PRODUCT, // bLength DESCRIPTOR_STRING, // bDescriptorType = String Descriptor # ?3 H2 p' W0 y% L: K0 H8 i a6 ~ // 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 7 d' ^3 I- o& k) ^ }; const unsigned char cbDescriptor_StringSerial[DESC_SIZE_STRING_SERIAL] = 5 g6 u0 d' K2 Z/ |& K8 @6 ?7 N { ) @1 r$ J5 O+ `. p DESC_SIZE_STRING_SERIAL, // bLength l! C& ?3 `* R0 V DESCRIPTOR_STRING, // bDescriptorType = String Descriptor 7 i# k: |* d8 F& f' b% \ // 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, ! X7 N, A& n/ N '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 }; 了解这些描述符的用法以及作用,最好的方法的是编写自定义的USB上位机驱动以及应用程序,这样你可以深刻了解USB设备与主机间的数据交换方式以及实现手段,下篇将开始介绍USB上位机驱动以及应用程序的编写以及开发环境的建立,STM32 USB设备的固件程序如有什么疑问,请朋友们多花几分种时间留言、讨论,共同学习与进步。 |
最全USB HID开发资料,悉心整理一个月,亲自测试
【源码】STLINK-V3MINI 高速USB仿真器,成功改刷【高速CMSIS-DAP】
STM32 USB CDC 虚拟多串口
实战经验 | 选择USBX模块生成USB CDC ACM无PD的项目
STM32 USB HID键盘例程
刘氓兔的杂谈【001】-片上USB 高速PHY
【经验分享】在进行 USB CDC 类开发时,无法发送 64整数倍的数据
在线直播|无需编写任何代码即可在STM32上实现USB-C Power Delivery
圈圈发布USB图书第二版有感,以及分享一些我学习USB过程...
USB Audio设计与实现
微信公众号
手机版
回复:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)
RE:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)
RE:基于STM32的USB程序开发笔记(五) ——USB设备的枚举(下)