STM32 USB HID键盘例程5 j# i5 p3 C4 k8 O
8 U% M3 G% i, y9 j9 w; r最全USB HID开发资料,悉心整理一个月,亲自测试
3 P2 h; `- {5 k5 Z- `. I
0 g3 n8 x' a4 ?, L6 @通过STM32CUbeMX建立USB HID的双向通讯实验成功+ Z: i: C3 b R8 i: s
0 _8 K* r2 K6 ?7 ~% E
: B8 L$ b' L# n: Z; N发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
3 R# r8 ~8 M" P+ F% T
# t+ k) S0 A2 ]% J W7 ^' LHID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
% q# F. f' |- }
/ @0 z' @6 g5 t! ~; h一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
; n \; X/ u8 Z9 E3 x0 q% h+ M, y7 [; o, o. i; O7 w5 k' N
二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。: p" ^0 j9 }9 z/ C8 W
! s+ P1 A5 X: C& C
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
* l; B" H% _) e- x0x83, /*idVendor (0x0483)*/
7 j7 Y0 m2 f0 `' x$ E' X @5 H7 S) Y0x04,
) N5 f, z' Q! y* b9 E0x50, /*idProduct = 0x5750*/
* J9 f$ Q A/ R- S0x57,
" U3 W+ g$ Z$ |7 j//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。3 ]. n8 ^: Q0 m3 F
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
! w2 c$ k4 h$ U Q3 P//注意小端模式,低字节在先。
' T5 k1 C# O4 ]! i' X//idProduct字段。产品ID号,我们这里取0x5750。
% t# M( t' n" W& n7 \//注意小端模式,低字节应该在前。' b* D9 L5 a! o( W
- i. q+ h% |6 `9 S. U7 ?1 {/ Y+ |) O
const u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下
/ G) n ~* ]- ~3 |- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */% o1 \$ G& }4 m, e" H ^4 L
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */4 t) x1 w* a. [5 B# @8 Z. ~* n
- 0x03, /* bmAttributes: Interrupt endpoint */
3 S* U& c: d& {* u5 f( a f+ Z6 E - 0x02, /* wMaxPacketSize: 2 Bytes max */
5 ^! U' v2 {; V - 0x00,
) ?1 h6 G" _ [6 ]. y% p0 S# i& y - 0x20, /* bInterval: Polling Interval (32 ms) */
: a! _3 x8 b' d9 `+ B- D - /* 34 */
% v: y) _9 }) p/ d6 I# y4 ~ Y - 0x07, /* bLength: Endpoint Descriptor size */
: a6 B1 z4 w: O/ U - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */: I* E; s" l# z; `% `- l, f1 n
- /* Endpoint descriptor type */2 K7 Z0 ~3 Y) J0 {. f! e& `
- 0x01, /* bEndpointAddress: */
( e7 W9 ]* O/ [; M# u* Q - /* Endpoint Address (OUT) *// S0 a9 \2 i1 O- R9 v- x
- 0x03, /* bmAttributes: Interrupt endpoint */, i f+ j; }( h0 C3 H, o
- 0x02, /* wMaxPacketSize: 2 Bytes max */! V7 f, \; |2 p1 K! v9 F5 b
- 0x00,3 ]! | a5 d2 g
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 # L! d8 z* L# o {- ~ `3 q
上面包含了“输入端点描述符”和“输出端点描述符”。
w8 s# ~6 A( I! M2 d/ _//wMaxPacketSize字段。该端点的最大包长。
! g; ?0 Q8 C: ]2 [+ V% C" C2 j" o3 C. b//bInterval字段。端点查询的时间,% @# e0 R" W8 z5 Y8 R" m/ ?1 h5 N, C
/ g; \/ C7 g6 h/ e0 X
为了实现更高速的通信我们修改如下:
! G/ r" z N+ m9 |$ X: V- /******************** Descriptor of endpoint ********************/4 }% ]: @* b9 _$ M! |8 Z
- /* 27 */
9 }; d( z3 T9 a c- a - 0x07, /*bLength: Endpoint Descriptor size*/' c4 x5 K) `% `& s" {2 x
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/4 E& U. L1 t" y6 C7 i5 e6 z
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/' P) t$ c" d& X$ g6 L5 @" d, ^8 q
- 0x03, /*bmAttributes: Interrupt endpoint*/( x2 |) P/ }) H+ i$ f- q; b
- 0x40, /*wMaxPacketSize: 64 Byte max */
8 \# L, P1 ^ T4 A- t, a - 0x00,
: u, u% r* P0 \' h% q - 0x0A, /*bInterval: Polling Interval (10 ms)*/
2 A W: r4 F& [: m# v8 u - /* 34 */6 m: W: w9 H/ a/ Z: y5 F- h3 Q
- /******************** Descriptor of endpoint ********************/
0 o1 W7 s, D- g: R( j - /* 27 */7 s2 E! ^ F* F2 I$ H; _+ L
- 0x07, /*bLength: Endpoint Descriptor size*/6 z d4 v, [5 b1 N/ N
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
; W# C2 f. Z0 N2 j- o' d - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/ R+ m, L6 f: y
- 0x03, /*bmAttributes: Interrupt endpoint*/
: m! E" r8 g3 l) [ - 0x40, /*wMaxPacketSize: 64 Byte max */2 r$ k$ h# P' [# x3 ^8 }$ d" j9 F
- 0x00,
- X' V4 A+ ~$ U7 w% D" h - 0x0A, /*bInterval: Polling Interval (10 ms)*/
9 F9 P- @: K, x8 |, e
# m( Y. v2 d W- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。4 H0 ~1 {0 _- V3 T$ F* n& s
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
, }6 V9 h) v- O1 u/ @$ m - {
2 v) D9 i# g+ t, Y& d" k4 l - 0x05, 0xFF, // USAGE_PAGE(User define)- X9 B; [* Z _3 ?0 Q) W. k
- 0x09, 0xFF, // USAGE(User define)
0 H/ }- X$ z% r4 P - 0xa1, 0x01, // COLLECTION (Application)
, G1 z( F4 J1 O2 g$ n' r - 0x05, 0x01, // USAGE_PAGE(1)
: e+ H y4 o) B. p: B* { - 0x19, 0x00, // USAGE_MINIMUM(0)) m8 n5 |6 P" D% J# e4 x4 g
- 0x29, 0xFF, // USAGE_MAXIMUM(255)* ?+ j) M# @" e% M2 b, _4 D! x5 ?
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
0 z$ i: o* E, d* n# M. D5 o - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
$ D$ [8 ^# a& A - 0x75, 0x08, // REPORT_SIZE (8)
3 ]( p3 ~2 F% n1 s }6 @+ N# o - 0x95, 0x40, // REPORT_COUNT (64)& w" M8 g. b3 |. \6 _" ]1 U0 o
- 0x81, 0x02, // INPUT (Data,Var,Abs)
$ d* y- t @+ e7 x2 j - 0x05, 0x02, // USAGE_PAGE(2)
G# l/ \3 A j& D" A& z - 0x19, 0x00, // USAGE_MINIMUM (0)
0 F! f. P! A" b: [& R! t& p - 0x29, 0xFF, // USAGE_MAXIMUM (255); N/ [! b6 ^, Z3 Y: P% S
- 0x15, 0x00, // LOGICAL_MINIMUM (0)
! b) T* q9 K0 f% Y' N" V - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
4 H& d7 ~4 J# P3 D( M: ]- ?* Y - 0x95, 0x08, // REPORT_COUNT (8)
1 ~3 Q! j* |" o C5 M' Z7 i - 0x75, 0x40, // REPORT_SIZE (64)
+ f0 `0 W# J& z$ ~3 T - 0x91, 0x02, // OUTPUT (Data,Var,Abs); Q8 E- @# T" R% g8 j
- 0xc0 // END_COLLECTION7 i. T$ B- @- E& G9 j& ~' r* } q! ~% p
- }; /* ReportDescriptor */
# w0 a5 A- V }0 J' @: v/ u% z - 0 n5 C/ V/ A0 v6 D! I
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]% q, m! m: z! C
- const u8 StringProduct[SIZ_STRING_PRODUCT]
; `# a! o, r% o; a0 g; e2 w - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 2 `4 g' d0 r T2 @ a( i: }* `
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
1 V# c0 g7 G1 `/ g& O! n
; Y7 u J3 }& @- #define CUSTOMHID_SIZ_REPORT_DESC 39) P! [. p% a9 z; i) b4 C
- #define CUSTOMHID_SIZ_STRING_VENDOR 645 S8 y( e7 s4 t# ?4 ^
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28, @* a# C/ m O3 }8 G3 k
- #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码
7 I7 ^1 E0 b. n; ?三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数
7 F( l0 F" p/ [7 V+ F( h$ Ma) Set_System(void)
5 t; E: P% v) i: x1 R) Z9 `1 C8 {b) void Set_USBClock(void) 8 T8 |# L& n4 Q Q' K) z+ H
c) void USB_Interrupts_Config(void)
- x5 [/ P* m6 e. b+ G% jd) void USB_Cable_Config (FunctionalState NewState)4 \( i8 T" ^" I! J' P3 F
特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。1 e3 F6 a" A0 S5 e1 F
5 j$ S( q* S3 l' B0 N! Z' H: y
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
. r: C4 O* F( U7 ^打开usb_prop.c文件,修改如下:
+ L& d$ O8 Q7 i) q1 N( R- void CustomHID_Reset(void)
8 S8 _- b* E6 {& ^4 ]. ] - {
* P/ t# G2 @* {) M$ c - /* Set Joystick_DEVICE as not configured */) g/ v' U5 O3 o9 N4 C
- pInformation->Current_Configuration = 0;
! [/ |* B9 A* z7 c% s" U9 Z9 H - pInformation->Current_Interface = 0;/*the default Interface*/# U( d6 `" \6 J+ ^# i! `
- SetBTABLE(BTABLE_ADDRESS);! t$ _: N2 m e8 D, u& f) B% G, e# `
) O2 o: u4 a' ^1 B- /* Initialize Endpoint 0 */
; D3 d; @1 Y. o' [' W - SetEPType(ENDP0, EP_CONTROL);
3 Z! n. Q& k4 A3 b7 h - SetEPTxStatus(ENDP0, EP_TX_STALL);
; F$ O. M, \$ T - SetEPRxAddr(ENDP0, ENDP0_RXADDR);) ]% ]# c8 `9 k9 _9 j
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
5 `/ M" ~/ Z3 E - Clear_Status_Out(ENDP0);9 D6 {" l4 O0 j' h8 }
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);) z" T6 Z+ |8 w' s: s4 }; a' D0 b
- SetEPRxValid(ENDP0);
/ k, b. J6 D2 e* K - $ t/ r- Y: S* x1 r" p7 F. K$ v
- /* Initialize Endpoint 1 */
/ P" Q) t R: q! q% Z x - SetEPType(ENDP1, EP_INTERRUPT);" t5 Z- U: K1 ]6 \9 F w: b
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);! n* z3 X- K) Y$ {/ |, X
- SetEPTxCount(ENDP1, 64);2 O; |; w) ~* d3 i! S. C
- SetEPRxStatus(ENDP1, EP_RX_DIS);9 y2 f* I6 c, ]" J0 m v
- SetEPTxStatus(ENDP1, EP_TX_NAK);
6 b6 q5 L6 N5 Y
' [. K# ~8 x% G6 N2 _+ e- /* Initialize Endpoint 1 */
% H; g0 T1 j( D! b6 C9 _3 e r' b - // SetEPType(ENDP1, EP_INTERRUPT);
+ A8 {9 i/ Q; ^6 J+ v/ j - SetEPRxAddr(ENDP1, ENDP1_RXADDR);7 a/ J! E; z& D1 |( X
- SetEPRxCount(ENDP1, 64);
: ?0 p( o. Z5 D. e& B; F6 i" R - // SetEPTxStatus(ENDP1, EP_TX_DIS);2 B% S% d: D, {& Q% L2 h) f
- SetEPRxStatus(ENDP1, EP_RX_VALID);
$ e3 |% c( m4 U+ e$ t$ }+ p3 O( P& G - /* Set this device to response on default address */- r y+ Q" ], Q3 R& D4 p
- SetDeviceAddress(0);
5 y9 X. ]8 c( W; t" v1 S& I& y - }
复制代码 * p6 G9 M; s. e: k" ? B2 V; Q) c& E
五、usb_endp.c文件
8 a9 h: y3 i0 s- B( W/ m- void EP1_OUT_Callback(void)
: F& l) k& m$ Q- y' I8 a5 E - {
$ }$ r! n: e- E2 x$ s+ U3 \! N - 这些写接收代码
- q* q' s/ P; z) i - }
复制代码 % v" {7 {) m1 @
六、数据发送和接收,举例说明 ~( q7 y9 ]# Q
1、数据接收- B( M' S5 X" o* f' O2 H: Z4 R3 ?7 G: z
- u8 DataLen;1 q' e! H. k1 s1 V% z. _( R
- DataLen = GetEPRxCount(ENDP1);7 z7 W; R f8 n8 j
- PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);& X$ K5 |+ C% y" G3 @: B" ^7 ]3 {6 V9 D
- SetEPRxValid(ENDP1);
; _: g/ H T1 N* F7 j - USART1_Send(DataLen); J& w3 X% w) n x& M6 A
- count_out = 1;
复制代码 ( e4 p; V* o& L4 B( `" I
2、数据发送
' l% c2 Q( i* l) T, e- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
8 z% }. \$ T4 Y4 q - SetEPTxCount(ENDP1, 64);
3 O9 \0 M- l! K! e5 a - SetEPTxValid(ENDP1);
复制代码 8 ^! l7 A! h# |9 Q' m" z5 J6 z
如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。
9 }0 ]( Y$ W& L5 \# N# P# p7 d3 r% F: o1 I6 |, o
- {( q' A4 K$ D2 d(by xidongs)' k% u- t; b) i C- F
|