STM32 USB HID键盘例程! b! u7 A* H7 ?1 p' Y9 @9 F
: {; ]" b8 ]* t1 | V h最全USB HID开发资料,悉心整理一个月,亲自测试" y* C+ I# M' `- w/ G' s
8 }% m4 M: r$ L. W2 {8 t1 g通过STM32CUbeMX建立USB HID的双向通讯实验成功
0 \( z+ A9 T! d3 m2 ^2 J5 `4 l1 ^% P1 p& O: ~6 d! m" b+ x
/ u/ Y7 l, R5 \: T# d+ B0 P% {
发现很多人对STM32的USB通信很感兴趣。要将USB的通信协议搞懂确实是一个比较漫长的过程。但是USB的HID通信无论是上位机的设计还是STM32程序的编程都非常的简单。只是我想很多人都不知道而已。这篇文章的目的是让大家以最短的时间将USB加到你的设备中。如果想学得更深就靠大家。
( W* y/ P4 T; ^) y l6 G& @% _" H
HID只是适合低速传输,其理论上可以达到64KB/S,但多由于windows系统和硬件的关系一般达不到这个传输数度。但这个速度对于一般系统的控制和数据传输都已经足够了,而且是免驱,省去了很多麻烦。如果您需要高速传输可参考我的另外一篇文章《STM32的USB例程修改步骤》文章在
" I3 R+ h* G+ y
5 d S' a; K. C! a! b2 p9 n( N6 C一、安装完MDK后请打开C:/Keil/ARM/Examples/ST/STM32F10xUSBLib/Demos路径,将Custom_HID在同一个目录下复制一份,如果你要放到其他路径你需要在MDK Options for target的C/C++中添加USB的头文件路径(MDK下的/INC/ST/STM32F10x/USB)。
) U) V, ~1 Z/ J- O, B9 x
% B" O! T! Z, j& M; P6 A Z二、打开usb_desc.c文件,该文件主要包含的端点描述符、设备描述符、配置描述符和字符描述符等。具体请大家参考其他资料了,这里主要说几个常用。" O! b3 Y: C3 I* l
|" J0 [$ d$ t9 G2 i4 X
u8 DeviceDescriptor[SIZ_DEVICE_DESC]为USB设备描述符。当中的4 M4 L; O% |( A5 B) t. T
0x83, /*idVendor (0x0483)*/
/ P i5 T; O: {0 v8 p( N0x04,
9 Q( h1 z* u8 u% m8 R0x50, /*idProduct = 0x5750*/5 X7 p7 p$ @4 z) Z4 d( n5 K
0x57,+ A/ p* `5 o0 B' v) K P2 h4 t2 r* V
//idVender字段。厂商ID号,我们这里取0x0483,仅供实验用。
- \5 t+ P) c4 g. S//实际产品不能随便使用厂商ID号,必须跟USB协会申请厂商ID号。1 z6 H9 p, a4 V6 p1 h
//注意小端模式,低字节在先。
/ ]+ D; m/ k! o/ p//idProduct字段。产品ID号,我们这里取0x5750。4 `1 n! Q5 O/ G) O1 i( B, f; L
//注意小端模式,低字节应该在前。
& u. B1 c( K5 p8 {2 @
8 e: c$ d% n ?2 ~! N
' |1 C6 o* ~, o& s1 zconst u8 ConfigDescriptor[SIZ_CONFIG_DESC]是配置描述符,注意如下" C3 w# d" \. {2 N* o
- USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */* _1 R5 s/ D I ^
- 0x81, /* bEndpointAddress: Endpoint Address (IN) */: l% N# Z( |7 e" T2 G0 j
- 0x03, /* bmAttributes: Interrupt endpoint *// D* P- f* k- P2 `0 R7 I
- 0x02, /* wMaxPacketSize: 2 Bytes max */
2 B# [- ~- ]- e* J5 T8 n - 0x00,
% \. V+ D% B5 d$ h - 0x20, /* bInterval: Polling Interval (32 ms) */; \/ _( Y$ ^: }! q1 v. Z5 p
- /* 34 */! x# ~ U/ y4 |* f
- 0x07, /* bLength: Endpoint Descriptor size */
( a% T0 n9 L7 C5 ~ [ - USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */
. G. N: A4 `( @. {: y3 o1 ` - /* Endpoint descriptor type */1 R; S; y! J+ u. X5 m& C+ {
- 0x01, /* bEndpointAddress: */
4 } n) k$ h& ^4 K% Z5 Z& F9 I) I - /* Endpoint Address (OUT) */
( f3 `6 Y* P/ ~, V: l - 0x03, /* bmAttributes: Interrupt endpoint */* v. Z0 W% _$ j: l) s2 j0 M7 g( |
- 0x02, /* wMaxPacketSize: 2 Bytes max */
3 g5 C. k5 z/ y5 J8 { - 0x00,
! o/ m3 ?+ I/ d5 F( P" H( Z - 0x20, /* bInterval: Polling Interval (20 ms) */
复制代码 % c$ ^3 G5 v3 }' h
上面包含了“输入端点描述符”和“输出端点描述符”。& W+ m" D ?3 ~, H$ `
//wMaxPacketSize字段。该端点的最大包长。
$ j) J5 l8 S/ t6 ^//bInterval字段。端点查询的时间,
. ]6 ?: c) X' h m
+ }0 z6 l* O) N- H4 j8 \' T为了实现更高速的通信我们修改如下:6 Z1 N |$ h- T* R6 n3 g1 R2 u
- /******************** Descriptor of endpoint ********************/% @0 c7 S- X% K; H; z
- /* 27 */
7 Y6 t; c1 d9 B4 N- j; n. [ - 0x07, /*bLength: Endpoint Descriptor size*/! M( q* ]# _; q2 l2 C# S- Q5 l
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
: U6 e) ?0 R" K1 r6 |% ?/ e - 0x81, /*bEndpointAddress: Endpoint Address (IN)*/
- y; @8 c" `& I3 J7 P. G" k - 0x03, /*bmAttributes: Interrupt endpoint*/
' `, q4 b' C" I7 i7 x" J* Q9 i - 0x40, /*wMaxPacketSize: 64 Byte max */
4 C9 ^' v0 C3 U" M7 b! a+ S T9 L - 0x00,0 h) T8 ?" b2 y, m- T( W4 h
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
- ~% F/ O ^5 D+ G$ u$ M6 g/ D- y - /* 34 */
; U7 e( p" H; N6 `2 a2 _ - /******************** Descriptor of endpoint ********************/6 e c; A3 T/ B8 Y, @
- /* 27 */ }7 O# h$ f8 t: H! b3 Q+ r z
- 0x07, /*bLength: Endpoint Descriptor size*/4 u# \) n$ J6 }) I! P7 e8 K! l V
- USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
; u3 C. Q9 }) z- v; w& w - 0x01, /*bEndpointAddress: Endpoint Address (OUT)*/
3 V) u* h1 ?# \2 `) d$ z" z - 0x03, /*bmAttributes: Interrupt endpoint*/
" @2 n" N9 c6 g1 k/ b9 i2 N: @ - 0x40, /*wMaxPacketSize: 64 Byte max */
" W/ ~) d* B8 F6 N) P. J, p. N - 0x00,2 i& D/ m+ W1 M) {" B2 t# k
- 0x0A, /*bInterval: Polling Interval (10 ms)*/
0 K( z) B! i& m3 |
9 Z" R& {1 [' \3 \7 Z- const u8 ReportDescriptor[SIZ_REPORT_DESC]为HID专用的报告描述符,具体的大家就参考资料了,这里可以直接复制了。( X$ ~3 r; T5 m
- const u8 CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] =
) Z. I8 b# Z: j4 E6 n3 @+ ]9 E& k - {
( b6 `" _* _; u - 0x05, 0xFF, // USAGE_PAGE(User define)
3 Z& y7 d2 x$ x1 { - 0x09, 0xFF, // USAGE(User define)
9 e4 R# S* m S - 0xa1, 0x01, // COLLECTION (Application)3 q7 U$ X# P" W7 Q* V: Y. X! d
- 0x05, 0x01, // USAGE_PAGE(1)
4 L3 M* t; Q5 W7 w) K9 ~( F - 0x19, 0x00, // USAGE_MINIMUM(0)
/ ^- y3 X6 U8 [. H1 a - 0x29, 0xFF, // USAGE_MAXIMUM(255). m5 b" L# y/ `+ ~& x) V, S8 O
- 0x15, 0x00, // LOGICAL_MINIMUM (0)3 Z# x/ x& v2 M8 a. _9 B0 ?1 }
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)9 N& V3 Y2 w0 ^7 W+ ^/ ~0 S
- 0x75, 0x08, // REPORT_SIZE (8)
2 j% d& Q- [6 z/ ?9 o - 0x95, 0x40, // REPORT_COUNT (64)
1 z2 A3 x6 d( q# G+ c2 } - 0x81, 0x02, // INPUT (Data,Var,Abs)
+ T1 e* k0 l: i; B+ u - 0x05, 0x02, // USAGE_PAGE(2), ~. A! g+ v% S& n8 h8 f
- 0x19, 0x00, // USAGE_MINIMUM (0)6 G; N8 n, ]& u9 P3 l( x
- 0x29, 0xFF, // USAGE_MAXIMUM (255)
& s) q( _+ W: j9 M4 I - 0x15, 0x00, // LOGICAL_MINIMUM (0); m* i5 L# g" M! D
- 0x25, 0xFF, // LOGICAL_MAXIMUM (255)
9 \# c6 `1 W) J - 0x95, 0x08, // REPORT_COUNT (8). y$ P4 u) a% h9 V
- 0x75, 0x40, // REPORT_SIZE (64)
& W* x3 g- I$ }4 c: s/ i" _' X+ I - 0x91, 0x02, // OUTPUT (Data,Var,Abs)4 R) D7 g+ G+ y# P
- 0xc0 // END_COLLECTION
* k# {9 Z9 U8 W( W% M - }; /* ReportDescriptor */
( H( `2 t2 C' \0 [4 O1 a+ g - 8 B8 s9 W& W: N, o
- const u8 CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR]/ H# F6 B% m0 L6 ]
- const u8 StringProduct[SIZ_STRING_PRODUCT]) e$ n% _) w6 }! K1 n" P' c
- const u8 StringSerial[SIZ_STRING_SERIAL]
复制代码 + N6 T- H2 f; r0 D) f$ E: ^' t
分别是“厂商字符”、“产品字符”、“产品序列号”,这些将在USB HID设备加载的时候显示。但是这需要这些字符要求为Unicode编码,你需要将你要显示的字符先转为Unicode编码。最好大家还要根据各个数组的长度修改如下定义。
/ Z$ l) k; f8 F% S
8 i2 M" H5 M; E5 }1 h% w' `1 S' A! Y- m- #define CUSTOMHID_SIZ_REPORT_DESC 39
+ V) ^3 y, Z/ `8 @ - #define CUSTOMHID_SIZ_STRING_VENDOR 642 ] }4 D) ^/ F4 a% q9 b
- #define CUSTOMHID_SIZ_STRING_PRODUCT 28
' r9 G" x" ?. }1 y6 [, n! X* }, Y/ u - #define CUSTOMHID_SIZ_STRING_SERIAL 26
复制代码 1 Y$ _ p* t) _
三、打开hw_config.c文件,将那些没有的函数删除,只保留如下函数) f, }' T# @( d3 c
a) Set_System(void)- g5 Y6 G& V g: t/ v7 _( h+ y
b) void Set_USBClock(void) 2 g: L3 g% \4 V+ U5 g4 a
c) void USB_Interrupts_Config(void)
$ ?$ ^( Y/ ]. g6 E2 |' I9 n5 ]d) void USB_Cable_Config (FunctionalState NewState)
8 p, m0 g: H% s a; w特别要注意最后一个函数,其主要作用是控制USB的上拉电阻,让电脑检测USB设备是否连接的。
; L1 F- z' m! K" E! t+ X( N4 I( Z! x; {5 _9 l
/ u/ N- T5 s# \' g9 q; P
四、打开stm32f10x_it.c文件,把EXTI15_10_IRQHandler等中断内的代码删除。
0 @1 D0 k' h2 P+ g4 ~6 i! e& e打开usb_prop.c文件,修改如下:
% v& O! X: W3 G. | L2 o- void CustomHID_Reset(void)3 L/ u( h, I& Y* e. X4 C! W- Z
- {
' W# s: x6 L# w, W! h# G: _, T( ? - /* Set Joystick_DEVICE as not configured */) G/ I8 f7 o+ K" J- {: {. M
- pInformation->Current_Configuration = 0;! E6 I/ R5 k1 y+ d U
- pInformation->Current_Interface = 0;/*the default Interface*/
: u K2 @% r ^* L - SetBTABLE(BTABLE_ADDRESS);' a6 {3 d9 [# B9 V9 _
1 P( X3 {8 |& o, S9 j- /* Initialize Endpoint 0 */
4 o7 `! ~5 W& c. p0 ]2 T' ~ - SetEPType(ENDP0, EP_CONTROL);& t8 j ^6 l( I6 Z' G
- SetEPTxStatus(ENDP0, EP_TX_STALL);4 u; [7 `' u& Y) n8 E _1 x0 p
- SetEPRxAddr(ENDP0, ENDP0_RXADDR);- S: V# z6 f- ^: A0 g
- SetEPTxAddr(ENDP0, ENDP0_TXADDR);
3 W) A2 J& T) \' A5 \- Q - Clear_Status_Out(ENDP0);& O& |! Y5 x% P8 {
- SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);& o! s$ d/ G; X5 O
- SetEPRxValid(ENDP0);
/ c# t$ f% f% @: v2 F& g# l
$ h8 u( P9 n1 q- S" d y: d- /* Initialize Endpoint 1 */9 v; H5 K9 c3 I8 R
- SetEPType(ENDP1, EP_INTERRUPT);6 A @' w7 E! z4 {) J
- SetEPTxAddr(ENDP1, ENDP1_TXADDR);
: E* T/ ~7 v. }& p5 f, K - SetEPTxCount(ENDP1, 64);" ^4 K- ^ [5 J5 Y$ y& o: u
- SetEPRxStatus(ENDP1, EP_RX_DIS);5 M: I; y8 M7 B, O. g& {
- SetEPTxStatus(ENDP1, EP_TX_NAK);
9 d6 d; w9 t5 v$ C3 {
j1 o/ z' Q' {- /* Initialize Endpoint 1 */8 W. o I2 I$ z3 T
- // SetEPType(ENDP1, EP_INTERRUPT);3 I. }% g- L. a+ Q/ P; u; {
- SetEPRxAddr(ENDP1, ENDP1_RXADDR);
- ?# X' G7 x: \; o t4 }8 |. q - SetEPRxCount(ENDP1, 64);
6 E; D! c: C. M - // SetEPTxStatus(ENDP1, EP_TX_DIS);9 Z1 m2 I F6 O& `+ y1 a
- SetEPRxStatus(ENDP1, EP_RX_VALID);2 t2 v ?: E$ h, ~; F. p9 M7 E
- /* Set this device to response on default address */
( r8 o z7 m; m7 k2 \7 C" ] - SetDeviceAddress(0);
5 u T( Z- L$ g$ r4 O- D, v - }
复制代码 8 @& D7 ~' x' e, p, @
五、usb_endp.c文件- a& {$ k4 x: B" N: k
- void EP1_OUT_Callback(void). e5 l" g1 [: H6 f) i3 |# Q
- {% z8 G6 ^6 P8 Q- U( J- ?( a1 F
- 这些写接收代码
, ^, _2 K6 r% V9 O$ U - }
复制代码
0 X* r+ n5 Q, q+ t3 a3 w六、数据发送和接收,举例说明
& F# B7 W$ [' S9 F1、数据接收/ r, m+ E5 b3 D: |
- u8 DataLen;
4 s1 |1 g3 K# q& J1 x& f2 |" M - DataLen = GetEPRxCount(ENDP1);
0 I) M7 y5 u& [% [9 S) N9 O- |; \ - PMAToUserBufferCopy(TX1_buffer, ENDP1_RXADDR, DataLen);
0 L% i% V7 C/ }5 ^% H - SetEPRxValid(ENDP1);* L" D& p; {) u- c3 C; ^/ ~$ j
- USART1_Send(DataLen);# Z, V0 X8 K0 Z: m( l
- count_out = 1;
复制代码
6 D* D0 P7 U( i& e) U- m& \2、数据发送+ L J. d' i: `+ b' z- [
- UserToPMABufferCopy(InBuffer, GetEPTxAddr(ENDP1), 64);
$ b3 D( A" x. g! q - SetEPTxCount(ENDP1, 64); 0 a) N: F, l: I- }4 j
- SetEPTxValid(ENDP1);
复制代码
% q% F" v6 S6 a% I( v: P4 x% j0 u如果你发送数据较为频繁,每次发送前应使用GetEPTxStatus(ENDP1)检测上次发送是否完成。如果端点状态处于EP_TX_VALID,说明发送未结束,如果端点状态处于EP_TX_NAK,说明发送结束。4 S, @5 U: u- |: X- ~
! {) G/ H+ s5 [: f
2 q2 N- R) f: g# B# U' K/ x+ X(by xidongs)( O8 O4 d r7 S0 f6 T5 C8 a
|