STM32 USB HID键盘例程
% v6 ^5 h* G8 k/ }3 u
: z3 @" P! Q7 H7 {$ v% M6 u# |最全USB HID开发资料,悉心整理一个月,亲自测试
' d, M7 D; J5 U; ?
0 u/ M/ X/ E- ?6 T6 s通过STM32CUbeMX建立USB HID的双向通讯实验成功# `9 I, o. G8 l4 Y |2 G' Y2 V
% C$ P) B- i/ A1 ]6 ^: h1 Z; c3 h" j5 ] V- j9 Y9 P( f6 N$ A
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
" R% `( {% ]8 ]; q5 v+ L; e2 ]; z- F( S; ~, o! i
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在; d8 U% X9 d- o& o- E1 ~( r
! c0 M% a( s0 `$ w7 E
一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
4 R' @& p/ Z, |6 [1 p9 A5 Y* R
% s' H, z1 K2 P% T/ N% o, Z* g0 g二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。/ w* ]; R% z$ }7 A( A2 o8 }4 J% T
/ B( e ^$ q* q" p: Du8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的
2 u1 w7 S- S0 L8 x( ]2 b- r0x83, /*idVendor (0x0483)*/
2 c. P7 E& f1 z9 E) A, f2 e0x04,1 S) U) s& q* x
0x50, /*idProduct = 0x5750*/& A; z1 _. T0 W
0x57,! {4 m/ P. J4 E L% a- D# V
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。; ~9 x: U: i: J
//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。
! L6 ]$ P! i( Z9 p# d& t( D j//注意小端模式,低字节在先。3 p4 K- b! g. T0 x3 g
//idProduct字段。产品ID号,我们这里取0x5750。: m' z, b7 q. m
//注意小端模式,低字节应该在前。6 }/ n0 ]* q4 [4 a. ~
- {% G4 Y! O& Q: N# x. `7 ]5 L% ]( [ s
, |3 ` i3 e' u* d7 econst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下. s) s, k2 U: A0 f7 S; L+ {2 z4 m9 x* u
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */, K& q- H' ~' l+ m# e6 c
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */
2 O0 h3 I0 M7 }' h$ Q0 W - 0x03, /* bmAttributes: Interrupt endpoint */
2 \9 y5 Y6 Y2 S3 V# } - 0x02, /* wMaxPacketSize: 2 Bytes max */
9 ^0 {+ j$ B9 D* u - 0x00,6 U) b' N9 A. o2 k0 S
- 0x20, /* bInterval: Polling Interval (32 ms) */: y/ m6 j, R, ~3 b a
- /* 34 */
: D" Y, F/ ` N8 C( e - 0x07, /* bLength: Endpoint Descriptor size */6 l8 O ]% n; r
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */* _5 g" a9 v+ h) W
- /* Endpoint descriptor type */- |) I+ Y, N; {
- 0x01, /* bEndpointAddress: */
. ]7 A/ }7 U! c3 e+ L - /* Endpoint Address (OUT) */5 H+ U& N8 s9 d3 B6 p5 ?3 }- {
- 0x03, /* bmAttributes: Interrupt endpoint */& M) x! M% i2 c! ` i
- 0x02, /* wMaxPacketSize: 2 Bytes max */
E% W4 B; i- N0 K# x- r - 0x00,. K* T: N$ @0 O0 H" ^
- 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码
0 m; P+ |5 v' t) s上面包含了“输入端点描述符”和“输出端点描述符”。5 k; h, _: P# y
//wMaxPacketSize字段。该端点的最大包长。) c$ v9 r" P3 B6 g; L
//bInterval字段。端点查询的时间,
! K- V( `% s: j* N6 J# I
( L5 F3 N! G6 J. x5 D4 r为了实现更高速的通信我们修改如下:' c- @1 V& u9 T( t
- /******************** Descriptor of endpoint ********************/
3 v& v4 y t9 A' z8 N - /* 27 */0 r2 e4 T* N7 j3 T. }
- 0x07, /*bLength: Endpoint Descriptor size*/
# H% X) F8 W8 c - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/6 ~6 f( Z7 X: B7 g& @' x
- 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
( B; N8 I2 J2 q2 S2 r# s& c - 0x03, /*bmAttributes: Interrupt endpoint*/
/ l! E, h- S) a7 U4 q& S7 P0 H# b - 0x40, /*wMaxPacketSize: 64 Byte max */9 w! X4 t" {5 g
- 0x00,/ l4 f0 h, B5 a c% u
- 0x0A, /*bInterval: Polling Interval (10 ms)*/5 w6 o! ?9 a1 m$ m. E
- /* 34 */- n( B0 A( \5 p; `
- /******************** Descriptor of endpoint ********************/
( l$ p9 g2 @: |, ]) q* x! Y - /* 27 */- E. F4 U. u ^0 ^' s4 e/ {& v2 m; j
- 0x07, /*bLength: Endpoint Descriptor size*/
" j- z, d& n: n$ j" ` - USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
7 j7 [! y* D- Y) c - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/4 O4 @0 d, O4 \8 |- o( |
- 0x03, /*bmAttributes: Interrupt endpoint*/
: G+ ]8 d9 I. k; l6 Y - 0x40, /*wMaxPacketSize: 64 Byte max */
8 `" V+ q' K, o4 u - 0x00,2 G4 T; e( B4 d
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
) a/ [" m* k) N) ?+ B8 N# a - - s; j; ?; r6 i8 |8 E
- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。
" J: v7 b: ^ P0 k i2 P/ A& c5 I2 V - const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
$ p) ~5 I6 G9 \ - {
^( ^4 N& X! R# M0 }% T, u - 0x05, 0xFF, // USAGE_PAGE(User define)
% ?/ c4 Q- q9 o' J; I0 L, @. D - 0x09, 0xFF, // USAGE(User define)2 c% M5 L4 @) V! E& h' P% S% b
- 0xa1, 0x01, // COLLECTION (Application)
% r( \5 S: @$ j8 e4 i$ }6 M - 0x05, 0x01, // USAGE_PAGE(1)* D7 j/ {2 C* y0 v' j1 O
- 0x19, 0x00, // USAGE_MINIMUM(0)
) N& L" r5 f, d - 0x29, 0xFF, // USAGE_MAXIMUM(255)
( O7 P$ R, i! |9 Z+ n( F6 H, q: _: d - 0x15, 0x00, // LOGICAL_MINIMUM (0)
# I$ V/ p6 [5 M2 m* o - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)& \$ }: r" x/ } C% p9 |( X
- 0x75, 0x08, // REPORT_SIZE (8)7 M, h% \8 P7 a$ F3 m" o
- 0x95, 0x40, // REPORT_COUNT (64)
" i, I0 `% z- L- C0 X - 0x81, 0x02, // INPUT (Data,Var,Abs)) T8 }5 U# R h# q. Q; p( O* ^! ~8 O
- 0x05, 0x02, // USAGE_PAGE(2)
2 m- e& M* ]6 t8 |) D - 0x19, 0x00, // USAGE_MINIMUM (0)1 W. N+ U& j2 ?* i0 Z
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
% y; a% Z* s0 t/ K - 0x15, 0x00, // LOGICAL_MINIMUM (0)
( L/ z( V! h* [8 z; M4 t+ R - 0x25, 0xFF, // LOGICAL_MAXIMUM (255)# u( w5 r* j8 J+ `9 `! q
- 0x95, 0x08, // REPORT_COUNT (8)
( M! d: Z4 @* A) @, l) Z - 0x75, 0x40, // REPORT_SIZE (64)
( J4 P v- A0 C) R7 n8 c - 0x91, 0x02, // OUTPUT (Data,Var,Abs)
/ e/ u% f: V' y7 f - 0xc0 // END_COLLECTION/ Y- m& A9 r% t5 j
- }; /* ReportDescriptor */
4 O! u3 d7 N- H& J; _7 j
5 f1 z7 ?( K) Q2 O& X/ a- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]% ^" c; W2 p. {5 k4 x" u. b
- const u8 StringProduct[SIZ_STRING_PRODUCT]
6 A* p2 ~. k ~( d* | - const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码
/ j& E, K* x7 q0 ^* a6 ~+ c# L分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
' x0 j% Y; V8 h! K- S
0 \- R5 J5 ?/ c- #define CUSTOMHID_SIZ_REPORT_DESC 39
, x6 Z0 r9 t9 k" t2 K h - #define CUSTOMHID_SIZ_STRING_VENDOR 64
l, a% T+ I& L4 I/ c6 I - #define CUSTOMHID_SIZ_STRING_PRODUCT 28
* r( L) S$ C b4 ?; O - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 9 |1 y5 @# S6 v: g
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数: }, |& @ z4 n0 N8 R4 ^) K
a) Set_System(void)2 V* E8 l: I. d0 F
b) void Set_USBClock(void) 4 ~, h/ C3 P/ m" q( Z' r9 m
c) void USB_Interrupts_Config(void)
6 P8 H; V2 G& B! e& ud) void USB_Cable_Config (FunctionalState NewState)
" f; R7 V. `7 n% y特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。/ ^3 v+ }0 {) B
3 y. R* p4 o2 J' ~
4 v8 f5 x9 t8 X2 J/ ?四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
9 t( X- ^ m( E" B! x& x打开usb_prop.c文件,修改如下:
q, I9 I+ V. H' h) F2 c- void CustomHID_Reset(void)7 {9 \4 P# Q/ B# K
- {, c1 `0 H6 @* V2 S7 x+ w2 K
- /* Set Joystick_DEVICE as not configured */. g+ J% m1 {% m% F' X
- pInformation->Current_Configuration = 0;1 c5 g- ^, }: n- P
- pInformation->Current_Interface = 0;/*the default Interface*/6 F8 [" K2 N% i/ `" h+ b
- SetBTABLE(BTABLE_ADDRESS);' m p3 \. S, c, \! B4 [
# Y& H. I* d, J) t$ u: K) z4 R- /* Initialize Endpoint 0 */
6 b- i8 E; j0 j u - SetEPType(ENDP0, EP_CONTROL);
- S* @$ e/ x( W8 R" l - SetEPTxStatus(ENDP0, EP_TX_STALL);
' ~# G& D6 w, m - SetEPRxAddr(ENDP0, ENDP0_RXADDR);( ]- E4 e8 ~. K1 [9 U3 S
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
: v$ j( ?: j5 a" H - Clear_Status_Out(ENDP0);
# }+ q, J% V6 ` - SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);" n0 u5 @' t4 Y
- SetEPRxValid(ENDP0);
' G* T- }! T' D' |# ]" T
6 E6 W- h* s: k2 P5 k; O6 k6 H- /* Initialize Endpoint 1 */4 [/ ?# o2 c! w# F4 u7 \+ `
- SetEPType(ENDP1, EP_INTERRUPT);, }$ _! |/ [! s% b1 u
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);4 O3 S& ]( h- R9 K" G' Y e1 L
- SetEPTxCount(ENDP1, 64); q/ ^; _$ O' `! E6 y0 Y: B1 f
- SetEPRxStatus(ENDP1, EP_RX_DIS);: p9 C+ j- [( C) [' a
- SetEPTxStatus(ENDP1, EP_TX_NAK); d. W9 q3 k, Y1 W8 D4 f w { L2 L
4 ]1 J; B% D6 t/ F1 K# u! S- /* Initialize Endpoint 1 */9 U0 d/ K5 s) I. e
- // SetEPType(ENDP1, EP_INTERRUPT);
* i- ]8 e8 {( r+ M - SetEPRxAddr(ENDP1, ENDP1_RXADDR);" C5 m8 s3 M, ]7 e' }
- SetEPRxCount(ENDP1, 64);; `. v$ f! B' l( \
- // SetEPTxStatus(ENDP1, EP_TX_DIS);
1 j, n7 Y, D: z6 D3 y/ E4 N - SetEPRxStatus(ENDP1, EP_RX_VALID);
3 b- P+ K& d4 ]+ h, ~' S - /* Set this device to response on default address */
4 H0 @/ W" t9 [' K/ Z - SetDeviceAddress(0);
1 h2 J" v/ x# {6 H& F7 I8 h - }
复制代码 ) I1 K* ?5 q( r* u
五、usb_endp.c文件
3 j9 f" t( Y; V4 V6 [) C- \, p- void EP1_OUT_Callback(void)
" T/ l. @7 U: R. |6 n/ s0 e" t* u* h - {9 G% L5 v0 U7 I
- 这些写接收代码
* P$ _, d8 I5 n: ]' U/ Q5 Z% _ - }
复制代码 & o( E2 E, f1 f- C
六、数据发送和接收,举例说明6 x) ]; U* |9 H( K; x0 E
1、数据接收
, W+ W" S* d. K$ k! q/ ^- u8 DataLen;
# h8 q0 \% n# z( a) X* G - DataLen = GetEPRxCount(ENDP1);
" I' w6 R9 m) E5 e* w - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
! {0 E6 n1 q' R* W) q% m - SetEPRxValid(ENDP1);
' [; c! J! J. g8 t! ` - USART1_Send(DataLen);
$ f! i1 i: o! J0 j% k0 N - count_out = 1;
复制代码 / M$ f3 @0 H2 {
2、数据发送2 n2 k) Z8 R w! t( V% C3 @
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
) O5 X, `# l1 c( a2 _" j4 h - SetEPTxCount(ENDP1, 64); 5 u$ W* z) |- t" U
- SetEPTxValid(ENDP1);
复制代码
2 i7 P* {6 Z5 g2 Q2 g如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。5 F7 H, {% `, B6 G0 V# o
$ D X7 w" l* a1 \, K- h% [) t" | f/ x& _
(by xidongs)& Y, }3 z0 l+ u+ @+ A
|